Overall wasm refactoring & improvements

This commit is contained in:
Luke Street 2024-08-21 19:48:58 -06:00
parent 0fccae1049
commit 1f4175dc21
11 changed files with 326 additions and 170 deletions

59
Cargo.lock generated
View File

@ -780,6 +780,16 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "console_log"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be8aed40e4edbf4d3b4431ab260b63fdc40f5780a4766824329ea0f1eefe3c0f"
dependencies = [
"log",
"web-sys",
]
[[package]] [[package]]
name = "const_format" name = "const_format"
version = "0.2.32" version = "0.2.32"
@ -2851,6 +2861,8 @@ dependencies = [
"anyhow", "anyhow",
"arm-attr", "arm-attr",
"byteorder", "byteorder",
"console_error_panic_hook",
"console_log",
"cpp_demangle", "cpp_demangle",
"cwdemangle", "cwdemangle",
"cwextab", "cwextab",
@ -2876,6 +2888,7 @@ dependencies = [
"serde_yaml", "serde_yaml",
"similar", "similar",
"strum", "strum",
"tsify-next",
"unarm", "unarm",
"wasm-bindgen", "wasm-bindgen",
] ]
@ -3860,6 +3873,17 @@ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]]
name = "serde-wasm-bindgen"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
dependencies = [
"js-sys",
"serde",
"wasm-bindgen",
]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.199" version = "1.0.199"
@ -3871,6 +3895,17 @@ dependencies = [
"syn 2.0.60", "syn 2.0.60",
] ]
[[package]]
name = "serde_derive_internals"
version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.116" version = "1.0.116"
@ -4447,6 +4482,30 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "tsify-next"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f4a645dca4ee0800f5ab60ce166deba2db6a0315de795a2691e138a3d55d756"
dependencies = [
"serde",
"serde-wasm-bindgen",
"tsify-next-macros",
"wasm-bindgen",
]
[[package]]
name = "tsify-next-macros"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d5c06f8a51d759bb58129e30b2631739e7e1e4579fad1f30ac09a6c88e488a6"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn 2.0.60",
]
[[package]] [[package]]
name = "ttf-parser" name = "ttf-parser"
version = "0.20.0" version = "0.20.0"

View File

@ -23,7 +23,7 @@ mips = ["any-arch", "rabbitizer"]
ppc = ["any-arch", "cwdemangle", "cwextab", "ppc750cl"] ppc = ["any-arch", "cwdemangle", "cwextab", "ppc750cl"]
x86 = ["any-arch", "cpp_demangle", "iced-x86", "msvc-demangler"] x86 = ["any-arch", "cpp_demangle", "iced-x86", "msvc-demangler"]
arm = ["any-arch", "cpp_demangle", "unarm", "arm-attr"] arm = ["any-arch", "cpp_demangle", "unarm", "arm-attr"]
wasm = ["serde_json"] wasm = ["serde_json", "console_error_panic_hook", "console_log"]
[dependencies] [dependencies]
anyhow = "1.0.82" anyhow = "1.0.82"
@ -40,6 +40,9 @@ serde = { version = "1", features = ["derive"] }
similar = { version = "2.5.0", default-features = false } similar = { version = "2.5.0", default-features = false }
strum = { version = "0.26.2", features = ["derive"] } strum = { version = "0.26.2", features = ["derive"] }
wasm-bindgen = "0.2.93" wasm-bindgen = "0.2.93"
tsify-next = { version = "0.5.4", default-features = false, features = ["js"] }
console_log = { version = "1.0.0", optional = true }
console_error_panic_hook = { version = "0.1.7", optional = true }
# config # config
globset = { version = "0.4.14", features = ["serde1"], optional = true } globset = { version = "0.4.14", features = ["serde1"], optional = true }

View File

