Extract jit_int.rs and most of jitdump_linux.rs for use outside of wasmtime (#2744)
* Extract gdb jit_int into wasmtime-jit-debug * Move a big chunk of the jitdump code to wasmtime-jit-debug * Fix doc markdown in perf_jitdump.rs
This commit is contained in:
12
Cargo.lock
generated
12
Cargo.lock
generated
@@ -3628,10 +3628,20 @@ dependencies = [
|
|||||||
"target-lexicon",
|
"target-lexicon",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"wasmtime-environ",
|
"wasmtime-environ",
|
||||||
|
"wasmtime-jit-debug",
|
||||||
"wasmtime-runtime",
|
"wasmtime-runtime",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmtime-jit-debug"
|
||||||
|
version = "0.34.0"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"object",
|
||||||
|
"rustix",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasmtime-runtime"
|
name = "wasmtime-runtime"
|
||||||
version = "0.34.0"
|
version = "0.34.0"
|
||||||
@@ -3641,7 +3651,6 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"lazy_static",
|
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"mach",
|
"mach",
|
||||||
@@ -3655,6 +3664,7 @@ dependencies = [
|
|||||||
"userfaultfd",
|
"userfaultfd",
|
||||||
"wasmtime-environ",
|
"wasmtime-environ",
|
||||||
"wasmtime-fiber",
|
"wasmtime-fiber",
|
||||||
|
"wasmtime-jit-debug",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
25
crates/jit-debug/Cargo.toml
Normal file
25
crates/jit-debug/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
[package]
|
||||||
|
name = "wasmtime-jit-debug"
|
||||||
|
version = "0.34.0"
|
||||||
|
authors = ["The Wasmtime Project Developers"]
|
||||||
|
description = "JIT debug interfaces support for Wasmtime"
|
||||||
|
license = "Apache-2.0 WITH LLVM-exception"
|
||||||
|
categories = ["development-tools::debugging"]
|
||||||
|
keywords = ["gdb", "jit"]
|
||||||
|
repository = "https://github.com/bytecodealliance/wasmtime"
|
||||||
|
readme = "README.md"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
lazy_static = {version = "1.3.0", optional = true }
|
||||||
|
object = { version = "0.27.0", default-features = false, features = ["std", "read_core"], optional = true }
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
rustix = { version = "0.33.0", optional = true }
|
||||||
|
|
||||||
|
[badges]
|
||||||
|
maintenance = { status = "actively-developed" }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
gdb_jit_int = ["lazy_static"]
|
||||||
|
perf_jitdump = ["rustix", "object"]
|
||||||
1
crates/jit-debug/README.md
Normal file
1
crates/jit-debug/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
This is the `wasmtime-jit-debug` crate, which contains JIT debug interfaces support for Wasmtime.
|
||||||
5
crates/jit-debug/src/lib.rs
Normal file
5
crates/jit-debug/src/lib.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#[cfg(feature = "gdb_jit_int")]
|
||||||
|
pub mod gdb_jit_int;
|
||||||
|
|
||||||
|
#[cfg(all(feature = "perf_jitdump", target_os = "linux"))]
|
||||||
|
pub mod perf_jitdump;
|
||||||
293
crates/jit-debug/src/perf_jitdump.rs
Normal file
293
crates/jit-debug/src/perf_jitdump.rs
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
//! Support for jitdump files which can be used by perf for profiling jitted code.
|
||||||
|
//! Spec definitions for the output format is as described here:
|
||||||
|
//! <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/perf/Documentation/jitdump-specification.txt>
|
||||||
|
//!
|
||||||
|
//! Usage Example:
|
||||||
|
//! Record
|
||||||
|
//! sudo perf record -k 1 -e instructions:u target/debug/wasmtime -g --jitdump test.wasm
|
||||||
|
//! Combine
|
||||||
|
//! sudo perf inject -v -j -i perf.data -o perf.jit.data
|
||||||
|
//! Report
|
||||||
|
//! sudo perf report -i perf.jit.data -F+period,srcline
|
||||||
|
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::fs::{File, OpenOptions};
|
||||||
|
use std::io;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::ptr;
|
||||||
|
use std::{mem, process};
|
||||||
|
|
||||||
|
/// Defines jitdump record types
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum RecordId {
|
||||||
|
/// Value 0: JIT_CODE_LOAD: record describing a jitted function
|
||||||
|
JitCodeLoad = 0,
|
||||||
|
/// Value 1: JIT_CODE_MOVE: record describing an already jitted function which is moved
|
||||||
|
_JitCodeMove = 1,
|
||||||
|
/// Value 2: JIT_CODE_DEBUG_INFO: record describing the debug information for a jitted function
|
||||||
|
JitCodeDebugInfo = 2,
|
||||||
|
/// Value 3: JIT_CODE_CLOSE: record marking the end of the jit runtime (optional)
|
||||||
|
_JitCodeClose = 3,
|
||||||
|
/// Value 4: JIT_CODE_UNWINDING_INFO: record describing a function unwinding information
|
||||||
|
_JitCodeUnwindingInfo = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Each record starts with this fixed size record header which describes the record that follows
|
||||||
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct RecordHeader {
|
||||||
|
/// uint32_t id: a value identifying the record type (see below)
|
||||||
|
pub id: u32,
|
||||||
|
/// uint32_t total_size: the size in bytes of the record including the header.
|
||||||
|
pub record_size: u32,
|
||||||
|
/// uint64_t timestamp: a timestamp of when the record was created.
|
||||||
|
pub timestamp: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl object::Pod for RecordHeader {}
|
||||||
|
|
||||||
|
/// The CodeLoadRecord is used for describing jitted functions
|
||||||
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct CodeLoadRecord {
|
||||||
|
/// Fixed sized header that describes this record
|
||||||
|
pub header: RecordHeader,
|
||||||
|
/// `uint32_t pid`: OS process id of the runtime generating the jitted code
|
||||||
|
pub pid: u32,
|
||||||
|
/// `uint32_t tid`: OS thread identification of the runtime thread generating the jitted code
|
||||||
|
pub tid: u32,
|
||||||
|
/// `uint64_t vma`: virtual address of jitted code start
|
||||||
|
pub virtual_address: u64,
|
||||||
|
/// `uint64_t code_addr`: code start address for the jitted code. By default vma = code_addr
|
||||||
|
pub address: u64,
|
||||||
|
/// `uint64_t code_size`: size in bytes of the generated jitted code
|
||||||
|
pub size: u64,
|
||||||
|
/// `uint64_t code_index`: unique identifier for the jitted code (see below)
|
||||||
|
pub index: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl object::Pod for CodeLoadRecord {}
|
||||||
|
|
||||||
|
/// Describes source line information for a jitted function
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct DebugEntry {
|
||||||
|
/// `uint64_t code_addr`: address of function for which the debug information is generated
|
||||||
|
pub address: u64,
|
||||||
|
/// `uint32_t line`: source file line number (starting at 1)
|
||||||
|
pub line: u32,
|
||||||
|
/// `uint32_t discrim`: column discriminator, 0 is default
|
||||||
|
pub discriminator: u32,
|
||||||
|
/// `char name[n]`: source file name in ASCII, including null termination
|
||||||
|
pub filename: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describes debug information for a jitted function. An array of debug entries are
|
||||||
|
/// appended to this record during writting. Note, this record must preceed the code
|
||||||
|
/// load record that describes the same jitted function.
|
||||||
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct DebugInfoRecord {
|
||||||
|
/// Fixed sized header that describes this record
|
||||||
|
pub header: RecordHeader,
|
||||||
|
/// `uint64_t code_addr`: address of function for which the debug information is generated
|
||||||
|
pub address: u64,
|
||||||
|
/// `uint64_t nr_entry`: number of debug entries for the function appended to this record
|
||||||
|
pub count: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl object::Pod for DebugInfoRecord {}
|
||||||
|
|
||||||
|
/// Fixed-sized header for each jitdump file
|
||||||
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct FileHeader {
|
||||||
|
/// `uint32_t magic`: a magic number tagging the file type. The value is 4-byte long and represents the
|
||||||
|
/// string "JiTD" in ASCII form. It is 0x4A695444 or 0x4454694a depending on the endianness. The field can
|
||||||
|
/// be used to detect the endianness of the file
|
||||||
|
pub magic: u32,
|
||||||
|
/// `uint32_t version`: a 4-byte value representing the format version. It is currently set to 2
|
||||||
|
pub version: u32,
|
||||||
|
/// `uint32_t total_size`: size in bytes of file header
|
||||||
|
pub size: u32,
|
||||||
|
/// `uint32_t elf_mach`: ELF architecture encoding (ELF e_machine value as specified in /usr/include/elf.h)
|
||||||
|
pub e_machine: u32,
|
||||||
|
/// `uint32_t pad1`: padding. Reserved for future use
|
||||||
|
pub pad1: u32,
|
||||||
|
/// `uint32_t pid`: JIT runtime process identification (OS specific)
|
||||||
|
pub pid: u32,
|
||||||
|
/// `uint64_t timestamp`: timestamp of when the file was created
|
||||||
|
pub timestamp: u64,
|
||||||
|
/// `uint64_t flags`: a bitmask of flags
|
||||||
|
pub flags: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl object::Pod for FileHeader {}
|
||||||
|
|
||||||
|
/// Interface for driving the creation of jitdump files
|
||||||
|
pub struct JitDumpFile {
|
||||||
|
/// File instance for the jit dump file
|
||||||
|
jitdump_file: File,
|
||||||
|
|
||||||
|
map_addr: usize,
|
||||||
|
|
||||||
|
/// Unique identifier for jitted code
|
||||||
|
code_index: u64,
|
||||||
|
|
||||||
|
e_machine: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JitDumpFile {
|
||||||
|
/// Intialize a JitDumpAgent and write out the header
|
||||||
|
pub fn new(filename: impl AsRef<Path>, e_machine: u32) -> io::Result<Self> {
|
||||||
|
let jitdump_file = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.truncate(true)
|
||||||
|
.open(filename.as_ref())?;
|
||||||
|
|
||||||
|
// 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 = rustix::io::mmap(
|
||||||
|
ptr::null_mut(),
|
||||||
|
rustix::process::page_size(),
|
||||||
|
rustix::io::ProtFlags::EXEC | rustix::io::ProtFlags::READ,
|
||||||
|
rustix::io::MapFlags::PRIVATE,
|
||||||
|
&jitdump_file,
|
||||||
|
0,
|
||||||
|
)?;
|
||||||
|
ptr as usize
|
||||||
|
};
|
||||||
|
let mut state = JitDumpFile {
|
||||||
|
jitdump_file,
|
||||||
|
map_addr,
|
||||||
|
code_index: 0,
|
||||||
|
e_machine,
|
||||||
|
};
|
||||||
|
state.write_file_header()?;
|
||||||
|
Ok(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JitDumpFile {
|
||||||
|
/// Returns timestamp from a single source
|
||||||
|
pub 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.
|
||||||
|
let ts = rustix::time::clock_gettime(rustix::time::ClockId::Monotonic);
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the next code index
|
||||||
|
pub fn next_code_index(&mut self) -> u64 {
|
||||||
|
let code_index = self.code_index;
|
||||||
|
self.code_index += 1;
|
||||||
|
code_index
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_file_header(&mut self) -> io::Result<()> {
|
||||||
|
let header = FileHeader {
|
||||||
|
timestamp: self.get_time_stamp(),
|
||||||
|
e_machine: self.e_machine,
|
||||||
|
magic: 0x4A695444,
|
||||||
|
version: 1,
|
||||||
|
size: mem::size_of::<FileHeader>() as u32,
|
||||||
|
pad1: 0,
|
||||||
|
pid: process::id(),
|
||||||
|
flags: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.jitdump_file.write_all(object::bytes_of(&header))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_code_load_record(
|
||||||
|
&mut self,
|
||||||
|
record_name: &str,
|
||||||
|
cl_record: CodeLoadRecord,
|
||||||
|
code_buffer: &[u8],
|
||||||
|
) -> io::Result<()> {
|
||||||
|
self.jitdump_file.write_all(object::bytes_of(&cl_record))?;
|
||||||
|
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.
|
||||||
|
pub fn write_debug_info_record(&mut self, dir_record: DebugInfoRecord) -> io::Result<()> {
|
||||||
|
self.jitdump_file.write_all(object::bytes_of(&dir_record))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write DebugInfoRecord to open jit dump file.
|
||||||
|
/// Must be written before the corresponding CodeLoadRecord.
|
||||||
|
pub fn write_debug_info_entries(&mut self, die_entries: Vec<DebugEntry>) -> io::Result<()> {
|
||||||
|
for entry in die_entries.iter() {
|
||||||
|
self.jitdump_file
|
||||||
|
.write_all(object::bytes_of(&entry.address))?;
|
||||||
|
self.jitdump_file.write_all(object::bytes_of(&entry.line))?;
|
||||||
|
self.jitdump_file
|
||||||
|
.write_all(object::bytes_of(&entry.discriminator))?;
|
||||||
|
self.jitdump_file.write_all(entry.filename.as_bytes())?;
|
||||||
|
self.jitdump_file.write_all(b"\0")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dump_code_load_record(
|
||||||
|
&mut self,
|
||||||
|
method_name: &str,
|
||||||
|
addr: *const u8,
|
||||||
|
len: usize,
|
||||||
|
timestamp: u64,
|
||||||
|
pid: u32,
|
||||||
|
tid: u32,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
let name_len = method_name.len() + 1;
|
||||||
|
let size_limit = mem::size_of::<CodeLoadRecord>();
|
||||||
|
|
||||||
|
let rh = RecordHeader {
|
||||||
|
id: RecordId::JitCodeLoad as u32,
|
||||||
|
record_size: size_limit as u32 + name_len as u32 + len as u32,
|
||||||
|
timestamp,
|
||||||
|
};
|
||||||
|
|
||||||
|
let clr = CodeLoadRecord {
|
||||||
|
header: rh,
|
||||||
|
pid,
|
||||||
|
tid,
|
||||||
|
virtual_address: addr as u64,
|
||||||
|
address: addr as u64,
|
||||||
|
size: len as u64,
|
||||||
|
index: self.next_code_index(),
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let code_buffer: &[u8] = std::slice::from_raw_parts(addr, len);
|
||||||
|
self.write_code_load_record(method_name, clr, code_buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for JitDumpFile {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
rustix::io::munmap(self.map_addr as *mut _, rustix::process::page_size()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ edition = "2018"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasmtime-environ = { path = "../environ", version = "=0.34.0" }
|
wasmtime-environ = { path = "../environ", version = "=0.34.0" }
|
||||||
|
wasmtime-jit-debug = { path = "../jit-debug", version = "=0.34.0", features = ["perf_jitdump"], optional = true }
|
||||||
wasmtime-runtime = { path = "../runtime", version = "=0.34.0" }
|
wasmtime-runtime = { path = "../runtime", version = "=0.34.0" }
|
||||||
region = "2.2.0"
|
region = "2.2.0"
|
||||||
thiserror = "1.0.4"
|
thiserror = "1.0.4"
|
||||||
@@ -35,7 +36,7 @@ winapi = { version = "0.3.8", features = ["winnt", "impl-default"] }
|
|||||||
rustix = "0.33.0"
|
rustix = "0.33.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
jitdump = []
|
jitdump = ['wasmtime-jit-debug']
|
||||||
vtune = ['ittapi-rs']
|
vtune = ['ittapi-rs']
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
|
|||||||
@@ -14,124 +14,14 @@
|
|||||||
use crate::{CompiledModule, ProfilingAgent};
|
use crate::{CompiledModule, ProfilingAgent};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use object::{Object, ObjectSection};
|
use object::{Object, ObjectSection};
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::fs::{File, OpenOptions};
|
|
||||||
use std::io::Write;
|
|
||||||
use std::ptr;
|
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::{borrow, mem, process};
|
use std::{borrow, mem, process};
|
||||||
use target_lexicon::Architecture;
|
use target_lexicon::Architecture;
|
||||||
use wasmtime_environ::EntityRef;
|
use wasmtime_environ::EntityRef;
|
||||||
|
use wasmtime_jit_debug::perf_jitdump::*;
|
||||||
|
|
||||||
use object::elf;
|
use object::elf;
|
||||||
|
|
||||||
/// Defines jitdump record types
|
|
||||||
#[repr(u32)]
|
|
||||||
pub enum RecordId {
|
|
||||||
/// Value 0: JIT_CODE_LOAD: record describing a jitted function
|
|
||||||
JitCodeLoad = 0,
|
|
||||||
/// Value 1: JIT_CODE_MOVE: record describing an already jitted function which is moved
|
|
||||||
_JitCodeMove = 1,
|
|
||||||
/// Value 2: JIT_CODE_DEBUG_INFO: record describing the debug information for a jitted function
|
|
||||||
JitCodeDebugInfo = 2,
|
|
||||||
/// Value 3: JIT_CODE_CLOSE: record marking the end of the jit runtime (optional)
|
|
||||||
_JitCodeClose = 3,
|
|
||||||
/// Value 4: JIT_CODE_UNWINDING_INFO: record describing a function unwinding information
|
|
||||||
_JitCodeUnwindingInfo = 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Each record starts with this fixed size record header which describes the record that follows
|
|
||||||
#[derive(Debug, Default, Clone, Copy)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct RecordHeader {
|
|
||||||
/// uint32_t id: a value identifying the record type (see below)
|
|
||||||
id: u32,
|
|
||||||
/// uint32_t total_size: the size in bytes of the record including the header.
|
|
||||||
record_size: u32,
|
|
||||||
/// uint64_t timestamp: a timestamp of when the record was created.
|
|
||||||
timestamp: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl object::Pod for RecordHeader {}
|
|
||||||
|
|
||||||
/// The CodeLoadRecord is used for describing jitted functions
|
|
||||||
#[derive(Debug, Default, Clone, Copy)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct CodeLoadRecord {
|
|
||||||
/// Fixed sized header that describes this record
|
|
||||||
header: RecordHeader,
|
|
||||||
/// uint32_t pid: OS process id of the runtime generating the jitted code
|
|
||||||
pid: u32,
|
|
||||||
/// uint32_t tid: OS thread identification of the runtime thread generating the jitted code
|
|
||||||
tid: u32,
|
|
||||||
/// uint64_t vma: virtual address of jitted code start
|
|
||||||
virtual_address: u64,
|
|
||||||
/// uint64_t code_addr: code start address for the jitted code. By default vma = code_addr
|
|
||||||
address: u64,
|
|
||||||
/// uint64_t code_size: size in bytes of the generated jitted code
|
|
||||||
size: u64,
|
|
||||||
/// uint64_t code_index: unique identifier for the jitted code (see below)
|
|
||||||
index: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl object::Pod for CodeLoadRecord {}
|
|
||||||
|
|
||||||
/// Describes source line information for a jitted function
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct DebugEntry {
|
|
||||||
/// uint64_t code_addr: address of function for which the debug information is generated
|
|
||||||
address: u64,
|
|
||||||
/// uint32_t line: source file line number (starting at 1)
|
|
||||||
line: u32,
|
|
||||||
/// uint32_t discrim: column discriminator, 0 is default
|
|
||||||
discriminator: u32,
|
|
||||||
/// char name[n]: source file name in ASCII, including null termination
|
|
||||||
filename: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes debug information for a jitted function. An array of debug entries are
|
|
||||||
/// appended to this record during writting. Note, this record must preceed the code
|
|
||||||
/// load record that describes the same jitted function.
|
|
||||||
#[derive(Debug, Default, Clone, Copy)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct DebugInfoRecord {
|
|
||||||
/// Fixed sized header that describes this record
|
|
||||||
header: RecordHeader,
|
|
||||||
/// uint64_t code_addr: address of function for which the debug information is generated
|
|
||||||
address: u64,
|
|
||||||
/// uint64_t nr_entry: number of debug entries for the function appended to this record
|
|
||||||
count: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl object::Pod for DebugInfoRecord {}
|
|
||||||
|
|
||||||
/// Fixed-sized header for each jitdump file
|
|
||||||
#[derive(Debug, Default, Clone, Copy)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct FileHeader {
|
|
||||||
/// uint32_t magic: a magic number tagging the file type. The value is 4-byte long and represents the
|
|
||||||
/// string "JiTD" in ASCII form. It is 0x4A695444 or 0x4454694a depending on the endianness. The field can
|
|
||||||
/// be used to detect the endianness of the file
|
|
||||||
magic: u32,
|
|
||||||
/// uint32_t version: a 4-byte value representing the format version. It is currently set to 2
|
|
||||||
version: u32,
|
|
||||||
/// uint32_t total_size: size in bytes of file header
|
|
||||||
size: u32,
|
|
||||||
/// uint32_t elf_mach: ELF architecture encoding (ELF e_machine value as specified in /usr/include/elf.h)
|
|
||||||
e_machine: u32,
|
|
||||||
/// uint32_t pad1: padding. Reserved for future use
|
|
||||||
pad1: u32,
|
|
||||||
/// uint32_t pid: JIT runtime process identification (OS specific)
|
|
||||||
pid: u32,
|
|
||||||
/// uint64_t timestamp: timestamp of when the file was created
|
|
||||||
timestamp: u64,
|
|
||||||
/// uint64_t flags: a bitmask of flags
|
|
||||||
flags: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl object::Pod for FileHeader {}
|
|
||||||
|
|
||||||
/// Interface for driving the creation of jitdump files
|
/// Interface for driving the creation of jitdump files
|
||||||
pub struct JitDumpAgent {
|
pub struct JitDumpAgent {
|
||||||
// Note that we use a mutex internally to serialize writing out to our
|
// Note that we use a mutex internally to serialize writing out to our
|
||||||
@@ -141,13 +31,7 @@ pub struct JitDumpAgent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct State {
|
struct State {
|
||||||
/// File instance for the jit dump file
|
jitdump_file: JitDumpFile,
|
||||||
jitdump_file: File,
|
|
||||||
|
|
||||||
map_addr: usize,
|
|
||||||
|
|
||||||
/// Unique identifier for jitted code
|
|
||||||
code_index: u64,
|
|
||||||
|
|
||||||
/// Flag for experimenting with dumping code load record
|
/// Flag for experimenting with dumping code load record
|
||||||
/// after each function (true) or after each module. This
|
/// after each function (true) or after each module. This
|
||||||
@@ -159,42 +43,23 @@ impl JitDumpAgent {
|
|||||||
/// Intialize a JitDumpAgent and write out the header
|
/// Intialize a JitDumpAgent and write out the header
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
let filename = format!("./jit-{}.dump", process::id());
|
let filename = format!("./jit-{}.dump", process::id());
|
||||||
let jitdump_file = OpenOptions::new()
|
|
||||||
.read(true)
|
|
||||||
.write(true)
|
|
||||||
.create(true)
|
|
||||||
.truncate(true)
|
|
||||||
.open(&filename)?;
|
|
||||||
|
|
||||||
// After we make our `*.dump` file we execute an `mmap` syscall,
|
let e_machine = match target_lexicon::HOST.architecture {
|
||||||
// specifically with executable permissions, to map it into our address
|
Architecture::X86_64 => elf::EM_X86_64 as u32,
|
||||||
// space. This is required so `perf inject` will work later. The `perf
|
Architecture::X86_32(_) => elf::EM_386 as u32,
|
||||||
// inject` command will see that an mmap syscall happened, and it'll see
|
Architecture::Arm(_) => elf::EM_ARM as u32,
|
||||||
// the filename we mapped, and that'll trigger it to actually read and
|
Architecture::Aarch64(_) => elf::EM_AARCH64 as u32,
|
||||||
// parse the file.
|
Architecture::S390x => elf::EM_S390 as u32,
|
||||||
//
|
_ => unimplemented!("unrecognized architecture"),
|
||||||
// To match what some perf examples are doing we keep this `mmap` alive
|
|
||||||
// until this agent goes away.
|
|
||||||
let map_addr = unsafe {
|
|
||||||
let ptr = rustix::io::mmap(
|
|
||||||
ptr::null_mut(),
|
|
||||||
rustix::process::page_size(),
|
|
||||||
rustix::io::ProtFlags::EXEC | rustix::io::ProtFlags::READ,
|
|
||||||
rustix::io::MapFlags::PRIVATE,
|
|
||||||
&jitdump_file,
|
|
||||||
0,
|
|
||||||
)?;
|
|
||||||
ptr as usize
|
|
||||||
};
|
};
|
||||||
let mut state = State {
|
|
||||||
jitdump_file,
|
let jitdump_file = JitDumpFile::new(filename, e_machine)?;
|
||||||
map_addr,
|
|
||||||
code_index: 0,
|
|
||||||
dump_funcs: true,
|
|
||||||
};
|
|
||||||
state.write_file_header()?;
|
|
||||||
Ok(JitDumpAgent {
|
Ok(JitDumpAgent {
|
||||||
state: Mutex::new(state),
|
state: Mutex::new(State {
|
||||||
|
jitdump_file,
|
||||||
|
dump_funcs: true,
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -212,80 +77,6 @@ impl ProfilingAgent for JitDumpAgent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
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.
|
|
||||||
let ts = rustix::time::clock_gettime(rustix::time::ClockId::Monotonic);
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the ELF machine architecture.
|
|
||||||
fn get_e_machine(&self) -> u32 {
|
|
||||||
match target_lexicon::HOST.architecture {
|
|
||||||
Architecture::X86_64 => elf::EM_X86_64 as u32,
|
|
||||||
Architecture::X86_32(_) => elf::EM_386 as u32,
|
|
||||||
Architecture::Arm(_) => elf::EM_ARM as u32,
|
|
||||||
Architecture::Aarch64(_) => elf::EM_AARCH64 as u32,
|
|
||||||
Architecture::S390x => elf::EM_S390 as u32,
|
|
||||||
_ => unimplemented!("unrecognized architecture"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_file_header(&mut self) -> Result<()> {
|
|
||||||
let header = FileHeader {
|
|
||||||
timestamp: self.get_time_stamp(),
|
|
||||||
e_machine: self.get_e_machine(),
|
|
||||||
magic: 0x4A695444,
|
|
||||||
version: 1,
|
|
||||||
size: mem::size_of::<FileHeader>() as u32,
|
|
||||||
pad1: 0,
|
|
||||||
pid: process::id(),
|
|
||||||
flags: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.jitdump_file.write_all(object::bytes_of(&header))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_code_load_record(
|
|
||||||
&mut self,
|
|
||||||
record_name: &str,
|
|
||||||
cl_record: CodeLoadRecord,
|
|
||||||
code_buffer: &[u8],
|
|
||||||
) -> Result<()> {
|
|
||||||
self.jitdump_file.write_all(object::bytes_of(&cl_record))?;
|
|
||||||
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<()> {
|
|
||||||
self.jitdump_file.write_all(object::bytes_of(&dir_record))?;
|
|
||||||
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<()> {
|
|
||||||
for entry in die_entries.iter() {
|
|
||||||
self.jitdump_file
|
|
||||||
.write_all(object::bytes_of(&entry.address))?;
|
|
||||||
self.jitdump_file.write_all(object::bytes_of(&entry.line))?;
|
|
||||||
self.jitdump_file
|
|
||||||
.write_all(object::bytes_of(&entry.discriminator))?;
|
|
||||||
self.jitdump_file.write_all(entry.filename.as_bytes())?;
|
|
||||||
self.jitdump_file.write_all(b"\0")?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sent when a method is compiled and loaded into memory by the VM.
|
/// Sent when a method is compiled and loaded into memory by the VM.
|
||||||
pub fn module_load(&mut self, module: &CompiledModule, dbg_image: Option<&[u8]>) {
|
pub fn module_load(&mut self, module: &CompiledModule, dbg_image: Option<&[u8]>) {
|
||||||
let pid = process::id();
|
let pid = process::id();
|
||||||
@@ -301,18 +92,28 @@ impl State {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let timestamp = self.get_time_stamp();
|
let timestamp = self.jitdump_file.get_time_stamp();
|
||||||
let name = super::debug_name(module, idx);
|
let name = super::debug_name(module, idx);
|
||||||
self.dump_code_load_record(&name, addr, len, timestamp, pid, tid);
|
if let Err(err) = self
|
||||||
|
.jitdump_file
|
||||||
|
.dump_code_load_record(&name, addr, len, timestamp, pid, tid)
|
||||||
|
{
|
||||||
|
println!("Jitdump: write_code_load_failed_record failed: {:?}\n", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: these are the trampolines into exported functions.
|
// Note: these are the trampolines into exported functions.
|
||||||
for (idx, func, len) in module.trampolines() {
|
for (idx, func, len) in module.trampolines() {
|
||||||
let (addr, len) = (func as usize as *const u8, len);
|
let (addr, len) = (func as usize as *const u8, len);
|
||||||
let timestamp = self.get_time_stamp();
|
let timestamp = self.jitdump_file.get_time_stamp();
|
||||||
let name = format!("wasm::trampoline[{}]", idx.index());
|
let name = format!("wasm::trampoline[{}]", idx.index());
|
||||||
self.dump_code_load_record(&name, addr, len, timestamp, pid, tid);
|
if let Err(err) = self
|
||||||
|
.jitdump_file
|
||||||
|
.dump_code_load_record(&name, addr, len, timestamp, pid, tid)
|
||||||
|
{
|
||||||
|
println!("Jitdump: write_code_load_failed_record failed: {:?}\n", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,44 +125,12 @@ impl State {
|
|||||||
pid: u32,
|
pid: u32,
|
||||||
tid: u32,
|
tid: u32,
|
||||||
) {
|
) {
|
||||||
let timestamp = self.get_time_stamp();
|
let timestamp = self.jitdump_file.get_time_stamp();
|
||||||
self.dump_code_load_record(name, addr, size, timestamp, pid, tid);
|
if let Err(err) = self
|
||||||
}
|
.jitdump_file
|
||||||
|
.dump_code_load_record(&name, addr, size, timestamp, pid, tid)
|
||||||
fn dump_code_load_record(
|
{
|
||||||
&mut self,
|
println!("Jitdump: write_code_load_failed_record failed: {:?}\n", err);
|
||||||
method_name: &str,
|
|
||||||
addr: *const u8,
|
|
||||||
len: usize,
|
|
||||||
timestamp: u64,
|
|
||||||
pid: u32,
|
|
||||||
tid: u32,
|
|
||||||
) {
|
|
||||||
let name_len = method_name.len() + 1;
|
|
||||||
let size_limit = mem::size_of::<CodeLoadRecord>();
|
|
||||||
|
|
||||||
let rh = RecordHeader {
|
|
||||||
id: RecordId::JitCodeLoad as u32,
|
|
||||||
record_size: size_limit as u32 + name_len as u32 + len as u32,
|
|
||||||
timestamp,
|
|
||||||
};
|
|
||||||
|
|
||||||
let clr = CodeLoadRecord {
|
|
||||||
header: rh,
|
|
||||||
pid,
|
|
||||||
tid,
|
|
||||||
virtual_address: addr as u64,
|
|
||||||
address: addr as u64,
|
|
||||||
size: len as u64,
|
|
||||||
index: self.code_index,
|
|
||||||
};
|
|
||||||
self.code_index += 1;
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let code_buffer: &[u8] = std::slice::from_raw_parts(addr, len);
|
|
||||||
if let Err(err) = self.write_code_load_record(method_name, clr, code_buffer) {
|
|
||||||
println!("Jitdump: write_code_load_failed_record failed: {:?}\n", err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,8 +183,13 @@ impl State {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if !self.dump_funcs {
|
if !self.dump_funcs {
|
||||||
let timestamp = self.get_time_stamp();
|
let timestamp = self.jitdump_file.get_time_stamp();
|
||||||
self.dump_code_load_record(module_name, addr, len, timestamp, pid, tid);
|
if let Err(err) =
|
||||||
|
self.jitdump_file
|
||||||
|
.dump_code_load_record(module_name, addr, len, timestamp, pid, tid)
|
||||||
|
{
|
||||||
|
println!("Jitdump: write_code_load_failed_record failed: {:?}\n", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -510,16 +284,17 @@ impl State {
|
|||||||
clr.header.record_size = mem::size_of::<CodeLoadRecord>() as u32
|
clr.header.record_size = mem::size_of::<CodeLoadRecord>() as u32
|
||||||
+ (clr_name.len() + 1) as u32
|
+ (clr_name.len() + 1) as u32
|
||||||
+ clr.size as u32;
|
+ clr.size as u32;
|
||||||
clr.index = self.code_index;
|
clr.index = self.jitdump_file.next_code_index();
|
||||||
self.code_index += 1;
|
|
||||||
self.dump_debug_info(&unit, &dwarf, clr.address, clr.size, None)?;
|
self.dump_debug_info(&unit, &dwarf, clr.address, clr.size, None)?;
|
||||||
|
|
||||||
clr.header.timestamp = self.get_time_stamp();
|
clr.header.timestamp = self.jitdump_file.get_time_stamp();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let code_buffer: &[u8] =
|
let code_buffer: &[u8] =
|
||||||
std::slice::from_raw_parts(clr.address as *const u8, clr.size as usize);
|
std::slice::from_raw_parts(clr.address as *const u8, clr.size as usize);
|
||||||
let _ = self.write_code_load_record(&clr_name, clr, code_buffer);
|
let _ =
|
||||||
|
self.jitdump_file
|
||||||
|
.write_code_load_record(&clr_name, clr, code_buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -597,7 +372,7 @@ impl State {
|
|||||||
size: u64,
|
size: u64,
|
||||||
file_suffix: Option<&str>,
|
file_suffix: Option<&str>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let timestamp = self.get_time_stamp();
|
let timestamp = self.jitdump_file.get_time_stamp();
|
||||||
if let Some(program) = unit.line_program.clone() {
|
if let Some(program) = unit.line_program.clone() {
|
||||||
let mut debug_info_record = DebugInfoRecord {
|
let mut debug_info_record = DebugInfoRecord {
|
||||||
header: RecordHeader {
|
header: RecordHeader {
|
||||||
@@ -654,21 +429,13 @@ impl State {
|
|||||||
debug_info_record.header.record_size =
|
debug_info_record.header.record_size =
|
||||||
mem::size_of::<DebugInfoRecord>() as u32 + debug_entries_size as u32;
|
mem::size_of::<DebugInfoRecord>() as u32 + debug_entries_size as u32;
|
||||||
|
|
||||||
let _ = self.write_debug_info_record(debug_info_record);
|
let _ = self.jitdump_file.write_debug_info_record(debug_info_record);
|
||||||
let _ = self.write_debug_info_entries(debug_entries);
|
let _ = self.jitdump_file.write_debug_info_entries(debug_entries);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for State {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
rustix::io::munmap(self.map_addr as *mut _, rustix::process::page_size()).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait Reader: gimli::Reader<Offset = usize> + Send + Sync {}
|
trait Reader: gimli::Reader<Offset = usize> + Send + Sync {}
|
||||||
|
|
||||||
impl<'input, Endian> Reader for gimli::EndianSlice<'input, Endian> where
|
impl<'input, Endian> Reader for gimli::EndianSlice<'input, Endian> where
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ edition = "2018"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
wasmtime-environ = { path = "../environ", version = "=0.34.0" }
|
wasmtime-environ = { path = "../environ", version = "=0.34.0" }
|
||||||
wasmtime-fiber = { path = "../fiber", version = "=0.34.0", optional = true }
|
wasmtime-fiber = { path = "../fiber", version = "=0.34.0", optional = true }
|
||||||
|
wasmtime-jit-debug = { path = "../jit-debug", version = "=0.34.0", features = ["gdb_jit_int"] }
|
||||||
region = "2.1.0"
|
region = "2.1.0"
|
||||||
libc = { version = "0.2.112", default-features = false }
|
libc = { version = "0.2.112", default-features = false }
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
@@ -22,7 +23,6 @@ thiserror = "1.0.4"
|
|||||||
more-asserts = "0.2.1"
|
more-asserts = "0.2.1"
|
||||||
cfg-if = "1.0"
|
cfg-if = "1.0"
|
||||||
backtrace = "0.3.61"
|
backtrace = "0.3.61"
|
||||||
lazy_static = "1.3.0"
|
|
||||||
rand = "0.8.3"
|
rand = "0.8.3"
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
memfd = { version = "0.4.1", optional = true }
|
memfd = { version = "0.4.1", optional = true }
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ mod export;
|
|||||||
mod externref;
|
mod externref;
|
||||||
mod imports;
|
mod imports;
|
||||||
mod instance;
|
mod instance;
|
||||||
mod jit_int;
|
|
||||||
mod memory;
|
mod memory;
|
||||||
mod mmap;
|
mod mmap;
|
||||||
mod mmap_vec;
|
mod mmap_vec;
|
||||||
@@ -45,6 +44,8 @@ mod vmcontext;
|
|||||||
pub mod debug_builtins;
|
pub mod debug_builtins;
|
||||||
pub mod libcalls;
|
pub mod libcalls;
|
||||||
|
|
||||||
|
pub use wasmtime_jit_debug::gdb_jit_int::GdbJitImageRegistration;
|
||||||
|
|
||||||
pub use crate::export::*;
|
pub use crate::export::*;
|
||||||
pub use crate::externref::*;
|
pub use crate::externref::*;
|
||||||
pub use crate::imports::Imports;
|
pub use crate::imports::Imports;
|
||||||
@@ -56,7 +57,6 @@ pub use crate::instance::{
|
|||||||
pub use crate::instance::{
|
pub use crate::instance::{
|
||||||
InstanceLimits, ModuleLimits, PoolingAllocationStrategy, PoolingInstanceAllocator,
|
InstanceLimits, ModuleLimits, PoolingAllocationStrategy, PoolingInstanceAllocator,
|
||||||
};
|
};
|
||||||
pub use crate::jit_int::GdbJitImageRegistration;
|
|
||||||
pub use crate::memory::{DefaultMemoryCreator, Memory, RuntimeLinearMemory, RuntimeMemoryCreator};
|
pub use crate::memory::{DefaultMemoryCreator, Memory, RuntimeLinearMemory, RuntimeMemoryCreator};
|
||||||
pub use crate::mmap::Mmap;
|
pub use crate::mmap::Mmap;
|
||||||
pub use crate::mmap_vec::MmapVec;
|
pub use crate::mmap_vec::MmapVec;
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ const CRATES_TO_PUBLISH: &[&str] = &[
|
|||||||
"wiggle-generate",
|
"wiggle-generate",
|
||||||
"wiggle-macro",
|
"wiggle-macro",
|
||||||
// wasmtime
|
// wasmtime
|
||||||
|
"wasmtime-jit-debug",
|
||||||
"wasmtime-fiber",
|
"wasmtime-fiber",
|
||||||
"wasmtime-environ",
|
"wasmtime-environ",
|
||||||
"wasmtime-runtime",
|
"wasmtime-runtime",
|
||||||
|
|||||||
Reference in New Issue
Block a user