Merge remote-tracking branch 'origin/main' into pch/wasi_common_cap_std

This commit is contained in:
Pat Hickey
2020-12-14 16:17:10 -08:00
160 changed files with 7328 additions and 1466 deletions

View File

@@ -41,7 +41,6 @@ if (process.env.CENTOS !== undefined) {
let path = process.env.PATH;
path = `${path}:/rust/bin`;
path = `/opt/rh/devtoolset-8/root/usr/bin:${path}`;
path = `/opt/rh/rh-python36/root/usr/bin:${path}`;
// Spawn a container daemonized in the background which we'll connect to via
// `docker exec`. This'll have access to the current directory.
@@ -52,7 +51,7 @@ child_process.execFileSync('docker', [
'-v', `${process.cwd()}:${process.cwd()}`,
'-v', `${child_process.execSync('rustc --print sysroot').toString().trim()}:/rust:ro`,
'--env', `PATH=${path}`,
'centos:6',
'centos:7',
], stdio);
// Use ourselves to run future commands
@@ -63,7 +62,7 @@ const exec = s => {
child_process.execSync(`docker exec centos ${s}`, stdio);
};
exec('yum install -y centos-release-scl cmake xz epel-release');
exec('yum install -y rh-python36 patchelf unzip');
exec('yum install -y python3 patchelf unzip');
exec('yum install -y devtoolset-8-gcc devtoolset-8-binutils devtoolset-8-gcc-c++');
exec('yum install -y git');

View File

@@ -60,7 +60,10 @@ jobs:
# nightly-only feature right now.
- uses: ./.github/actions/install-rust
with:
toolchain: nightly
# TODO (rust-lang/rust#79661): We are seeing an internal compiler error when
# building with the latest (2020-12-06) nightly; pin on a slightly older
# version for now.
toolchain: nightly-2020-11-29
- run: cargo doc --no-deps --all --exclude wasmtime-cli --exclude test-programs --exclude cranelift-codegen-meta
- run: cargo doc --package cranelift-codegen-meta --document-private-items
- uses: actions/upload-artifact@v1
@@ -145,7 +148,7 @@ jobs:
# flags to rustc.
- uses: ./.github/actions/install-rust
with:
toolchain: nightly
toolchain: nightly-2020-11-29
- run: cargo install cargo-fuzz --vers "^0.8"
- run: cargo fetch
working-directory: ./fuzz
@@ -202,7 +205,7 @@ jobs:
rust: beta
- build: nightly
os: ubuntu-latest
rust: nightly
rust: nightly-2020-11-29
- build: macos
os: macos-latest
rust: stable
@@ -292,7 +295,7 @@ jobs:
submodules: true
- uses: ./.github/actions/install-rust
with:
toolchain: nightly
toolchain: nightly-2020-11-29
- uses: ./.github/actions/define-llvm-env
# Install wasm32 targets in order to build various tests throughout the
@@ -303,7 +306,7 @@ jobs:
# Run the x64 CI script.
- run: ./ci/run-experimental-x64-ci.sh
env:
CARGO_VERSION: "+nightly"
CARGO_VERSION: "+nightly-2020-11-29"
RUST_BACKTRACE: 1
# Build and test the wasi-nn module.

192
Cargo.lock generated
View File

@@ -391,6 +391,16 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "cpp_demangle"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44919ecaf6f99e8e737bc239408931c9a01e9a6c74814fee8242dd2506b65390"
dependencies = [
"cfg-if 1.0.0",
"glob",
]
[[package]]
name = "cpu-time"
version = "1.0.0"
@@ -444,7 +454,7 @@ dependencies = [
"souper-ir",
"target-lexicon",
"thiserror",
"wast 27.0.0",
"wast 29.0.0",
]
[[package]]
@@ -512,6 +522,26 @@ dependencies = [
"thiserror",
]
[[package]]
name = "cranelift-jit"
version = "0.68.0"
dependencies = [
"anyhow",
"cranelift",
"cranelift-codegen",
"cranelift-entity",
"cranelift-frontend",
"cranelift-module",
"cranelift-native",
"errno",
"libc",
"log",
"memmap",
"region",
"target-lexicon",
"winapi",
]
[[package]]
name = "cranelift-module"
version = "0.68.0"
@@ -577,25 +607,6 @@ dependencies = [
"serde_json",
]
[[package]]
name = "cranelift-simplejit"
version = "0.68.0"
dependencies = [
"cranelift",
"cranelift-codegen",
"cranelift-entity",
"cranelift-frontend",
"cranelift-module",
"cranelift-native",
"errno",
"libc",
"log",
"memmap",
"region",
"target-lexicon",
"winapi",
]
[[package]]
name = "cranelift-tools"
version = "0.66.0"
@@ -609,13 +620,13 @@ dependencies = [
"cranelift-filetests",
"cranelift-frontend",
"cranelift-interpreter",
"cranelift-jit",
"cranelift-module",
"cranelift-native",
"cranelift-object",
"cranelift-preopt",
"cranelift-reader",
"cranelift-serde",
"cranelift-simplejit",
"cranelift-wasm",
"file-per-thread-logger",
"filecheck",
@@ -646,7 +657,7 @@ dependencies = [
"smallvec",
"target-lexicon",
"thiserror",
"wasmparser 0.68.0",
"wasmparser 0.70.0",
"wat",
]
@@ -816,6 +827,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "downcast-rs"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]]
name = "dynasm"
version = "1.0.0"
@@ -1226,7 +1243,7 @@ dependencies = [
"smallvec",
"thiserror",
"typemap",
"wasmparser 0.68.0",
"wasmparser 0.70.0",
"wat",
]
@@ -1304,6 +1321,12 @@ dependencies = [
"autocfg",
]
[[package]]
name = "memory_units"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d96e3f3c0b6325d8ccd83c33b28acb183edcb6c67938ba104ec546854b0882"
[[package]]
name = "miniz_oxide"
version = "0.4.3"
@@ -1330,6 +1353,17 @@ dependencies = [
"version_check",
]
[[package]]
name = "num-bigint"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.44"
@@ -1340,6 +1374,18 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
dependencies = [
"autocfg",
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
@@ -1418,6 +1464,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "parity-wasm"
version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddfc878dac00da22f8f61e7af3157988424567ab01d9920b962ef7dcbd7cd865"
[[package]]
name = "peeking_take_while"
version = "0.1.2"
@@ -1435,7 +1487,7 @@ dependencies = [
"peepmatic-test-operator",
"peepmatic-traits",
"serde",
"wast 27.0.0",
"wast 29.0.0",
"z3",
]
@@ -1463,7 +1515,7 @@ dependencies = [
"peepmatic-traits",
"rand",
"serde",
"wast 27.0.0",
"wast 29.0.0",
]
[[package]]
@@ -1488,7 +1540,7 @@ dependencies = [
"serde",
"serde_test",
"thiserror",
"wast 27.0.0",
"wast 29.0.0",
]
[[package]]
@@ -1500,7 +1552,7 @@ dependencies = [
"peepmatic",
"peepmatic-test-operator",
"souper-ir",
"wast 27.0.0",
"wast 29.0.0",
]
[[package]]
@@ -1521,7 +1573,7 @@ version = "0.68.0"
dependencies = [
"peepmatic-traits",
"serde",
"wast 27.0.0",
"wast 29.0.0",
]
[[package]]
@@ -2027,9 +2079,9 @@ checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252"
[[package]]
name = "souper-ir"
version = "1.0.0"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "163cc2bdd8a66cbaccdf06a6b476689a97e928883e09bffbe06fd5945842a83f"
checksum = "a50c18ce33988e1973003afbaa66e6a465ad7a614dc33f246879ccc209c2c044"
dependencies = [
"id-arena",
]
@@ -2434,13 +2486,47 @@ dependencies = [
]
[[package]]
name = "wasm-smith"
version = "0.1.10"
name = "wasm-encoder"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd5e4720bb44dc5e46a917139dc9dfa4bb6fee023bf9f77d2c55ec12eeaf9930"
checksum = "ed89eaf99e08b84f96e477a16588a07dd3b51dc5f07291c3706782f62a10a5e1"
dependencies = [
"leb128",
]
[[package]]
name = "wasm-smith"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "509904d9c4c4659ac238a3f27c3656dd6d3931697eddd4b0f32e335769c298d0"
dependencies = [
"arbitrary",
"leb128",
"wasm-encoder",
]
[[package]]
name = "wasmi"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bb6825d9b2147105789adb4c2d84b9b568719713f3ac39618b637b4dafc86c4"
dependencies = [
"downcast-rs",
"libc",
"memory_units",
"num-rational",
"num-traits",
"parity-wasm",
"wasmi-validation",
]
[[package]]
name = "wasmi-validation"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea78c597064ba73596099281e2f4cfc019075122a65cdda3205af94f0b264d93"
dependencies = [
"parity-wasm",
]
[[package]]
@@ -2451,18 +2537,18 @@ checksum = "32fddd575d477c6e9702484139cf9f23dcd554b06d185ed0f56c857dd3a47aa6"
[[package]]
name = "wasmparser"
version = "0.68.0"
version = "0.70.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29a00e14eed9c2ecbbdbdd4fb284f49d21b6808965de24769a6379a13ec47d4c"
checksum = "ed1b3f9e9cf01a580b9f3281214dfdb1922b5dfb8494ee312ca03ae10036c2a2"
[[package]]
name = "wasmprinter"
version = "0.2.15"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f39a73b5f09cfcb1b568b61968d39b19e4ddec9b49040cfc091adf3b0788bca6"
checksum = "f89b2b24dce17e27fe9c09c28331cbd77067fcde5c6ea2508ac84bcbd5d3e018"
dependencies = [
"anyhow",
"wasmparser 0.68.0",
"wasmparser 0.70.0",
]
[[package]]
@@ -2473,6 +2559,8 @@ dependencies = [
"backtrace",
"bincode",
"cfg-if 1.0.0",
"cpp_demangle",
"indexmap",
"libc",
"log",
"region",
@@ -2481,7 +2569,7 @@ dependencies = [
"smallvec",
"target-lexicon",
"tempfile",
"wasmparser 0.68.0",
"wasmparser 0.70.0",
"wasmtime-cache",
"wasmtime-environ",
"wasmtime-jit",
@@ -2559,7 +2647,7 @@ dependencies = [
"test-programs",
"tracing-subscriber",
"wasi-common",
"wasmparser 0.68.0",
"wasmparser 0.70.0",
"wasmtime",
"wasmtime-cache",
"wasmtime-debug",
@@ -2595,7 +2683,7 @@ dependencies = [
"object",
"target-lexicon",
"thiserror",
"wasmparser 0.68.0",
"wasmparser 0.70.0",
"wasmtime-environ",
]
@@ -2614,7 +2702,7 @@ dependencies = [
"more-asserts",
"serde",
"thiserror",
"wasmparser 0.68.0",
"wasmparser 0.70.0",
]
[[package]]
@@ -2642,7 +2730,8 @@ dependencies = [
"log",
"rayon",
"wasm-smith",
"wasmparser 0.68.0",
"wasmi",
"wasmparser 0.70.0",
"wasmprinter",
"wasmtime",
"wasmtime-wast",
@@ -2653,6 +2742,7 @@ dependencies = [
name = "wasmtime-jit"
version = "0.21.0"
dependencies = [
"addr2line",
"anyhow",
"cfg-if 1.0.0",
"cranelift-codegen",
@@ -2669,7 +2759,7 @@ dependencies = [
"serde",
"target-lexicon",
"thiserror",
"wasmparser 0.68.0",
"wasmparser 0.70.0",
"wasmtime-cranelift",
"wasmtime-debug",
"wasmtime-environ",
@@ -2686,7 +2776,7 @@ version = "0.21.0"
dependencies = [
"cranelift-codegen",
"lightbeam",
"wasmparser 0.68.0",
"wasmparser 0.70.0",
"wasmtime-environ",
]
@@ -2793,7 +2883,7 @@ version = "0.21.0"
dependencies = [
"anyhow",
"wasmtime",
"wast 27.0.0",
"wast 29.0.0",
]
[[package]]
@@ -2829,20 +2919,20 @@ dependencies = [
[[package]]
name = "wast"
version = "27.0.0"
version = "29.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2c3ef5f6a72dffa44c24d5811123f704e18a1dbc83637d347b1852b41d3835c"
checksum = "dcf2268937131d63c3d833242bf5e075406f9ed868b4265f3280e15dac29ac18"
dependencies = [
"leb128",
]
[[package]]
name = "wat"
version = "1.0.28"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "835cf59c907f67e2bbc20f50157e08f35006fe2a8444d8ec9f5683e22f937045"
checksum = "0d11a88d953b298172d218d18f22853f4e6e12873b62755d05617b864d312c68"
dependencies = [
"wast 27.0.0",
"wast 29.0.0",
]
[[package]]

View File

@@ -38,12 +38,12 @@ anyhow = "1.0.19"
target-lexicon = { version = "0.11.0", default-features = false }
pretty_env_logger = "0.4.0"
file-per-thread-logger = "0.1.1"
wat = "1.0.27"
wat = "1.0.30"
libc = "0.2.60"
log = "0.4.8"
rayon = "1.2.1"
humantime = "2.0.0"
wasmparser = "0.68"
wasmparser = "0.70.0"
[dev-dependencies]
env_logger = "0.8.1"

View File

@@ -31,6 +31,7 @@ fn main() -> anyhow::Result<()> {
test_directory_module(out, "tests/misc_testsuite/bulk-memory-operations", strategy)?;
test_directory_module(out, "tests/misc_testsuite/reference-types", strategy)?;
test_directory_module(out, "tests/misc_testsuite/multi-memory", strategy)?;
test_directory_module(out, "tests/misc_testsuite/module-linking", strategy)?;
Ok(())
})?;
@@ -174,46 +175,10 @@ fn write_testsuite_tests(
/// For experimental_x64 backend features that are not supported yet, mark tests as panicking, so
/// they stop "passing" once the features are properly implemented.
fn experimental_x64_should_panic(testsuite: &str, testname: &str, strategy: &str) -> bool {
if !cfg!(feature = "experimental_x64") || strategy != "Cranelift" {
return false;
}
match (testsuite, testname) {
("simd", "simd_address") => return false,
("simd", "simd_bitwise") => return false,
("simd", "simd_bit_shift") => return false,
("simd", "simd_boolean") => return false,
("simd", "simd_const") => return false,
("simd", "simd_i8x16_arith") => return false,
("simd", "simd_i8x16_arith2") => return false,
("simd", "simd_i8x16_cmp") => return false,
("simd", "simd_i8x16_sat_arith") => return false,
("simd", "simd_i16x8_arith") => return false,
("simd", "simd_i16x8_arith2") => return false,
("simd", "simd_i16x8_cmp") => return false,
("simd", "simd_i16x8_sat_arith") => return false,
("simd", "simd_i32x4_arith") => return false,
("simd", "simd_i32x4_arith2") => return false,
("simd", "simd_i32x4_cmp") => return false,
("simd", "simd_i64x2_arith") => return false,
("simd", "simd_f32x4") => return false,
("simd", "simd_f32x4_arith") => return false,
("simd", "simd_f32x4_cmp") => return false,
("simd", "simd_f32x4_pmin_pmax") => return false,
("simd", "simd_f64x2") => return false,
("simd", "simd_f64x2_arith") => return false,
("simd", "simd_f64x2_cmp") => return false,
("simd", "simd_f64x2_pmin_pmax") => return false,
("simd", "simd_lane") => return false,
("simd", "simd_load") => return false,
("simd", "simd_load_splat") => return false,
("simd", "simd_splat") => return false,
("simd", "simd_store") => return false,
("simd", _) => return true,
_ => {}
}
///
/// TODO(#2470): removed all tests from this set as we are disabling x64 SIMD tests unconditionally
/// instead until we resolve a nondeterminism bug. Restore when fixed.
fn experimental_x64_should_panic(_testsuite: &str, _testname: &str, _strategy: &str) -> bool {
false
}
@@ -235,21 +200,22 @@ fn ignore(testsuite: &str, testname: &str, strategy: &str) -> bool {
return env::var("CARGO_CFG_TARGET_ARCH").unwrap() != "x86_64";
}
// Ignore all x64 SIMD tests for now (#2470).
("simd", _) if cfg!(feature = "experimental_x64") => {
return env::var("CARGO_CFG_TARGET_ARCH").unwrap() == "x86_64";
}
// These are only implemented on aarch64 and x64.
("simd", "simd_boolean")
| ("simd", "simd_f32x4_pmin_pmax")
| ("simd", "simd_f64x2_pmin_pmax") => {
| ("simd", "simd_f64x2_pmin_pmax")
| ("simd", "simd_f32x4_rounding")
| ("simd", "simd_f64x2_rounding")
| ("simd", "simd_i32x4_dot_i16x8") => {
return !(cfg!(feature = "experimental_x64")
|| env::var("CARGO_CFG_TARGET_ARCH").unwrap() == "aarch64")
}
// These are only implemented on aarch64.
("simd", "simd_f32x4_rounding") | ("simd", "simd_f64x2_rounding") => {
return env::var("CARGO_CFG_TARGET_ARCH").unwrap() != "aarch64";
}
// These tests have simd operators which aren't implemented yet.
// (currently none)
_ => {}
},
_ => panic!("unrecognized strategy"),

View File

@@ -7,7 +7,16 @@
# executed with the Wasmtime CLI.
set -e
WASMTIME_DIR=$(dirname "$0" | xargs dirname)
FIXTURE=https://gist.github.com/abrown/c7847bf3701f9efbb2070da1878542c1/raw/07a9f163994b0ff8f0d7c5a5c9645ec3d8b24024
FIXTURE=https://github.com/intel/openvino-rs/raw/main/crates/openvino/tests/fixtures/alexnet
if [ -z "${1+x}" ]; then
# If no temporary directory is specified, create one.
TMP_DIR=$(mktemp -d -t ci-XXXXXXXXXX)
REMOVE_TMP_DIR=1
else
# If a directory was specified, use it and avoid removing it.
TMP_DIR=$(realpath $1)
REMOVE_TMP_DIR=0
fi
# Inform the environment of OpenVINO library locations. Then we use OPENVINO_INSTALL_DIR below to avoid building all of
# OpenVINO from source (quite slow).
@@ -17,10 +26,9 @@ source /opt/intel/openvino/bin/setupvars.sh
OPENVINO_INSTALL_DIR=/opt/intel/openvino cargo build -p wasmtime-cli --features wasi-nn
# Download all necessary test fixtures to the temporary directory.
TMP_DIR=${1:-$(mktemp -d -t ci-XXXXXXXXXX)}
wget --no-clobber --directory-prefix=$TMP_DIR $FIXTURE/frozen_inference_graph.bin
wget --no-clobber --directory-prefix=$TMP_DIR $FIXTURE/frozen_inference_graph.xml
wget --no-clobber --directory-prefix=$TMP_DIR $FIXTURE/tensor-1x3x300x300-f32.bgr
wget --no-clobber --directory-prefix=$TMP_DIR $FIXTURE/alexnet.bin
wget --no-clobber --directory-prefix=$TMP_DIR $FIXTURE/alexnet.xml
wget --no-clobber --directory-prefix=$TMP_DIR $FIXTURE/tensor-1x3x227x227-f32.bgr
# Now build an example that uses the wasi-nn API.
pushd $WASMTIME_DIR/crates/wasi-nn/examples/classification-example
@@ -31,5 +39,7 @@ popd
# Run the example in Wasmtime (note that the example uses `fixture` as the expected location of the model/tensor files).
OPENVINO_INSTALL_DIR=/opt/intel/openvino cargo run --features wasi-nn -- run --mapdir fixture::$TMP_DIR $TMP_DIR/wasi-nn-example.wasm
# Clean up.
rm -rf $TMP_DIR
# Clean up the temporary directory only if it was not specified (users may want to keep the directory around).
if [[ $REMOVE_TMP_DIR -eq 1 ]]; then
rm -rf $TMP_DIR
fi

View File

@@ -26,7 +26,7 @@ cranelift-native = { path = "native", version = "0.68.0" }
cranelift-filetests = { path = "filetests", version = "0.66.0" }
cranelift-module = { path = "module", version = "0.68.0" }
cranelift-object = { path = "object", version = "0.68.0" }
cranelift-simplejit = { path = "simplejit", version = "0.68.0" }
cranelift-jit = { path = "jit", version = "0.68.0" }
cranelift-preopt = { path = "preopt", version = "0.68.0" }
cranelift = { path = "umbrella", version = "0.68.0" }
filecheck = "0.5.0"

View File

@@ -16,10 +16,10 @@ into executable machine code.
For more information, see [the documentation](docs/index.md).
For an example of how to use the JIT, see the [SimpleJIT Demo], which
For an example of how to use the JIT, see the [JIT Demo], which
implements a toy language.
[SimpleJIT Demo]: https://github.com/bytecodealliance/simplejit-demo
[JIT Demo]: https://github.com/bytecodealliance/simplejit-demo
For an example of how to use Cranelift to run WebAssembly code, see
[Wasmtime], which implements a standalone, embeddable, VM using Cranelift.

View File

@@ -29,8 +29,8 @@ peepmatic = { path = "../peepmatic", optional = true, version = "0.68.0" }
peepmatic-traits = { path = "../peepmatic/crates/traits", optional = true, version = "0.68.0" }
peepmatic-runtime = { path = "../peepmatic/crates/runtime", optional = true, version = "0.68.0" }
regalloc = { version = "0.0.31" }
souper-ir = { version = "1", optional = true }
wast = { version = "27.0.0", optional = true }
souper-ir = { version = "2.1.0", optional = true }
wast = { version = "29.0.0", optional = true }
# It is a goal of the cranelift-codegen crate to have minimal external dependencies.
# Please don't add any unless they are essential to the task of creating binary
# machine code. Integration tests that need external dependencies can be

View File

@@ -549,10 +549,13 @@ fn define_moves(e: &mut PerCpuModeEncodings, shared_defs: &SharedDefinitions, r:
}
e.enc64(bconst.bind(B64), rec_pu_id_bool.opcodes(&MOV_IMM).rex());
// You may expect that i8 encodings would use 0x30 (XORB) to indicate that encodings should be
// on 8-bit operands (f.ex "xor %al, %al"). Cranelift currently does not know when it can
// safely drop the 0x66 prefix, so we explicitly select a wider but permissible opcode.
let is_zero_int = InstructionPredicate::new_is_zero_int(&formats.unary_imm, "imm");
e.enc_both_instp(
iconst.bind(I8),
rec_u_id_z.opcodes(&XORB),
rec_u_id_z.opcodes(&XOR),
is_zero_int.clone(),
);

View File

@@ -711,9 +711,6 @@ pub static XOR_IMM8_SIGN_EXTEND: [u8; 1] = [0x83];
/// r/m{16,32,64} XOR register of the same size.
pub static XOR: [u8; 1] = [0x31];
/// r/m8 XOR r8.
pub static XORB: [u8; 1] = [0x30];
/// Bitwise logical XOR of packed double-precision floating-point values.
pub static XORPD: [u8; 3] = [0x66, 0x0f, 0x57];

View File

@@ -7,8 +7,9 @@ use crate::binemit::CodeOffset;
use crate::entity::{PrimaryMap, SecondaryMap};
use crate::ir;
use crate::ir::{
Block, ExtFuncData, FuncRef, GlobalValue, GlobalValueData, Heap, HeapData, Inst, JumpTable,
JumpTableData, Opcode, SigRef, StackSlot, StackSlotData, Table, TableData,
instructions::BranchInfo, Block, ExtFuncData, FuncRef, GlobalValue, GlobalValueData, Heap,
HeapData, Inst, InstructionData, JumpTable, JumpTableData, Opcode, SigRef, StackSlot,
StackSlotData, Table, TableData,
};
use crate::ir::{BlockOffsets, InstEncodings, SourceLocs, StackSlots, ValueLocations};
use crate::ir::{DataFlowGraph, ExternalName, Layout, Signature};
@@ -270,6 +271,8 @@ impl Function {
/// Changes the destination of a jump or branch instruction.
/// Does nothing if called with a non-jump or non-branch instruction.
///
/// Note that this method ignores multi-destination branches like `br_table`.
pub fn change_branch_destination(&mut self, inst: Inst, new_dest: Block) {
match self.dfg[inst].branch_destination_mut() {
None => (),
@@ -277,6 +280,43 @@ impl Function {
}
}
/// Rewrite the branch destination to `new_dest` if the destination matches `old_dest`.
/// Does nothing if called with a non-jump or non-branch instruction.
///
/// Unlike [change_branch_destination](Function::change_branch_destination), this method rewrite the destinations of
/// multi-destination branches like `br_table`.
pub fn rewrite_branch_destination(&mut self, inst: Inst, old_dest: Block, new_dest: Block) {
match self.dfg.analyze_branch(inst) {
BranchInfo::SingleDest(dest, ..) => {
if dest == old_dest {
self.change_branch_destination(inst, new_dest);
}
}
BranchInfo::Table(table, default_dest) => {
self.jump_tables[table].iter_mut().for_each(|entry| {
if *entry == old_dest {
*entry = new_dest;
}
});
if default_dest == Some(old_dest) {
match &mut self.dfg[inst] {
InstructionData::BranchTable { destination, .. } => {
*destination = new_dest;
}
_ => panic!(
"Unexpected instruction {} having default destination",
self.dfg.display_inst(inst, None)
),
}
}
}
BranchInfo::NotABranch => {}
}
}
/// Checks that the specified block can be encoded as a basic block.
///
/// On error, returns the first invalid instruction and an error message.

View File

@@ -277,7 +277,7 @@ impl InstructionData {
ref mut destination,
..
} => Some(destination),
Self::BranchTable { .. } => None,
Self::BranchTable { .. } | Self::IndirectJump { .. } => None,
_ => {
debug_assert!(!self.opcode().is_branch());
None

View File

@@ -63,6 +63,7 @@ pub enum LibCall {
/// Elf __tls_get_addr
ElfTlsGetAddr,
// When adding a new variant make sure to add it to `all_libcalls` too.
}
impl fmt::Display for LibCall {
@@ -136,6 +137,33 @@ impl LibCall {
_ => return None,
})
}
/// Get a list of all known `LibCall`'s.
pub fn all_libcalls() -> &'static [LibCall] {
use LibCall::*;
&[
Probestack,
UdivI64,
SdivI64,
UremI64,
SremI64,
IshlI64,
UshrI64,
SshrI64,
CeilF32,
CeilF64,
FloorF32,
FloorF64,
TruncF32,
TruncF64,
NearestF32,
NearestF64,
Memcpy,
Memset,
Memmove,
ElfTlsGetAddr,
]
}
}
/// Get a function reference for `libcall` in `func`, following the signature

View File

@@ -6,15 +6,31 @@ enum FlagBit {
Notrap,
Aligned,
Readonly,
LittleEndian,
BigEndian,
}
const NAMES: [&str; 3] = ["notrap", "aligned", "readonly"];
const NAMES: [&str; 5] = ["notrap", "aligned", "readonly", "little", "big"];
/// Endianness of a memory access.
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub enum Endianness {
/// Little-endian
Little,
/// Big-endian
Big,
}
/// Flags for memory operations like load/store.
///
/// Each of these flags introduce a limited form of undefined behavior. The flags each enable
/// certain optimizations that need to make additional assumptions. Generally, the semantics of a
/// program does not change when a flag is removed, but adding a flag will.
///
/// In addition, the flags determine the endianness of the memory access. By default,
/// any memory access uses the native endianness determined by the target ISA. This can
/// be overridden for individual accesses by explicitly specifying little- or big-endian
/// semantics via the flags.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub struct MemFlags {
bits: u8,
@@ -48,16 +64,48 @@ impl MemFlags {
/// Set a flag bit by name.
///
/// Returns true if the flag was found and set, false for an unknown flag name.
/// Will also return false when trying to set inconsistent endianness flags.
pub fn set_by_name(&mut self, name: &str) -> bool {
match NAMES.iter().position(|&s| s == name) {
Some(bit) => {
self.bits |= 1 << bit;
true
let bits = self.bits | 1 << bit;
if (bits & (1 << FlagBit::LittleEndian as usize)) != 0
&& (bits & (1 << FlagBit::BigEndian as usize)) != 0
{
false
} else {
self.bits = bits;
true
}
}
None => false,
}
}
/// Return endianness of the memory access. This will return the endianness
/// explicitly specified by the flags if any, and will default to the native
/// endianness otherwise. The native endianness has to be provided by the
/// caller since it is not explicitly encoded in CLIF IR -- this allows a
/// front end to create IR without having to know the target endianness.
pub fn endianness(self, native_endianness: Endianness) -> Endianness {
if self.read(FlagBit::LittleEndian) {
Endianness::Little
} else if self.read(FlagBit::BigEndian) {
Endianness::Big
} else {
native_endianness
}
}
/// Set endianness of the memory access.
pub fn set_endianness(&mut self, endianness: Endianness) {
match endianness {
Endianness::Little => self.set(FlagBit::LittleEndian),
Endianness::Big => self.set(FlagBit::BigEndian),
};
assert!(!(self.read(FlagBit::LittleEndian) && self.read(FlagBit::BigEndian)));
}
/// Test if the `notrap` flag is set.
///
/// Normally, trapping is part of the semantics of a load/store operation. If the platform

View File

@@ -50,7 +50,7 @@ pub use crate::ir::instructions::{
pub use crate::ir::jumptable::JumpTableData;
pub use crate::ir::layout::Layout;
pub use crate::ir::libcall::{get_probestack_funcref, LibCall};
pub use crate::ir::memflags::MemFlags;
pub use crate::ir::memflags::{Endianness, MemFlags};
pub use crate::ir::progpoint::{ExpandedProgramPoint, ProgramOrder, ProgramPoint};
pub use crate::ir::sourceloc::SourceLoc;
pub use crate::ir::stackslot::{StackLayoutInfo, StackSlotData, StackSlotKind, StackSlots};

View File

@@ -12,7 +12,7 @@ use crate::{CodegenError, CodegenResult};
use alloc::boxed::Box;
use alloc::vec::Vec;
use regalloc::{RealReg, Reg, RegClass, Set, Writable};
use smallvec::SmallVec;
use smallvec::{smallvec, SmallVec};
// We use a generic implementation that factors out AArch64 and x64 ABI commonalities, because
// these ABIs are very similar.
@@ -508,6 +508,12 @@ impl ABIMachineSpec for AArch64MachineDeps {
insts
}
fn gen_probestack(_: u32) -> SmallVec<[Self::I; 2]> {
// TODO: implement if we ever require stack probes on an AArch64 host
// (unlikely unless Lucet is ported)
smallvec![]
}
// Returns stack bytes used as well as instructions. Does not adjust
// nominal SP offset; abi_impl generic code will do that.
fn gen_clobber_save(

View File

@@ -1312,6 +1312,13 @@ impl MachInstEmit for Inst {
| machreg_to_vec(rd.to_reg()),
);
}
&Inst::FpuExtend { rd, rn, size } => {
sink.put4(enc_fpurr(
0b000_11110_00_1_000000_10000 | (size.ftype() << 13),
rd,
rn,
));
}
&Inst::FpuRR { fpu_op, rd, rn } => {
let top22 = match fpu_op {
FPUOp1::Abs32 => 0b000_11110_00_1_000001_10000,
@@ -1746,6 +1753,17 @@ impl MachInstEmit for Inst {
| machreg_to_vec(rd.to_reg()),
);
}
&Inst::VecDupFPImm { rd, imm, size } => {
let imm = imm.enc_bits();
let op = match size.lane_size() {
ScalarSize::Size32 => 0,
ScalarSize::Size64 => 1,
_ => unimplemented!(),
};
let q_op = op | ((size.is_128bits() as u32) << 1);
sink.put4(enc_asimd_mod_imm(rd, q_op, 0b1111, imm));
}
&Inst::VecDupImm {
rd,
imm,

View File

@@ -2072,6 +2072,24 @@ fn test_aarch64_binemit() {
"5205084E",
"dup v18.2d, v10.d[0]",
));
insns.push((
Inst::VecDupFPImm {
rd: writable_vreg(31),
imm: ASIMDFPModImm::maybe_from_u64(1_f32.to_bits() as u64, ScalarSize::Size32).unwrap(),
size: VectorSize::Size32x2,
},
"1FF6030F",
"fmov v31.2s, #1",
));
insns.push((
Inst::VecDupFPImm {
rd: writable_vreg(0),
imm: ASIMDFPModImm::maybe_from_u64(2_f64.to_bits(), ScalarSize::Size64).unwrap(),
size: VectorSize::Size64x2,
},
"00F4006F",
"fmov v0.2d, #2",
));
insns.push((
Inst::VecDupImm {
rd: writable_vreg(31),
@@ -2082,16 +2100,96 @@ fn test_aarch64_binemit() {
"FFE7074F",
"movi v31.16b, #255",
));
insns.push((
Inst::VecDupImm {
rd: writable_vreg(30),
imm: ASIMDMovModImm::maybe_from_u64(0, ScalarSize::Size16).unwrap(),
invert: false,
size: VectorSize::Size16x8,
},
"1E84004F",
"movi v30.8h, #0",
));
insns.push((
Inst::VecDupImm {
rd: writable_vreg(0),
imm: ASIMDMovModImm::zero(),
imm: ASIMDMovModImm::zero(ScalarSize::Size16),
invert: true,
size: VectorSize::Size16x4,
},
"0084002F",
"mvni v0.4h, #0",
));
insns.push((
Inst::VecDupImm {
rd: writable_vreg(0),
imm: ASIMDMovModImm::maybe_from_u64(256, ScalarSize::Size16).unwrap(),
invert: false,
size: VectorSize::Size16x8,
},
"20A4004F",
"movi v0.8h, #1, LSL #8",
));
insns.push((
Inst::VecDupImm {
rd: writable_vreg(8),
imm: ASIMDMovModImm::maybe_from_u64(2228223, ScalarSize::Size32).unwrap(),
invert: false,
size: VectorSize::Size32x4,
},
"28D4014F",
"movi v8.4s, #33, MSL #16",
));
insns.push((
Inst::VecDupImm {
rd: writable_vreg(16),
imm: ASIMDMovModImm::maybe_from_u64(35071, ScalarSize::Size32).unwrap(),
invert: true,
size: VectorSize::Size32x2,
},
"10C5042F",
"mvni v16.2s, #136, MSL #8",
));
insns.push((
Inst::VecDupImm {
rd: writable_vreg(1),
imm: ASIMDMovModImm::maybe_from_u64(0, ScalarSize::Size32).unwrap(),
invert: false,
size: VectorSize::Size32x2,
},
"0104000F",
"movi v1.2s, #0",
));
insns.push((
Inst::VecDupImm {
rd: writable_vreg(24),
imm: ASIMDMovModImm::maybe_from_u64(1107296256, ScalarSize::Size32).unwrap(),
invert: false,
size: VectorSize::Size32x4,
},
"5864024F",
"movi v24.4s, #66, LSL #24",
));
insns.push((
Inst::VecDupImm {
rd: writable_vreg(8),
imm: ASIMDMovModImm::zero(ScalarSize::Size64),
invert: false,
size: VectorSize::Size64x2,
},
"08E4006F",
"movi v8.2d, #0",
));
insns.push((
Inst::VecDupImm {
rd: writable_vreg(7),
imm: ASIMDMovModImm::maybe_from_u64(18374687574904995840, ScalarSize::Size64).unwrap(),
invert: false,
size: VectorSize::Size64x2,
},
"87E6046F",
"movi v7.2d, #18374687574904995840",
));
insns.push((
Inst::VecExtend {
t: VecExtendOp::Sxtl8,
@@ -4376,6 +4474,16 @@ fn test_aarch64_binemit() {
"mov d23, v11.d[0]",
));
insns.push((
Inst::FpuExtend {
rd: writable_vreg(31),
rn: vreg(0),
size: ScalarSize::Size32,
},
"1F40201E",
"fmov s31, s0",
));
insns.push((
Inst::FpuRR {
fpu_op: FPUOp1::Abs32,

View File

@@ -668,39 +668,208 @@ impl MoveWideConst {
}
/// Advanced SIMD modified immediate as used by MOVI/MVNI.
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ASIMDMovModImm {
imm: u8,
shift: u8,
is_64bit: bool,
shift_ones: bool,
}
impl ASIMDMovModImm {
/// Construct an ASIMDMovModImm from an arbitrary 64-bit constant, if possible.
/// Note that the bits in `value` outside of the range specified by `size` are
/// ignored; for example, in the case of `ScalarSize::Size8` all bits above the
/// lowest 8 are ignored.
pub fn maybe_from_u64(value: u64, size: ScalarSize) -> Option<ASIMDMovModImm> {
match size {
ScalarSize::Size8 => Some(ASIMDMovModImm {
imm: value as u8,
shift: 0,
is_64bit: false,
shift_ones: false,
}),
ScalarSize::Size16 => {
let value = value as u16;
if value >> 8 == 0 {
Some(ASIMDMovModImm {
imm: value as u8,
shift: 0,
is_64bit: false,
shift_ones: false,
})
} else if value as u8 == 0 {
Some(ASIMDMovModImm {
imm: (value >> 8) as u8,
shift: 8,
is_64bit: false,
shift_ones: false,
})
} else {
None
}
}
ScalarSize::Size32 => {
let value = value as u32;
// Value is of the form 0x00MMFFFF.
if value & 0xFF00FFFF == 0x0000FFFF {
let imm = (value >> 16) as u8;
Some(ASIMDMovModImm {
imm,
shift: 16,
is_64bit: false,
shift_ones: true,
})
// Value is of the form 0x0000MMFF.
} else if value & 0xFFFF00FF == 0x000000FF {
let imm = (value >> 8) as u8;
Some(ASIMDMovModImm {
imm,
shift: 8,
is_64bit: false,
shift_ones: true,
})
} else {
// Of the 4 bytes, at most one is non-zero.
for shift in (0..32).step_by(8) {
if value & (0xFF << shift) == value {
return Some(ASIMDMovModImm {
imm: (value >> shift) as u8,
shift,
is_64bit: false,
shift_ones: false,
});
}
}
None
}
}
ScalarSize::Size64 => {
let mut imm = 0u8;
// Check if all bytes are either 0 or 0xFF.
for i in 0..8 {
let b = (value >> (i * 8)) as u8;
if b == 0 || b == 0xFF {
imm |= (b & 1) << i;
} else {
return None;
}
}
Some(ASIMDMovModImm {
imm,
shift: 0,
is_64bit: true,
shift_ones: false,
})
}
_ => None,
}
}
/// Create a zero immediate of this format.
pub fn zero() -> Self {
pub fn zero(size: ScalarSize) -> Self {
ASIMDMovModImm {
imm: 0,
shift: 0,
is_64bit: size == ScalarSize::Size64,
shift_ones: false,
}
}
/// Returns the value that this immediate represents.
pub fn value(&self) -> (u8, u32, bool) {
(self.imm, self.shift as u32, self.shift_ones)
}
}
/// Advanced SIMD modified immediate as used by the vector variant of FMOV.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ASIMDFPModImm {
imm: u8,
is_64bit: bool,
}
impl ASIMDFPModImm {
/// Construct an ASIMDFPModImm from an arbitrary 64-bit constant, if possible.
pub fn maybe_from_u64(value: u64, size: ScalarSize) -> Option<ASIMDFPModImm> {
// In all cases immediates are encoded as an 8-bit number 0b_abcdefgh;
// let `D` be the inverse of the digit `d`.
match size {
ScalarSize::Size32 => {
// In this case the representable immediates are 32-bit numbers of the form
// 0b_aBbb_bbbc_defg_h000 shifted to the left by 16.
let value = value as u32;
let b0_5 = (value >> 19) & 0b111111;
let b6 = (value >> 19) & (1 << 6);
let b7 = (value >> 24) & (1 << 7);
let imm = (b0_5 | b6 | b7) as u8;
if value == Self::value32(imm) {
Some(ASIMDFPModImm {
imm,
is_64bit: false,
})
} else {
None
}
}
ScalarSize::Size64 => {
// In this case the representable immediates are 64-bit numbers of the form
// 0b_aBbb_bbbb_bbcd_efgh shifted to the left by 48.
let b0_5 = (value >> 48) & 0b111111;
let b6 = (value >> 48) & (1 << 6);
let b7 = (value >> 56) & (1 << 7);
let imm = (b0_5 | b6 | b7) as u8;
if value == Self::value64(imm) {
Some(ASIMDFPModImm {
imm,
is_64bit: true,
})
} else {
None
}
}
_ => None,
}
}
/// Returns bits ready for encoding.
pub fn enc_bits(&self) -> u8 {
self.imm
}
/// Returns the 32-bit value that corresponds to an 8-bit encoding.
fn value32(imm: u8) -> u32 {
let imm = imm as u32;
let b0_5 = imm & 0b111111;
let b6 = (imm >> 6) & 1;
let b6_inv = b6 ^ 1;
let b7 = (imm >> 7) & 1;
b0_5 << 19 | (b6 * 0b11111) << 25 | b6_inv << 30 | b7 << 31
}
/// Returns the 64-bit value that corresponds to an 8-bit encoding.
fn value64(imm: u8) -> u64 {
let imm = imm as u64;
let b0_5 = imm & 0b111111;
let b6 = (imm >> 6) & 1;
let b6_inv = b6 ^ 1;
let b7 = (imm >> 7) & 1;
b0_5 << 48 | (b6 * 0b11111111) << 54 | b6_inv << 62 | b7 << 63
}
}
impl PrettyPrint for NZCV {
fn show_rru(&self, _mb_rru: Option<&RealRegUniverse>) -> String {
let fmt = |c: char, v| if v { c.to_ascii_uppercase() } else { c };
@@ -782,7 +951,20 @@ impl PrettyPrint for MoveWideConst {
impl PrettyPrint for ASIMDMovModImm {
fn show_rru(&self, _mb_rru: Option<&RealRegUniverse>) -> String {
if self.shift == 0 {
if self.is_64bit {
debug_assert_eq!(self.shift, 0);
let enc_imm = self.imm as i8;
let mut imm = 0u64;
for i in 0..8 {
let b = (enc_imm >> i) & 1;
imm |= (-b as u8 as u64) << (i * 8);
}
format!("#{}", imm)
} else if self.shift == 0 {
format!("#{}", self.imm)
} else {
let shift_type = if self.shift_ones { "MSL" } else { "LSL" };
@@ -791,6 +973,16 @@ impl PrettyPrint for ASIMDMovModImm {
}
}
impl PrettyPrint for ASIMDFPModImm {
fn show_rru(&self, _mb_rru: Option<&RealRegUniverse>) -> String {
if self.is_64bit {
format!("#{}", f64::from_bits(Self::value64(self.imm)))
} else {
format!("#{}", f32::from_bits(Self::value32(self.imm)))
}
}
}
#[cfg(test)]
mod test {
use super::*;
@@ -1022,4 +1214,44 @@ mod test {
unreachable!();
}
}
#[test]
fn asimd_fp_mod_imm_test() {
assert_eq!(None, ASIMDFPModImm::maybe_from_u64(0, ScalarSize::Size32));
assert_eq!(
None,
ASIMDFPModImm::maybe_from_u64(0.013671875_f32.to_bits() as u64, ScalarSize::Size32)
);
assert_eq!(None, ASIMDFPModImm::maybe_from_u64(0, ScalarSize::Size64));
assert_eq!(
None,
ASIMDFPModImm::maybe_from_u64(10000_f64.to_bits(), ScalarSize::Size64)
);
}
#[test]
fn asimd_mov_mod_imm_test() {
assert_eq!(
None,
ASIMDMovModImm::maybe_from_u64(513, ScalarSize::Size16)
);
assert_eq!(
None,
ASIMDMovModImm::maybe_from_u64(4278190335, ScalarSize::Size32)
);
assert_eq!(
None,
ASIMDMovModImm::maybe_from_u64(8388608, ScalarSize::Size64)
);
assert_eq!(
Some(ASIMDMovModImm {
imm: 66,
shift: 16,
is_64bit: false,
shift_ones: true,
}),
ASIMDMovModImm::maybe_from_u64(4390911, ScalarSize::Size32)
);
}
}

View File

@@ -755,6 +755,13 @@ pub enum Inst {
size: VectorSize,
},
/// Zero-extend a SIMD & FP scalar to the full width of a vector register.
FpuExtend {
rd: Writable<Reg>,
rn: Reg,
size: ScalarSize,
},
/// 1-op FPU instruction.
FpuRR {
fpu_op: FPUOp1,
@@ -928,6 +935,13 @@ pub enum Inst {
size: VectorSize,
},
/// Duplicate FP immediate to vector.
VecDupFPImm {
rd: Writable<Reg>,
imm: ASIMDFPModImm,
size: VectorSize,
},
/// Duplicate immediate to vector.
VecDupImm {
rd: Writable<Reg>,
@@ -1295,12 +1309,15 @@ impl Inst {
value: u32,
mut alloc_tmp: F,
) -> SmallVec<[Inst; 4]> {
// Note that we must make sure that all bits outside the lowest 32 are set to 0
// because this function is also used to load wider constants (that have zeros
// in their most significant bits).
if value == 0 {
smallvec![Inst::VecDupImm {
rd,
imm: ASIMDMovModImm::zero(),
imm: ASIMDMovModImm::zero(ScalarSize::Size32),
invert: false,
size: VectorSize::Size8x8
size: VectorSize::Size32x2
}]
} else {
// TODO: use FMOV immediate form when `value` has sufficiently few mantissa/exponent
@@ -1324,6 +1341,9 @@ impl Inst {
const_data: u64,
mut alloc_tmp: F,
) -> SmallVec<[Inst; 4]> {
// Note that we must make sure that all bits outside the lowest 64 are set to 0
// because this function is also used to load wider constants (that have zeros
// in their most significant bits).
if let Ok(const_data) = u32::try_from(const_data) {
Inst::load_fp_constant32(rd, const_data, alloc_tmp)
// TODO: use FMOV immediate form when `const_data` has sufficiently few mantissa/exponent
@@ -1394,7 +1414,7 @@ impl Inst {
r
}
/// Create instructions that load a 128-bit vector constant consisting of elements with
/// Create instructions that load a vector constant consisting of elements with
/// the same value.
pub fn load_replicated_vector_pattern<F: FnMut(RegClass, Type) -> Writable<Reg>>(
rd: Writable<Reg>,
@@ -1403,6 +1423,15 @@ impl Inst {
mut alloc_tmp: F,
) -> SmallVec<[Inst; 5]> {
let lane_size = size.lane_size();
let widen_32_bit_pattern = |pattern, lane_size| {
if lane_size == ScalarSize::Size32 {
let pattern = pattern as u32 as u64;
ASIMDMovModImm::maybe_from_u64(pattern | (pattern << 32), ScalarSize::Size64)
} else {
None
}
};
if let Some(imm) = ASIMDMovModImm::maybe_from_u64(pattern, lane_size) {
smallvec![Inst::VecDupImm {
@@ -1421,6 +1450,27 @@ impl Inst {
invert: true,
size
}]
} else if let Some(imm) = widen_32_bit_pattern(pattern, lane_size) {
let mut insts = smallvec![Inst::VecDupImm {
rd,
imm,
invert: false,
size: VectorSize::Size64x2,
}];
// TODO: Implement support for 64-bit scalar MOVI; we zero-extend the
// lower 64 bits instead.
if !size.is_128bits() {
insts.push(Inst::FpuExtend {
rd,
rn: rd.to_reg(),
size: ScalarSize::Size64,
});
}
insts
} else if let Some(imm) = ASIMDFPModImm::maybe_from_u64(pattern, lane_size) {
smallvec![Inst::VecDupFPImm { rd, imm, size }]
} else {
let tmp = alloc_tmp(RegClass::I64, I64);
let mut insts = SmallVec::from(&Inst::load_constant(tmp, pattern)[..]);
@@ -1721,6 +1771,10 @@ fn aarch64_get_regs(inst: &Inst, collector: &mut RegUsageCollector) {
collector.add_def(rd);
collector.add_use(rn);
}
&Inst::FpuExtend { rd, rn, .. } => {
collector.add_def(rd);
collector.add_use(rn);
}
&Inst::FpuRR { rd, rn, .. } => {
collector.add_def(rd);
collector.add_use(rn);
@@ -1870,6 +1924,9 @@ fn aarch64_get_regs(inst: &Inst, collector: &mut RegUsageCollector) {
collector.add_def(rd);
collector.add_use(rn);
}
&Inst::VecDupFPImm { rd, .. } => {
collector.add_def(rd);
}
&Inst::VecDupImm { rd, .. } => {
collector.add_def(rd);
}
@@ -2299,6 +2356,14 @@ fn aarch64_map_regs<RUM: RegUsageMapper>(inst: &mut Inst, mapper: &RUM) {
map_def(mapper, rd);
map_use(mapper, rn);
}
&mut Inst::FpuExtend {
ref mut rd,
ref mut rn,
..
} => {
map_def(mapper, rd);
map_use(mapper, rn);
}
&mut Inst::FpuRR {
ref mut rd,
ref mut rn,
@@ -2582,6 +2647,9 @@ fn aarch64_map_regs<RUM: RegUsageMapper>(inst: &mut Inst, mapper: &RUM) {
map_def(mapper, rd);
map_use(mapper, rn);
}
&mut Inst::VecDupFPImm { ref mut rd, .. } => {
map_def(mapper, rd);
}
&mut Inst::VecDupImm { ref mut rd, .. } => {
map_def(mapper, rd);
}
@@ -3229,6 +3297,12 @@ impl Inst {
let rn = show_vreg_element(rn, mb_rru, idx, size);
format!("mov {}, {}", rd, rn)
}
&Inst::FpuExtend { rd, rn, size } => {
let rd = show_vreg_scalar(rd.to_reg(), mb_rru, size);
let rn = show_vreg_scalar(rn, mb_rru, size);
format!("fmov {}, {}", rd, rn)
}
&Inst::FpuRR { fpu_op, rd, rn } => {
let (op, sizesrc, sizedest) = match fpu_op {
FPUOp1::Abs32 => ("fabs", ScalarSize::Size32, ScalarSize::Size32),
@@ -3465,6 +3539,12 @@ impl Inst {
let rn = show_vreg_element(rn, mb_rru, 0, size);
format!("dup {}, {}", rd, rn)
}
&Inst::VecDupFPImm { rd, imm, size } => {
let imm = imm.show_rru(mb_rru);
let rd = show_vreg_vector(rd.to_reg(), mb_rru, size);
format!("fmov {}, {}", rd, imm)
}
&Inst::VecDupImm {
rd,
imm,

View File

@@ -616,7 +616,7 @@ fn collect_address_addends<C: LowerCtx<I = Inst>>(
maybe_input_insn(ctx, extendee_input, Opcode::Iconst),
extendop,
) {
let value = ctx.get_constant(insn).unwrap() as i64;
let value = (ctx.get_constant(insn).unwrap() & 0xFFFF_FFFF_u64) as i64;
offset += value;
} else {
let reg = put_input_in_reg(ctx, extendee_input, NarrowValueMode::None);
@@ -853,7 +853,7 @@ pub(crate) fn lower_constant_f128<C: LowerCtx<I = Inst>>(
// is potentially expensive.
ctx.emit(Inst::VecDupImm {
rd,
imm: ASIMDMovModImm::zero(),
imm: ASIMDMovModImm::zero(ScalarSize::Size8),
invert: false,
size: VectorSize::Size8x16,
});

View File

@@ -2075,8 +2075,6 @@ pub(crate) fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
// derivation of these sequences. Alternative sequences are discussed in
// https://github.com/bytecodealliance/wasmtime/issues/2296, although they are not
// used here.
// Also .. FIXME: when https://github.com/bytecodealliance/wasmtime/pull/2310 is
// merged, use `lower_splat_constant` instead to generate the constants.
let tmp_r0 = ctx.alloc_tmp(RegClass::I64, I64);
let tmp_v0 = ctx.alloc_tmp(RegClass::V128, I8X16);
let tmp_v1 = ctx.alloc_tmp(RegClass::V128, I8X16);
@@ -2100,12 +2098,7 @@ pub(crate) fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
size: VectorSize::Size8x16,
imm: 7,
});
lower_constant_u64(ctx, tmp_r0, 0x8040201008040201u64);
ctx.emit(Inst::VecDup {
rd: tmp_v0,
rn: tmp_r0.to_reg(),
size: VectorSize::Size64x2,
});
lower_splat_const(ctx, tmp_v0, 0x8040201008040201u64, VectorSize::Size64x2);
ctx.emit(Inst::VecRRR {
alu_op: VecALUOp::And,
rd: tmp_v1,

View File

@@ -10,7 +10,7 @@ use crate::{CodegenError, CodegenResult};
use alloc::boxed::Box;
use alloc::vec::Vec;
use regalloc::{RealReg, Reg, RegClass, Set, Writable};
use smallvec::SmallVec;
use smallvec::{smallvec, SmallVec};
/// Support for the ARM ABI from the callee side (within a function body).
pub(crate) type Arm32ABICallee = ABICalleeImpl<Arm32MachineDeps>;
@@ -305,6 +305,12 @@ impl ABIMachineSpec for Arm32MachineDeps {
ret
}
fn gen_probestack(_: u32) -> SmallVec<[Self::I; 2]> {
// TODO: implement if we ever require stack probes on ARM32 (unlikely
// unless Lucet is ported)
smallvec![]
}
/// Returns stack bytes used as well as instructions. Does not adjust
/// nominal SP offset; caller will do that.
fn gen_clobber_save(

View File

@@ -235,6 +235,14 @@ pub trait TargetIsa: fmt::Display + Send + Sync {
CallConv::triple_default(self.triple())
}
/// Get the endianness of this ISA.
fn endianness(&self) -> ir::Endianness {
match self.triple().endianness().unwrap() {
target_lexicon::Endianness::Little => ir::Endianness::Little,
target_lexicon::Endianness::Big => ir::Endianness::Big,
}
}
/// Get the pointer type of this ISA.
fn pointer_type(&self) -> ir::Type {
ir::Type::int(u16::from(self.pointer_bits())).unwrap()

View File

@@ -1,7 +1,7 @@
//! Implementation of the standard x64 ABI.
use crate::ir::types::*;
use crate::ir::{self, types, MemFlags, TrapCode, Type};
use crate::ir::{self, types, ExternalName, LibCall, MemFlags, Opcode, TrapCode, Type};
use crate::isa;
use crate::isa::{x64::inst::*, CallConv};
use crate::machinst::abi_impl::*;
@@ -389,6 +389,22 @@ impl ABIMachineSpec for X64ABIMachineSpec {
insts
}
fn gen_probestack(frame_size: u32) -> SmallVec<[Self::I; 2]> {
let mut insts = SmallVec::new();
insts.push(Inst::imm(
OperandSize::Size32,
frame_size as u64,
Writable::from_reg(regs::rax()),
));
insts.push(Inst::CallKnown {
dest: ExternalName::LibCall(LibCall::Probestack),
uses: vec![regs::rax()],
defs: vec![],
opcode: Opcode::Call,
});
insts
}
fn gen_clobber_save(
call_conv: isa::CallConv,
_: &settings::Flags,

View File

@@ -467,7 +467,10 @@ pub enum SseOpcode {
Pabsb,
Pabsw,
Pabsd,
Packssdw,
Packsswb,
Packusdw,
Packuswb,
Paddb,
Paddd,
Paddq,
@@ -476,6 +479,7 @@ pub enum SseOpcode {
Paddsw,
Paddusb,
Paddusw,
Palignr,
Pand,
Pandn,
Pavgb,
@@ -507,6 +511,18 @@ pub enum SseOpcode {
Pminuw,
Pminud,
Pmovmskb,
Pmovsxbd,
Pmovsxbw,
Pmovsxbq,
Pmovsxwd,
Pmovsxwq,
Pmovsxdq,
Pmovzxbd,
Pmovzxbw,
Pmovzxbq,
Pmovzxwd,
Pmovzxwq,
Pmovzxdq,
Pmulld,
Pmullw,
Pmuludq,
@@ -534,6 +550,8 @@ pub enum SseOpcode {
Punpcklbw,
Pxor,
Rcpss,
Roundps,
Roundpd,
Roundss,
Roundsd,
Rsqrtss,
@@ -620,7 +638,9 @@ impl SseOpcode {
| SseOpcode::Mulpd
| SseOpcode::Mulsd
| SseOpcode::Orpd
| SseOpcode::Packssdw
| SseOpcode::Packsswb
| SseOpcode::Packuswb
| SseOpcode::Paddb
| SseOpcode::Paddd
| SseOpcode::Paddq
@@ -676,9 +696,14 @@ impl SseOpcode {
| SseOpcode::Ucomisd
| SseOpcode::Xorpd => SSE2,
SseOpcode::Pabsb | SseOpcode::Pabsw | SseOpcode::Pabsd | SseOpcode::Pshufb => SSSE3,
SseOpcode::Pabsb
| SseOpcode::Pabsw
| SseOpcode::Pabsd
| SseOpcode::Palignr
| SseOpcode::Pshufb => SSSE3,
SseOpcode::Insertps
| SseOpcode::Packusdw
| SseOpcode::Pcmpeqq
| SseOpcode::Pextrb
| SseOpcode::Pextrd
@@ -692,8 +717,22 @@ impl SseOpcode {
| SseOpcode::Pminsd
| SseOpcode::Pminuw
| SseOpcode::Pminud
| SseOpcode::Pmovsxbd
| SseOpcode::Pmovsxbw
| SseOpcode::Pmovsxbq
| SseOpcode::Pmovsxwd
| SseOpcode::Pmovsxwq
| SseOpcode::Pmovsxdq
| SseOpcode::Pmovzxbd
| SseOpcode::Pmovzxbw
| SseOpcode::Pmovzxbq
| SseOpcode::Pmovzxwd
| SseOpcode::Pmovzxwq
| SseOpcode::Pmovzxdq
| SseOpcode::Pmulld
| SseOpcode::Ptest
| SseOpcode::Roundps
| SseOpcode::Roundpd
| SseOpcode::Roundss
| SseOpcode::Roundsd => SSE41,
@@ -772,7 +811,10 @@ impl fmt::Debug for SseOpcode {
SseOpcode::Pabsb => "pabsb",
SseOpcode::Pabsw => "pabsw",
SseOpcode::Pabsd => "pabsd",
SseOpcode::Packssdw => "packssdw",
SseOpcode::Packsswb => "packsswb",
SseOpcode::Packusdw => "packusdw",
SseOpcode::Packuswb => "packuswb",
SseOpcode::Paddb => "paddb",
SseOpcode::Paddd => "paddd",
SseOpcode::Paddq => "paddq",
@@ -781,6 +823,7 @@ impl fmt::Debug for SseOpcode {
SseOpcode::Paddsw => "paddsw",
SseOpcode::Paddusb => "paddusb",
SseOpcode::Paddusw => "paddusw",
SseOpcode::Palignr => "palignr",
SseOpcode::Pand => "pand",
SseOpcode::Pandn => "pandn",
SseOpcode::Pavgb => "pavgb",
@@ -812,6 +855,18 @@ impl fmt::Debug for SseOpcode {
SseOpcode::Pminuw => "pminuw",
SseOpcode::Pminud => "pminud",
SseOpcode::Pmovmskb => "pmovmskb",
SseOpcode::Pmovsxbd => "pmovsxbd",
SseOpcode::Pmovsxbw => "pmovsxbw",
SseOpcode::Pmovsxbq => "pmovsxbq",
SseOpcode::Pmovsxwd => "pmovsxwd",
SseOpcode::Pmovsxwq => "pmovsxwq",
SseOpcode::Pmovsxdq => "pmovsxdq",
SseOpcode::Pmovzxbd => "pmovzxbd",
SseOpcode::Pmovzxbw => "pmovzxbw",
SseOpcode::Pmovzxbq => "pmovzxbq",
SseOpcode::Pmovzxwd => "pmovzxwd",
SseOpcode::Pmovzxwq => "pmovzxwq",
SseOpcode::Pmovzxdq => "pmovzxdq",
SseOpcode::Pmulld => "pmulld",
SseOpcode::Pmullw => "pmullw",
SseOpcode::Pmuludq => "pmuludq",
@@ -839,6 +894,8 @@ impl fmt::Debug for SseOpcode {
SseOpcode::Punpcklbw => "punpcklbw",
SseOpcode::Pxor => "pxor",
SseOpcode::Rcpss => "rcpss",
SseOpcode::Roundps => "roundps",
SseOpcode::Roundpd => "roundpd",
SseOpcode::Roundss => "roundss",
SseOpcode::Roundsd => "roundsd",
SseOpcode::Rsqrtss => "rsqrtss",
@@ -1187,6 +1244,25 @@ impl From<FloatCC> for FcmpImm {
}
}
/// Encode the rounding modes used as part of the Rounding Control field.
/// Note, these rounding immediates only consider the rounding control field
/// (i.e. the rounding mode) which only take up the first two bits when encoded.
/// However the rounding immediate which this field helps make up, also includes
/// bits 3 and 4 which define the rounding select and precision mask respectively.
/// These two bits are not defined here and are implictly set to zero when encoded.
pub(crate) enum RoundImm {
RoundNearest = 0x00,
RoundDown = 0x01,
RoundUp = 0x02,
RoundZero = 0x03,
}
impl RoundImm {
pub(crate) fn encode(self) -> u8 {
self as u8
}
}
/// An operand's size in bits.
#[derive(Clone, Copy, PartialEq)]
pub enum OperandSize {

View File

@@ -182,6 +182,7 @@ impl LegacyPrefixes {
fn emit_std_enc_mem(
sink: &mut MachBuffer<Inst>,
state: &EmitState,
info: &EmitInfo,
prefixes: LegacyPrefixes,
opcodes: u32,
mut num_opcodes: usize,
@@ -194,7 +195,8 @@ fn emit_std_enc_mem(
// expression. But `enc_g` can be derived from a register of any class.
let srcloc = state.cur_srcloc();
if srcloc != SourceLoc::default() && mem_e.can_trap() {
let can_trap = mem_e.can_trap();
if srcloc != SourceLoc::default() && can_trap {
sink.add_trap(srcloc, TrapCode::HeapOutOfBounds);
}
@@ -202,6 +204,12 @@ fn emit_std_enc_mem(
match mem_e {
Amode::ImmReg { simm32, base, .. } => {
// If this is an access based off of RSP, it may trap with a stack overflow if it's the
// first touch of a new stack page.
if *base == regs::rsp() && !can_trap && info.flags().enable_probestack() {
sink.add_trap(srcloc, TrapCode::StackOverflow);
}
// First, the REX byte.
let enc_e = int_reg_enc(*base);
rex.emit_two_op(sink, enc_g, enc_e);
@@ -262,6 +270,12 @@ fn emit_std_enc_mem(
shift,
..
} => {
// If this is an access based off of RSP, it may trap with a stack overflow if it's the
// first touch of a new stack page.
if *reg_base == regs::rsp() && !can_trap && info.flags().enable_probestack() {
sink.add_trap(srcloc, TrapCode::StackOverflow);
}
let enc_base = int_reg_enc(*reg_base);
let enc_index = int_reg_enc(*reg_index);
@@ -350,6 +364,7 @@ fn emit_std_enc_enc(
fn emit_std_reg_mem(
sink: &mut MachBuffer<Inst>,
state: &EmitState,
info: &EmitInfo,
prefixes: LegacyPrefixes,
opcodes: u32,
num_opcodes: usize,
@@ -361,6 +376,7 @@ fn emit_std_reg_mem(
emit_std_enc_mem(
sink,
state,
info,
prefixes,
opcodes,
num_opcodes,
@@ -538,6 +554,7 @@ pub(crate) fn emit(
emit_std_reg_mem(
sink,
state,
info,
LegacyPrefixes::None,
0x0FAF,
2,
@@ -597,6 +614,7 @@ pub(crate) fn emit(
emit_std_reg_mem(
sink,
state,
info,
LegacyPrefixes::None,
opcode_m,
1,
@@ -654,6 +672,7 @@ pub(crate) fn emit(
emit_std_reg_mem(
sink,
state,
info,
prefix,
opcode,
num_opcodes,
@@ -717,7 +736,9 @@ pub(crate) fn emit(
}
RegMem::Mem { addr: src } => {
let amode = src.finalize(state, sink);
emit_std_enc_mem(sink, state, prefix, opcode, 1, subopcode, &amode, rex_flags);
emit_std_enc_mem(
sink, state, info, prefix, opcode, 1, subopcode, &amode, rex_flags,
);
}
}
}
@@ -738,7 +759,9 @@ pub(crate) fn emit(
}
RegMem::Mem { addr: src } => {
let amode = src.finalize(state, sink);
emit_std_enc_mem(sink, state, prefix, 0xF7, 1, subopcode, &amode, rex_flags);
emit_std_enc_mem(
sink, state, info, prefix, 0xF7, 1, subopcode, &amode, rex_flags,
);
}
}
}
@@ -987,6 +1010,7 @@ pub(crate) fn emit(
emit_std_reg_mem(
sink,
state,
info,
LegacyPrefixes::None,
opcodes,
num_opcodes,
@@ -1004,6 +1028,7 @@ pub(crate) fn emit(
emit_std_reg_mem(
sink,
state,
info,
LegacyPrefixes::None,
0x8B,
1,
@@ -1019,6 +1044,7 @@ pub(crate) fn emit(
emit_std_reg_mem(
sink,
state,
info,
LegacyPrefixes::None,
0x8D,
1,
@@ -1081,6 +1107,7 @@ pub(crate) fn emit(
emit_std_reg_mem(
sink,
state,
info,
LegacyPrefixes::None,
opcodes,
num_opcodes,
@@ -1108,7 +1135,17 @@ pub(crate) fn emit(
};
// MOV r8, r/m8 is (REX.W==0) 88 /r
emit_std_reg_mem(sink, state, LegacyPrefixes::None, 0x88, 1, *src, dst, rex)
emit_std_reg_mem(
sink,
state,
info,
LegacyPrefixes::None,
0x88,
1,
*src,
dst,
rex,
)
}
2 => {
@@ -1116,6 +1153,7 @@ pub(crate) fn emit(
emit_std_reg_mem(
sink,
state,
info,
LegacyPrefixes::_66,
0x89,
1,
@@ -1130,6 +1168,7 @@ pub(crate) fn emit(
emit_std_reg_mem(
sink,
state,
info,
LegacyPrefixes::None,
0x89,
1,
@@ -1144,6 +1183,7 @@ pub(crate) fn emit(
emit_std_reg_mem(
sink,
state,
info,
LegacyPrefixes::None,
0x89,
1,
@@ -1253,6 +1293,7 @@ pub(crate) fn emit(
emit_std_reg_mem(
sink,
state,
info,
prefix,
opcode_bytes,
2,
@@ -1311,7 +1352,7 @@ pub(crate) fn emit(
let addr = &addr.finalize(state, sink);
// Whereas here we revert to the "normal" G-E ordering.
let opcode = if *size == 1 { 0x3A } else { 0x3B };
emit_std_reg_mem(sink, state, prefix, opcode, 1, *reg_g, addr, rex);
emit_std_reg_mem(sink, state, info, prefix, opcode, 1, *reg_g, addr, rex);
}
RegMemImm::Imm { simm32 } => {
@@ -1372,6 +1413,7 @@ pub(crate) fn emit(
emit_std_reg_mem(
sink,
state,
info,
prefix,
opcode,
2,
@@ -1408,6 +1450,10 @@ pub(crate) fn emit(
}
Inst::Push64 { src } => {
if info.flags().enable_probestack() {
sink.add_trap(state.cur_srcloc(), TrapCode::StackOverflow);
}
match src {
RegMemImm::Reg { reg } => {
let enc_reg = int_reg_enc(*reg);
@@ -1423,6 +1469,7 @@ pub(crate) fn emit(
emit_std_enc_mem(
sink,
state,
info,
LegacyPrefixes::None,
0xFF,
1,
@@ -1454,6 +1501,9 @@ pub(crate) fn emit(
}
Inst::CallKnown { dest, opcode, .. } => {
if info.flags().enable_probestack() {
sink.add_trap(state.cur_srcloc(), TrapCode::StackOverflow);
}
if let Some(s) = state.take_stack_map() {
sink.add_stack_map(StackMapExtent::UpcomingBytes(5), s);
}
@@ -1469,6 +1519,9 @@ pub(crate) fn emit(
}
Inst::CallUnknown { dest, opcode, .. } => {
if info.flags().enable_probestack() {
sink.add_trap(state.cur_srcloc(), TrapCode::StackOverflow);
}
let start_offset = sink.cur_offset();
match dest {
RegMem::Reg { reg } => {
@@ -1489,6 +1542,7 @@ pub(crate) fn emit(
emit_std_enc_mem(
sink,
state,
info,
LegacyPrefixes::None,
0xFF,
1,
@@ -1587,6 +1641,7 @@ pub(crate) fn emit(
emit_std_enc_mem(
sink,
state,
info,
LegacyPrefixes::None,
0xFF,
1,
@@ -1733,6 +1788,7 @@ pub(crate) fn emit(
emit_std_reg_mem(
sink,
state,
info,
prefix,
opcode,
num_opcodes,
@@ -1781,7 +1837,10 @@ pub(crate) fn emit(
SseOpcode::Mulsd => (LegacyPrefixes::_F2, 0x0F59, 2),
SseOpcode::Orpd => (LegacyPrefixes::_66, 0x0F56, 2),
SseOpcode::Orps => (LegacyPrefixes::None, 0x0F56, 2),
SseOpcode::Packssdw => (LegacyPrefixes::_66, 0x0F6B, 2),
SseOpcode::Packsswb => (LegacyPrefixes::_66, 0x0F63, 2),
SseOpcode::Packusdw => (LegacyPrefixes::_66, 0x0F382B, 3),
SseOpcode::Packuswb => (LegacyPrefixes::_66, 0x0F67, 2),
SseOpcode::Paddb => (LegacyPrefixes::_66, 0x0FFC, 2),
SseOpcode::Paddd => (LegacyPrefixes::_66, 0x0FFE, 2),
SseOpcode::Paddq => (LegacyPrefixes::_66, 0x0FD4, 2),
@@ -1802,6 +1861,18 @@ pub(crate) fn emit(
SseOpcode::Pcmpgtw => (LegacyPrefixes::_66, 0x0F65, 2),
SseOpcode::Pcmpgtd => (LegacyPrefixes::_66, 0x0F66, 2),
SseOpcode::Pcmpgtq => (LegacyPrefixes::_66, 0x0F3837, 3),
SseOpcode::Pmovsxbd => (LegacyPrefixes::_66, 0x0F3821, 3),
SseOpcode::Pmovsxbw => (LegacyPrefixes::_66, 0x0F3820, 3),
SseOpcode::Pmovsxbq => (LegacyPrefixes::_66, 0x0F3822, 3),
SseOpcode::Pmovsxwd => (LegacyPrefixes::_66, 0x0F3823, 3),
SseOpcode::Pmovsxwq => (LegacyPrefixes::_66, 0x0F3824, 3),
SseOpcode::Pmovsxdq => (LegacyPrefixes::_66, 0x0F3825, 3),
SseOpcode::Pmovzxbd => (LegacyPrefixes::_66, 0x0F3831, 3),
SseOpcode::Pmovzxbw => (LegacyPrefixes::_66, 0x0F3830, 3),
SseOpcode::Pmovzxbq => (LegacyPrefixes::_66, 0x0F3832, 3),
SseOpcode::Pmovzxwd => (LegacyPrefixes::_66, 0x0F3833, 3),
SseOpcode::Pmovzxwq => (LegacyPrefixes::_66, 0x0F3834, 3),
SseOpcode::Pmovzxdq => (LegacyPrefixes::_66, 0x0F3835, 3),
SseOpcode::Pmaxsb => (LegacyPrefixes::_66, 0x0F383C, 3),
SseOpcode::Pmaxsw => (LegacyPrefixes::_66, 0x0FEE, 2),
SseOpcode::Pmaxsd => (LegacyPrefixes::_66, 0x0F383D, 3),
@@ -1848,6 +1919,7 @@ pub(crate) fn emit(
emit_std_reg_mem(
sink,
state,
info,
prefix,
opcode,
length,
@@ -1958,6 +2030,7 @@ pub(crate) fn emit(
SseOpcode::Cmpss => (LegacyPrefixes::_F3, 0x0FC2, 2),
SseOpcode::Cmpsd => (LegacyPrefixes::_F2, 0x0FC2, 2),
SseOpcode::Insertps => (LegacyPrefixes::_66, 0x0F3A21, 3),
SseOpcode::Palignr => (LegacyPrefixes::_66, 0x0F3A0F, 3),
SseOpcode::Pinsrb => (LegacyPrefixes::_66, 0x0F3A20, 3),
SseOpcode::Pinsrw => (LegacyPrefixes::_66, 0x0FC4, 2),
SseOpcode::Pinsrd => (LegacyPrefixes::_66, 0x0F3A22, 3),
@@ -1965,6 +2038,8 @@ pub(crate) fn emit(
SseOpcode::Pextrw => (LegacyPrefixes::_66, 0x0FC5, 2),
SseOpcode::Pextrd => (LegacyPrefixes::_66, 0x0F3A16, 3),
SseOpcode::Pshufd => (LegacyPrefixes::_66, 0x0F70, 2),
SseOpcode::Roundps => (LegacyPrefixes::_66, 0x0F3A08, 3),
SseOpcode::Roundpd => (LegacyPrefixes::_66, 0x0F3A09, 3),
_ => unimplemented!("Opcode {:?} not implemented", op),
};
let rex = if *is64 {
@@ -1994,7 +2069,17 @@ pub(crate) fn emit(
!regs_swapped,
"No existing way to encode a mem argument in the ModRM r/m field."
);
emit_std_reg_mem(sink, state, prefix, opcode, len, dst.to_reg(), addr, rex);
emit_std_reg_mem(
sink,
state,
info,
prefix,
opcode,
len,
dst.to_reg(),
addr,
rex,
);
}
}
sink.put1(*imm);
@@ -2027,6 +2112,7 @@ pub(crate) fn emit(
emit_std_reg_mem(
sink,
state,
info,
prefix,
opcode,
2,
@@ -2091,7 +2177,17 @@ pub(crate) fn emit(
}
RegMem::Mem { addr } => {
let addr = &addr.finalize(state, sink);
emit_std_reg_mem(sink, state, prefix, opcode, 2, reg_g.to_reg(), addr, rex);
emit_std_reg_mem(
sink,
state,
info,
prefix,
opcode,
2,
reg_g.to_reg(),
addr,
rex,
);
}
}
}
@@ -2111,7 +2207,7 @@ pub(crate) fn emit(
}
RegMem::Mem { addr } => {
let addr = &addr.finalize(state, sink);
emit_std_reg_mem(sink, state, prefix, opcode, len, *dst, addr, rex);
emit_std_reg_mem(sink, state, info, prefix, opcode, len, *dst, addr, rex);
}
}
}
@@ -2625,7 +2721,7 @@ pub(crate) fn emit(
_ => unreachable!(),
};
let amode = dst.finalize(state, sink);
emit_std_reg_mem(sink, state, prefix, opcodes, 2, *src, &amode, rex);
emit_std_reg_mem(sink, state, info, prefix, opcodes, 2, *src, &amode, rex);
}
Inst::AtomicRmwSeq { ty, op } => {

View File

@@ -3151,12 +3151,30 @@ fn test_x64_emit() {
"pshufb %xmm11, %xmm2",
));
insns.push((
Inst::xmm_rm_r(SseOpcode::Packssdw, RegMem::reg(xmm11), w_xmm12),
"66450F6BE3",
"packssdw %xmm11, %xmm12",
));
insns.push((
Inst::xmm_rm_r(SseOpcode::Packsswb, RegMem::reg(xmm11), w_xmm2),
"66410F63D3",
"packsswb %xmm11, %xmm2",
));
insns.push((
Inst::xmm_rm_r(SseOpcode::Packusdw, RegMem::reg(xmm13), w_xmm6),
"66410F382BF5",
"packusdw %xmm13, %xmm6",
));
insns.push((
Inst::xmm_rm_r(SseOpcode::Packuswb, RegMem::reg(xmm9), w_xmm4),
"66410F67E1",
"packuswb %xmm9, %xmm4",
));
insns.push((
Inst::xmm_rm_r(SseOpcode::Punpckhbw, RegMem::reg(xmm3), w_xmm2),
"660F68D3",
@@ -3183,6 +3201,81 @@ fn test_x64_emit() {
"cvttps2dq %xmm9, %xmm8",
));
// ========================================================
// XMM_RM_R: Packed Move
insns.push((
Inst::xmm_rm_r(SseOpcode::Pmovsxbd, RegMem::reg(xmm6), w_xmm8),
"66440F3821C6",
"pmovsxbd %xmm6, %xmm8",
));
insns.push((
Inst::xmm_rm_r(SseOpcode::Pmovsxbw, RegMem::reg(xmm9), w_xmm10),
"66450F3820D1",
"pmovsxbw %xmm9, %xmm10",
));
insns.push((
Inst::xmm_rm_r(SseOpcode::Pmovsxbq, RegMem::reg(xmm1), w_xmm1),
"660F3822C9",
"pmovsxbq %xmm1, %xmm1",
));
insns.push((
Inst::xmm_rm_r(SseOpcode::Pmovsxwd, RegMem::reg(xmm13), w_xmm10),
"66450F3823D5",
"pmovsxwd %xmm13, %xmm10",
));
insns.push((
Inst::xmm_rm_r(SseOpcode::Pmovsxwq, RegMem::reg(xmm12), w_xmm12),
"66450F3824E4",
"pmovsxwq %xmm12, %xmm12",
));
insns.push((
Inst::xmm_rm_r(SseOpcode::Pmovsxdq, RegMem::reg(xmm10), w_xmm8),
"66450F3825C2",
"pmovsxdq %xmm10, %xmm8",
));
insns.push((
Inst::xmm_rm_r(SseOpcode::Pmovzxbd, RegMem::reg(xmm5), w_xmm6),
"660F3831F5",
"pmovzxbd %xmm5, %xmm6",
));
insns.push((
Inst::xmm_rm_r(SseOpcode::Pmovzxbw, RegMem::reg(xmm5), w_xmm13),
"66440F3830ED",
"pmovzxbw %xmm5, %xmm13",
));
insns.push((
Inst::xmm_rm_r(SseOpcode::Pmovzxbq, RegMem::reg(xmm10), w_xmm11),
"66450F3832DA",
"pmovzxbq %xmm10, %xmm11",
));
insns.push((
Inst::xmm_rm_r(SseOpcode::Pmovzxwd, RegMem::reg(xmm2), w_xmm10),
"66440F3833D2",
"pmovzxwd %xmm2, %xmm10",
));
insns.push((
Inst::xmm_rm_r(SseOpcode::Pmovzxwq, RegMem::reg(xmm7), w_xmm4),
"660F3834E7",
"pmovzxwq %xmm7, %xmm4",
));
insns.push((
Inst::xmm_rm_r(SseOpcode::Pmovzxdq, RegMem::reg(xmm3), w_xmm4),
"660F3835E3",
"pmovzxdq %xmm3, %xmm4",
));
// XMM_Mov_R_M: float stores
insns.push((
Inst::xmm_mov_r_m(SseOpcode::Movss, xmm15, Amode::imm_reg(128, r12)),
@@ -3406,6 +3499,32 @@ fn test_x64_emit() {
"410FC2FF00",
"cmpps $0, %xmm15, %xmm7",
));
insns.push((
Inst::xmm_rm_r_imm(SseOpcode::Palignr, RegMem::reg(xmm1), w_xmm9, 3, false),
"66440F3A0FC903",
"palignr $3, %xmm1, %xmm9",
));
insns.push((
Inst::xmm_rm_r_imm(SseOpcode::Roundps, RegMem::reg(xmm7), w_xmm8, 3, false),
"66440F3A08C703",
"roundps $3, %xmm7, %xmm8",
));
insns.push((
Inst::xmm_rm_r_imm(SseOpcode::Roundpd, RegMem::reg(xmm10), w_xmm7, 2, false),
"66410F3A09FA02",
"roundpd $2, %xmm10, %xmm7",
));
insns.push((
Inst::xmm_rm_r_imm(SseOpcode::Roundps, RegMem::reg(xmm4), w_xmm8, 1, false),
"66440F3A08C401",
"roundps $1, %xmm4, %xmm8",
));
insns.push((
Inst::xmm_rm_r_imm(SseOpcode::Roundpd, RegMem::reg(xmm15), w_xmm15, 0, false),
"66450F3A09FF00",
"roundpd $0, %xmm15, %xmm15",
));
// ========================================================
// Pertaining to atomics.

View File

@@ -1,14 +1,20 @@
//! Registers, the Universe thereof, and printing.
//!
//! These are ordered by sequence number, as required in the Universe. The strange ordering is
//! intended to make callee-save registers available before caller-saved ones. This is a net win
//! provided that each function makes at least one onward call. It'll be a net loss for leaf
//! functions, and we should change the ordering in that case, so as to make caller-save regs
//! available first.
//! These are ordered by sequence number, as required in the Universe.
//!
//! TODO Maybe have two different universes, one for leaf functions and one for non-leaf functions?
//! Also, they will have to be ABI dependent. Need to find a way to avoid constructing a universe
//! for each function we compile.
//! The caller-saved registers are placed first in order to prefer not to clobber (requiring
//! saves/restores in prologue/epilogue code) when possible. Note that there is no other heuristic
//! in the backend that will apply such pressure; the register allocator's cost heuristics are not
//! aware of the cost of clobber-save/restore code.
//!
//! One might worry that this pessimizes code with many callsites, where using caller-saves causes
//! us to have to save them (as we are the caller) frequently. However, the register allocator
//! *should be* aware of *this* cost, because it sees that the call instruction modifies all of the
//! caller-saved (i.e., callee-clobbered) registers.
//!
//! Hence, this ordering encodes pressure in one direction (prefer not to clobber registers that we
//! ourselves have to save) and this is balanaced against the RA's pressure in the other direction
//! at callsites.
use crate::settings;
use alloc::vec::Vec;
@@ -31,44 +37,44 @@ fn gpr(enc: u8, index: u8) -> Reg {
Reg::new_real(RegClass::I64, enc, index)
}
pub(crate) fn r12() -> Reg {
gpr(ENC_R12, 16)
}
pub(crate) fn r13() -> Reg {
gpr(ENC_R13, 17)
}
pub(crate) fn r14() -> Reg {
gpr(ENC_R14, 18)
}
pub(crate) fn rbx() -> Reg {
gpr(ENC_RBX, 19)
}
pub(crate) fn rsi() -> Reg {
gpr(6, 20)
gpr(6, 16)
}
pub(crate) fn rdi() -> Reg {
gpr(7, 21)
gpr(7, 17)
}
pub(crate) fn rax() -> Reg {
gpr(0, 22)
gpr(0, 18)
}
pub(crate) fn rcx() -> Reg {
gpr(1, 23)
gpr(1, 19)
}
pub(crate) fn rdx() -> Reg {
gpr(2, 24)
gpr(2, 20)
}
pub(crate) fn r8() -> Reg {
gpr(8, 25)
gpr(8, 21)
}
pub(crate) fn r9() -> Reg {
gpr(9, 26)
gpr(9, 22)
}
pub(crate) fn r10() -> Reg {
gpr(10, 27)
gpr(10, 23)
}
pub(crate) fn r11() -> Reg {
gpr(11, 28)
gpr(11, 24)
}
pub(crate) fn r12() -> Reg {
gpr(ENC_R12, 25)
}
pub(crate) fn r13() -> Reg {
gpr(ENC_R13, 26)
}
pub(crate) fn r14() -> Reg {
gpr(ENC_R14, 27)
}
pub(crate) fn rbx() -> Reg {
gpr(ENC_RBX, 28)
}
pub(crate) fn r15() -> Reg {
@@ -176,13 +182,6 @@ pub(crate) fn create_reg_universe_systemv(flags: &settings::Flags) -> RealRegUni
// Integer regs.
let first_gpr = regs.len();
// Callee-saved, in the SystemV x86_64 ABI.
regs.push((r12().to_real_reg(), "%r12".into()));
regs.push((r13().to_real_reg(), "%r13".into()));
regs.push((r14().to_real_reg(), "%r14".into()));
regs.push((rbx().to_real_reg(), "%rbx".into()));
// Caller-saved, in the SystemV x86_64 ABI.
regs.push((rsi().to_real_reg(), "%rsi".into()));
regs.push((rdi().to_real_reg(), "%rdi".into()));
@@ -194,6 +193,13 @@ pub(crate) fn create_reg_universe_systemv(flags: &settings::Flags) -> RealRegUni
regs.push((r10().to_real_reg(), "%r10".into()));
regs.push((r11().to_real_reg(), "%r11".into()));
// Callee-saved, in the SystemV x86_64 ABI.
regs.push((r12().to_real_reg(), "%r12".into()));
regs.push((r13().to_real_reg(), "%r13".into()));
regs.push((r14().to_real_reg(), "%r14".into()));
regs.push((rbx().to_real_reg(), "%rbx".into()));
// Other regs, not available to the allocator.
debug_assert_eq!(r15(), pinned_reg());
let allocable = if use_pinned_reg {

View File

@@ -1530,7 +1530,10 @@ fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
let src = if let Some(ext_spec) = ext_spec {
RegMem::reg(extend_input_to_reg(ctx, inputs[0], ext_spec))
} else {
input_to_reg_mem(ctx, inputs[0])
// N.B.: explicitly put input in a reg here because the width of the instruction
// into which this RM op goes may not match the width of the input type (in fact,
// it won't for i32.popcnt), and we don't want a larger than necessary load.
RegMem::reg(put_input_in_reg(ctx, inputs[0]))
};
let dst = get_output_reg(ctx, outputs[0]);
@@ -2722,6 +2725,7 @@ fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
} else {
if op == Opcode::FcvtToSintSat {
// Sets destination to zero if float is NaN
assert_eq!(types::F32X4, ctx.input_ty(insn, 0));
let tmp = ctx.alloc_tmp(RegClass::V128, types::I32X4);
ctx.emit(Inst::xmm_unary_rm_r(
SseOpcode::Movapd,
@@ -2776,7 +2780,118 @@ fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
dst,
));
} else if op == Opcode::FcvtToUintSat {
unimplemented!("f32x4.convert_i32x4_u");
// The algorithm for converting floats to unsigned ints is a little tricky. The
// complication arises because we are converting from a signed 64-bit int with a positive
// integer range from 1..INT_MAX (0x1..0x7FFFFFFF) to an unsigned integer with an extended
// range from (INT_MAX+1)..UINT_MAX. It's this range from (INT_MAX+1)..UINT_MAX
// (0x80000000..0xFFFFFFFF) that needs to be accounted for as a special case since our
// conversion instruction (cvttps2dq) only converts as high as INT_MAX (0x7FFFFFFF), but
// which conveniently setting underflows and overflows (smaller than MIN_INT or larger than
// MAX_INT) to be INT_MAX+1 (0x80000000). Nothing that the range (INT_MAX+1)..UINT_MAX includes
// precisely INT_MAX values we can correctly account for and convert every value in this range
// if we simply subtract INT_MAX+1 before doing the cvttps2dq conversion. After the subtraction
// every value originally (INT_MAX+1)..UINT_MAX is now the range (0..INT_MAX).
// After the conversion we add INT_MAX+1 back to this converted value, noting again that
// values we are trying to account for were already set to INT_MAX+1 during the original conversion.
// We simply have to create a mask and make sure we are adding together only the lanes that need
// to be accounted for. Digesting it all the steps then are:
//
// Step 1 - Account for NaN and negative floats by setting these src values to zero.
// Step 2 - Make a copy (tmp1) of the src value since we need to convert twice for
// reasons described above.
// Step 3 - Convert the original src values. This will convert properly all floats up to INT_MAX
// Step 4 - Subtract INT_MAX from the copy set (tmp1). Note, all zero and negative values are those
// values that were originally in the range (0..INT_MAX). This will come in handy during
// step 7 when we zero negative lanes.
// Step 5 - Create a bit mask for tmp1 that will correspond to all lanes originally less than
// UINT_MAX that are now less than INT_MAX thanks to the subtraction.
// Step 6 - Convert the second set of values (tmp1)
// Step 7 - Prep the converted second set by zeroing out negative lanes (these have already been
// converted correctly with the first set) and by setting overflow lanes to 0x7FFFFFFF
// as this will allow us to properly saturate overflow lanes when adding to 0x80000000
// Step 8 - Add the orginal converted src and the converted tmp1 where float values originally less
// than and equal to INT_MAX will be unchanged, float values originally between INT_MAX+1 and
// UINT_MAX will add together (INT_MAX) + (SRC - INT_MAX), and float values originally
// greater than UINT_MAX will be saturated to UINT_MAX (0xFFFFFFFF) after adding (0x8000000 + 0x7FFFFFFF).
//
//
// The table below illustrates the result after each step where it matters for the converted set.
// Note the original value range (original src set) is the final dst in Step 8:
//
// Original src set:
// | Original Value Range | Step 1 | Step 3 | Step 8 |
// | -FLT_MIN..FLT_MAX | 0.0..FLT_MAX | 0..INT_MAX(w/overflow) | 0..UINT_MAX(w/saturation) |
//
// Copied src set (tmp1):
// | Step 2 | Step 4 |
// | 0.0..FLT_MAX | (0.0-(INT_MAX+1))..(FLT_MAX-(INT_MAX+1)) |
//
// | Step 6 | Step 7 |
// | (0-(INT_MAX+1))..(UINT_MAX-(INT_MAX+1))(w/overflow) | ((INT_MAX+1)-(INT_MAX+1))..(INT_MAX+1) |
// Create temporaries
assert_eq!(types::F32X4, ctx.input_ty(insn, 0));
let tmp1 = ctx.alloc_tmp(RegClass::V128, types::I32X4);
let tmp2 = ctx.alloc_tmp(RegClass::V128, types::I32X4);
// Converting to unsigned int so if float src is negative or NaN
// will first set to zero.
ctx.emit(Inst::xmm_rm_r(SseOpcode::Pxor, RegMem::from(tmp2), tmp2));
ctx.emit(Inst::gen_move(dst, src, input_ty));
ctx.emit(Inst::xmm_rm_r(SseOpcode::Maxps, RegMem::from(tmp2), dst));
// Set tmp2 to INT_MAX+1. It is important to note here that after it looks
// like we are only converting INT_MAX (0x7FFFFFFF) but in fact because
// single precision IEEE-754 floats can only accurately represent contingous
// integers up to 2^23 and outside of this range it rounds to the closest
// integer that it can represent. In the case of INT_MAX, this value gets
// represented as 0x4f000000 which is the integer value (INT_MAX+1).
ctx.emit(Inst::xmm_rm_r(SseOpcode::Pcmpeqd, RegMem::from(tmp2), tmp2));
ctx.emit(Inst::xmm_rmi_reg(SseOpcode::Psrld, RegMemImm::imm(1), tmp2));
ctx.emit(Inst::xmm_rm_r(
SseOpcode::Cvtdq2ps,
RegMem::from(tmp2),
tmp2,
));
// Make a copy of these lanes and then do the first conversion.
// Overflow lanes greater than the maximum allowed signed value will
// set to 0x80000000. Negative and NaN lanes will be 0x0
ctx.emit(Inst::xmm_mov(SseOpcode::Movaps, RegMem::from(dst), tmp1));
ctx.emit(Inst::xmm_rm_r(SseOpcode::Cvttps2dq, RegMem::from(dst), dst));
// Set lanes to src - max_signed_int
ctx.emit(Inst::xmm_rm_r(SseOpcode::Subps, RegMem::from(tmp2), tmp1));
// Create mask for all positive lanes to saturate (i.e. greater than
// or equal to the maxmimum allowable unsigned int).
let cond = FcmpImm::from(FloatCC::LessThanOrEqual);
ctx.emit(Inst::xmm_rm_r_imm(
SseOpcode::Cmpps,
RegMem::from(tmp1),
tmp2,
cond.encode(),
false,
));
// Convert those set of lanes that have the max_signed_int factored out.
ctx.emit(Inst::xmm_rm_r(
SseOpcode::Cvttps2dq,
RegMem::from(tmp1),
tmp1,
));
// Prepare converted lanes by zeroing negative lanes and prepping lanes
// that have positive overflow (based on the mask) by setting these lanes
// to 0x7FFFFFFF
ctx.emit(Inst::xmm_rm_r(SseOpcode::Pxor, RegMem::from(tmp2), tmp1));
ctx.emit(Inst::xmm_rm_r(SseOpcode::Pxor, RegMem::from(tmp2), tmp2));
ctx.emit(Inst::xmm_rm_r(SseOpcode::Pmaxsd, RegMem::from(tmp2), tmp1));
// Add this second set of converted lanes to the original to properly handle
// values greater than max signed int.
ctx.emit(Inst::xmm_rm_r(SseOpcode::Paddd, RegMem::from(tmp1), dst));
} else {
// Since this branch is also guarded by a check for vector types
// neither Opcode::FcvtToUint nor Opcode::FcvtToSint can reach here
@@ -2786,7 +2901,127 @@ fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
}
}
}
Opcode::UwidenHigh | Opcode::UwidenLow | Opcode::SwidenHigh | Opcode::SwidenLow => {
let input_ty = ctx.input_ty(insn, 0);
let output_ty = ctx.output_ty(insn, 0);
let src = put_input_in_reg(ctx, inputs[0]);
let dst = get_output_reg(ctx, outputs[0]);
if output_ty.is_vector() {
match op {
Opcode::SwidenLow => match (input_ty, output_ty) {
(types::I8X16, types::I16X8) => {
ctx.emit(Inst::gen_move(dst, src, output_ty));
ctx.emit(Inst::xmm_rm_r(SseOpcode::Pmovsxbw, RegMem::from(dst), dst));
}
(types::I16X8, types::I32X4) => {
ctx.emit(Inst::gen_move(dst, src, output_ty));
ctx.emit(Inst::xmm_rm_r(SseOpcode::Pmovsxwd, RegMem::from(dst), dst));
}
_ => unreachable!(),
},
Opcode::SwidenHigh => match (input_ty, output_ty) {
(types::I8X16, types::I16X8) => {
ctx.emit(Inst::gen_move(dst, src, output_ty));
ctx.emit(Inst::xmm_rm_r_imm(
SseOpcode::Palignr,
RegMem::reg(src),
dst,
8,
false,
));
ctx.emit(Inst::xmm_rm_r(SseOpcode::Pmovsxbw, RegMem::from(dst), dst));
}
(types::I16X8, types::I32X4) => {
ctx.emit(Inst::gen_move(dst, src, output_ty));
ctx.emit(Inst::xmm_rm_r_imm(
SseOpcode::Palignr,
RegMem::reg(src),
dst,
8,
false,
));
ctx.emit(Inst::xmm_rm_r(SseOpcode::Pmovsxwd, RegMem::from(dst), dst));
}
_ => unreachable!(),
},
Opcode::UwidenLow => match (input_ty, output_ty) {
(types::I8X16, types::I16X8) => {
ctx.emit(Inst::gen_move(dst, src, output_ty));
ctx.emit(Inst::xmm_rm_r(SseOpcode::Pmovzxbw, RegMem::from(dst), dst));
}
(types::I16X8, types::I32X4) => {
ctx.emit(Inst::gen_move(dst, src, output_ty));
ctx.emit(Inst::xmm_rm_r(SseOpcode::Pmovzxwd, RegMem::from(dst), dst));
}
_ => unreachable!(),
},
Opcode::UwidenHigh => match (input_ty, output_ty) {
(types::I8X16, types::I16X8) => {
ctx.emit(Inst::gen_move(dst, src, output_ty));
ctx.emit(Inst::xmm_rm_r_imm(
SseOpcode::Palignr,
RegMem::reg(src),
dst,
8,
false,
));
ctx.emit(Inst::xmm_rm_r(SseOpcode::Pmovzxbw, RegMem::from(dst), dst));
}
(types::I16X8, types::I32X4) => {
ctx.emit(Inst::gen_move(dst, src, output_ty));
ctx.emit(Inst::xmm_rm_r_imm(
SseOpcode::Palignr,
RegMem::reg(src),
dst,
8,
false,
));
ctx.emit(Inst::xmm_rm_r(SseOpcode::Pmovzxwd, RegMem::from(dst), dst));
}
_ => unreachable!(),
},
_ => unreachable!(),
}
} else {
panic!("Unsupported non-vector type for widen instruction {:?}", ty);
}
}
Opcode::Snarrow | Opcode::Unarrow => {
let input_ty = ctx.input_ty(insn, 0);
let output_ty = ctx.output_ty(insn, 0);
let src1 = put_input_in_reg(ctx, inputs[0]);
let src2 = put_input_in_reg(ctx, inputs[1]);
let dst = get_output_reg(ctx, outputs[0]);
if output_ty.is_vector() {
match op {
Opcode::Snarrow => match (input_ty, output_ty) {
(types::I16X8, types::I8X16) => {
ctx.emit(Inst::gen_move(dst, src1, input_ty));
ctx.emit(Inst::xmm_rm_r(SseOpcode::Packsswb, RegMem::reg(src2), dst));
}
(types::I32X4, types::I16X8) => {
ctx.emit(Inst::gen_move(dst, src1, input_ty));
ctx.emit(Inst::xmm_rm_r(SseOpcode::Packssdw, RegMem::reg(src2), dst));
}
_ => unreachable!(),
},
Opcode::Unarrow => match (input_ty, output_ty) {
(types::I16X8, types::I8X16) => {
ctx.emit(Inst::gen_move(dst, src1, input_ty));
ctx.emit(Inst::xmm_rm_r(SseOpcode::Packuswb, RegMem::reg(src2), dst));
}
(types::I32X4, types::I16X8) => {
ctx.emit(Inst::gen_move(dst, src1, input_ty));
ctx.emit(Inst::xmm_rm_r(SseOpcode::Packusdw, RegMem::reg(src2), dst));
}
_ => unreachable!(),
},
_ => unreachable!(),
}
} else {
panic!("Unsupported non-vector type for widen instruction {:?}", ty);
}
}
Opcode::Bitcast => {
let input_ty = ctx.input_ty(insn, 0);
let output_ty = ctx.output_ty(insn, 0);
@@ -2975,22 +3210,45 @@ fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
// Lower to VM calls when there's no access to SSE4.1.
let ty = ty.unwrap();
let libcall = match (ty, op) {
(types::F32, Opcode::Ceil) => LibCall::CeilF32,
(types::F64, Opcode::Ceil) => LibCall::CeilF64,
(types::F32, Opcode::Floor) => LibCall::FloorF32,
(types::F64, Opcode::Floor) => LibCall::FloorF64,
(types::F32, Opcode::Nearest) => LibCall::NearestF32,
(types::F64, Opcode::Nearest) => LibCall::NearestF64,
(types::F32, Opcode::Trunc) => LibCall::TruncF32,
(types::F64, Opcode::Trunc) => LibCall::TruncF64,
_ => panic!(
"unexpected type/opcode {:?}/{:?} in Ceil/Floor/Nearest/Trunc",
ty, op
),
};
emit_vm_call(ctx, flags, triple, libcall, insn, inputs, outputs)?;
if !ty.is_vector() {
let libcall = match (op, ty) {
(Opcode::Ceil, types::F32) => LibCall::CeilF32,
(Opcode::Ceil, types::F64) => LibCall::CeilF64,
(Opcode::Floor, types::F32) => LibCall::FloorF32,
(Opcode::Floor, types::F64) => LibCall::FloorF64,
(Opcode::Nearest, types::F32) => LibCall::NearestF32,
(Opcode::Nearest, types::F64) => LibCall::NearestF64,
(Opcode::Trunc, types::F32) => LibCall::TruncF32,
(Opcode::Trunc, types::F64) => LibCall::TruncF64,
_ => panic!(
"unexpected type/opcode {:?}/{:?} in Ceil/Floor/Nearest/Trunc",
ty, op
),
};
emit_vm_call(ctx, flags, triple, libcall, insn, inputs, outputs)?;
} else {
let (op, mode) = match (op, ty) {
(Opcode::Ceil, types::F32X4) => (SseOpcode::Roundps, RoundImm::RoundUp),
(Opcode::Ceil, types::F64X2) => (SseOpcode::Roundpd, RoundImm::RoundUp),
(Opcode::Floor, types::F32X4) => (SseOpcode::Roundps, RoundImm::RoundDown),
(Opcode::Floor, types::F64X2) => (SseOpcode::Roundpd, RoundImm::RoundDown),
(Opcode::Trunc, types::F32X4) => (SseOpcode::Roundps, RoundImm::RoundZero),
(Opcode::Trunc, types::F64X2) => (SseOpcode::Roundpd, RoundImm::RoundZero),
(Opcode::Nearest, types::F32X4) => (SseOpcode::Roundps, RoundImm::RoundNearest),
(Opcode::Nearest, types::F64X2) => (SseOpcode::Roundpd, RoundImm::RoundNearest),
_ => panic!("Unknown op/ty combination (vector){:?}", ty),
};
let src = put_input_in_reg(ctx, inputs[0]);
let dst = get_output_reg(ctx, outputs[0]);
ctx.emit(Inst::gen_move(dst, src, ty));
ctx.emit(Inst::xmm_rm_r_imm(
op,
RegMem::from(dst),
dst,
mode.encode(),
false,
));
}
}
Opcode::Load
@@ -4079,8 +4337,8 @@ impl LowerBackend for X64Backend {
let ty = ctx.input_ty(ifcmp_sp, 0);
ctx.emit(Inst::cmp_rmi_r(
ty.bytes() as u8,
RegMemImm::reg(operand),
regs::rsp(),
RegMemImm::reg(regs::rsp()),
operand,
));
let cond_code = ctx.data(branches[0]).cond_code().unwrap();
let cc = CC::from_intcc(cond_code);

View File

@@ -92,15 +92,15 @@ impl MachBackend for X64Backend {
}
fn unsigned_add_overflow_condition(&self) -> IntCC {
// Unsigned `>=`; this corresponds to the carry flag set on x86, which happens on
// overflow of an add.
IntCC::UnsignedGreaterThanOrEqual
// Unsigned `<`; this corresponds to the carry flag set on x86, which
// indicates an add has overflowed.
IntCC::UnsignedLessThan
}
fn unsigned_sub_overflow_condition(&self) -> IntCC {
// unsigned `>=`; this corresponds to the carry flag set on x86, which happens on
// underflow of a subtract (carry is borrow for subtract).
IntCC::UnsignedGreaterThanOrEqual
// unsigned `<`; this corresponds to the carry flag set on x86, which
// indicates a sub has underflowed (carry is borrow for subtract).
IntCC::UnsignedLessThan
}
#[cfg(feature = "unwind")]

View File

@@ -659,7 +659,7 @@ fn narrow_load(
inst: ir::Inst,
func: &mut ir::Function,
_cfg: &mut ControlFlowGraph,
_isa: &dyn TargetIsa,
isa: &dyn TargetIsa,
) {
let mut pos = FuncCursor::new(func).at_inst(inst);
pos.use_srcloc(inst);
@@ -684,6 +684,10 @@ fn narrow_load(
ptr,
offset.try_add_i64(8).expect("load offset overflow"),
);
let (al, ah) = match flags.endianness(isa.endianness()) {
ir::Endianness::Little => (al, ah),
ir::Endianness::Big => (ah, al),
};
pos.func.dfg.replace(inst).iconcat(al, ah);
}
@@ -692,7 +696,7 @@ fn narrow_store(
inst: ir::Inst,
func: &mut ir::Function,
_cfg: &mut ControlFlowGraph,
_isa: &dyn TargetIsa,
isa: &dyn TargetIsa,
) {
let mut pos = FuncCursor::new(func).at_inst(inst);
pos.use_srcloc(inst);
@@ -708,6 +712,10 @@ fn narrow_store(
};
let (al, ah) = pos.ins().isplit(val);
let (al, ah) = match flags.endianness(isa.endianness()) {
ir::Endianness::Little => (al, ah),
ir::Endianness::Big => (ah, al),
};
pos.ins().store(flags, al, ptr, offset);
pos.ins().store(
flags,

View File

@@ -61,8 +61,8 @@ pub fn do_licm(
domtree.compute(func, cfg);
}
// Insert a pre-header before the header, modifying the function layout and CFG to reflect it.
// A jump instruction to the header is placed at the end of the pre-header.
/// Insert a pre-header before the header, modifying the function layout and CFG to reflect it.
/// A jump instruction to the header is placed at the end of the pre-header.
fn create_pre_header(
isa: &dyn TargetIsa,
header: Block,
@@ -81,30 +81,31 @@ fn create_pre_header(
for typ in header_args_types {
pre_header_args_value.push(func.dfg.append_block_param(pre_header, typ), pool);
}
for BlockPredecessor {
inst: last_inst, ..
} in cfg.pred_iter(header)
{
// We only follow normal edges (not the back edges)
if !domtree.dominates(header, last_inst, &func.layout) {
func.change_branch_destination(last_inst, pre_header);
func.rewrite_branch_destination(last_inst, header, pre_header);
}
}
{
let mut pos = EncCursor::new(func, isa).at_top(header);
// Inserts the pre-header at the right place in the layout.
pos.insert_block(pre_header);
pos.next_inst();
pos.ins().jump(header, pre_header_args_value.as_slice(pool));
}
// Inserts the pre-header at the right place in the layout.
let mut pos = EncCursor::new(func, isa).at_top(header);
pos.insert_block(pre_header);
pos.next_inst();
pos.ins().jump(header, pre_header_args_value.as_slice(pool));
pre_header
}
// Detects if a loop header has a natural pre-header.
//
// A loop header has a pre-header if there is only one predecessor that the header doesn't
// dominate.
// Returns the pre-header Block and the instruction jumping to the header.
/// Detects if a loop header has a natural pre-header.
///
/// A loop header has a pre-header if there is only one predecessor that the header doesn't
/// dominate.
/// Returns the pre-header Block and the instruction jumping to the header.
fn has_pre_header(
layout: &Layout,
cfg: &ControlFlowGraph,
@@ -176,9 +177,9 @@ fn is_loop_invariant(inst: Inst, dfg: &DataFlowGraph, loop_values: &FxHashSet<Va
true
}
// Traverses a loop in reverse post-order from a header block and identify loop-invariant
// instructions. These loop-invariant instructions are then removed from the code and returned
// (in reverse post-order) for later use.
/// Traverses a loop in reverse post-order from a header block and identify loop-invariant
/// instructions. These loop-invariant instructions are then removed from the code and returned
/// (in reverse post-order) for later use.
fn remove_loop_invariant_instructions(
lp: Loop,
func: &mut Function,

View File

@@ -314,6 +314,9 @@ pub trait ABIMachineSpec {
/// Generate the usual frame-restore sequence for this architecture.
fn gen_epilogue_frame_restore() -> SmallVec<[Self::I; 2]>;
/// Generate a probestack call.
fn gen_probestack(_frame_size: u32) -> SmallVec<[Self::I; 2]>;
/// Generate a clobber-save sequence. This takes the list of *all* registers
/// written/modified by the function body. The implementation here is
/// responsible for determining which of these are callee-saved according to
@@ -481,6 +484,9 @@ pub struct ABICalleeImpl<M: ABIMachineSpec> {
/// manually register-allocated and carefully only use caller-saved
/// registers and keep nothing live after this sequence of instructions.
stack_limit: Option<(Reg, Vec<M::I>)>,
/// Are we to invoke the probestack function in the prologue? If so,
/// what is the minimum size at which we must invoke it?
probestack_min_frame: Option<u32>,
_mach: PhantomData<M>,
}
@@ -536,6 +542,18 @@ impl<M: ABIMachineSpec> ABICalleeImpl<M> {
.map(|reg| (reg, Vec::new()))
.or_else(|| f.stack_limit.map(|gv| gen_stack_limit::<M>(f, &sig, gv)));
// Determine whether a probestack call is required for large enough
// frames (and the minimum frame size if so).
let probestack_min_frame = if flags.enable_probestack() {
assert!(
!flags.probestack_func_adjusts_sp(),
"SP-adjusting probestack not supported in new backends"
);
Some(1 << flags.probestack_size_log2())
} else {
None
};
Ok(Self {
sig,
stackslots,
@@ -550,6 +568,7 @@ impl<M: ABIMachineSpec> ABICalleeImpl<M> {
flags,
is_leaf: f.is_leaf(),
stack_limit,
probestack_min_frame,
_mach: PhantomData,
})
}
@@ -978,6 +997,11 @@ impl<M: ABIMachineSpec> ABICallee for ABICalleeImpl<M> {
insts.extend_from_slice(stack_limit_load);
self.insert_stack_check(*reg, total_stacksize, &mut insts);
}
if let Some(min_frame) = &self.probestack_min_frame {
if total_stacksize >= *min_frame {
insts.extend(M::gen_probestack(total_stacksize));
}
}
}
if total_stacksize > 0 {
self.fixed_frame_storage_size += total_stacksize;

View File

@@ -674,6 +674,12 @@ impl<I: VCodeInst> MachBuffer<I> {
// (end of buffer)
self.data.truncate(b.start as usize);
self.fixup_records.truncate(b.fixup);
while let Some(last_srcloc) = self.srclocs.last() {
if last_srcloc.end <= b.start {
break;
}
self.srclocs.pop();
}
// State:
// [PRE CODE]
// cur_off, Offset b.start, b.labels_at_this_branch:
@@ -1184,12 +1190,15 @@ impl<I: VCodeInst> MachBuffer<I> {
// incorrect.
assert!(self.fixup_records.is_empty());
let mut srclocs = self.srclocs;
srclocs.sort_by_key(|entry| entry.start);
MachBufferFinalized {
data: self.data,
relocs: self.relocs,
traps: self.traps,
call_sites: self.call_sites,
srclocs: self.srclocs,
srclocs,
stack_maps: self.stack_maps,
}
}

View File

@@ -913,6 +913,7 @@ impl<'func, I: VCodeInst> Lower<'func, I> {
// the code-motion.
if self.cur_scan_entry_color.is_some()
&& self.value_uses[val] == 1
&& self.value_lowered_uses[val] == 0
&& self.num_outputs(src_inst) == 1
&& self
.side_effect_inst_entry_colors

View File

@@ -1312,19 +1312,31 @@ unsafe impl<'a, 'b> InstructionSet<'b> for &'a dyn TargetIsa {
}
#[cfg(test)]
#[cfg(feature = "x86")]
#[cfg(any(feature = "x64", feature = "x86", feature = "arm64"))]
mod tests {
use super::*;
use crate::isa::lookup;
use crate::isa::{lookup, TargetIsa};
use crate::settings::{builder, Flags};
use std::str::FromStr;
use target_lexicon::triple;
fn isa() -> Box<dyn TargetIsa> {
// We need a triple to instantiate and run the peephole optimizer, but we
// don't care which one when we're just trying to trigger a rebuild of the
// peephole optimizer (it doesn't affect the serialized bytes at all).
let triple = if cfg!(any(feature = "x64", feature = "x86")) {
triple!("x86_64")
} else if cfg!(feature = "arm64") {
triple!("aarch64")
} else {
panic!("unknown arch")
};
lookup(triple).unwrap().finish(Flags::new(builder()))
}
#[test]
fn get_peepmatic_preopt() {
let isa = lookup(triple!("x86_64"))
.expect("expect x86 ISA")
.finish(Flags::new(builder()));
let isa = isa();
let _ = preopt(&*isa);
}
}

View File

@@ -142,7 +142,49 @@ fn harvest_candidate_lhs(
let souper_assignment_rhs = match func.dfg.value_def(val) {
ir::ValueDef::Result(inst, 0) => {
let args = func.dfg.inst_args(inst);
let arg = |allocs: &mut Allocs, n| allocs.ir_to_souper_val[&args[n]].into();
// Get the n^th argument as a souper operand.
let arg = |allocs: &mut Allocs, n| {
let arg = args[n];
if let Some(a) = allocs.ir_to_souper_val.get(&arg).copied() {
a.into()
} else {
// The only arguments we get that we haven't already
// converted into a souper instruction are `iconst`s and
// `bconst`s. This is because souper only allows
// constants as operands, and it doesn't allow assigning
// constants to a variable name. So we lazily convert
// `iconst`s and `bconst`s into souper operands here,
// when they are actually used.
match func.dfg.value_def(arg) {
ir::ValueDef::Result(inst, 0) => match func.dfg[inst] {
ir::InstructionData::UnaryImm { opcode, imm } => {
debug_assert_eq!(opcode, ir::Opcode::Iconst);
let imm: i64 = imm.into();
ast::Operand::Constant(ast::Constant {
value: imm.into(),
r#type: souper_type_of(&func.dfg, arg),
})
}
ir::InstructionData::UnaryBool { opcode, imm } => {
debug_assert_eq!(opcode, ir::Opcode::Iconst);
ast::Operand::Constant(ast::Constant {
value: imm.into(),
r#type: souper_type_of(&func.dfg, arg),
})
}
_ => unreachable!(
"only iconst and bconst instructions \
aren't in `ir_to_souper_val`"
),
},
_ => unreachable!(
"only iconst and bconst instructions \
aren't in `ir_to_souper_val`"
),
}
}
};
match (func.dfg[inst].opcode(), &func.dfg[inst]) {
(ir::Opcode::Iadd, _) => {
@@ -350,6 +392,28 @@ fn harvest_candidate_lhs(
}
(ir::Opcode::Select, _) => {
let a = arg(allocs, 0);
// While Cranelift allows any width condition for
// `select`, Souper requires an `i1`.
let a = match a {
ast::Operand::Value(id) => match lhs.get_value(id).r#type {
Some(ast::Type { width: 1 }) => a,
_ => lhs
.assignment(
None,
Some(ast::Type { width: 1 }),
ast::Instruction::Trunc { a },
vec![],
)
.into(),
},
ast::Operand::Constant(ast::Constant { value, .. }) => ast::Constant {
value: (value != 0) as _,
r#type: Some(ast::Type { width: 1 }),
}
.into(),
};
let b = arg(allocs, 1);
let c = arg(allocs, 2);
ast::Instruction::Select { a, b, c }.into()
@@ -421,23 +485,13 @@ fn harvest_candidate_lhs(
let b = arg(allocs, 1);
ast::Instruction::UsubSat { a, b }.into()
}
(ir::Opcode::Iconst, ir::InstructionData::UnaryImm { imm, .. }) => {
let value: i64 = (*imm).into();
let value: i128 = value.into();
ast::Constant {
value,
r#type: souper_type_of(&func.dfg, val),
}
.into()
}
(ir::Opcode::Bconst, ir::InstructionData::UnaryBool { imm, .. }) => {
let value = *imm as i128;
ast::Constant {
value,
r#type: souper_type_of(&func.dfg, val),
}
.into()
}
// Because Souper doesn't allow constants to be on the right
// hand side of an assignment (i.e. `%0:i32 = 1234` is
// disallowed) we have to ignore `iconst` and `bconst`
// instructions until we process them as operands for some
// other instruction. See the `arg` closure above for
// details.
(ir::Opcode::Iconst, _) | (ir::Opcode::Bconst, _) => return,
_ => ast::AssignmentRhs::Var,
}
}

View File

@@ -52,6 +52,6 @@
emits native object files using the
`object <https://github.com/gimli-rs/object>`_ library.
- [cranelift-simplejit](https://docs.rs/cranelift-simplejit)
This crate provides a simple JIT backend for `cranelift-module`, which
- [cranelift-jit](https://docs.rs/cranelift-jit)
This crate provides a JIT backend for `cranelift-module`, which
emits code and data into memory.

View File

@@ -344,3 +344,69 @@ block0(v0: i64, v1: i32):
; nextln: mov sp, fp
; nextln: ldp fp, lr, [sp], #16
; nextln: ret
function %f18(i64, i64, i64) -> i32 {
block0(v0: i64, v1: i64, v2: i64):
v3 = iconst.i32 -4098
v6 = uextend.i64 v3
v5 = sload16.i32 v6+0
return v5
}
; check: stp fp, lr, [sp, #-16]!
; nextln: mov fp, sp
; nextln: movn w0, #4097
; nextln: ldrsh x0, [x0]
; nextln: mov sp, fp
; nextln: ldp fp, lr, [sp], #16
; nextln: ret
function %f19(i64, i64, i64) -> i32 {
block0(v0: i64, v1: i64, v2: i64):
v3 = iconst.i32 4098
v6 = uextend.i64 v3
v5 = sload16.i32 v6+0
return v5
}
; check: stp fp, lr, [sp, #-16]!
; nextln: mov fp, sp
; nextln: movz x0, #4098
; nextln: ldrsh x0, [x0]
; nextln: mov sp, fp
; nextln: ldp fp, lr, [sp], #16
; nextln: ret
function %f20(i64, i64, i64) -> i32 {
block0(v0: i64, v1: i64, v2: i64):
v3 = iconst.i32 -4098
v6 = sextend.i64 v3
v5 = sload16.i32 v6+0
return v5
}
; check: stp fp, lr, [sp, #-16]!
; nextln: mov fp, sp
; nextln: movn w0, #4097
; nextln: sxtw x0, w0
; nextln: ldrsh x0, [x0]
; nextln: mov sp, fp
; nextln: ldp fp, lr, [sp], #16
; nextln: ret
function %f21(i64, i64, i64) -> i32 {
block0(v0: i64, v1: i64, v2: i64):
v3 = iconst.i32 4098
v6 = sextend.i64 v3
v5 = sload16.i32 v6+0
return v5
}
; check: stp fp, lr, [sp, #-16]!
; nextln: mov fp, sp
; nextln: movz x0, #4098
; nextln: sxtw x0, w0
; nextln: ldrsh x0, [x0]
; nextln: mov sp, fp
; nextln: ldp fp, lr, [sp], #16
; nextln: ret

View File

@@ -715,7 +715,7 @@ block0(v0: f32):
; nextln: movz x0, #20352, LSL #16
; nextln: fmov d1, x0
; nextln: fmin s2, s0, s1
; nextln: movi v1.8b, #0
; nextln: movi v1.2s, #0
; nextln: fmax s2, s2, s1
; nextln: fcmp s0, s0
; nextln: fcsel s0, s1, s2, ne
@@ -738,7 +738,7 @@ block0(v0: f32):
; nextln: movz x0, #52992, LSL #16
; nextln: fmov d2, x0
; nextln: fmax s1, s1, s2
; nextln: movi v2.8b, #0
; nextln: movi v2.2s, #0
; nextln: fcmp s0, s0
; nextln: fcsel s0, s2, s1, ne
; nextln: fcvtzs w0, s0
@@ -757,7 +757,7 @@ block0(v0: f32):
; nextln: movz x0, #24448, LSL #16
; nextln: fmov d1, x0
; nextln: fmin s2, s0, s1
; nextln: movi v1.8b, #0
; nextln: movi v1.2s, #0
; nextln: fmax s2, s2, s1
; nextln: fcmp s0, s0
; nextln: fcsel s0, s1, s2, ne
@@ -780,7 +780,7 @@ block0(v0: f32):
; nextln: movz x0, #57088, LSL #16
; nextln: fmov d2, x0
; nextln: fmax s1, s1, s2
; nextln: movi v2.8b, #0
; nextln: movi v2.2s, #0
; nextln: fcmp s0, s0
; nextln: fcsel s0, s2, s1, ne
; nextln: fcvtzs x0, s0
@@ -798,7 +798,7 @@ block0(v0: f64):
; nextln: mov fp, sp
; nextln: ldr d1, pc+8 ; b 12 ; data.f64 4294967295
; nextln: fmin d2, d0, d1
; nextln: movi v1.8b, #0
; nextln: movi v1.2s, #0
; nextln: fmax d2, d2, d1
; nextln: fcmp d0, d0
; nextln: fcsel d0, d1, d2, ne
@@ -820,7 +820,7 @@ block0(v0: f64):
; nextln: movz x0, #49632, LSL #48
; nextln: fmov d2, x0
; nextln: fmax d1, d1, d2
; nextln: movi v2.8b, #0
; nextln: movi v2.2s, #0
; nextln: fcmp d0, d0
; nextln: fcsel d0, d2, d1, ne
; nextln: fcvtzs w0, d0
@@ -839,7 +839,7 @@ block0(v0: f64):
; nextln: movz x0, #17392, LSL #48
; nextln: fmov d1, x0
; nextln: fmin d2, d0, d1
; nextln: movi v1.8b, #0
; nextln: movi v1.2s, #0
; nextln: fmax d2, d2, d1
; nextln: fcmp d0, d0
; nextln: fcsel d0, d1, d2, ne
@@ -862,7 +862,7 @@ block0(v0: f64):
; nextln: movz x0, #50144, LSL #48
; nextln: fmov d2, x0
; nextln: fmax d1, d1, d2
; nextln: movi v2.8b, #0
; nextln: movi v2.2s, #0
; nextln: fcmp d0, d0
; nextln: fcsel d0, d2, d1, ne
; nextln: fcvtzs x0, d0

View File

@@ -127,3 +127,46 @@ block0(v0: i64, v1: i64):
; nextln: mov sp, fp
; nextln: ldp fp, lr, [sp], #16
; nextln: ret
function %f9() -> i32x2 {
block0:
v0 = iconst.i32 4278190335
v1 = splat.i32x2 v0
return v1
}
; check: stp fp, lr, [sp, #-16]!
; nextln: mov fp, sp
; nextln: movi v0.2d, #18374687579166474495
; nextln: fmov d0, d0
; nextln: mov sp, fp
; nextln: ldp fp, lr, [sp], #16
; nextln: ret
function %f10() -> i32x4 {
block0:
v0 = iconst.i32 4293918720
v1 = splat.i32x4 v0
return v1
}
; check: stp fp, lr, [sp, #-16]!
; nextln: mov fp, sp
; nextln: mvni v0.4s, #15, MSL #16
; nextln: mov sp, fp
; nextln: ldp fp, lr, [sp], #16
; nextln: ret
function %f11() -> f32x4 {
block0:
v0 = f32const 0x1.5
v1 = splat.f32x4 v0
return v1
}
; check: stp fp, lr, [sp, #-16]!
; nextln: mov fp, sp
; nextln: fmov v0.4s, #1.3125
; nextln: mov sp, fp
; nextln: ldp fp, lr, [sp], #16
; nextln: ret

View File

@@ -7,7 +7,7 @@ block0(v0: i64, v1: i64):
v2 = iadd v0, v1
v3 = load.i64 v2
return v3
; check: movq 0(%rdi,%rsi,1), %r12
; check: movq 0(%rdi,%rsi,1), %rsi
}
function %amode_add_imm(i64) -> i64 {
@@ -16,7 +16,7 @@ block0(v0: i64):
v2 = iadd v0, v1
v3 = load.i64 v2
return v3
; check: movq 42(%rdi), %r12
; check: movq 42(%rdi), %rsi
}
;; Same as above, but add operands have been reversed.
@@ -26,7 +26,7 @@ block0(v0: i64):
v2 = iadd v1, v0
v3 = load.i64 v2
return v3
; check: movq 42(%rdi), %r12
; check: movq 42(%rdi), %rsi
}
;; Make sure that uextend(cst) are ignored when the cst will naturally sign-extend.
@@ -37,5 +37,5 @@ block0(v0: i64):
v3 = iadd v2, v0
v4 = load.i64 v3
return v4
; check: movq 42(%rdi), %r12
; check: movq 42(%rdi), %rsi
}

View File

@@ -0,0 +1,23 @@
test compile
target x86_64
feature "experimental_x64"
function %f(i32, i64 vmctx) -> i64 {
gv0 = vmctx
gv1 = load.i64 notrap aligned gv0+0
gv2 = load.i32 notrap aligned gv0+8
heap0 = dynamic gv1, bound gv2, offset_guard 0x1000, index_type i32
block0(v0: i32, v1: i64):
v2 = heap_addr.i64 heap0, v0, 0x8000
; check: movl 8(%rsi), %ecx
; nextln: movq %rdi, %rax
; nextln: addl $$32768, %eax
; nextln: jnb ; ud2 heap_oob ;
; nextln: cmpl %ecx, %eax
; nextln: jbe label1; j label2
; check: Block 1:
return v2
}

View File

@@ -6,7 +6,7 @@ function %add_from_mem_u32_1(i64, i32) -> i32 {
block0(v0: i64, v1: i32):
v2 = load.i32 v0
v3 = iadd.i32 v2, v1
; check: addl 0(%rdi), %r12d
; check: addl 0(%rdi), %esi
return v3
}
@@ -14,7 +14,7 @@ function %add_from_mem_u32_2(i64, i32) -> i32 {
block0(v0: i64, v1: i32):
v2 = load.i32 v0
v3 = iadd.i32 v1, v2
; check: addl 0(%rdi), %r12d
; check: addl 0(%rdi), %esi
return v3
}
@@ -22,7 +22,7 @@ function %add_from_mem_u64_1(i64, i64) -> i64 {
block0(v0: i64, v1: i64):
v2 = load.i64 v0
v3 = iadd.i64 v2, v1
; check: addq 0(%rdi), %r12
; check: addq 0(%rdi), %rsi
return v3
}
@@ -30,7 +30,7 @@ function %add_from_mem_u64_2(i64, i64) -> i64 {
block0(v0: i64, v1: i64):
v2 = load.i64 v0
v3 = iadd.i64 v1, v2
; check: addq 0(%rdi), %r12
; check: addq 0(%rdi), %rsi
return v3
}
@@ -40,7 +40,22 @@ function %add_from_mem_not_narrow(i64, i8) -> i8 {
block0(v0: i64, v1: i8):
v2 = load.i8 v0
v3 = iadd.i8 v2, v1
; check: movzbq 0(%rdi), %r12
; nextln: addl %esi, %r12d
; check: movzbq 0(%rdi), %rdi
; nextln: addl %esi, %edi
return v3
}
function %no_merge_if_lookback_use(i64, i64) -> i64 {
block0(v0: i64, v1: i64):
v2 = load.i64 v0
v3 = iadd.i64 v2, v0
store.i64 v3, v1
v4 = load.i64 v3
return v4
; check: movq 0(%rdi), %rax
; nextln: movq %rax, %rcx
; nextln: addq %rdi, %rcx
; nextln: movq %rcx, 0(%rsi)
; nextln: movq 0(%rax,%rdi,1), %rsi
; nextln: movq %rsi, %rax
}

View File

@@ -0,0 +1,113 @@
test compile
target x86_64
feature "experimental_x64"
; TODO: test with popcnt feature available too, once new backend supports that.
function %popcnt64(i64) -> i64 {
block0(v0: i64):
v1 = popcnt v0
; check: movq %rdi, %rsi
; nextln: shrq $$1, %rsi
; nextln: movabsq $$8608480567731124087, %rax
; nextln: andq %rax, %rsi
; nextln: subq %rsi, %rdi
; nextln: shrq $$1, %rsi
; nextln: andq %rax, %rsi
; nextln: subq %rsi, %rdi
; nextln: shrq $$1, %rsi
; nextln: andq %rax, %rsi
; nextln: subq %rsi, %rdi
; nextln: movq %rdi, %rsi
; nextln: shrq $$4, %rsi
; nextln: addq %rdi, %rsi
; nextln: movabsq $$1085102592571150095, %rdi
; nextln: andq %rdi, %rsi
; nextln: movabsq $$72340172838076673, %rdi
; nextln: imulq %rdi, %rsi
; nextln: shrq $$56, %rsi
; nextln: movq %rsi, %rax
return v1
}
function %popcnt64load(i64) -> i64 {
block0(v0: i64):
v1 = load.i64 v0
v2 = popcnt v1
return v2
; check: movq 0(%rdi), %rdi
; nextln: movq %rdi, %rsi
; nextln: shrq $$1, %rsi
; nextln: movabsq $$8608480567731124087, %rax
; nextln: andq %rax, %rsi
; nextln: subq %rsi, %rdi
; nextln: shrq $$1, %rsi
; nextln: andq %rax, %rsi
; nextln: subq %rsi, %rdi
; nextln: shrq $$1, %rsi
; nextln: andq %rax, %rsi
; nextln: subq %rsi, %rdi
; nextln: movq %rdi, %rsi
; nextln: shrq $$4, %rsi
; nextln: addq %rdi, %rsi
; nextln: movabsq $$1085102592571150095, %rdi
; nextln: andq %rdi, %rsi
; nextln: movabsq $$72340172838076673, %rdi
; nextln: imulq %rdi, %rsi
; nextln: shrq $$56, %rsi
; nextln: movq %rsi, %rax
}
function %popcnt32(i32) -> i32 {
block0(v0: i32):
v1 = popcnt v0
return v1
; check: movq %rdi, %rsi
; nextln: shrl $$1, %esi
; nextln: andl $$2004318071, %esi
; nextln: subl %esi, %edi
; nextln: shrl $$1, %esi
; nextln: andl $$2004318071, %esi
; nextln: subl %esi, %edi
; nextln: shrl $$1, %esi
; nextln: andl $$2004318071, %esi
; nextln: subl %esi, %edi
; nextln: movq %rdi, %rsi
; nextln: shrl $$4, %esi
; nextln: addl %edi, %esi
; nextln: andl $$252645135, %esi
; nextln: imull $$16843009, %esi
; nextln: shrl $$24, %esi
; nextln: movq %rsi, %rax
; nextln: movq %rbp, %rsp
; nextln: popq %rbp
; nextln: ret
}
function %popcnt32load(i64) -> i32 {
block0(v0: i64):
v1 = load.i32 v0
v2 = popcnt v1
return v2
; check: movl 0(%rdi), %edi
; nextln: movq %rdi, %rsi
; nextln: shrl $$1, %esi
; nextln: andl $$2004318071, %esi
; nextln: subl %esi, %edi
; nextln: shrl $$1, %esi
; nextln: andl $$2004318071, %esi
; nextln: subl %esi, %edi
; nextln: shrl $$1, %esi
; nextln: andl $$2004318071, %esi
; nextln: subl %esi, %edi
; nextln: movq %rdi, %rsi
; nextln: shrl $$4, %esi
; nextln: addl %edi, %esi
; nextln: andl $$252645135, %esi
; nextln: imull $$16843009, %esi
; nextln: shrl $$24, %esi
; nextln: movq %rsi, %rax
; nextln: movq %rbp, %rsp
; nextln: popq %rbp
; nextln: ret
}

View File

@@ -0,0 +1,17 @@
test compile
set enable_probestack=true
target x86_64
feature "experimental_x64"
function %f1() -> i64 {
ss0 = explicit_slot 100000
block0:
v1 = stack_addr.i64 ss0
return v1
}
; check: pushq %rbp
; nextln: movq %rsp, %rbp
; nextln: movl $$100000, %eax
; nextln: call LibCall(Probestack)

View File

@@ -28,9 +28,9 @@ block0(v0: i32):
}
; check: movd %edi, %xmm1
; nextln: psllw %xmm1, %xmm0
; nextln: lea const(VCodeConstant(0)), %r12
; nextln: lea const(VCodeConstant(0)), %rsi
; nextln: shlq $$4, %rdi
; nextln: movdqu 0(%r12,%rdi,1), %xmm1
; nextln: movdqu 0(%rsi,%rdi,1), %xmm1
; nextln: pand %xmm1, %xmm0
function %ushr_i8x16_imm() -> i8x16 {
@@ -81,12 +81,12 @@ block0(v0: i64x2, v1: i32):
v2 = sshr v0, v1
return v2
}
; check: pextrd.w $$0, %xmm0, %r12
; nextln: pextrd.w $$1, %xmm0, %r13
; check: pextrd.w $$0, %xmm0, %rsi
; nextln: pextrd.w $$1, %xmm0, %rax
; nextln: movq %rdi, %rcx
; nextln: sarq %cl, %r12
; nextln: sarq %cl, %rsi
; nextln: movq %rdi, %rcx
; nextln: sarq %cl, %r13
; nextln: pinsrd.w $$0, %r12, %xmm1
; nextln: pinsrd.w $$1, %r13, %xmm1
; nextln: sarq %cl, %rax
; nextln: pinsrd.w $$0, %rsi, %xmm1
; nextln: pinsrd.w $$1, %rax, %xmm1
; nextln: movdqa %xmm1, %xmm0

View File

@@ -70,8 +70,8 @@ block0:
return v1
}
; check: uninit %xmm0
; nextln: pinsrw $$0, %r12, %xmm0
; nextln: pinsrw $$1, %r12, %xmm0
; nextln: pinsrw $$0, %rsi, %xmm0
; nextln: pinsrw $$1, %rsi, %xmm0
; nextln: pshufd $$0, %xmm0, %xmm0
function %splat_i32(i32) -> i32x4 {

View File

@@ -17,7 +17,7 @@ block0(v0: b32x4):
return v1
}
; check: ptest %xmm0, %xmm0
; nextln: setnz %r12b
; nextln: setnz %sil
function %vall_true_i64x2(i64x2) -> b1 {
block0(v0: i64x2):
@@ -27,4 +27,4 @@ block0(v0: i64x2):
; check: pxor %xmm1, %xmm1
; nextln: pcmpeqq %xmm0, %xmm1
; nextln: ptest %xmm1, %xmm1
; nextln: setz %r12b
; nextln: setz %sil

View File

@@ -44,9 +44,9 @@ block0:
function %zero_byte() -> i8 fast {
block0:
; asm: xor %al, %al
[-,%rax] v0 = iconst.i8 0 ; bin: 30 c0
; asm: xor %dh, %dh
[-,%rdi] v1 = iconst.i8 0 ; bin: 30 ff
; asm: xor %eax, %eax
[-,%rax] v0 = iconst.i8 0 ; bin: 31 c0
; asm: xor %edi, %edi
[-,%rdi] v1 = iconst.i8 0 ; bin: 31 ff
return v0
}

View File

@@ -62,11 +62,11 @@ block0:
function %zero_byte() -> i8 fast {
block0:
; asm: xor %r8b, %r8b
[-,%r15] v0 = iconst.i8 0 ; bin: 45 30 ff
; asm: xor %al, %al
[-,%rax] v1 = iconst.i8 0 ; bin: 30 c0
; asm: xor %dh, %dh
[-,%rdi] v2 = iconst.i8 0 ; bin: 30 ff
; asm: xor %r8d, %r8d
[-,%r15] v0 = iconst.i8 0 ; bin: 45 31 ff
; asm: xor %eax, eax
[-,%rax] v1 = iconst.i8 0 ; bin: 31 c0
; asm: xor %edi, %edi
[-,%rdi] v2 = iconst.i8 0 ; bin: 31 ff
return v0
}

View File

@@ -0,0 +1,19 @@
test compile
set opt_level=speed_and_size
target x86_64
function %br_table_opt() {
jt0 = jump_table [block1, block2]
block0:
v0 = iconst.i32 1
br_table v0, block2, jt0
block1:
return
block2:
v1 = iconst.i32 1
jump block2
}

View File

@@ -0,0 +1,28 @@
test licm
target aarch64
function %rewrite_jump_table() {
jt0 = jump_table [block1, block2]
block0:
v0 = iconst.i64 1
v1 = jump_table_base.i64 jt0
v2 = jump_table_entry.i64 v0, v1, 4, jt0
v3 = iadd v1, v2
indirect_jump_table_br v3, jt0
block1:
return
block2:
v4 = bconst.b1 false
jump block2
}
; sameln: function
; nextln: jt0 = jump_table [block1, block3]
; check: block3:
; nextln: v4 = bconst.b1 false
; nextln: jump block2
; check: block2:
; nextln: jump block2

View File

@@ -21,7 +21,7 @@ use thiserror::Error;
/// `CompiledFunction`s and subsequently calling them through the use of a `Trampoline`. As its
/// name indicates, this compiler is limited: any functionality that requires knowledge of things
/// outside the [Function] will likely not work (e.g. global values, calls). For an example of this
/// "outside-of-function" functionality, see `cranelift_simplejit::backend::SimpleJITBackend`.
/// "outside-of-function" functionality, see `cranelift_jit::backend::JITBackend`.
///
/// ```
/// use cranelift_filetests::SingleFunctionCompiler;

View File

@@ -1,52 +1,74 @@
//! Implements the function environment (e.g. a name-to-function mapping) for interpretation.
use cranelift_codegen::ir::{FuncRef, Function};
use cranelift_entity::{entity_impl, PrimaryMap};
use std::collections::HashMap;
/// A function store contains all of the functions that are accessible to an interpreter.
#[derive(Default, Clone)]
pub struct FunctionStore<'a> {
functions: HashMap<FuncRef, &'a Function>,
function_name_to_func_ref: HashMap<String, FuncRef>,
functions: PrimaryMap<FuncIndex, &'a Function>,
function_names: HashMap<String, FuncIndex>,
}
/// An opaque reference to a [`Function`](Function) stored in the [FunctionStore].
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct FuncIndex(u32);
entity_impl!(FuncIndex, "fn");
/// This is a helpful conversion for instantiating a store from a single [Function].
impl<'a> From<&'a Function> for FunctionStore<'a> {
fn from(f: &'a Function) -> Self {
let func_ref = FuncRef::from_u32(0);
let mut function_name_to_func_ref = HashMap::new();
function_name_to_func_ref.insert(f.name.to_string(), func_ref);
let mut functions = HashMap::new();
functions.insert(func_ref, f);
Self {
functions,
function_name_to_func_ref,
}
fn from(function: &'a Function) -> Self {
let mut store = FunctionStore::default();
store.add(function.name.to_string(), function);
store
}
}
impl<'a> FunctionStore<'a> {
/// Add a function by name.
pub fn add(&mut self, name: String, function: &'a Function) {
let func_ref = FuncRef::with_number(self.function_name_to_func_ref.len() as u32)
.expect("a valid function reference");
self.function_name_to_func_ref.insert(name, func_ref);
self.functions.insert(func_ref, function);
assert!(!self.function_names.contains_key(&name));
let index = self.functions.push(function);
self.function_names.insert(name, index);
}
/// Retrieve a reference to a function in the environment by its name.
pub fn index_of(&self, name: &str) -> Option<FuncRef> {
self.function_name_to_func_ref.get(name).cloned()
/// Retrieve the index of a function in the function store by its `name`.
pub fn index_of(&self, name: &str) -> Option<FuncIndex> {
self.function_names.get(name).cloned()
}
/// Retrieve a function by its function reference.
pub fn get_by_func_ref(&self, func_ref: FuncRef) -> Option<&'a Function> {
self.functions.get(&func_ref).cloned()
/// Retrieve a function by its index in the function store.
pub fn get_by_index(&self, index: FuncIndex) -> Option<&'a Function> {
self.functions.get(index).cloned()
}
/// Retrieve a function by its name.
pub fn get_by_name(&self, name: &str) -> Option<&'a Function> {
let func_ref = self.index_of(name)?;
self.get_by_func_ref(func_ref)
let index = self.index_of(name)?;
self.get_by_index(index)
}
/// Retrieve a function from a [FuncRef] within a [Function]. TODO this should be optimized, if possible, as
/// currently it retrieves the function name as a string and performs string matching.
pub fn get_from_func_ref(
&self,
func_ref: FuncRef,
function: &Function,
) -> Option<&'a Function> {
self.get_by_name(&get_function_name(func_ref, function))
}
}
/// Retrieve a function name from a [FuncRef] within a [Function]. TODO this should be optimized, if possible, as
/// currently it retrieves the function name as a string and performs string matching.
fn get_function_name(func_ref: FuncRef, function: &Function) -> String {
function
.dfg
.ext_funcs
.get(func_ref)
.expect("function to exist")
.name
.to_string()
}
#[cfg(test)]
@@ -77,6 +99,6 @@ mod tests {
let signature = Signature::new(CallConv::Fast);
let func = &Function::with_name_signature(name, signature);
let env: FunctionStore = func.into();
assert_eq!(env.index_of("%test"), FuncRef::with_number(0));
assert_eq!(env.index_of("%test"), Some(FuncIndex::from_u32(0)));
}
}

View File

@@ -2,41 +2,45 @@
use cranelift_codegen::data_value::DataValue;
use cranelift_codegen::ir::{Function, Value as ValueRef};
use cranelift_entity::EntityRef;
use log::trace;
use std::collections::HashMap;
/// Holds the mutable elements of an interpretation. At some point I thought about using
/// Cell/RefCell to do field-level mutability, thinking that otherwise I would have to
/// pass around a mutable object (for inst and registers) and an immutable one (for function,
/// could be self)--in the end I decided to do exactly that but perhaps one day that will become
/// untenable.
/// The type used for ensuring [Frame](crate::frame::Frame) entries conform to the expected memory layout.
pub(crate) type Entries = Vec<Option<DataValue>>;
/// Holds the mutable elements of an interpreted function call.
#[derive(Debug)]
pub struct Frame<'a> {
/// The currently executing function.
pub function: &'a Function,
/// The current mapping of SSA value-references to their actual values.
registers: HashMap<ValueRef, DataValue>,
pub(crate) function: &'a Function,
/// The current mapping of SSA value-references to their actual values. For efficiency, each SSA value is used as an
/// index into the Vec, meaning some slots may be unused.
registers: Entries,
}
impl<'a> Frame<'a> {
/// Construct a new [Frame] for a function. This allocates a slot in the hash map for each SSA
/// `Value` (renamed to `ValueRef` here) which should mean that no additional allocations are
/// needed while interpreting the frame.
/// Construct a new [Frame] for a function. This allocates a slot in the hash map for each SSA `Value` (renamed to
/// `ValueRef` here) which should mean that no additional allocations are needed while interpreting the frame.
pub fn new(function: &'a Function) -> Self {
let num_slots = function.dfg.num_values();
trace!("Create new frame for function: {}", function.signature);
Self {
function,
registers: HashMap::with_capacity(function.dfg.num_values()),
registers: vec![None; num_slots],
}
}
/// Retrieve the actual value associated with an SSA reference.
#[inline]
pub fn get(&self, name: ValueRef) -> &DataValue {
assert!(name.index() < self.registers.len());
trace!("Get {}", name);
self.registers
.get(&name)
&self
.registers
.get(name.index())
.unwrap_or_else(|| panic!("unknown value: {}", name))
.as_ref()
.unwrap_or_else(|| panic!("empty slot: {}", name))
}
/// Retrieve multiple SSA references; see `get`.
@@ -47,8 +51,9 @@ impl<'a> Frame<'a> {
/// Assign `value` to the SSA reference `name`.
#[inline]
pub fn set(&mut self, name: ValueRef, value: DataValue) -> Option<DataValue> {
assert!(name.index() < self.registers.len());
trace!("Set {} -> {}", name, value);
self.registers.insert(name, value)
std::mem::replace(&mut self.registers[name.index()], Some(value))
}
/// Assign to multiple SSA references; see `set`.
@@ -66,12 +71,18 @@ impl<'a> Frame<'a> {
pub fn rename(&mut self, old_names: &[ValueRef], new_names: &[ValueRef]) {
trace!("Renaming {:?} -> {:?}", old_names, new_names);
assert_eq!(old_names.len(), new_names.len());
let mut registers = HashMap::with_capacity(self.registers.len());
for (on, nn) in old_names.iter().zip(new_names) {
let v = self.registers.get(on).unwrap().clone();
registers.insert(*nn, v);
let new_registers = vec![None; self.registers.len()];
let mut old_registers = std::mem::replace(&mut self.registers, new_registers);
self.registers = vec![None; self.registers.len()];
for (&on, &nn) in old_names.iter().zip(new_names) {
let value = std::mem::replace(&mut old_registers[on.index()], None);
self.registers[nn.index()] = value;
}
self.registers = registers;
}
/// Accessor for the current entries in the frame.
pub fn entries_mut(&mut self) -> &mut [Option<DataValue>] {
&mut self.registers
}
}
@@ -79,8 +90,15 @@ impl<'a> Frame<'a> {
mod tests {
use super::*;
use cranelift_codegen::data_value::DataValue;
use cranelift_codegen::ir::immediates::{Ieee32, Ieee64};
use cranelift_codegen::ir::InstBuilder;
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
use cranelift_reader::parse_functions;
/// Helper to create a function from CLIF IR.
fn function(code: &str) -> Function {
parse_functions(code).unwrap().into_iter().next().unwrap()
}
/// Build an empty function with a single return.
fn empty_function() -> Function {
@@ -101,23 +119,112 @@ mod tests {
}
#[test]
fn assignment() {
let func = empty_function();
fn assignment_and_retrieval() {
let func = function("function %test(i32) -> i32 { block0(v0:i32): return v0 }");
let mut frame = Frame::new(&func);
let a = ValueRef::with_number(1).unwrap();
let ssa_value_ref = ValueRef::from_u32(0);
let fortytwo = DataValue::I32(42);
frame.set(a, fortytwo.clone());
assert_eq!(frame.get(a), &fortytwo);
// Verify that setting a valid SSA ref will make the value retrievable.
frame.set(ssa_value_ref, fortytwo.clone());
assert_eq!(frame.get(ssa_value_ref), &fortytwo);
}
#[test]
#[should_panic]
fn no_existing_value() {
fn assignment_to_extra_slots() {
let func = function("function %test(i32) -> i32 { block0(v10:i32): return v10 }");
let mut frame = Frame::new(&func);
let ssa_value_ref = ValueRef::from_u32(5);
let fortytwo = DataValue::I32(42);
// Due to how Cranelift organizes its SSA values, the use of v10 defines 11 slots for values
// to fit in--the following should work.
frame.set(ssa_value_ref, fortytwo.clone());
assert_eq!(frame.get(ssa_value_ref), &fortytwo);
}
#[test]
#[should_panic(expected = "assertion failed: name.index() < self.registers.len()")]
fn invalid_assignment() {
let func = function("function %test(i32) -> i32 { block0(v10:i32): return v10 }");
let mut frame = Frame::new(&func);
let fortytwo = DataValue::I32(42);
// Since the SSA value ref points to 42 and the function only has 11 slots, this should
// fail. TODO currently this is a panic under the assumption we will not set indexes outside
// of the valid SSA value range but it might be better as a result.
frame.set(ValueRef::from_u32(11), fortytwo.clone());
}
#[test]
#[should_panic(expected = "assertion failed: name.index() < self.registers.len()")]
fn retrieve_nonexistent_value() {
let func = empty_function();
let frame = Frame::new(&func);
let ssa_value_ref = ValueRef::from_u32(1);
let a = ValueRef::with_number(1).unwrap();
frame.get(a);
// Retrieving a non-existent value should return an error.
frame.get(ssa_value_ref);
}
#[test]
#[should_panic(expected = "empty slot: v5")]
fn retrieve_and_assign_multiple_values() {
let func = function("function %test(i32) -> i32 { block0(v10:i32): return v10 }");
let mut frame = Frame::new(&func);
let ssa_value_refs = [
ValueRef::from_u32(2),
ValueRef::from_u32(4),
ValueRef::from_u32(6),
];
let values = vec![
DataValue::B(true),
DataValue::I8(42),
DataValue::F32(Ieee32::from(0.42)),
];
// We can assign and retrieve multiple (cloned) values.
frame.set_all(&ssa_value_refs, values.clone());
let retrieved_values = frame.get_all(&ssa_value_refs);
assert_eq!(values, retrieved_values);
// But if we attempt to retrieve an invalid value we should get an error:
frame.get_all(&[ValueRef::from_u32(2), ValueRef::from_u32(5)]);
}
#[test]
#[should_panic(expected = "empty slot: v10")]
fn rename() {
let func = function("function %test(i32) -> i32 { block0(v10:i32): return v10 }");
let mut frame = Frame::new(&func);
let old_ssa_value_refs = [ValueRef::from_u32(9), ValueRef::from_u32(10)];
let values = vec![DataValue::B(true), DataValue::F64(Ieee64::from(0.0))];
frame.set_all(&old_ssa_value_refs, values.clone());
// Rename the old SSA values to the new values.
let new_ssa_value_refs = [ValueRef::from_u32(4), ValueRef::from_u32(2)];
frame.rename(&old_ssa_value_refs, &new_ssa_value_refs);
// Now we should be able to retrieve new values and the old ones should fail.
assert_eq!(frame.get_all(&new_ssa_value_refs), values);
frame.get(ValueRef::from_u32(10));
}
#[test]
#[should_panic(expected = "empty slot: v2")]
fn rename_duplicates_causes_inconsistency() {
let func = function("function %test(i32) -> i32 { block0(v10:i32): return v10 }");
let mut frame = Frame::new(&func);
let old_ssa_value_refs = [ValueRef::from_u32(1), ValueRef::from_u32(9)];
let values = vec![DataValue::B(true), DataValue::F64(Ieee64::from(f64::NAN))];
frame.set_all(&old_ssa_value_refs, values.clone());
// Rename the old SSA values to the new values.
let old_duplicated_ssa_value_refs = [ValueRef::from_u32(1), ValueRef::from_u32(1)];
let new_ssa_value_refs = [ValueRef::from_u32(4), ValueRef::from_u32(2)];
frame.rename(&old_duplicated_ssa_value_refs, &new_ssa_value_refs);
// If we use duplicates then subsequent renamings (v1 -> v2) will be empty.
frame.get(ValueRef::from_u32(2));
}
}

View File

@@ -2,7 +2,7 @@
//!
//! This module partially contains the logic for interpreting Cranelift IR.
use crate::environment::FunctionStore;
use crate::environment::{FuncIndex, FunctionStore};
use crate::frame::Frame;
use crate::instruction::DfgInstructionContext;
use crate::state::{MemoryError, State};
@@ -34,22 +34,22 @@ impl<'a> Interpreter<'a> {
func_name: &str,
arguments: &[DataValue],
) -> Result<ControlFlow<'a, DataValue>, InterpreterError> {
let func_ref = self
let index = self
.state
.functions
.index_of(func_name)
.ok_or_else(|| InterpreterError::UnknownFunctionName(func_name.to_string()))?;
self.call_by_index(func_ref, arguments)
self.call_by_index(index, arguments)
}
/// Call a function by its index in the [FunctionStore]; this is a proxy for [Interpreter::call].
pub fn call_by_index(
&mut self,
func_ref: FuncRef,
index: FuncIndex,
arguments: &[DataValue],
) -> Result<ControlFlow<'a, DataValue>, InterpreterError> {
match self.state.get_function(func_ref) {
None => Err(InterpreterError::UnknownFunctionReference(func_ref)),
match self.state.functions.get_by_index(index) {
None => Err(InterpreterError::UnknownFunctionIndex(index)),
Some(func) => self.call(func, arguments),
}
}
@@ -98,8 +98,9 @@ impl<'a> Interpreter<'a> {
.set_all(function.dfg.block_params(block), block_arguments.to_vec());
maybe_inst = layout.first_inst(block)
}
ControlFlow::Call(function, arguments) => {
let returned_arguments = self.call(function, &arguments)?.unwrap_return();
ControlFlow::Call(called_function, arguments) => {
let returned_arguments =
self.call(called_function, &arguments)?.unwrap_return();
self.state
.current_frame_mut()
.set_all(function.dfg.inst_results(inst), returned_arguments);
@@ -123,8 +124,8 @@ pub enum InterpreterError {
StepError(#[from] StepError),
#[error("reached an unreachable statement")]
Unreachable,
#[error("unknown function reference (has it been added to the function store?): {0}")]
UnknownFunctionReference(FuncRef),
#[error("unknown function index (has it been added to the function store?): {0}")]
UnknownFunctionIndex(FuncIndex),
#[error("unknown function with name (has it been added to the function store?): {0}")]
UnknownFunctionName(String),
#[error("value error")]
@@ -176,7 +177,8 @@ impl<'a> InterpreterState<'a> {
impl<'a> State<'a, DataValue> for InterpreterState<'a> {
fn get_function(&self, func_ref: FuncRef) -> Option<&'a Function> {
self.functions.get_by_func_ref(func_ref)
self.functions
.get_from_func_ref(func_ref, self.frame_stack.last().unwrap().function)
}
fn push_frame(&mut self, function: &'a Function) {
self.frame_stack.push(Frame::new(function));
@@ -273,6 +275,40 @@ mod tests {
assert_eq!(result, vec![DataValue::B(true)])
}
// This test verifies that functions can refer to each other using the function store. A double indirection is
// required, which is tricky to get right: a referenced function is a FuncRef when called but a FuncIndex inside the
// function store. This test would preferably be a CLIF filetest but the filetest infrastructure only looks at a
// single function at a time--we need more than one function in the store for this test.
#[test]
fn function_references() {
let code = "
function %child(i32) -> i32 {
block0(v0: i32):
v1 = iadd_imm v0, -1
return v1
}
function %parent(i32) -> i32 {
fn42 = %child(i32) -> i32
block0(v0: i32):
v1 = iadd_imm v0, 1
v2 = call fn42(v1)
return v2
}";
let mut env = FunctionStore::default();
let funcs = parse_functions(code).unwrap().to_vec();
funcs.iter().for_each(|f| env.add(f.name.to_string(), f));
let state = InterpreterState::default().with_function_store(env);
let result = Interpreter::new(state)
.call_by_name("%parent", &[DataValue::I32(0)])
.unwrap()
.unwrap_return();
assert_eq!(result, vec![DataValue::I32(0)])
}
#[test]
fn state_heap_roundtrip() -> Result<(), MemoryError> {
let mut state = InterpreterState::default();

View File

@@ -1,10 +1,10 @@
[package]
name = "cranelift-simplejit"
name = "cranelift-jit"
version = "0.68.0"
authors = ["The Cranelift Project Developers"]
description = "A simple JIT library backed by Cranelift"
description = "A JIT library backed by Cranelift"
repository = "https://github.com/bytecodealliance/wasmtime"
documentation = "https://docs.rs/cranelift-simplejit"
documentation = "https://docs.rs/cranelift-jit"
license = "Apache-2.0 WITH LLVM-exception"
readme = "README.md"
edition = "2018"
@@ -14,6 +14,7 @@ cranelift-module = { path = "../module", version = "0.68.0" }
cranelift-native = { path = "../native", version = "0.68.0" }
cranelift-codegen = { path = "../codegen", version = "0.68.0", default-features = false, features = ["std"] }
cranelift-entity = { path = "../entity", version = "0.68.0" }
anyhow = "1.0"
region = "2.2.0"
libc = { version = "0.2.42" }
errno = "0.2.4"

View File

@@ -1,8 +1,8 @@
This crate provides a simple JIT library that uses
This crate provides a JIT library that uses
[Cranelift](https://crates.io/crates/cranelift).
This crate is extremely experimental.
See the [example program] for a brief overview of how to use this.
[example program]: https://github.com/bytecodealliance/wasmtime/blob/main/cranelift/simplejit/examples/simplejit-minimal.rs
[example program]: https://github.com/bytecodealliance/wasmtime/blob/main/cranelift/jit/examples/jit-minimal.rs

View File

@@ -1,12 +1,21 @@
use cranelift::prelude::*;
use cranelift_codegen::binemit::NullTrapSink;
use cranelift_codegen::settings::{self, Configurable};
use cranelift_jit::{JITBuilder, JITModule};
use cranelift_module::{default_libcall_names, Linkage, Module};
use cranelift_simplejit::{SimpleJITBuilder, SimpleJITModule};
use std::mem;
fn main() {
let mut module: SimpleJITModule =
SimpleJITModule::new(SimpleJITBuilder::new(default_libcall_names()));
let mut flag_builder = settings::builder();
flag_builder.set("use_colocated_libcalls", "false").unwrap();
// FIXME set back to true once the x64 backend supports it.
flag_builder.set("is_pic", "false").unwrap();
let isa_builder = cranelift_native::builder().unwrap_or_else(|msg| {
panic!("host machine is not supported: {}", msg);
});
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
let mut module = JITModule::new(JITBuilder::with_isa(isa, default_libcall_names()));
let mut ctx = module.make_context();
let mut func_ctx = FunctionBuilderContext::new();

View File

@@ -1,4 +1,4 @@
//! Defines `SimpleJITModule`.
//! Defines `JITModule`.
use crate::{compiled_blob::CompiledBlob, memory::Memory};
use cranelift_codegen::isa::TargetIsa;
@@ -18,10 +18,11 @@ use cranelift_native;
use libc;
use log::info;
use std::collections::HashMap;
use std::convert::TryInto;
use std::convert::{TryFrom, TryInto};
use std::ffi::CString;
use std::io::Write;
use std::ptr;
use std::ptr::NonNull;
use target_lexicon::PointerWidth;
#[cfg(windows)]
use winapi;
@@ -30,15 +31,16 @@ const EXECUTABLE_DATA_ALIGNMENT: u64 = 0x10;
const WRITABLE_DATA_ALIGNMENT: u64 = 0x8;
const READONLY_DATA_ALIGNMENT: u64 = 0x1;
/// A builder for `SimpleJITModule`.
pub struct SimpleJITBuilder {
/// A builder for `JITModule`.
pub struct JITBuilder {
isa: Box<dyn TargetIsa>,
symbols: HashMap<String, *const u8>,
libcall_names: Box<dyn Fn(ir::LibCall) -> String + Send + Sync>,
hotswap_enabled: bool,
}
impl SimpleJITBuilder {
/// Create a new `SimpleJITBuilder`.
impl JITBuilder {
/// Create a new `JITBuilder`.
///
/// The `libcall_names` function provides a way to translate `cranelift_codegen`'s `ir::LibCall`
/// enum to symbols. LibCalls are inserted in the IR as part of the legalization for certain
@@ -50,6 +52,7 @@ impl SimpleJITBuilder {
// which might not reach all definitions; we can't handle that here, so
// we require long-range relocation types.
flag_builder.set("use_colocated_libcalls", "false").unwrap();
flag_builder.set("is_pic", "true").unwrap();
let isa_builder = cranelift_native::builder().unwrap_or_else(|msg| {
panic!("host machine is not supported: {}", msg);
});
@@ -57,12 +60,10 @@ impl SimpleJITBuilder {
Self::with_isa(isa, libcall_names)
}
/// Create a new `SimpleJITBuilder` with an arbitrary target. This is mainly
/// Create a new `JITBuilder` with an arbitrary target. This is mainly
/// useful for testing.
///
/// SimpleJIT requires a `TargetIsa` configured for non-PIC.
///
/// To create a `SimpleJITBuilder` for native use, use the `new` constructor
/// To create a `JITBuilder` for native use, use the `new` constructor
/// instead.
///
/// The `libcall_names` function provides a way to translate `cranelift_codegen`'s `ir::LibCall`
@@ -73,12 +74,12 @@ impl SimpleJITBuilder {
isa: Box<dyn TargetIsa>,
libcall_names: Box<dyn Fn(ir::LibCall) -> String + Send + Sync>,
) -> Self {
debug_assert!(!isa.flags().is_pic(), "SimpleJIT requires non-PIC code");
let symbols = HashMap::new();
Self {
isa,
symbols,
libcall_names,
hotswap_enabled: false,
}
}
@@ -117,18 +118,33 @@ impl SimpleJITBuilder {
}
self
}
/// Enable or disable hotswap support. See [`JITModule::prepare_for_function_redefine`]
/// for more information.
///
/// Enabling hotswap support requires PIC code.
pub fn hotswap(&mut self, enabled: bool) -> &mut Self {
self.hotswap_enabled = enabled;
self
}
}
/// A `SimpleJITModule` implements `Module` and emits code and data into memory where it can be
/// A `JITModule` implements `Module` and emits code and data into memory where it can be
/// directly called and accessed.
///
/// See the `SimpleJITBuilder` for a convenient way to construct `SimpleJITModule` instances.
pub struct SimpleJITModule {
/// See the `JITBuilder` for a convenient way to construct `JITModule` instances.
pub struct JITModule {
isa: Box<dyn TargetIsa>,
hotswap_enabled: bool,
symbols: HashMap<String, *const u8>,
libcall_names: Box<dyn Fn(ir::LibCall) -> String>,
memory: MemoryHandle,
declarations: ModuleDeclarations,
function_got_entries: SecondaryMap<FuncId, Option<NonNull<*const u8>>>,
function_plt_entries: SecondaryMap<FuncId, Option<NonNull<[u8; 16]>>>,
data_object_got_entries: SecondaryMap<DataId, Option<NonNull<*const u8>>>,
libcall_got_entries: HashMap<ir::LibCall, NonNull<*const u8>>,
libcall_plt_entries: HashMap<ir::LibCall, NonNull<[u8; 16]>>,
compiled_functions: SecondaryMap<FuncId, Option<CompiledBlob>>,
compiled_data_objects: SecondaryMap<DataId, Option<CompiledBlob>>,
functions_to_finalize: Vec<FuncId>,
@@ -142,7 +158,7 @@ struct MemoryHandle {
writable: Memory,
}
impl SimpleJITModule {
impl JITModule {
/// Free memory allocated for code and data segments of compiled functions.
///
/// # Safety
@@ -164,16 +180,35 @@ impl SimpleJITModule {
.or_else(|| lookup_with_dlsym(name))
}
fn get_definition(&self, name: &ir::ExternalName) -> *const u8 {
unsafe fn write_plt_entry_bytes(plt_ptr: *mut [u8; 16], got_ptr: *mut *const u8) {
assert!(
cfg!(target_arch = "x86_64"),
"PLT is currently only supported on x86_64"
);
// jmp *got_ptr; ud2; ud2; ud2; ud2; ud2
let mut plt_val = [
0xff, 0x25, 0, 0, 0, 0, 0x0f, 0x0b, 0x0f, 0x0b, 0x0f, 0x0b, 0x0f, 0x0b, 0x0f, 0x0b,
];
let what = got_ptr as isize - 4;
let at = plt_ptr as isize + 2;
plt_val[2..6].copy_from_slice(&i32::to_ne_bytes(i32::try_from(what - at).unwrap()));
std::ptr::write(plt_ptr, plt_val);
}
fn get_address(&self, name: &ir::ExternalName) -> *const u8 {
match *name {
ir::ExternalName::User { .. } => {
let (name, linkage) = if ModuleDeclarations::is_function(name) {
let func_id = FuncId::from_name(name);
match &self.compiled_functions[func_id] {
Some(compiled) => return compiled.ptr,
None => {
let decl = self.declarations.get_function_decl(func_id);
(&decl.name, decl.linkage)
if self.hotswap_enabled {
return self.get_plt_address(name);
} else {
let func_id = FuncId::from_name(name);
match &self.compiled_functions[func_id] {
Some(compiled) => return compiled.ptr,
None => {
let decl = self.declarations.get_function_decl(func_id);
(&decl.name, decl.linkage)
}
}
}
} else {
@@ -203,10 +238,60 @@ impl SimpleJITModule {
}
}
fn get_got_address(&self, name: &ir::ExternalName) -> *const u8 {
match *name {
ir::ExternalName::User { .. } => {
if ModuleDeclarations::is_function(name) {
let func_id = FuncId::from_name(name);
self.function_got_entries[func_id]
.unwrap()
.as_ptr()
.cast::<u8>()
} else {
let data_id = DataId::from_name(name);
self.data_object_got_entries[data_id]
.unwrap()
.as_ptr()
.cast::<u8>()
}
}
ir::ExternalName::LibCall(ref libcall) => self
.libcall_got_entries
.get(libcall)
.unwrap_or_else(|| panic!("can't resolve libcall {}", libcall))
.as_ptr()
.cast::<u8>(),
_ => panic!("invalid ExternalName {}", name),
}
}
fn get_plt_address(&self, name: &ir::ExternalName) -> *const u8 {
match *name {
ir::ExternalName::User { .. } => {
if ModuleDeclarations::is_function(name) {
let func_id = FuncId::from_name(name);
self.function_plt_entries[func_id]
.unwrap()
.as_ptr()
.cast::<u8>()
} else {
unreachable!("PLT relocations can only have functions as target");
}
}
ir::ExternalName::LibCall(ref libcall) => self
.libcall_plt_entries
.get(libcall)
.unwrap_or_else(|| panic!("can't resolve libcall {}", libcall))
.as_ptr()
.cast::<u8>(),
_ => panic!("invalid ExternalName {}", name),
}
}
/// Returns the address of a finalized function.
pub fn get_finalized_function(&self, func_id: FuncId) -> *const u8 {
let info = &self.compiled_functions[func_id];
debug_assert!(
assert!(
!self.functions_to_finalize.iter().any(|x| *x == func_id),
"function not yet finalized"
);
@@ -218,7 +303,7 @@ impl SimpleJITModule {
/// Returns the address and size of a finalized data object.
pub fn get_finalized_data(&self, data_id: DataId) -> (*const u8, usize) {
let info = &self.compiled_data_objects[data_id];
debug_assert!(
assert!(
!self.data_objects_to_finalize.iter().any(|x| *x == data_id),
"data object not yet finalized"
);
@@ -255,19 +340,28 @@ impl SimpleJITModule {
pub fn finalize_definitions(&mut self) {
for func in std::mem::take(&mut self.functions_to_finalize) {
let decl = self.declarations.get_function_decl(func);
debug_assert!(decl.linkage.is_definable());
assert!(decl.linkage.is_definable());
let func = self.compiled_functions[func]
.as_ref()
.expect("function must be compiled before it can be finalized");
func.perform_relocations(|name| self.get_definition(name));
func.perform_relocations(
|name| self.get_address(name),
|name| self.get_got_address(name),
|name| self.get_plt_address(name),
);
}
for data in std::mem::take(&mut self.data_objects_to_finalize) {
let decl = self.declarations.get_data_decl(data);
debug_assert!(decl.linkage.is_definable());
assert!(decl.linkage.is_definable());
let data = self.compiled_data_objects[data]
.as_ref()
.expect("data object must be compiled before it can be finalized");
data.perform_relocations(|name| self.get_definition(name));
data.perform_relocations(
|name| self.get_address(name),
|name| self.get_got_address(name),
|name| self.get_plt_address(name),
);
}
// Now that we're done patching, prepare the memory for execution!
@@ -275,29 +369,111 @@ impl SimpleJITModule {
self.memory.code.set_readable_and_executable();
}
/// Create a new `SimpleJITModule`.
pub fn new(builder: SimpleJITBuilder) -> Self {
let memory = MemoryHandle {
/// Create a new `JITModule`.
pub fn new(builder: JITBuilder) -> Self {
if builder.hotswap_enabled {
assert!(
builder.isa.flags().is_pic(),
"Hotswapping requires PIC code"
);
}
let mut memory = MemoryHandle {
code: Memory::new(),
readonly: Memory::new(),
writable: Memory::new(),
};
let mut libcall_got_entries = HashMap::new();
let mut libcall_plt_entries = HashMap::new();
// Pre-create a GOT and PLT entry for each libcall.
let all_libcalls = if builder.isa.flags().is_pic() {
ir::LibCall::all_libcalls()
} else {
&[] // Not PIC, so no GOT and PLT entries necessary
};
for &libcall in all_libcalls {
let got_entry = memory
.writable
.allocate(
std::mem::size_of::<*const u8>(),
std::mem::align_of::<*const u8>().try_into().unwrap(),
)
.unwrap()
.cast::<*const u8>();
libcall_got_entries.insert(libcall, NonNull::new(got_entry).unwrap());
let sym = (builder.libcall_names)(libcall);
let addr = if let Some(addr) = builder
.symbols
.get(&sym)
.copied()
.or_else(|| lookup_with_dlsym(&sym))
{
addr
} else {
continue;
};
unsafe {
std::ptr::write(got_entry, addr);
}
let plt_entry = memory
.code
.allocate(std::mem::size_of::<[u8; 16]>(), EXECUTABLE_DATA_ALIGNMENT)
.unwrap()
.cast::<[u8; 16]>();
libcall_plt_entries.insert(libcall, NonNull::new(plt_entry).unwrap());
unsafe {
Self::write_plt_entry_bytes(plt_entry, got_entry);
}
}
Self {
isa: builder.isa,
hotswap_enabled: builder.hotswap_enabled,
symbols: builder.symbols,
libcall_names: builder.libcall_names,
memory,
declarations: ModuleDeclarations::default(),
function_got_entries: SecondaryMap::new(),
function_plt_entries: SecondaryMap::new(),
data_object_got_entries: SecondaryMap::new(),
libcall_got_entries,
libcall_plt_entries,
compiled_functions: SecondaryMap::new(),
compiled_data_objects: SecondaryMap::new(),
functions_to_finalize: Vec::new(),
data_objects_to_finalize: Vec::new(),
}
}
/// Allow a single future `define_function` on a previously defined function. This allows for
/// hot code swapping and lazy compilation of functions.
///
/// This requires hotswap support to be enabled first using [`JITBuilder::hotswap`].
pub fn prepare_for_function_redefine(&mut self, func_id: FuncId) -> ModuleResult<()> {
assert!(self.hotswap_enabled, "Hotswap support is not enabled");
let decl = self.declarations.get_function_decl(func_id);
if !decl.linkage.is_definable() {
return Err(ModuleError::InvalidImportDefinition(decl.name.clone()));
}
if self.compiled_functions[func_id].is_none() {
return Err(ModuleError::Backend(anyhow::anyhow!(
"Tried to redefine not yet defined function {}",
decl.name
)));
}
self.compiled_functions[func_id] = None;
// FIXME return some kind of handle that allows for deallocating the function
Ok(())
}
}
impl<'simple_jit_backend> Module for SimpleJITModule {
impl Module for JITModule {
fn isa(&self) -> &dyn TargetIsa {
&*self.isa
}
@@ -315,6 +491,38 @@ impl<'simple_jit_backend> Module for SimpleJITModule {
let (id, _decl) = self
.declarations
.declare_function(name, linkage, signature)?;
if self.function_got_entries[id].is_none() && self.isa.flags().is_pic() {
let got_entry = self
.memory
.writable
.allocate(
std::mem::size_of::<*const u8>(),
std::mem::align_of::<*const u8>().try_into().unwrap(),
)
.unwrap()
.cast::<*const u8>();
self.function_got_entries[id] = Some(NonNull::new(got_entry).unwrap());
// FIXME populate got entries with a null pointer when defined
let val = self.lookup_symbol(name).unwrap_or(std::ptr::null());
unsafe {
std::ptr::write(got_entry, val);
}
let plt_entry = self
.memory
.code
.allocate(std::mem::size_of::<[u8; 16]>(), EXECUTABLE_DATA_ALIGNMENT)
.unwrap()
.cast::<[u8; 16]>();
self.record_function_for_perf(
plt_entry as *mut _,
std::mem::size_of::<[u8; 16]>(),
&format!("{}@plt", name),
);
self.function_plt_entries[id] = Some(NonNull::new(plt_entry).unwrap());
unsafe {
Self::write_plt_entry_bytes(plt_entry, got_entry);
}
}
Ok(id)
}
@@ -325,13 +533,69 @@ impl<'simple_jit_backend> Module for SimpleJITModule {
writable: bool,
tls: bool,
) -> ModuleResult<DataId> {
assert!(!tls, "SimpleJIT doesn't yet support TLS");
assert!(!tls, "JIT doesn't yet support TLS");
let (id, _decl) = self
.declarations
.declare_data(name, linkage, writable, tls)?;
if self.data_object_got_entries[id].is_none() && self.isa.flags().is_pic() {
let got_entry = self
.memory
.writable
.allocate(
std::mem::size_of::<*const u8>(),
std::mem::align_of::<*const u8>().try_into().unwrap(),
)
.unwrap()
.cast::<*const u8>();
self.data_object_got_entries[id] = Some(NonNull::new(got_entry).unwrap());
// FIXME populate got entries with a null pointer when defined
let val = self.lookup_symbol(name).unwrap_or(std::ptr::null());
unsafe {
std::ptr::write(got_entry, val);
}
}
Ok(id)
}
/// Use this when you're building the IR of a function to reference a function.
///
/// TODO: Coalesce redundant decls and signatures.
/// TODO: Look into ways to reduce the risk of using a FuncRef in the wrong function.
fn declare_func_in_func(&self, func: FuncId, in_func: &mut ir::Function) -> ir::FuncRef {
let decl = self.declarations.get_function_decl(func);
let signature = in_func.import_signature(decl.signature.clone());
let colocated = !self.hotswap_enabled && decl.linkage.is_final();
in_func.import_function(ir::ExtFuncData {
name: ir::ExternalName::user(0, func.as_u32()),
signature,
colocated,
})
}
/// Use this when you're building the IR of a function to reference a data object.
///
/// TODO: Same as above.
fn declare_data_in_func(&self, data: DataId, func: &mut ir::Function) -> ir::GlobalValue {
let decl = self.declarations.get_data_decl(data);
let colocated = !self.hotswap_enabled && decl.linkage.is_final();
func.create_global_value(ir::GlobalValueData::Symbol {
name: ir::ExternalName::user(1, data.as_u32()),
offset: ir::immediates::Imm64::new(0),
colocated,
tls: decl.tls,
})
}
/// TODO: Same as above.
fn declare_func_in_data(&self, func: FuncId, ctx: &mut DataContext) -> ir::FuncRef {
ctx.import_function(ir::ExternalName::user(0, func.as_u32()))
}
/// TODO: Same as above.
fn declare_data_in_data(&self, data: DataId, ctx: &mut DataContext) -> ir::GlobalValue {
ctx.import_global_value(ir::ExternalName::user(1, data.as_u32()))
}
fn define_function<TS>(
&mut self,
id: FuncId,
@@ -363,7 +627,7 @@ impl<'simple_jit_backend> Module for SimpleJITModule {
.allocate(size, EXECUTABLE_DATA_ALIGNMENT)
.expect("TODO: handle OOM etc.");
let mut reloc_sink = SimpleJITRelocSink::default();
let mut reloc_sink = JITRelocSink::default();
let mut stack_map_sink = binemit::NullStackMapSink {};
unsafe {
ctx.emit_to_memory(
@@ -381,7 +645,36 @@ impl<'simple_jit_backend> Module for SimpleJITModule {
size,
relocs: reloc_sink.relocs,
});
self.functions_to_finalize.push(id);
if self.isa.flags().is_pic() {
unsafe {
std::ptr::write(self.function_got_entries[id].unwrap().as_ptr(), ptr);
}
}
if self.hotswap_enabled {
self.compiled_functions[id]
.as_ref()
.unwrap()
.perform_relocations(
|name| match *name {
ir::ExternalName::User { .. } => {
unreachable!("non GOT or PLT relocation in function {} to {}", id, name)
}
ir::ExternalName::LibCall(ref libcall) => self
.libcall_plt_entries
.get(libcall)
.unwrap_or_else(|| panic!("can't resolve libcall {}", libcall))
.as_ptr()
.cast::<u8>(),
_ => panic!("invalid ExternalName {}", name),
},
|name| self.get_got_address(name),
|name| self.get_plt_address(name),
);
} else {
self.functions_to_finalize.push(id);
}
Ok(ModuleCompiledFunction { size: code_size })
}
@@ -424,7 +717,25 @@ impl<'simple_jit_backend> Module for SimpleJITModule {
size,
relocs: relocs.to_vec(),
});
self.functions_to_finalize.push(id);
if self.isa.flags().is_pic() {
unsafe {
std::ptr::write(self.function_got_entries[id].unwrap().as_ptr(), ptr);
}
}
if self.hotswap_enabled {
self.compiled_functions[id]
.as_ref()
.unwrap()
.perform_relocations(
|name| unreachable!("non GOT or PLT relocation in function {} to {}", id, name),
|name| self.get_got_address(name),
|name| self.get_plt_address(name),
);
} else {
self.functions_to_finalize.push(id);
}
Ok(ModuleCompiledFunction { size: total_size })
}
@@ -439,7 +750,7 @@ impl<'simple_jit_backend> Module for SimpleJITModule {
return Err(ModuleError::DuplicateDefinition(decl.name.to_owned()));
}
assert!(!decl.tls, "SimpleJIT doesn't yet support TLS");
assert!(!decl.tls, "JIT doesn't yet support TLS");
let &DataDescription {
ref init,
@@ -489,6 +800,11 @@ impl<'simple_jit_backend> Module for SimpleJITModule {
self.compiled_data_objects[id] = Some(CompiledBlob { ptr, size, relocs });
self.data_objects_to_finalize.push(id);
if self.isa.flags().is_pic() {
unsafe {
std::ptr::write(self.data_object_got_entries[id].unwrap().as_ptr(), ptr);
}
}
Ok(())
}
@@ -534,11 +850,11 @@ fn lookup_with_dlsym(name: &str) -> Option<*const u8> {
}
#[derive(Default)]
struct SimpleJITRelocSink {
struct JITRelocSink {
relocs: Vec<RelocRecord>,
}
impl RelocSink for SimpleJITRelocSink {
impl RelocSink for JITRelocSink {
fn reloc_external(
&mut self,
offset: CodeOffset,

View File

@@ -0,0 +1,79 @@
use cranelift_codegen::binemit::Reloc;
use cranelift_codegen::ir::ExternalName;
use cranelift_module::RelocRecord;
use std::convert::TryFrom;
#[derive(Clone)]
pub(crate) struct CompiledBlob {
pub(crate) ptr: *mut u8,
pub(crate) size: usize,
pub(crate) relocs: Vec<RelocRecord>,
}
impl CompiledBlob {
pub(crate) fn perform_relocations(
&self,
get_address: impl Fn(&ExternalName) -> *const u8,
get_got_entry: impl Fn(&ExternalName) -> *const u8,
get_plt_entry: impl Fn(&ExternalName) -> *const u8,
) {
use std::ptr::write_unaligned;
for &RelocRecord {
reloc,
offset,
ref name,
addend,
} in &self.relocs
{
debug_assert!((offset as usize) < self.size);
let at = unsafe { self.ptr.offset(isize::try_from(offset).unwrap()) };
match reloc {
Reloc::Abs4 => {
let base = get_address(name);
let what = unsafe { base.offset(isize::try_from(addend).unwrap()) };
#[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))]
unsafe {
write_unaligned(at as *mut u32, u32::try_from(what as usize).unwrap())
};
}
Reloc::Abs8 => {
let base = get_address(name);
let what = unsafe { base.offset(isize::try_from(addend).unwrap()) };
#[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))]
unsafe {
write_unaligned(at as *mut u64, u64::try_from(what as usize).unwrap())
};
}
Reloc::X86PCRel4 | Reloc::X86CallPCRel4 => {
let base = get_address(name);
let what = unsafe { base.offset(isize::try_from(addend).unwrap()) };
let pcrel = i32::try_from((what as isize) - (at as isize)).unwrap();
#[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))]
unsafe {
write_unaligned(at as *mut i32, pcrel)
};
}
Reloc::X86GOTPCRel4 => {
let base = get_got_entry(name);
let what = unsafe { base.offset(isize::try_from(addend).unwrap()) };
let pcrel = i32::try_from((what as isize) - (at as isize)).unwrap();
#[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))]
unsafe {
write_unaligned(at as *mut i32, pcrel)
};
}
Reloc::X86CallPLTRel4 => {
let base = get_plt_entry(name);
let what = unsafe { base.offset(isize::try_from(addend).unwrap()) };
let pcrel = i32::try_from((what as isize) - (at as isize)).unwrap();
#[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))]
unsafe {
write_unaligned(at as *mut i32, pcrel)
};
}
_ => unimplemented!(),
}
}
}
}

View File

@@ -1,4 +1,4 @@
//! Top-level lib.rs for `cranelift_simplejit`.
//! Top-level lib.rs for `cranelift_jit`.
#![deny(
missing_docs,
@@ -27,7 +27,7 @@ mod backend;
mod compiled_blob;
mod memory;
pub use crate::backend::{SimpleJITBuilder, SimpleJITModule};
pub use crate::backend::{JITBuilder, JITModule};
/// Version number of this crate.
pub const VERSION: &str = env!("CARGO_PKG_VERSION");

View File

@@ -1,16 +1,25 @@
use cranelift_codegen::binemit::NullTrapSink;
use cranelift_codegen::ir::*;
use cranelift_codegen::isa::CallConv;
use cranelift_codegen::settings::{self, Configurable};
use cranelift_codegen::{ir::types::I16, Context};
use cranelift_entity::EntityRef;
use cranelift_frontend::*;
use cranelift_jit::*;
use cranelift_module::*;
use cranelift_simplejit::*;
#[test]
fn error_on_incompatible_sig_in_declare_function() {
let mut module: SimpleJITModule =
SimpleJITModule::new(SimpleJITBuilder::new(default_libcall_names()));
let mut flag_builder = settings::builder();
flag_builder.set("use_colocated_libcalls", "false").unwrap();
// FIXME set back to true once the x64 backend supports it.
flag_builder.set("is_pic", "false").unwrap();
let isa_builder = cranelift_native::builder().unwrap_or_else(|msg| {
panic!("host machine is not supported: {}", msg);
});
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
let mut module = JITModule::new(JITBuilder::with_isa(isa, default_libcall_names()));
let mut sig = Signature {
params: vec![AbiParam::new(types::I64)],
returns: vec![],
@@ -26,7 +35,7 @@ fn error_on_incompatible_sig_in_declare_function() {
.unwrap(); // Make sure this is an error
}
fn define_simple_function(module: &mut SimpleJITModule) -> FuncId {
fn define_simple_function(module: &mut JITModule) -> FuncId {
let sig = Signature {
params: vec![],
returns: vec![],
@@ -58,8 +67,15 @@ fn define_simple_function(module: &mut SimpleJITModule) -> FuncId {
#[test]
#[should_panic(expected = "Result::unwrap()` on an `Err` value: DuplicateDefinition(\"abc\")")]
fn panic_on_define_after_finalize() {
let mut module: SimpleJITModule =
SimpleJITModule::new(SimpleJITBuilder::new(default_libcall_names()));
let mut flag_builder = settings::builder();
flag_builder.set("use_colocated_libcalls", "false").unwrap();
// FIXME set back to true once the x64 backend supports it.
flag_builder.set("is_pic", "false").unwrap();
let isa_builder = cranelift_native::builder().unwrap_or_else(|msg| {
panic!("host machine is not supported: {}", msg);
});
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
let mut module = JITModule::new(JITBuilder::with_isa(isa, default_libcall_names()));
define_simple_function(&mut module);
define_simple_function(&mut module);
@@ -140,8 +156,15 @@ fn switch_error() {
#[test]
fn libcall_function() {
let mut module: SimpleJITModule =
SimpleJITModule::new(SimpleJITBuilder::new(default_libcall_names()));
let mut flag_builder = settings::builder();
flag_builder.set("use_colocated_libcalls", "false").unwrap();
// FIXME set back to true once the x64 backend supports it.
flag_builder.set("is_pic", "false").unwrap();
let isa_builder = cranelift_native::builder().unwrap_or_else(|msg| {
panic!("host machine is not supported: {}", msg);
});
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
let mut module = JITModule::new(JITBuilder::with_isa(isa, default_libcall_names()));
let sig = Signature {
params: vec![],

View File

@@ -10,10 +10,8 @@ A module is a collection of functions and data objects that are linked
together. The `Module` trait that defines a common interface for various kinds
of modules. Most users will use one of the following `Module` implementations:
- `SimpleJITModule`, provided by [cranelift-simplejit], which JITs
code to memory for direct execution.
- `ObjectModule`, provided by [cranelift-object], which emits native
object files.
- `JITModule`, provided by [cranelift-jit], which JITs code to memory for direct execution.
- `ObjectModule`, provided by [cranelift-object], which emits native object files.
[cranelift-simplejit]: https://crates.io/crates/cranelift-simplejit
[cranelift-jit]: https://crates.io/crates/cranelift-jit
[cranelift-object]: https://crates.io/crates/cranelift-object

View File

@@ -490,3 +490,97 @@ pub trait Module {
/// Define a data object, producing the data contents from the given `DataContext`.
fn define_data(&mut self, data: DataId, data_ctx: &DataContext) -> ModuleResult<()>;
}
impl<M: Module> Module for &mut M {
fn isa(&self) -> &dyn isa::TargetIsa {
(**self).isa()
}
fn declarations(&self) -> &ModuleDeclarations {
(**self).declarations()
}
fn get_name(&self, name: &str) -> Option<FuncOrDataId> {
(**self).get_name(name)
}
fn target_config(&self) -> isa::TargetFrontendConfig {
(**self).target_config()
}
fn make_context(&self) -> Context {
(**self).make_context()
}
fn clear_context(&self, ctx: &mut Context) {
(**self).clear_context(ctx)
}
fn make_signature(&self) -> ir::Signature {
(**self).make_signature()
}
fn clear_signature(&self, sig: &mut ir::Signature) {
(**self).clear_signature(sig)
}
fn declare_function(
&mut self,
name: &str,
linkage: Linkage,
signature: &ir::Signature,
) -> ModuleResult<FuncId> {
(**self).declare_function(name, linkage, signature)
}
fn declare_data(
&mut self,
name: &str,
linkage: Linkage,
writable: bool,
tls: bool,
) -> ModuleResult<DataId> {
(**self).declare_data(name, linkage, writable, tls)
}
fn declare_func_in_func(&self, func: FuncId, in_func: &mut ir::Function) -> ir::FuncRef {
(**self).declare_func_in_func(func, in_func)
}
fn declare_data_in_func(&self, data: DataId, func: &mut ir::Function) -> ir::GlobalValue {
(**self).declare_data_in_func(data, func)
}
fn declare_func_in_data(&self, func: FuncId, ctx: &mut DataContext) -> ir::FuncRef {
(**self).declare_func_in_data(func, ctx)
}
fn declare_data_in_data(&self, data: DataId, ctx: &mut DataContext) -> ir::GlobalValue {
(**self).declare_data_in_data(data, ctx)
}
fn define_function<TS>(
&mut self,
func: FuncId,
ctx: &mut Context,
trap_sink: &mut TS,
) -> ModuleResult<ModuleCompiledFunction>
where
TS: binemit::TrapSink,
{
(**self).define_function(func, ctx, trap_sink)
}
fn define_function_bytes(
&mut self,
func: FuncId,
bytes: &[u8],
relocs: &[RelocRecord],
) -> ModuleResult<ModuleCompiledFunction> {
(**self).define_function_bytes(func, bytes, relocs)
}
fn define_data(&mut self, data: DataId, data_ctx: &DataContext) -> ModuleResult<()> {
(**self).define_data(data, data_ctx)
}
}

View File

@@ -15,7 +15,7 @@ peepmatic-macro = { version = "0.68.0", path = "crates/macro" }
peepmatic-runtime = { version = "0.68.0", path = "crates/runtime", features = ["construct"] }
peepmatic-traits = { version = "0.68.0", path = "crates/traits" }
serde = { version = "1.0.105", features = ["derive"] }
wast = "27.0.0"
wast = "29.0.0"
z3 = { version = "0.7.1", features = ["static-link-z3"] }
[dev-dependencies]

View File

@@ -21,4 +21,4 @@ peepmatic-test-operator = { path = "../test-operator" }
peepmatic-traits = { path = "../traits" }
rand = { version = "0.7.3", features = ["small_rng"] }
serde = "1.0.106"
wast = "27.0.0"
wast = "29.0.0"

View File

@@ -16,7 +16,7 @@ peepmatic-automata = { version = "0.68.0", path = "../automata", features = ["se
peepmatic-traits = { version = "0.68.0", path = "../traits" }
serde = { version = "1.0.105", features = ["derive"] }
thiserror = "1.0.15"
wast = { version = "27.0.0", optional = true }
wast = { version = "29.0.0", optional = true }
[dev-dependencies]
peepmatic-test-operator = { version = "0.68.0", path = "../test-operator" }

View File

@@ -10,10 +10,10 @@ description = "Converting Souper optimizations into Peepmatic DSL"
[dependencies]
anyhow = "1"
souper-ir = { version = "1", features = ["parse"] }
souper-ir = { version = "2.1.0", features = ["parse"] }
log = "0.4.8"
[dev-dependencies]
peepmatic = { path = "../..", version = "0.68.0" }
peepmatic-test-operator = { version = "0.68.0", path = "../test-operator" }
wast = "27.0.0"
wast = "29.0.0"

View File

@@ -174,7 +174,6 @@ fn convert_operand(
}
Some(format!("{}", convert_name(&assn.name)))
}
ast::AssignmentRhs::Constant(c) => Some(format!("{}", c.value)),
ast::AssignmentRhs::Instruction(inst) => match inst {
// Unsupported instructions.
ast::Instruction::Bswap { .. }
@@ -619,8 +618,7 @@ mod tests {
"
%0:i64 = var
%1:i32 = trunc %0
%2:i32 = 0
cand %1 %2
cand %1 0
",
"\
(=> (when (ireduce {i32} $v0)
@@ -631,8 +629,7 @@ mod tests {
"
%0:i32 = var
%1:i64 = sext %0
%2:i64 = 0
cand %1 %2
cand %1 0
",
"\
(=> (when (sextend {i64} $v0)
@@ -643,8 +640,7 @@ mod tests {
"
%0:i32 = var
%1:i64 = zext %0
%2:i64 = 0
cand %1 %2
cand %1 0
",
"\
(=> (when (uextend {i64} $v0)
@@ -677,8 +673,7 @@ mod tests {
%1:i32 = var
%2:i1 = eq %0, %1
%3:i32 = zext %2
%4:i32 = 0
cand %3 %4
cand %3 0
",
"\
(=> (when (bint (icmp eq $v0 $v1))
@@ -693,8 +688,7 @@ mod tests {
"
%0:i32 = var
%1:i32 = add %0, 1
%2:i32 = 0
cand %1 %2
cand %1 0
",
"\
(=> (when (iadd_imm 1 $v0)
@@ -705,8 +699,7 @@ mod tests {
"
%0:i32 = var
%1:i32 = add 1, %0
%2:i32 = 0
cand %1 %2
cand %1 0
",
"\
(=> (when (iadd_imm 1 $v0)

View File

@@ -9,4 +9,4 @@ edition = "2018"
[dependencies]
peepmatic-traits = { version = "0.68.0", path = "../traits" }
serde = { version = "1.0.105", features = ["derive"] }
wast = "27.0.0"
wast = "29.0.0"

View File

@@ -1,55 +0,0 @@
use cranelift_codegen::binemit::Reloc;
use cranelift_codegen::ir::ExternalName;
use cranelift_module::RelocRecord;
#[derive(Clone)]
pub(crate) struct CompiledBlob {
pub(crate) ptr: *mut u8,
pub(crate) size: usize,
pub(crate) relocs: Vec<RelocRecord>,
}
impl CompiledBlob {
pub(crate) fn perform_relocations(&self, get_definition: impl Fn(&ExternalName) -> *const u8) {
use std::ptr::write_unaligned;
for &RelocRecord {
reloc,
offset,
ref name,
addend,
} in &self.relocs
{
debug_assert!((offset as usize) < self.size);
let at = unsafe { self.ptr.offset(offset as isize) };
let base = get_definition(name);
// TODO: Handle overflow.
let what = unsafe { base.offset(addend as isize) };
match reloc {
Reloc::Abs4 => {
// TODO: Handle overflow.
#[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))]
unsafe {
write_unaligned(at as *mut u32, what as u32)
};
}
Reloc::Abs8 => {
#[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))]
unsafe {
write_unaligned(at as *mut u64, what as u64)
};
}
Reloc::X86PCRel4 | Reloc::X86CallPCRel4 => {
// TODO: Handle overflow.
let pcrel = ((what as isize) - (at as isize)) as i32;
#[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))]
unsafe {
write_unaligned(at as *mut i32, pcrel)
};
}
Reloc::X86GOTPCRel4 | Reloc::X86CallPLTRel4 => panic!("unexpected PIC relocation"),
_ => unimplemented!(),
}
}
}
}

View File

@@ -12,7 +12,7 @@ keywords = ["webassembly", "wasm"]
edition = "2018"
[dependencies]
wasmparser = { version = "0.68.0", default-features = false }
wasmparser = { version = "0.70", default-features = false }
cranelift-codegen = { path = "../codegen", version = "0.68.0", default-features = false }
cranelift-entity = { path = "../entity", version = "0.68.0" }
cranelift-frontend = { path = "../frontend", version = "0.68.0", default-features = false }

View File

@@ -538,10 +538,10 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
}
/********************************** Exception handing **********************************/
Operator::Try { .. }
| Operator::Catch
| Operator::BrOnExn { .. }
| Operator::Catch { .. }
| Operator::Throw { .. }
| Operator::Rethrow => {
| Operator::Unwind
| Operator::Rethrow { .. } => {
return Err(wasm_unsupported!(
"proposed exception handling operator {:?}",
op
@@ -2056,7 +2056,9 @@ fn prepare_load<FE: FuncEnvironment + ?Sized>(
// Note that we don't set `is_aligned` here, even if the load instruction's
// alignment immediate says it's aligned, because WebAssembly's immediate
// field is just a hint, while Cranelift's aligned flag needs a guarantee.
let flags = MemFlags::new();
// WebAssembly memory accesses are always little-endian.
let mut flags = MemFlags::new();
flags.set_endianness(ir::Endianness::Little);
Ok((flags, base, offset.into()))
}
@@ -2103,7 +2105,8 @@ fn translate_store<FE: FuncEnvironment + ?Sized>(
builder,
);
// See the comments in `prepare_load` about the flags.
let flags = MemFlags::new();
let mut flags = MemFlags::new();
flags.set_endianness(ir::Endianness::Little);
builder
.ins()
.Store(opcode, val_ty, flags, offset.into(), val, base);
@@ -2207,7 +2210,8 @@ fn translate_atomic_rmw<FE: FuncEnvironment + ?Sized>(
finalise_atomic_mem_addr(linear_mem_addr, memarg, access_ty, builder, state, environ)?;
// See the comments in `prepare_load` about the flags.
let flags = MemFlags::new();
let mut flags = MemFlags::new();
flags.set_endianness(ir::Endianness::Little);
let mut res = builder
.ins()
.atomic_rmw(access_ty, flags, op, final_effective_address, arg2);
@@ -2260,7 +2264,8 @@ fn translate_atomic_cas<FE: FuncEnvironment + ?Sized>(
finalise_atomic_mem_addr(linear_mem_addr, memarg, access_ty, builder, state, environ)?;
// See the comments in `prepare_load` about the flags.
let flags = MemFlags::new();
let mut flags = MemFlags::new();
flags.set_endianness(ir::Endianness::Little);
let mut res = builder
.ins()
.atomic_cas(flags, final_effective_address, expected, replacement);
@@ -2302,7 +2307,8 @@ fn translate_atomic_load<FE: FuncEnvironment + ?Sized>(
finalise_atomic_mem_addr(linear_mem_addr, memarg, access_ty, builder, state, environ)?;
// See the comments in `prepare_load` about the flags.
let flags = MemFlags::new();
let mut flags = MemFlags::new();
flags.set_endianness(ir::Endianness::Little);
let mut res = builder
.ins()
.atomic_load(access_ty, flags, final_effective_address);
@@ -2348,7 +2354,8 @@ fn translate_atomic_store<FE: FuncEnvironment + ?Sized>(
finalise_atomic_mem_addr(linear_mem_addr, memarg, access_ty, builder, state, environ)?;
// See the comments in `prepare_load` about the flags.
let flags = MemFlags::new();
let mut flags = MemFlags::new();
flags.set_endianness(ir::Endianness::Little);
builder
.ins()
.atomic_store(flags, data, final_effective_address);

View File

@@ -6,6 +6,6 @@ mod spec;
pub use crate::environ::dummy::DummyEnvironment;
pub use crate::environ::spec::{
FuncEnvironment, GlobalVariable, ModuleEnvironment, ReturnMode, TargetEnvironment, WasmError,
WasmFuncType, WasmResult, WasmType,
Alias, FuncEnvironment, GlobalVariable, ModuleEnvironment, ReturnMode, TargetEnvironment,
WasmError, WasmFuncType, WasmResult, WasmType,
};

View File

@@ -8,8 +8,9 @@
use crate::state::FuncTranslationState;
use crate::translation_utils::{
DataIndex, ElemIndex, EntityType, Event, EventIndex, FuncIndex, Global, GlobalIndex, Memory,
MemoryIndex, Table, TableIndex, TypeIndex,
DataIndex, ElemIndex, EntityIndex, EntityType, Event, EventIndex, FuncIndex, Global,
GlobalIndex, InstanceIndex, InstanceTypeIndex, Memory, MemoryIndex, ModuleIndex,
ModuleTypeIndex, SignatureIndex, Table, TableIndex, TypeIndex,
};
use core::convert::From;
use core::convert::TryFrom;
@@ -22,6 +23,7 @@ use cranelift_frontend::FunctionBuilder;
use serde::{Deserialize, Serialize};
use std::boxed::Box;
use std::string::ToString;
use std::vec::Vec;
use thiserror::Error;
use wasmparser::ValidatorResources;
use wasmparser::{BinaryReaderError, FuncValidator, FunctionBody, Operator, WasmFeatures};
@@ -201,6 +203,30 @@ pub enum ReturnMode {
FallthroughReturn,
}
/// An entry in the alias section of a wasm module (from the module linking
/// proposal)
pub enum Alias {
/// A parent's module is being aliased into our own index space.
///
/// Note that the index here is in the parent's index space, not our own.
ParentModule(ModuleIndex),
/// A parent's type is being aliased into our own index space
///
/// Note that the index here is in the parent's index space, not our own.
ParentType(TypeIndex),
/// A previously created instance is having one of its exports aliased into
/// our index space.
Child {
/// The index we're aliasing.
instance: InstanceIndex,
/// The nth export that we're inserting into our own index space
/// locally.
export: usize,
},
}
/// Environment affecting the translation of a WebAssembly.
pub trait TargetEnvironment {
/// Get the information needed to produce Cranelift IR for the given target.
@@ -683,6 +709,27 @@ pub trait ModuleEnvironment<'data>: TargetEnvironment {
Err(WasmError::Unsupported("module linking".to_string()))
}
/// Translates a type index to its signature index, only called for type
/// indices which point to functions.
fn type_to_signature(&self, index: TypeIndex) -> WasmResult<SignatureIndex> {
drop(index);
Err(WasmError::Unsupported("module linking".to_string()))
}
/// Translates a type index to its module type index, only called for type
/// indices which point to modules.
fn type_to_module_type(&self, index: TypeIndex) -> WasmResult<ModuleTypeIndex> {
drop(index);
Err(WasmError::Unsupported("module linking".to_string()))
}
/// Translates a type index to its instance type index, only called for type
/// indices which point to instances.
fn type_to_instance_type(&self, index: TypeIndex) -> WasmResult<InstanceTypeIndex> {
drop(index);
Err(WasmError::Unsupported("module linking".to_string()))
}
/// Provides the number of imports up front. By default this does nothing, but
/// implementations can use this to preallocate memory if desired.
fn reserve_imports(&mut self, _num: u32) -> WasmResult<()> {
@@ -844,6 +891,22 @@ pub trait ModuleEnvironment<'data>: TargetEnvironment {
name: &'data str,
) -> WasmResult<()>;
/// Declares an instance export to the environment.
fn declare_instance_export(
&mut self,
index: InstanceIndex,
name: &'data str,
) -> WasmResult<()> {
drop((index, name));
Err(WasmError::Unsupported("module linking".to_string()))
}
/// Declares an instance export to the environment.
fn declare_module_export(&mut self, index: ModuleIndex, name: &'data str) -> WasmResult<()> {
drop((index, name));
Err(WasmError::Unsupported("module linking".to_string()))
}
/// Notifies the implementation that all exports have been declared.
fn finish_exports(&mut self) -> WasmResult<()> {
Ok(())
@@ -951,6 +1014,12 @@ pub trait ModuleEnvironment<'data>: TargetEnvironment {
drop(amount);
}
/// Declares that a module will come later with the type signature provided.
fn declare_module(&mut self, ty: TypeIndex) -> WasmResult<()> {
drop(ty);
Err(WasmError::Unsupported("module linking".to_string()))
}
/// Called at the beginning of translating a module.
///
/// The `index` argument is a monotonically increasing index which
@@ -969,4 +1038,26 @@ pub trait ModuleEnvironment<'data>: TargetEnvironment {
fn module_end(&mut self, index: usize) {
drop(index);
}
/// Indicates that this module will have `amount` instances.
fn reserve_instances(&mut self, amount: u32) {
drop(amount);
}
/// Declares a new instance which this module will instantiate before it's
/// instantiated.
fn declare_instance(&mut self, module: ModuleIndex, args: Vec<EntityIndex>) -> WasmResult<()> {
drop((module, args));
Err(WasmError::Unsupported("wasm instance".to_string()))
}
/// Declares a new alias being added to this module.
///
/// The alias comes from the `instance` specified (or the parent if `None`
/// is supplied) and the index is either in the module's own index spaces
/// for the parent or an index into the exports for nested instances.
fn declare_alias(&mut self, alias: Alias) -> WasmResult<()> {
drop(alias);
Err(WasmError::Unsupported("wasm alias".to_string()))
}
}

View File

@@ -57,7 +57,7 @@ mod state;
mod translation_utils;
pub use crate::environ::{
DummyEnvironment, FuncEnvironment, GlobalVariable, ModuleEnvironment, ReturnMode,
Alias, DummyEnvironment, FuncEnvironment, GlobalVariable, ModuleEnvironment, ReturnMode,
TargetEnvironment, WasmError, WasmFuncType, WasmResult, WasmType,
};
pub use crate::func_translator::FuncTranslator;

View File

@@ -2,9 +2,10 @@
//! to deal with each part of it.
use crate::environ::{ModuleEnvironment, WasmResult};
use crate::sections_translator::{
parse_data_section, parse_element_section, parse_event_section, parse_export_section,
parse_function_section, parse_global_section, parse_import_section, parse_memory_section,
parse_name_section, parse_start_section, parse_table_section, parse_type_section,
parse_alias_section, parse_data_section, parse_element_section, parse_event_section,
parse_export_section, parse_function_section, parse_global_section, parse_import_section,
parse_instance_section, parse_memory_section, parse_module_section, parse_name_section,
parse_start_section, parse_table_section, parse_type_section,
};
use crate::state::ModuleTranslationState;
use cranelift_codegen::timing;
@@ -112,15 +113,15 @@ pub fn translate_module<'data>(
Payload::ModuleSection(s) => {
validator.module_section(&s)?;
environ.reserve_modules(s.get_count());
parse_module_section(s, environ)?;
}
Payload::InstanceSection(s) => {
validator.instance_section(&s)?;
unimplemented!("module linking not implemented yet")
parse_instance_section(s, environ)?;
}
Payload::AliasSection(s) => {
validator.alias_section(&s)?;
unimplemented!("module linking not implemented yet")
parse_alias_section(s, environ)?;
}
Payload::ModuleCodeSectionStart {
count,

View File

@@ -7,12 +7,12 @@
//! The special case of the initialize expressions for table elements offsets or global variables
//! is handled, according to the semantics of WebAssembly, to only specific expressions that are
//! interpreted on the fly.
use crate::environ::{ModuleEnvironment, WasmError, WasmResult};
use crate::environ::{Alias, ModuleEnvironment, WasmError, WasmResult};
use crate::state::ModuleTranslationState;
use crate::translation_utils::{
tabletype_to_type, type_to_type, DataIndex, ElemIndex, EntityType, Event, EventIndex,
FuncIndex, Global, GlobalIndex, GlobalInit, Memory, MemoryIndex, Table, TableElementType,
TableIndex, TypeIndex,
tabletype_to_type, type_to_type, DataIndex, ElemIndex, EntityIndex, EntityType, Event,
EventIndex, FuncIndex, Global, GlobalIndex, GlobalInit, InstanceIndex, Memory, MemoryIndex,
ModuleIndex, Table, TableElementType, TableIndex, TypeIndex,
};
use crate::wasm_unsupported;
use core::convert::TryFrom;
@@ -36,9 +36,15 @@ fn entity_type(
environ: &mut dyn ModuleEnvironment<'_>,
) -> WasmResult<EntityType> {
Ok(match ty {
ImportSectionEntryType::Function(sig) => EntityType::Function(TypeIndex::from_u32(sig)),
ImportSectionEntryType::Module(sig) => EntityType::Module(TypeIndex::from_u32(sig)),
ImportSectionEntryType::Instance(sig) => EntityType::Instance(TypeIndex::from_u32(sig)),
ImportSectionEntryType::Function(sig) => {
EntityType::Function(environ.type_to_signature(TypeIndex::from_u32(sig))?)
}
ImportSectionEntryType::Module(sig) => {
EntityType::Module(environ.type_to_module_type(TypeIndex::from_u32(sig))?)
}
ImportSectionEntryType::Instance(sig) => {
EntityType::Instance(environ.type_to_instance_type(TypeIndex::from_u32(sig))?)
}
ImportSectionEntryType::Memory(ty) => EntityType::Memory(memory(ty)),
ImportSectionEntryType::Event(evt) => EntityType::Event(event(evt)),
ImportSectionEntryType::Global(ty) => {
@@ -156,24 +162,40 @@ pub fn parse_import_section<'data>(
for entry in imports {
let import = entry?;
match entity_type(import.ty, environ)? {
EntityType::Function(idx) => {
environ.declare_func_import(idx, import.module, import.field)?;
match import.ty {
ImportSectionEntryType::Function(sig) => {
environ.declare_func_import(
TypeIndex::from_u32(sig),
import.module,
import.field,
)?;
}
EntityType::Module(idx) => {
environ.declare_module_import(idx, import.module, import.field)?;
ImportSectionEntryType::Module(sig) => {
environ.declare_module_import(
TypeIndex::from_u32(sig),
import.module,
import.field,
)?;
}
EntityType::Instance(idx) => {
environ.declare_instance_import(idx, import.module, import.field)?;
ImportSectionEntryType::Instance(sig) => {
environ.declare_instance_import(
TypeIndex::from_u32(sig),
import.module,
import.field,
)?;
}
EntityType::Memory(ty) => {
environ.declare_memory_import(ty, import.module, import.field)?;
ImportSectionEntryType::Memory(ty) => {
environ.declare_memory_import(memory(ty), import.module, import.field)?;
}
EntityType::Event(e) => environ.declare_event_import(e, import.module, import.field)?,
EntityType::Global(ty) => {
ImportSectionEntryType::Event(e) => {
environ.declare_event_import(event(e), import.module, import.field)?;
}
ImportSectionEntryType::Global(ty) => {
let ty = global(ty, environ, GlobalInit::Import)?;
environ.declare_global_import(ty, import.module, import.field)?;
}
EntityType::Table(ty) => {
ImportSectionEntryType::Table(ty) => {
let ty = table(ty, environ)?;
environ.declare_table_import(ty, import.module, import.field)?;
}
}
@@ -316,9 +338,15 @@ pub fn parse_export_section<'data>(
ExternalKind::Global => {
environ.declare_global_export(GlobalIndex::new(index), field)?
}
ExternalKind::Type | ExternalKind::Module | ExternalKind::Instance => {
unimplemented!("module linking not implemented yet")
ExternalKind::Module => {
environ.declare_module_export(ModuleIndex::new(index), field)?
}
ExternalKind::Instance => {
environ.declare_instance_export(InstanceIndex::new(index), field)?
}
// this never gets past validation
ExternalKind::Type => unreachable!(),
}
}
@@ -475,3 +503,76 @@ pub fn parse_name_section<'data>(
}
Ok(())
}
/// Parses the Module section of the wasm module.
pub fn parse_module_section<'data>(
section: wasmparser::ModuleSectionReader<'data>,
environ: &mut dyn ModuleEnvironment<'data>,
) -> WasmResult<()> {
environ.reserve_modules(section.get_count());
for module_ty in section {
environ.declare_module(TypeIndex::from_u32(module_ty?))?;
}
Ok(())
}
/// Parses the Instance section of the wasm module.
pub fn parse_instance_section<'data>(
section: wasmparser::InstanceSectionReader<'data>,
environ: &mut dyn ModuleEnvironment<'data>,
) -> WasmResult<()> {
environ.reserve_instances(section.get_count());
for instance in section {
let instance = instance?;
let module = ModuleIndex::from_u32(instance.module());
let args = instance
.args()?
.into_iter()
.map(|result| {
let (kind, idx) = result?;
Ok(match kind {
ExternalKind::Function => EntityIndex::Function(FuncIndex::from_u32(idx)),
ExternalKind::Table => EntityIndex::Table(TableIndex::from_u32(idx)),
ExternalKind::Memory => EntityIndex::Memory(MemoryIndex::from_u32(idx)),
ExternalKind::Global => EntityIndex::Global(GlobalIndex::from_u32(idx)),
ExternalKind::Module => EntityIndex::Module(ModuleIndex::from_u32(idx)),
ExternalKind::Instance => EntityIndex::Instance(InstanceIndex::from_u32(idx)),
ExternalKind::Event => unimplemented!(),
// this won't pass validation
ExternalKind::Type => unreachable!(),
})
})
.collect::<WasmResult<Vec<_>>>()?;
environ.declare_instance(module, args)?;
}
Ok(())
}
/// Parses the Alias section of the wasm module.
pub fn parse_alias_section<'data>(
section: wasmparser::AliasSectionReader<'data>,
environ: &mut dyn ModuleEnvironment<'data>,
) -> WasmResult<()> {
for alias in section {
let alias = alias?;
let alias = match alias.instance {
wasmparser::AliasedInstance::Parent => {
match alias.kind {
ExternalKind::Module => Alias::ParentModule(ModuleIndex::from_u32(alias.index)),
ExternalKind::Type => Alias::ParentType(TypeIndex::from_u32(alias.index)),
// shouldn't get past validation
_ => unreachable!(),
}
}
wasmparser::AliasedInstance::Child(i) => Alias::Child {
instance: InstanceIndex::from_u32(i),
export: alias.index as usize,
},
};
environ.declare_alias(alias)?;
}
Ok(())
}

View File

@@ -97,8 +97,20 @@ entity_impl!(InstanceIndex);
pub struct EventIndex(u32);
entity_impl!(EventIndex);
/// Specialized index for just module types.
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct ModuleTypeIndex(u32);
entity_impl!(ModuleTypeIndex);
/// Specialized index for just instance types.
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct InstanceTypeIndex(u32);
entity_impl!(InstanceTypeIndex);
/// An index of an entity.
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub enum EntityIndex {
/// Function index.
@@ -131,13 +143,13 @@ pub enum EntityType {
Table(Table),
/// A function type where the index points to the type section and records a
/// function signature.
Function(TypeIndex),
Function(SignatureIndex),
/// An instance where the index points to the type section and records a
/// instance's exports.
Instance(TypeIndex),
Instance(InstanceTypeIndex),
/// A module where the index points to the type section and records a
/// module's imports and exports.
Module(TypeIndex),
Module(ModuleTypeIndex),
}
/// A WebAssembly global.

View File

@@ -793,8 +793,15 @@
* \typedef wasm_externkind_t
* \brief Classifier for #wasm_externtype_t, defined by #wasm_externkind_enum
*
* This is returned from #wasm_extern_kind and #wasm_externtype_kind to
* determine what kind of type is wrapped.
*
* \enum wasm_externkind_enum
* \brief Kinds of external items for a wasm module.
*
* Note that this also includes #WASM_EXTERN_INSTANCE as well as
* #WASM_EXTERN_MODULE and is intended to be used when #wasm_externkind_t is
* used.
*/
/**

View File

@@ -208,6 +208,14 @@ WASMTIME_CONFIG_PROP(void, wasm_bulk_memory, bool)
*/
WASMTIME_CONFIG_PROP(void, wasm_multi_value, bool)
/**
* \brief Configures whether the WebAssembly module linking proposal is
* enabled.
*
* This setting is `false` by default.
*/
WASMTIME_CONFIG_PROP(void, wasm_module_linking, bool)
/**
* \brief Configures how JIT code will be compiled.
*
@@ -961,6 +969,291 @@ WASM_API_EXTERN own wasmtime_error_t *wasmtime_module_deserialize(
own wasm_module_t **ret
);
/**
* \struct wasm_instancetype_t
* \brief An opaque object representing the type of a function.
*
* \typedef wasm_instancetype_t
* \brief Convenience alias for #wasm_instancetype_t
*
* \struct wasm_instancetype_vec_t
* \brief A list of #wasm_instancetype_t values.
*
* \var wasm_instancetype_vec_t::size
* \brief Length of this vector.
*
* \var wasm_instancetype_vec_t::data
* \brief Pointer to the base of this vector
*
* \typedef wasm_instancetype_vec_t
* \brief Convenience alias for #wasm_instancetype_vec_t
*
* \fn void wasm_instancetype_delete(own wasm_instancetype_t *);
* \brief Deletes a type.
*
* \fn void wasm_instancetype_vec_new_empty(own wasm_instancetype_vec_t *out);
* \brief Creates an empty vector.
*
* See #wasm_byte_vec_new_empty for more information.
*
* \fn void wasm_instancetype_vec_new_uninitialized(own wasm_instancetype_vec_t *out, size_t);
* \brief Creates a vector with the given capacity.
*
* See #wasm_byte_vec_new_uninitialized for more information.
*
* \fn void wasm_instancetype_vec_new(own wasm_instancetype_vec_t *out, size_t, own wasm_instancetype_t *const[]);
* \brief Creates a vector with the provided contents.
*
* See #wasm_byte_vec_new for more information.
*
* \fn void wasm_instancetype_vec_copy(own wasm_instancetype_vec_t *out, const wasm_instancetype_vec_t *)
* \brief Copies one vector to another
*
* See #wasm_byte_vec_copy for more information.
*
* \fn void wasm_instancetype_vec_delete(own wasm_instancetype_vec_t *out)
* \brief Deallocates memory for a vector.
*
* See #wasm_byte_vec_delete for more information.
*
* \fn own wasm_instancetype_t* wasm_instancetype_copy(wasm_instancetype_t *)
* \brief Creates a new value which matches the provided one.
*
* The caller is responsible for deleting the returned value.
*/
WASM_DECLARE_TYPE(instancetype)
/**
* \brief Returns the list of exports that this instance type provides.
*
* This function does not take ownership of the provided instance type but
* ownership of `out` is passed to the caller. Note that `out` is treated as
* uninitialized when passed to this function.
*/
WASM_API_EXTERN void wasm_instancetype_exports(const wasm_instancetype_t*, own wasm_exporttype_vec_t* out);
/**
* \brief Converts a #wasm_instancetype_t to a #wasm_externtype_t
*
* The returned value is owned by the #wasm_instancetype_t argument and should not
* be deleted.
*/
WASM_API_EXTERN wasm_externtype_t* wasm_instancetype_as_externtype(wasm_instancetype_t*);
/**
* \brief Attempts to convert a #wasm_externtype_t to a #wasm_instancetype_t
*
* The returned value is owned by the #wasm_instancetype_t argument and should not
* be deleted. Returns `NULL` if the provided argument is not a
* #wasm_instancetype_t.
*/
WASM_API_EXTERN wasm_instancetype_t* wasm_externtype_as_instancetype(wasm_externtype_t*);
/**
* \brief Converts a #wasm_instancetype_t to a #wasm_externtype_t
*
* The returned value is owned by the #wasm_instancetype_t argument and should not
* be deleted.
*/
WASM_API_EXTERN const wasm_externtype_t* wasm_instancetype_as_externtype_const(const wasm_instancetype_t*);
/**
* \brief Attempts to convert a #wasm_externtype_t to a #wasm_instancetype_t
*
* The returned value is owned by the #wasm_instancetype_t argument and should not
* be deleted. Returns `NULL` if the provided argument is not a
* #wasm_instancetype_t.
*/
WASM_API_EXTERN const wasm_instancetype_t* wasm_externtype_as_instancetype_const(const wasm_externtype_t*);
/**
* \struct wasm_moduletype_t
* \brief An opaque object representing the type of a function.
*
* \typedef wasm_moduletype_t
* \brief Convenience alias for #wasm_moduletype_t
*
* \struct wasm_moduletype_vec_t
* \brief A list of #wasm_moduletype_t values.
*
* \var wasm_moduletype_vec_t::size
* \brief Length of this vector.
*
* \var wasm_moduletype_vec_t::data
* \brief Pointer to the base of this vector
*
* \typedef wasm_moduletype_vec_t
* \brief Convenience alias for #wasm_moduletype_vec_t
*
* \fn void wasm_moduletype_delete(own wasm_moduletype_t *);
* \brief Deletes a type.
*
* \fn void wasm_moduletype_vec_new_empty(own wasm_moduletype_vec_t *out);
* \brief Creates an empty vector.
*
* See #wasm_byte_vec_new_empty for more information.
*
* \fn void wasm_moduletype_vec_new_uninitialized(own wasm_moduletype_vec_t *out, size_t);
* \brief Creates a vector with the given capacity.
*
* See #wasm_byte_vec_new_uninitialized for more information.
*
* \fn void wasm_moduletype_vec_new(own wasm_moduletype_vec_t *out, size_t, own wasm_moduletype_t *const[]);
* \brief Creates a vector with the provided contents.
*
* See #wasm_byte_vec_new for more information.
*
* \fn void wasm_moduletype_vec_copy(own wasm_moduletype_vec_t *out, const wasm_moduletype_vec_t *)
* \brief Copies one vector to another
*
* See #wasm_byte_vec_copy for more information.
*
* \fn void wasm_moduletype_vec_delete(own wasm_moduletype_vec_t *out)
* \brief Deallocates memory for a vector.
*
* See #wasm_byte_vec_delete for more information.
*
* \fn own wasm_moduletype_t* wasm_moduletype_copy(wasm_moduletype_t *)
* \brief Creates a new value which matches the provided one.
*
* The caller is responsible for deleting the returned value.
*/
WASM_DECLARE_TYPE(moduletype)
/**
* \brief Returns the list of imports that this module type requires.
*
* This function does not take ownership of the provided module type but
* ownership of `out` is passed to the caller. Note that `out` is treated as
* uninitialized when passed to this function.
*/
WASM_API_EXTERN void wasm_moduletype_imports(const wasm_moduletype_t*, own wasm_importtype_vec_t* out);
/**
* \brief Returns the list of exports that this module type provides.
*
* This function does not take ownership of the provided module type but
* ownership of `out` is passed to the caller. Note that `out` is treated as
* uninitialized when passed to this function.
*/
WASM_API_EXTERN void wasm_moduletype_exports(const wasm_moduletype_t*, own wasm_exporttype_vec_t* out);
/**
* \brief Converts a #wasm_moduletype_t to a #wasm_externtype_t
*
* The returned value is owned by the #wasm_moduletype_t argument and should not
* be deleted.
*/
WASM_API_EXTERN wasm_externtype_t* wasm_moduletype_as_externtype(wasm_moduletype_t*);
/**
* \brief Attempts to convert a #wasm_externtype_t to a #wasm_moduletype_t
*
* The returned value is owned by the #wasm_moduletype_t argument and should not
* be deleted. Returns `NULL` if the provided argument is not a
* #wasm_moduletype_t.
*/
WASM_API_EXTERN wasm_moduletype_t* wasm_externtype_as_moduletype(wasm_externtype_t*);
/**
* \brief Converts a #wasm_moduletype_t to a #wasm_externtype_t
*
* The returned value is owned by the #wasm_moduletype_t argument and should not
* be deleted.
*/
WASM_API_EXTERN const wasm_externtype_t* wasm_moduletype_as_externtype_const(const wasm_moduletype_t*);
/**
* \brief Attempts to convert a #wasm_externtype_t to a #wasm_moduletype_t
*
* The returned value is owned by the #wasm_moduletype_t argument and should not
* be deleted. Returns `NULL` if the provided argument is not a
* #wasm_moduletype_t.
*/
WASM_API_EXTERN const wasm_moduletype_t* wasm_externtype_as_moduletype_const(const wasm_externtype_t*);
/**
* \brief Converts a #wasm_module_t to #wasm_extern_t.
*
* The returned #wasm_extern_t is owned by the #wasm_module_t argument. Callers
* should not delete the returned value, and it only lives as long as the
* #wasm_module_t argument.
*/
WASM_API_EXTERN wasm_extern_t* wasm_module_as_extern(wasm_module_t*);
/**
* \brief Converts a #wasm_extern_t to #wasm_module_t.
*
* The returned #wasm_module_t is owned by the #wasm_extern_t argument. Callers
* should not delete the returned value, and it only lives as long as the
* #wasm_extern_t argument.
*
* If the #wasm_extern_t argument isn't a #wasm_module_t then `NULL` is returned.
*/
WASM_API_EXTERN wasm_module_t* wasm_extern_as_module(wasm_extern_t*);
/**
* \brief Converts a #wasm_extern_t to #wasm_instance_t.
*
* The returned #wasm_instance_t is owned by the #wasm_extern_t argument. Callers
* should not delete the returned value, and it only lives as long as the
* #wasm_extern_t argument.
*/
WASM_API_EXTERN const wasm_module_t* wasm_extern_as_module_const(const wasm_extern_t*);
/**
* \brief Converts a #wasm_instance_t to #wasm_extern_t.
*
* The returned #wasm_extern_t is owned by the #wasm_instance_t argument. Callers
* should not delete the returned value, and it only lives as long as the
* #wasm_instance_t argument.
*/
WASM_API_EXTERN wasm_extern_t* wasm_instance_as_extern(wasm_instance_t*);
/**
* \brief Converts a #wasm_extern_t to #wasm_instance_t.
*
* The returned #wasm_instance_t is owned by the #wasm_extern_t argument. Callers
* should not delete the returned value, and it only lives as long as the
* #wasm_extern_t argument.
*
* If the #wasm_extern_t argument isn't a #wasm_instance_t then `NULL` is returned.
*/
WASM_API_EXTERN wasm_instance_t* wasm_extern_as_instance(wasm_extern_t*);
/**
* \brief Converts a #wasm_extern_t to #wasm_instance_t.
*
* The returned #wasm_instance_t is owned by the #wasm_extern_t argument. Callers
* should not delete the returned value, and it only lives as long as the
* #wasm_extern_t argument.
*/
WASM_API_EXTERN const wasm_instance_t* wasm_extern_as_instance_const(const wasm_extern_t*);
/**
* \brief Returns the type of this instance.
*
* The returned #wasm_instancetype_t is expected to be deallocated by the caller.
*/
WASM_API_EXTERN own wasm_instancetype_t* wasm_instance_type(const wasm_instance_t*);
/**
* \brief Returns the type of this module.
*
* The returned #wasm_moduletype_t is expected to be deallocated by the caller.
*/
WASM_API_EXTERN own wasm_moduletype_t* wasm_module_type(const wasm_module_t*);
/**
* \brief Value of #wasm_externkind_enum corresponding to a wasm module.
*/
#define WASM_EXTERN_MODULE 4
/**
* \brief Value of #wasm_externkind_enum corresponding to a wasm instance.
*/
#define WASM_EXTERN_INSTANCE 5
#undef own
#ifdef __cplusplus

View File

@@ -85,6 +85,11 @@ pub extern "C" fn wasmtime_config_wasm_multi_value_set(c: &mut wasm_config_t, en
c.config.wasm_multi_value(enable);
}
#[no_mangle]
pub extern "C" fn wasmtime_config_wasm_module_linking_set(c: &mut wasm_config_t, enable: bool) {
c.config.wasm_module_linking(enable);
}
#[no_mangle]
pub extern "C" fn wasmtime_config_strategy_set(
c: &mut wasm_config_t,

View File

@@ -1,5 +1,7 @@
use crate::wasm_externkind_t;
use crate::{wasm_externtype_t, wasm_func_t, wasm_global_t, wasm_memory_t, wasm_table_t};
use crate::{
wasm_externkind_t, wasm_externtype_t, wasm_func_t, wasm_global_t, wasm_instance_t,
wasm_memory_t, wasm_module_t, wasm_table_t,
};
use wasmtime::Extern;
#[derive(Clone)]
@@ -16,6 +18,8 @@ pub extern "C" fn wasm_extern_kind(e: &wasm_extern_t) -> wasm_externkind_t {
Extern::Global(_) => crate::WASM_EXTERN_GLOBAL,
Extern::Table(_) => crate::WASM_EXTERN_TABLE,
Extern::Memory(_) => crate::WASM_EXTERN_MEMORY,
Extern::Instance(_) => crate::WASM_EXTERN_INSTANCE,
Extern::Module(_) => crate::WASM_EXTERN_MODULE,
}
}
@@ -63,3 +67,23 @@ pub extern "C" fn wasm_extern_as_memory(e: &wasm_extern_t) -> Option<&wasm_memor
pub extern "C" fn wasm_extern_as_memory_const(e: &wasm_extern_t) -> Option<&wasm_memory_t> {
wasm_extern_as_memory(e)
}
#[no_mangle]
pub extern "C" fn wasm_extern_as_module(e: &wasm_extern_t) -> Option<&wasm_module_t> {
wasm_module_t::try_from(e)
}
#[no_mangle]
pub extern "C" fn wasm_extern_as_module_const(e: &wasm_extern_t) -> Option<&wasm_module_t> {
wasm_extern_as_module(e)
}
#[no_mangle]
pub extern "C" fn wasm_extern_as_instance(e: &wasm_extern_t) -> Option<&wasm_instance_t> {
wasm_instance_t::try_from(e)
}
#[no_mangle]
pub extern "C" fn wasm_extern_as_instance_const(e: &wasm_extern_t) -> Option<&wasm_instance_t> {
wasm_extern_as_instance(e)
}

View File

@@ -1,20 +1,38 @@
use crate::{wasm_extern_t, wasm_extern_vec_t, wasm_module_t, wasm_trap_t};
use crate::{wasm_store_t, wasmtime_error_t};
use crate::{wasm_instancetype_t, wasm_store_t, wasmtime_error_t};
use anyhow::Result;
use std::ptr;
use wasmtime::{Instance, Trap};
use wasmtime::{Extern, Instance, Trap};
#[repr(C)]
#[derive(Clone)]
#[repr(transparent)]
pub struct wasm_instance_t {
pub(crate) instance: Instance,
ext: wasm_extern_t,
}
wasmtime_c_api_macros::declare_ref!(wasm_instance_t);
impl wasm_instance_t {
pub(crate) fn new(instance: Instance) -> wasm_instance_t {
wasm_instance_t { instance: instance }
wasm_instance_t {
ext: wasm_extern_t {
which: instance.into(),
},
}
}
pub(crate) fn try_from(e: &wasm_extern_t) -> Option<&wasm_instance_t> {
match &e.which {
Extern::Instance(_) => Some(unsafe { &*(e as *const _ as *const _) }),
_ => None,
}
}
pub(crate) fn instance(&self) -> &Instance {
match &self.ext.which {
Extern::Instance(i) => i,
_ => unreachable!(),
}
}
}
@@ -31,7 +49,7 @@ pub unsafe extern "C" fn wasm_instance_new(
store,
wasm_module,
imports,
wasm_module.imports.len(),
wasm_module.module().imports().len(),
&mut instance,
&mut trap,
);
@@ -92,7 +110,7 @@ fn _wasmtime_instance_new(
.map(|import| import.which.clone())
.collect::<Vec<_>>();
handle_instantiate(
Instance::new(store, &module.module, &imports),
Instance::new(store, module.module(), &imports),
instance_ptr,
trap_ptr,
)
@@ -122,11 +140,16 @@ pub fn handle_instantiate(
}
}
#[no_mangle]
pub extern "C" fn wasm_instance_as_extern(m: &wasm_instance_t) -> &wasm_extern_t {
&m.ext
}
#[no_mangle]
pub extern "C" fn wasm_instance_exports(instance: &wasm_instance_t, out: &mut wasm_extern_vec_t) {
out.set_buffer(
instance
.instance
.instance()
.exports()
.map(|e| {
Some(Box::new(wasm_extern_t {
@@ -136,3 +159,8 @@ pub extern "C" fn wasm_instance_exports(instance: &wasm_instance_t, out: &mut wa
.collect(),
);
}
#[no_mangle]
pub extern "C" fn wasm_instance_type(f: &wasm_instance_t) -> Box<wasm_instancetype_t> {
Box::new(wasm_instancetype_t::new(f.instance().ty()))
}

View File

@@ -68,7 +68,7 @@ pub extern "C" fn wasmtime_linker_define_instance(
Ok(s) => s,
Err(_) => return bad_utf8(),
};
handle_result(linker.instance(name, &instance.instance), |_linker| ())
handle_result(linker.instance(name, instance.instance()), |_linker| ())
}
#[no_mangle]
@@ -78,7 +78,7 @@ pub extern "C" fn wasmtime_linker_instantiate(
instance_ptr: &mut *mut wasm_instance_t,
trap_ptr: &mut *mut wasm_trap_t,
) -> Option<Box<wasmtime_error_t>> {
let result = linker.linker.instantiate(&module.module);
let result = linker.linker.instantiate(module.module());
super::instance::handle_instantiate(result, instance_ptr, trap_ptr)
}
@@ -93,7 +93,7 @@ pub extern "C" fn wasmtime_linker_module(
Ok(s) => s,
Err(_) => return bad_utf8(),
};
handle_result(linker.module(name, &module.module), |_linker| ())
handle_result(linker.module(name, module.module()), |_linker| ())
}
#[no_mangle]

View File

@@ -1,20 +1,43 @@
use crate::{
handle_result, wasm_byte_vec_t, wasm_engine_t, wasm_exporttype_t, wasm_exporttype_vec_t,
wasm_importtype_t, wasm_importtype_vec_t, wasm_store_t, wasmtime_error_t,
wasm_extern_t, wasm_importtype_t, wasm_importtype_vec_t, wasm_moduletype_t, wasm_store_t,
wasmtime_error_t,
};
use std::ptr;
use wasmtime::{Engine, Module};
use wasmtime::{Engine, Extern, Module};
#[repr(C)]
#[derive(Clone)]
#[repr(transparent)]
pub struct wasm_module_t {
pub(crate) module: Module,
pub(crate) imports: Vec<wasm_importtype_t>,
pub(crate) exports: Vec<wasm_exporttype_t>,
ext: wasm_extern_t,
}
wasmtime_c_api_macros::declare_ref!(wasm_module_t);
impl wasm_module_t {
pub(crate) fn new(module: Module) -> wasm_module_t {
wasm_module_t {
ext: wasm_extern_t {
which: module.into(),
},
}
}
pub(crate) fn try_from(e: &wasm_extern_t) -> Option<&wasm_module_t> {
match &e.which {
Extern::Module(_) => Some(unsafe { &*(e as *const _ as *const _) }),
_ => None,
}
}
pub(crate) fn module(&self) -> &Module {
match &self.ext.which {
Extern::Module(i) => i,
_ => unreachable!(),
}
}
}
#[repr(C)]
#[derive(Clone)]
pub struct wasm_shared_module_t {
@@ -49,25 +72,7 @@ pub extern "C" fn wasmtime_module_new(
) -> Option<Box<wasmtime_error_t>> {
let binary = binary.as_slice();
handle_result(Module::from_binary(&engine.engine, binary), |module| {
let imports = module
.imports()
.map(|i| {
wasm_importtype_t::new(
i.module().to_owned(),
i.name().map(|s| s.to_owned()),
i.ty(),
)
})
.collect::<Vec<_>>();
let exports = module
.exports()
.map(|e| wasm_exporttype_t::new(e.name().to_owned(), e.ty()))
.collect::<Vec<_>>();
let module = Box::new(wasm_module_t {
module: module,
imports,
exports,
});
let module = Box::new(wasm_module_t::new(module));
*ret = Box::into_raw(module);
})
}
@@ -86,30 +91,46 @@ pub extern "C" fn wasmtime_module_validate(
handle_result(Module::validate(store.store.engine(), binary), |()| {})
}
#[no_mangle]
pub extern "C" fn wasm_module_as_extern(m: &wasm_module_t) -> &wasm_extern_t {
&m.ext
}
#[no_mangle]
pub extern "C" fn wasm_module_exports(module: &wasm_module_t, out: &mut wasm_exporttype_vec_t) {
let buffer = module
.exports
.iter()
.map(|et| Some(Box::new(et.clone())))
let exports = module
.module()
.exports()
.map(|e| {
Some(Box::new(wasm_exporttype_t::new(
e.name().to_owned(),
e.ty(),
)))
})
.collect::<Vec<_>>();
out.set_buffer(buffer);
out.set_buffer(exports);
}
#[no_mangle]
pub extern "C" fn wasm_module_imports(module: &wasm_module_t, out: &mut wasm_importtype_vec_t) {
let buffer = module
.imports
.iter()
.map(|it| Some(Box::new(it.clone())))
let imports = module
.module()
.imports()
.map(|i| {
Some(Box::new(wasm_importtype_t::new(
i.module().to_owned(),
i.name().map(|s| s.to_owned()),
i.ty(),
)))
})
.collect::<Vec<_>>();
out.set_buffer(buffer);
out.set_buffer(imports);
}
#[no_mangle]
pub extern "C" fn wasm_module_share(module: &wasm_module_t) -> Box<wasm_shared_module_t> {
Box::new(wasm_shared_module_t {
module: module.module.clone(),
module: module.module().clone(),
})
}
@@ -122,25 +143,7 @@ pub extern "C" fn wasm_module_obtain(
if !Engine::same(store.store.engine(), module.engine()) {
return None;
}
let imports = module
.imports()
.map(|i| {
wasm_importtype_t::new(
i.module().to_owned(),
i.name().map(|s| s.to_owned()),
i.ty(),
)
})
.collect::<Vec<_>>();
let exports = module
.exports()
.map(|e| wasm_exporttype_t::new(e.name().to_owned(), e.ty()))
.collect::<Vec<_>>();
Some(Box::new(wasm_module_t {
module: module,
imports,
exports,
}))
Some(Box::new(wasm_module_t::new(module)))
}
#[no_mangle]
@@ -171,7 +174,7 @@ pub extern "C" fn wasmtime_module_serialize(
module: &wasm_module_t,
ret: &mut wasm_byte_vec_t,
) -> Option<Box<wasmtime_error_t>> {
handle_result(module.module.serialize(), |buf| {
handle_result(module.module().serialize(), |buf| {
ret.set_buffer(buf);
})
}
@@ -185,26 +188,13 @@ pub extern "C" fn wasmtime_module_deserialize(
handle_result(
Module::deserialize(&engine.engine, binary.as_slice()),
|module| {
let imports = module
.imports()
.map(|i| {
wasm_importtype_t::new(
i.module().to_owned(),
i.name().map(|s| s.to_owned()),
i.ty(),
)
})
.collect::<Vec<_>>();
let exports = module
.exports()
.map(|e| wasm_exporttype_t::new(e.name().to_owned(), e.ty()))
.collect::<Vec<_>>();
let module = Box::new(wasm_module_t {
module: module,
imports,
exports,
});
let module = Box::new(wasm_module_t::new(module));
*ret = Box::into_raw(module);
},
)
}
#[no_mangle]
pub extern "C" fn wasm_module_type(f: &wasm_module_t) -> Box<wasm_moduletype_t> {
Box::new(wasm_moduletype_t::new(f.module().ty()))
}

View File

@@ -27,8 +27,8 @@ pub const WASM_EXTERN_FUNC: wasm_externkind_t = 0;
pub const WASM_EXTERN_GLOBAL: wasm_externkind_t = 1;
pub const WASM_EXTERN_TABLE: wasm_externkind_t = 2;
pub const WASM_EXTERN_MEMORY: wasm_externkind_t = 3;
pub const WASMTIME_EXTERN_MODULE: wasm_externkind_t = 4;
pub const WASMTIME_EXTERN_INSTANCE: wasm_externkind_t = 5;
pub const WASM_EXTERN_MODULE: wasm_externkind_t = 4;
pub const WASM_EXTERN_INSTANCE: wasm_externkind_t = 5;
impl wasm_externtype_t {
pub(crate) fn new(ty: ExternType) -> wasm_externtype_t {
@@ -63,8 +63,8 @@ pub extern "C" fn wasm_externtype_kind(et: &wasm_externtype_t) -> wasm_externkin
CExternType::Table(_) => WASM_EXTERN_TABLE,
CExternType::Global(_) => WASM_EXTERN_GLOBAL,
CExternType::Memory(_) => WASM_EXTERN_MEMORY,
CExternType::Instance(_) => WASMTIME_EXTERN_INSTANCE,
CExternType::Module(_) => WASMTIME_EXTERN_MODULE,
CExternType::Instance(_) => WASM_EXTERN_INSTANCE,
CExternType::Module(_) => WASM_EXTERN_MODULE,
}
}

View File

@@ -1,5 +1,4 @@
use crate::{wasm_externtype_t, wasm_limits_t, CExternType};
use once_cell::unsync::OnceCell;
use crate::{wasm_exporttype_t, wasm_exporttype_vec_t, wasm_externtype_t, CExternType};
use wasmtime::InstanceType;
#[repr(transparent)]
@@ -13,24 +12,33 @@ wasmtime_c_api_macros::declare_ty!(wasm_instancetype_t);
#[derive(Clone)]
pub(crate) struct CInstanceType {
pub(crate) ty: InstanceType,
limits_cache: OnceCell<wasm_limits_t>,
}
impl wasm_instancetype_t {
pub(crate) fn new(ty: InstanceType) -> wasm_instancetype_t {
wasm_instancetype_t {
ext: wasm_externtype_t::new(ty.into()),
}
}
pub(crate) fn try_from(e: &wasm_externtype_t) -> Option<&wasm_instancetype_t> {
match &e.which {
CExternType::Instance(_) => Some(unsafe { &*(e as *const _ as *const _) }),
_ => None,
}
}
pub(crate) fn ty(&self) -> &CInstanceType {
match &self.ext.which {
CExternType::Instance(f) => &f,
_ => unreachable!(),
}
}
}
impl CInstanceType {
pub(crate) fn new(ty: InstanceType) -> CInstanceType {
CInstanceType {
ty,
limits_cache: OnceCell::new(),
}
CInstanceType { ty }
}
}
#[no_mangle]
@@ -44,3 +52,22 @@ pub extern "C" fn wasm_instancetype_as_externtype_const(
) -> &wasm_externtype_t {
&ty.ext
}
#[no_mangle]
pub extern "C" fn wasm_instancetype_exports(
instance: &wasm_instancetype_t,
out: &mut wasm_exporttype_vec_t,
) {
let exports = instance
.ty()
.ty
.exports()
.map(|e| {
Some(Box::new(wasm_exporttype_t::new(
e.name().to_owned(),
e.ty(),
)))
})
.collect::<Vec<_>>();
out.set_buffer(exports);
}

View File

@@ -1,5 +1,7 @@
use crate::{wasm_externtype_t, wasm_limits_t, CExternType};
use once_cell::unsync::OnceCell;
use crate::{
wasm_exporttype_t, wasm_exporttype_vec_t, wasm_externtype_t, wasm_importtype_t,
wasm_importtype_vec_t, CExternType,
};
use wasmtime::ModuleType;
#[repr(transparent)]
@@ -13,24 +15,33 @@ wasmtime_c_api_macros::declare_ty!(wasm_moduletype_t);
#[derive(Clone)]
pub(crate) struct CModuleType {
pub(crate) ty: ModuleType,
limits_cache: OnceCell<wasm_limits_t>,
}
impl wasm_moduletype_t {
pub(crate) fn new(ty: ModuleType) -> wasm_moduletype_t {
wasm_moduletype_t {
ext: wasm_externtype_t::new(ty.into()),
}
}
pub(crate) fn try_from(e: &wasm_externtype_t) -> Option<&wasm_moduletype_t> {
match &e.which {
CExternType::Module(_) => Some(unsafe { &*(e as *const _ as *const _) }),
_ => None,
}
}
pub(crate) fn ty(&self) -> &CModuleType {
match &self.ext.which {
CExternType::Module(f) => &f,
_ => unreachable!(),
}
}
}
impl CModuleType {
pub(crate) fn new(ty: ModuleType) -> CModuleType {
CModuleType {
ty,
limits_cache: OnceCell::new(),
}
CModuleType { ty }
}
}
@@ -45,3 +56,42 @@ pub extern "C" fn wasm_moduletype_as_externtype_const(
) -> &wasm_externtype_t {
&ty.ext
}
#[no_mangle]
pub extern "C" fn wasm_moduletype_exports(
module: &wasm_moduletype_t,
out: &mut wasm_exporttype_vec_t,
) {
let exports = module
.ty()
.ty
.exports()
.map(|e| {
Some(Box::new(wasm_exporttype_t::new(
e.name().to_owned(),
e.ty(),
)))
})
.collect::<Vec<_>>();
out.set_buffer(exports);
}
#[no_mangle]
pub extern "C" fn wasm_moduletype_imports(
module: &wasm_moduletype_t,
out: &mut wasm_importtype_vec_t,
) {
let imports = module
.ty()
.ty
.imports()
.map(|i| {
Some(Box::new(wasm_importtype_t::new(
i.module().to_owned(),
i.name().map(|s| s.to_owned()),
i.ty(),
)))
})
.collect::<Vec<_>>();
out.set_buffer(imports);
}

View File

@@ -1,7 +1,8 @@
use crate::wasm_valtype_t;
use crate::{wasm_exporttype_t, wasm_extern_t, wasm_frame_t, wasm_val_t};
use crate::{wasm_externtype_t, wasm_importtype_t, wasm_memorytype_t};
use crate::{wasm_functype_t, wasm_globaltype_t, wasm_tabletype_t};
use crate::{
wasm_exporttype_t, wasm_extern_t, wasm_externtype_t, wasm_frame_t, wasm_functype_t,
wasm_globaltype_t, wasm_importtype_t, wasm_instancetype_t, wasm_memorytype_t,
wasm_moduletype_t, wasm_tabletype_t, wasm_val_t, wasm_valtype_t,
};
use std::mem;
use std::ptr;
use std::slice;
@@ -172,6 +173,24 @@ declare_vecs! {
copy: wasm_memorytype_vec_copy,
delete: wasm_memorytype_vec_delete,
)
(
name: wasm_instancetype_vec_t,
ty: Option<Box<wasm_instancetype_t>>,
new: wasm_instancetype_vec_new,
empty: wasm_instancetype_vec_new_empty,
uninit: wasm_instancetype_vec_new_uninitialized,
copy: wasm_instancetype_vec_copy,
delete: wasm_instancetype_vec_delete,
)
(
name: wasm_moduletype_vec_t,
ty: Option<Box<wasm_moduletype_t>>,
new: wasm_moduletype_vec_new,
empty: wasm_moduletype_vec_new_empty,
uninit: wasm_moduletype_vec_new_uninitialized,
copy: wasm_moduletype_vec_copy,
delete: wasm_moduletype_vec_delete,
)
(
name: wasm_externtype_vec_t,
ty: Option<Box<wasm_externtype_t>>,

View File

@@ -1039,7 +1039,6 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
callee: ir::Value,
call_args: &[ir::Value],
) -> WasmResult<ir::Inst> {
let sig_index = self.module.types[ty_index].unwrap_function();
let pointer_type = self.pointer_type();
let table_entry_addr = pos.ins().table_addr(pointer_type, table, callee, 0);
@@ -1071,7 +1070,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
let vmctx = self.vmctx(pos.func);
let base = pos.ins().global_value(pointer_type, vmctx);
let offset =
i32::try_from(self.offsets.vmctx_vmshared_signature_id(sig_index)).unwrap();
i32::try_from(self.offsets.vmctx_vmshared_signature_id(ty_index)).unwrap();
// Load the caller ID.
let mut mem_flags = ir::MemFlags::trusted();

View File

@@ -99,7 +99,7 @@ use std::sync::Mutex;
use wasmtime_environ::{
CompileError, CompiledFunction, Compiler, FunctionAddressMap, FunctionBodyData,
InstructionAddressMap, ModuleTranslation, Relocation, RelocationTarget, StackMapInformation,
TrapInformation, Tunables,
TrapInformation, Tunables, TypeTables,
};
mod func_environ;
@@ -348,21 +348,22 @@ impl Compiler for Cranelift {
mut input: FunctionBodyData<'_>,
isa: &dyn isa::TargetIsa,
tunables: &Tunables,
types: &TypeTables,
) -> Result<CompiledFunction, CompileError> {
let module = &translation.module;
let func_index = module.func_index(func_index);
let mut context = Context::new();
context.func.name = get_func_name(func_index);
let sig_index = module.functions[func_index];
context.func.signature = translation.native_signatures[sig_index].clone();
if tunables.debug_info {
context.func.signature = types.native_signatures[sig_index].clone();
if tunables.generate_native_debuginfo {
context.func.collect_debug_info();
}
let mut func_env = FuncEnvironment::new(
isa.frontend_config(),
module,
&translation.native_signatures,
&types.native_signatures,
tunables,
);
@@ -434,7 +435,7 @@ impl Compiler for Cranelift {
let address_transform =
get_function_address_map(&context, &input, code_buf.len() as u32, isa);
let ranges = if tunables.debug_info {
let ranges = if tunables.generate_native_debuginfo {
let ranges = context.build_value_labels_ranges(isa).map_err(|error| {
CompileError::Codegen(pretty_error(&context.func, Some(isa), error))
})?;

View File

@@ -13,7 +13,7 @@ edition = "2018"
[dependencies]
gimli = "0.23.0"
wasmparser = "0.68.0"
wasmparser = "0.70"
object = { version = "0.22.0", default-features = false, features = ["read", "write"] }
wasmtime-environ = { path = "../environ", version = "0.21.0" }
target-lexicon = { version = "0.11.0", default-features = false }

View File

@@ -16,7 +16,7 @@ anyhow = "1.0"
cranelift-codegen = { path = "../../cranelift/codegen", version = "0.68.0", features = ["enable-serde"] }
cranelift-entity = { path = "../../cranelift/entity", version = "0.68.0", features = ["enable-serde"] }
cranelift-wasm = { path = "../../cranelift/wasm", version = "0.68.0", features = ["enable-serde"] }
wasmparser = "0.68.0"
wasmparser = "0.70"
indexmap = { version = "1.0.2", features = ["serde-1"] }
thiserror = "1.0.4"
serde = { version = "1.0.94", features = ["derive"] }

Some files were not shown because too many files have changed in this diff Show More