Merge pull request #2791 from peterhuene/compile-command

Add a compile command to Wasmtime.
This commit is contained in:
Peter Huene
2021-04-02 11:18:14 -07:00
committed by GitHub
54 changed files with 2324 additions and 505 deletions

5
Cargo.lock generated
View File

@@ -3273,6 +3273,7 @@ dependencies = [
"file-per-thread-logger",
"filecheck",
"humantime 2.1.0",
"lazy_static",
"libc",
"log",
"more-asserts",
@@ -3597,9 +3598,9 @@ dependencies = [
[[package]]
name = "wat"
version = "1.0.36"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b0fa059022c5dabe129f02b429d67086400deb8277f89c975555dacc1dadbcc"
checksum = "8ec280a739b69173e0ffd12c1658507996836ba4e992ed9bc1e5385a0bd72a02"
dependencies = [
"wast 35.0.1",
]

View File

@@ -38,12 +38,13 @@ anyhow = "1.0.19"
target-lexicon = { version = "0.12.0", default-features = false }
pretty_env_logger = "0.4.0"
file-per-thread-logger = "0.1.1"
wat = "1.0.36"
wat = "1.0.37"
libc = "0.2.60"
log = "0.4.8"
rayon = "1.2.1"
humantime = "2.0.0"
wasmparser = "0.77.0"
lazy_static = "1.4.0"
[dev-dependencies]
env_logger = "0.8.1"
@@ -90,6 +91,7 @@ vtune = ["wasmtime/vtune"]
wasi-crypto = ["wasmtime-wasi-crypto"]
wasi-nn = ["wasmtime-wasi-nn"]
uffd = ["wasmtime/uffd"]
all-arch = ["wasmtime/all-arch"]
# Try the experimental, work-in-progress new x86_64 backend. This is not stable
# as of June 2020.

View File

@@ -2,6 +2,47 @@
--------------------------------------------------------------------------------
## Unreleased
### Added
* Added the `wasmtime compile` command to support AOT compilation of Wasm modules.
* Added the `Engine::precompile_module` method to support AOT module compilation.
* Added the `Config::target` method to change the compilation target of the
configuration. This can be used in conjunction with `Engine::precompile_module`
to target a different host triple than the current one.
* Added the `Config::cranelift_flag_enable` method to enable setting Cranelift
boolean flags or presets in a config.
* Added CLI option `--cranelift-enable` to enable boolean settings and ISA presets.
### Changed
* Wasmtime CLI options to enable WebAssembly features have been replaced with a
singular `--wasm-features` option. The previous options are still supported, but
are not displayed in help text.
* Breaking: `Module::deserialize` has been removed in favor of `Module::new`.
* Breaking: `Config::cranelift_clear_cpu_flags` was removed. Use `Config::target`
to clear the CPU flags for the host's target.
* Breaking: `Config::cranelift_other_flag` was renamed to `Config::cranelift_flag_set`.
* Breaking: the CLI option `--cranelift-flags` was changed to `--cranelift-set`.
* Breaking: the CLI option `--enable-reference-types=false` has been changed to
`--wasm-features=-reference-types`.
* Breaking: the CLI option `--enable-multi-value=false` has been changed to
`--wasm-features=-multi-value`.
* Breaking: the CLI option `--enable-bulk-memory=false` has been changed to
`--wasm-features=-bulk-memory`.
## 0.25.0
Released 2021-03-16.
@@ -39,7 +80,7 @@ Released 2021-03-16.
### Fixed
* Interepretation of timestamps in `poll_oneoff` for WASI have been fixed to
* Interpretation of timestamps in `poll_oneoff` for WASI have been fixed to
correctly use nanoseconds instead of microseconds.
[#2717](https://github.com/bytecodealliance/wasmtime/pull/2717)

View File

@@ -53,3 +53,4 @@ wasm = ["wat", "cranelift-wasm"]
experimental_x64 = ["cranelift-codegen/x64", "cranelift-filetests/experimental_x64", "cranelift-reader/experimental_x64"]
experimental_arm32 = ["cranelift-codegen/arm32", "cranelift-filetests/experimental_arm32"]
souper-harvest = ["cranelift-codegen/souper-harvest", "rayon"]
all-arch = ["cranelift-codegen/all-arch"]

View File

@@ -20,6 +20,7 @@ pub(crate) enum SpecificSetting {
#[derive(Hash, PartialEq, Eq)]
pub(crate) struct Setting {
pub name: &'static str,
pub description: &'static str,
pub comment: &'static str,
pub specific: SpecificSetting,
pub byte_offset: u8,
@@ -88,6 +89,7 @@ impl Into<PresetType> for PresetIndex {
#[derive(Hash, PartialEq, Eq)]
pub(crate) struct Preset {
pub name: &'static str,
pub description: &'static str,
values: Vec<BoolSettingIndex>,
}
@@ -169,6 +171,7 @@ pub(crate) enum ProtoSpecificSetting {
/// This is the information provided during building for a setting.
struct ProtoSetting {
name: &'static str,
description: &'static str,
comment: &'static str,
specific: ProtoSpecificSetting,
}
@@ -251,11 +254,13 @@ impl SettingGroupBuilder {
fn add_setting(
&mut self,
name: &'static str,
description: &'static str,
comment: &'static str,
specific: ProtoSpecificSetting,
) {
self.settings.push(ProtoSetting {
name,
description,
comment,
specific,
})
@@ -264,6 +269,7 @@ impl SettingGroupBuilder {
pub fn add_bool(
&mut self,
name: &'static str,
description: &'static str,
comment: &'static str,
default: bool,
) -> BoolSettingIndex {
@@ -271,28 +277,55 @@ impl SettingGroupBuilder {
self.predicates.is_empty(),
"predicates must be added after the boolean settings"
);
self.add_setting(name, comment, ProtoSpecificSetting::Bool(default));
self.add_setting(
name,
description,
comment,
ProtoSpecificSetting::Bool(default),
);
BoolSettingIndex(self.settings.len() - 1)
}
pub fn add_enum(
&mut self,
name: &'static str,
description: &'static str,
comment: &'static str,
values: Vec<&'static str>,
) {
self.add_setting(name, comment, ProtoSpecificSetting::Enum(values));
self.add_setting(
name,
description,
comment,
ProtoSpecificSetting::Enum(values),
);
}
pub fn add_num(&mut self, name: &'static str, comment: &'static str, default: u8) {
self.add_setting(name, comment, ProtoSpecificSetting::Num(default));
pub fn add_num(
&mut self,
name: &'static str,
description: &'static str,
comment: &'static str,
default: u8,
) {
self.add_setting(
name,
description,
comment,
ProtoSpecificSetting::Num(default),
);
}
pub fn add_predicate(&mut self, name: &'static str, node: PredicateNode) {
self.predicates.push(ProtoPredicate { name, node });
}
pub fn add_preset(&mut self, name: &'static str, args: Vec<PresetType>) -> PresetIndex {
pub fn add_preset(
&mut self,
name: &'static str,
description: &'static str,
args: Vec<PresetType>,
) -> PresetIndex {
let mut values = Vec::new();
for arg in args {
match arg {
@@ -302,7 +335,11 @@ impl SettingGroupBuilder {
PresetType::BoolSetting(index) => values.push(index),
}
}
self.presets.push(Preset { name, values });
self.presets.push(Preset {
name,
description,
values,
});
PresetIndex(self.presets.len() - 1)
}
@@ -347,6 +384,7 @@ impl SettingGroupBuilder {
group.settings.push(Setting {
name: s.name,
description: s.description,
comment: s.comment,
byte_offset,
specific,
@@ -367,6 +405,7 @@ impl SettingGroupBuilder {
};
group.settings.push(Setting {
name: s.name,
description: s.description,
comment: s.comment,
byte_offset: byte_offset + predicate_number / 8,
specific: SpecificSetting::Bool(BoolSetting {

View File

@@ -70,6 +70,33 @@ fn gen_constructor(group: &SettingGroup, parent: ParentGroup, fmt: &mut Formatte
fmtln!(fmt, "}");
}
/// Generates the `iter` function.
fn gen_iterator(group: &SettingGroup, fmt: &mut Formatter) {
fmtln!(fmt, "impl Flags {");
fmt.indent(|fmt| {
fmt.doc_comment("Iterates the setting values.");
fmtln!(fmt, "pub fn iter(&self) -> impl Iterator<Item = Value> {");
fmt.indent(|fmt| {
fmtln!(fmt, "let mut bytes = [0; {}];", group.settings_size);
fmtln!(fmt, "bytes.copy_from_slice(&self.bytes[0..{}]);", group.settings_size);
fmtln!(fmt, "DESCRIPTORS.iter().filter_map(move |d| {");
fmt.indent(|fmt| {
fmtln!(fmt, "let values = match &d.detail {");
fmt.indent(|fmt| {
fmtln!(fmt, "detail::Detail::Preset => return None,");
fmtln!(fmt, "detail::Detail::Enum { last, enumerators } => Some(TEMPLATE.enums(*last, *enumerators)),");
fmtln!(fmt, "_ => None");
});
fmtln!(fmt, "};");
fmtln!(fmt, "Some(Value{ name: d.name, detail: d.detail, values, value: bytes[d.offset as usize] })");
});
fmtln!(fmt, "})");
});
fmtln!(fmt, "}");
});
fmtln!(fmt, "}");
}
/// Emit Display and FromStr implementations for enum settings.
fn gen_to_and_from_str(name: &str, values: &[&'static str], fmt: &mut Formatter) {
fmtln!(fmt, "impl fmt::Display for {} {{", name);
@@ -136,7 +163,7 @@ fn gen_enum_types(group: &SettingGroup, fmt: &mut Formatter) {
/// Emit a getter function for `setting`.
fn gen_getter(setting: &Setting, fmt: &mut Formatter) {
fmt.doc_comment(setting.comment);
fmt.doc_comment(format!("{}\n{}", setting.description, setting.comment));
match setting.specific {
SpecificSetting::Bool(BoolSetting {
predicate_number, ..
@@ -254,6 +281,7 @@ fn gen_descriptors(group: &SettingGroup, fmt: &mut Formatter) {
fmtln!(fmt, "detail::Descriptor {");
fmt.indent(|fmt| {
fmtln!(fmt, "name: \"{}\",", setting.name);
fmtln!(fmt, "description: \"{}\",", setting.description);
fmtln!(fmt, "offset: {},", setting.byte_offset);
match setting.specific {
SpecificSetting::Bool(BoolSetting { bit_offset, .. }) => {
@@ -286,6 +314,7 @@ fn gen_descriptors(group: &SettingGroup, fmt: &mut Formatter) {
fmtln!(fmt, "detail::Descriptor {");
fmt.indent(|fmt| {
fmtln!(fmt, "name: \"{}\",", preset.name);
fmtln!(fmt, "description: \"{}\",", preset.description);
fmtln!(fmt, "offset: {},", (idx as u8) * group.settings_size);
fmtln!(fmt, "detail: detail::Detail::Preset,");
});
@@ -427,6 +456,7 @@ fn gen_group(group: &SettingGroup, parent: ParentGroup, fmt: &mut Formatter) {
fmtln!(fmt, "}");
gen_constructor(group, parent, fmt);
gen_iterator(group, fmt);
gen_enum_types(group, fmt);
gen_getters(group, fmt);
gen_descriptors(group, fmt);

View File

@@ -9,7 +9,7 @@ use crate::shared::Definitions as SharedDefinitions;
fn define_settings(_shared: &SettingGroup) -> SettingGroup {
let mut setting = SettingGroupBuilder::new("arm64");
let has_lse = setting.add_bool("has_lse", "Large System Extensions", false);
let has_lse = setting.add_bool("has_lse", "Has Large System Extensions support.", "", false);
setting.add_predicate("use_lse", predicate!(has_lse));
setting.build()

View File

@@ -17,33 +17,39 @@ fn define_settings(shared: &SettingGroup) -> SettingGroup {
let supports_m = setting.add_bool(
"supports_m",
"CPU supports the 'M' extension (mul/div)",
"",
false,
);
let supports_a = setting.add_bool(
"supports_a",
"CPU supports the 'A' extension (atomics)",
"",
false,
);
let supports_f = setting.add_bool(
"supports_f",
"CPU supports the 'F' extension (float)",
"",
false,
);
let supports_d = setting.add_bool(
"supports_d",
"CPU supports the 'D' extension (double)",
"",
false,
);
let enable_m = setting.add_bool(
"enable_m",
"Enable the use of 'M' instructions if available",
"",
true,
);
setting.add_bool(
"enable_e",
"Enable the 'RV32E' instruction set with only 16 registers",
"",
false,
);

View File

@@ -4,37 +4,77 @@ pub(crate) fn define(shared: &SettingGroup) -> SettingGroup {
let mut settings = SettingGroupBuilder::new("x86");
// CPUID.01H:ECX
let has_sse3 = settings.add_bool("has_sse3", "SSE3: CPUID.01H:ECX.SSE3[bit 0]", false);
let has_ssse3 = settings.add_bool("has_ssse3", "SSSE3: CPUID.01H:ECX.SSSE3[bit 9]", false);
let has_sse41 = settings.add_bool("has_sse41", "SSE4.1: CPUID.01H:ECX.SSE4_1[bit 19]", false);
let has_sse42 = settings.add_bool("has_sse42", "SSE4.2: CPUID.01H:ECX.SSE4_2[bit 20]", false);
let has_avx = settings.add_bool("has_avx", "AVX: CPUID.01H:ECX.AVX[bit 28]", false);
let has_avx2 = settings.add_bool("has_avx2", "AVX2: CPUID.07H:EBX.AVX2[bit 5]", false);
let has_sse3 = settings.add_bool(
"has_sse3",
"Has support for SSE3.",
"SSE3: CPUID.01H:ECX.SSE3[bit 0]",
false,
);
let has_ssse3 = settings.add_bool(
"has_ssse3",
"Has support for SSSE3.",
"SSSE3: CPUID.01H:ECX.SSSE3[bit 9]",
false,
);
let has_sse41 = settings.add_bool(
"has_sse41",
"Has support for SSE4.1.",
"SSE4.1: CPUID.01H:ECX.SSE4_1[bit 19]",
false,
);
let has_sse42 = settings.add_bool(
"has_sse42",
"Has support for SSE4.2.",
"SSE4.2: CPUID.01H:ECX.SSE4_2[bit 20]",
false,
);
let has_avx = settings.add_bool(
"has_avx",
"Has support for AVX.",
"AVX: CPUID.01H:ECX.AVX[bit 28]",
false,
);
let has_avx2 = settings.add_bool(
"has_avx2",
"Has support for AVX2.",
"AVX2: CPUID.07H:EBX.AVX2[bit 5]",
false,
);
let has_avx512dq = settings.add_bool(
"has_avx512dq",
"Has support for AVX512DQ.",
"AVX512DQ: CPUID.07H:EBX.AVX512DQ[bit 17]",
false,
);
let has_avx512vl = settings.add_bool(
"has_avx512vl",
"Has support for AVX512VL.",
"AVX512VL: CPUID.07H:EBX.AVX512VL[bit 31]",
false,
);
let has_avx512f = settings.add_bool(
"has_avx512f",
"Has support for AVX512F.",
"AVX512F: CPUID.07H:EBX.AVX512F[bit 16]",
false,
);
let has_popcnt = settings.add_bool("has_popcnt", "POPCNT: CPUID.01H:ECX.POPCNT[bit 23]", false);
let has_popcnt = settings.add_bool(
"has_popcnt",
"Has support for POPCNT.",
"POPCNT: CPUID.01H:ECX.POPCNT[bit 23]",
false,
);
// CPUID.(EAX=07H, ECX=0H):EBX
let has_bmi1 = settings.add_bool(
"has_bmi1",
"Has support for BMI1.",
"BMI1: CPUID.(EAX=07H, ECX=0H):EBX.BMI1[bit 3]",
false,
);
let has_bmi2 = settings.add_bool(
"has_bmi2",
"Has support for BMI2.",
"BMI2: CPUID.(EAX=07H, ECX=0H):EBX.BMI2[bit 8]",
false,
);
@@ -42,6 +82,7 @@ pub(crate) fn define(shared: &SettingGroup) -> SettingGroup {
// CPUID.EAX=80000001H:ECX
let has_lzcnt = settings.add_bool(
"has_lzcnt",
"Has support for LZCNT.",
"LZCNT: CPUID.EAX=80000001H:ECX.LZCNT[bit 5]",
false,
);
@@ -85,7 +126,7 @@ pub(crate) fn define(shared: &SettingGroup) -> SettingGroup {
settings.add_predicate("use_lzcnt", predicate!(has_lzcnt));
// Some shared boolean values are used in x86 instruction predicates, so we need to group them
// in the same TargetIsa, for compabitibity with code generated by meta-python.
// in the same TargetIsa, for compatibility with code generated by meta-python.
// TODO Once all the meta generation code has been migrated from Python to Rust, we can put it
// back in the shared SettingGroup, and use it in x86 instruction predicates.
@@ -104,21 +145,40 @@ pub(crate) fn define(shared: &SettingGroup) -> SettingGroup {
// Presets corresponding to x86 CPUs.
settings.add_preset("baseline", preset!());
settings.add_preset(
"baseline",
"A baseline preset with no extensions enabled.",
preset!(),
);
let nehalem = settings.add_preset(
"nehalem",
"Nehalem microarchitecture.",
preset!(has_sse3 && has_ssse3 && has_sse41 && has_sse42 && has_popcnt),
);
let haswell = settings.add_preset(
"haswell",
"Haswell microarchitecture.",
preset!(nehalem && has_bmi1 && has_bmi2 && has_lzcnt),
);
let broadwell = settings.add_preset("broadwell", preset!(haswell));
let skylake = settings.add_preset("skylake", preset!(broadwell));
let cannonlake = settings.add_preset("cannonlake", preset!(skylake));
settings.add_preset("icelake", preset!(cannonlake));
let broadwell = settings.add_preset(
"broadwell",
"Broadwell microarchitecture.",
preset!(haswell),
);
let skylake = settings.add_preset("skylake", "Skylake microarchitecture.", preset!(broadwell));
let cannonlake = settings.add_preset(
"cannonlake",
"Canon Lake microarchitecture.",
preset!(skylake),
);
settings.add_preset(
"icelake",
"Ice Lake microarchitecture.",
preset!(cannonlake),
);
settings.add_preset(
"znver1",
"Zen (first generation) microarchitecture.",
preset!(
has_sse3
&& has_ssse3

View File

@@ -5,29 +5,29 @@ pub(crate) fn define() -> SettingGroup {
settings.add_enum(
"regalloc",
r#"Register allocator to use with the MachInst backend.
"Register allocator to use with the MachInst backend.",
r#"
This selects the register allocator as an option among those offered by the `regalloc.rs`
crate. Please report register allocation bugs to the maintainers of this crate whenever
possible.
This selects the register allocator as an option among those offered by the `regalloc.rs`
crate. Please report register allocation bugs to the maintainers of this crate whenever
possible.
Note: this only applies to target that use the MachInst backend. As of 2020-04-17, this
means the x86_64 backend doesn't use this yet.
Note: this only applies to target that use the MachInst backend. As of 2020-04-17, this
means the x86_64 backend doesn't use this yet.
Possible values:
Possible values:
- `backtracking` is a greedy, backtracking register allocator as implemented in
Spidermonkey's optimizing tier IonMonkey. It may take more time to allocate registers, but
it should generate better code in general, resulting in better throughput of generated
code.
- `backtracking_checked` is the backtracking allocator with additional self checks that may
take some time to run, and thus these checks are disabled by default.
- `experimental_linear_scan` is an experimental linear scan allocator. It may take less
time to allocate registers, but generated code's quality may be inferior. As of
2020-04-17, it is still experimental and it should not be used in production settings.
- `experimental_linear_scan_checked` is the linear scan allocator with additional self
checks that may take some time to run, and thus these checks are disabled by default.
"#,
- `backtracking` is a greedy, backtracking register allocator as implemented in
Spidermonkey's optimizing tier IonMonkey. It may take more time to allocate registers, but
it should generate better code in general, resulting in better throughput of generated
code.
- `backtracking_checked` is the backtracking allocator with additional self checks that may
take some time to run, and thus these checks are disabled by default.
- `experimental_linear_scan` is an experimental linear scan allocator. It may take less
time to allocate registers, but generated code's quality may be inferior. As of
2020-04-17, it is still experimental and it should not be used in production settings.
- `experimental_linear_scan_checked` is the linear scan allocator with additional self
checks that may take some time to run, and thus these checks are disabled by default.
"#,
vec![
"backtracking",
"backtracking_checked",
@@ -38,24 +38,23 @@ pub(crate) fn define() -> SettingGroup {
settings.add_enum(
"opt_level",
"Optimization level for generated code.",
r#"
Optimization level:
Supported levels:
- none: Minimise compile time by disabling most optimizations.
- speed: Generate the fastest possible code
- speed_and_size: like "speed", but also perform transformations
aimed at reducing code size.
- `none`: Minimise compile time by disabling most optimizations.
- `speed`: Generate the fastest possible code
- `speed_and_size`: like "speed", but also perform transformations aimed at reducing code size.
"#,
vec!["none", "speed", "speed_and_size"],
);
settings.add_bool(
"enable_verifier",
"Run the Cranelift IR verifier at strategic times during compilation.",
r#"
Run the Cranelift IR verifier at strategic times during compilation.
This makes compilation slower but catches many bugs. The verifier is always enabled by
default, which is useful during development.
This makes compilation slower but catches many bugs. The verifier is always enabled by
default, which is useful during development.
"#,
true,
);
@@ -65,110 +64,110 @@ pub(crate) fn define() -> SettingGroup {
// `colocated` flag on external functions and global values.
settings.add_bool(
"is_pic",
"Enable Position-Independent Code generation",
"Enable Position-Independent Code generation.",
"",
false,
);
settings.add_bool(
"use_colocated_libcalls",
"Use colocated libcalls.",
r#"
Use colocated libcalls.
Generate code that assumes that libcalls can be declared "colocated",
meaning they will be defined along with the current function, such that
they can use more efficient addressing.
"#,
"#,
false,
);
settings.add_bool(
"avoid_div_traps",
"Generate explicit checks around native division instructions to avoid their trapping.",
r#"
Generate explicit checks around native division instructions to avoid
their trapping.
This is primarily used by SpiderMonkey which doesn't install a signal
handler for SIGFPE, but expects a SIGILL trap for division by zero.
On ISAs like ARM where the native division instructions don't trap,
this setting has no effect - explicit checks are always inserted.
"#,
"#,
false,
);
settings.add_bool(
"enable_float",
"Enable the use of floating-point instructions.",
r#"
Enable the use of floating-point instructions
Disabling use of floating-point instructions is not yet implemented.
"#,
"#,
true,
);
settings.add_bool(
"enable_nan_canonicalization",
"Enable NaN canonicalization.",
r#"
Enable NaN canonicalization
This replaces NaNs with a single canonical value, for users requiring
entirely deterministic WebAssembly computation. This is not required
by the WebAssembly spec, so it is not enabled by default.
"#,
"#,
false,
);
settings.add_bool(
"enable_pinned_reg",
r#"Enable the use of the pinned register.
This register is excluded from register allocation, and is completely under the control of
the end-user. It is possible to read it via the get_pinned_reg instruction, and to set it
with the set_pinned_reg instruction.
"Enable the use of the pinned register.",
r#"
This register is excluded from register allocation, and is completely under the control of
the end-user. It is possible to read it via the get_pinned_reg instruction, and to set it
with the set_pinned_reg instruction.
"#,
false,
);
settings.add_bool(
"use_pinned_reg_as_heap_base",
r#"Use the pinned register as the heap base.
"Use the pinned register as the heap base.",
r#"
Enabling this requires the enable_pinned_reg setting to be set to true. It enables a custom
legalization of the `heap_addr` instruction so it will use the pinned register as the heap
base, instead of fetching it from a global value.
Enabling this requires the enable_pinned_reg setting to be set to true. It enables a custom
legalization of the `heap_addr` instruction so it will use the pinned register as the heap
base, instead of fetching it from a global value.
Warning! Enabling this means that the pinned register *must* be maintained to contain the
heap base address at all times, during the lifetime of a function. Using the pinned
register for other purposes when this is set is very likely to cause crashes.
Warning! Enabling this means that the pinned register *must* be maintained to contain the
heap base address at all times, during the lifetime of a function. Using the pinned
register for other purposes when this is set is very likely to cause crashes.
"#,
false,
);
settings.add_bool("enable_simd", "Enable the use of SIMD instructions.", false);
settings.add_bool(
"enable_simd",
"Enable the use of SIMD instructions.",
"",
false,
);
settings.add_bool(
"enable_atomics",
"Enable the use of atomic instructions",
"",
true,
);
settings.add_bool(
"enable_safepoints",
"Enable safepoint instruction insertions.",
r#"
Enable safepoint instruction insertions.
This will allow the emit_stack_maps() function to insert the safepoint
instruction on top of calls and interrupt traps in order to display the
live reference values at that point in the program.
"#,
"#,
false,
);
settings.add_enum(
"tls_model",
r#"
Defines the model used to perform TLS accesses.
"#,
"Defines the model used to perform TLS accesses.",
"",
vec!["none", "elf_gd", "macho", "coff"],
);
@@ -176,9 +175,9 @@ pub(crate) fn define() -> SettingGroup {
settings.add_enum(
"libcall_call_conv",
"Defines the calling convention to use for LibCalls call expansion.",
r#"
Defines the calling convention to use for LibCalls call expansion,
since it may be different from the ISA default calling convention.
This may be different from the ISA default calling convention.
The default value is to use the same calling convention as the ISA
default calling convention.
@@ -202,9 +201,8 @@ pub(crate) fn define() -> SettingGroup {
settings.add_num(
"baldrdash_prologue_words",
"Number of pointer-sized words pushed by the baldrdash prologue.",
r#"
Number of pointer-sized words pushed by the baldrdash prologue.
Functions with the `baldrdash` calling convention don't generate their
own prologue and epilogue. They depend on externally generated code
that pushes a fixed number of words in the prologue and restores them
@@ -213,15 +211,14 @@ pub(crate) fn define() -> SettingGroup {
This setting configures the number of pointer-sized words pushed on the
stack when the Cranelift-generated code is entered. This includes the
pushed return address on x86.
"#,
"#,
0,
);
settings.add_bool(
"enable_llvm_abi_extensions",
"Enable various ABI extensions defined by LLVM's behavior.",
r#"
Enable various ABI extensions defined by LLVM's behavior.
In some cases, LLVM's implementation of an ABI (calling convention)
goes beyond a standard and supports additional argument types or
behavior. This option instructs Cranelift codegen to follow LLVM's
@@ -232,18 +229,18 @@ pub(crate) fn define() -> SettingGroup {
registers. The Fastcall implementation otherwise does not support
`i128` arguments, and will panic if they are present and this
option is not set.
"#,
"#,
false,
);
settings.add_bool(
"unwind_info",
"Generate unwind information.",
r#"
Generate unwind info. This increases metadata size and compile time,
but allows for the debugger to trace frames, is needed for GC tracing
that relies on libunwind (such as in Wasmtime), and is
unconditionally needed on certain platforms (such as Windows) that
must always be able to unwind.
This increases metadata size and compile time, but allows for the
debugger to trace frames, is needed for GC tracing that relies on
libunwind (such as in Wasmtime), and is unconditionally needed on
certain platforms (such as Windows) that must always be able to unwind.
"#,
true,
);
@@ -253,6 +250,7 @@ pub(crate) fn define() -> SettingGroup {
settings.add_bool(
"emit_all_ones_funcaddrs",
"Emit not-yet-relocated function addresses as all-ones bit patterns.",
"",
false,
);
@@ -260,32 +258,27 @@ pub(crate) fn define() -> SettingGroup {
settings.add_bool(
"enable_probestack",
r#"
Enable the use of stack probes, for calling conventions which support this
functionality.
"#,
"Enable the use of stack probes for supported calling conventions.",
"",
true,
);
settings.add_bool(
"probestack_func_adjusts_sp",
r#"
Set this to true of the stack probe function modifies the stack pointer
itself.
"#,
"Enable if the stack probe adjusts the stack pointer.",
"",
false,
);
settings.add_num(
"probestack_size_log2",
"The log2 of the size of the stack guard region.",
r#"
The log2 of the size of the stack guard region.
Stack frames larger than this size will have stack overflow checked
by calling the probestack function.
The default is 12, which translates to a size of 4096.
"#,
"#,
12,
);
@@ -294,6 +287,7 @@ pub(crate) fn define() -> SettingGroup {
settings.add_bool(
"enable_jump_tables",
"Enable the use of jump tables in generated machine code.",
"",
true,
);
@@ -301,16 +295,15 @@ pub(crate) fn define() -> SettingGroup {
settings.add_bool(
"enable_heap_access_spectre_mitigation",
"Enable Spectre mitigation on heap bounds checks.",
r#"
Enable Spectre mitigation on heap bounds checks.
This is a no-op for any heap that needs no bounds checks; e.g.,
if the limit is static and the guard region is large enough that
the index cannot reach past it.
This is a no-op for any heap that needs no bounds checks; e.g.,
if the limit is static and the guard region is large enough that
the index cannot reach past it.
This option is enabled by default because it is highly
recommended for secure sandboxing. The embedder should consider
the security implications carefully before disabling this option.
This option is enabled by default because it is highly
recommended for secure sandboxing. The embedder should consider
the security implications carefully before disabling this option.
"#,
true,
);

View File

@@ -7,10 +7,8 @@ use crate::isa::Builder as IsaBuilder;
use crate::machinst::{compile, MachBackend, MachCompileResult, TargetIsaAdapter, VCode};
use crate::result::CodegenResult;
use crate::settings as shared_settings;
use alloc::boxed::Box;
use alloc::{boxed::Box, vec::Vec};
use core::hash::{Hash, Hasher};
use regalloc::{PrettyPrint, RealRegUniverse};
use target_lexicon::{Aarch64Architecture, Architecture, Triple};
@@ -104,6 +102,10 @@ impl MachBackend for AArch64Backend {
&self.flags
}
fn isa_flags(&self) -> Vec<shared_settings::Value> {
self.isa_flags.iter().collect()
}
fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) {
self.flags.hash(&mut hasher);
self.isa_flags.hash(&mut hasher);

View File

@@ -1,6 +1,6 @@
//! AArch64 Settings.
use crate::settings::{self, detail, Builder};
use crate::settings::{self, detail, Builder, Value};
use core::fmt;
// Include code generated by `cranelift-codegen/meta/src/gen_settings.rs:`. This file contains a

View File

@@ -7,7 +7,7 @@ use crate::machinst::{compile, MachBackend, MachCompileResult, TargetIsaAdapter,
use crate::result::CodegenResult;
use crate::settings;
use alloc::boxed::Box;
use alloc::{boxed::Box, vec::Vec};
use core::hash::{Hash, Hasher};
use regalloc::{PrettyPrint, RealRegUniverse};
use target_lexicon::{Architecture, ArmArchitecture, Triple};
@@ -92,6 +92,10 @@ impl MachBackend for Arm32Backend {
&self.flags
}
fn isa_flags(&self) -> Vec<settings::Value> {
Vec::new()
}
fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) {
self.flags.hash(&mut hasher);
}

View File

@@ -63,8 +63,7 @@ use crate::result::CodegenResult;
use crate::settings;
use crate::settings::SetResult;
use crate::timing;
use alloc::borrow::Cow;
use alloc::boxed::Box;
use alloc::{borrow::Cow, boxed::Box, vec::Vec};
use core::any::Any;
use core::fmt;
use core::fmt::{Debug, Formatter};
@@ -201,6 +200,16 @@ pub struct Builder {
}
impl Builder {
/// Gets the triple for the builder.
pub fn triple(&self) -> &Triple {
&self.triple
}
/// Iterates the available settings in the builder.
pub fn iter(&self) -> impl Iterator<Item = settings::Setting> {
self.setup.iter()
}
/// Combine the ISA-specific settings with the provided ISA-independent settings and allocate a
/// fully configured `TargetIsa` trait object.
pub fn finish(self, shared_flags: settings::Flags) -> Box<dyn TargetIsa> {
@@ -265,8 +274,10 @@ pub trait TargetIsa: fmt::Display + Send + Sync {
/// Get the ISA-independent flags that were used to make this trait object.
fn flags(&self) -> &settings::Flags;
/// Hashes all flags, both ISA-independent and ISA-specific, into the
/// specified hasher.
/// Get the ISA-dependent flag values that were used to make this trait object.
fn isa_flags(&self) -> Vec<settings::Value>;
/// Hashes all flags, both ISA-independent and ISA-dependent, into the specified hasher.
fn hash_all_flags(&self, hasher: &mut dyn Hasher);
/// Get the default calling convention of this target.

View File

@@ -15,8 +15,7 @@ use crate::isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encoding
use crate::isa::Builder as IsaBuilder;
use crate::isa::{EncInfo, RegClass, RegInfo, TargetIsa};
use crate::regalloc;
use alloc::borrow::Cow;
use alloc::boxed::Box;
use alloc::{borrow::Cow, boxed::Box, vec::Vec};
use core::any::Any;
use core::fmt;
use core::hash::{Hash, Hasher};
@@ -70,6 +69,10 @@ impl TargetIsa for Isa {
&self.shared_flags
}
fn isa_flags(&self) -> Vec<shared_settings::Value> {
self.isa_flags.iter().collect()
}
fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) {
self.shared_flags.hash(&mut hasher);
self.isa_flags.hash(&mut hasher);

View File

@@ -1,6 +1,6 @@
//! RISC-V Settings.
use crate::settings::{self, detail, Builder};
use crate::settings::{self, detail, Builder, Value};
use core::fmt;
// Include code generated by `cranelift-codegen/meta/src/gen_settings.rs`. This file contains a

View File

@@ -9,7 +9,7 @@ use crate::isa::Builder as IsaBuilder;
use crate::machinst::{compile, MachBackend, MachCompileResult, TargetIsaAdapter, VCode};
use crate::result::CodegenResult;
use crate::settings::{self as shared_settings, Flags};
use alloc::boxed::Box;
use alloc::{boxed::Box, vec::Vec};
use core::hash::{Hash, Hasher};
use regalloc::{PrettyPrint, RealRegUniverse, Reg};
use target_lexicon::Triple;
@@ -85,6 +85,10 @@ impl MachBackend for X64Backend {
&self.flags
}
fn isa_flags(&self) -> Vec<shared_settings::Value> {
self.x64_flags.iter().collect()
}
fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) {
self.flags.hash(&mut hasher);
self.x64_flags.hash(&mut hasher);

View File

@@ -1,6 +1,6 @@
//! x86 Settings.
use crate::settings::{self, detail, Builder};
use crate::settings::{self, detail, Builder, Value};
use core::fmt;
// Include code generated by `cranelift-codegen/meta/src/gen_settings.rs:`. This file contains a

View File

@@ -21,8 +21,7 @@ use crate::isa::{EncInfo, RegClass, RegInfo, TargetIsa};
use crate::regalloc;
use crate::result::CodegenResult;
use crate::timing;
use alloc::borrow::Cow;
use alloc::boxed::Box;
use alloc::{borrow::Cow, boxed::Box, vec::Vec};
use core::any::Any;
use core::fmt;
use core::hash::{Hash, Hasher};
@@ -79,6 +78,10 @@ impl TargetIsa for Isa {
&self.shared_flags
}
fn isa_flags(&self) -> Vec<shared_settings::Value> {
self.isa_flags.iter().collect()
}
fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) {
self.shared_flags.hash(&mut hasher);
self.isa_flags.hash(&mut hasher);

View File

@@ -1,6 +1,6 @@
//! x86 Settings.
use crate::settings::{self, detail, Builder};
use crate::settings::{self, detail, Builder, Value};
use core::fmt;
// Include code generated by `cranelift-codegen/meta/src/gen_settings.rs:`. This file contains a

View File

@@ -5,7 +5,7 @@ use crate::ir;
use crate::isa::{EncInfo, Encoding, Encodings, Legalize, RegClass, RegInfo, TargetIsa};
use crate::machinst::*;
use crate::regalloc::RegisterSet;
use crate::settings::Flags;
use crate::settings::{self, Flags};
#[cfg(feature = "testing_hooks")]
use crate::regalloc::RegDiversions;
@@ -14,7 +14,6 @@ use crate::regalloc::RegDiversions;
use crate::isa::unwind::systemv::RegisterMappingError;
use core::any::Any;
use core::hash::Hasher;
use std::borrow::Cow;
use std::fmt;
use target_lexicon::Triple;
@@ -59,8 +58,12 @@ impl TargetIsa for TargetIsaAdapter {
self.backend.flags()
}
fn isa_flags(&self) -> Vec<settings::Value> {
self.backend.isa_flags()
}
fn hash_all_flags(&self, hasher: &mut dyn Hasher) {
self.backend.hash_all_flags(hasher)
self.backend.hash_all_flags(hasher);
}
fn register_info(&self) -> RegInfo {

View File

@@ -64,18 +64,18 @@ use crate::binemit::{CodeInfo, CodeOffset, StackMap};
use crate::ir::condcodes::IntCC;
use crate::ir::{Function, SourceLoc, StackSlot, Type, ValueLabel};
use crate::result::CodegenResult;
use crate::settings::Flags;
use crate::settings::{self, Flags};
use crate::value_label::ValueLabelsRanges;
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::fmt::Debug;
use core::hash::Hasher;
use cranelift_entity::PrimaryMap;
use regalloc::RegUsageCollector;
use regalloc::{
RealReg, RealRegUniverse, Reg, RegClass, RegUsageMapper, SpillSlot, VirtualReg, Writable,
};
use smallvec::{smallvec, SmallVec};
use std::hash::Hasher;
use std::string::String;
use target_lexicon::Triple;
@@ -368,8 +368,10 @@ pub trait MachBackend {
/// Return flags for this backend.
fn flags(&self) -> &Flags;
/// Hashes all flags, both ISA-independent and ISA-specific, into the
/// specified hasher.
/// Get the ISA-dependent flag values that were used to make this trait object.
fn isa_flags(&self) -> Vec<settings::Value>;
/// Hashes all flags, both ISA-independent and ISA-dependent, into the specified hasher.
fn hash_all_flags(&self, hasher: &mut dyn Hasher);
/// Return triple for this backend.

View File

@@ -44,6 +44,78 @@ pub trait Configurable {
fn enable(&mut self, name: &str) -> SetResult<()>;
}
/// Represents the kind of setting.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SettingKind {
/// The setting is an enumeration.
Enum,
/// The setting is a number.
Num,
/// The setting is a boolean.
Bool,
/// The setting is a preset.
Preset,
}
/// Represents an available builder setting.
///
/// This is used for iterating settings in a builder.
#[derive(Clone, Copy, Debug)]
pub struct Setting {
/// The name of the setting.
pub name: &'static str,
/// The description of the setting.
pub description: &'static str,
/// The kind of the setting.
pub kind: SettingKind,
/// The supported values of the setting (for enum values).
pub values: Option<&'static [&'static str]>,
}
/// Represents a setting value.
///
/// This is used for iterating values in `Flags`.
pub struct Value {
/// The name of the setting associated with this value.
pub name: &'static str,
pub(crate) detail: detail::Detail,
pub(crate) values: Option<&'static [&'static str]>,
pub(crate) value: u8,
}
impl Value {
/// Gets the kind of setting.
pub fn kind(&self) -> SettingKind {
match &self.detail {
detail::Detail::Enum { .. } => SettingKind::Enum,
detail::Detail::Num => SettingKind::Num,
detail::Detail::Bool { .. } => SettingKind::Bool,
detail::Detail::Preset => unreachable!(),
}
}
/// Gets the enum value if the value is from an enum setting.
pub fn as_enum(&self) -> Option<&'static str> {
self.values.map(|v| v[self.value as usize])
}
/// Gets the numerical value if the value is from a num setting.
pub fn as_num(&self) -> Option<u8> {
match &self.detail {
detail::Detail::Num => Some(self.value),
_ => None,
}
}
/// Gets the boolean value if the value is from a boolean setting.
pub fn as_bool(&self) -> Option<bool> {
match &self.detail {
detail::Detail::Bool { bit } => Some(self.value & (1 << bit) != 0),
_ => None,
}
}
}
/// Collect settings values based on a template.
#[derive(Clone, Hash)]
pub struct Builder {
@@ -66,6 +138,30 @@ impl Builder {
self.bytes
}
/// Iterates the available settings in the builder.
pub fn iter(&self) -> impl Iterator<Item = Setting> {
let template = self.template;
template.descriptors.iter().map(move |d| {
let (kind, values) = match d.detail {
detail::Detail::Enum { last, enumerators } => {
let values = template.enums(last, enumerators);
(SettingKind::Enum, Some(values))
}
detail::Detail::Num => (SettingKind::Num, None),
detail::Detail::Bool { .. } => (SettingKind::Bool, None),
detail::Detail::Preset => (SettingKind::Preset, None),
};
Setting {
name: d.name,
description: d.description,
kind,
values,
}
})
}
/// Set the value of a single bit.
fn set_bit(&mut self, offset: usize, bit: u8, value: bool) {
let byte = &mut self.bytes[offset];
@@ -288,6 +384,9 @@ pub mod detail {
/// Lower snake-case name of setting as defined in meta.
pub name: &'static str,
/// The description of the setting.
pub description: &'static str,
/// Offset of byte containing this setting.
pub offset: u32,

View File

@@ -24,7 +24,7 @@ smallvec = "1.6.1"
thiserror = "1.0.4"
[dev-dependencies]
wat = "1.0.36"
wat = "1.0.37"
target-lexicon = "0.12"
# Enable the riscv feature for cranelift-codegen, as some tests require it
cranelift-codegen = { path = "../codegen", version = "0.72.0", default-features = false, features = ["riscv"] }

View File

@@ -185,13 +185,10 @@ pub extern "C" fn wasmtime_module_deserialize(
binary: &wasm_byte_vec_t,
ret: &mut *mut wasm_module_t,
) -> Option<Box<wasmtime_error_t>> {
handle_result(
Module::deserialize(&engine.engine, binary.as_slice()),
|module| {
let module = Box::new(wasm_module_t::new(module));
*ret = Box::into_raw(module);
},
)
handle_result(Module::new(&engine.engine, binary.as_slice()), |module| {
let module = Box::new(wasm_module_t::new(module));
*ret = Box::into_raw(module);
})
}
#[no_mangle]

View File

@@ -10,7 +10,9 @@ pub mod ir {
}
pub mod settings {
pub use cranelift_codegen::settings::{builder, Builder, Configurable, Flags, SetError};
pub use cranelift_codegen::settings::{
builder, Builder, Configurable, Flags, OptLevel, SetError, Setting, SettingKind, Value,
};
}
pub mod isa {

View File

@@ -1,5 +1,7 @@
use serde::{Deserialize, Serialize};
/// Tunable parameters for WebAssembly compilation.
#[derive(Clone, Hash)]
#[derive(Clone, Hash, Serialize, Deserialize)]
pub struct Tunables {
/// For static heaps, the size in wasm pages of the heap protected by bounds checking.
pub static_memory_bound: u32,

View File

@@ -22,7 +22,7 @@ wasm-smith = "0.4.4"
wasmi = "0.7.0"
[dev-dependencies]
wat = "1.0.36"
wat = "1.0.37"
[features]
experimental_x64 = ["wasmtime/experimental_x64"]

View File

@@ -46,6 +46,7 @@ lightbeam = ["wasmtime-lightbeam"]
jitdump = ["wasmtime-profiling/jitdump"]
vtune = ["wasmtime-profiling/vtune"]
parallel-compilation = ["rayon"]
all-arch = ["cranelift-codegen/all-arch"]
# Try the experimental, work-in-progress new x86_64 backend. This is not stable
# as of June 2020.

View File

@@ -312,7 +312,7 @@ impl CodeMemory {
}
}
// Register all unwind entiries for functions and trampolines.
// Register all unwind entries for functions and trampolines.
// TODO will `u32` type for start/len be enough for large code base.
for i in unwind_info {
match i {

View File

@@ -5,6 +5,7 @@ use crate::object::{build_object, ObjectUnwindInfo};
use object::write::Object;
#[cfg(feature = "parallel-compilation")]
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use std::hash::{Hash, Hasher};
use std::mem;
use wasmparser::WasmFeatures;
@@ -18,7 +19,7 @@ use wasmtime_environ::{
};
/// Select which kind of compilation to use.
#[derive(Copy, Clone, Debug, Hash)]
#[derive(Copy, Clone, Debug, Hash, Serialize, Deserialize, Eq, PartialEq)]
pub enum CompilationStrategy {
/// Let Wasmtime pick the strategy.
Auto,
@@ -108,6 +109,11 @@ impl Compiler {
self.isa.as_ref()
}
/// Return the compiler's strategy.
pub fn strategy(&self) -> CompilationStrategy {
self.strategy
}
/// Return the target's frontend configuration settings.
pub fn frontend_config(&self) -> TargetFrontendConfig {
self.isa.frontend_config()

View File

@@ -438,7 +438,7 @@ fn build_code_memory(
isa: &dyn TargetIsa,
obj: &[u8],
module: &Module,
unwind_info: &Box<[ObjectUnwindInfo]>,
unwind_info: &[ObjectUnwindInfo],
) -> Result<
(
CodeMemory,

View File

@@ -28,7 +28,7 @@ wasmparser = "0.77"
[dev-dependencies]
lazy_static = "1.2"
wat = "1.0.36"
wat = "1.0.37"
quickcheck = "1.0.0"
anyhow = "1.0"

View File

@@ -20,7 +20,7 @@ pretty_env_logger = "0.4.0"
tempfile = "3.1.0"
os_pipe = "0.9"
anyhow = "1.0.19"
wat = "1.0.36"
wat = "1.0.37"
cap-std = "0.13"
[features]

View File

@@ -79,3 +79,6 @@ async = ["wasmtime-fiber", "wasmtime-runtime/async"]
# Enables userfaultfd support in the runtime's pooling allocator when building on Linux
uffd = ["wasmtime-runtime/uffd"]
# Enables support for all architectures in JIT and the `wasmtime compile` CLI command.
all-arch = ["wasmtime-jit/all-arch"]

View File

@@ -2,6 +2,7 @@ use crate::memory::MemoryCreator;
use crate::trampoline::MemoryCreatorProxy;
use crate::{func::HostFunc, Caller, FuncType, IntoFunc, Trap, Val, WasmRet, WasmTy};
use anyhow::{bail, Result};
use serde::{Deserialize, Serialize};
use std::cmp;
use std::collections::HashMap;
use std::convert::TryFrom;
@@ -399,16 +400,6 @@ impl Config {
.enable("avoid_div_traps")
.expect("should be valid flag");
// Invert cranelift's default-on verification to instead default off.
flags
.set("enable_verifier", "false")
.expect("should be valid flag");
// Turn on cranelift speed optimizations by default
flags
.set("opt_level", "speed")
.expect("should be valid flag");
// We don't use probestack as a stack limit mechanism
flags
.set("enable_probestack", "false")
@@ -426,12 +417,7 @@ impl Config {
allocation_strategy: InstanceAllocationStrategy::OnDemand,
max_wasm_stack: 1 << 20,
wasm_backtrace_details_env_used: false,
features: WasmFeatures {
reference_types: true,
bulk_memory: true,
multi_value: true,
..WasmFeatures::default()
},
features: WasmFeatures::default(),
max_instances: 10_000,
max_tables: 10_000,
max_memories: 10_000,
@@ -440,10 +426,36 @@ impl Config {
host_funcs: HostFuncMap::new(),
async_support: false,
};
ret.cranelift_debug_verifier(false);
ret.cranelift_opt_level(OptLevel::Speed);
ret.wasm_reference_types(true);
ret.wasm_multi_value(true);
ret.wasm_bulk_memory(true);
ret.wasm_backtrace_details(WasmBacktraceDetails::Environment);
ret
}
/// Sets the target triple for the [`Config`].
///
/// By default, the host target triple is used for the [`Config`].
///
/// This method can be used to change the target triple.
///
/// Cranelift flags will not be inferred for the given target and any
/// existing target-specific Cranelift flags will be cleared.
///
/// # Errors
///
/// This method will error if the given target triple is not supported.
pub fn target(&mut self, target: &str) -> Result<&mut Self> {
use std::str::FromStr;
self.isa_flags = native::lookup(
target_lexicon::Triple::from_str(target).map_err(|e| anyhow::anyhow!(e))?,
)?;
Ok(self)
}
/// Whether or not to enable support for asynchronous functions in Wasmtime.
///
/// When enabled, the config can optionally define host functions with `async`.
@@ -884,18 +896,31 @@ impl Config {
self
}
/// Clears native CPU flags inferred from the host.
/// Allows setting a Cranelift boolean flag or preset. This allows
/// fine-tuning of Cranelift settings.
///
/// By default Wasmtime will tune generated code for the host that Wasmtime
/// itself is running on. If you're compiling on one host, however, and
/// shipping artifacts to another host then this behavior may not be
/// desired. This function will clear all inferred native CPU features.
/// Since Cranelift flags may be unstable, this method should not be considered to be stable
/// either; other `Config` functions should be preferred for stability.
///
/// To enable CPU features afterwards it's recommended to use the
/// [`Config::cranelift_other_flag`] method.
pub fn cranelift_clear_cpu_flags(&mut self) -> &mut Self {
self.isa_flags = native::builder_without_flags();
self
/// # Safety
///
/// This is marked as unsafe, because setting the wrong flag might break invariants,
/// resulting in execution hazards.
///
/// # Errors
///
/// This method can fail if the flag's name does not exist.
pub unsafe fn cranelift_flag_enable(&mut self, flag: &str) -> Result<&mut Self> {
if let Err(err) = self.flags.enable(flag) {
match err {
SetError::BadName(_) => {
// Try the target-specific flags.
self.isa_flags.enable(flag)?;
}
_ => bail!(err),
}
}
Ok(self)
}
/// Allows settings another Cranelift flag defined by a flag name and value. This allows
@@ -911,7 +936,7 @@ impl Config {
///
/// This method can fail if the flag's name does not exist, or the value is not appropriate for
/// the flag type.
pub unsafe fn cranelift_other_flag(&mut self, name: &str, value: &str) -> Result<&mut Self> {
pub unsafe fn cranelift_flag_set(&mut self, name: &str, value: &str) -> Result<&mut Self> {
if let Err(err) = self.flags.set(name, value) {
match err {
SetError::BadName(_) => {
@@ -1419,7 +1444,7 @@ pub enum Strategy {
/// Possible optimization levels for the Cranelift codegen backend.
#[non_exhaustive]
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub enum OptLevel {
/// No optimizations performed, minimizes compilation time by disabling most
/// optimizations.

View File

@@ -78,6 +78,43 @@ impl Engine {
pub fn same(a: &Engine, b: &Engine) -> bool {
Arc::ptr_eq(&a.inner, &b.inner)
}
/// Ahead-of-time (AOT) compiles a WebAssembly module.
///
/// The `bytes` provided must be in one of two formats:
///
/// * A [binary-encoded][binary] WebAssembly module. This is always supported.
/// * A [text-encoded][text] instance of the WebAssembly text format.
/// This is only supported when the `wat` feature of this crate is enabled.
/// If this is supplied then the text format will be parsed before validation.
/// Note that the `wat` feature is enabled by default.
///
/// This method may be used to compile a module for use with a different target
/// host. The output of this method may be used with [`Module::new`](crate::Module::new)
/// on hosts compatible with the [`Config`] associated with this [`Engine`].
///
/// The output of this method is safe to send to another host machine for later
/// execution. As the output is already a compiled module, translation and code
/// generation will be skipped and this will improve the performance of constructing
/// a [`Module`](crate::Module) from the output of this method.
///
/// [binary]: https://webassembly.github.io/spec/core/binary/index.html
/// [text]: https://webassembly.github.io/spec/core/text/index.html
pub fn precompile_module(&self, bytes: &[u8]) -> Result<Vec<u8>> {
const USE_PAGED_MEM_INIT: bool = cfg!(all(feature = "uffd", target_os = "linux"));
#[cfg(feature = "wat")]
let bytes = wat::parse_bytes(&bytes)?;
let (_, artifacts, types) = wasmtime_jit::CompilationArtifacts::build(
&self.inner.compiler,
&bytes,
USE_PAGED_MEM_INIT,
)?;
crate::module::SerializedModule::from_artifacts(&self.inner.compiler, &artifacts, &types)
.to_bytes()
}
}
impl Default for Engine {

View File

@@ -175,6 +175,10 @@
//! lock contention is hampering multithreading throughput. This feature is only
//! supported on Linux and requires a Linux kernel version 4.11 or higher.
//!
//! * `all-arch` - Not enabled by default. This feature compiles in support for
//! all architectures for both the JIT compiler and the `wasmtime compile` CLI
//! command.
//!
//! ## Examples
//!
//! In addition to the examples below be sure to check out the [online embedding

View File

@@ -1,10 +1,7 @@
use crate::types::{ExportType, ExternType, ImportType};
use crate::{Engine, ModuleType};
use anyhow::{bail, Context, Result};
use bincode::Options;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::hash::Hash;
use std::fs;
use std::path::Path;
use std::sync::Arc;
use wasmparser::Validator;
@@ -14,6 +11,10 @@ use wasmtime_environ::entity::PrimaryMap;
use wasmtime_environ::wasm::ModuleIndex;
use wasmtime_jit::{CompilationArtifacts, CompiledModule, TypeTables};
mod serialization;
pub use serialization::SerializedModule;
/// A compiled WebAssembly module, ready to be instantiated.
///
/// A `Module` is a compiled in-memory representation of an input WebAssembly
@@ -30,7 +31,7 @@ use wasmtime_jit::{CompilationArtifacts, CompiledModule, TypeTables};
/// compiling the original wasm module only once with a single [`Module`]
/// instance.
///
/// The `Module` is threadsafe and safe to share accross threads.
/// The `Module` is thread-safe and safe to share across threads.
///
/// ## Modules and `Clone`
///
@@ -103,75 +104,28 @@ struct ModuleInner {
types: Arc<TypeTables>,
}
/// A small helper struct which defines modules are serialized.
#[derive(serde::Serialize, serde::Deserialize)]
struct ModuleSerialized<'a> {
/// All compiled artifacts neeeded by this module, where the last entry in
/// this list is the artifacts for the module itself.
artifacts: Vec<MyCow<'a, CompilationArtifacts>>,
/// Closed-over module values that are also needed for this module.
modules: Vec<ModuleSerialized<'a>>,
/// The index into the list of type tables that are used for this module's
/// type tables.
type_tables: usize,
}
// This is like `std::borrow::Cow` but it doesn't have a `Clone` bound on `T`
enum MyCow<'a, T> {
Borrowed(&'a T),
Owned(T),
}
impl<'a, T> MyCow<'a, T> {
fn unwrap_owned(self) -> T {
match self {
MyCow::Owned(val) => val,
MyCow::Borrowed(_) => unreachable!(),
}
}
}
impl<'a, T: Serialize> Serialize for MyCow<'a, T> {
fn serialize<S>(&self, dst: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
match self {
MyCow::Borrowed(val) => val.serialize(dst),
MyCow::Owned(val) => val.serialize(dst),
}
}
}
impl<'a, 'b, T: Deserialize<'a>> Deserialize<'a> for MyCow<'b, T> {
fn deserialize<D>(src: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'a>,
{
Ok(MyCow::Owned(T::deserialize(src)?))
}
}
impl Module {
/// Creates a new WebAssembly `Module` from the given in-memory `bytes`.
///
/// The `bytes` provided must be in one of two formats:
/// The `bytes` provided must be in one of the following formats:
///
/// * It can be a [binary-encoded][binary] WebAssembly module. This
/// is always supported.
/// * It may also be a [text-encoded][text] instance of the WebAssembly
/// text format. This is only supported when the `wat` feature of this
/// crate is enabled. If this is supplied then the text format will be
/// parsed before validation. Note that the `wat` feature is enabled by
/// default.
/// * A [binary-encoded][binary] WebAssembly module. This is always supported.
/// * A [text-encoded][text] instance of the WebAssembly text format.
/// This is only supported when the `wat` feature of this crate is enabled.
/// If this is supplied then the text format will be parsed before validation.
/// Note that the `wat` feature is enabled by default.
/// * A module serialized with [`Module::serialize`].
/// * A module compiled with [`Engine::precompile_module`] or the
/// `wasmtime compile` command.
///
/// The data for the wasm module must be loaded in-memory if it's present
/// elsewhere, for example on disk. This requires that the entire binary is
/// loaded into memory all at once, this API does not support streaming
/// compilation of a module.
///
/// The WebAssembly binary will be decoded and validated. It will also be
/// compiled according to the configuration of the provided `engine`.
/// If the module has not been already been compiled, the WebAssembly binary will
/// be decoded and validated. It will also be compiled according to the
/// configuration of the provided `engine`.
///
/// # Errors
///
@@ -184,7 +138,7 @@ impl Module {
/// * Implementation-specific limits were exceeded with a valid binary (for
/// example too many locals)
/// * The wasm binary may use features that are not enabled in the
/// configuration of `enging`
/// configuration of `engine`
/// * If the `wat` feature is enabled and the input is text, then it may be
/// rejected if it fails to parse.
///
@@ -220,9 +174,15 @@ impl Module {
/// # }
/// ```
pub fn new(engine: &Engine, bytes: impl AsRef<[u8]>) -> Result<Module> {
let bytes = bytes.as_ref();
if let Some(module) = SerializedModule::from_bytes(bytes)? {
return module.into_module(engine);
}
#[cfg(feature = "wat")]
let bytes = wat::parse_bytes(bytes.as_ref())?;
Module::from_binary(engine, bytes.as_ref())
let bytes = wat::parse_bytes(bytes)?;
Self::from_binary(engine, &bytes)
}
/// Creates a new WebAssembly `Module` from the given in-memory `binary`
@@ -230,7 +190,7 @@ impl Module {
///
/// See [`Module::new`] for other details.
pub fn new_with_name(engine: &Engine, bytes: impl AsRef<[u8]>, name: &str) -> Result<Module> {
let mut module = Module::new(engine, bytes.as_ref())?;
let mut module = Self::new(engine, bytes.as_ref())?;
Arc::get_mut(&mut Arc::get_mut(&mut module.inner).unwrap().module)
.unwrap()
.module_mut()
@@ -268,21 +228,33 @@ impl Module {
/// # }
/// ```
pub fn from_file(engine: &Engine, file: impl AsRef<Path>) -> Result<Module> {
#[cfg(feature = "wat")]
let wasm = wat::parse_file(file)?;
#[cfg(not(feature = "wat"))]
let wasm = std::fs::read(file)?;
Module::new(engine, &wasm)
match Self::new(
engine,
&fs::read(&file).with_context(|| "failed to read input file")?,
) {
Ok(m) => Ok(m),
Err(e) => {
cfg_if::cfg_if! {
if #[cfg(feature = "wat")] {
let mut e = e.downcast::<wat::Error>()?;
e.set_path(file);
bail!(e)
} else {
Err(e)
}
}
}
}
}
/// Creates a new WebAssembly `Module` from the given in-memory `binary`
/// data.
///
/// This is similar to [`Module::new`] except that it requires that the
/// `binary` input is a WebAssembly binary, the text format is not supported
/// by this function. It's generally recommended to use [`Module::new`],
/// but if it's required to not support the text format this function can be
/// used instead.
/// `binary` input is a WebAssembly binary or a compiled module, the
/// text format is not supported by this function. It's generally
/// recommended to use [`Module::new`], but if it's required to not
/// support the text format this function can be used instead.
///
/// # Examples
///
@@ -307,6 +279,23 @@ impl Module {
/// # }
/// ```
pub fn from_binary(engine: &Engine, binary: &[u8]) -> Result<Module> {
if let Some(module) = SerializedModule::from_bytes(binary)? {
return module.into_module(engine);
}
// Check to see that the config's target matches the host
let target = engine.config().isa_flags.triple();
if *target != target_lexicon::Triple::host() {
bail!(
"target '{}' specified in the configuration does not match the host",
target
);
}
// FIXME: we may want to validate that the ISA flags in the config match those that
// would be inferred for the host, otherwise the JIT might produce unrunnable code
// for the features the host's CPU actually has.
const USE_PAGED_MEM_INIT: bool = cfg!(all(feature = "uffd", target_os = "linux"));
cfg_if::cfg_if! {
@@ -388,98 +377,12 @@ impl Module {
sig
}
/// Serialize compilation artifacts to the buffer. See also `deseriaize`.
pub fn serialize(&self) -> Result<Vec<u8>> {
let mut pushed = HashMap::new();
let mut tables = Vec::new();
let module = self.serialized_module(&mut pushed, &mut tables);
let artifacts = (compiler_fingerprint(self.engine()), tables, module);
let buffer = bincode_options().serialize(&artifacts)?;
Ok(buffer)
}
fn serialized_module<'a>(
&'a self,
type_tables_pushed: &mut HashMap<usize, usize>,
type_tables: &mut Vec<&'a TypeTables>,
) -> ModuleSerialized<'a> {
// Deduplicate `Arc<TypeTables>` using our two parameters to ensure we
// serialize type tables as little as possible.
let ptr = Arc::as_ptr(self.types());
let type_tables_idx = *type_tables_pushed.entry(ptr as usize).or_insert_with(|| {
type_tables.push(self.types());
type_tables.len() - 1
});
ModuleSerialized {
artifacts: self
.inner
.artifact_upvars
.iter()
.map(|i| MyCow::Borrowed(i.compilation_artifacts()))
.chain(Some(MyCow::Borrowed(
self.compiled_module().compilation_artifacts(),
)))
.collect(),
modules: self
.inner
.module_upvars
.iter()
.map(|i| i.serialized_module(type_tables_pushed, type_tables))
.collect(),
type_tables: type_tables_idx,
}
}
/// Deserializes and creates a module from the compilation artifacts.
/// The `serialize` saves the compilation artifacts along with the host
/// fingerprint, which consists of target, compiler flags, and wasmtime
/// package version.
/// Serialize the module to a vector of bytes.
///
/// The method will fail if fingerprints of current host and serialized
/// one are different. The method does not verify the serialized artifacts
/// for modifications or curruptions. All responsibily of signing and its
/// verification falls on the embedder.
pub fn deserialize(engine: &Engine, serialized: &[u8]) -> Result<Module> {
let (fingerprint, types, serialized) = bincode_options()
.deserialize::<(u64, Vec<TypeTables>, _)>(serialized)
.context("Deserialize compilation artifacts")?;
if fingerprint != compiler_fingerprint(engine) {
bail!("Incompatible compilation artifact");
}
let types = types.into_iter().map(Arc::new).collect::<Vec<_>>();
return mk(engine, &types, serialized);
fn mk(
engine: &Engine,
types: &Vec<Arc<TypeTables>>,
module: ModuleSerialized<'_>,
) -> Result<Module> {
let mut artifacts = CompiledModule::from_artifacts_list(
module
.artifacts
.into_iter()
.map(|i| i.unwrap_owned())
.collect(),
engine.compiler().isa(),
&*engine.config().profiler,
)?;
let inner = ModuleInner {
engine: engine.clone(),
types: types[module.type_tables].clone(),
module: artifacts.pop().unwrap(),
artifact_upvars: artifacts,
module_upvars: module
.modules
.into_iter()
.map(|m| mk(engine, types, m))
.collect::<Result<Vec<_>>>()?,
};
Ok(Module {
inner: Arc::new(inner),
})
}
/// Use `Module::new` or `Module::from_binary` to create the module
/// from the bytes.
pub fn serialize(&self) -> Result<Vec<u8>> {
SerializedModule::new(self).to_bytes()
}
/// Creates a submodule `Module` value from the specified parameters.
@@ -493,7 +396,7 @@ impl Module {
/// the upvars array in the submodule to be created, and each element of
/// this array is an index into this module's upvar array.
/// * `module_upvars` - similar to `artifact_upvars` this is a mapping of
/// how to create the e`module_upvars` of the submodule being created.
/// how to create the `module_upvars` of the submodule being created.
/// Each entry in this array is either an index into this module's own
/// module upvars array or it's an index into `modules`, the list of
/// modules so far for the instance where this submodule is being
@@ -764,24 +667,6 @@ impl Module {
}
}
fn bincode_options() -> impl Options {
// Use a variable-length integer encoding instead of fixed length. The
// module shown on #2318 gets compressed from ~160MB to ~110MB simply using
// this, presumably because there's a lot of 8-byte integers which generally
// have small values. Local testing shows that the deserialization
// performance, while higher, is in the few-percent range. For huge size
// savings this seems worthwhile to lose a small percentage of
// deserialization performance.
bincode::DefaultOptions::new().with_varint_encoding()
}
fn compiler_fingerprint(engine: &Engine) -> u64 {
use std::hash::Hasher;
let mut hasher = std::collections::hash_map::DefaultHasher::new();
engine.compiler().hash(&mut hasher);
hasher.finish()
}
fn _assert_send_sync() {
fn _assert<T: Send + Sync>() {}
_assert::<Module>();

View File

@@ -0,0 +1,813 @@
//! Implements module serialization.
use super::ModuleInner;
use crate::{Engine, Module, OptLevel};
use anyhow::{anyhow, bail, Context, Result};
use bincode::Options;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::fmt;
use std::str::FromStr;
use std::sync::Arc;
use std::{collections::HashMap, fmt::Display};
use wasmtime_environ::Tunables;
use wasmtime_environ::{isa::TargetIsa, settings};
use wasmtime_jit::{
CompilationArtifacts, CompilationStrategy, CompiledModule, Compiler, TypeTables,
};
const HEADER: &[u8] = b"\0wasmtime-aot";
fn bincode_options() -> impl Options {
// Use a variable-length integer encoding instead of fixed length. The
// module shown on #2318 gets compressed from ~160MB to ~110MB simply using
// this, presumably because there's a lot of 8-byte integers which generally
// have small values. Local testing shows that the deserialization
// performance, while higher, is in the few-percent range. For huge size
// savings this seems worthwhile to lose a small percentage of
// deserialization performance.
bincode::DefaultOptions::new().with_varint_encoding()
}
// This exists because `wasmparser::WasmFeatures` isn't serializable
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
struct WasmFeatures {
pub reference_types: bool,
pub multi_value: bool,
pub bulk_memory: bool,
pub module_linking: bool,
pub simd: bool,
pub threads: bool,
pub tail_call: bool,
pub deterministic_only: bool,
pub multi_memory: bool,
pub exceptions: bool,
pub memory64: bool,
}
impl From<&wasmparser::WasmFeatures> for WasmFeatures {
fn from(other: &wasmparser::WasmFeatures) -> Self {
let wasmparser::WasmFeatures {
reference_types,
multi_value,
bulk_memory,
module_linking,
simd,
threads,
tail_call,
deterministic_only,
multi_memory,
exceptions,
memory64,
} = other;
Self {
reference_types: *reference_types,
multi_value: *multi_value,
bulk_memory: *bulk_memory,
module_linking: *module_linking,
simd: *simd,
threads: *threads,
tail_call: *tail_call,
deterministic_only: *deterministic_only,
multi_memory: *multi_memory,
exceptions: *exceptions,
memory64: *memory64,
}
}
}
// This is like `std::borrow::Cow` but it doesn't have a `Clone` bound on `T`
enum MyCow<'a, T> {
Borrowed(&'a T),
Owned(T),
}
impl<'a, T> MyCow<'a, T> {
fn unwrap_owned(self) -> T {
match self {
MyCow::Owned(val) => val,
MyCow::Borrowed(_) => unreachable!(),
}
}
}
impl<'a, T: Serialize> Serialize for MyCow<'a, T> {
fn serialize<S>(&self, dst: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
match self {
MyCow::Borrowed(val) => val.serialize(dst),
MyCow::Owned(val) => val.serialize(dst),
}
}
}
impl<'a, 'b, T: Deserialize<'a>> Deserialize<'a> for MyCow<'b, T> {
fn deserialize<D>(src: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'a>,
{
Ok(MyCow::Owned(T::deserialize(src)?))
}
}
impl From<settings::OptLevel> for OptLevel {
fn from(level: settings::OptLevel) -> Self {
match level {
settings::OptLevel::Speed => OptLevel::Speed,
settings::OptLevel::SpeedAndSize => OptLevel::SpeedAndSize,
settings::OptLevel::None => OptLevel::None,
}
}
}
/// A small helper struct which defines modules are serialized.
#[derive(Serialize, Deserialize)]
struct SerializedModuleData<'a> {
/// All compiled artifacts needed by this module, where the last entry in
/// this list is the artifacts for the module itself.
artifacts: Vec<MyCow<'a, CompilationArtifacts>>,
/// Closed-over module values that are also needed for this module.
modules: Vec<SerializedModuleData<'a>>,
/// The index into the list of type tables that are used for this module's
/// type tables.
type_tables: usize,
}
impl<'a> SerializedModuleData<'a> {
pub fn new(module: &'a Module) -> (Self, Vec<MyCow<'a, TypeTables>>) {
let mut pushed = HashMap::new();
let mut tables = Vec::new();
return (module_data(module, &mut pushed, &mut tables), tables);
fn module_data<'a>(
module: &'a Module,
type_tables_pushed: &mut HashMap<usize, usize>,
type_tables: &mut Vec<MyCow<'a, TypeTables>>,
) -> SerializedModuleData<'a> {
// Deduplicate `Arc<TypeTables>` using our two parameters to ensure we
// serialize type tables as little as possible.
let ptr = Arc::as_ptr(module.types());
let type_tables_idx = *type_tables_pushed.entry(ptr as usize).or_insert_with(|| {
type_tables.push(MyCow::Borrowed(module.types()));
type_tables.len() - 1
});
SerializedModuleData {
artifacts: module
.inner
.artifact_upvars
.iter()
.map(|i| MyCow::Borrowed(i.compilation_artifacts()))
.chain(Some(MyCow::Borrowed(
module.compiled_module().compilation_artifacts(),
)))
.collect(),
modules: module
.inner
.module_upvars
.iter()
.map(|i| module_data(i, type_tables_pushed, type_tables))
.collect(),
type_tables: type_tables_idx,
}
}
}
}
#[derive(Serialize, Deserialize, Eq, PartialEq)]
enum FlagValue {
Enum(Cow<'static, str>),
Num(u8),
Bool(bool),
}
impl From<settings::Value> for FlagValue {
fn from(v: settings::Value) -> Self {
match v.kind() {
settings::SettingKind::Enum => Self::Enum(v.as_enum().unwrap().into()),
settings::SettingKind::Num => Self::Num(v.as_num().unwrap()),
settings::SettingKind::Bool => Self::Bool(v.as_bool().unwrap()),
settings::SettingKind::Preset => unreachable!(),
}
}
}
impl Display for FlagValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Enum(v) => v.fmt(f),
Self::Num(v) => v.fmt(f),
Self::Bool(v) => v.fmt(f),
}
}
}
#[derive(Serialize, Deserialize)]
pub struct SerializedModule<'a> {
target: String,
shared_flags: HashMap<String, FlagValue>,
isa_flags: HashMap<String, FlagValue>,
strategy: CompilationStrategy,
tunables: Tunables,
features: WasmFeatures,
data: SerializedModuleData<'a>,
tables: Vec<MyCow<'a, TypeTables>>,
}
impl<'a> SerializedModule<'a> {
pub fn new(module: &'a Module) -> Self {
let (data, tables) = SerializedModuleData::new(module);
Self::with_data(module.engine().compiler(), data, tables)
}
pub fn from_artifacts(
compiler: &Compiler,
artifacts: &'a Vec<CompilationArtifacts>,
types: &'a TypeTables,
) -> Self {
Self::with_data(
compiler,
SerializedModuleData {
artifacts: artifacts.iter().map(MyCow::Borrowed).collect(),
modules: Vec::new(),
type_tables: 0,
},
vec![MyCow::Borrowed(types)],
)
}
fn with_data(
compiler: &Compiler,
data: SerializedModuleData<'a>,
tables: Vec<MyCow<'a, TypeTables>>,
) -> Self {
let isa = compiler.isa();
Self {
target: isa.triple().to_string(),
shared_flags: isa
.flags()
.iter()
.map(|v| (v.name.to_owned(), v.into()))
.collect(),
isa_flags: isa
.isa_flags()
.into_iter()
.map(|v| (v.name.to_owned(), v.into()))
.collect(),
strategy: compiler.strategy(),
tunables: compiler.tunables().clone(),
features: compiler.features().into(),
data,
tables,
}
}
pub fn into_module(mut self, engine: &Engine) -> Result<Module> {
let compiler = engine.compiler();
let isa = compiler.isa();
self.check_triple(isa)?;
self.check_shared_flags(isa)?;
self.check_isa_flags(isa)?;
self.check_strategy(compiler)?;
self.check_tunables(compiler)?;
self.check_features(compiler)?;
let types = self
.tables
.into_iter()
.map(|t| Arc::new(t.unwrap_owned()))
.collect::<Vec<_>>();
let module = mk(engine, &types, self.data)?;
// Validate the module can be used with the current allocator
engine.allocator().validate(module.inner.module.module())?;
return Ok(module);
fn mk(
engine: &Engine,
types: &Vec<Arc<TypeTables>>,
data: SerializedModuleData<'_>,
) -> Result<Module> {
let mut artifacts = CompiledModule::from_artifacts_list(
data.artifacts
.into_iter()
.map(|i| i.unwrap_owned())
.collect(),
engine.compiler().isa(),
&*engine.config().profiler,
)?;
let inner = ModuleInner {
engine: engine.clone(),
types: types[data.type_tables].clone(),
module: artifacts.pop().unwrap(),
artifact_upvars: artifacts,
module_upvars: data
.modules
.into_iter()
.map(|m| mk(engine, types, m))
.collect::<Result<Vec<_>>>()?,
};
Ok(Module {
inner: Arc::new(inner),
})
}
}
pub fn to_bytes(&self) -> Result<Vec<u8>> {
use std::io::Write;
let mut bytes = Vec::new();
bytes.write_all(HEADER)?;
// Preface the data with a version so we can do a version check independent
// of the serialized data.
let version = env!("CARGO_PKG_VERSION");
assert!(
version.len() < 256,
"package version must be less than 256 bytes"
);
bytes.write(&[version.len() as u8])?;
bytes.write_all(version.as_bytes())?;
bincode_options().serialize_into(&mut bytes, self)?;
Ok(bytes)
}
pub fn from_bytes(bytes: &[u8]) -> Result<Option<Self>> {
if !bytes.starts_with(HEADER) {
return Ok(None);
}
let bytes = &bytes[HEADER.len()..];
if bytes.is_empty() {
bail!("serialized data data is empty");
}
let version_len = bytes[0] as usize;
if bytes.len() < version_len + 1 {
bail!("serialized data is malformed");
}
let version = std::str::from_utf8(&bytes[1..1 + version_len])?;
if version != env!("CARGO_PKG_VERSION") {
bail!(
"Module was compiled with incompatible Wasmtime version '{}'",
version
);
}
Ok(Some(
bincode_options()
.deserialize::<SerializedModule<'_>>(&bytes[1 + version_len..])
.context("deserialize compilation artifacts")?,
))
}
fn check_triple(&self, isa: &dyn TargetIsa) -> Result<()> {
let triple = target_lexicon::Triple::from_str(&self.target).map_err(|e| anyhow!(e))?;
if triple.architecture != isa.triple().architecture {
bail!(
"Module was compiled for architecture '{}'",
triple.architecture
);
}
if triple.operating_system != isa.triple().operating_system {
bail!(
"Module was compiled for operating system '{}'",
triple.operating_system
);
}
Ok(())
}
fn check_shared_flags(&mut self, isa: &dyn TargetIsa) -> Result<()> {
let mut shared_flags = std::mem::take(&mut self.shared_flags);
for value in isa.flags().iter() {
let name = value.name;
match shared_flags.remove(name) {
Some(v) => {
let host: FlagValue = value.into();
if v != host {
bail!("Module was compiled with a different '{}' setting: expected '{}' but host has '{}'", name, v, host);
}
}
None => bail!("Module was compiled without setting '{}'", name),
}
}
for (name, _) in shared_flags {
bail!(
"Module was compiled with setting '{}' but it is not present for the host",
name
);
}
Ok(())
}
fn check_isa_flags(&mut self, isa: &dyn TargetIsa) -> Result<()> {
let mut isa_flags = std::mem::take(&mut self.isa_flags);
for value in isa.isa_flags().into_iter() {
let name = value.name;
let host: FlagValue = value.into();
match isa_flags.remove(name) {
Some(v) => match (&v, &host) {
(FlagValue::Bool(v), FlagValue::Bool(host)) => {
// ISA flags represent CPU features; for boolean values, only
// treat it as an error if the module was compiled with the setting enabled
// but the host does not have it enabled.
if *v && !*host {
bail!("Module was compiled with setting '{}' enabled but the host does not support it", name);
}
}
_ => {
if v != host {
bail!("Module was compiled with a different '{}' setting: expected '{}' but host has '{}'", name, v, host);
}
}
},
None => bail!("Module was compiled without setting '{}'", name),
}
}
for (name, _) in isa_flags {
bail!(
"Module was compiled with setting '{}' but it is not present for the host",
name
);
}
Ok(())
}
fn check_strategy(&self, compiler: &Compiler) -> Result<()> {
#[allow(unreachable_patterns)]
let matches = match (self.strategy, compiler.strategy()) {
(CompilationStrategy::Auto, CompilationStrategy::Auto)
| (CompilationStrategy::Auto, CompilationStrategy::Cranelift)
| (CompilationStrategy::Cranelift, CompilationStrategy::Auto)
| (CompilationStrategy::Cranelift, CompilationStrategy::Cranelift) => true,
#[cfg(feature = "lightbeam")]
(CompilationStrategy::Lightbeam, CompilationStrategy::Lightbeam) => true,
_ => false,
};
if !matches {
bail!("Module was compiled with strategy '{:?}'", self.strategy);
}
Ok(())
}
fn check_int<T: Eq + std::fmt::Display>(found: T, expected: T, feature: &str) -> Result<()> {
if found == expected {
return Ok(());
}
bail!(
"Module was compiled with a {} of '{}' but '{}' is expected for the host",
feature,
found,
expected
);
}
fn check_bool(found: bool, expected: bool, feature: &str) -> Result<()> {
if found == expected {
return Ok(());
}
bail!(
"Module was compiled {} {} but it {} enabled for the host",
if found { "with" } else { "without" },
feature,
if expected { "is" } else { "is not" }
);
}
fn check_tunables(&self, compiler: &Compiler) -> Result<()> {
let Tunables {
static_memory_bound,
static_memory_offset_guard_size,
dynamic_memory_offset_guard_size,
generate_native_debuginfo,
parse_wasm_debuginfo,
interruptable,
consume_fuel,
static_memory_bound_is_maximum,
} = self.tunables;
let other = compiler.tunables();
Self::check_int(
static_memory_bound,
other.static_memory_bound,
"static memory bound",
)?;
Self::check_int(
static_memory_offset_guard_size,
other.static_memory_offset_guard_size,
"static memory guard size",
)?;
Self::check_int(
dynamic_memory_offset_guard_size,
other.dynamic_memory_offset_guard_size,
"dynamic memory guard size",
)?;
Self::check_bool(
generate_native_debuginfo,
other.generate_native_debuginfo,
"debug information support",
)?;
Self::check_bool(
parse_wasm_debuginfo,
other.parse_wasm_debuginfo,
"WebAssembly backtrace support",
)?;
Self::check_bool(interruptable, other.interruptable, "interruption support")?;
Self::check_bool(consume_fuel, other.consume_fuel, "fuel support")?;
Self::check_bool(
static_memory_bound_is_maximum,
other.static_memory_bound_is_maximum,
"pooling allocation support",
)?;
Ok(())
}
fn check_features(&self, compiler: &Compiler) -> Result<()> {
let WasmFeatures {
reference_types,
multi_value,
bulk_memory,
module_linking,
simd,
threads,
tail_call,
deterministic_only,
multi_memory,
exceptions,
memory64,
} = self.features;
let other = compiler.features();
Self::check_bool(
reference_types,
other.reference_types,
"WebAssembly reference types support",
)?;
Self::check_bool(
multi_value,
other.multi_value,
"WebAssembly multi-value support",
)?;
Self::check_bool(
bulk_memory,
other.bulk_memory,
"WebAssembly bulk memory support",
)?;
Self::check_bool(
module_linking,
other.module_linking,
"WebAssembly module linking support",
)?;
Self::check_bool(simd, other.simd, "WebAssembly SIMD support")?;
Self::check_bool(threads, other.threads, "WebAssembly threads support")?;
Self::check_bool(tail_call, other.tail_call, "WebAssembly tail-call support")?;
Self::check_bool(
deterministic_only,
other.deterministic_only,
"WebAssembly deterministic-only support",
)?;
Self::check_bool(
multi_memory,
other.multi_memory,
"WebAssembly multi-memory support",
)?;
Self::check_bool(
exceptions,
other.exceptions,
"WebAssembly exceptions support",
)?;
Self::check_bool(
memory64,
other.memory64,
"WebAssembly 64-bit memory support",
)?;
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::Config;
#[test]
fn test_architecture_mismatch() -> Result<()> {
let engine = Engine::default();
let module = Module::new(&engine, "(module)")?;
let mut serialized = SerializedModule::new(&module);
serialized.target = "unknown-generic-linux".to_string();
match serialized.into_module(&engine) {
Ok(_) => unreachable!(),
Err(e) => assert_eq!(
e.to_string(),
"Module was compiled for architecture 'unknown'",
),
}
Ok(())
}
#[test]
fn test_os_mismatch() -> Result<()> {
let engine = Engine::default();
let module = Module::new(&engine, "(module)")?;
let mut serialized = SerializedModule::new(&module);
serialized.target = format!(
"{}-generic-unknown",
target_lexicon::Triple::host().architecture
);
match serialized.into_module(&engine) {
Ok(_) => unreachable!(),
Err(e) => assert_eq!(
e.to_string(),
"Module was compiled for operating system 'unknown'",
),
}
Ok(())
}
#[test]
fn test_cranelift_flags_mismatch() -> Result<()> {
let engine = Engine::default();
let module = Module::new(&engine, "(module)")?;
let mut serialized = SerializedModule::new(&module);
serialized.shared_flags.insert(
"opt_level".to_string(),
FlagValue::Enum(Cow::Borrowed("none")),
);
match serialized.into_module(&engine) {
Ok(_) => unreachable!(),
Err(e) => assert_eq!(
e.to_string(),
"Module was compiled with a different 'opt_level' setting: expected 'none' but host has 'speed'"
),
}
Ok(())
}
#[cfg(target_arch = "x86_64")]
#[test]
fn test_isa_flags_mismatch() -> Result<()> {
let engine = Engine::default();
let module = Module::new(&engine, "(module)")?;
let mut serialized = SerializedModule::new(&module);
serialized
.isa_flags
.insert("not_a_flag".to_string(), FlagValue::Bool(true));
match serialized.into_module(&engine) {
Ok(_) => unreachable!(),
Err(e) => assert_eq!(
e.to_string(),
"Module was compiled with setting 'not_a_flag' but it is not present for the host",
),
}
Ok(())
}
#[cfg(feature = "lightbeam")]
#[test]
fn test_compilation_strategy_mismatch() -> Result<()> {
let engine = Engine::default();
let module = Module::new(&engine, "(module)")?;
let mut serialized = SerializedModule::new(&module);
serialized.strategy = CompilationStrategy::Lightbeam;
match serialized.into_module(&engine) {
Ok(_) => unreachable!(),
Err(e) => assert_eq!(
e.to_string(),
"Module was compiled with strategy 'Cranelift'",
),
}
Ok(())
}
#[test]
fn test_tunables_int_mismatch() -> Result<()> {
let engine = Engine::default();
let module = Module::new(&engine, "(module)")?;
let mut serialized = SerializedModule::new(&module);
serialized.tunables.static_memory_offset_guard_size = 0;
match serialized.into_module(&engine) {
Ok(_) => unreachable!(),
Err(e) => assert_eq!(e.to_string(), "Module was compiled with a static memory guard size of '0' but '2147483648' is expected for the host"),
}
Ok(())
}
#[test]
fn test_tunables_bool_mismatch() -> Result<()> {
let mut config = Config::new();
config.interruptable(true);
let engine = Engine::new(&config)?;
let module = Module::new(&engine, "(module)")?;
let mut serialized = SerializedModule::new(&module);
serialized.tunables.interruptable = false;
match serialized.into_module(&engine) {
Ok(_) => unreachable!(),
Err(e) => assert_eq!(
e.to_string(),
"Module was compiled without interruption support but it is enabled for the host"
),
}
let mut config = Config::new();
config.interruptable(false);
let engine = Engine::new(&config)?;
let module = Module::new(&engine, "(module)")?;
let mut serialized = SerializedModule::new(&module);
serialized.tunables.interruptable = true;
match serialized.into_module(&engine) {
Ok(_) => unreachable!(),
Err(e) => assert_eq!(
e.to_string(),
"Module was compiled with interruption support but it is not enabled for the host"
),
}
Ok(())
}
#[test]
fn test_feature_mismatch() -> Result<()> {
let mut config = Config::new();
config.wasm_simd(true);
let engine = Engine::new(&config)?;
let module = Module::new(&engine, "(module)")?;
let mut serialized = SerializedModule::new(&module);
serialized.features.simd = false;
match serialized.into_module(&engine) {
Ok(_) => unreachable!(),
Err(e) => assert_eq!(e.to_string(), "Module was compiled without WebAssembly SIMD support but it is enabled for the host"),
}
let mut config = Config::new();
config.wasm_simd(false);
let engine = Engine::new(&config)?;
let module = Module::new(&engine, "(module)")?;
let mut serialized = SerializedModule::new(&module);
serialized.features.simd = true;
match serialized.into_module(&engine) {
Ok(_) => unreachable!(),
Err(e) => assert_eq!(e.to_string(), "Module was compiled with WebAssembly SIMD support but it is not enabled for the host"),
}
Ok(())
}
}

View File

@@ -80,3 +80,29 @@ with:
```sh
$ wasmtime wasm2obj foo.wasm foo.o
```
## `compile`
This subcommand is used to Ahead-Of-Time (AOT) compile a WebAssembly module to produce
a "compiled wasm" (.cwasm) file.
The `wasmtime run` subcommand can then be used to run a AOT-compiled WebAssembly module:
```sh
$ wasmtime compile foo.wasm
$ wasmtime foo.cwasm
```
AOT-compiled modules can be run from hosts that are compatible with the target
environment of the AOT-completed module.
## `settings`
This subcommand is used to print the available Cranelift settings for a given target.
When run without options, it will print the settings for the host target and also
display what Cranelift settings are inferred for the host:
```sh
$ wasmtime settings
```

View File

@@ -31,7 +31,7 @@ fn deserialize(buffer: &[u8]) -> Result<()> {
// Compile the wasm binary into an in-memory instance of a `Module`.
println!("Deserialize module...");
let module = Module::deserialize(store.engine(), buffer)?;
let module = Module::new(store.engine(), buffer)?;
// Here we handle the imports of the module, which in this case is our
// `HelloCallback` type and its associated implementation of `Callback.

View File

@@ -6,7 +6,7 @@
use anyhow::Result;
use structopt::{clap::AppSettings, clap::ErrorKind, StructOpt};
use wasmtime_cli::commands::{
ConfigCommand, RunCommand, WasmToObjCommand, WastCommand, WASM2OBJ_AFTER_HELP,
CompileCommand, ConfigCommand, RunCommand, SettingsCommand, WasmToObjCommand, WastCommand,
};
/// Wasmtime WebAssembly Runtime
@@ -38,10 +38,14 @@ enum WasmtimeApp {
// !!! IMPORTANT: if subcommands are added or removed, update `parse_module` in `src/commands/run.rs`. !!!
/// Controls Wasmtime configuration settings
Config(ConfigCommand),
/// Compiles a WebAssembly module.
Compile(CompileCommand),
/// Runs a WebAssembly module
Run(RunCommand),
/// Displays available Cranelift settings for a target.
Settings(SettingsCommand),
/// Translates a WebAssembly module to native object file
#[structopt(name = "wasm2obj", after_help = WASM2OBJ_AFTER_HELP)]
#[structopt(name = "wasm2obj")]
WasmToObj(WasmToObjCommand),
/// Runs a WebAssembly test script file
Wast(WastCommand),
@@ -49,10 +53,12 @@ enum WasmtimeApp {
impl WasmtimeApp {
/// Executes the command.
pub fn execute(&self) -> Result<()> {
pub fn execute(self) -> Result<()> {
match self {
Self::Config(c) => c.execute(),
Self::Compile(c) => c.execute(),
Self::Run(c) => c.execute(),
Self::Settings(c) => c.execute(),
Self::WasmToObj(c) => c.execute(),
Self::Wast(c) => c.execute(),
}

View File

@@ -1,8 +1,10 @@
//! The module for the Wasmtime CLI commands.
mod compile;
mod config;
mod run;
mod settings;
mod wasm2obj;
mod wast;
pub use self::{config::*, run::*, wasm2obj::*, wast::*};
pub use self::{compile::*, config::*, run::*, settings::*, wasm2obj::*, wast::*};

273
src/commands/compile.rs Normal file
View File

@@ -0,0 +1,273 @@
//! The module that implements the `wasmtime compile` command.
use crate::CommonOptions;
use anyhow::{bail, Context, Result};
use std::fs;
use std::path::PathBuf;
use structopt::{clap::AppSettings, StructOpt};
use target_lexicon::Triple;
use wasmtime::Engine;
lazy_static::lazy_static! {
static ref AFTER_HELP: String = {
format!(
"By default, no CPU features or presets will be enabled for the compilation.\n\
\n\
{}\
\n\
Usage examples:\n\
\n\
Compiling a WebAssembly module for the current platform:\n\
\n \
wasmtime compile example.wasm
\n\
Specifying the output file:\n\
\n \
wasmtime compile -o output.cwasm input.wasm\n\
\n\
Compiling for a specific platform (Linux) and CPU preset (Skylake):\n\
\n \
wasmtime compile --target x86_64-unknown-linux --cranelift-enable skylake foo.wasm\n",
crate::WASM_FEATURES.as_str()
)
};
}
/// Compiles a WebAssembly module.
#[derive(StructOpt)]
#[structopt(
name = "compile",
version = env!("CARGO_PKG_VERSION"),
setting = AppSettings::ColoredHelp,
after_help = AFTER_HELP.as_str()
)]
pub struct CompileCommand {
#[structopt(flatten)]
common: CommonOptions,
/// Enable support for interrupting WebAssembly code.
#[structopt(long)]
interruptable: bool,
/// The target triple; default is the host triple
#[structopt(long, value_name = "TARGET")]
target: Option<String>,
/// The path of the output compiled module; defaults to <MODULE>.cwasm
#[structopt(short = "o", long, value_name = "OUTPUT", parse(from_os_str))]
output: Option<PathBuf>,
/// The path of the WebAssembly to compile
#[structopt(index = 1, value_name = "MODULE", parse(from_os_str))]
module: PathBuf,
}
impl CompileCommand {
/// Executes the command.
pub fn execute(mut self) -> Result<()> {
self.common.init_logging();
let target = self
.target
.take()
.unwrap_or_else(|| Triple::host().to_string());
let mut config = self.common.config(Some(&target))?;
config.interruptable(self.interruptable);
let engine = Engine::new(&config)?;
if self.module.file_name().is_none() {
bail!(
"'{}' is not a valid input module path",
self.module.display()
);
}
let input = fs::read(&self.module).with_context(|| "failed to read input file")?;
let output = self.output.take().unwrap_or_else(|| {
let mut output: PathBuf = self.module.file_name().unwrap().into();
output.set_extension("cwasm");
output
});
fs::write(output, engine.precompile_module(&input)?)?;
Ok(())
}
}
#[cfg(test)]
mod test {
use super::*;
use std::io::Write;
use tempfile::NamedTempFile;
use wasmtime::{Instance, Module, Store};
#[test]
fn test_successful_compile() -> Result<()> {
let (mut input, input_path) = NamedTempFile::new()?.into_parts();
input.write_all(
"(module (func (export \"f\") (param i32) (result i32) local.get 0))".as_bytes(),
)?;
drop(input);
let output_path = NamedTempFile::new()?.into_temp_path();
let command = CompileCommand::from_iter_safe(vec![
"compile",
"--disable-logging",
"-o",
output_path.to_str().unwrap(),
input_path.to_str().unwrap(),
])?;
command.execute()?;
let engine = Engine::default();
let module = Module::from_file(&engine, output_path)?;
let store = Store::new(&engine);
let instance = Instance::new(&store, &module, &[])?;
let f = instance.get_typed_func::<i32, i32>("f")?;
assert_eq!(f.call(1234).unwrap(), 1234);
Ok(())
}
#[cfg(target_arch = "x86_64")]
#[test]
fn test_x64_flags_compile() -> Result<()> {
let (mut input, input_path) = NamedTempFile::new()?.into_parts();
input.write_all("(module)".as_bytes())?;
drop(input);
let output_path = NamedTempFile::new()?.into_temp_path();
// Set all the x64 flags to make sure they work
let command = CompileCommand::from_iter_safe(vec![
"compile",
"--disable-logging",
"--cranelift-enable",
"has_sse3",
"--cranelift-enable",
"has_ssse3",
"--cranelift-enable",
"has_sse41",
"--cranelift-enable",
"has_sse42",
"--cranelift-enable",
"has_avx",
"--cranelift-enable",
"has_avx2",
"--cranelift-enable",
"has_avx512dq",
"--cranelift-enable",
"has_avx512vl",
"--cranelift-enable",
"has_avx512f",
"--cranelift-enable",
"has_popcnt",
"--cranelift-enable",
"has_bmi1",
"--cranelift-enable",
"has_bmi2",
"--cranelift-enable",
"has_lzcnt",
"-o",
output_path.to_str().unwrap(),
input_path.to_str().unwrap(),
])?;
command.execute()?;
Ok(())
}
#[cfg(target_arch = "aarch64")]
#[test]
fn test_aarch64_flags_compile() -> Result<()> {
let (mut input, input_path) = NamedTempFile::new()?.into_parts();
input.write_all("(module)".as_bytes())?;
drop(input);
let output_path = NamedTempFile::new()?.into_temp_path();
// Set all the aarch64 flags to make sure they work
let command = CompileCommand::from_iter_safe(vec![
"compile",
"--disable-logging",
"--cranelift-enable",
"has_lse",
"-o",
output_path.to_str().unwrap(),
input_path.to_str().unwrap(),
])?;
command.execute()?;
Ok(())
}
#[cfg(target_arch = "x86_64")]
#[test]
fn test_unsupported_flags_compile() -> Result<()> {
let (mut input, input_path) = NamedTempFile::new()?.into_parts();
input.write_all("(module)".as_bytes())?;
drop(input);
let output_path = NamedTempFile::new()?.into_temp_path();
// aarch64 flags should not be supported
let command = CompileCommand::from_iter_safe(vec![
"compile",
"--disable-logging",
"--cranelift-enable",
"has_lse",
"-o",
output_path.to_str().unwrap(),
input_path.to_str().unwrap(),
])?;
assert_eq!(
command.execute().unwrap_err().to_string(),
"No existing setting named 'has_lse'"
);
Ok(())
}
#[cfg(target_arch = "x86_64")]
#[test]
fn test_x64_presets_compile() -> Result<()> {
let (mut input, input_path) = NamedTempFile::new()?.into_parts();
input.write_all("(module)".as_bytes())?;
drop(input);
let output_path = NamedTempFile::new()?.into_temp_path();
for preset in &[
"nehalem",
"haswell",
"broadwell",
"skylake",
"cannonlake",
"icelake",
"znver1",
] {
let command = CompileCommand::from_iter_safe(vec![
"compile",
"--disable-logging",
"--cranelift-enable",
preset,
"-o",
output_path.to_str().unwrap(),
input_path.to_str().unwrap(),
])?;
command.execute()?;
}
Ok(())
}
}

View File

@@ -17,7 +17,7 @@ pub enum ConfigCommand {
impl ConfigCommand {
/// Executes the command.
pub fn execute(&self) -> Result<()> {
pub fn execute(self) -> Result<()> {
match self {
Self::New(c) => c.execute(),
}
@@ -35,7 +35,7 @@ pub struct ConfigNewCommand {
impl ConfigNewCommand {
/// Executes the command.
pub fn execute(&self) -> Result<()> {
pub fn execute(self) -> Result<()> {
let path = wasmtime_cache::create_new_config(self.path.as_ref())?;
println!(

View File

@@ -1,6 +1,6 @@
//! The module that implements the `wasmtime run` command.
use crate::{init_file_per_thread_logger, CommonOptions};
use crate::CommonOptions;
use anyhow::{bail, Context as _, Result};
use std::thread;
use std::time::Duration;
@@ -28,9 +28,8 @@ use wasmtime_wasi_crypto::{
fn parse_module(s: &OsStr) -> Result<PathBuf, OsString> {
// Do not accept wasmtime subcommand names as the module name
match s.to_str() {
Some("help") | Some("config") | Some("run") | Some("wasm2obj") | Some("wast") => {
Err("module name cannot be the same as a subcommand".into())
}
Some("help") | Some("config") | Some("run") | Some("wasm2obj") | Some("wast")
| Some("compile") => Err("module name cannot be the same as a subcommand".into()),
_ => Ok(s.into()),
}
}
@@ -69,9 +68,15 @@ fn parse_preloads(s: &str) -> Result<(String, PathBuf)> {
Ok((parts[0].into(), parts[1].into()))
}
lazy_static::lazy_static! {
static ref AFTER_HELP: String = {
crate::WASM_FEATURES.to_string()
};
}
/// Runs a WebAssembly module
#[derive(StructOpt)]
#[structopt(name = "run", setting = AppSettings::TrailingVarArg)]
#[structopt(name = "run", setting = AppSettings::TrailingVarArg, after_help = AFTER_HELP.as_str())]
pub struct RunCommand {
#[structopt(flatten)]
common: CommonOptions,
@@ -96,7 +101,7 @@ pub struct RunCommand {
#[structopt(
index = 1,
required = true,
value_name = "WASM_MODULE",
value_name = "MODULE",
parse(try_from_os_str = parse_module),
)]
module: PathBuf,
@@ -127,14 +132,9 @@ pub struct RunCommand {
impl RunCommand {
/// Executes the command.
pub fn execute(&self) -> Result<()> {
if self.common.log_to_files {
let prefix = "wasmtime.dbg.";
init_file_per_thread_logger(prefix);
} else {
pretty_env_logger::init();
}
self.common.init_logging();
let mut config = self.common.config()?;
let mut config = self.common.config(None)?;
if self.wasm_timeout.is_some() {
config.interruptable(true);
}

98
src/commands/settings.rs Normal file
View File

@@ -0,0 +1,98 @@
//! The module that implements the `wasmtime settings` command.
use anyhow::{anyhow, Result};
use std::str::FromStr;
use structopt::StructOpt;
use wasmtime_environ::settings::{self, Setting, SettingKind};
use wasmtime_jit::native;
/// Displays available Cranelift settings for a target.
#[derive(StructOpt)]
#[structopt(name = "run")]
pub struct SettingsCommand {
/// The target triple to get the settings for; defaults to the host triple.
#[structopt(long, value_name = "TARGET")]
target: Option<String>,
}
impl SettingsCommand {
/// Executes the command.
pub fn execute(self) -> Result<()> {
let settings = match &self.target {
Some(target) => {
native::lookup(target_lexicon::Triple::from_str(target).map_err(|e| anyhow!(e))?)?
}
None => native::builder(),
};
let mut enums = (Vec::new(), 0, "Enum settings:");
let mut nums = (Vec::new(), 0, "Numerical settings:");
let mut bools = (Vec::new(), 0, "Boolean settings:");
let mut presets = (Vec::new(), 0, "Presets:");
for setting in settings.iter() {
let (collection, max, _) = match setting.kind {
SettingKind::Enum => &mut enums,
SettingKind::Num => &mut nums,
SettingKind::Bool => &mut bools,
SettingKind::Preset => &mut presets,
};
if setting.name.len() > *max {
*max = setting.name.len();
}
collection.push(setting);
}
if enums.0.is_empty() && nums.0.is_empty() && bools.0.is_empty() && presets.0.is_empty() {
println!("Target '{}' has no settings.", settings.triple());
return Ok(());
}
println!("Cranelift settings for target '{}':", settings.triple());
for (collection, max, header) in &mut [enums, nums, bools, presets] {
if collection.is_empty() {
continue;
}
collection.sort_by_key(|k| k.name);
println!();
Self::print_settings(header, collection, *max);
}
if self.target.is_none() {
let isa = settings.finish(settings::Flags::new(settings::builder()));
println!();
println!("Settings inferred for the current host:");
let mut values = isa.isa_flags();
values.sort_by_key(|k| k.name);
for value in values {
if value.as_bool().unwrap_or(false) {
println!(" {}", value.name);
}
}
}
Ok(())
}
fn print_settings(header: &str, settings: &[Setting], width: usize) {
println!("{}", header);
for setting in settings {
println!(
" {:width$} {}{}",
setting.name,
setting.description,
setting
.values
.map(|v| format!(" Supported values: {}.", v.join(", ")))
.unwrap_or("".to_string()),
width = width + 2
);
}
}
}

View File

@@ -1,23 +1,26 @@
//! The module that implements the `wasmtime wasm2obj` command.
use crate::obj::compile_to_obj;
use crate::{init_file_per_thread_logger, pick_compilation_strategy, CommonOptions};
use anyhow::{anyhow, Context as _, Result};
use crate::{parse_target, pick_compilation_strategy, CommonOptions};
use anyhow::{Context as _, Result};
use std::{
fs::File,
io::Write,
path::{Path, PathBuf},
str::FromStr,
};
use structopt::{clap::AppSettings, StructOpt};
use target_lexicon::Triple;
/// The after help text for the `wasm2obj` command.
pub const WASM2OBJ_AFTER_HELP: &str = "The translation is dependent on the environment chosen.\n\
The default is a dummy environment that produces placeholder values.";
fn parse_target(s: &str) -> Result<Triple> {
Triple::from_str(&s).map_err(|e| anyhow!(e))
lazy_static::lazy_static! {
static ref AFTER_HELP: String = {
format!(
"The translation is dependent on the environment chosen.\n\
The default is a dummy environment that produces placeholder values.\n\
\n\
{}",
crate::WASM_FEATURES.as_str()
)
};
}
/// Translates a WebAssembly module to native object file
@@ -26,7 +29,7 @@ fn parse_target(s: &str) -> Result<Triple> {
name = "wasm2obj",
version = env!("CARGO_PKG_VERSION"),
setting = AppSettings::ColoredHelp,
after_help = WASM2OBJ_AFTER_HELP,
after_help = AFTER_HELP.as_str(),
)]
pub struct WasmToObjCommand {
#[structopt(flatten)]
@@ -47,17 +50,8 @@ pub struct WasmToObjCommand {
impl WasmToObjCommand {
/// Executes the command.
pub fn execute(&self) -> Result<()> {
self.handle_module()
}
fn handle_module(&self) -> Result<()> {
if self.common.log_to_files {
let prefix = "wasm2obj.dbg.";
init_file_per_thread_logger(prefix);
} else {
pretty_env_logger::init();
}
pub fn execute(self) -> Result<()> {
self.common.init_logging();
let strategy = pick_compilation_strategy(self.common.cranelift, self.common.lightbeam)?;

View File

@@ -1,18 +1,25 @@
//! The module that implements the `wasmtime wast` command.
use crate::{init_file_per_thread_logger, CommonOptions};
use crate::CommonOptions;
use anyhow::{Context as _, Result};
use std::path::PathBuf;
use structopt::{clap::AppSettings, StructOpt};
use wasmtime::{Engine, Store};
use wasmtime_wast::WastContext;
lazy_static::lazy_static! {
static ref AFTER_HELP: String = {
crate::WASM_FEATURES.to_string()
};
}
/// Runs a WebAssembly test script file
#[derive(StructOpt)]
#[structopt(
name = "wast",
version = env!("CARGO_PKG_VERSION"),
setting = AppSettings::ColoredHelp,
after_help = AFTER_HELP.as_str(),
)]
pub struct WastCommand {
#[structopt(flatten)]
@@ -25,15 +32,10 @@ pub struct WastCommand {
impl WastCommand {
/// Executes the command.
pub fn execute(&self) -> Result<()> {
if self.common.log_to_files {
let prefix = "wast.dbg.";
init_file_per_thread_logger(prefix);
} else {
pretty_env_logger::init();
}
pub fn execute(self) -> Result<()> {
self.common.init_logging();
let config = self.common.config()?;
let config = self.common.config(None)?;
let store = Store::new(&Engine::new(&config)?);
let mut wast_context = WastContext::new(store);

View File

@@ -23,12 +23,55 @@
)
)]
const SUPPORTED_WASM_FEATURES: &[(&str, &str)] = &[
("all", "enables all supported WebAssembly features"),
(
"bulk-memory",
"enables support for bulk memory instructions",
),
(
"module-linking",
"enables support for the module-linking proposal",
),
(
"multi-memory",
"enables support for the multi-memory proposal",
),
("multi-value", "enables support for multi-value functions"),
("reference-types", "enables support for reference types"),
("simd", "enables support for proposed SIMD instructions"),
("threads", "enables support for WebAssembly threads"),
];
lazy_static::lazy_static! {
static ref WASM_FEATURES: String = {
use std::fmt::Write;
let mut s = String::new();
writeln!(&mut s, "Supported values for `--wasm-features`:").unwrap();
writeln!(&mut s).unwrap();
let max = SUPPORTED_WASM_FEATURES.iter().max_by_key(|(name, _)| name.len()).unwrap();
for (name, desc) in SUPPORTED_WASM_FEATURES.iter() {
writeln!(&mut s, "{:width$} {}", name, desc, width = max.0.len() + 2).unwrap();
}
writeln!(&mut s).unwrap();
writeln!(&mut s, "Features prefixed with '-' will be disabled.").unwrap();
s
};
}
pub mod commands;
mod obj;
use anyhow::{bail, Result};
use std::collections::HashMap;
use std::path::PathBuf;
use structopt::StructOpt;
use target_lexicon::Triple;
use wasmtime::{Config, ProfilingStrategy, Strategy};
pub use obj::compile_to_obj;
@@ -91,6 +134,10 @@ struct CommonOptions {
#[structopt(long, conflicts_with = "lightbeam")]
cranelift: bool,
/// Disable logging.
#[structopt(long, conflicts_with = "log_to_files")]
disable_logging: bool,
/// Log to per-thread log files instead of stderr.
#[structopt(long)]
log_to_files: bool,
@@ -103,38 +150,42 @@ struct CommonOptions {
#[structopt(long)]
disable_cache: bool,
/// Enable support for proposed SIMD instructions
#[structopt(long)]
/// Enable support for proposed SIMD instructions (deprecated; use `--wasm-features=simd`)
#[structopt(long, hidden = true)]
enable_simd: bool,
/// Enable support for reference types
#[structopt(long)]
enable_reference_types: Option<bool>,
/// Enable support for reference types (deprecated; use `--wasm-features=reference-types`)
#[structopt(long, hidden = true)]
enable_reference_types: bool,
/// Enable support for multi-value functions
#[structopt(long)]
enable_multi_value: Option<bool>,
/// Enable support for multi-value functions (deprecated; use `--wasm-features=multi-value`)
#[structopt(long, hidden = true)]
enable_multi_value: bool,
/// Enable support for Wasm threads
#[structopt(long)]
/// Enable support for Wasm threads (deprecated; use `--wasm-features=threads`)
#[structopt(long, hidden = true)]
enable_threads: bool,
/// Enable support for bulk memory instructions
#[structopt(long)]
enable_bulk_memory: Option<bool>,
/// Enable support for bulk memory instructions (deprecated; use `--wasm-features=bulk-memory`)
#[structopt(long, hidden = true)]
enable_bulk_memory: bool,
/// Enable support for the multi-memory proposal
#[structopt(long)]
/// Enable support for the multi-memory proposal (deprecated; use `--wasm-features=multi-memory`)
#[structopt(long, hidden = true)]
enable_multi_memory: bool,
/// Enable support for the module-linking proposal
#[structopt(long)]
/// Enable support for the module-linking proposal (deprecated; use `--wasm-features=module-linking`)
#[structopt(long, hidden = true)]
enable_module_linking: bool,
/// Enable all experimental Wasm features
#[structopt(long)]
/// Enable all experimental Wasm features (deprecated; use `--wasm-features=all`)
#[structopt(long, hidden = true)]
enable_all: bool,
/// Enables or disables WebAssembly features
#[structopt(long, value_name = "FEATURE,FEATURE,...", parse(try_from_str = parse_wasm_features))]
wasm_features: Option<wasmparser::WasmFeatures>,
/// Use Lightbeam for all compilation
#[structopt(long, conflicts_with = "cranelift")]
lightbeam: bool,
@@ -151,30 +202,42 @@ struct CommonOptions {
#[structopt(short = "O", long)]
optimize: bool,
/// Optimization level for generated functions (0 (none), 1, 2 (most), or s
/// (size))
/// Optimization level for generated functions
/// Supported levels: 0 (none), 1, 2 (most), or s (size); default is "most"
#[structopt(
long,
value_name = "LEVEL",
parse(try_from_str = parse_opt_level),
default_value = "2",
verbatim_doc_comment,
)]
opt_level: wasmtime::OptLevel,
opt_level: Option<wasmtime::OptLevel>,
/// Other Cranelift flags to be passed down to Cranelift.
#[structopt(long, parse(try_from_str = parse_cranelift_flag))]
cranelift_flags: Vec<CraneliftFlag>,
/// Set a Cranelift setting to a given value.
/// Use `wasmtime settings` to list Cranelift settings for a target.
#[structopt(long = "cranelift-set", value_name = "NAME=VALUE", number_of_values = 1, verbatim_doc_comment, parse(try_from_str = parse_cranelift_flag))]
cranelift_set: Vec<(String, String)>,
/// Enable a Cranelift boolean setting or preset.
/// Use `wasmtime settings` to list Cranelift settings for a target.
#[structopt(
long,
value_name = "SETTING",
number_of_values = 1,
verbatim_doc_comment
)]
cranelift_enable: Vec<String>,
/// Maximum size in bytes of wasm memory before it becomes dynamically
/// relocatable instead of up-front-reserved.
#[structopt(long)]
#[structopt(long, value_name = "MAXIMUM")]
static_memory_maximum_size: Option<u64>,
/// Byte size of the guard region after static memories are allocated.
#[structopt(long)]
#[structopt(long, value_name = "SIZE")]
static_memory_guard_size: Option<u64>,
/// Byte size of the guard region after dynamic memories are allocated.
#[structopt(long)]
#[structopt(long, value_name = "SIZE")]
dynamic_memory_guard_size: Option<u64>,
/// Enable Cranelift's internal debug verifier (expensive)
@@ -187,31 +250,48 @@ struct CommonOptions {
}
impl CommonOptions {
fn config(&self) -> Result<Config> {
fn init_logging(&self) {
if self.disable_logging {
return;
}
if self.log_to_files {
let prefix = "wasmtime.dbg.";
init_file_per_thread_logger(prefix);
} else {
pretty_env_logger::init();
}
}
fn config(&self, target: Option<&str>) -> Result<Config> {
let mut config = Config::new();
// Set the target before setting any cranelift options
if let Some(target) = target {
config.target(target)?;
}
config
.cranelift_debug_verifier(self.enable_cranelift_debug_verifier)
.debug_info(self.debug_info)
.wasm_simd(self.enable_simd || self.enable_all)
.wasm_bulk_memory(self.enable_bulk_memory.unwrap_or(true) || self.enable_all)
.wasm_reference_types(
self.enable_reference_types
.unwrap_or(cfg!(target_arch = "x86_64"))
|| self.enable_all,
)
.wasm_multi_value(self.enable_multi_value.unwrap_or(true) || self.enable_all)
.wasm_threads(self.enable_threads || self.enable_all)
.wasm_multi_memory(self.enable_multi_memory || self.enable_all)
.wasm_module_linking(self.enable_module_linking || self.enable_all)
.cranelift_opt_level(self.opt_level())
.strategy(pick_compilation_strategy(self.cranelift, self.lightbeam)?)?
.profiler(pick_profiling_strategy(self.jitdump, self.vtune)?)?
.cranelift_nan_canonicalization(self.enable_cranelift_nan_canonicalization);
for CraneliftFlag { name, value } in &self.cranelift_flags {
self.enable_wasm_features(&mut config);
for name in &self.cranelift_enable {
unsafe {
config.cranelift_other_flag(name, value)?;
config.cranelift_flag_enable(name)?;
}
}
for (name, value) in &self.cranelift_set {
unsafe {
config.cranelift_flag_set(name, value)?;
}
}
if !self.disable_cache {
match &self.config {
Some(path) => {
@@ -222,22 +302,43 @@ impl CommonOptions {
}
}
}
if let Some(max) = self.static_memory_maximum_size {
config.static_memory_maximum_size(max);
}
if let Some(size) = self.static_memory_guard_size {
config.static_memory_guard_size(size);
}
if let Some(size) = self.dynamic_memory_guard_size {
config.dynamic_memory_guard_size(size);
}
Ok(config)
}
fn enable_wasm_features(&self, config: &mut Config) {
let features = self.wasm_features.unwrap_or_default();
config
.wasm_simd(features.simd || self.enable_simd || self.enable_all)
.wasm_bulk_memory(features.bulk_memory || self.enable_bulk_memory || self.enable_all)
.wasm_reference_types(
features.reference_types || self.enable_reference_types || self.enable_all,
)
.wasm_multi_value(features.multi_value || self.enable_multi_value || self.enable_all)
.wasm_threads(features.threads || self.enable_threads || self.enable_all)
.wasm_multi_memory(features.multi_memory || self.enable_multi_memory || self.enable_all)
.wasm_module_linking(
features.module_linking || self.enable_module_linking || self.enable_all,
);
}
fn opt_level(&self) -> wasmtime::OptLevel {
match (self.optimize, self.opt_level.clone()) {
(true, _) => wasmtime::OptLevel::Speed,
(false, other) => other,
(false, other) => other.unwrap_or(wasmtime::OptLevel::Speed),
}
}
}
@@ -255,12 +356,59 @@ fn parse_opt_level(opt_level: &str) -> Result<wasmtime::OptLevel> {
}
}
struct CraneliftFlag {
name: String,
value: String,
}
fn parse_wasm_features(features: &str) -> Result<wasmparser::WasmFeatures> {
let features = features.trim();
fn parse_cranelift_flag(name_and_value: &str) -> Result<CraneliftFlag> {
let mut all = None;
let mut values: HashMap<_, _> = SUPPORTED_WASM_FEATURES
.iter()
.map(|(name, _)| (name.to_string(), None))
.collect();
if features == "all" {
all = Some(true);
} else if features == "-all" {
all = Some(false);
} else {
for feature in features.split(',') {
let feature = feature.trim();
if feature.is_empty() {
continue;
}
let (feature, value) = if feature.starts_with('-') {
(&feature[1..], false)
} else {
(feature, true)
};
if feature == "all" {
bail!("'all' cannot be specified with other WebAssembly features");
}
match values.get_mut(feature) {
Some(v) => *v = Some(value),
None => bail!("unsupported WebAssembly feature '{}'", feature),
}
}
}
Ok(wasmparser::WasmFeatures {
reference_types: all.unwrap_or(values["reference-types"].unwrap_or(true)),
multi_value: all.unwrap_or(values["multi-value"].unwrap_or(true)),
bulk_memory: all.unwrap_or(values["bulk-memory"].unwrap_or(true)),
module_linking: all.unwrap_or(values["module-linking"].unwrap_or(false)),
simd: all.unwrap_or(values["simd"].unwrap_or(false)),
threads: all.unwrap_or(values["threads"].unwrap_or(false)),
tail_call: false,
deterministic_only: false,
multi_memory: all.unwrap_or(values["multi-memory"].unwrap_or(false)),
exceptions: false,
memory64: false,
})
}
fn parse_cranelift_flag(name_and_value: &str) -> Result<(String, String)> {
let mut split = name_and_value.splitn(2, '=');
let name = if let Some(name) = split.next() {
name.to_string()
@@ -272,5 +420,158 @@ fn parse_cranelift_flag(name_and_value: &str) -> Result<CraneliftFlag> {
} else {
bail!("missing value in cranelift flag");
};
Ok(CraneliftFlag { name, value })
Ok((name, value))
}
fn parse_target(s: &str) -> Result<Triple> {
use std::str::FromStr;
Triple::from_str(&s).map_err(|e| anyhow::anyhow!(e))
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_all_features() -> Result<()> {
let options = CommonOptions::from_iter_safe(vec!["foo", "--wasm-features=all"])?;
let wasmparser::WasmFeatures {
reference_types,
multi_value,
bulk_memory,
module_linking,
simd,
threads,
tail_call,
deterministic_only,
multi_memory,
exceptions,
memory64,
} = options.wasm_features.unwrap();
assert!(reference_types);
assert!(multi_value);
assert!(bulk_memory);
assert!(module_linking);
assert!(simd);
assert!(threads);
assert!(!tail_call); // Not supported
assert!(!deterministic_only); // Not supported
assert!(multi_memory);
assert!(!exceptions); // Not supported
assert!(!memory64); // Not supported
Ok(())
}
#[test]
fn test_no_features() -> Result<()> {
let options = CommonOptions::from_iter_safe(vec!["foo", "--wasm-features=-all"])?;
let wasmparser::WasmFeatures {
reference_types,
multi_value,
bulk_memory,
module_linking,
simd,
threads,
tail_call,
deterministic_only,
multi_memory,
exceptions,
memory64,
} = options.wasm_features.unwrap();
assert!(!reference_types);
assert!(!multi_value);
assert!(!bulk_memory);
assert!(!module_linking);
assert!(!simd);
assert!(!threads);
assert!(!tail_call);
assert!(!deterministic_only);
assert!(!multi_memory);
assert!(!exceptions);
assert!(!memory64);
Ok(())
}
#[test]
fn test_multiple_features() -> Result<()> {
let options = CommonOptions::from_iter_safe(vec![
"foo",
"--wasm-features=-reference-types,simd,multi-memory",
])?;
let wasmparser::WasmFeatures {
reference_types,
multi_value,
bulk_memory,
module_linking,
simd,
threads,
tail_call,
deterministic_only,
multi_memory,
exceptions,
memory64,
} = options.wasm_features.unwrap();
assert!(!reference_types);
assert!(multi_value);
assert!(bulk_memory);
assert!(!module_linking);
assert!(simd);
assert!(!threads);
assert!(!tail_call); // Not supported
assert!(!deterministic_only); // Not supported
assert!(multi_memory);
assert!(!exceptions); // Not supported
assert!(!memory64); // Not supported
Ok(())
}
macro_rules! feature_test {
($test_name:ident, $name:ident, $flag:literal) => {
#[test]
fn $test_name() -> Result<()> {
let options =
CommonOptions::from_iter_safe(vec!["foo", concat!("--wasm-features=", $flag)])?;
let wasmparser::WasmFeatures { $name, .. } = options.wasm_features.unwrap();
assert!($name);
let options = CommonOptions::from_iter_safe(vec![
"foo",
concat!("--wasm-features=-", $flag),
])?;
let wasmparser::WasmFeatures { $name, .. } = options.wasm_features.unwrap();
assert!(!$name);
Ok(())
}
};
}
feature_test!(
test_reference_types_feature,
reference_types,
"reference-types"
);
feature_test!(test_multi_value_feature, multi_value, "multi-value");
feature_test!(test_bulk_memory_feature, bulk_memory, "bulk-memory");
feature_test!(
test_module_linking_feature,
module_linking,
"module-linking"
);
feature_test!(test_simd_feature, simd, "simd");
feature_test!(test_threads_feature, threads, "threads");
feature_test!(test_multi_memory_feature, multi_memory, "multi-memory");
}

View File

@@ -1,57 +1,78 @@
use anyhow::Result;
use wasmtime::*;
#[test]
fn checks_incompatible_target() -> Result<()> {
let mut target = target_lexicon::Triple::host();
target.operating_system = target_lexicon::OperatingSystem::Unknown;
match Module::new(
&Engine::new(Config::new().target(&target.to_string())?)?,
"(module)",
) {
Ok(_) => unreachable!(),
Err(e) => assert!(e
.to_string()
.contains("configuration does not match the host")),
}
Ok(())
}
#[test]
fn caches_across_engines() {
let mut c = Config::new();
c.cranelift_clear_cpu_flags();
let c = Config::new();
let bytes = Module::new(&Engine::new(&c).unwrap(), "(module)")
.unwrap()
.serialize()
.unwrap();
let res = Module::deserialize(
&Engine::new(&Config::new().cranelift_clear_cpu_flags()).unwrap(),
&bytes,
);
let res = Module::new(&Engine::new(&Config::new()).unwrap(), &bytes);
assert!(res.is_ok());
// differ in shared cranelift flags
let res = Module::deserialize(
&Engine::new(
&Config::new()
.cranelift_clear_cpu_flags()
.cranelift_nan_canonicalization(true),
)
.unwrap(),
let res = Module::new(
&Engine::new(Config::new().cranelift_nan_canonicalization(true)).unwrap(),
&bytes,
);
assert!(res.is_err());
// differ in cranelift settings
let res = Module::deserialize(
&Engine::new(
&Config::new()
.cranelift_clear_cpu_flags()
.cranelift_opt_level(OptLevel::None),
)
.unwrap(),
let res = Module::new(
&Engine::new(Config::new().cranelift_opt_level(OptLevel::None)).unwrap(),
&bytes,
);
assert!(res.is_err());
// differ in cpu-specific flags
// Missing required cpu flags
if cfg!(target_arch = "x86_64") {
let res = Module::deserialize(
&Engine::new(unsafe {
&Config::new()
.cranelift_clear_cpu_flags()
.cranelift_other_flag("has_sse3", "true")
.unwrap()
})
let res = Module::new(
&Engine::new(
Config::new()
.target(&target_lexicon::Triple::host().to_string())
.unwrap(),
)
.unwrap(),
&bytes,
);
assert!(res.is_err());
}
}
#[test]
fn aot_compiles() -> Result<()> {
let engine = Engine::default();
let bytes = engine.precompile_module(
"(module (func (export \"f\") (param i32) (result i32) local.get 0))".as_bytes(),
)?;
let module = Module::from_binary(&engine, &bytes)?;
let store = Store::new(&engine);
let instance = Instance::new(&store, &module, &[])?;
let f = instance.get_typed_func::<i32, i32>("f")?;
assert_eq!(f.call(101).unwrap(), 101);
Ok(())
}

View File

@@ -39,7 +39,7 @@ fn compile() -> Result<()> {
assert_eq!(m.imports().len(), 0);
assert_eq!(m.exports().len(), 0);
let bytes = m.serialize()?;
Module::deserialize(&engine, &bytes)?;
Module::new(&engine, &bytes)?;
assert_eq!(m.imports().len(), 0);
assert_eq!(m.exports().len(), 0);
Ok(())

View File

@@ -7,10 +7,27 @@ fn serialize(engine: &Engine, wat: &'static str) -> Result<Vec<u8>> {
}
fn deserialize_and_instantiate(store: &Store, buffer: &[u8]) -> Result<Instance> {
let module = Module::deserialize(store.engine(), buffer)?;
let module = Module::new(store.engine(), buffer)?;
Ok(Instance::new(&store, &module, &[])?)
}
#[test]
fn test_version_mismatch() -> Result<()> {
let engine = Engine::default();
let mut buffer = serialize(&engine, "(module)")?;
buffer[13 /* header length */ + 1 /* version length */] = 'x' as u8;
match Module::new(&engine, &buffer) {
Ok(_) => bail!("expected deserialization to fail"),
Err(e) => assert_eq!(
e.to_string(),
"Module was compiled with incompatible Wasmtime version 'x.25.0'"
),
}
Ok(())
}
#[test]
fn test_module_serialize_simple() -> Result<()> {
let buffer = serialize(