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

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