WIP objdiff 3.0 refactor

This commit is contained in:
Luke Street
2025-02-20 17:48:00 -07:00
parent 6d3c63ccd8
commit f3c157ff06
79 changed files with 14886 additions and 6820 deletions
+321
View File
@@ -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) = &regex {
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);
+64
View File
@@ -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
}
-107
View File
@@ -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});
}
}
+14
View File
@@ -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() };
+52
View File
@@ -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();
}
-132
View File
@@ -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;
}
-93
View File
@@ -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);
});
};