@ -1,53 +1,78 @@
use anyhow::Context;
use prost::Message; use prost::Message;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use crate::{bindings::diff::DiffResult, diff, obj}; use crate::{bindings::diff::DiffResult, diff, obj};
#[wasm_bindgen] fn parse_object(
pub fn run_diff( data: Option<Box<[u8]>>,
config: &diff::DiffObjConfig,
) -> Result<Option<obj::ObjInfo>, JsError> {
data.as_ref().map(|data| obj::read::parse(data, config)).transpose().to_js()
}
fn parse_and_run_diff(
left: Option<Box<[u8]>>, left: Option<Box<[u8]>>,
right: Option<Box<[u8]>>, right: Option<Box<[u8]>>,
config: diff::DiffObjConfig, config: diff::DiffObjConfig,
) -> Result<String, JsError> { ) -> Result<DiffResult, JsError> {
let target = left let target = parse_object(left, &config)?;
.as_ref() let base = parse_object(right, &config)?;
.map(|data| obj::read::parse(data, &config).context("Loading target")) run_diff(target.as_ref(), base.as_ref(), config)
.transpose()
.map_err(|e| JsError::new(&e.to_string()))?;
let base = right
.as_ref()
.map(|data| obj::read::parse(data, &config).context("Loading base"))
.transpose()
.map_err(|e| JsError::new(&e.to_string()))?;
let result = diff::diff_objs(&config, target.as_ref(), base.as_ref(), None)
.map_err(|e| JsError::new(&e.to_string()))?;
let left = target.as_ref().and_then(|o| result.left.as_ref().map(|d| (o, d)));
let right = base.as_ref().and_then(|o| result.right.as_ref().map(|d| (o, d)));
let out = DiffResult::new(left, right);
serde_json::to_string(&out).map_err(|e| JsError::new(&e.to_string()))
} }
fn run_diff(
left: Option<&obj::ObjInfo>,
right: Option<&obj::ObjInfo>,
config: diff::DiffObjConfig,
) -> Result<DiffResult, JsError> {
log::debug!("Running diff with config: {:?}", config);
let result = diff::diff_objs(&config, left, right, None).to_js()?;
let left = left.and_then(|o| result.left.as_ref().map(|d| (o, d)));
let right = right.and_then(|o| result.right.as_ref().map(|d| (o, d)));
Ok(DiffResult::new(left, right))
}
// #[wasm_bindgen]
// pub fn run_diff_json(
// left: Option<Box<[u8]>>,
// right: Option<Box<[u8]>>,
// config: diff::DiffObjConfig,
// ) -> Result<String, JsError> {
// let out = run_diff_opt_box(left, right, config)?;
// serde_json::to_string(&out).map_err(|e| JsError::new(&e.to_string()))
// }
#[wasm_bindgen] #[wasm_bindgen]
pub fn run_diff_proto( pub fn run_diff_proto(
left: Option<Box<[u8]>>, left: Option<Box<[u8]>>,
right: Option<Box<[u8]>>, right: Option<Box<[u8]>>,
config: diff::DiffObjConfig, config: diff::DiffObjConfig,
) -> Result<Box<[u8]>, JsError> { ) -> Result<Box<[u8]>, JsError> {
let target = left let out = parse_and_run_diff(left, right, config)?;
.as_ref()
.map(|data| obj::read::parse(data, &config).context("Loading target"))
.transpose()
.map_err(|e| JsError::new(&e.to_string()))?;
let base = right
.as_ref()
.map(|data| obj::read::parse(data, &config).context("Loading base"))
.transpose()
.map_err(|e| JsError::new(&e.to_string()))?;
let result = diff::diff_objs(&config, target.as_ref(), base.as_ref(), None)
.map_err(|e| JsError::new(&e.to_string()))?;
let left = target.as_ref().and_then(|o| result.left.as_ref().map(|d| (o, d)));
let right = base.as_ref().and_then(|o| result.right.as_ref().map(|d| (o, d)));
let out = DiffResult::new(left, right);
Ok(out.encode_to_vec().into_boxed_slice()) Ok(out.encode_to_vec().into_boxed_slice())
} }
#[wasm_bindgen(start)]
fn start() -> Result<(), JsError> {
console_error_panic_hook::set_once();
#[cfg(debug_assertions)]
console_log::init_with_level(log::Level::Debug).to_js()?;
#[cfg(not(debug_assertions))]
console_log::init_with_level(log::Level::Info).to_js()?;
Ok(())
}
#[inline]
fn to_js_error(e: impl std::fmt::Display) -> JsError { JsError::new(&e.to_string()) }
trait ToJsResult {
type Output;
fn to_js(self) -> Result<Self::Output, JsError>;
}
impl<T, E: std::fmt::Display> ToJsResult for Result<T, E> {
type Output = T;
fn to_js(self) -> Result<T, JsError> { self.map_err(to_js_error) }
}

