mirror of
https://github.com/encounter/objdiff.git
synced 2026-03-30 11:32:16 -07:00
WIP objdiff 3.0 refactor
This commit is contained in:
@@ -0,0 +1,321 @@
|
||||
use alloc::{
|
||||
format,
|
||||
rc::Rc,
|
||||
str::FromStr,
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
use core::cell::RefCell;
|
||||
|
||||
use objdiff_core::{diff, obj};
|
||||
use regex::RegexBuilder;
|
||||
|
||||
use super::logging;
|
||||
|
||||
wit_bindgen::generate!({
|
||||
world: "api",
|
||||
with: {
|
||||
"wasi:logging/logging@0.1.0-draft": logging::wasi_logging,
|
||||
},
|
||||
});
|
||||
|
||||
use exports::objdiff::core::{
|
||||
diff::Guest as GuestDiff,
|
||||
diff_types::{
|
||||
DiffConfigBorrow, DiffResult, Guest as GuestDiffTypes, GuestDiffConfig, GuestObject,
|
||||
GuestObjectDiff, Object, ObjectBorrow, ObjectDiff, ObjectDiffBorrow,
|
||||
},
|
||||
display_types::{
|
||||
ContextMenuItem, DiffText, DiffTextOpcode, DiffTextSegment, DiffTextSymbol, DisplayConfig,
|
||||
HoverItem, InstructionDiffKind, InstructionDiffRow, SectionDisplay, SectionDisplaySymbol,
|
||||
SymbolDisplay, SymbolFilter, SymbolFlags, SymbolKind, SymbolRef,
|
||||
},
|
||||
};
|
||||
|
||||
struct Component;
|
||||
|
||||
impl Guest for Component {
|
||||
fn init(level: logging::wasi_logging::Level) { logging::init(level); }
|
||||
|
||||
fn version() -> String { env!("CARGO_PKG_VERSION").to_string() }
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
struct ResourceObject(Rc<obj::Object>);
|
||||
|
||||
struct ResourceObjectDiff(Rc<obj::Object>, diff::ObjectDiff);
|
||||
|
||||
#[repr(transparent)]
|
||||
struct ResourceDiffConfig(RefCell<diff::DiffObjConfig>);
|
||||
|
||||
impl GuestDiffTypes for Component {
|
||||
type DiffConfig = ResourceDiffConfig;
|
||||
type Object = ResourceObject;
|
||||
type ObjectDiff = ResourceObjectDiff;
|
||||
}
|
||||
|
||||
impl GuestDiff for Component {
|
||||
fn run_diff(
|
||||
left: Option<ObjectBorrow>,
|
||||
right: Option<ObjectBorrow>,
|
||||
diff_config: DiffConfigBorrow,
|
||||
) -> Result<DiffResult, String> {
|
||||
let diff_config = diff_config.get::<ResourceDiffConfig>().0.borrow();
|
||||
log::debug!("Running diff with config: {:?}", diff_config);
|
||||
let result = diff::diff_objs(
|
||||
left.as_ref().map(|o| o.get::<ResourceObject>().0.as_ref()),
|
||||
right.as_ref().map(|o| o.get::<ResourceObject>().0.as_ref()),
|
||||
None,
|
||||
&diff_config,
|
||||
&diff::MappingConfig::default(),
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(DiffResult {
|
||||
left: result.left.map(|d| {
|
||||
ObjectDiff::new(ResourceObjectDiff(
|
||||
left.unwrap().get::<ResourceObject>().0.clone(),
|
||||
d,
|
||||
))
|
||||
}),
|
||||
right: result.right.map(|d| {
|
||||
ObjectDiff::new(ResourceObjectDiff(
|
||||
right.unwrap().get::<ResourceObject>().0.clone(),
|
||||
d,
|
||||
))
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
fn symbol_context(_obj: ObjectBorrow, _symbol: SymbolRef) -> Vec<ContextMenuItem> { todo!() }
|
||||
|
||||
fn symbol_hover(_obj: ObjectBorrow, _symbol: SymbolRef) -> Vec<HoverItem> { todo!() }
|
||||
|
||||
fn display_sections(
|
||||
diff: ObjectDiffBorrow,
|
||||
filter: SymbolFilter,
|
||||
config: DisplayConfig,
|
||||
) -> Vec<SectionDisplay> {
|
||||
let regex = filter.regex.as_ref().and_then(|s| {
|
||||
RegexBuilder::new(s).case_insensitive(true).build().ok().or_else(|| {
|
||||
// Use the string as a literal if the regex fails to compile
|
||||
let escaped = regex::escape(s);
|
||||
RegexBuilder::new(&escaped).case_insensitive(true).build().ok()
|
||||
})
|
||||
});
|
||||
let filter = if let Some(mapping) = filter.mapping {
|
||||
diff::display::SymbolFilter::Mapping(mapping as usize, regex.as_ref())
|
||||
} else if let Some(regex) = ®ex {
|
||||
diff::display::SymbolFilter::Search(regex)
|
||||
} else {
|
||||
diff::display::SymbolFilter::None
|
||||
};
|
||||
let obj_diff = diff.get::<ResourceObjectDiff>();
|
||||
diff::display::display_sections(
|
||||
obj_diff.0.as_ref(),
|
||||
&obj_diff.1,
|
||||
filter,
|
||||
config.show_hidden_symbols,
|
||||
config.show_mapped_symbols,
|
||||
config.reverse_fn_order,
|
||||
)
|
||||
.into_iter()
|
||||
.map(|d| SectionDisplay {
|
||||
id: d.id,
|
||||
name: d.name,
|
||||
size: d.size,
|
||||
match_percent: d.match_percent,
|
||||
symbols: d
|
||||
.symbols
|
||||
.into_iter()
|
||||
.map(|s| SectionDisplaySymbol {
|
||||
symbol: s.symbol as SymbolRef,
|
||||
is_mapping_symbol: s.is_mapping_symbol,
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn display_symbol(
|
||||
diff: ObjectDiffBorrow,
|
||||
symbol_display: SectionDisplaySymbol,
|
||||
) -> SymbolDisplay {
|
||||
let obj_diff = diff.get::<ResourceObjectDiff>();
|
||||
let obj = obj_diff.0.as_ref();
|
||||
let obj_diff = &obj_diff.1;
|
||||
let symbol_idx = symbol_display.symbol as usize;
|
||||
let symbol = &obj.symbols[symbol_idx];
|
||||
let symbol_diff = if symbol_display.is_mapping_symbol {
|
||||
obj_diff
|
||||
.mapping_symbols
|
||||
.iter()
|
||||
.find(|s| s.symbol_index == symbol_idx)
|
||||
.map(|s| &s.symbol_diff)
|
||||
.unwrap()
|
||||
} else {
|
||||
&obj_diff.symbols[symbol_idx]
|
||||
};
|
||||
SymbolDisplay {
|
||||
name: symbol.name.clone(),
|
||||
demangled_name: symbol.demangled_name.clone(),
|
||||
address: symbol.address,
|
||||
size: symbol.size,
|
||||
kind: SymbolKind::from(symbol.kind),
|
||||
section: symbol.section.map(|s| s as u32),
|
||||
flags: SymbolFlags::from(symbol.flags),
|
||||
align: symbol.align.map(|a| a.get()),
|
||||
virtual_address: symbol.virtual_address,
|
||||
target_symbol: symbol_diff.target_symbol.map(|s| s as u32),
|
||||
match_percent: symbol_diff.match_percent,
|
||||
diff_score: symbol_diff.diff_score,
|
||||
row_count: symbol_diff.instruction_rows.len() as u32,
|
||||
}
|
||||
}
|
||||
|
||||
fn display_instruction_row(
|
||||
diff: ObjectDiffBorrow,
|
||||
symbol_display: SectionDisplaySymbol,
|
||||
row_index: u32,
|
||||
diff_config: DiffConfigBorrow,
|
||||
) -> InstructionDiffRow {
|
||||
let mut segments = Vec::with_capacity(16);
|
||||
let obj_diff = diff.get::<ResourceObjectDiff>();
|
||||
let obj = obj_diff.0.as_ref();
|
||||
let obj_diff = &obj_diff.1;
|
||||
let symbol_idx = symbol_display.symbol as usize;
|
||||
let symbol_diff = if symbol_display.is_mapping_symbol {
|
||||
obj_diff
|
||||
.mapping_symbols
|
||||
.iter()
|
||||
.find(|s| s.symbol_index == symbol_idx)
|
||||
.map(|s| &s.symbol_diff)
|
||||
.unwrap()
|
||||
} else {
|
||||
&obj_diff.symbols[symbol_idx]
|
||||
};
|
||||
let row = &symbol_diff.instruction_rows[row_index as usize];
|
||||
let diff_config = diff_config.get::<ResourceDiffConfig>().0.borrow();
|
||||
diff::display::display_row(obj, symbol_idx, row, &diff_config, |text, idx| {
|
||||
segments.push(DiffTextSegment { text: DiffText::from(text), diff_index: idx.get() });
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
InstructionDiffRow { segments, diff_kind: InstructionDiffKind::from(row.kind) }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<obj::SymbolKind> for SymbolKind {
|
||||
fn from(kind: obj::SymbolKind) -> Self {
|
||||
match kind {
|
||||
obj::SymbolKind::Unknown => SymbolKind::Unknown,
|
||||
obj::SymbolKind::Function => SymbolKind::Function,
|
||||
obj::SymbolKind::Object => SymbolKind::Object,
|
||||
obj::SymbolKind::Section => SymbolKind::Section,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<obj::SymbolFlagSet> for SymbolFlags {
|
||||
fn from(flags: obj::SymbolFlagSet) -> SymbolFlags {
|
||||
let mut out = SymbolFlags::empty();
|
||||
for flag in flags {
|
||||
out |= match flag {
|
||||
obj::SymbolFlag::Global => SymbolFlags::GLOBAL,
|
||||
obj::SymbolFlag::Local => SymbolFlags::LOCAL,
|
||||
obj::SymbolFlag::Weak => SymbolFlags::WEAK,
|
||||
obj::SymbolFlag::Common => SymbolFlags::COMMON,
|
||||
obj::SymbolFlag::Hidden => SymbolFlags::HIDDEN,
|
||||
obj::SymbolFlag::HasExtra => SymbolFlags::HAS_EXTRA,
|
||||
obj::SymbolFlag::SizeInferred => SymbolFlags::SIZE_INFERRED,
|
||||
};
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
impl From<diff::display::DiffText<'_>> for DiffText {
|
||||
fn from(text: diff::display::DiffText) -> Self {
|
||||
match text {
|
||||
diff::display::DiffText::Basic(v) => DiffText::Basic(v.to_string()),
|
||||
diff::display::DiffText::Line(v) => DiffText::Line(v),
|
||||
diff::display::DiffText::Address(v) => DiffText::Address(v),
|
||||
diff::display::DiffText::Opcode(n, op) => {
|
||||
DiffText::Opcode(DiffTextOpcode { mnemonic: n.to_string(), opcode: op })
|
||||
}
|
||||
diff::display::DiffText::Argument(s) => match s {
|
||||
obj::InstructionArgValue::Signed(v) => DiffText::Signed(*v),
|
||||
obj::InstructionArgValue::Unsigned(v) => DiffText::Unsigned(*v),
|
||||
obj::InstructionArgValue::Opaque(v) => DiffText::Opaque(v.to_string()),
|
||||
},
|
||||
diff::display::DiffText::BranchDest(v) => DiffText::BranchDest(v),
|
||||
diff::display::DiffText::Symbol(s) => DiffText::Symbol(DiffTextSymbol {
|
||||
name: s.name.clone(),
|
||||
demangled_name: s.demangled_name.clone(),
|
||||
}),
|
||||
diff::display::DiffText::Addend(v) => DiffText::Addend(v),
|
||||
diff::display::DiffText::Spacing(v) => DiffText::Spacing(v as u32),
|
||||
diff::display::DiffText::Eol => DiffText::Eol,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<diff::InstructionDiffKind> for InstructionDiffKind {
|
||||
fn from(kind: diff::InstructionDiffKind) -> Self {
|
||||
match kind {
|
||||
diff::InstructionDiffKind::None => InstructionDiffKind::None,
|
||||
diff::InstructionDiffKind::OpMismatch => InstructionDiffKind::OpMismatch,
|
||||
diff::InstructionDiffKind::ArgMismatch => InstructionDiffKind::ArgMismatch,
|
||||
diff::InstructionDiffKind::Replace => InstructionDiffKind::Replace,
|
||||
diff::InstructionDiffKind::Insert => InstructionDiffKind::Insert,
|
||||
diff::InstructionDiffKind::Delete => InstructionDiffKind::Delete,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GuestDiffConfig for ResourceDiffConfig {
|
||||
fn new() -> Self { Self(RefCell::new(diff::DiffObjConfig::default())) }
|
||||
|
||||
fn set_property(&self, key: String, value: String) -> Result<(), String> {
|
||||
let id = diff::ConfigPropertyId::from_str(&key)
|
||||
.map_err(|_| format!("Invalid property key {:?}", key))?;
|
||||
self.0
|
||||
.borrow_mut()
|
||||
.set_property_value_str(id, &value)
|
||||
.map_err(|_| format!("Invalid property value {:?}", value))
|
||||
}
|
||||
|
||||
fn get_property(&self, key: String) -> Result<String, String> {
|
||||
let id = diff::ConfigPropertyId::from_str(&key)
|
||||
.map_err(|_| format!("Invalid property key {:?}", key))?;
|
||||
Ok(self.0.borrow().get_property_value(id).to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl GuestObject for ResourceObject {
|
||||
fn parse(data: Vec<u8>, diff_config: DiffConfigBorrow) -> Result<Object, String> {
|
||||
let diff_config = diff_config.get::<ResourceDiffConfig>().0.borrow();
|
||||
obj::read::parse(&data, &diff_config)
|
||||
.map(|o| Object::new(ResourceObject(Rc::new(o))))
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl GuestObjectDiff for ResourceObjectDiff {
|
||||
fn find_symbol(&self, name: String, section_name: Option<String>) -> Option<SymbolRef> {
|
||||
let obj = self.0.as_ref();
|
||||
obj.symbols
|
||||
.iter()
|
||||
.position(|s| {
|
||||
s.name == name
|
||||
&& match section_name.as_deref() {
|
||||
Some(section_name) => {
|
||||
s.section.is_some_and(|n| obj.sections[n].name == section_name)
|
||||
}
|
||||
None => true,
|
||||
}
|
||||
})
|
||||
.map(|i| i as SymbolRef)
|
||||
}
|
||||
}
|
||||
|
||||
export!(Component);
|
||||
@@ -0,0 +1,64 @@
|
||||
//! This module contains a canonical definition of the `cabi_realloc` function
|
||||
//! for the component model.
|
||||
//!
|
||||
//! The component model's canonical ABI for representing datatypes in memory
|
||||
//! makes use of this function when transferring lists and strings, for example.
|
||||
//! This function behaves like C's `realloc` but also takes alignment into
|
||||
//! account.
|
||||
//!
|
||||
//! Components are notably not required to export this function, but nearly
|
||||
//! all components end up doing so currently. This definition in the standard
|
||||
//! library removes the need for all compilations to define this themselves.
|
||||
//!
|
||||
//! More information about the canonical ABI can be found at
|
||||
//! <https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md>
|
||||
//!
|
||||
//! Note that the name of this function is not standardized in the canonical ABI
|
||||
//! at this time. Instead it's a convention of the "componentization process"
|
||||
//! where a core wasm module is converted to a component to use this name.
|
||||
//! Additionally this is not the only possible definition of this function, so
|
||||
//! this is defined as a "weak" symbol. This means that other definitions are
|
||||
//! allowed to overwrite it if they are present in a compilation.
|
||||
|
||||
use alloc::{alloc, Layout};
|
||||
use core::ptr;
|
||||
|
||||
#[used]
|
||||
static FORCE_CODEGEN_OF_CABI_REALLOC: unsafe extern "C" fn(
|
||||
*mut u8,
|
||||
usize,
|
||||
usize,
|
||||
usize,
|
||||
) -> *mut u8 = cabi_realloc;
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn cabi_realloc(
|
||||
old_ptr: *mut u8,
|
||||
old_len: usize,
|
||||
align: usize,
|
||||
new_len: usize,
|
||||
) -> *mut u8 {
|
||||
let layout;
|
||||
let ptr = if old_len == 0 {
|
||||
if new_len == 0 {
|
||||
return ptr::without_provenance_mut(align);
|
||||
}
|
||||
layout = Layout::from_size_align_unchecked(new_len, align);
|
||||
alloc::alloc(layout)
|
||||
} else {
|
||||
debug_assert_ne!(new_len, 0, "non-zero old_len requires non-zero new_len!");
|
||||
layout = Layout::from_size_align_unchecked(old_len, align);
|
||||
alloc::realloc(old_ptr, layout, new_len)
|
||||
};
|
||||
if ptr.is_null() {
|
||||
// Print a nice message in debug mode, but in release mode don't
|
||||
// pull in so many dependencies related to printing so just emit an
|
||||
// `unreachable` instruction.
|
||||
if cfg!(debug_assertions) {
|
||||
alloc::handle_alloc_error(layout);
|
||||
} else {
|
||||
core::unreachable!("allocation failed")
|
||||
}
|
||||
}
|
||||
ptr
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
import {ArgumentValue, InstructionDiff, RelocationTarget} from "../gen/diff_pb";
|
||||
|
||||
export type DiffText =
|
||||
DiffTextBasic
|
||||
| DiffTextBasicColor
|
||||
| DiffTextAddress
|
||||
| DiffTextLine
|
||||
| DiffTextOpcode
|
||||
| DiffTextArgument
|
||||
| DiffTextSymbol
|
||||
| DiffTextBranchDest
|
||||
| DiffTextSpacing;
|
||||
|
||||
type DiffTextBase = {
|
||||
diff_index?: number,
|
||||
};
|
||||
export type DiffTextBasic = DiffTextBase & {
|
||||
type: 'basic',
|
||||
text: string,
|
||||
};
|
||||
export type DiffTextBasicColor = DiffTextBase & {
|
||||
type: 'basic_color',
|
||||
text: string,
|
||||
index: number,
|
||||
};
|
||||
export type DiffTextAddress = DiffTextBase & {
|
||||
type: 'address',
|
||||
address: bigint,
|
||||
};
|
||||
export type DiffTextLine = DiffTextBase & {
|
||||
type: 'line',
|
||||
line_number: number,
|
||||
};
|
||||
export type DiffTextOpcode = DiffTextBase & {
|
||||
type: 'opcode',
|
||||
mnemonic: string,
|
||||
opcode: number,
|
||||
};
|
||||
export type DiffTextArgument = DiffTextBase & {
|
||||
type: 'argument',
|
||||
value: ArgumentValue,
|
||||
};
|
||||
export type DiffTextSymbol = DiffTextBase & {
|
||||
type: 'symbol',
|
||||
target: RelocationTarget,
|
||||
};
|
||||
export type DiffTextBranchDest = DiffTextBase & {
|
||||
type: 'branch_dest',
|
||||
address: bigint,
|
||||
};
|
||||
export type DiffTextSpacing = DiffTextBase & {
|
||||
type: 'spacing',
|
||||
count: number,
|
||||
};
|
||||
|
||||
// Native JavaScript implementation of objdiff_core::diff::display::display_diff
|
||||
export function displayDiff(diff: InstructionDiff, baseAddr: bigint, cb: (text: DiffText) => void) {
|
||||
const ins = diff.instruction;
|
||||
if (!ins) {
|
||||
return;
|
||||
}
|
||||
if (ins.line_number != null) {
|
||||
cb({type: 'line', line_number: ins.line_number});
|
||||
}
|
||||
cb({type: 'address', address: ins.address - baseAddr});
|
||||
if (diff.branch_from) {
|
||||
cb({type: 'basic_color', text: ' ~> ', index: diff.branch_from.branch_index});
|
||||
} else {
|
||||
cb({type: 'spacing', count: 4});
|
||||
}
|
||||
cb({type: 'opcode', mnemonic: ins.mnemonic, opcode: ins.opcode});
|
||||
let arg_diff_idx = 0; // non-PlainText argument index
|
||||
for (let i = 0; i < ins.arguments.length; i++) {
|
||||
if (i === 0) {
|
||||
cb({type: 'spacing', count: 1});
|
||||
}
|
||||
const arg = ins.arguments[i].value;
|
||||
let diff_index: number | undefined;
|
||||
if (arg.oneofKind !== 'plain_text') {
|
||||
diff_index = diff.arg_diff[arg_diff_idx]?.diff_index;
|
||||
arg_diff_idx++;
|
||||
}
|
||||
switch (arg.oneofKind) {
|
||||
case "plain_text":
|
||||
cb({type: 'basic', text: arg.plain_text, diff_index});
|
||||
break;
|
||||
case "argument":
|
||||
cb({type: 'argument', value: arg.argument, diff_index});
|
||||
break;
|
||||
case "relocation": {
|
||||
const reloc = ins.relocation!;
|
||||
cb({type: 'symbol', target: reloc.target!, diff_index});
|
||||
break;
|
||||
}
|
||||
case "branch_dest":
|
||||
if (arg.branch_dest < baseAddr) {
|
||||
cb({type: 'basic', text: '<unknown>', diff_index});
|
||||
} else {
|
||||
cb({type: 'branch_dest', address: arg.branch_dest - baseAddr, diff_index});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (diff.branch_to) {
|
||||
cb({type: 'basic_color', text: ' ~> ', index: diff.branch_to.branch_index});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
extern crate alloc;
|
||||
|
||||
#[cfg(target_os = "wasi")]
|
||||
mod api;
|
||||
#[cfg(target_os = "wasi")]
|
||||
mod logging;
|
||||
|
||||
#[cfg(all(target_os = "wasi", not(feature = "std")))]
|
||||
mod cabi_realloc;
|
||||
|
||||
#[cfg(all(target_family = "wasm", not(feature = "std")))]
|
||||
#[global_allocator]
|
||||
static ALLOCATOR: talc::TalckWasm = unsafe { talc::TalckWasm::new_global() };
|
||||
@@ -0,0 +1,52 @@
|
||||
wit_bindgen::generate!({
|
||||
world: "imports",
|
||||
path: "wit/deps/logging",
|
||||
});
|
||||
|
||||
use alloc::format;
|
||||
|
||||
pub use wasi::logging::logging as wasi_logging;
|
||||
|
||||
struct WasiLogger;
|
||||
|
||||
impl log::Log for WasiLogger {
|
||||
fn enabled(&self, metadata: &log::Metadata) -> bool { metadata.level() <= log::max_level() }
|
||||
|
||||
fn log(&self, record: &log::Record) {
|
||||
if !self.enabled(record.metadata()) {
|
||||
return;
|
||||
}
|
||||
let level = match record.level() {
|
||||
log::Level::Error => wasi_logging::Level::Error,
|
||||
log::Level::Warn => wasi_logging::Level::Warn,
|
||||
log::Level::Info => wasi_logging::Level::Info,
|
||||
log::Level::Debug => wasi_logging::Level::Debug,
|
||||
log::Level::Trace => wasi_logging::Level::Trace,
|
||||
};
|
||||
wasi_logging::log(level, record.target(), &format!("{}", record.args()));
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
static LOGGER: WasiLogger = WasiLogger;
|
||||
|
||||
pub fn init(level: wasi_logging::Level) {
|
||||
let _ = log::set_logger(&LOGGER);
|
||||
log::set_max_level(match level {
|
||||
wasi_logging::Level::Error => log::LevelFilter::Error,
|
||||
wasi_logging::Level::Warn => log::LevelFilter::Warn,
|
||||
wasi_logging::Level::Info => log::LevelFilter::Info,
|
||||
wasi_logging::Level::Debug => log::LevelFilter::Debug,
|
||||
wasi_logging::Level::Trace => log::LevelFilter::Trace,
|
||||
wasi_logging::Level::Critical => log::LevelFilter::Off,
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
#[panic_handler]
|
||||
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||
use alloc::string::ToString;
|
||||
wasi_logging::log(wasi_logging::Level::Critical, "objdiff_core::panic", &info.to_string());
|
||||
core::arch::wasm32::unreachable();
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
import {DiffResult} from "../gen/diff_pb";
|
||||
import type {
|
||||
ConfigProperty,
|
||||
MappingConfig,
|
||||
SymbolMappings,
|
||||
} from '../pkg';
|
||||
import {AnyHandlerData, InMessage, OutMessage} from './worker';
|
||||
|
||||
// Export wasm types
|
||||
export {ConfigProperty, MappingConfig, SymbolMappings};
|
||||
|
||||
// Export protobuf types
|
||||
export * from '../gen/diff_pb';
|
||||
|
||||
// Export display types
|
||||
export * from './display';
|
||||
|
||||
interface PromiseCallbacks<T> {
|
||||
start: number;
|
||||
resolve: (value: T | PromiseLike<T>) => void;
|
||||
reject: (reason?: string) => void;
|
||||
}
|
||||
|
||||
let workerInit = false;
|
||||
let workerCallbacks: PromiseCallbacks<Worker>;
|
||||
const workerReady = new Promise<Worker>((resolve, reject) => {
|
||||
workerCallbacks = {start: performance.now(), resolve, reject};
|
||||
});
|
||||
|
||||
export async function initialize(data?: {
|
||||
workerUrl?: string | URL,
|
||||
wasmUrl?: string | URL, // Relative to worker URL
|
||||
}): Promise<Worker> {
|
||||
if (workerInit) {
|
||||
return workerReady;
|
||||
}
|
||||
workerInit = true;
|
||||
let {workerUrl, wasmUrl} = data || {};
|
||||
if (!workerUrl) {
|
||||
try {
|
||||
// Bundlers will convert this into an asset URL
|
||||
workerUrl = new URL('./worker.js', import.meta.url);
|
||||
} catch (_) {
|
||||
workerUrl = 'worker.js';
|
||||
}
|
||||
}
|
||||
if (!wasmUrl) {
|
||||
try {
|
||||
// Bundlers will convert this into an asset URL
|
||||
wasmUrl = new URL('./objdiff_core_bg.wasm', import.meta.url);
|
||||
} catch (_) {
|
||||
wasmUrl = 'objdiff_core_bg.js';
|
||||
}
|
||||
}
|
||||
const worker = new Worker(workerUrl, {
|
||||
name: 'objdiff',
|
||||
type: 'module',
|
||||
});
|
||||
worker.onmessage = onMessage;
|
||||
worker.onerror = (event) => {
|
||||
console.error("Worker error", event);
|
||||
workerCallbacks.reject("Worker failed to initialize, wrong URL?");
|
||||
};
|
||||
defer<void>({
|
||||
type: 'init',
|
||||
// URL can't be sent directly
|
||||
wasmUrl: wasmUrl.toString(),
|
||||
}, worker).then(() => {
|
||||
workerCallbacks.resolve(worker);
|
||||
}, (e) => {
|
||||
workerCallbacks.reject(e);
|
||||
});
|
||||
return workerReady;
|
||||
}
|
||||
|
||||
let globalMessageId = 0;
|
||||
const messageCallbacks = new Map<number, PromiseCallbacks<never>>();
|
||||
|
||||
function onMessage(event: MessageEvent<OutMessage>) {
|
||||
switch (event.data.type) {
|
||||
case 'result': {
|
||||
const {result, error, messageId} = event.data;
|
||||
const callbacks = messageCallbacks.get(messageId);
|
||||
if (callbacks) {
|
||||
const end = performance.now();
|
||||
console.debug(`Message ${messageId} took ${end - callbacks.start}ms`);
|
||||
messageCallbacks.delete(messageId);
|
||||
if (error != null) {
|
||||
callbacks.reject(error);
|
||||
} else {
|
||||
callbacks.resolve(result as never);
|
||||
}
|
||||
} else {
|
||||
console.warn(`Unknown message ID ${messageId}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function defer<T>(message: AnyHandlerData, worker?: Worker): Promise<T> {
|
||||
worker = worker || await initialize();
|
||||
const messageId = globalMessageId++;
|
||||
const promise = new Promise<T>((resolve, reject) => {
|
||||
messageCallbacks.set(messageId, {start: performance.now(), resolve, reject});
|
||||
});
|
||||
worker.postMessage({
|
||||
...message,
|
||||
messageId
|
||||
} as InMessage);
|
||||
return promise;
|
||||
}
|
||||
|
||||
export async function runDiff(
|
||||
left: Uint8Array | null | undefined,
|
||||
right: Uint8Array | null | undefined,
|
||||
properties?: ConfigProperty[],
|
||||
mappingConfig?: MappingConfig,
|
||||
): Promise<DiffResult> {
|
||||
const data = await defer<Uint8Array>({
|
||||
type: 'run_diff_proto',
|
||||
left,
|
||||
right,
|
||||
properties,
|
||||
mappingConfig,
|
||||
});
|
||||
const parseStart = performance.now();
|
||||
const result = DiffResult.fromBinary(data, {readUnknownField: false});
|
||||
const end = performance.now();
|
||||
console.debug(`Parsing message took ${end - parseStart}ms`);
|
||||
return result;
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
import wasmInit, * as exports from '../pkg';
|
||||
|
||||
const handlers = {
|
||||
init: init,
|
||||
run_diff_proto: run_diff_proto,
|
||||
} as const;
|
||||
type ExtractData<T> = T extends (arg: infer U) => Promise<unknown> ? U : never;
|
||||
type HandlerData = {
|
||||
[K in keyof typeof handlers]: { type: K } & ExtractData<typeof handlers[K]>;
|
||||
};
|
||||
|
||||
let wasmReady: Promise<void> | null = null;
|
||||
|
||||
async function init({wasmUrl}: { wasmUrl?: string }): Promise<void> {
|
||||
if (wasmReady != null) {
|
||||
throw new Error('Already initialized');
|
||||
}
|
||||
wasmReady = wasmInit({module_or_path: wasmUrl})
|
||||
.then(() => {
|
||||
});
|
||||
return wasmReady;
|
||||
}
|
||||
|
||||
async function initIfNeeded() {
|
||||
if (wasmReady == null) {
|
||||
await init({});
|
||||
}
|
||||
return wasmReady;
|
||||
}
|
||||
|
||||
async function run_diff_proto({left, right, properties, mappingConfig}: {
|
||||
left: Uint8Array | null | undefined,
|
||||
right: Uint8Array | null | undefined,
|
||||
properties?: exports.ConfigProperty[],
|
||||
mappingConfig?: exports.MappingConfig,
|
||||
}): Promise<Uint8Array> {
|
||||
const diffConfig = exports.config_from_properties(properties || []);
|
||||
const leftObj = left ? exports.parse_object(left, diffConfig) : null;
|
||||
const rightObj = right ? exports.parse_object(right, diffConfig) : null;
|
||||
return exports.run_diff(leftObj, rightObj, diffConfig, mappingConfig || {});
|
||||
}
|
||||
|
||||
export type AnyHandlerData = HandlerData[keyof HandlerData];
|
||||
export type InMessage = AnyHandlerData & { messageId: number };
|
||||
|
||||
export type OutMessage = {
|
||||
type: 'result',
|
||||
result: unknown | null,
|
||||
error: string | null,
|
||||
messageId: number,
|
||||
};
|
||||
|
||||
self.onmessage = (event: MessageEvent<InMessage>) => {
|
||||
const data = event.data;
|
||||
const messageId = data?.messageId;
|
||||
(async () => {
|
||||
if (!data) {
|
||||
throw new Error('No data');
|
||||
}
|
||||
const handler = handlers[data.type];
|
||||
if (handler) {
|
||||
if (data.type !== 'init') {
|
||||
await initIfNeeded();
|
||||
}
|
||||
const start = performance.now();
|
||||
const result = await handler(data as never);
|
||||
const end = performance.now();
|
||||
console.debug(`Worker message ${data.messageId} took ${end - start}ms`);
|
||||
let transfer: Transferable[] = [];
|
||||
if (result instanceof Uint8Array) {
|
||||
console.log("Transferring!", result.byteLength);
|
||||
transfer = [result.buffer];
|
||||
} else {
|
||||
console.log("Didn't transfer", typeof result);
|
||||
}
|
||||
self.postMessage({
|
||||
type: 'result',
|
||||
result: result,
|
||||
error: null,
|
||||
messageId,
|
||||
} as OutMessage, {transfer});
|
||||
} else {
|
||||
throw new Error(`No handler for ${data.type}`);
|
||||
}
|
||||
})().catch(error => {
|
||||
self.postMessage({
|
||||
type: 'result',
|
||||
result: null,
|
||||
error: error.toString(),
|
||||
messageId,
|
||||
} as OutMessage);
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user