Handle select relocations while generating trampolines (#1347)

* Handle select relocations while generating trampolines

Trampoline generation for all function signatures exposed a preexisting
bug in wasmtime where trampoline generation occasionally does have
relocations, but it's asserted that trampolines don't generate
relocations, causing a panic. The relocation is currently primarily the
probestack function which happens when functions might have a huge
number of parameters, but not so huge as to blow the wasmparser limit of
how many parameters are allowed.

This commit fixes the issue by handling relocations for trampolines in
the same manner as the rest of the code. Note that dynamically-generated
trampolines via the `Func` API still panic if they have too many
arguments and generate a relocation, but it seems like we can try to fix
that later if the need truly arises.

Closes #1322

* Log trampoline relocations
This commit is contained in:
Alex Crichton
2020-03-17 16:30:21 -05:00
committed by GitHub
parent 4c3c717698
commit ba0dc40b2b
8 changed files with 310 additions and 154 deletions

1
Cargo.lock generated
View File

@@ -2513,6 +2513,7 @@ dependencies = [
"cranelift-frontend", "cranelift-frontend",
"cranelift-native", "cranelift-native",
"cranelift-wasm", "cranelift-wasm",
"log",
"more-asserts", "more-asserts",
"region", "region",
"target-lexicon", "target-lexicon",

View File

@@ -289,13 +289,14 @@ pub fn create_handle_with_function(
// ... and then we also need a trampoline with the standard "trampoline ABI" // ... and then we also need a trampoline with the standard "trampoline ABI"
// which enters into the ABI specified by `ft`. Note that this is only used // which enters into the ABI specified by `ft`. Note that this is only used
// if `Func::call` is called on an object created by `Func::new`. // if `Func::call` is called on an object created by `Func::new`.
let trampoline = wasmtime_jit::make_trampoline( let (trampoline, relocations) = wasmtime_jit::make_trampoline(
&*isa, &*isa,
&mut code_memory, &mut code_memory,
&mut fn_builder_ctx, &mut fn_builder_ctx,
&sig, &sig,
mem::size_of::<u128>(), mem::size_of::<u128>(),
)?; )?;
assert!(relocations.is_empty());
let sig_id = store.compiler().signatures().register(&sig); let sig_id = store.compiler().signatures().register(&sig);
trampolines.insert(sig_id, trampoline); trampolines.insert(sig_id, trampoline);

View File

@@ -27,6 +27,7 @@ wasmparser = "0.51.2"
more-asserts = "0.2.1" more-asserts = "0.2.1"
anyhow = "1.0" anyhow = "1.0"
cfg-if = "0.1.9" cfg-if = "0.1.9"
log = "0.4"
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3.7", features = ["winnt", "impl-default"] } winapi = { version = "0.3.7", features = ["winnt", "impl-default"] }

View File

@@ -3,6 +3,7 @@
use crate::code_memory::CodeMemory; use crate::code_memory::CodeMemory;
use crate::instantiate::SetupError; use crate::instantiate::SetupError;
use crate::target_tunables::target_tunables; use crate::target_tunables::target_tunables;
use cranelift_codegen::ir::ExternalName;
use cranelift_codegen::ir::InstBuilder; use cranelift_codegen::ir::InstBuilder;
use cranelift_codegen::print_errors::pretty_error; use cranelift_codegen::print_errors::pretty_error;
use cranelift_codegen::Context; use cranelift_codegen::Context;
@@ -15,10 +16,11 @@ use wasmtime_debug::{emit_debugsections_image, DebugInfoData};
use wasmtime_environ::entity::{EntityRef, PrimaryMap}; use wasmtime_environ::entity::{EntityRef, PrimaryMap};
use wasmtime_environ::isa::{TargetFrontendConfig, TargetIsa}; use wasmtime_environ::isa::{TargetFrontendConfig, TargetIsa};
use wasmtime_environ::wasm::{DefinedFuncIndex, DefinedMemoryIndex, MemoryIndex}; use wasmtime_environ::wasm::{DefinedFuncIndex, DefinedMemoryIndex, MemoryIndex};
use wasmtime_environ::RelocationTarget;
use wasmtime_environ::{ use wasmtime_environ::{
CacheConfig, Compilation, CompileError, CompiledFunction, CompiledFunctionUnwindInfo, CacheConfig, CompileError, CompiledFunction, CompiledFunctionUnwindInfo, Compiler as _C,
Compiler as _C, FunctionBodyData, Module, ModuleMemoryOffset, ModuleVmctxInfo, Relocations, FunctionBodyData, Module, ModuleMemoryOffset, ModuleVmctxInfo, Relocation, Relocations, Traps,
Traps, Tunables, VMOffsets, Tunables, VMOffsets,
}; };
use wasmtime_profiling::ProfilingAgent; use wasmtime_profiling::ProfilingAgent;
use wasmtime_runtime::{ use wasmtime_runtime::{
@@ -76,6 +78,17 @@ impl Compiler {
} }
} }
#[allow(missing_docs)]
pub struct Compilation {
pub finished_functions: PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
pub relocations: Relocations,
pub trampolines: HashMap<VMSharedSignatureIndex, VMTrampoline>,
pub trampoline_relocations: HashMap<VMSharedSignatureIndex, Vec<Relocation>>,
pub jt_offsets: PrimaryMap<DefinedFuncIndex, ir::JumpTableOffsets>,
pub dbg_image: Option<Vec<u8>>,
pub trap_registration: TrapRegistration,
}
impl Compiler { impl Compiler {
/// Return the target's frontend configuration settings. /// Return the target's frontend configuration settings.
pub fn frontend_config(&self) -> TargetFrontendConfig { pub fn frontend_config(&self) -> TargetFrontendConfig {
@@ -94,17 +107,7 @@ impl Compiler {
module_translation: &ModuleTranslationState, module_translation: &ModuleTranslationState,
function_body_inputs: PrimaryMap<DefinedFuncIndex, FunctionBodyData<'data>>, function_body_inputs: PrimaryMap<DefinedFuncIndex, FunctionBodyData<'data>>,
debug_data: Option<DebugInfoData>, debug_data: Option<DebugInfoData>,
) -> Result< ) -> Result<Compilation, SetupError> {
(
PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
HashMap<VMSharedSignatureIndex, VMTrampoline>,
PrimaryMap<DefinedFuncIndex, ir::JumpTableOffsets>,
Relocations,
Option<Vec<u8>>,
TrapRegistration,
),
SetupError,
> {
let ( let (
compilation, compilation,
relocations, relocations,
@@ -142,7 +145,7 @@ impl Compiler {
// Allocate all of the compiled functions into executable memory, // Allocate all of the compiled functions into executable memory,
// copying over their contents. // copying over their contents.
let allocated_functions = let finished_functions =
allocate_functions(&mut self.code_memory, &compilation).map_err(|message| { allocate_functions(&mut self.code_memory, &compilation).map_err(|message| {
SetupError::Instantiate(InstantiationError::Resource(format!( SetupError::Instantiate(InstantiationError::Resource(format!(
"failed to allocate memory for functions: {}", "failed to allocate memory for functions: {}",
@@ -153,7 +156,7 @@ impl Compiler {
// Create a registration value for all traps in our allocated // Create a registration value for all traps in our allocated
// functions. This registration will allow us to map a trapping PC // functions. This registration will allow us to map a trapping PC
// value to what the trap actually means if it came from JIT code. // value to what the trap actually means if it came from JIT code.
let trap_registration = register_traps(&allocated_functions, &traps, &self.trap_registry); let trap_registration = register_traps(&finished_functions, &traps, &self.trap_registry);
// Eagerly generate a entry trampoline for every type signature in the // Eagerly generate a entry trampoline for every type signature in the
// module. This should be "relatively lightweight" for most modules and // module. This should be "relatively lightweight" for most modules and
@@ -161,38 +164,37 @@ impl Compiler {
// tables) have a trampoline when invoked through the wasmtime API. // tables) have a trampoline when invoked through the wasmtime API.
let mut cx = FunctionBuilderContext::new(); let mut cx = FunctionBuilderContext::new();
let mut trampolines = HashMap::new(); let mut trampolines = HashMap::new();
let mut trampoline_relocations = HashMap::new();
for sig in module.local.signatures.values() { for sig in module.local.signatures.values() {
let index = self.signatures.register(sig); let index = self.signatures.register(sig);
if trampolines.contains_key(&index) { if trampolines.contains_key(&index) {
continue; continue;
} }
// FIXME(#1322) we should be generating a trampoline for all let (trampoline, relocations) = make_trampoline(
// functions in a module, not just those with less than 40
// arguments. Currently there is no relocation support for
// trampoline compilation; when that is added this check can
// go away.
if sig.params.len() > 40 {
continue;
}
trampolines.insert(
index,
make_trampoline(
&*self.isa, &*self.isa,
&mut self.code_memory, &mut self.code_memory,
&mut cx, &mut cx,
sig, sig,
std::mem::size_of::<u128>(), std::mem::size_of::<u128>(),
)?, )?;
); trampolines.insert(index, trampoline);
// Typically trampolines do not have relocations, so if one does
// show up be sure to log it in case anyone's listening and there's
// an accidental bug.
if relocations.len() > 0 {
log::info!("relocations found in trampoline for {:?}", sig);
trampoline_relocations.insert(index, relocations);
}
} }
// Translate debug info (DWARF) only if at least one function is present. // Translate debug info (DWARF) only if at least one function is present.
let dbg = if debug_data.is_some() && !allocated_functions.is_empty() { let dbg_image = if debug_data.is_some() && !finished_functions.is_empty() {
let target_config = self.isa.frontend_config(); let target_config = self.isa.frontend_config();
let ofs = VMOffsets::new(target_config.pointer_bytes(), &module.local); let ofs = VMOffsets::new(target_config.pointer_bytes(), &module.local);
let mut funcs = Vec::new(); let mut funcs = Vec::new();
for (i, allocated) in allocated_functions.into_iter() { for (i, allocated) in finished_functions.into_iter() {
let ptr = (*allocated) as *const u8; let ptr = (*allocated) as *const u8;
let body_len = compilation.get(i).body.len(); let body_len = compilation.get(i).body.len();
funcs.push((ptr, body_len)); funcs.push((ptr, body_len));
@@ -228,14 +230,15 @@ impl Compiler {
let jt_offsets = compilation.get_jt_offsets(); let jt_offsets = compilation.get_jt_offsets();
Ok(( Ok(Compilation {
allocated_functions, finished_functions,
trampolines,
jt_offsets,
relocations, relocations,
dbg, trampolines,
trampoline_relocations,
jt_offsets,
dbg_image,
trap_registration, trap_registration,
)) })
} }
/// Make memory containing compiled code executable. /// Make memory containing compiled code executable.
@@ -271,7 +274,7 @@ pub fn make_trampoline(
fn_builder_ctx: &mut FunctionBuilderContext, fn_builder_ctx: &mut FunctionBuilderContext,
signature: &ir::Signature, signature: &ir::Signature,
value_size: usize, value_size: usize,
) -> Result<VMTrampoline, SetupError> { ) -> Result<(VMTrampoline, Vec<Relocation>), SetupError> {
let pointer_type = isa.pointer_type(); let pointer_type = isa.pointer_type();
let mut wrapper_sig = ir::Signature::new(isa.frontend_config().default_call_conv); let mut wrapper_sig = ir::Signature::new(isa.frontend_config().default_call_conv);
@@ -352,7 +355,7 @@ pub fn make_trampoline(
} }
let mut code_buf = Vec::new(); let mut code_buf = Vec::new();
let mut reloc_sink = RelocSink {}; let mut reloc_sink = RelocSink::default();
let mut trap_sink = binemit::NullTrapSink {}; let mut trap_sink = binemit::NullTrapSink {};
let mut stackmap_sink = binemit::NullStackmapSink {}; let mut stackmap_sink = binemit::NullStackmapSink {};
context context
@@ -381,12 +384,15 @@ pub fn make_trampoline(
}) })
.map_err(|message| SetupError::Instantiate(InstantiationError::Resource(message)))? .map_err(|message| SetupError::Instantiate(InstantiationError::Resource(message)))?
.as_ptr(); .as_ptr();
Ok(unsafe { std::mem::transmute::<*const VMFunctionBody, VMTrampoline>(ptr) }) Ok((
unsafe { std::mem::transmute::<*const VMFunctionBody, VMTrampoline>(ptr) },
reloc_sink.relocs,
))
} }
fn allocate_functions( fn allocate_functions(
code_memory: &mut CodeMemory, code_memory: &mut CodeMemory,
compilation: &Compilation, compilation: &wasmtime_environ::Compilation,
) -> Result<PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>, String> { ) -> Result<PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>, String> {
let fat_ptrs = code_memory.allocate_for_compilation(compilation)?; let fat_ptrs = code_memory.allocate_for_compilation(compilation)?;
@@ -419,9 +425,13 @@ fn register_traps(
registry.register_traps(traps) registry.register_traps(traps)
} }
/// We don't expect trampoline compilation to produce any relocations, so /// We don't expect trampoline compilation to produce many relocations, so
/// this `RelocSink` just asserts that it doesn't recieve any. /// this `RelocSink` just asserts that it doesn't recieve most of them, but
struct RelocSink {} /// handles libcall ones.
#[derive(Default)]
struct RelocSink {
relocs: Vec<Relocation>,
}
impl binemit::RelocSink for RelocSink { impl binemit::RelocSink for RelocSink {
fn reloc_block( fn reloc_block(
@@ -434,12 +444,22 @@ impl binemit::RelocSink for RelocSink {
} }
fn reloc_external( fn reloc_external(
&mut self, &mut self,
_offset: binemit::CodeOffset, offset: binemit::CodeOffset,
_reloc: binemit::Reloc, reloc: binemit::Reloc,
_name: &ir::ExternalName, name: &ir::ExternalName,
_addend: binemit::Addend, addend: binemit::Addend,
) { ) {
panic!("trampoline compilation should not produce external symbol relocs"); let reloc_target = if let ExternalName::LibCall(libcall) = *name {
RelocationTarget::LibCall(libcall)
} else {
panic!("unrecognized external name")
};
self.relocs.push(Relocation {
reloc,
reloc_target,
offset,
addend,
});
} }
fn reloc_constant( fn reloc_constant(
&mut self, &mut self,

View File

@@ -80,26 +80,14 @@ impl<'data> RawCompiledModule<'data> {
None None
}; };
let ( let compilation = compiler.compile(
finished_functions,
trampolines,
jt_offsets,
relocations,
dbg_image,
trap_registration,
) = compiler.compile(
&translation.module, &translation.module,
translation.module_translation.as_ref().unwrap(), translation.module_translation.as_ref().unwrap(),
translation.function_body_inputs, translation.function_body_inputs,
debug_data, debug_data,
)?; )?;
link_module( link_module(&translation.module, &compilation);
&translation.module,
&finished_functions,
&jt_offsets,
relocations,
);
// Compute indices into the shared signature table. // Compute indices into the shared signature table.
let signatures = { let signatures = {
@@ -121,7 +109,7 @@ impl<'data> RawCompiledModule<'data> {
Some(_) => { Some(_) => {
let region_name = String::from("wasm_module"); let region_name = String::from("wasm_module");
let mut profiler = profiler.unwrap().lock().unwrap(); let mut profiler = profiler.unwrap().lock().unwrap();
match &dbg_image { match &compilation.dbg_image {
Some(dbg) => { Some(dbg) => {
compiler.profiler_module_load(&mut profiler, &region_name, Some(&dbg)) compiler.profiler_module_load(&mut profiler, &region_name, Some(&dbg))
} }
@@ -131,7 +119,7 @@ impl<'data> RawCompiledModule<'data> {
_ => (), _ => (),
}; };
let dbg_jit_registration = if let Some(img) = dbg_image { let dbg_jit_registration = if let Some(img) = compilation.dbg_image {
let mut bytes = Vec::new(); let mut bytes = Vec::new();
bytes.write_all(&img).expect("all written"); bytes.write_all(&img).expect("all written");
let reg = GdbJitImageRegistration::register(bytes); let reg = GdbJitImageRegistration::register(bytes);
@@ -142,12 +130,12 @@ impl<'data> RawCompiledModule<'data> {
Ok(Self { Ok(Self {
module: translation.module, module: translation.module,
finished_functions: finished_functions.into_boxed_slice(), finished_functions: compilation.finished_functions.into_boxed_slice(),
trampolines, trampolines: compilation.trampolines,
data_initializers: translation.data_initializers.into_boxed_slice(), data_initializers: translation.data_initializers.into_boxed_slice(),
signatures: signatures.into_boxed_slice(), signatures: signatures.into_boxed_slice(),
dbg_jit_registration, dbg_jit_registration,
trap_registration, trap_registration: compilation.trap_registration,
}) })
} }
} }

View File

@@ -34,7 +34,7 @@ pub mod native;
pub mod trampoline; pub mod trampoline;
pub use crate::code_memory::CodeMemory; pub use crate::code_memory::CodeMemory;
pub use crate::compiler::{make_trampoline, CompilationStrategy, Compiler}; pub use crate::compiler::{make_trampoline, Compilation, CompilationStrategy, Compiler};
pub use crate::instantiate::{instantiate, CompiledModule, SetupError}; pub use crate::instantiate::{instantiate, CompiledModule, SetupError};
pub use crate::link::link_module; pub use crate::link::link_module;
pub use crate::resolver::{NullResolver, Resolver}; pub use crate::resolver::{NullResolver, Resolver};

View File

@@ -1,30 +1,44 @@
//! Linking for JIT-compiled code. //! Linking for JIT-compiled code.
use crate::Compilation;
use cranelift_codegen::binemit::Reloc; use cranelift_codegen::binemit::Reloc;
use cranelift_codegen::ir::JumpTableOffsets;
use std::ptr::write_unaligned; use std::ptr::write_unaligned;
use wasmtime_environ::entity::PrimaryMap; use wasmtime_environ::{Module, Relocation, RelocationTarget};
use wasmtime_environ::wasm::DefinedFuncIndex;
use wasmtime_environ::{Module, RelocationTarget, Relocations};
use wasmtime_runtime::libcalls; use wasmtime_runtime::libcalls;
use wasmtime_runtime::VMFunctionBody; use wasmtime_runtime::VMFunctionBody;
/// Links a module that has been compiled with `compiled_module` in `wasmtime-environ`. /// Links a module that has been compiled with `compiled_module` in `wasmtime-environ`.
/// ///
/// Performs all required relocations inside the function code, provided the necessary metadata. /// Performs all required relocations inside the function code, provided the necessary metadata.
pub fn link_module( pub fn link_module(module: &Module, compilation: &Compilation) {
for (i, function_relocs) in compilation.relocations.iter() {
for r in function_relocs.iter() {
let fatptr: *const [VMFunctionBody] = compilation.finished_functions[i];
let body = fatptr as *const VMFunctionBody;
apply_reloc(module, compilation, body, r);
}
}
for (i, function_relocs) in compilation.trampoline_relocations.iter() {
for r in function_relocs.iter() {
println!("tramopline relocation");
let body = compilation.trampolines[&i] as *const VMFunctionBody;
apply_reloc(module, compilation, body, r);
}
}
}
fn apply_reloc(
module: &Module, module: &Module,
allocated_functions: &PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>, compilation: &Compilation,
jt_offsets: &PrimaryMap<DefinedFuncIndex, JumpTableOffsets>, body: *const VMFunctionBody,
relocations: Relocations, r: &Relocation,
) { ) {
for (i, function_relocs) in relocations.into_iter() {
for r in function_relocs {
use self::libcalls::*; use self::libcalls::*;
let target_func_address: usize = match r.reloc_target { let target_func_address: usize = match r.reloc_target {
RelocationTarget::UserFunc(index) => match module.local.defined_func_index(index) { RelocationTarget::UserFunc(index) => match module.local.defined_func_index(index) {
Some(f) => { Some(f) => {
let fatptr: *const [VMFunctionBody] = allocated_functions[f]; let fatptr: *const [VMFunctionBody] = compilation.finished_functions[f];
fatptr as *const VMFunctionBody as usize fatptr as *const VMFunctionBody as usize
} }
None => panic!("direct call to import"), None => panic!("direct call to import"),
@@ -47,11 +61,12 @@ pub fn link_module(
RelocationTarget::JumpTable(func_index, jt) => { RelocationTarget::JumpTable(func_index, jt) => {
match module.local.defined_func_index(func_index) { match module.local.defined_func_index(func_index) {
Some(f) => { Some(f) => {
let offset = *jt_offsets let offset = *compilation
.jt_offsets
.get(f) .get(f)
.and_then(|ofs| ofs.get(jt)) .and_then(|ofs| ofs.get(jt))
.expect("func jump table"); .expect("func jump table");
let fatptr: *const [VMFunctionBody] = allocated_functions[f]; let fatptr: *const [VMFunctionBody] = compilation.finished_functions[f];
fatptr as *const VMFunctionBody as usize + offset as usize fatptr as *const VMFunctionBody as usize + offset as usize
} }
None => panic!("func index of jump table"), None => panic!("func index of jump table"),
@@ -59,8 +74,6 @@ pub fn link_module(
} }
}; };
let fatptr: *const [VMFunctionBody] = allocated_functions[i];
let body = fatptr as *const VMFunctionBody;
match r.reloc { match r.reloc {
#[cfg(target_pointer_width = "64")] #[cfg(target_pointer_width = "64")]
Reloc::Abs8 => unsafe { Reloc::Abs8 => unsafe {
@@ -91,8 +104,6 @@ pub fn link_module(
_ => panic!("unsupported reloc kind"), _ => panic!("unsupported reloc kind"),
} }
} }
}
}
// A declaration for the stack probe function in Rust's standard library, for // A declaration for the stack probe function in Rust's standard library, for
// catching callstack overflow. // catching callstack overflow.

View File

@@ -0,0 +1,134 @@
(module
(type (;0;) (func (param
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
i32 i32 i32 i32 i32 i32 i32 i32 i32 i32
)
(result i32)
))
(func (export "x") (type 0) local.get 0)
)
(assert_return
(invoke "x"
(i32.const 1) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
(i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0) (i32.const 0)
)
(i32.const 1)
)