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<dyn ProfilingAgent>` instead of
  `Option<Arc<Mutex<Box<dyn ProfilingAgent>>>>`

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!
This commit is contained in:
Alex Crichton
2020-03-20 11:44:51 -05:00
committed by GitHub
parent 0a30fdf85f
commit 3b7cb6ee64
27 changed files with 488 additions and 325 deletions

6
Cargo.lock generated
View File

@@ -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",
]

View File

@@ -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]

View File

@@ -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

View File

@@ -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::*;

View File

@@ -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 {

View File

@@ -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<Arc<Mutex<Box<dyn ProfilingAgent + Send>>>>,
pub(crate) profiler: Arc<dyn ProfilingAgent>,
}
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<dyn ProfilingAgent>,
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

View File

@@ -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"]

View File

@@ -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)
///////////////////////////////////////////////////////////////////////////////

View File

@@ -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,

View File

@@ -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" }

View File

@@ -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<dyn ProfilingAgent + Send>,
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);
}
}
}
}

View File

@@ -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<dyn ProfilingAgent + Send>,
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

View File

@@ -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<Mutex<Box<dyn ProfilingAgent + Send>>>>,
profiler: &dyn ProfilingAgent,
) -> Result<Self, SetupError> {
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, &region_name, Some(&dbg))
}
_ => compiler.profiler_module_load(&mut profiler, &region_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<Mutex<Box<dyn ProfilingAgent + Send>>>>,
profiler: &dyn ProfilingAgent,
) -> Result<Self, SetupError> {
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<Mutex<Box<dyn ProfilingAgent + Send>>>>,
profiler: &dyn ProfilingAgent,
) -> Result<InstanceHandle, SetupError> {
let instance = CompiledModule::new(compiler, data, debug_info, profiler)?.instantiate(
is_bulk_memory,

View File

@@ -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']

View File

@@ -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<Self> {
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<DefinedFuncIndex, *mut [VMFunctionBody]>,
_dbg_image: Option<&[u8]>,
) {
}
}

View File

@@ -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<State>,
}
struct State {
/// File instance for the jit dump file
pub jitdump_file: Option<File>,
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<dyn Error>> {
#[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<Self> {
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<DefinedFuncIndex, *mut [VMFunctionBody]>,
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::<FileHeader>() 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::<FileHeader>() 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<DebugEntry>,
) -> Result<(), JitDumpError> {
fn write_debug_info_entries(&mut self, die_entries: Vec<DebugEntry>) -> 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<DefinedFuncIndex, *mut [VMFunctionBody]>,
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<borrow::Cow<[u8]>, JitDumpError> {
let load_section = |id: gimli::SectionId| -> Result<borrow::Cow<[u8]>> {
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<gimli::Error> for JitDumpError {
fn from(err: gimli::Error) -> Self {
JitDumpError::GimliError(err)
}
}
impl From<std::io::Error> for JitDumpError {
fn from(_err: std::io::Error) -> Self {
JitDumpError::IOError
}
}
trait Reader: gimli::Reader<Offset = usize> + Send + Sync {}
impl<'input, Endian> Reader for gimli::EndianSlice<'input, Endian> where

View File

@@ -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<DefinedFuncIndex, *mut [VMFunctionBody]>,
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<DefinedFuncIndex, *mut [VMFunctionBody]>,
_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()),
}
}

View File

@@ -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 }

View File

@@ -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" }

View File

@@ -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"]

View File

@@ -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)

BIN
docs/assets/perf-annotate-fib.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
docs/assets/perf-report-fib.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

152
docs/examples-profiling.md Normal file
View File

@@ -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

View File

@@ -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<Strateg
fn pick_profiling_strategy(jitdump: bool) -> Result<ProfilingStrategy> {
Ok(match jitdump {
true => ProfilingStrategy::JitDumpProfiler,
false => ProfilingStrategy::NullProfiler,
true => ProfilingStrategy::JitDump,
false => ProfilingStrategy::None,
})
}

View File

@@ -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());
}
}

View File

@@ -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"))