View File

@ -1,7 +1,6 @@
use std::collections::HashSet; use std::collections::HashSet;
use anyhow::Result; use anyhow::Result;
use wasm_bindgen::prelude::wasm_bindgen;
use crate::{ use crate::{
diff::{ diff::{
@ -18,7 +17,6 @@ pub mod code;
pub mod data; pub mod data;
pub mod display; pub mod display;
#[wasm_bindgen]
#[derive( #[derive(
Debug, Debug,
Copy, Copy,
@ -30,6 +28,7 @@ pub mod display;
serde::Serialize, serde::Serialize,
strum::VariantArray, strum::VariantArray,
strum::EnumMessage, strum::EnumMessage,
tsify_next::Tsify,
)] )]
pub enum X86Formatter { pub enum X86Formatter {
#[default] #[default]
@ -43,7 +42,6 @@ pub enum X86Formatter {
Masm, Masm,
} }
#[wasm_bindgen]
#[derive( #[derive(
Debug, Debug,
Copy, Copy,
@ -55,6 +53,7 @@ pub enum X86Formatter {
serde::Serialize, serde::Serialize,
strum::VariantArray, strum::VariantArray,
strum::EnumMessage, strum::EnumMessage,
tsify_next::Tsify,
)] )]
pub enum MipsAbi { pub enum MipsAbi {
#[default] #[default]
@ -68,7 +67,6 @@ pub enum MipsAbi {
N64, N64,
} }
#[wasm_bindgen]
#[derive( #[derive(
Debug, Debug,
Copy, Copy,
@ -80,6 +78,7 @@ pub enum MipsAbi {
serde::Serialize, serde::Serialize,
strum::VariantArray, strum::VariantArray,
strum::EnumMessage, strum::EnumMessage,
tsify_next::Tsify,
)] )]
pub enum MipsInstrCategory { pub enum MipsInstrCategory {
#[default] #[default]
@ -97,7 +96,6 @@ pub enum MipsInstrCategory {
R5900, R5900,
} }
#[wasm_bindgen]
#[derive( #[derive(
Debug, Debug,
Copy, Copy,
@ -109,6 +107,7 @@ pub enum MipsInstrCategory {
serde::Serialize, serde::Serialize,
strum::VariantArray, strum::VariantArray,
strum::EnumMessage, strum::EnumMessage,
tsify_next::Tsify,
)] )]
pub enum ArmArchVersion { pub enum ArmArchVersion {
#[default] #[default]
@ -122,7 +121,6 @@ pub enum ArmArchVersion {
V6K, V6K,
} }
#[wasm_bindgen]
#[derive( #[derive(
Debug, Debug,
Copy, Copy,
@ -134,6 +132,7 @@ pub enum ArmArchVersion {
serde::Serialize, serde::Serialize,
strum::VariantArray, strum::VariantArray,
strum::EnumMessage, strum::EnumMessage,
tsify_next::Tsify,
)] )]
pub enum ArmR9Usage { pub enum ArmR9Usage {
#[default] #[default]
@ -154,8 +153,8 @@ pub enum ArmR9Usage {
#[inline] #[inline]
const fn default_true() -> bool { true } const fn default_true() -> bool { true }
#[wasm_bindgen] #[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, tsify_next::Tsify)]
#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] #[tsify(from_wasm_abi)]
#[serde(default)] #[serde(default)]
pub struct DiffObjConfig { pub struct DiffObjConfig {
pub relax_reloc_diffs: bool, pub relax_reloc_diffs: bool,
@ -207,9 +206,6 @@ impl DiffObjConfig {
} }
} }
#[wasm_bindgen]
pub fn default_diff_obj_config() -> DiffObjConfig { Default::default() }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ObjSectionDiff { pub struct ObjSectionDiff {
pub symbols: Vec<ObjSymbolDiff>, pub symbols: Vec<ObjSymbolDiff>,

View File

@ -7,5 +7,22 @@ export default [
{languageOptions: {globals: globals.browser}}, {languageOptions: {globals: globals.browser}},
pluginJs.configs.recommended, pluginJs.configs.recommended,
...tseslint.configs.recommended, ...tseslint.configs.recommended,
{rules: {"semi": [2, "always"]}}, {
rules: {
"semi": [2, "always"],
"@typescript-eslint/no-unused-vars": [
"error",
// https://typescript-eslint.io/rules/no-unused-vars/#benefits-over-typescript
{
"args": "all",
"argsIgnorePattern": "^_",
"caughtErrors": "all",
"caughtErrorsIgnorePattern": "^_",
"destructuredArrayIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"ignoreRestSiblings": true
},
],
}
},
]; ];

View File

@ -1,24 +1,24 @@
{ {
"name": "objdiff-wasm", "name": "objdiff-wasm",
"version": "2.0.0-beta.6", "version": "2.0.0-beta.10",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "objdiff-wasm", "name": "objdiff-wasm",
"version": "2.0.0-beta.6", "version": "2.0.0-beta.10",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"dependencies": { "dependencies": {
"@protobuf-ts/runtime": "2.9.4" "@protobuf-ts/runtime": "^2.9.4"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.9.0", "@eslint/js": "^9.9.0",
"@protobuf-ts/plugin": "2.9.4", "@protobuf-ts/plugin": "^2.9.4",
"@types/node": "22.4.1", "@types/node": "^22.4.1",
"esbuild": "0.23.1", "esbuild": "^0.23.1",
"eslint": "^9.9.0", "eslint": "^9.9.0",
"globals": "^15.9.0", "globals": "^15.9.0",
"tsup": "8.2.4", "tsup": "^8.2.4",
"typescript-eslint": "^8.2.0" "typescript-eslint": "^8.2.0"
} }
}, },

View File

@ -1,6 +1,6 @@
{ {
"name": "objdiff-wasm", "name": "objdiff-wasm",
"version": "2.0.0-beta.6", "version": "2.0.0-beta.10",
"description": "A local diffing tool for decompilation projects.", "description": "A local diffing tool for decompilation projects.",
"author": { "author": {
"name": "Luke Street", "name": "Luke Street",
@ -19,21 +19,21 @@
"types": "dist/main.d.ts", "types": "dist/main.d.ts",
"scripts": { "scripts": {
"build": "tsup", "build": "tsup",
"build:all": "npm run build && npm run build:proto && npm run build:wasm", "build:all": "npm run build:wasm && npm run build:proto && npm run build",
"build:proto": "protoc --ts_out=gen --ts_opt add_pb_suffix,eslint_disable,ts_nocheck,use_proto_field_name --proto_path=../objdiff-core/protos ../objdiff-core/protos/*.proto", "build:proto": "protoc --ts_out=gen --ts_opt add_pb_suffix,eslint_disable,ts_nocheck,use_proto_field_name --proto_path=../objdiff-core/protos ../objdiff-core/protos/*.proto",
"build:wasm": "cd ../objdiff-core && wasm-pack build --out-dir ../objdiff-wasm/pkg --target web -- --features arm,dwarf,ppc,x86,wasm" "build:wasm": "cd ../objdiff-core && wasm-pack build --out-dir ../objdiff-wasm/pkg --target web -- --features arm,dwarf,ppc,x86,wasm"
}, },
"dependencies": { "dependencies": {
"@protobuf-ts/runtime": "2.9.4" "@protobuf-ts/runtime": "^2.9.4"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.9.0", "@eslint/js": "^9.9.0",
"@protobuf-ts/plugin": "2.9.4", "@protobuf-ts/plugin": "^2.9.4",
"@types/node": "22.4.1", "@types/node": "^22.4.1",
"esbuild": "0.23.1", "esbuild": "^0.23.1",
"eslint": "^9.9.0", "eslint": "^9.9.0",
"globals": "^15.9.0", "globals": "^15.9.0",
"tsup": "8.2.4", "tsup": "^8.2.4",
"typescript-eslint": "^8.2.0" "typescript-eslint": "^8.2.0"
} }
} }

View File

@ -2,61 +2,94 @@ import {ArgumentValue, DiffResult, InstructionDiff, RelocationTarget} from "../g
import type { import type {
ArmArchVersion, ArmArchVersion,
ArmR9Usage, ArmR9Usage,
DiffObjConfig as WasmDiffObjConfig, DiffObjConfig,
MipsAbi, MipsAbi,
MipsInstrCategory, MipsInstrCategory,
X86Formatter X86Formatter
} from '../pkg'; } from '../pkg';
import {InMessage, OutMessage} from './worker'; import {AnyHandlerData, InMessage, OutMessage} from './worker';
// Export wasm types // Export wasm types
export type DiffObjConfig = Omit<Partial<WasmDiffObjConfig>, 'free'>; export {ArmArchVersion, ArmR9Usage, MipsAbi, MipsInstrCategory, X86Formatter, DiffObjConfig};
export {ArmArchVersion, ArmR9Usage, MipsAbi, MipsInstrCategory, X86Formatter};
// Export protobuf types // Export protobuf types
export * from '../gen/diff_pb'; export * from '../gen/diff_pb';
interface PromiseCallbacks { interface PromiseCallbacks<T> {
start: number; start: number;
resolve: (value: unknown) => void; resolve: (value: T | PromiseLike<T>) => void;
reject: (reason?: unknown) => void; reject: (reason?: string) => void;
} }
let workerInit = false; let workerInit = false;
let workerCallbacks: PromiseCallbacks | null = null; let workerCallbacks: PromiseCallbacks<Worker>;
const workerReady = new Promise<Worker>((resolve, reject) => { const workerReady = new Promise<Worker>((resolve, reject) => {
workerCallbacks = {start: performance.now(), resolve, reject}; workerCallbacks = {start: performance.now(), resolve, reject};
}); });
export function initialize(workerUrl?: string | URL) { export async function initialize(data?: {
workerUrl?: string | URL,
wasmUrl?: string | URL, // Relative to worker URL
}): Promise<Worker> {
if (workerInit) { if (workerInit) {
return; return workerReady;
} }
workerInit = true; workerInit = true;
const worker = new Worker(workerUrl || 'worker.js', {type: 'module'}); let {workerUrl, wasmUrl} = data || {};
worker.onmessage = onMessage.bind(null, worker); if (!workerUrl) {
worker.onerror = (error) => { try {
console.error("Worker error", error); // Bundlers will convert this into an asset URL
workerCallbacks.reject(error); 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; let globalMessageId = 0;
const messageCallbacks = new Map<number, PromiseCallbacks>(); const messageCallbacks = new Map<number, PromiseCallbacks<never>>();
function onMessage(worker: Worker, event: MessageEvent<OutMessage>) { function onMessage(event: MessageEvent<OutMessage>) {
switch (event.data.type) { switch (event.data.type) {
case 'ready':
workerCallbacks.resolve(worker);
break;
case 'result': { case 'result': {
const {messageId, result} = event.data; const {result, error, messageId} = event.data;
const callbacks = messageCallbacks.get(messageId); const callbacks = messageCallbacks.get(messageId);
if (callbacks) { if (callbacks) {
const end = performance.now(); const end = performance.now();
console.debug(`Message ${messageId} took ${end - callbacks.start}ms`); console.debug(`Message ${messageId} took ${end - callbacks.start}ms`);
messageCallbacks.delete(messageId); messageCallbacks.delete(messageId);
callbacks.resolve(result); if (error != null) {
callbacks.reject(error);
} else {
callbacks.resolve(result as never);
}
} else { } else {
console.warn(`Unknown message ID ${messageId}`); console.warn(`Unknown message ID ${messageId}`);
} }
@ -65,11 +98,8 @@ function onMessage(worker: Worker, event: MessageEvent<OutMessage>) {
} }
} }
async function defer<T>(message: Omit<InMessage, 'messageId'>): Promise<T> { async function defer<T>(message: AnyHandlerData, worker?: Worker): Promise<T> {
if (!workerInit) { worker = worker || await initialize();
throw new Error('Worker not initialized');
}
const worker = await workerReady;
const messageId = globalMessageId++; const messageId = globalMessageId++;
const promise = new Promise<T>((resolve, reject) => { const promise = new Promise<T>((resolve, reject) => {
messageCallbacks.set(messageId, {start: performance.now(), resolve, reject}); messageCallbacks.set(messageId, {start: performance.now(), resolve, reject});
@ -77,17 +107,17 @@ async function defer<T>(message: Omit<InMessage, 'messageId'>): Promise<T> {
worker.postMessage({ worker.postMessage({
...message, ...message,
messageId messageId
}); } as InMessage);
return promise; return promise;
} }
export async function runDiff(left: Uint8Array | undefined, right: Uint8Array | undefined, config?: DiffObjConfig): Promise<DiffResult> { export async function runDiff(left: Uint8Array | undefined, right: Uint8Array | undefined, config?: DiffObjConfig): Promise<DiffResult> {
const data = await defer<Uint8Array>({ const data = await defer<Uint8Array>({
type: 'run_diff', type: 'run_diff_proto',
left, left,
right, right,
config config
} as InMessage); });
const parseStart = performance.now(); const parseStart = performance.now();
const result = DiffResult.fromBinary(data, {readUnknownField: false}); const result = DiffResult.fromBinary(data, {readUnknownField: false});
const end = performance.now(); const end = performance.now();
@ -148,11 +178,6 @@ export type DiffTextSpacing = DiffTextBase & {
count: number, count: number,
}; };
// TypeScript workaround for oneof types
export function oneof<T extends { oneofKind: string }>(type: T): T & { oneofKind: string } {
return type as T & { oneofKind: string };
}
// Native JavaScript implementation of objdiff_core::diff::display::display_diff // Native JavaScript implementation of objdiff_core::diff::display::display_diff
export function displayDiff(diff: InstructionDiff, baseAddr: bigint, cb: (text: DiffText) => void) { export function displayDiff(diff: InstructionDiff, baseAddr: bigint, cb: (text: DiffText) => void) {
const ins = diff.instruction; const ins = diff.instruction;
@ -173,7 +198,7 @@ export function displayDiff(diff: InstructionDiff, baseAddr: bigint, cb: (text:
if (i === 0) { if (i === 0) {
cb({type: 'spacing', count: 1}); cb({type: 'spacing', count: 1});
} }
const arg = oneof(ins.arguments[i].value); const arg = ins.arguments[i].value;
const diff_index = diff.arg_diff[i]?.diff_index; const diff_index = diff.arg_diff[i]?.diff_index;
switch (arg.oneofKind) { switch (arg.oneofKind) {
case "plain_text": case "plain_text":
@ -184,7 +209,7 @@ export function displayDiff(diff: InstructionDiff, baseAddr: bigint, cb: (text:
break; break;
case "relocation": { case "relocation": {
const reloc = ins.relocation!; const reloc = ins.relocation!;
cb({type: 'symbol', target: reloc.target, diff_index}); cb({type: 'symbol', target: reloc.target!, diff_index});
break; break;
} }
case "branch_dest": case "branch_dest":

View File

@ -1,81 +1,93 @@
import wasmInit, {default_diff_obj_config, run_diff_proto} from '../pkg'; import wasmInit, * as exports from '../pkg';
import {DiffObjConfig} from "./main";
self.postMessage({type: 'init'} as OutMessage); const handlers = {
await wasmInit({}); init: init,
self.postMessage({type: 'ready'} as OutMessage); // run_diff_json: run_diff_json,
run_diff_proto: run_diff_proto,
type ExtractParam<T> = { } as const;
[K in keyof T]: T[K] extends (arg1: infer U, ...args: any[]) => any ? U & { type: K } : never; type ExtractData<T> = T extends (arg: infer U) => Promise<unknown> ? U : never;
}[keyof T]; type HandlerData = {
type HandlerData = ExtractParam<{ [K in keyof typeof handlers]: { type: K } & ExtractData<typeof handlers[K]>;
run_diff: typeof run_diff,
}>;
const handlers: {
[K in HandlerData['type']]: (data: Omit<HandlerData, 'type'>) => unknown
} = {
'run_diff': run_diff,
}; };
function run_diff({left, right, config}: { let wasmReady: Promise<void> | null = null;
left: Uint8Array | undefined,
right: Uint8Array | undefined, async function init({wasmUrl}: { wasmUrl?: string }): Promise<void> {
config?: DiffObjConfig if (wasmReady != null) {
}): Uint8Array { throw new Error('Already initialized');
const cfg = default_diff_obj_config();
if (config) {
for (const key in config) {
if (key in config) {
cfg[key] = config[key];
}
}
} }
return run_diff_proto(left, right, cfg); wasmReady = wasmInit({module_or_path: wasmUrl})
.then(() => {
});
return wasmReady;
} }
export type InMessage = HandlerData & { messageId: number }; async function initIfNeeded() {
if (wasmReady == null) {
await init({});
}
return wasmReady;
}
export type OutMessage = ({ // async function run_diff_json({left, right, config}: {
// left: Uint8Array | undefined,
// right: Uint8Array | undefined,
// config?: exports.DiffObjConfig,
// }): Promise<string> {
// config = config || exports.default_diff_obj_config();
// return exports.run_diff_json(left, right, cfg);
// }
async function run_diff_proto({left, right, config}: {
left: Uint8Array | undefined,
right: Uint8Array | undefined,
config?: exports.DiffObjConfig,
}): Promise<Uint8Array> {
config = config || {};
return exports.run_diff_proto(left, right, config);
}
export type AnyHandlerData = HandlerData[keyof HandlerData];
export type InMessage = AnyHandlerData & { messageId: number };
export type OutMessage = {
type: 'result', type: 'result',
result: unknown | null, result: unknown | null,
error: unknown | null, error: string | null,
} | { messageId: number,
type: 'init', };
msg: string
} | {
type: 'ready',
msg: string
}) & { messageId: number };
self.onmessage = async (event: MessageEvent<InMessage>) => { self.onmessage = (event: MessageEvent<InMessage>) => {
const data = event.data; const data = event.data;
const handler = handlers[data.type]; const messageId = data?.messageId;
if (handler) { (async () => {
try { 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 start = performance.now();
const result = handler(data); const result = await handler(data as never);
const end = performance.now(); const end = performance.now();
console.debug(`Worker message ${data.messageId} took ${end - start}ms`); console.debug(`Worker message ${data.messageId} took ${end - start}ms`);
self.postMessage({ self.postMessage({
type: 'result', type: 'result',
result: result, result: result,
error: null, error: null,
messageId: data.messageId messageId,
}); } as OutMessage);
} catch (error) { } else {
self.postMessage({ throw new Error(`No handler for ${data.type}`);
type: 'result',
result: null,
error: error,
messageId: data.messageId
});
} }
} else { })().catch(error => {
self.postMessage({ self.postMessage({
type: 'result', type: 'result',
result: null, result: null,
error: `No handler for ${data.type}`, error: error.toString(),
messageId: data.messageId messageId,
}); } as OutMessage);
} });
}; };

View File

@ -1,8 +1,9 @@
{ {
"compilerOptions": { "compilerOptions": {
"esModuleInterop": true,
"module": "ES2022", "module": "ES2022",
"moduleResolution": "Node", "moduleResolution": "Node",
"strict": true,
"target": "ES2022", "target": "ES2022",
"esModuleInterop": true
} }
} }

View File

@ -1,15 +1,33 @@
import {defineConfig} from 'tsup'; import {defineConfig} from 'tsup';
import fs from 'node:fs/promises'; import fs from 'node:fs/promises';
export default defineConfig({ export default defineConfig([
entry: ['src/main.ts', 'src/worker.ts'], // Build main library
clean: true, {
dts: true, entry: ['src/main.ts'],
format: 'esm', clean: true,
sourcemap: true, dts: true,
splitting: false, format: 'esm',
target: ['es2022', 'chrome89', 'edge89', 'firefox89', 'safari15', 'node14.8'], outDir: 'dist',
async onSuccess() { skipNodeModulesBundle: true,
await fs.copyFile('pkg/objdiff_core_bg.wasm', 'dist/objdiff_core_bg.wasm'); sourcemap: true,
splitting: false,
target: 'es2022',
},
// Build web worker
{
entry: ['src/worker.ts'],
clean: true,
dts: true,
format: 'esm', // type: 'module'
minify: true,
outDir: 'dist',
sourcemap: true,
splitting: false,
target: 'es2022',
// https://github.com/egoist/tsup/issues/278
async onSuccess() {
await fs.copyFile('pkg/objdiff_core_bg.wasm', 'dist/objdiff_core_bg.wasm');
}
} }
}); ]);