From 3b7cb6ee64469470fcdd68e185abca8eb2a1b20a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 20 Mar 2020 11:44:51 -0500 Subject: [PATCH] Enable jitdump profiling support by default (#1310) * Enable jitdump profiling support by default This the result of some of the investigation I was doing for #1017. I've done a number of refactorings here which culminated in a number of changes that all amount to what I think should result in jitdump support being enabled by default: * Pass in a list of finished functions instead of just a range to ensure that we're emitting jit dump data for a specific module rather than a whole `CodeMemory` which may have other modules. * Define `ProfilingStrategy` in the `wasmtime` crate to have everything locally-defined * Add support to the C API to enable profiling * Documentation added for profiling with jitdump to the book * Split out supported/unsupported files in `jitdump.rs` to avoid having lots of `#[cfg]`. * Make dependencies optional that are only used for `jitdump`. * Move initialization up-front to `JitDumpAgent::new()` instead of deferring it to the first module. * Pass around `Arc` instead of `Option>>>` The `jitdump` Cargo feature is now enabled by default which means that our published binaries, C API artifacts, and crates will support profiling at runtime by default. The support I don't think is fully fleshed out and working but I think it's probably in a good enough spot we can get users playing around with it! --- Cargo.lock | 6 +- Cargo.toml | 6 +- crates/api/Cargo.toml | 5 +- crates/api/src/lib.rs | 2 +- crates/api/src/module.rs | 2 +- crates/api/src/runtime.rs | 27 +- crates/c-api/Cargo.toml | 7 +- crates/c-api/include/wasmtime.h | 7 + crates/c-api/src/ext.rs | 21 +- crates/jit/Cargo.toml | 1 + crates/jit/src/code_memory.rs | 19 - crates/jit/src/compiler.rs | 11 - crates/jit/src/instantiate.rs | 26 +- crates/profiling/Cargo.toml | 20 +- crates/profiling/src/jitdump_disabled.rs | 33 ++ .../src/{jitdump.rs => jitdump_linux.rs} | 335 +++++++++--------- crates/profiling/src/lib.rs | 58 +-- crates/runtime/Cargo.toml | 1 - crates/wasi/Cargo.toml | 2 +- crates/wast/Cargo.toml | 4 +- docs/SUMMARY.md | 1 + docs/assets/perf-annotate-fib.png | Bin 0 -> 59722 bytes docs/assets/perf-report-fib.png | Bin 0 -> 27649 bytes docs/examples-profiling.md | 152 ++++++++ src/lib.rs | 7 +- tests/instantiate.rs | 40 --- tests/misc_testsuite/rs2wasm-add-func.wast | 20 ++ 27 files changed, 488 insertions(+), 325 deletions(-) create mode 100644 crates/profiling/src/jitdump_disabled.rs rename crates/profiling/src/{jitdump.rs => jitdump_linux.rs} (77%) create mode 100755 docs/assets/perf-annotate-fib.png create mode 100755 docs/assets/perf-report-fib.png create mode 100644 docs/examples-profiling.md delete mode 100644 tests/instantiate.rs create mode 100644 tests/misc_testsuite/rs2wasm-add-func.wast diff --git a/Cargo.lock b/Cargo.lock index a896d1f280..9bddeda50c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2433,7 +2433,6 @@ dependencies = [ "wasmtime-environ", "wasmtime-jit", "wasmtime-obj", - "wasmtime-profiling", "wasmtime-runtime", "wasmtime-wasi", "wasmtime-wast", @@ -2555,6 +2554,8 @@ dependencies = [ name = "wasmtime-profiling" version = "0.12.0" dependencies = [ + "anyhow", + "cfg-if", "gimli", "goblin", "lazy_static", @@ -2563,6 +2564,8 @@ dependencies = [ "scroll", "serde", "target-lexicon", + "wasmtime-environ", + "wasmtime-runtime", ] [[package]] @@ -2592,7 +2595,6 @@ dependencies = [ "region", "thiserror", "wasmtime-environ", - "wasmtime-profiling", "winapi", ] diff --git a/Cargo.toml b/Cargo.toml index cb254357eb..f7f690398b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,12 +23,11 @@ doc = false [dependencies] # Enable all supported architectures by default. -wasmtime = { path = "crates/api" } +wasmtime = { path = "crates/api", default-features = false } wasmtime-debug = { path = "crates/debug" } wasmtime-environ = { path = "crates/environ" } wasmtime-jit = { path = "crates/jit" } wasmtime-obj = { path = "crates/obj" } -wasmtime-profiling = { path = "crates/profiling" } wasmtime-wast = { path = "crates/wast" } wasmtime-wasi = { path = "crates/wasi" } wasi-common = { path = "crates/wasi-common" } @@ -72,13 +71,14 @@ members = [ ] [features] +default = ["jitdump", "wasmtime/wat"] lightbeam = [ "wasmtime-environ/lightbeam", "wasmtime-jit/lightbeam", "wasmtime-wast/lightbeam", "wasmtime/lightbeam", ] -jitdump = ["wasmtime-profiling/jitdump"] +jitdump = ["wasmtime/jitdump"] test_programs = ["test-programs/test_programs"] [badges] diff --git a/crates/api/Cargo.toml b/crates/api/Cargo.toml index d9be1dd090..746bbdeb5e 100644 --- a/crates/api/Cargo.toml +++ b/crates/api/Cargo.toml @@ -40,13 +40,16 @@ tempfile = "3.1" maintenance = { status = "actively-developed" } [features] -default = ['wat'] +default = ['wat', 'jitdump'] # Enables experimental support for the lightbeam codegen backend, an alternative # to cranelift. Requires Nightly Rust currently, and this is not enabled by # default. lightbeam = ["wasmtime-jit/lightbeam"] +# Enables support for the `perf` jitdump profiler +jitdump = ["wasmtime-jit/jitdump"] + [[test]] name = "host-segfault" harness = false diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index b6e5cb5ee9..f7eb7b47df 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -26,7 +26,7 @@ pub use crate::func::*; pub use crate::instance::Instance; pub use crate::module::Module; pub use crate::r#ref::{AnyRef, HostInfo, HostRef}; -pub use crate::runtime::{Config, Engine, OptLevel, Store, Strategy}; +pub use crate::runtime::*; pub use crate::trap::Trap; pub use crate::types::*; pub use crate::values::*; diff --git a/crates/api/src/module.rs b/crates/api/src/module.rs index 0338cdbb4b..ea8ae1809d 100644 --- a/crates/api/src/module.rs +++ b/crates/api/src/module.rs @@ -367,7 +367,7 @@ impl Module { &mut store.compiler_mut(), binary, store.engine().config().debug_info, - store.engine().config().profiler.as_ref(), + &*store.engine().config().profiler, )?; Ok(Module { diff --git a/crates/api/src/runtime.rs b/crates/api/src/runtime.rs index 25aa3f6a58..8908483996 100644 --- a/crates/api/src/runtime.rs +++ b/crates/api/src/runtime.rs @@ -3,12 +3,12 @@ use std::cell::RefCell; use std::fmt; use std::path::Path; use std::rc::Rc; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use wasmparser::{OperatorValidatorConfig, ValidatingParserConfig}; use wasmtime_environ::settings::{self, Configurable}; use wasmtime_environ::CacheConfig; use wasmtime_jit::{native, CompilationStrategy, Compiler}; -use wasmtime_profiling::{JitDumpAgent, ProfilingAgent, ProfilingStrategy}; +use wasmtime_profiling::{JitDumpAgent, NullProfilerAgent, ProfilingAgent}; // Runtime Environment @@ -26,7 +26,7 @@ pub struct Config { pub(crate) debug_info: bool, pub(crate) strategy: CompilationStrategy, pub(crate) cache_config: CacheConfig, - pub(crate) profiler: Option>>>, + pub(crate) profiler: Arc, } impl Config { @@ -65,7 +65,7 @@ impl Config { flags, strategy: CompilationStrategy::Auto, cache_config: CacheConfig::new_cache_disabled(), - profiler: None, + profiler: Arc::new(NullProfilerAgent), } } @@ -225,11 +225,9 @@ impl Config { /// Profiler creation calls the type's default initializer where the purpose is /// really just to put in place the type used for profiling. pub fn profiler(&mut self, profile: ProfilingStrategy) -> Result<&mut Self> { - match profile { - ProfilingStrategy::JitDumpProfiler => { - self.profiler = { Some(Arc::new(Mutex::new(Box::new(JitDumpAgent::default())))) } - } - _ => self.profiler = { None }, + self.profiler = match profile { + ProfilingStrategy::JitDump => Arc::new(JitDumpAgent::new()?) as Arc, + ProfilingStrategy::None => Arc::new(NullProfilerAgent), }; Ok(self) } @@ -381,6 +379,17 @@ pub enum OptLevel { SpeedAndSize, } +/// Select which profiling technique to support. +#[derive(Debug, Clone, Copy)] +pub enum ProfilingStrategy { + /// No profiler support. + None, + + /// Collect profiling info for "jitdump" file format, used with `perf` on + /// Linux. + JitDump, +} + // Engine /// An `Engine` which is a global context for compilation and management of wasm diff --git a/crates/c-api/Cargo.toml b/crates/c-api/Cargo.toml index e489843651..5978269575 100644 --- a/crates/c-api/Cargo.toml +++ b/crates/c-api/Cargo.toml @@ -17,7 +17,12 @@ test = false doctest = false [dependencies] -wasmtime = { path = "../api" } +wasmtime = { path = "../api", default-features = false } wasi-common = { path = "../wasi-common" } wasmtime-wasi = { path = "../wasi" } wat = "1.0" + +[features] +default = ['jitdump'] +lightbeam = ["wasmtime/lightbeam"] +jitdump = ["wasmtime/jitdump"] diff --git a/crates/c-api/include/wasmtime.h b/crates/c-api/include/wasmtime.h index 5334d49bd3..52e39825af 100644 --- a/crates/c-api/include/wasmtime.h +++ b/crates/c-api/include/wasmtime.h @@ -25,6 +25,12 @@ enum wasmtime_opt_level_enum { // OptLevel WASMTIME_OPT_LEVEL_SPEED_AND_SIZE, }; +typedef uint8_t wasmtime_profiling_strategy_t; +enum wasmtime_profiling_strategy_t { // ProfilingStrategy + WASMTIME_PROFILING_STRATEGY_NONE, + WASMTIME_PROFILING_STRATEGY_JITDUMP, +}; + #define WASMTIME_CONFIG_PROP(name, ty) \ WASM_API_EXTERN void wasmtime_config_##name##_set(wasm_config_t*, ty); @@ -37,6 +43,7 @@ WASMTIME_CONFIG_PROP(wasm_multi_value, bool) WASMTIME_CONFIG_PROP(strategy, wasmtime_strategy_t) WASMTIME_CONFIG_PROP(cranelift_debug_verifier, bool) WASMTIME_CONFIG_PROP(cranelift_opt_level, wasmtime_opt_level_t) +WASMTIME_CONFIG_PROP(profiler, wasmtime_profiling_strategy_t) /////////////////////////////////////////////////////////////////////////////// diff --git a/crates/c-api/src/ext.rs b/crates/c-api/src/ext.rs index c61e7ad07d..a81be32b9c 100644 --- a/crates/c-api/src/ext.rs +++ b/crates/c-api/src/ext.rs @@ -3,7 +3,7 @@ use crate::{wasm_byte_vec_t, wasm_config_t, wasm_engine_t}; use std::str; -use wasmtime::{OptLevel, Strategy}; +use wasmtime::{OptLevel, ProfilingStrategy, Strategy}; #[repr(u8)] #[derive(Clone)] @@ -21,6 +21,13 @@ pub enum wasmtime_opt_level_t { WASMTIME_OPT_LEVEL_SPEED_AND_SIZE, } +#[repr(u8)] +#[derive(Clone)] +pub enum wasmtime_profiling_strategy_t { + WASMTIME_PROFILING_STRATEGY_NONE, + WASMTIME_PROFILING_STRATEGY_JITDUMP, +} + #[no_mangle] pub unsafe extern "C" fn wasmtime_config_debug_info_set(c: *mut wasm_config_t, enable: bool) { (*c).config.debug_info(enable); @@ -88,6 +95,18 @@ pub unsafe extern "C" fn wasmtime_config_cranelift_opt_level_set( }); } +#[no_mangle] +pub unsafe extern "C" fn wasmtime_config_profiler_set( + c: *mut wasm_config_t, + strategy: wasmtime_profiling_strategy_t, +) { + use wasmtime_profiling_strategy_t::*; + drop((*c).config.profiler(match strategy { + WASMTIME_PROFILING_STRATEGY_NONE => ProfilingStrategy::None, + WASMTIME_PROFILING_STRATEGY_JITDUMP => ProfilingStrategy::JitDump, + })); +} + #[no_mangle] pub unsafe extern "C" fn wasmtime_wat2wasm( _engine: *mut wasm_engine_t, diff --git a/crates/jit/Cargo.toml b/crates/jit/Cargo.toml index f90142c8cc..5f8e53819e 100644 --- a/crates/jit/Cargo.toml +++ b/crates/jit/Cargo.toml @@ -34,6 +34,7 @@ winapi = { version = "0.3.7", features = ["winnt", "impl-default"] } [features] lightbeam = ["wasmtime-environ/lightbeam"] +jitdump = ["wasmtime-profiling/jitdump"] [badges] maintenance = { status = "actively-developed" } diff --git a/crates/jit/src/code_memory.rs b/crates/jit/src/code_memory.rs index 968b1cb62e..a31695527b 100644 --- a/crates/jit/src/code_memory.rs +++ b/crates/jit/src/code_memory.rs @@ -5,7 +5,6 @@ use region; use std::mem::ManuallyDrop; use std::{cmp, mem}; use wasmtime_environ::{Compilation, CompiledFunction}; -use wasmtime_profiling::ProfilingAgent; use wasmtime_runtime::{Mmap, VMFunctionBody}; struct CodeMemoryEntry { @@ -237,22 +236,4 @@ impl CodeMemory { Ok(()) } - - /// Calls the module_load for a given ProfilerAgent. Includes - /// all memory address and length for the given module. - /// TODO: Properly handle the possibilities of multiple mmapped regions - /// which may, amongst other things, influence being more specific about - /// the module name. - pub fn profiler_module_load( - &mut self, - profiler: &mut Box, - module_name: &str, - dbg_image: Option<&[u8]>, - ) -> () { - for CodeMemoryEntry { mmap: m, table: _t } in &mut self.entries { - if m.len() > 0 { - profiler.module_load(module_name, m.as_ptr(), m.len(), dbg_image); - } - } - } } diff --git a/crates/jit/src/compiler.rs b/crates/jit/src/compiler.rs index 0329320a76..5bf2fd95ff 100644 --- a/crates/jit/src/compiler.rs +++ b/crates/jit/src/compiler.rs @@ -22,7 +22,6 @@ use wasmtime_environ::{ FunctionBodyData, Module, ModuleMemoryOffset, ModuleVmctxInfo, Relocation, Relocations, Traps, Tunables, VMOffsets, }; -use wasmtime_profiling::ProfilingAgent; use wasmtime_runtime::{ InstantiationError, SignatureRegistry, TrapRegistration, TrapRegistry, VMFunctionBody, VMSharedSignatureIndex, VMTrampoline, @@ -246,16 +245,6 @@ impl Compiler { self.code_memory.publish(); } - pub(crate) fn profiler_module_load( - &mut self, - profiler: &mut Box, - module_name: &str, - dbg_image: Option<&[u8]>, - ) -> () { - self.code_memory - .profiler_module_load(profiler, module_name, dbg_image); - } - /// Shared signature registry. pub fn signatures(&self) -> &SignatureRegistry { &self.signatures diff --git a/crates/jit/src/instantiate.rs b/crates/jit/src/instantiate.rs index 9fbef727b7..50380ea97e 100644 --- a/crates/jit/src/instantiate.rs +++ b/crates/jit/src/instantiate.rs @@ -10,7 +10,7 @@ use crate::resolver::Resolver; use std::collections::HashMap; use std::io::Write; use std::rc::Rc; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use thiserror::Error; use wasmtime_debug::read_debuginfo; use wasmtime_environ::entity::{BoxedSlice, PrimaryMap}; @@ -64,7 +64,7 @@ impl<'data> RawCompiledModule<'data> { compiler: &mut Compiler, data: &'data [u8], debug_info: bool, - profiler: Option<&Arc>>>, + profiler: &dyn ProfilingAgent, ) -> Result { let environ = ModuleEnvironment::new(compiler.frontend_config(), compiler.tunables()); @@ -105,19 +105,11 @@ impl<'data> RawCompiledModule<'data> { compiler.publish_compiled_code(); // Initialize profiler and load the wasm module - match profiler { - Some(_) => { - let region_name = String::from("wasm_module"); - let mut profiler = profiler.unwrap().lock().unwrap(); - match &compilation.dbg_image { - Some(dbg) => { - compiler.profiler_module_load(&mut profiler, ®ion_name, Some(&dbg)) - } - _ => compiler.profiler_module_load(&mut profiler, ®ion_name, None), - }; - } - _ => (), - }; + profiler.module_load( + &translation.module, + &compilation.finished_functions, + compilation.dbg_image.as_deref(), + ); let dbg_jit_registration = if let Some(img) = compilation.dbg_image { let mut bytes = Vec::new(); @@ -157,7 +149,7 @@ impl CompiledModule { compiler: &mut Compiler, data: &'data [u8], debug_info: bool, - profiler: Option<&Arc>>>, + profiler: &dyn ProfilingAgent, ) -> Result { let raw = RawCompiledModule::<'data>::new(compiler, data, debug_info, profiler)?; @@ -290,7 +282,7 @@ pub unsafe fn instantiate( resolver: &mut dyn Resolver, debug_info: bool, is_bulk_memory: bool, - profiler: Option<&Arc>>>, + profiler: &dyn ProfilingAgent, ) -> Result { let instance = CompiledModule::new(compiler, data, debug_info, profiler)?.instantiate( is_bulk_memory, diff --git a/crates/profiling/Cargo.toml b/crates/profiling/Cargo.toml index 8ca66bd1d9..eca6a0ee03 100644 --- a/crates/profiling/Cargo.toml +++ b/crates/profiling/Cargo.toml @@ -11,17 +11,21 @@ readme = "README.md" edition = "2018" [dependencies] -libc = { version = "0.2.60", default-features = false } -goblin = "0.1.3" -serde = { version = "1.0.99", features = ["derive"] } -scroll = "0.10.1" -gimli = "0.20.0" -object = "0.17.0" -target-lexicon = "0.10.0" +anyhow = "1.0" +cfg-if = "0.1" +gimli = { version = "0.20.0", optional = true } +goblin = { version = "0.1.3", optional = true } lazy_static = "1.4" +libc = { version = "0.2.60", default-features = false } +object = { version = "0.17.0", optional = true } +scroll = { version = "0.10.1", optional = true } +serde = { version = "1.0.99", features = ["derive"] } +target-lexicon = "0.10.0" +wasmtime-environ = { path = "../environ", version = "0.12.0" } +wasmtime-runtime = { path = "../runtime", version = "0.12.0" } [badges] maintenance = { status = "actively-developed" } [features] -jitdump = [] +jitdump = ['goblin', 'object', 'scroll', 'gimli'] diff --git a/crates/profiling/src/jitdump_disabled.rs b/crates/profiling/src/jitdump_disabled.rs new file mode 100644 index 0000000000..5197ddb5b5 --- /dev/null +++ b/crates/profiling/src/jitdump_disabled.rs @@ -0,0 +1,33 @@ +use crate::ProfilingAgent; +use anyhow::{bail, Result}; +use wasmtime_environ::entity::PrimaryMap; +use wasmtime_environ::wasm::DefinedFuncIndex; +use wasmtime_environ::Module; +use wasmtime_runtime::VMFunctionBody; + +/// Interface for driving the creation of jitdump files +#[derive(Debug)] +pub struct JitDumpAgent { + _private: (), +} + +impl JitDumpAgent { + /// Intialize a JitDumpAgent and write out the header + pub fn new() -> Result { + if cfg!(feature = "jitdump") { + bail!("jitdump is not supported on this platform"); + } else { + bail!("jitdump support disabled at compile time"); + } + } +} + +impl ProfilingAgent for JitDumpAgent { + fn module_load( + &self, + _module: &Module, + _functions: &PrimaryMap, + _dbg_image: Option<&[u8]>, + ) { + } +} diff --git a/crates/profiling/src/jitdump.rs b/crates/profiling/src/jitdump_linux.rs similarity index 77% rename from crates/profiling/src/jitdump.rs rename to crates/profiling/src/jitdump_linux.rs index cff4155c83..0b98b77f43 100644 --- a/crates/profiling/src/jitdump.rs +++ b/crates/profiling/src/jitdump_linux.rs @@ -10,22 +10,25 @@ //! Report //! sudo perf report -i perf.jit.data -F+period,srcline //! Note: For descriptive results, the WASM file being executed should contain dwarf debug data -use libc::c_int; -#[cfg(not(target_os = "windows"))] -use libc::{c_void, clock_gettime, mmap, open, sysconf}; + +use crate::ProfilingAgent; +use anyhow::Result; use object::Object; use scroll::{IOwrite, SizeWith, NATIVE}; use serde::{Deserialize, Serialize}; -use std::error::Error; -#[cfg(not(target_os = "windows"))] -use std::ffi::CString; use std::fmt::Debug; -use std::fs::File; +use std::fs::{File, OpenOptions}; +use std::io; use std::io::Write; -#[cfg(not(target_os = "windows"))] -use std::os::unix::io::FromRawFd; +use std::os::unix::prelude::*; +use std::ptr; +use std::sync::Mutex; use std::{borrow, mem, process}; use target_lexicon::Architecture; +use wasmtime_environ::entity::PrimaryMap; +use wasmtime_environ::wasm::DefinedFuncIndex; +use wasmtime_environ::Module; +use wasmtime_runtime::VMFunctionBody; #[cfg(target_pointer_width = "64")] use goblin::elf64 as elf; @@ -133,71 +136,110 @@ pub struct FileHeader { } /// Interface for driving the creation of jitdump files -#[derive(Debug, Default)] pub struct JitDumpAgent { + // Note that we use a mutex internally to serialize writing out to our + // `jitdump_file` within this process, since multiple threads may be sharing + // this jit agent. + state: Mutex, +} + +struct State { /// File instance for the jit dump file - pub jitdump_file: Option, + jitdump_file: File, + + map_addr: usize, + /// Unique identifier for jitted code - pub code_index: u64, + code_index: u64, + /// Flag for experimenting with dumping code load record /// after each function (true) or after each module. This /// flag is currently set to true. - pub dump_funcs: bool, + dump_funcs: bool, } impl JitDumpAgent { /// Intialize a JitDumpAgent and write out the header - pub fn init(&mut self) -> Result<(), Box> { - #[cfg(target_os = "windows")] - return Err("Target OS not supported."); - #[cfg(not(target_os = "windows"))] - { - let filename = format!("./jit-{}.dump", process::id()); - let mut jitdump_file; - unsafe { - let filename_c = CString::new(filename)?; - let fd = open( - filename_c.as_ptr(), - libc::O_CREAT | libc::O_TRUNC | libc::O_RDWR, - 0666, - ); - let pgsz = sysconf(libc::_SC_PAGESIZE) as usize; - mmap( - 0 as *mut c_void, - pgsz, - libc::PROT_EXEC | libc::PROT_READ, - libc::MAP_PRIVATE, - fd, - 0, - ); - jitdump_file = File::from_raw_fd(fd); - } - JitDumpAgent::write_file_header(&mut jitdump_file)?; - self.jitdump_file = Some(jitdump_file); - self.code_index = 0; - self.dump_funcs = true; - Ok(()) - } - } + pub fn new() -> Result { + let filename = format!("./jit-{}.dump", process::id()); + let jitdump_file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(true) + .open(&filename)?; - /// Returns timestamp from a single source - #[allow(unused_variables)] - fn get_time_stamp(timestamp: &mut u64) -> c_int { - #[cfg(not(target_os = "windows"))] - { - unsafe { - let mut ts = mem::MaybeUninit::zeroed().assume_init(); - clock_gettime(libc::CLOCK_MONOTONIC, &mut ts); - // TODO: What does it mean for either sec or nsec to be negative? - *timestamp = (ts.tv_sec * 1000000000 + ts.tv_nsec) as u64; + // After we make our `*.dump` file we execute an `mmap` syscall, + // specifically with executable permissions, to map it into our address + // space. This is required so `perf inject` will work later. The `perf + // inject` command will see that an mmap syscall happened, and it'll see + // the filename we mapped, and that'll trigger it to actually read and + // parse the file. + // + // To match what some perf examples are doing we keep this `mmap` alive + // until this agent goes away. + let map_addr = unsafe { + let ptr = libc::mmap( + ptr::null_mut(), + libc::sysconf(libc::_SC_PAGESIZE) as usize, + libc::PROT_EXEC | libc::PROT_READ, + libc::MAP_PRIVATE, + jitdump_file.as_raw_fd(), + 0, + ); + if ptr == libc::MAP_FAILED { + return Err(io::Error::last_os_error().into()); } + ptr as usize + }; + let mut state = State { + jitdump_file, + map_addr, + code_index: 0, + dump_funcs: true, + }; + state.write_file_header()?; + Ok(JitDumpAgent { + state: Mutex::new(state), + }) + } +} + +impl ProfilingAgent for JitDumpAgent { + fn module_load( + &self, + module: &Module, + functions: &PrimaryMap, + dbg_image: Option<&[u8]>, + ) { + self.state + .lock() + .unwrap() + .module_load(module, functions, dbg_image); + } +} + +impl State { + /// Returns timestamp from a single source + fn get_time_stamp(&self) -> u64 { + // We need to use `CLOCK_MONOTONIC` on Linux which is what `Instant` + // conveniently also uses, but `Instant` doesn't allow us to get access + // to nanoseconds as an internal detail, so we calculate the nanoseconds + // ourselves here. + unsafe { + let mut ts = mem::MaybeUninit::zeroed(); + assert_eq!( + libc::clock_gettime(libc::CLOCK_MONOTONIC, ts.as_mut_ptr()), + 0 + ); + let ts = ts.assume_init(); + // TODO: What does it mean for either sec or nsec to be negative? + (ts.tv_sec * 1_000_000_000 + ts.tv_nsec) as u64 } - return 0; } /// Returns the ELF machine architecture. - #[allow(dead_code)] - fn get_e_machine() -> u32 { + fn get_e_machine(&self) -> u32 { match target_lexicon::HOST.architecture { Architecture::X86_64 => elf::header::EM_X86_64 as u32, Architecture::I686 => elf::header::EM_386 as u32, @@ -207,30 +249,23 @@ impl JitDumpAgent { } } - #[allow(dead_code)] - fn write_file_header(file: &mut File) -> Result<(), JitDumpError> { - let mut header: FileHeader = Default::default(); - let mut timestamp: u64 = 0; - JitDumpAgent::get_time_stamp(&mut timestamp); - header.timestamp = timestamp; + fn write_file_header(&mut self) -> Result<()> { + let header = FileHeader { + timestamp: self.get_time_stamp(), + e_machine: self.get_e_machine(), + magic: if cfg!(target_endian = "little") { + 0x4A695444 + } else { + 0x4454694a + }, + version: 1, + size: mem::size_of::() as u32, + pad1: 0, + pid: process::id(), + flags: 0, + }; - let e_machine = JitDumpAgent::get_e_machine(); - if e_machine != elf::header::EM_NONE as u32 { - header.e_machine = e_machine; - } - - if cfg!(target_endian = "little") { - header.magic = 0x4A695444 - } else { - header.magic = 0x4454694a - } - header.version = 1; - header.size = mem::size_of::() as u32; - header.pad1 = 0; - header.pid = process::id(); - header.flags = 0; - - file.iowrite_with(header, NATIVE)?; + self.jitdump_file.iowrite_with(header, NATIVE)?; Ok(()) } @@ -239,38 +274,31 @@ impl JitDumpAgent { record_name: &str, cl_record: CodeLoadRecord, code_buffer: &[u8], - ) -> Result<(), JitDumpError> { - let mut jitdump_file = self.jitdump_file.as_ref().unwrap(); - jitdump_file.iowrite_with(cl_record, NATIVE)?; - jitdump_file.write_all(record_name.as_bytes())?; - jitdump_file.write_all(b"\0")?; - jitdump_file.write_all(code_buffer)?; + ) -> Result<()> { + self.jitdump_file.iowrite_with(cl_record, NATIVE)?; + self.jitdump_file.write_all(record_name.as_bytes())?; + self.jitdump_file.write_all(b"\0")?; + self.jitdump_file.write_all(code_buffer)?; Ok(()) } /// Write DebugInfoRecord to open jit dump file. /// Must be written before the corresponding CodeLoadRecord. - fn write_debug_info_record(&mut self, dir_record: DebugInfoRecord) -> Result<(), JitDumpError> { - self.jitdump_file - .as_ref() - .unwrap() - .iowrite_with(dir_record, NATIVE)?; + fn write_debug_info_record(&mut self, dir_record: DebugInfoRecord) -> Result<()> { + self.jitdump_file.iowrite_with(dir_record, NATIVE)?; Ok(()) } /// Write DebugInfoRecord to open jit dump file. /// Must be written before the corresponding CodeLoadRecord. - fn write_debug_info_entries( - &mut self, - die_entries: Vec, - ) -> Result<(), JitDumpError> { + fn write_debug_info_entries(&mut self, die_entries: Vec) -> Result<()> { for entry in die_entries.iter() { - let mut jitdump_file = self.jitdump_file.as_ref().unwrap(); - jitdump_file.iowrite_with(entry.address, NATIVE)?; - jitdump_file.iowrite_with(entry.line, NATIVE)?; - jitdump_file.iowrite_with(entry.discriminator, NATIVE)?; - jitdump_file.write_all(entry.filename.as_bytes())?; - jitdump_file.write_all(b"\0")?; + self.jitdump_file.iowrite_with(entry.address, NATIVE)?; + self.jitdump_file.iowrite_with(entry.line, NATIVE)?; + self.jitdump_file + .iowrite_with(entry.discriminator, NATIVE)?; + self.jitdump_file.write_all(entry.filename.as_bytes())?; + self.jitdump_file.write_all(b"\0")?; } Ok(()) } @@ -278,25 +306,27 @@ impl JitDumpAgent { /// Sent when a method is compiled and loaded into memory by the VM. pub fn module_load( &mut self, - module_name: &str, - addr: *const u8, - len: usize, + module: &Module, + functions: &PrimaryMap, dbg_image: Option<&[u8]>, ) -> () { let pid = process::id(); let tid = pid; // ThreadId does appear to track underlying thread. Using PID. - if let Some(img) = &dbg_image { - if let Err(err) = self.dump_from_debug_image(img, module_name, addr, len, pid, tid) { - println!( - "Jitdump: module_load failed dumping from debug image: {:?}\n", - err - ); + for (idx, func) in functions.iter() { + let (addr, len) = unsafe { ((**func).as_ptr() as *const u8, (**func).len()) }; + if let Some(img) = &dbg_image { + if let Err(err) = self.dump_from_debug_image(img, "wasm", addr, len, pid, tid) { + println!( + "Jitdump: module_load failed dumping from debug image: {:?}\n", + err + ); + } + } else { + let timestamp = self.get_time_stamp(); + let name = super::debug_name(module, idx); + self.dump_code_load_record(&name, addr, len, timestamp, pid, tid); } - } else { - let mut timestamp: u64 = 0; - JitDumpAgent::get_time_stamp(&mut timestamp); - self.dump_code_load_record(module_name, addr, len, timestamp, pid, tid); } } @@ -347,7 +377,7 @@ impl JitDumpAgent { len: usize, pid: u32, tid: u32, - ) -> Result<(), JitDumpError> { + ) -> Result<()> { let file = object::File::parse(&dbg_image).unwrap(); let endian = if file.is_little_endian() { gimli::RunTimeEndian::Little @@ -355,7 +385,7 @@ impl JitDumpAgent { gimli::RunTimeEndian::Big }; - let load_section = |id: gimli::SectionId| -> Result, JitDumpError> { + let load_section = |id: gimli::SectionId| -> Result> { Ok(file .section_data_by_name(id.name()) .unwrap_or(borrow::Cow::Borrowed(&[][..]))) @@ -385,8 +415,7 @@ impl JitDumpAgent { break; } if !self.dump_funcs { - let mut timestamp: u64 = 0; - JitDumpAgent::get_time_stamp(&mut timestamp); + let timestamp = self.get_time_stamp(); self.dump_code_load_record(module_name, addr, len, timestamp, pid, tid); } Ok(()) @@ -401,7 +430,7 @@ impl JitDumpAgent { len: usize, pid: u32, tid: u32, - ) -> Result<(), JitDumpError> { + ) -> Result<()> { let mut depth = 0; let mut entries = unit.entries(); while let Some((delta_depth, entry)) = entries.next_dfs()? { @@ -486,9 +515,7 @@ impl JitDumpAgent { self.code_index += 1; self.dump_debug_info(&unit, &dwarf, clr.address, clr.size, None)?; - let mut timestamp: u64 = 0; - JitDumpAgent::get_time_stamp(&mut timestamp); - clr.header.timestamp = timestamp; + clr.header.timestamp = self.get_time_stamp(); unsafe { let code_buffer: &[u8] = @@ -570,9 +597,8 @@ impl JitDumpAgent { address: u64, size: u64, file_suffix: Option<&str>, - ) -> Result<(), JitDumpError> { - let mut timestamp: u64 = 0; - JitDumpAgent::get_time_stamp(&mut timestamp); + ) -> Result<()> { + let timestamp = self.get_time_stamp(); if let Some(program) = unit.line_program.clone() { let mut debug_info_record = DebugInfoRecord { header: RecordHeader { @@ -636,60 +662,17 @@ impl JitDumpAgent { } } -use crate::ProfilingAgent; -impl ProfilingAgent for JitDumpAgent { - fn module_load( - &mut self, - module_name: &str, - addr: *const u8, - len: usize, - dbg_image: Option<&[u8]>, - ) -> () { - if self.jitdump_file.is_none() { - if JitDumpAgent::init(self).ok().is_some() { - JitDumpAgent::module_load(self, module_name, addr, len, dbg_image); - } else { - println!("Jitdump: Failed to initialize JitDumpAgent\n"); - } - } else { - JitDumpAgent::module_load(self, module_name, addr, len, dbg_image); +impl Drop for State { + fn drop(&mut self) { + unsafe { + libc::munmap( + self.map_addr as *mut _, + libc::sysconf(libc::_SC_PAGESIZE) as usize, + ); } } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum JitDumpError { - GimliError(gimli::Error), - IOError, -} - -impl Error for JitDumpError { - fn description(&self) -> &str { - match *self { - JitDumpError::GimliError(ref err) => err.description(), - JitDumpError::IOError => "An I/O error occurred.", - } - } -} - -impl std::fmt::Display for JitDumpError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> { - Debug::fmt(self, f) - } -} - -impl From for JitDumpError { - fn from(err: gimli::Error) -> Self { - JitDumpError::GimliError(err) - } -} - -impl From for JitDumpError { - fn from(_err: std::io::Error) -> Self { - JitDumpError::IOError - } -} - trait Reader: gimli::Reader + Send + Sync {} impl<'input, Endian> Reader for gimli::EndianSlice<'input, Endian> where diff --git a/crates/profiling/src/lib.rs b/crates/profiling/src/lib.rs index 7e6c851a10..d708143140 100644 --- a/crates/profiling/src/lib.rs +++ b/crates/profiling/src/lib.rs @@ -1,40 +1,36 @@ use std::error::Error; use std::fmt; +use wasmtime_environ::entity::{EntityRef, PrimaryMap}; +use wasmtime_environ::wasm::DefinedFuncIndex; +use wasmtime_environ::Module; +use wasmtime_runtime::VMFunctionBody; -#[cfg(feature = "jitdump")] -mod jitdump; - -#[cfg(feature = "jitdump")] -pub use crate::jitdump::JitDumpAgent; - -#[cfg(not(feature = "jitdump"))] -pub type JitDumpAgent = NullProfilerAgent; - -/// Select which profiling technique to use -#[derive(Debug, Clone, Copy)] -pub enum ProfilingStrategy { - /// No profiler support - NullProfiler, - - /// Collect profile for jitdump file format - JitDumpProfiler, +cfg_if::cfg_if! { + if #[cfg(all(feature = "jitdump", target_os = "linux"))] { + #[path = "jitdump_linux.rs"] + mod jitdump; + } else { + #[path = "jitdump_disabled.rs"] + mod jitdump; + } } +pub use crate::jitdump::JitDumpAgent; + /// Common interface for profiling tools. -pub trait ProfilingAgent { +pub trait ProfilingAgent: Send + Sync + 'static { /// Notify the profiler of a new module loaded into memory fn module_load( - &mut self, - module_name: &str, - addr: *const u8, - len: usize, + &self, + module: &Module, + functions: &PrimaryMap, dbg_image: Option<&[u8]>, ) -> (); } /// Default agent for unsupported profiling build. #[derive(Debug, Default, Clone, Copy)] -pub struct NullProfilerAgent {} +pub struct NullProfilerAgent; #[derive(Debug)] struct NullProfilerAgentError; @@ -55,11 +51,19 @@ impl Error for NullProfilerAgentError { impl ProfilingAgent for NullProfilerAgent { fn module_load( - &mut self, - _module_name: &str, - _addr: *const u8, - _len: usize, + &self, + _module: &Module, + _functions: &PrimaryMap, _dbg_image: Option<&[u8]>, ) -> () { } } + +#[allow(dead_code)] +fn debug_name(module: &Module, index: DefinedFuncIndex) -> String { + let index = module.local.func_index(index); + match module.func_names.get(&index) { + Some(s) => s.clone(), + None => format!("wasm::wasm-function[{}]", index.index()), + } +} diff --git a/crates/runtime/Cargo.toml b/crates/runtime/Cargo.toml index 6a40360d28..516498566a 100644 --- a/crates/runtime/Cargo.toml +++ b/crates/runtime/Cargo.toml @@ -11,7 +11,6 @@ readme = "README.md" edition = "2018" [dependencies] -wasmtime-profiling = { path = "../profiling", version = "0.12.0" } wasmtime-environ = { path = "../environ", version = "0.12.0" } region = "2.0.0" libc = { version = "0.2.60", default-features = false } diff --git a/crates/wasi/Cargo.toml b/crates/wasi/Cargo.toml index 47fae58778..3a84c96bcb 100644 --- a/crates/wasi/Cargo.toml +++ b/crates/wasi/Cargo.toml @@ -14,7 +14,7 @@ edition = "2018" anyhow = "1.0" log = { version = "0.4.8", default-features = false } wasi-common = { path = "../wasi-common", version = "0.12.0" } -wasmtime = { path = "../api", version = "0.12.0" } +wasmtime = { path = "../api", version = "0.12.0", default-features = false } wasmtime-runtime = { path = "../runtime", version = "0.12.0" } wig = { path = "../wasi-common/wig", version = "0.12.0" } diff --git a/crates/wast/Cargo.toml b/crates/wast/Cargo.toml index 4425b2f75e..917ed3c989 100644 --- a/crates/wast/Cargo.toml +++ b/crates/wast/Cargo.toml @@ -12,11 +12,11 @@ edition = "2018" [dependencies] anyhow = "1.0.19" -wasmtime = { path = "../api", version = "0.12.0" } +wasmtime = { path = "../api", version = "0.12.0", default-features = false } wast = "11.0.0" [badges] maintenance = { status = "actively-developed" } [features] -lightbeam = [] +lightbeam = ["wasmtime/lightbeam"] diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index ab60ce0bd7..9db228cf27 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -6,6 +6,7 @@ - [Running `hello-world.wasm`](./tutorial-run-hello-world.md) - [Examples](./examples.md) - [Markdown parser](./examples-markdown.md) + - [Profiling WebAssembly](./examples-profiling.md) - [Using WebAssembly from your lanugage](./lang.md) - [Python](./lang-python.md) - [.NET](./lang-dotnet.md) diff --git a/docs/assets/perf-annotate-fib.png b/docs/assets/perf-annotate-fib.png new file mode 100755 index 0000000000000000000000000000000000000000..be296a76742348ef311b3a3bf09f2c352f4e2bd5 GIT binary patch literal 59722 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

vGrX9uH9Zr2EU8dY|@ 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 ZX#o5i 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 literal 0 HcmV?d00001 diff --git a/docs/assets/perf-report-fib.png b/docs/assets/perf-report-fib.png new file mode 100755 index 0000000000000000000000000000000000000000..2589b77cf8538066e45500f438b02052c57bd215 GIT binary patch literal 27649 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 literal 0 HcmV?d00001 diff --git a/docs/examples-profiling.md b/docs/examples-profiling.md new file mode 100644 index 0000000000..36666dae57 --- /dev/null +++ b/docs/examples-profiling.md @@ -0,0 +1,152 @@ +# Profiling WebAssembly + +One of WebAssembly's major goals is to be quite close to native code in terms of +performance, so typically when executing wasm you'll be quite interested in how +well your wasm module is performing! From time to time you might want to dive a +bit deeper into the performance of your wasm, and this is where profiling comes +into the picture. + +Profiling support in Wasmtime is still under development, but if you're using a +supported profiler this example is targeted at helping you get some more +information about the performance of your wasm. + +## Using `perf` on Linux + +One profiler supported by Wasmtime is the [`perf` +profiler](https://perf.wiki.kernel.org/index.php/Main_Page) for Linux. This is +an extremely powerful profiler with lots of documentation on the web, but for +the rest of this section we'll assume you're running on Linux and already have +`perf` installed. + +Profiling support with `perf` uses the "jitdump" support in the `perf` CLI. This +requires runtime support from Wasmtime itself, so you will need to manually +change a few things to enable profiling support in your application. First +you'll want to make sure that Wasmtime is compiled with the `jitdump` Cargo +feature (which is enabled by default). Otherwise enabling runtime support +depends on how you're using Wasmtime: + +* **Rust API** - you'll want to call the [`Config::profiler`] method with + `ProfilingStrategy::JitDump` to enable profiling of your wasm modules. + +* **C API** - you'll want to call the `wasmtime_config_profiler_set` API with a + `WASMTIME_PROFILING_STRATEGY_JITDUMP` value. + +* **Command Line** - you'll want to pass the `--jitdump` flag on the command + line. + +Once jitdump support is enabled, you'll use `perf record` like usual to record +your application's performance. You'll need to also be sure to pass the +`--clockid mono` or `-k mono` flag to `perf record`. + +For example if you're using the CLI, you'll execute: + +```sh +$ perf record -k mono wasmtime --jitdump foo.wasm +``` + +This will create a `perf.data` file as per usual, but it will *also* create a +`jit-XXXX.dump` file. This extra `*.dump` file is the jitdump file which is +specified by `perf` and Wasmtime generates at runtime. + +The next thing you need to do is to merge the `*.dump` file into the +`perf.data` file, which you can do with the `perf inject` command: + +```sh +$ perf inject --jit --input perf.data --output perf.jit.data +``` + +This will read `perf.data`, automatically pick up the `*.dump` file that's +correct, and then create `perf.jit.data` which merges all the JIT information +together. This should also create a lot of `jitted-XXXX-N.so` files in the +current directory which are ELF images for all the JIT functions that were +created by Wasmtime. + +After that you can explore the `perf.jit.data` profile as you usually would, +for example with: + +```sh +$ perf report --input perf.jit.data +``` + +You should be able to annotate wasm functions and see their raw assembly. You +should also see entries for wasm functions show up as one function and the +name of each function matches the debug name section in the wasm file. + +Note that support for jitdump is still relatively new in Wasmtime, so if you +have any problems, please don't hesitate to [file an issue]! + +[file an issue]: https://github.com/bytecodealliance/wasmtime/issues/new + +### `perf` and DWARF information + +If the jitdump profile doesn't give you enough information by default, you can +also enable dwarf debug information to be generated for JIT code which should +give the `perf` profiler more information about what's being profiled. This can +include information like more desriptive function names, filenames, and line +numbers. + +Enabling dwarf debug information for JIT code depends on how you're using +Wasmtime: + +* **Rust API** - you'll want to call the [`Config::debug_info`] method. + +* **C API** - you'll want to call the `wasmtime_config_debug_info_set` API. + +* **Command Line** - you'll want to pass the `-g` flag on the command line. + +You shouldn't need to do anything else to get this information into `perf`. The +perf collection data should automatically pick up all this dwarf debug +information. + +### `perf` example + +Let's run through a quick example with `perf` to get the feel for things. First +let's take a look at some wasm: + +```rust +fn main() { + let n = 42; + println!("fib({}) = {}", n, fib(n)); +} + +fn fib(n: u32) -> u32 { + if n <= 2 { + 1 + } else { + fib(n - 1) + fib(n - 2) + } +} +``` + +To collect perf information for this wasm module we'll execute: + +```sh +$ rustc --target wasm32-wasi fib.rs -O +$ perf record -k mono wasmtime --jitdump fib.wasm +fib(42) = 267914296 +[ perf record: Woken up 1 times to write data ] +[ perf record: Captured and wrote 0.147 MB perf.data (3435 samples) ] +$ perf inject --jit --input perf.data --output perf.jit.data +``` + +And we should have all out information now! We can execute `perf report` for +example to see that 99% of our runtime (as expected) is spent in our `fib` +function. Note that the symbol has been demangled to `fib::fib` which is what +the Rust symbol is: + +``` +$ perf report --input perf.jit-data +``` + +![perf report output](assets/perf-report-fib.png) + +Alternatively we could also use `perf annotate` to take a look at the +disassembly of the `fib` function, seeing what the JIT generated: + +``` +$ perf annotate --input perf.jit-data +``` + +![perf annotate output](assets/perf-annotate-fib.png) + +[`Config::debug_info`]: https://bytecodealliance.github.io/wasmtime/api/wasmtime/struct.Config.html#method.debug_info diff --git a/src/lib.rs b/src/lib.rs index 89c5f417a5..db0d65b72c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,8 +30,7 @@ mod obj; use anyhow::{bail, Result}; use std::path::PathBuf; use structopt::StructOpt; -use wasmtime::{Config, Strategy}; -use wasmtime_profiling::ProfilingStrategy; +use wasmtime::{Config, ProfilingStrategy, Strategy}; pub use obj::compile_to_obj; @@ -46,8 +45,8 @@ fn pick_compilation_strategy(cranelift: bool, lightbeam: bool) -> Result Result { Ok(match jitdump { - true => ProfilingStrategy::JitDumpProfiler, - false => ProfilingStrategy::NullProfiler, + true => ProfilingStrategy::JitDump, + false => ProfilingStrategy::None, }) } diff --git a/tests/instantiate.rs b/tests/instantiate.rs deleted file mode 100644 index 961b219e8d..0000000000 --- a/tests/instantiate.rs +++ /dev/null @@ -1,40 +0,0 @@ -use more_asserts::assert_gt; -use std::path::PathBuf; -use wasmtime_environ::settings; -use wasmtime_environ::settings::Configurable; -use wasmtime_environ::CacheConfig; -use wasmtime_jit::{instantiate, native, CompilationStrategy, Compiler, NullResolver}; - -const PATH_MODULE_RS2WASM_ADD_FUNC: &str = r"tests/wasm/rs2wasm-add-func.wat"; - -/// Simple test reading a wasm-file and translating to binary representation. -#[test] -fn test_environ_translate() { - let path = PathBuf::from(PATH_MODULE_RS2WASM_ADD_FUNC); - let data = wat::parse_file(path).expect("expecting valid wat-file"); - assert_gt!(data.len(), 0); - - let mut flag_builder = settings::builder(); - flag_builder.enable("enable_verifier").unwrap(); - - let isa_builder = native::builder(); - let isa = isa_builder.finish(settings::Flags::new(flag_builder)); - - let mut resolver = NullResolver {}; - let cache_config = CacheConfig::new_cache_disabled(); - let mut compiler = Compiler::new(isa, CompilationStrategy::Auto, cache_config); - unsafe { - let instance = instantiate( - &mut compiler, - &data, - &mut resolver, - // Bulk memory. - false, - // Debug info. - false, - // Profiler. - None, - ); - assert!(instance.is_ok()); - } -} diff --git a/tests/misc_testsuite/rs2wasm-add-func.wast b/tests/misc_testsuite/rs2wasm-add-func.wast new file mode 100644 index 0000000000..21ff7f5d7a --- /dev/null +++ b/tests/misc_testsuite/rs2wasm-add-func.wast @@ -0,0 +1,20 @@ +(module + (type (;0;) (func)) + (type (;1;) (func (param i32 i32) (result i32))) + (func $add (type 1) (param i32 i32) (result i32) + get_local 1 + get_local 0 + i32.add) + (func $start (type 0)) + (table (;0;) 1 1 anyfunc) + (memory (;0;) 17) + (global (;0;) i32 (i32.const 1049114)) + (global (;1;) i32 (i32.const 1049114)) + (export "memory" (memory 0)) + (export "__indirect_function_table" (table 0)) + (export "__heap_base" (global 0)) + (export "__data_end" (global 1)) + (export "add" (func $add)) + (export "start" (func $start)) + (data (i32.const 1048576) "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") + (data (i32.const 1049092) "invalid malloc request"))