From ba0dc40b2b29957738ffcd8ce8f7167a46da469d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 17 Mar 2020 16:30:21 -0500 Subject: [PATCH] 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 --- Cargo.lock | 1 + crates/api/src/trampoline/func.rs | 3 +- crates/jit/Cargo.toml | 1 + crates/jit/src/compiler.rs | 126 +++++++++------- crates/jit/src/instantiate.rs | 26 +--- crates/jit/src/lib.rs | 2 +- crates/jit/src/link.rs | 171 ++++++++++++---------- tests/misc_testsuite/func-400-params.wast | 134 +++++++++++++++++ 8 files changed, 310 insertions(+), 154 deletions(-) create mode 100644 tests/misc_testsuite/func-400-params.wast diff --git a/Cargo.lock b/Cargo.lock index 3212364f7c..7c4f4fb106 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2513,6 +2513,7 @@ dependencies = [ "cranelift-frontend", "cranelift-native", "cranelift-wasm", + "log", "more-asserts", "region", "target-lexicon", diff --git a/crates/api/src/trampoline/func.rs b/crates/api/src/trampoline/func.rs index 76d29d2e8d..2e15c6b741 100644 --- a/crates/api/src/trampoline/func.rs +++ b/crates/api/src/trampoline/func.rs @@ -289,13 +289,14 @@ pub fn create_handle_with_function( // ... 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 // 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, &mut code_memory, &mut fn_builder_ctx, &sig, mem::size_of::(), )?; + assert!(relocations.is_empty()); let sig_id = store.compiler().signatures().register(&sig); trampolines.insert(sig_id, trampoline); diff --git a/crates/jit/Cargo.toml b/crates/jit/Cargo.toml index fa0614eb57..f90142c8cc 100644 --- a/crates/jit/Cargo.toml +++ b/crates/jit/Cargo.toml @@ -27,6 +27,7 @@ wasmparser = "0.51.2" more-asserts = "0.2.1" anyhow = "1.0" cfg-if = "0.1.9" +log = "0.4" [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3.7", features = ["winnt", "impl-default"] } diff --git a/crates/jit/src/compiler.rs b/crates/jit/src/compiler.rs index 7bfdbb17d3..0329320a76 100644 --- a/crates/jit/src/compiler.rs +++ b/crates/jit/src/compiler.rs @@ -3,6 +3,7 @@ use crate::code_memory::CodeMemory; use crate::instantiate::SetupError; use crate::target_tunables::target_tunables; +use cranelift_codegen::ir::ExternalName; use cranelift_codegen::ir::InstBuilder; use cranelift_codegen::print_errors::pretty_error; use cranelift_codegen::Context; @@ -15,10 +16,11 @@ use wasmtime_debug::{emit_debugsections_image, DebugInfoData}; use wasmtime_environ::entity::{EntityRef, PrimaryMap}; use wasmtime_environ::isa::{TargetFrontendConfig, TargetIsa}; use wasmtime_environ::wasm::{DefinedFuncIndex, DefinedMemoryIndex, MemoryIndex}; +use wasmtime_environ::RelocationTarget; use wasmtime_environ::{ - CacheConfig, Compilation, CompileError, CompiledFunction, CompiledFunctionUnwindInfo, - Compiler as _C, FunctionBodyData, Module, ModuleMemoryOffset, ModuleVmctxInfo, Relocations, - Traps, Tunables, VMOffsets, + CacheConfig, CompileError, CompiledFunction, CompiledFunctionUnwindInfo, Compiler as _C, + FunctionBodyData, Module, ModuleMemoryOffset, ModuleVmctxInfo, Relocation, Relocations, Traps, + Tunables, VMOffsets, }; use wasmtime_profiling::ProfilingAgent; use wasmtime_runtime::{ @@ -76,6 +78,17 @@ impl Compiler { } } +#[allow(missing_docs)] +pub struct Compilation { + pub finished_functions: PrimaryMap, + pub relocations: Relocations, + pub trampolines: HashMap, + pub trampoline_relocations: HashMap>, + pub jt_offsets: PrimaryMap, + pub dbg_image: Option>, + pub trap_registration: TrapRegistration, +} + impl Compiler { /// Return the target's frontend configuration settings. pub fn frontend_config(&self) -> TargetFrontendConfig { @@ -94,17 +107,7 @@ impl Compiler { module_translation: &ModuleTranslationState, function_body_inputs: PrimaryMap>, debug_data: Option, - ) -> Result< - ( - PrimaryMap, - HashMap, - PrimaryMap, - Relocations, - Option>, - TrapRegistration, - ), - SetupError, - > { + ) -> Result { let ( compilation, relocations, @@ -142,7 +145,7 @@ impl Compiler { // Allocate all of the compiled functions into executable memory, // copying over their contents. - let allocated_functions = + let finished_functions = allocate_functions(&mut self.code_memory, &compilation).map_err(|message| { SetupError::Instantiate(InstantiationError::Resource(format!( "failed to allocate memory for functions: {}", @@ -153,7 +156,7 @@ impl Compiler { // Create a registration value for all traps in our allocated // functions. This registration will allow us to map a trapping PC // 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 // 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. let mut cx = FunctionBuilderContext::new(); let mut trampolines = HashMap::new(); + let mut trampoline_relocations = HashMap::new(); for sig in module.local.signatures.values() { let index = self.signatures.register(sig); if trampolines.contains_key(&index) { continue; } - // FIXME(#1322) we should be generating a trampoline for all - // 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; + let (trampoline, relocations) = make_trampoline( + &*self.isa, + &mut self.code_memory, + &mut cx, + sig, + std::mem::size_of::(), + )?; + 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); } - trampolines.insert( - index, - make_trampoline( - &*self.isa, - &mut self.code_memory, - &mut cx, - sig, - std::mem::size_of::(), - )?, - ); } // 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 ofs = VMOffsets::new(target_config.pointer_bytes(), &module.local); 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 body_len = compilation.get(i).body.len(); funcs.push((ptr, body_len)); @@ -228,14 +230,15 @@ impl Compiler { let jt_offsets = compilation.get_jt_offsets(); - Ok(( - allocated_functions, - trampolines, - jt_offsets, + Ok(Compilation { + finished_functions, relocations, - dbg, + trampolines, + trampoline_relocations, + jt_offsets, + dbg_image, trap_registration, - )) + }) } /// Make memory containing compiled code executable. @@ -271,7 +274,7 @@ pub fn make_trampoline( fn_builder_ctx: &mut FunctionBuilderContext, signature: &ir::Signature, value_size: usize, -) -> Result { +) -> Result<(VMTrampoline, Vec), SetupError> { let pointer_type = isa.pointer_type(); 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 reloc_sink = RelocSink {}; + let mut reloc_sink = RelocSink::default(); let mut trap_sink = binemit::NullTrapSink {}; let mut stackmap_sink = binemit::NullStackmapSink {}; context @@ -381,12 +384,15 @@ pub fn make_trampoline( }) .map_err(|message| SetupError::Instantiate(InstantiationError::Resource(message)))? .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( code_memory: &mut CodeMemory, - compilation: &Compilation, + compilation: &wasmtime_environ::Compilation, ) -> Result, String> { let fat_ptrs = code_memory.allocate_for_compilation(compilation)?; @@ -419,9 +425,13 @@ fn register_traps( registry.register_traps(traps) } -/// We don't expect trampoline compilation to produce any relocations, so -/// this `RelocSink` just asserts that it doesn't recieve any. -struct RelocSink {} +/// We don't expect trampoline compilation to produce many relocations, so +/// this `RelocSink` just asserts that it doesn't recieve most of them, but +/// handles libcall ones. +#[derive(Default)] +struct RelocSink { + relocs: Vec, +} impl binemit::RelocSink for RelocSink { fn reloc_block( @@ -434,12 +444,22 @@ impl binemit::RelocSink for RelocSink { } fn reloc_external( &mut self, - _offset: binemit::CodeOffset, - _reloc: binemit::Reloc, - _name: &ir::ExternalName, - _addend: binemit::Addend, + offset: binemit::CodeOffset, + reloc: binemit::Reloc, + name: &ir::ExternalName, + 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( &mut self, diff --git a/crates/jit/src/instantiate.rs b/crates/jit/src/instantiate.rs index aa6d80438a..9fbef727b7 100644 --- a/crates/jit/src/instantiate.rs +++ b/crates/jit/src/instantiate.rs @@ -80,26 +80,14 @@ impl<'data> RawCompiledModule<'data> { None }; - let ( - finished_functions, - trampolines, - jt_offsets, - relocations, - dbg_image, - trap_registration, - ) = compiler.compile( + let compilation = compiler.compile( &translation.module, translation.module_translation.as_ref().unwrap(), translation.function_body_inputs, debug_data, )?; - link_module( - &translation.module, - &finished_functions, - &jt_offsets, - relocations, - ); + link_module(&translation.module, &compilation); // Compute indices into the shared signature table. let signatures = { @@ -121,7 +109,7 @@ impl<'data> RawCompiledModule<'data> { Some(_) => { let region_name = String::from("wasm_module"); let mut profiler = profiler.unwrap().lock().unwrap(); - match &dbg_image { + match &compilation.dbg_image { Some(dbg) => { compiler.profiler_module_load(&mut profiler, ®ion_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(); bytes.write_all(&img).expect("all written"); let reg = GdbJitImageRegistration::register(bytes); @@ -142,12 +130,12 @@ impl<'data> RawCompiledModule<'data> { Ok(Self { module: translation.module, - finished_functions: finished_functions.into_boxed_slice(), - trampolines, + finished_functions: compilation.finished_functions.into_boxed_slice(), + trampolines: compilation.trampolines, data_initializers: translation.data_initializers.into_boxed_slice(), signatures: signatures.into_boxed_slice(), dbg_jit_registration, - trap_registration, + trap_registration: compilation.trap_registration, }) } } diff --git a/crates/jit/src/lib.rs b/crates/jit/src/lib.rs index 4f672cfbd0..c524b31a09 100644 --- a/crates/jit/src/lib.rs +++ b/crates/jit/src/lib.rs @@ -34,7 +34,7 @@ pub mod native; pub mod trampoline; 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::link::link_module; pub use crate::resolver::{NullResolver, Resolver}; diff --git a/crates/jit/src/link.rs b/crates/jit/src/link.rs index fcd715589a..c8313b5d86 100644 --- a/crates/jit/src/link.rs +++ b/crates/jit/src/link.rs @@ -1,96 +1,107 @@ //! Linking for JIT-compiled code. +use crate::Compilation; use cranelift_codegen::binemit::Reloc; -use cranelift_codegen::ir::JumpTableOffsets; use std::ptr::write_unaligned; -use wasmtime_environ::entity::PrimaryMap; -use wasmtime_environ::wasm::DefinedFuncIndex; -use wasmtime_environ::{Module, RelocationTarget, Relocations}; +use wasmtime_environ::{Module, Relocation, RelocationTarget}; use wasmtime_runtime::libcalls; use wasmtime_runtime::VMFunctionBody; /// 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. -pub fn link_module( - module: &Module, - allocated_functions: &PrimaryMap, - jt_offsets: &PrimaryMap, - relocations: Relocations, -) { - for (i, function_relocs) in relocations.into_iter() { - for r in function_relocs { - use self::libcalls::*; - let target_func_address: usize = match r.reloc_target { - RelocationTarget::UserFunc(index) => match module.local.defined_func_index(index) { - Some(f) => { - let fatptr: *const [VMFunctionBody] = allocated_functions[f]; - fatptr as *const VMFunctionBody as usize - } - None => panic!("direct call to import"), - }, - RelocationTarget::LibCall(libcall) => { - use cranelift_codegen::ir::LibCall::*; - match libcall { - CeilF32 => wasmtime_f32_ceil as usize, - FloorF32 => wasmtime_f32_floor as usize, - TruncF32 => wasmtime_f32_trunc as usize, - NearestF32 => wasmtime_f32_nearest as usize, - CeilF64 => wasmtime_f64_ceil as usize, - FloorF64 => wasmtime_f64_floor as usize, - TruncF64 => wasmtime_f64_trunc as usize, - NearestF64 => wasmtime_f64_nearest as usize, - Probestack => PROBESTACK as usize, - other => panic!("unexpected libcall: {}", other), - } - } - RelocationTarget::JumpTable(func_index, jt) => { - match module.local.defined_func_index(func_index) { - Some(f) => { - let offset = *jt_offsets - .get(f) - .and_then(|ofs| ofs.get(jt)) - .expect("func jump table"); - let fatptr: *const [VMFunctionBody] = allocated_functions[f]; - fatptr as *const VMFunctionBody as usize + offset as usize - } - None => panic!("func index of jump table"), - } - } - }; - - let fatptr: *const [VMFunctionBody] = allocated_functions[i]; +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; - match r.reloc { - #[cfg(target_pointer_width = "64")] - Reloc::Abs8 => unsafe { - let reloc_address = body.add(r.offset as usize) as usize; - let reloc_addend = r.addend as isize; - let reloc_abs = (target_func_address as u64) - .checked_add(reloc_addend as u64) - .unwrap(); - write_unaligned(reloc_address as *mut u64, reloc_abs); - }, - #[cfg(target_pointer_width = "32")] - Reloc::X86PCRel4 => unsafe { - let reloc_address = body.add(r.offset as usize) as usize; - let reloc_addend = r.addend as isize; - let reloc_delta_u32 = (target_func_address as u32) - .wrapping_sub(reloc_address as u32) - .checked_add(reloc_addend as u32) - .unwrap(); - write_unaligned(reloc_address as *mut u32, reloc_delta_u32); - }, - #[cfg(target_pointer_width = "32")] - Reloc::X86CallPCRel4 => { - // ignore - } - Reloc::X86PCRelRodata4 => { - // ignore - } - _ => panic!("unsupported reloc kind"), + 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, + compilation: &Compilation, + body: *const VMFunctionBody, + r: &Relocation, +) { + use self::libcalls::*; + let target_func_address: usize = match r.reloc_target { + RelocationTarget::UserFunc(index) => match module.local.defined_func_index(index) { + Some(f) => { + let fatptr: *const [VMFunctionBody] = compilation.finished_functions[f]; + fatptr as *const VMFunctionBody as usize + } + None => panic!("direct call to import"), + }, + RelocationTarget::LibCall(libcall) => { + use cranelift_codegen::ir::LibCall::*; + match libcall { + CeilF32 => wasmtime_f32_ceil as usize, + FloorF32 => wasmtime_f32_floor as usize, + TruncF32 => wasmtime_f32_trunc as usize, + NearestF32 => wasmtime_f32_nearest as usize, + CeilF64 => wasmtime_f64_ceil as usize, + FloorF64 => wasmtime_f64_floor as usize, + TruncF64 => wasmtime_f64_trunc as usize, + NearestF64 => wasmtime_f64_nearest as usize, + Probestack => PROBESTACK as usize, + other => panic!("unexpected libcall: {}", other), } } + RelocationTarget::JumpTable(func_index, jt) => { + match module.local.defined_func_index(func_index) { + Some(f) => { + let offset = *compilation + .jt_offsets + .get(f) + .and_then(|ofs| ofs.get(jt)) + .expect("func jump table"); + let fatptr: *const [VMFunctionBody] = compilation.finished_functions[f]; + fatptr as *const VMFunctionBody as usize + offset as usize + } + None => panic!("func index of jump table"), + } + } + }; + + match r.reloc { + #[cfg(target_pointer_width = "64")] + Reloc::Abs8 => unsafe { + let reloc_address = body.add(r.offset as usize) as usize; + let reloc_addend = r.addend as isize; + let reloc_abs = (target_func_address as u64) + .checked_add(reloc_addend as u64) + .unwrap(); + write_unaligned(reloc_address as *mut u64, reloc_abs); + }, + #[cfg(target_pointer_width = "32")] + Reloc::X86PCRel4 => unsafe { + let reloc_address = body.add(r.offset as usize) as usize; + let reloc_addend = r.addend as isize; + let reloc_delta_u32 = (target_func_address as u32) + .wrapping_sub(reloc_address as u32) + .checked_add(reloc_addend as u32) + .unwrap(); + write_unaligned(reloc_address as *mut u32, reloc_delta_u32); + }, + #[cfg(target_pointer_width = "32")] + Reloc::X86CallPCRel4 => { + // ignore + } + Reloc::X86PCRelRodata4 => { + // ignore + } + _ => panic!("unsupported reloc kind"), } } diff --git a/tests/misc_testsuite/func-400-params.wast b/tests/misc_testsuite/func-400-params.wast new file mode 100644 index 0000000000..71e81cdf87 --- /dev/null +++ b/tests/misc_testsuite/func-400-params.wast @@ -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) +)