Implement roundtrip fuzzing of component adapters (#4640)
* Improve the `component_api` fuzzer on a few dimensions * Update the generated component to use an adapter module. This involves two core wasm instances communicating with each other to test that data flows through everything correctly. The intention here is to fuzz the fused adapter compiler. String encoding options have been plumbed here to exercise differences in string encodings. * Use `Cow<'static, ...>` and `static` declarations for each static test case to try to cut down on rustc codegen time. * Add `Copy` to derivation of fuzzed enums to make `derive(Clone)` smaller. * Use `Store<Box<dyn Any>>` to try to cut down on codegen by monomorphizing fewer `Store<T>` implementation. * Add debug logging to print out what's flowing in and what's flowing out for debugging failures. * Improve `Debug` representation of dynamic value types to more closely match their Rust counterparts. * Fix a variant issue with adapter trampolines Previously the offset of the payload was calculated as the discriminant aligned up to the alignment of a singular case, but instead this needs to be aligned up to the alignment of all cases to ensure all cases start at the same location. * Fix a copy/paste error when copying masked integers A 32-bit load was actually doing a 16-bit load by accident since it was copied from the 16-bit load-and-mask case. * Fix f32/i64 conversions in adapter modules The adapter previously erroneously converted the f32 to f64 and then to i64, where instead it should go from f32 to i32 to i64. * Fix zero-sized flags in adapter modules This commit corrects the size calculation for zero-sized flags in adapter modules. cc #4592 * Fix a variant size calculation bug in adapters This fixes the same issue found with variants during normal host-side fuzzing earlier where the size of a variant needs to align up the summation of the discriminant and the maximum case size. * Implement memory growth in libc bump realloc Some fuzz-generated test cases are copying lists large enough to exceed one page of memory so bake in a `memory.grow` to the bump allocator as well. * Avoid adapters of exponential size This commit is an attempt to avoid adapters being exponentially sized with respect to the type hierarchy of the input. Previously all adaptation was done inline within each adapter which meant that if something was structured as `tuple<T, T, T, T, ...>` the translation of `T` would be inlined N times. For very deeply nested types this can quickly create an exponentially sized adapter with types of the form: (type $t0 (list u8)) (type $t1 (tuple $t0 $t0)) (type $t2 (tuple $t1 $t1)) (type $t3 (tuple $t2 $t2)) ;; ... where the translation of `t4` has 8 different copies of translating `t0`. This commit changes the translation of types through memory to almost always go through a helper function. The hope here is that it doesn't lose too much performance because types already reside in memory. This can still lead to exponentially sized adapter modules to a lesser degree where if the translation all happens on the "stack", e.g. via `variant`s and their flat representation then many copies of one translation could still be made. For now this commit at least gets the problem under control for fuzzing where fuzzing doesn't trivially find type hierarchies that take over a minute to codegen the adapter module. One of the main tricky parts of this implementation is that when a function is generated the index that it will be placed at in the final module is not known at that time. To solve this the encoded form of the `Call` instruction is saved in a relocation-style format where the `Call` isn't encoded but instead saved into a different area for encoding later. When the entire adapter module is encoded to wasm these pseudo-`Call` instructions are encoded as real instructions at that time. * Fix some memory64 issues with string encodings Introduced just before #4623 I had a few mistakes related to 64-bit memories and mixing 32/64-bit memories. * Actually insert into the `translate_mem_funcs` map This... was the whole point of having the map! * Assert memory growth succeeds in bump allocator
This commit is contained in:
@@ -123,11 +123,35 @@ pub const REALLOC_AND_FREE: &str = r#"
|
|||||||
|
|
||||||
;; save the current value of `$last` as the return value
|
;; save the current value of `$last` as the return value
|
||||||
global.get $last
|
global.get $last
|
||||||
local.tee $ret
|
local.set $ret
|
||||||
|
|
||||||
|
;; bump our pointer
|
||||||
|
(global.set $last
|
||||||
|
(i32.add
|
||||||
|
(global.get $last)
|
||||||
|
(local.get $new_size)))
|
||||||
|
|
||||||
|
;; while `memory.size` is less than `$last`, grow memory
|
||||||
|
;; by one page
|
||||||
|
(loop $loop
|
||||||
|
(if
|
||||||
|
(i32.lt_u
|
||||||
|
(i32.mul (memory.size) (i32.const 65536))
|
||||||
|
(global.get $last))
|
||||||
|
(then
|
||||||
|
i32.const 1
|
||||||
|
memory.grow
|
||||||
|
;; test to make sure growth succeeded
|
||||||
|
i32.const -1
|
||||||
|
i32.eq
|
||||||
|
if unreachable end
|
||||||
|
|
||||||
|
br $loop)))
|
||||||
|
|
||||||
|
|
||||||
;; ensure anything necessary is set to valid data by spraying a bit
|
;; ensure anything necessary is set to valid data by spraying a bit
|
||||||
;; pattern that is invalid
|
;; pattern that is invalid
|
||||||
global.get $last
|
local.get $ret
|
||||||
i32.const 0xde
|
i32.const 0xde
|
||||||
local.get $new_size
|
local.get $new_size
|
||||||
memory.fill
|
memory.fill
|
||||||
@@ -142,10 +166,6 @@ pub const REALLOC_AND_FREE: &str = r#"
|
|||||||
memory.copy
|
memory.copy
|
||||||
end
|
end
|
||||||
|
|
||||||
;; bump our pointer
|
local.get $ret
|
||||||
(global.set $last
|
|
||||||
(i32.add
|
|
||||||
(global.get $last)
|
|
||||||
(local.get $new_size)))
|
|
||||||
)
|
)
|
||||||
"#;
|
"#;
|
||||||
|
|||||||
@@ -19,10 +19,13 @@
|
|||||||
//! that.
|
//! that.
|
||||||
|
|
||||||
use crate::component::dfg::CoreDef;
|
use crate::component::dfg::CoreDef;
|
||||||
use crate::component::{Adapter, AdapterOptions, ComponentTypes, StringEncoding, TypeFuncIndex};
|
use crate::component::{
|
||||||
use crate::{FuncIndex, GlobalIndex, MemoryIndex, PrimaryMap};
|
Adapter, AdapterOptions as AdapterOptionsDfg, ComponentTypes, InterfaceType, StringEncoding,
|
||||||
|
TypeFuncIndex,
|
||||||
|
};
|
||||||
|
use crate::fact::transcode::Transcoder;
|
||||||
|
use crate::{EntityRef, FuncIndex, GlobalIndex, MemoryIndex, PrimaryMap};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::mem;
|
|
||||||
use wasm_encoder::*;
|
use wasm_encoder::*;
|
||||||
|
|
||||||
mod core_types;
|
mod core_types;
|
||||||
@@ -50,26 +53,28 @@ pub struct Module<'a> {
|
|||||||
/// Final list of imports that this module ended up using, in the same order
|
/// Final list of imports that this module ended up using, in the same order
|
||||||
/// as the imports in the import section.
|
/// as the imports in the import section.
|
||||||
imports: Vec<Import>,
|
imports: Vec<Import>,
|
||||||
/// Intern'd imports and what index they were assigned.
|
/// Intern'd imports and what index they were assigned. Note that this map
|
||||||
imported: HashMap<CoreDef, u32>,
|
/// covers all the index spaces for imports, not just one.
|
||||||
imported_memories: PrimaryMap<MemoryIndex, CoreDef>,
|
imported: HashMap<CoreDef, usize>,
|
||||||
|
/// Intern'd transcoders and what index they were assigned.
|
||||||
|
imported_transcoders: HashMap<Transcoder, FuncIndex>,
|
||||||
|
|
||||||
// Current status of index spaces from the imports generated so far.
|
// Current status of index spaces from the imports generated so far.
|
||||||
core_funcs: u32,
|
imported_funcs: PrimaryMap<FuncIndex, Option<CoreDef>>,
|
||||||
core_memories: u32,
|
imported_memories: PrimaryMap<MemoryIndex, CoreDef>,
|
||||||
core_globals: u32,
|
imported_globals: PrimaryMap<GlobalIndex, CoreDef>,
|
||||||
|
|
||||||
/// Adapters which will be compiled once they're all registered.
|
funcs: PrimaryMap<FunctionId, Function>,
|
||||||
adapters: Vec<AdapterData>,
|
translate_mem_funcs: HashMap<(InterfaceType, InterfaceType, Options, Options), FunctionId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AdapterData {
|
struct AdapterData {
|
||||||
/// Export name of this adapter
|
/// Export name of this adapter
|
||||||
name: String,
|
name: String,
|
||||||
/// Options specified during the `canon lift` operation
|
/// Options specified during the `canon lift` operation
|
||||||
lift: Options,
|
lift: AdapterOptions,
|
||||||
/// Options specified during the `canon lower` operation
|
/// Options specified during the `canon lower` operation
|
||||||
lower: Options,
|
lower: AdapterOptions,
|
||||||
/// The core wasm function that this adapter will be calling (the original
|
/// The core wasm function that this adapter will be calling (the original
|
||||||
/// function that was `canon lift`'d)
|
/// function that was `canon lift`'d)
|
||||||
callee: FuncIndex,
|
callee: FuncIndex,
|
||||||
@@ -78,14 +83,38 @@ struct AdapterData {
|
|||||||
called_as_export: bool,
|
called_as_export: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Options {
|
/// Configuration options which apply at the "global adapter" level.
|
||||||
|
///
|
||||||
|
/// These options are typically unique per-adapter and generally aren't needed
|
||||||
|
/// when translating recursive types within an adapter.
|
||||||
|
struct AdapterOptions {
|
||||||
|
/// The ascribed type of this adapter.
|
||||||
ty: TypeFuncIndex,
|
ty: TypeFuncIndex,
|
||||||
string_encoding: StringEncoding,
|
/// The global that represents the instance flags for where this adapter
|
||||||
|
/// came from.
|
||||||
flags: GlobalIndex,
|
flags: GlobalIndex,
|
||||||
memory64: bool,
|
/// The configured post-return function, if any.
|
||||||
memory: Option<MemoryIndex>,
|
|
||||||
realloc: Option<FuncIndex>,
|
|
||||||
post_return: Option<FuncIndex>,
|
post_return: Option<FuncIndex>,
|
||||||
|
/// Other, more general, options configured.
|
||||||
|
options: Options,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This type is split out of `AdapterOptions` and is specifically used to
|
||||||
|
/// deduplicate translation functions within a module. Consequently this has
|
||||||
|
/// as few fields as possible to minimize the number of functions generated
|
||||||
|
/// within an adapter module.
|
||||||
|
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
|
||||||
|
struct Options {
|
||||||
|
/// The encoding that strings use from this adapter.
|
||||||
|
string_encoding: StringEncoding,
|
||||||
|
/// Whether or not the `memory` field, if present, is a 64-bit memory.
|
||||||
|
memory64: bool,
|
||||||
|
/// An optionally-specified memory where values may travel through for
|
||||||
|
/// types like lists.
|
||||||
|
memory: Option<MemoryIndex>,
|
||||||
|
/// An optionally-specified function to be used to allocate space for
|
||||||
|
/// types such as strings as they go into a module.
|
||||||
|
realloc: Option<FuncIndex>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Context {
|
enum Context {
|
||||||
@@ -102,12 +131,13 @@ impl<'a> Module<'a> {
|
|||||||
core_types: Default::default(),
|
core_types: Default::default(),
|
||||||
core_imports: Default::default(),
|
core_imports: Default::default(),
|
||||||
imported: Default::default(),
|
imported: Default::default(),
|
||||||
adapters: Default::default(),
|
|
||||||
imports: Default::default(),
|
imports: Default::default(),
|
||||||
|
imported_transcoders: Default::default(),
|
||||||
|
imported_funcs: PrimaryMap::new(),
|
||||||
imported_memories: PrimaryMap::new(),
|
imported_memories: PrimaryMap::new(),
|
||||||
core_funcs: 0,
|
imported_globals: PrimaryMap::new(),
|
||||||
core_memories: 0,
|
funcs: PrimaryMap::new(),
|
||||||
core_globals: 0,
|
translate_mem_funcs: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +158,7 @@ impl<'a> Module<'a> {
|
|||||||
// Import the core wasm function which was lifted using its appropriate
|
// Import the core wasm function which was lifted using its appropriate
|
||||||
// signature since the exported function this adapter generates will
|
// signature since the exported function this adapter generates will
|
||||||
// call the lifted function.
|
// call the lifted function.
|
||||||
let signature = self.signature(&lift, Context::Lift);
|
let signature = self.types.signature(&lift, Context::Lift);
|
||||||
let ty = self
|
let ty = self
|
||||||
.core_types
|
.core_types
|
||||||
.function(&signature.params, &signature.results);
|
.function(&signature.params, &signature.results);
|
||||||
@@ -141,19 +171,24 @@ impl<'a> Module<'a> {
|
|||||||
self.import_func("post_return", name, ty, func.clone())
|
self.import_func("post_return", name, ty, func.clone())
|
||||||
});
|
});
|
||||||
|
|
||||||
self.adapters.push(AdapterData {
|
// This will internally create the adapter as specified and append
|
||||||
name: name.to_string(),
|
// anything necessary to `self.funcs`.
|
||||||
lift,
|
trampoline::compile(
|
||||||
lower,
|
self,
|
||||||
callee,
|
&AdapterData {
|
||||||
// FIXME(#4185) should be plumbed and handled as part of the new
|
name: name.to_string(),
|
||||||
// reentrance rules not yet implemented here.
|
lift,
|
||||||
called_as_export: true,
|
lower,
|
||||||
});
|
callee,
|
||||||
|
// FIXME(#4185) should be plumbed and handled as part of the new
|
||||||
|
// reentrance rules not yet implemented here.
|
||||||
|
called_as_export: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn import_options(&mut self, ty: TypeFuncIndex, options: &AdapterOptions) -> Options {
|
fn import_options(&mut self, ty: TypeFuncIndex, options: &AdapterOptionsDfg) -> AdapterOptions {
|
||||||
let AdapterOptions {
|
let AdapterOptionsDfg {
|
||||||
instance,
|
instance,
|
||||||
string_encoding,
|
string_encoding,
|
||||||
memory,
|
memory,
|
||||||
@@ -192,23 +227,24 @@ impl<'a> Module<'a> {
|
|||||||
let ty = self.core_types.function(&[ptr, ptr, ptr, ptr], &[ptr]);
|
let ty = self.core_types.function(&[ptr, ptr, ptr, ptr], &[ptr]);
|
||||||
self.import_func("realloc", "", ty, func.clone())
|
self.import_func("realloc", "", ty, func.clone())
|
||||||
});
|
});
|
||||||
Options {
|
|
||||||
|
AdapterOptions {
|
||||||
ty,
|
ty,
|
||||||
string_encoding: *string_encoding,
|
|
||||||
flags,
|
flags,
|
||||||
memory64: *memory64,
|
|
||||||
memory,
|
|
||||||
realloc,
|
|
||||||
post_return: None,
|
post_return: None,
|
||||||
|
options: Options {
|
||||||
|
string_encoding: *string_encoding,
|
||||||
|
memory64: *memory64,
|
||||||
|
memory,
|
||||||
|
realloc,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn import_func(&mut self, module: &str, name: &str, ty: u32, def: CoreDef) -> FuncIndex {
|
fn import_func(&mut self, module: &str, name: &str, ty: u32, def: CoreDef) -> FuncIndex {
|
||||||
FuncIndex::from_u32(
|
self.import(module, name, EntityType::Function(ty), def, |m| {
|
||||||
self.import(module, name, EntityType::Function(ty), def, |m| {
|
&mut m.imported_funcs
|
||||||
&mut m.core_funcs
|
})
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn import_global(
|
fn import_global(
|
||||||
@@ -218,9 +254,9 @@ impl<'a> Module<'a> {
|
|||||||
ty: GlobalType,
|
ty: GlobalType,
|
||||||
def: CoreDef,
|
def: CoreDef,
|
||||||
) -> GlobalIndex {
|
) -> GlobalIndex {
|
||||||
GlobalIndex::from_u32(self.import(module, name, EntityType::Global(ty), def, |m| {
|
self.import(module, name, EntityType::Global(ty), def, |m| {
|
||||||
&mut m.core_globals
|
&mut m.imported_globals
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn import_memory(
|
fn import_memory(
|
||||||
@@ -230,82 +266,113 @@ impl<'a> Module<'a> {
|
|||||||
ty: MemoryType,
|
ty: MemoryType,
|
||||||
def: CoreDef,
|
def: CoreDef,
|
||||||
) -> MemoryIndex {
|
) -> MemoryIndex {
|
||||||
MemoryIndex::from_u32(self.import(module, name, EntityType::Memory(ty), def, |m| {
|
self.import(module, name, EntityType::Memory(ty), def, |m| {
|
||||||
&mut m.core_memories
|
&mut m.imported_memories
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn import(
|
fn import<K: EntityRef, V: From<CoreDef>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
module: &str,
|
module: &str,
|
||||||
name: &str,
|
name: &str,
|
||||||
ty: EntityType,
|
ty: EntityType,
|
||||||
def: CoreDef,
|
def: CoreDef,
|
||||||
new: impl FnOnce(&mut Self) -> &mut u32,
|
map: impl FnOnce(&mut Self) -> &mut PrimaryMap<K, V>,
|
||||||
) -> u32 {
|
) -> K {
|
||||||
if let Some(prev) = self.imported.get(&def) {
|
if let Some(prev) = self.imported.get(&def) {
|
||||||
return *prev;
|
return K::new(*prev);
|
||||||
}
|
}
|
||||||
let cnt = new(self);
|
let idx = map(self).push(def.clone().into());
|
||||||
*cnt += 1;
|
|
||||||
let ret = *cnt - 1;
|
|
||||||
self.core_imports.import(module, name, ty);
|
self.core_imports.import(module, name, ty);
|
||||||
self.imported.insert(def.clone(), ret);
|
self.imported.insert(def.clone(), idx.index());
|
||||||
if let EntityType::Memory(_) = ty {
|
|
||||||
self.imported_memories.push(def.clone());
|
|
||||||
}
|
|
||||||
self.imports.push(Import::CoreDef(def));
|
self.imports.push(Import::CoreDef(def));
|
||||||
ret
|
idx
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_transcoder(&mut self, transcoder: transcode::Transcoder) -> FuncIndex {
|
||||||
|
*self
|
||||||
|
.imported_transcoders
|
||||||
|
.entry(transcoder)
|
||||||
|
.or_insert_with(|| {
|
||||||
|
// Add the import to the core wasm import section...
|
||||||
|
let name = transcoder.name();
|
||||||
|
let ty = transcoder.ty(&mut self.core_types);
|
||||||
|
self.core_imports.import("transcode", &name, ty);
|
||||||
|
|
||||||
|
// ... and also record the metadata for what this import
|
||||||
|
// corresponds to.
|
||||||
|
let from = self.imported_memories[transcoder.from_memory].clone();
|
||||||
|
let to = self.imported_memories[transcoder.to_memory].clone();
|
||||||
|
self.imports.push(Import::Transcode {
|
||||||
|
op: transcoder.op,
|
||||||
|
from,
|
||||||
|
from64: transcoder.from_memory64,
|
||||||
|
to,
|
||||||
|
to64: transcoder.to_memory64,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.imported_funcs.push(None)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encodes this module into a WebAssembly binary.
|
/// Encodes this module into a WebAssembly binary.
|
||||||
pub fn encode(&mut self) -> Vec<u8> {
|
pub fn encode(&mut self) -> Vec<u8> {
|
||||||
let mut types = mem::take(&mut self.core_types);
|
// Build the function/export sections of the wasm module in a first pass
|
||||||
let mut transcoders = transcode::Transcoders::new(self.core_funcs);
|
// which will assign a final `FuncIndex` to all functions defined in
|
||||||
let mut adapter_funcs = Vec::new();
|
// `self.funcs`.
|
||||||
for adapter in self.adapters.iter() {
|
|
||||||
adapter_funcs.push(trampoline::compile(
|
|
||||||
self,
|
|
||||||
&mut types,
|
|
||||||
&mut transcoders,
|
|
||||||
adapter,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If any string transcoding imports were needed add imported items
|
|
||||||
// associated with them.
|
|
||||||
for (module, name, ty, transcoder) in transcoders.imports() {
|
|
||||||
self.core_imports.import(module, name, ty);
|
|
||||||
let from = self.imported_memories[transcoder.from_memory].clone();
|
|
||||||
let to = self.imported_memories[transcoder.to_memory].clone();
|
|
||||||
self.imports.push(Import::Transcode {
|
|
||||||
op: transcoder.op,
|
|
||||||
from,
|
|
||||||
from64: transcoder.from_memory64,
|
|
||||||
to,
|
|
||||||
to64: transcoder.to_memory64,
|
|
||||||
});
|
|
||||||
self.core_funcs += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now that all functions are known as well as all imports the actual
|
|
||||||
// bodies of all adapters are assembled into a final module.
|
|
||||||
let mut funcs = FunctionSection::new();
|
let mut funcs = FunctionSection::new();
|
||||||
let mut code = CodeSection::new();
|
|
||||||
let mut exports = ExportSection::new();
|
let mut exports = ExportSection::new();
|
||||||
let mut traps = traps::TrapSection::default();
|
let mut id_to_index = PrimaryMap::<FunctionId, FuncIndex>::new();
|
||||||
for (adapter, (function, func_traps)) in self.adapters.iter().zip(adapter_funcs) {
|
for (id, func) in self.funcs.iter() {
|
||||||
let idx = self.core_funcs + funcs.len();
|
assert!(func.filled_in);
|
||||||
exports.export(&adapter.name, ExportKind::Func, idx);
|
let idx = FuncIndex::from_u32(self.imported_funcs.next_key().as_u32() + id.as_u32());
|
||||||
|
let id2 = id_to_index.push(idx);
|
||||||
|
assert_eq!(id2, id);
|
||||||
|
|
||||||
let signature = self.signature(&adapter.lower, Context::Lower);
|
funcs.function(func.ty);
|
||||||
let ty = types.function(&signature.params, &signature.results);
|
|
||||||
funcs.function(ty);
|
|
||||||
|
|
||||||
code.raw(&function);
|
if let Some(name) = &func.export {
|
||||||
traps.append(idx, func_traps);
|
exports.export(name, ExportKind::Func, idx.as_u32());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.core_types = types;
|
|
||||||
|
// With all functions numbered the fragments of the body of each
|
||||||
|
// function can be assigned into one final adapter function.
|
||||||
|
let mut code = CodeSection::new();
|
||||||
|
let mut traps = traps::TrapSection::default();
|
||||||
|
for (id, func) in self.funcs.iter() {
|
||||||
|
let mut func_traps = Vec::new();
|
||||||
|
let mut body = Vec::new();
|
||||||
|
|
||||||
|
// Encode all locals used for this function
|
||||||
|
func.locals.len().encode(&mut body);
|
||||||
|
for (count, ty) in func.locals.iter() {
|
||||||
|
count.encode(&mut body);
|
||||||
|
ty.encode(&mut body);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then encode each "chunk" of a body which may have optional traps
|
||||||
|
// specified within it. Traps get offset by the current length of
|
||||||
|
// the body and otherwise our `Call` instructions are "relocated"
|
||||||
|
// here to the final function index.
|
||||||
|
for chunk in func.body.iter() {
|
||||||
|
match chunk {
|
||||||
|
Body::Raw(code, traps) => {
|
||||||
|
let start = body.len();
|
||||||
|
body.extend_from_slice(code);
|
||||||
|
for (offset, trap) in traps {
|
||||||
|
func_traps.push((start + offset, *trap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Body::Call(id) => {
|
||||||
|
Instruction::Call(id_to_index[*id].as_u32()).encode(&mut body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code.raw(&body);
|
||||||
|
traps.append(id_to_index[id].as_u32(), func_traps);
|
||||||
|
}
|
||||||
|
|
||||||
let traps = traps.finish();
|
let traps = traps.finish();
|
||||||
|
|
||||||
let mut result = wasm_encoder::Module::new();
|
let mut result = wasm_encoder::Module::new();
|
||||||
@@ -367,3 +434,82 @@ impl Options {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Temporary index which is not the same as `FuncIndex`.
|
||||||
|
///
|
||||||
|
/// This represents the nth generated function in the adapter module where the
|
||||||
|
/// final index of the function is not known at the time of generation since
|
||||||
|
/// more imports may be discovered (specifically string transcoders).
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
struct FunctionId(u32);
|
||||||
|
cranelift_entity::entity_impl!(FunctionId);
|
||||||
|
|
||||||
|
/// A generated function to be added to an adapter module.
|
||||||
|
///
|
||||||
|
/// At least one function is created per-adapter and dependeing on the type
|
||||||
|
/// hierarchy multiple functions may be generated per-adapter.
|
||||||
|
struct Function {
|
||||||
|
/// Whether or not the `body` has been finished.
|
||||||
|
///
|
||||||
|
/// Functions are added to a `Module` before they're defined so this is used
|
||||||
|
/// to assert that the function was in fact actually filled in by the
|
||||||
|
/// time we reach `Module::encode`.
|
||||||
|
filled_in: bool,
|
||||||
|
|
||||||
|
/// The type signature that this function has, as an index into the core
|
||||||
|
/// wasm type index space of the generated adapter module.
|
||||||
|
ty: u32,
|
||||||
|
|
||||||
|
/// The locals that are used by this function, organized by the number of
|
||||||
|
/// types of each local.
|
||||||
|
locals: Vec<(u32, ValType)>,
|
||||||
|
|
||||||
|
/// If specified, the export name of this function.
|
||||||
|
export: Option<String>,
|
||||||
|
|
||||||
|
/// The contents of the function.
|
||||||
|
///
|
||||||
|
/// See `Body` for more information, and the `Vec` here represents the
|
||||||
|
/// concatentation of all the `Body` fragments.
|
||||||
|
body: Vec<Body>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Representation of a fragment of the body of a core wasm function generated
|
||||||
|
/// for adapters.
|
||||||
|
///
|
||||||
|
/// This variant comes in one of two flavors:
|
||||||
|
///
|
||||||
|
/// 1. First a `Raw` variant is used to contain general instructions for the
|
||||||
|
/// wasm function. This is populated by `Compiler::instruction` primarily.
|
||||||
|
/// This also comes with a list of traps. and the byte offset within the
|
||||||
|
/// first vector of where the trap information applies to.
|
||||||
|
///
|
||||||
|
/// 2. A `Call` instruction variant for a `FunctionId` where the final
|
||||||
|
/// `FuncIndex` isn't known until emission time.
|
||||||
|
///
|
||||||
|
/// The purpose of this representation is the `Body::Call` variant. This can't
|
||||||
|
/// be encoded as an instruction when it's generated due to not knowing the
|
||||||
|
/// final index of the function being called. During `Module::encode`, however,
|
||||||
|
/// all indices are known and `Body::Call` is turned into a final
|
||||||
|
/// `Instruction::Call`.
|
||||||
|
///
|
||||||
|
/// One other possible representation in the future would be to encode a `Call`
|
||||||
|
/// instruction with a 5-byte leb to fill in later, but for now this felt
|
||||||
|
/// easier to represent. A 5-byte leb may be more efficient at compile-time if
|
||||||
|
/// necessary, however.
|
||||||
|
enum Body {
|
||||||
|
Raw(Vec<u8>, Vec<(usize, traps::Trap)>),
|
||||||
|
Call(FunctionId),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Function {
|
||||||
|
fn new(export: Option<String>, ty: u32) -> Function {
|
||||||
|
Function {
|
||||||
|
filled_in: false,
|
||||||
|
ty,
|
||||||
|
locals: Vec::new(),
|
||||||
|
export,
|
||||||
|
body: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
//! Size, align, and flattening information about component model types.
|
//! Size, align, and flattening information about component model types.
|
||||||
|
|
||||||
use crate::component::{InterfaceType, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS};
|
use crate::component::{ComponentTypes, InterfaceType, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS};
|
||||||
use crate::fact::{Context, Module, Options};
|
use crate::fact::{AdapterOptions, Context, Options};
|
||||||
use wasm_encoder::ValType;
|
use wasm_encoder::ValType;
|
||||||
|
use wasmtime_component_util::{DiscriminantSize, FlagsSize};
|
||||||
|
|
||||||
/// Metadata about a core wasm signature which is created for a component model
|
/// Metadata about a core wasm signature which is created for a component model
|
||||||
/// signature.
|
/// signature.
|
||||||
@@ -27,25 +28,25 @@ pub(crate) fn align_to(n: usize, align: usize) -> usize {
|
|||||||
(n + (align - 1)) & !(align - 1)
|
(n + (align - 1)) & !(align - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Module<'_> {
|
impl ComponentTypes {
|
||||||
/// Calculates the core wasm function signature for the component function
|
/// Calculates the core wasm function signature for the component function
|
||||||
/// type specified within `Context`.
|
/// type specified within `Context`.
|
||||||
///
|
///
|
||||||
/// This is used to generate the core wasm signatures for functions that are
|
/// This is used to generate the core wasm signatures for functions that are
|
||||||
/// imported (matching whatever was `canon lift`'d) and functions that are
|
/// imported (matching whatever was `canon lift`'d) and functions that are
|
||||||
/// exported (matching the generated function from `canon lower`).
|
/// exported (matching the generated function from `canon lower`).
|
||||||
pub(super) fn signature(&self, options: &Options, context: Context) -> Signature {
|
pub(super) fn signature(&self, options: &AdapterOptions, context: Context) -> Signature {
|
||||||
let ty = &self.types[options.ty];
|
let ty = &self[options.ty];
|
||||||
let ptr_ty = options.ptr();
|
let ptr_ty = options.options.ptr();
|
||||||
|
|
||||||
let mut params = self.flatten_types(options, ty.params.iter().map(|(_, ty)| *ty));
|
let mut params = self.flatten_types(&options.options, ty.params.iter().map(|(_, ty)| *ty));
|
||||||
let mut params_indirect = false;
|
let mut params_indirect = false;
|
||||||
if params.len() > MAX_FLAT_PARAMS {
|
if params.len() > MAX_FLAT_PARAMS {
|
||||||
params = vec![ptr_ty];
|
params = vec![ptr_ty];
|
||||||
params_indirect = true;
|
params_indirect = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut results = self.flatten_types(options, [ty.result]);
|
let mut results = self.flatten_types(&options.options, [ty.result]);
|
||||||
let mut results_indirect = false;
|
let mut results_indirect = false;
|
||||||
if results.len() > MAX_FLAT_RESULTS {
|
if results.len() > MAX_FLAT_RESULTS {
|
||||||
results_indirect = true;
|
results_indirect = true;
|
||||||
@@ -108,17 +109,17 @@ impl Module<'_> {
|
|||||||
dst.push(opts.ptr());
|
dst.push(opts.ptr());
|
||||||
}
|
}
|
||||||
InterfaceType::Record(r) => {
|
InterfaceType::Record(r) => {
|
||||||
for field in self.types[*r].fields.iter() {
|
for field in self[*r].fields.iter() {
|
||||||
self.push_flat(opts, &field.ty, dst);
|
self.push_flat(opts, &field.ty, dst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InterfaceType::Tuple(t) => {
|
InterfaceType::Tuple(t) => {
|
||||||
for ty in self.types[*t].types.iter() {
|
for ty in self[*t].types.iter() {
|
||||||
self.push_flat(opts, ty, dst);
|
self.push_flat(opts, ty, dst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InterfaceType::Flags(f) => {
|
InterfaceType::Flags(f) => {
|
||||||
let flags = &self.types[*f];
|
let flags = &self[*f];
|
||||||
let nflags = align_to(flags.names.len(), 32) / 32;
|
let nflags = align_to(flags.names.len(), 32) / 32;
|
||||||
for _ in 0..nflags {
|
for _ in 0..nflags {
|
||||||
dst.push(ValType::I32);
|
dst.push(ValType::I32);
|
||||||
@@ -127,13 +128,13 @@ impl Module<'_> {
|
|||||||
InterfaceType::Enum(_) => dst.push(ValType::I32),
|
InterfaceType::Enum(_) => dst.push(ValType::I32),
|
||||||
InterfaceType::Option(t) => {
|
InterfaceType::Option(t) => {
|
||||||
dst.push(ValType::I32);
|
dst.push(ValType::I32);
|
||||||
self.push_flat(opts, &self.types[*t], dst);
|
self.push_flat(opts, &self[*t], dst);
|
||||||
}
|
}
|
||||||
InterfaceType::Variant(t) => {
|
InterfaceType::Variant(t) => {
|
||||||
dst.push(ValType::I32);
|
dst.push(ValType::I32);
|
||||||
let pos = dst.len();
|
let pos = dst.len();
|
||||||
let mut tmp = Vec::new();
|
let mut tmp = Vec::new();
|
||||||
for case in self.types[*t].cases.iter() {
|
for case in self[*t].cases.iter() {
|
||||||
self.push_flat_variant(opts, &case.ty, pos, &mut tmp, dst);
|
self.push_flat_variant(opts, &case.ty, pos, &mut tmp, dst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,13 +142,13 @@ impl Module<'_> {
|
|||||||
dst.push(ValType::I32);
|
dst.push(ValType::I32);
|
||||||
let pos = dst.len();
|
let pos = dst.len();
|
||||||
let mut tmp = Vec::new();
|
let mut tmp = Vec::new();
|
||||||
for ty in self.types[*t].types.iter() {
|
for ty in self[*t].types.iter() {
|
||||||
self.push_flat_variant(opts, ty, pos, &mut tmp, dst);
|
self.push_flat_variant(opts, ty, pos, &mut tmp, dst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InterfaceType::Expected(t) => {
|
InterfaceType::Expected(t) => {
|
||||||
dst.push(ValType::I32);
|
dst.push(ValType::I32);
|
||||||
let e = &self.types[*t];
|
let e = &self[*t];
|
||||||
let pos = dst.len();
|
let pos = dst.len();
|
||||||
let mut tmp = Vec::new();
|
let mut tmp = Vec::new();
|
||||||
self.push_flat_variant(opts, &e.ok, pos, &mut tmp, dst);
|
self.push_flat_variant(opts, &e.ok, pos, &mut tmp, dst);
|
||||||
@@ -208,26 +209,26 @@ impl Module<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
InterfaceType::Record(r) => {
|
InterfaceType::Record(r) => {
|
||||||
self.record_size_align(opts, self.types[*r].fields.iter().map(|f| &f.ty))
|
self.record_size_align(opts, self[*r].fields.iter().map(|f| &f.ty))
|
||||||
}
|
}
|
||||||
InterfaceType::Tuple(t) => self.record_size_align(opts, self.types[*t].types.iter()),
|
InterfaceType::Tuple(t) => self.record_size_align(opts, self[*t].types.iter()),
|
||||||
InterfaceType::Flags(f) => match self.types[*f].names.len() {
|
InterfaceType::Flags(f) => match FlagsSize::from_count(self[*f].names.len()) {
|
||||||
n if n <= 8 => (1, 1),
|
FlagsSize::Size0 => (0, 1),
|
||||||
n if n <= 16 => (2, 2),
|
FlagsSize::Size1 => (1, 1),
|
||||||
n if n <= 32 => (4, 4),
|
FlagsSize::Size2 => (2, 2),
|
||||||
n => (4 * (align_to(n, 32) / 32), 4),
|
FlagsSize::Size4Plus(n) => (n * 4, 4),
|
||||||
},
|
},
|
||||||
InterfaceType::Enum(t) => self.discrim_size_align(self.types[*t].names.len()),
|
InterfaceType::Enum(t) => self.discrim_size_align(self[*t].names.len()),
|
||||||
InterfaceType::Option(t) => {
|
InterfaceType::Option(t) => {
|
||||||
let ty = &self.types[*t];
|
let ty = &self[*t];
|
||||||
self.variant_size_align(opts, [&InterfaceType::Unit, ty].into_iter())
|
self.variant_size_align(opts, [&InterfaceType::Unit, ty].into_iter())
|
||||||
}
|
}
|
||||||
InterfaceType::Variant(t) => {
|
InterfaceType::Variant(t) => {
|
||||||
self.variant_size_align(opts, self.types[*t].cases.iter().map(|c| &c.ty))
|
self.variant_size_align(opts, self[*t].cases.iter().map(|c| &c.ty))
|
||||||
}
|
}
|
||||||
InterfaceType::Union(t) => self.variant_size_align(opts, self.types[*t].types.iter()),
|
InterfaceType::Union(t) => self.variant_size_align(opts, self[*t].types.iter()),
|
||||||
InterfaceType::Expected(t) => {
|
InterfaceType::Expected(t) => {
|
||||||
let e = &self.types[*t];
|
let e = &self[*t];
|
||||||
self.variant_size_align(opts, [&e.ok, &e.err].into_iter())
|
self.variant_size_align(opts, [&e.ok, &e.err].into_iter())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,14 +261,18 @@ impl Module<'_> {
|
|||||||
payload_size = payload_size.max(csize);
|
payload_size = payload_size.max(csize);
|
||||||
align = align.max(calign);
|
align = align.max(calign);
|
||||||
}
|
}
|
||||||
(align_to(discrim_size, align) + payload_size, align)
|
(
|
||||||
|
align_to(align_to(discrim_size, align) + payload_size, align),
|
||||||
|
align,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn discrim_size_align<'a>(&self, cases: usize) -> (usize, usize) {
|
fn discrim_size_align<'a>(&self, cases: usize) -> (usize, usize) {
|
||||||
match cases {
|
match DiscriminantSize::from_count(cases) {
|
||||||
n if n <= u8::MAX as usize => (1, 1),
|
Some(DiscriminantSize::Size1) => (1, 1),
|
||||||
n if n <= u16::MAX as usize => (2, 2),
|
Some(DiscriminantSize::Size2) => (2, 2),
|
||||||
_ => (4, 4),
|
Some(DiscriminantSize::Size4) => (4, 4),
|
||||||
|
None => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,8 @@
|
|||||||
use crate::fact::core_types::CoreTypes;
|
use crate::fact::core_types::CoreTypes;
|
||||||
use crate::MemoryIndex;
|
use crate::MemoryIndex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
|
||||||
use wasm_encoder::{EntityType, ValType};
|
use wasm_encoder::{EntityType, ValType};
|
||||||
|
|
||||||
pub struct Transcoders {
|
|
||||||
imported: HashMap<Transcoder, u32>,
|
|
||||||
prev_func_imports: u32,
|
|
||||||
imports: Vec<(String, EntityType, Transcoder)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Hash, Eq, PartialEq)]
|
#[derive(Copy, Clone, Hash, Eq, PartialEq)]
|
||||||
pub struct Transcoder {
|
pub struct Transcoder {
|
||||||
pub from_memory: MemoryIndex,
|
pub from_memory: MemoryIndex,
|
||||||
@@ -46,33 +39,8 @@ pub enum FixedEncoding {
|
|||||||
Latin1,
|
Latin1,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Transcoders {
|
|
||||||
pub fn new(prev_func_imports: u32) -> Transcoders {
|
|
||||||
Transcoders {
|
|
||||||
imported: HashMap::new(),
|
|
||||||
prev_func_imports,
|
|
||||||
imports: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn import(&mut self, types: &mut CoreTypes, transcoder: Transcoder) -> u32 {
|
|
||||||
*self.imported.entry(transcoder).or_insert_with(|| {
|
|
||||||
let idx = self.prev_func_imports + (self.imports.len() as u32);
|
|
||||||
self.imports
|
|
||||||
.push((transcoder.name(), transcoder.ty(types), transcoder));
|
|
||||||
idx
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn imports(&self) -> impl Iterator<Item = (&str, &str, EntityType, &Transcoder)> {
|
|
||||||
self.imports
|
|
||||||
.iter()
|
|
||||||
.map(|(name, ty, transcoder)| ("transcode", &name[..], *ty, transcoder))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Transcoder {
|
impl Transcoder {
|
||||||
fn name(&self) -> String {
|
pub fn name(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"{} (mem{} => mem{})",
|
"{} (mem{} => mem{})",
|
||||||
self.op.desc(),
|
self.op.desc(),
|
||||||
@@ -81,7 +49,7 @@ impl Transcoder {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ty(&self, types: &mut CoreTypes) -> EntityType {
|
pub fn ty(&self, types: &mut CoreTypes) -> EntityType {
|
||||||
let from_ptr = if self.from_memory64 {
|
let from_ptr = if self.from_memory64 {
|
||||||
ValType::I64
|
ValType::I64
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
use arbitrary::{Arbitrary, Unstructured};
|
use arbitrary::{Arbitrary, Unstructured};
|
||||||
use component_fuzz_util::{Declarations, EXPORT_FUNCTION, IMPORT_FUNCTION};
|
use component_fuzz_util::{Declarations, EXPORT_FUNCTION, IMPORT_FUNCTION};
|
||||||
|
use std::any::Any;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::ops::ControlFlow;
|
use std::ops::ControlFlow;
|
||||||
use wasmtime::component::{self, Component, Lift, Linker, Lower, Val};
|
use wasmtime::component::{self, Component, Lift, Linker, Lower, Val};
|
||||||
@@ -141,25 +142,29 @@ macro_rules! define_static_api_test {
|
|||||||
let mut config = Config::new();
|
let mut config = Config::new();
|
||||||
config.wasm_component_model(true);
|
config.wasm_component_model(true);
|
||||||
let engine = Engine::new(&config).unwrap();
|
let engine = Engine::new(&config).unwrap();
|
||||||
let component = Component::new(
|
let wat = declarations.make_component();
|
||||||
&engine,
|
let wat = wat.as_bytes();
|
||||||
declarations.make_component().as_bytes()
|
crate::oracles::log_wasm(wat);
|
||||||
).unwrap();
|
let component = Component::new(&engine, wat).unwrap();
|
||||||
let mut linker = Linker::new(&engine);
|
let mut linker = Linker::new(&engine);
|
||||||
linker
|
linker
|
||||||
.root()
|
.root()
|
||||||
.func_wrap(
|
.func_wrap(
|
||||||
IMPORT_FUNCTION,
|
IMPORT_FUNCTION,
|
||||||
|cx: StoreContextMut<'_, ($(Option<$param>,)* Option<R>)>,
|
|cx: StoreContextMut<'_, Box<dyn Any>>,
|
||||||
$($param_name: $param,)*|
|
$($param_name: $param,)*|
|
||||||
{
|
{
|
||||||
let ($($param_expected_name,)* result) = cx.data();
|
log::trace!("received parameters {:?}", ($(&$param_name,)*));
|
||||||
$(assert_eq!($param_name, *$param_expected_name.as_ref().unwrap());)*
|
let data: &($($param,)* R,) =
|
||||||
Ok(result.as_ref().unwrap().clone())
|
cx.data().downcast_ref().unwrap();
|
||||||
|
let ($($param_expected_name,)* result,) = data;
|
||||||
|
$(assert_eq!($param_name, *$param_expected_name);)*
|
||||||
|
log::trace!("returning result {:?}", result);
|
||||||
|
Ok(result.clone())
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut store = Store::new(&engine, Default::default());
|
let mut store: Store<Box<dyn Any>> = Store::new(&engine, Box::new(()));
|
||||||
let instance = linker.instantiate(&mut store, &component).unwrap();
|
let instance = linker.instantiate(&mut store, &component).unwrap();
|
||||||
let func = instance
|
let func = instance
|
||||||
.get_typed_func::<($($param,)*), R, _>(&mut store, EXPORT_FUNCTION)
|
.get_typed_func::<($($param,)*), R, _>(&mut store, EXPORT_FUNCTION)
|
||||||
@@ -168,9 +173,17 @@ macro_rules! define_static_api_test {
|
|||||||
while input.arbitrary()? {
|
while input.arbitrary()? {
|
||||||
$(let $param_name = input.arbitrary::<$param>()?;)*
|
$(let $param_name = input.arbitrary::<$param>()?;)*
|
||||||
let result = input.arbitrary::<R>()?;
|
let result = input.arbitrary::<R>()?;
|
||||||
*store.data_mut() = ($(Some($param_name.clone()),)* Some(result.clone()));
|
*store.data_mut() = Box::new((
|
||||||
|
$($param_name.clone(),)*
|
||||||
assert_eq!(func.call(&mut store, ($($param_name,)*)).unwrap(), result);
|
result.clone(),
|
||||||
|
));
|
||||||
|
log::trace!(
|
||||||
|
"passing in parameters {:?}",
|
||||||
|
($(&$param_name,)*),
|
||||||
|
);
|
||||||
|
let actual = func.call(&mut store, ($($param_name,)*)).unwrap();
|
||||||
|
log::trace!("got result {:?}", actual);
|
||||||
|
assert_eq!(actual, result);
|
||||||
func.post_return(&mut store).unwrap();
|
func.post_return(&mut store).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1089,20 +1089,25 @@ pub fn dynamic_component_api_target(input: &mut arbitrary::Unstructured) -> arbi
|
|||||||
|
|
||||||
let engine = component_test_util::engine();
|
let engine = component_test_util::engine();
|
||||||
let mut store = Store::new(&engine, (Box::new([]) as Box<[Val]>, None));
|
let mut store = Store::new(&engine, (Box::new([]) as Box<[Val]>, None));
|
||||||
let component =
|
let wat = case.declarations().make_component();
|
||||||
Component::new(&engine, case.declarations().make_component().as_bytes()).unwrap();
|
let wat = wat.as_bytes();
|
||||||
|
log_wasm(wat);
|
||||||
|
let component = Component::new(&engine, wat).unwrap();
|
||||||
let mut linker = Linker::new(&engine);
|
let mut linker = Linker::new(&engine);
|
||||||
|
|
||||||
linker
|
linker
|
||||||
.root()
|
.root()
|
||||||
.func_new(&component, IMPORT_FUNCTION, {
|
.func_new(&component, IMPORT_FUNCTION, {
|
||||||
move |cx: StoreContextMut<'_, (Box<[Val]>, Option<Val>)>, args: &[Val]| -> Result<Val> {
|
move |cx: StoreContextMut<'_, (Box<[Val]>, Option<Val>)>, args: &[Val]| -> Result<Val> {
|
||||||
|
log::trace!("received arguments {args:?}");
|
||||||
let (expected_args, result) = cx.data();
|
let (expected_args, result) = cx.data();
|
||||||
assert_eq!(args.len(), expected_args.len());
|
assert_eq!(args.len(), expected_args.len());
|
||||||
for (expected, actual) in expected_args.iter().zip(args) {
|
for (expected, actual) in expected_args.iter().zip(args) {
|
||||||
assert_eq!(expected, actual);
|
assert_eq!(expected, actual);
|
||||||
}
|
}
|
||||||
Ok(result.as_ref().unwrap().clone())
|
let result = result.as_ref().unwrap().clone();
|
||||||
|
log::trace!("returning result {result:?}");
|
||||||
|
Ok(result)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -1122,10 +1127,10 @@ pub fn dynamic_component_api_target(input: &mut arbitrary::Unstructured) -> arbi
|
|||||||
|
|
||||||
*store.data_mut() = (args.clone(), Some(result.clone()));
|
*store.data_mut() = (args.clone(), Some(result.clone()));
|
||||||
|
|
||||||
assert_eq!(
|
log::trace!("passing args {args:?}");
|
||||||
func.call_and_post_return(&mut store, &args).unwrap(),
|
let actual = func.call_and_post_return(&mut store, &args).unwrap();
|
||||||
result
|
log::trace!("received return {actual:?}");
|
||||||
);
|
assert_eq!(actual, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
|
|
||||||
use arbitrary::{Arbitrary, Unstructured};
|
use arbitrary::{Arbitrary, Unstructured};
|
||||||
use proc_macro2::{Ident, TokenStream};
|
use proc_macro2::{Ident, TokenStream};
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote, ToTokens};
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::fmt::{self, Debug, Write};
|
use std::fmt::{self, Debug, Write};
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
@@ -328,7 +329,7 @@ fn variant_size_and_alignment<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_import_and_export(params: &[Type], result: &Type) -> Box<str> {
|
fn make_import_and_export(params: &[Type], result: &Type) -> String {
|
||||||
let params_lowered = params
|
let params_lowered = params
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|ty| ty.lowered())
|
.flat_map(|ty| ty.lowered())
|
||||||
@@ -400,7 +401,6 @@ fn make_import_and_export(params: &[Type], result: &Type) -> Box<str> {
|
|||||||
)"#
|
)"#
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_rust_name(name_counter: &mut u32) -> Ident {
|
fn make_rust_name(name_counter: &mut u32) -> Ident {
|
||||||
@@ -509,7 +509,7 @@ pub fn rust_type(ty: &Type, name_counter: &mut u32, declarations: &mut TokenStre
|
|||||||
let name = make_rust_name(name_counter);
|
let name = make_rust_name(name_counter);
|
||||||
|
|
||||||
declarations.extend(quote! {
|
declarations.extend(quote! {
|
||||||
#[derive(ComponentType, Lift, Lower, PartialEq, Debug, Clone, Arbitrary)]
|
#[derive(ComponentType, Lift, Lower, PartialEq, Debug, Copy, Clone, Arbitrary)]
|
||||||
#[component(enum)]
|
#[component(enum)]
|
||||||
enum #name {
|
enum #name {
|
||||||
#cases
|
#cases
|
||||||
@@ -677,13 +677,17 @@ fn write_component_type(
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Declarations {
|
pub struct Declarations {
|
||||||
/// Type declarations (if any) referenced by `params` and/or `result`
|
/// Type declarations (if any) referenced by `params` and/or `result`
|
||||||
pub types: Box<str>,
|
pub types: Cow<'static, str>,
|
||||||
/// Parameter declarations used for the imported and exported functions
|
/// Parameter declarations used for the imported and exported functions
|
||||||
pub params: Box<str>,
|
pub params: Cow<'static, str>,
|
||||||
/// Result declaration used for the imported and exported functions
|
/// Result declaration used for the imported and exported functions
|
||||||
pub result: Box<str>,
|
pub result: Cow<'static, str>,
|
||||||
/// A WAT fragment representing the core function import and export to use for testing
|
/// A WAT fragment representing the core function import and export to use for testing
|
||||||
pub import_and_export: Box<str>,
|
pub import_and_export: Cow<'static, str>,
|
||||||
|
/// String encoding to use for host -> component
|
||||||
|
pub encoding1: StringEncoding,
|
||||||
|
/// String encoding to use for component -> host
|
||||||
|
pub encoding2: StringEncoding,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Declarations {
|
impl Declarations {
|
||||||
@@ -694,7 +698,44 @@ impl Declarations {
|
|||||||
params,
|
params,
|
||||||
result,
|
result,
|
||||||
import_and_export,
|
import_and_export,
|
||||||
|
encoding1,
|
||||||
|
encoding2,
|
||||||
} = self;
|
} = self;
|
||||||
|
let mk_component = |name: &str, encoding: StringEncoding| {
|
||||||
|
format!(
|
||||||
|
r#"
|
||||||
|
(component ${name}
|
||||||
|
(import "echo" (func $f (type $sig)))
|
||||||
|
|
||||||
|
(core instance $libc (instantiate $libc))
|
||||||
|
|
||||||
|
(core func $f_lower (canon lower
|
||||||
|
(func $f)
|
||||||
|
(memory $libc "memory")
|
||||||
|
(realloc (func $libc "realloc"))
|
||||||
|
string-encoding={encoding}
|
||||||
|
))
|
||||||
|
|
||||||
|
(core instance $i (instantiate $m
|
||||||
|
(with "libc" (instance $libc))
|
||||||
|
(with "host" (instance (export "{IMPORT_FUNCTION}" (func $f_lower))))
|
||||||
|
))
|
||||||
|
|
||||||
|
(func (export "echo") (type $sig)
|
||||||
|
(canon lift
|
||||||
|
(core func $i "echo")
|
||||||
|
(memory $libc "memory")
|
||||||
|
(realloc (func $libc "realloc"))
|
||||||
|
string-encoding={encoding}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let c1 = mk_component("c1", *encoding2);
|
||||||
|
let c2 = mk_component("c2", *encoding1);
|
||||||
|
|
||||||
format!(
|
format!(
|
||||||
r#"
|
r#"
|
||||||
@@ -704,18 +745,6 @@ impl Declarations {
|
|||||||
{REALLOC_AND_FREE}
|
{REALLOC_AND_FREE}
|
||||||
)
|
)
|
||||||
|
|
||||||
(core instance $libc (instantiate $libc))
|
|
||||||
|
|
||||||
{types}
|
|
||||||
|
|
||||||
(import "{IMPORT_FUNCTION}" (func $f {params} {result}))
|
|
||||||
|
|
||||||
(core func $f_lower (canon lower
|
|
||||||
(func $f)
|
|
||||||
(memory $libc "memory")
|
|
||||||
(realloc (func $libc "realloc"))
|
|
||||||
))
|
|
||||||
|
|
||||||
(core module $m
|
(core module $m
|
||||||
(memory (import "libc" "memory") 1)
|
(memory (import "libc" "memory") 1)
|
||||||
(func $realloc (import "libc" "realloc") (param i32 i32 i32 i32) (result i32))
|
(func $realloc (import "libc" "realloc") (param i32 i32 i32 i32) (result i32))
|
||||||
@@ -723,18 +752,16 @@ impl Declarations {
|
|||||||
{import_and_export}
|
{import_and_export}
|
||||||
)
|
)
|
||||||
|
|
||||||
(core instance $i (instantiate $m
|
{types}
|
||||||
(with "libc" (instance $libc))
|
|
||||||
(with "host" (instance (export "{IMPORT_FUNCTION}" (func $f_lower))))
|
|
||||||
))
|
|
||||||
|
|
||||||
(func (export "echo") {params} {result}
|
(type $sig (func {params} {result}))
|
||||||
(canon lift
|
(import "{IMPORT_FUNCTION}" (func $f (type $sig)))
|
||||||
(core func $i "echo")
|
|
||||||
(memory $libc "memory")
|
{c1}
|
||||||
(realloc (func $libc "realloc"))
|
{c2}
|
||||||
)
|
(instance $c1 (instantiate $c1 (with "echo" (func $f))))
|
||||||
)
|
(instance $c2 (instantiate $c2 (with "echo" (func $c1 "echo"))))
|
||||||
|
(export "echo" (func $c2 "echo"))
|
||||||
)"#,
|
)"#,
|
||||||
)
|
)
|
||||||
.into()
|
.into()
|
||||||
@@ -748,6 +775,10 @@ pub struct TestCase {
|
|||||||
pub params: Box<[Type]>,
|
pub params: Box<[Type]>,
|
||||||
/// The type of the result to be returned by the function
|
/// The type of the result to be returned by the function
|
||||||
pub result: Type,
|
pub result: Type,
|
||||||
|
/// String encoding to use from host-to-component.
|
||||||
|
pub encoding1: StringEncoding,
|
||||||
|
/// String encoding to use from component-to-host.
|
||||||
|
pub encoding2: StringEncoding,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestCase {
|
impl TestCase {
|
||||||
@@ -781,7 +812,9 @@ impl TestCase {
|
|||||||
types: types.into(),
|
types: types.into(),
|
||||||
params,
|
params,
|
||||||
result,
|
result,
|
||||||
import_and_export,
|
import_and_export: import_and_export.into(),
|
||||||
|
encoding1: self.encoding1,
|
||||||
|
encoding2: self.encoding2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -795,6 +828,36 @@ impl<'a> Arbitrary<'a> for TestCase {
|
|||||||
.take(MAX_ARITY)
|
.take(MAX_ARITY)
|
||||||
.collect::<arbitrary::Result<Box<[_]>>>()?,
|
.collect::<arbitrary::Result<Box<[_]>>>()?,
|
||||||
result: input.arbitrary()?,
|
result: input.arbitrary()?,
|
||||||
|
encoding1: input.arbitrary()?,
|
||||||
|
encoding2: input.arbitrary()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Arbitrary)]
|
||||||
|
pub enum StringEncoding {
|
||||||
|
Utf8,
|
||||||
|
Utf16,
|
||||||
|
Latin1OrUtf16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for StringEncoding {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
StringEncoding::Utf8 => fmt::Display::fmt(&"utf8", f),
|
||||||
|
StringEncoding::Utf16 => fmt::Display::fmt(&"utf16", f),
|
||||||
|
StringEncoding::Latin1OrUtf16 => fmt::Display::fmt(&"latin1+utf16", f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for StringEncoding {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let me = match self {
|
||||||
|
StringEncoding::Utf8 => quote!(Utf8),
|
||||||
|
StringEncoding::Utf16 => quote!(Utf16),
|
||||||
|
StringEncoding::Latin1OrUtf16 => quote!(Latin1OrUtf16),
|
||||||
|
};
|
||||||
|
tokens.extend(quote!(component_fuzz_util::StringEncoding::#me));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ use crate::store::StoreOpaque;
|
|||||||
use crate::{AsContextMut, StoreContextMut, ValRaw};
|
use crate::{AsContextMut, StoreContextMut, ValRaw};
|
||||||
use anyhow::{anyhow, bail, Context, Error, Result};
|
use anyhow::{anyhow, bail, Context, Error, Result};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::fmt;
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use wasmtime_component_util::{DiscriminantSize, FlagsSize};
|
use wasmtime_component_util::{DiscriminantSize, FlagsSize};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(PartialEq, Eq, Clone)]
|
||||||
pub struct List {
|
pub struct List {
|
||||||
ty: types::List,
|
ty: types::List,
|
||||||
values: Box<[Val]>,
|
values: Box<[Val]>,
|
||||||
@@ -45,7 +46,17 @@ impl Deref for List {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
impl fmt::Debug for List {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let mut f = f.debug_list();
|
||||||
|
for val in self.iter() {
|
||||||
|
f.entry(val);
|
||||||
|
}
|
||||||
|
f.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone)]
|
||||||
pub struct Record {
|
pub struct Record {
|
||||||
ty: types::Record,
|
ty: types::Record,
|
||||||
values: Box<[Val]>,
|
values: Box<[Val]>,
|
||||||
@@ -105,6 +116,16 @@ impl Record {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Record {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let mut f = f.debug_struct("Record");
|
||||||
|
for (name, val) in self.fields() {
|
||||||
|
f.field(name, val);
|
||||||
|
}
|
||||||
|
f.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Tuple {
|
pub struct Tuple {
|
||||||
ty: types::Tuple,
|
ty: types::Tuple,
|
||||||
@@ -144,7 +165,7 @@ impl Tuple {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(PartialEq, Eq, Clone)]
|
||||||
pub struct Variant {
|
pub struct Variant {
|
||||||
ty: types::Variant,
|
ty: types::Variant,
|
||||||
discriminant: u32,
|
discriminant: u32,
|
||||||
@@ -197,6 +218,14 @@ impl Variant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Variant {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_tuple(self.discriminant())
|
||||||
|
.field(self.payload())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct Enum {
|
pub struct Enum {
|
||||||
ty: types::Enum,
|
ty: types::Enum,
|
||||||
@@ -273,7 +302,7 @@ impl Union {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(PartialEq, Eq, Clone)]
|
||||||
pub struct Option {
|
pub struct Option {
|
||||||
ty: types::Option,
|
ty: types::Option,
|
||||||
discriminant: u32,
|
discriminant: u32,
|
||||||
@@ -313,7 +342,13 @@ impl Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
impl fmt::Debug for Option {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
self.value().fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone)]
|
||||||
pub struct Expected {
|
pub struct Expected {
|
||||||
ty: types::Expected,
|
ty: types::Expected,
|
||||||
discriminant: u32,
|
discriminant: u32,
|
||||||
@@ -358,7 +393,13 @@ impl Expected {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
impl fmt::Debug for Expected {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
self.value().fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone)]
|
||||||
pub struct Flags {
|
pub struct Flags {
|
||||||
ty: types::Flags,
|
ty: types::Flags,
|
||||||
count: u32,
|
count: u32,
|
||||||
@@ -408,6 +449,16 @@ impl Flags {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Flags {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let mut set = f.debug_set();
|
||||||
|
for flag in self.flags() {
|
||||||
|
set.entry(&flag);
|
||||||
|
}
|
||||||
|
set.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents possible runtime values which a component function can either consume or produce
|
/// Represents possible runtime values which a component function can either consume or produce
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub enum Val {
|
pub enum Val {
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ mod component {
|
|||||||
params,
|
params,
|
||||||
result,
|
result,
|
||||||
import_and_export,
|
import_and_export,
|
||||||
|
encoding1,
|
||||||
|
encoding2,
|
||||||
} = case.declarations();
|
} = case.declarations();
|
||||||
|
|
||||||
let test = format_ident!("static_api_test{}", case.params.len());
|
let test = format_ident!("static_api_test{}", case.params.len());
|
||||||
@@ -95,11 +97,16 @@ mod component {
|
|||||||
|
|
||||||
let test = quote!(#index => component_types::#test::<#rust_params #rust_result>(
|
let test = quote!(#index => component_types::#test::<#rust_params #rust_result>(
|
||||||
input,
|
input,
|
||||||
&Declarations {
|
{
|
||||||
types: #types.into(),
|
static DECLS: Declarations = Declarations {
|
||||||
params: #params.into(),
|
types: Cow::Borrowed(#types),
|
||||||
result: #result.into(),
|
params: Cow::Borrowed(#params),
|
||||||
import_and_export: #import_and_export.into()
|
result: Cow::Borrowed(#result),
|
||||||
|
import_and_export: Cow::Borrowed(#import_and_export),
|
||||||
|
encoding1: #encoding1,
|
||||||
|
encoding2: #encoding2,
|
||||||
|
};
|
||||||
|
&DECLS
|
||||||
}
|
}
|
||||||
),);
|
),);
|
||||||
|
|
||||||
@@ -116,6 +123,7 @@ mod component {
|
|||||||
use std::sync::{Arc, Once};
|
use std::sync::{Arc, Once};
|
||||||
use wasmtime::component::{ComponentType, Lift, Lower};
|
use wasmtime::component::{ComponentType, Lift, Lower};
|
||||||
use wasmtime_fuzzing::generators::component_types;
|
use wasmtime_fuzzing::generators::component_types;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
const SEED: u64 = #seed;
|
const SEED: u64 = #seed;
|
||||||
|
|
||||||
|
|||||||
@@ -925,7 +925,7 @@
|
|||||||
(i32.eqz (local.get 0))
|
(i32.eqz (local.get 0))
|
||||||
if
|
if
|
||||||
(if (i32.ne (local.get 1) (i32.const 0)) (unreachable))
|
(if (i32.ne (local.get 1) (i32.const 0)) (unreachable))
|
||||||
(if (f64.ne (f64.reinterpret_i64 (local.get 2)) (f64.const 8)) (unreachable))
|
(if (f32.ne (f32.reinterpret_i32 (i32.wrap_i64 (local.get 2))) (f32.const 8)) (unreachable))
|
||||||
else
|
else
|
||||||
(if (i32.ne (local.get 1) (i32.const 1)) (unreachable))
|
(if (i32.ne (local.get 1) (i32.const 1)) (unreachable))
|
||||||
(if (f64.ne (f64.reinterpret_i64 (local.get 2)) (f64.const 9)) (unreachable))
|
(if (f64.ne (f64.reinterpret_i64 (local.get 2)) (f64.const 9)) (unreachable))
|
||||||
@@ -935,7 +935,7 @@
|
|||||||
(i32.eqz (local.get 0))
|
(i32.eqz (local.get 0))
|
||||||
if
|
if
|
||||||
(if (i32.ne (local.get 1) (i32.const 0)) (unreachable))
|
(if (i32.ne (local.get 1) (i32.const 0)) (unreachable))
|
||||||
(if (f64.ne (f64.reinterpret_i64 (local.get 2)) (f64.const 10)) (unreachable))
|
(if (f32.ne (f32.reinterpret_i32 (i32.wrap_i64 (local.get 2))) (f32.const 10)) (unreachable))
|
||||||
else
|
else
|
||||||
(if (i32.ne (local.get 1) (i32.const 1)) (unreachable))
|
(if (i32.ne (local.get 1) (i32.const 1)) (unreachable))
|
||||||
(if (i64.ne (local.get 2) (i64.const 11)) (unreachable))
|
(if (i64.ne (local.get 2) (i64.const 11)) (unreachable))
|
||||||
@@ -983,10 +983,10 @@
|
|||||||
(call $c (i32.const 0) (i32.const 0) (i64.const 6))
|
(call $c (i32.const 0) (i32.const 0) (i64.const 6))
|
||||||
(call $c (i32.const 1) (i32.const 1) (i64.reinterpret_f64 (f64.const 7)))
|
(call $c (i32.const 1) (i32.const 1) (i64.reinterpret_f64 (f64.const 7)))
|
||||||
|
|
||||||
(call $d (i32.const 0) (i32.const 0) (i64.reinterpret_f64 (f64.const 8)))
|
(call $d (i32.const 0) (i32.const 0) (i64.extend_i32_u (i32.reinterpret_f32 (f32.const 8))))
|
||||||
(call $d (i32.const 1) (i32.const 1) (i64.reinterpret_f64 (f64.const 9)))
|
(call $d (i32.const 1) (i32.const 1) (i64.reinterpret_f64 (f64.const 9)))
|
||||||
|
|
||||||
(call $e (i32.const 0) (i32.const 0) (i64.reinterpret_f64 (f64.const 10)))
|
(call $e (i32.const 0) (i32.const 0) (i64.extend_i32_u (i32.reinterpret_f32 (f32.const 10))))
|
||||||
(call $e (i32.const 1) (i32.const 1) (i64.const 11))
|
(call $e (i32.const 1) (i32.const 1) (i64.const 11))
|
||||||
)
|
)
|
||||||
(start $start)
|
(start $start)
|
||||||
|
|||||||
Reference in New Issue
Block a user