From 9383688f87cc8d9e6837a6d34b94470a996c53c5 Mon Sep 17 00:00:00 2001 From: zach Date: Thu, 5 Dec 2024 14:46:23 -0800 Subject: [PATCH 1/8] fix: better handling of js exception messages --- crates/core/Cargo.toml | 2 +- crates/core/src/globals.rs | 86 ++++++++++--------- crates/core/src/lib.rs | 169 +++++++++++++++++-------------------- 3 files changed, 127 insertions(+), 130 deletions(-) diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index b97aed3..de47040 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -13,7 +13,7 @@ once_cell = "1.16" anyhow = { workspace = true } quickjs-wasm-rs = "3" chrono = { version = "0.4", default-features = false, features = ["clock"] } -rquickjs = { version = "0.6.2", features = ["array-buffer"]} +rquickjs = { version = "0.8.1", features = ["array-buffer", "bindgen"]} base64 = "0.22.1" [lib] diff --git a/crates/core/src/globals.rs b/crates/core/src/globals.rs index a80e6b5..3116968 100644 --- a/crates/core/src/globals.rs +++ b/crates/core/src/globals.rs @@ -77,7 +77,8 @@ extern "C" { fn get_args_as_str(args: &Rest) -> anyhow::Result { args.iter() .map(|arg| { - arg.as_string() + arg.clone() + .into_string() .ok_or(rquickjs::Error::Unknown) .and_then(|s| s.to_string()) }) @@ -89,9 +90,9 @@ fn get_args_as_str(args: &Rest) -> anyhow::Result { fn to_js_error(cx: Ctx, e: anyhow::Error) -> rquickjs::Error { match e.downcast::() { Ok(e) => e, - Err(e) => cx.throw(Value::from_exception( - rquickjs::Exception::from_message(cx.clone(), &e.to_string()) - .expect("Creating an exception should succeed"), + Err(e) => cx.throw(rquickjs::Value::from_string( + rquickjs::String::from_str(cx.clone(), &e.to_string()) + .expect("rquickjs error conversion"), )), } } @@ -144,7 +145,7 @@ fn build_module_object(this: Ctx) -> anyhow::Result { Ok(module) } -fn build_host_object<'js>(this: Ctx<'js>) -> anyhow::Result { +fn build_host_object<'js>(this: Ctx<'js>) -> anyhow::Result> { let host_input_bytes = Function::new( this.clone(), MutFn::new(move |cx| { @@ -260,7 +261,6 @@ fn add_host_functions(this: Ctx<'_>) -> anyhow::Result<()> { if invoke_host.is_null() || invoke_host.is_undefined() { let host_invoke_func = Function::new(this.clone(), move |cx, args: Rest>| { let func_id = args.first().unwrap().as_int().unwrap() as u32; - println!("Invoke host func: {func_id}"); let len = args.len() - 1; match len { 0 => { @@ -273,8 +273,8 @@ fn add_host_functions(this: Ctx<'_>) -> anyhow::Result<()> { .get(1) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); let result = unsafe { __invokeHostFunc_1_1(func_id, ptr as u64) }; @@ -285,16 +285,16 @@ fn add_host_functions(this: Ctx<'_>) -> anyhow::Result<()> { .get(1) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); let ptr2 = args .get(2) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); let result = unsafe { __invokeHostFunc_2_1(func_id, ptr as u64, ptr2 as u64) }; @@ -305,24 +305,32 @@ fn add_host_functions(this: Ctx<'_>) -> anyhow::Result<()> { .get(1) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); let ptr2 = args .get(2) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); let ptr3 = args .get(3) .unwrap() .as_big_int() + .cloned() + .or_else(|| { + args.get(3) + .cloned() + .and_then(|x| match BigInt::from_value(x) { + Ok(x) => Some(x), + Err(_) => None, + }) + }) .unwrap() - .clone() .to_i64() .unwrap(); let result = unsafe { @@ -335,32 +343,32 @@ fn add_host_functions(this: Ctx<'_>) -> anyhow::Result<()> { .get(1) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); let ptr2 = args .get(2) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); let ptr3 = args .get(3) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); let ptr4 = args .get(4) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); let result = unsafe { @@ -379,40 +387,40 @@ fn add_host_functions(this: Ctx<'_>) -> anyhow::Result<()> { .get(1) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); let ptr2 = args .get(2) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); let ptr3 = args .get(3) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); let ptr4 = args .get(4) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); let ptr5 = args .get(5) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); let result = unsafe { @@ -445,8 +453,8 @@ fn add_host_functions(this: Ctx<'_>) -> anyhow::Result<()> { .get(1) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); unsafe { __invokeHostFunc_1_0(func_id, ptr as u64) }; @@ -456,16 +464,16 @@ fn add_host_functions(this: Ctx<'_>) -> anyhow::Result<()> { .get(1) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); let ptr2 = args .get(2) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); unsafe { __invokeHostFunc_2_0(func_id, ptr as u64, ptr2 as u64) }; @@ -475,24 +483,24 @@ fn add_host_functions(this: Ctx<'_>) -> anyhow::Result<()> { .get(1) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); let ptr2 = args .get(2) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); let ptr3 = args .get(3) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); unsafe { __invokeHostFunc_3_0(func_id, ptr as u64, ptr2 as u64, ptr3 as u64) }; @@ -502,32 +510,32 @@ fn add_host_functions(this: Ctx<'_>) -> anyhow::Result<()> { .get(1) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); let ptr2 = args .get(2) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); let ptr3 = args .get(3) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); let ptr4 = args .get(4) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); unsafe { @@ -545,40 +553,40 @@ fn add_host_functions(this: Ctx<'_>) -> anyhow::Result<()> { .get(1) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); let ptr2 = args .get(2) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); let ptr3 = args .get(3) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); let ptr4 = args .get(4) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); let ptr5 = args .get(5) .unwrap() .as_big_int() + .cloned() .unwrap() - .clone() .to_i64() .unwrap(); unsafe { @@ -607,7 +615,7 @@ fn add_host_functions(this: Ctx<'_>) -> anyhow::Result<()> { Ok(()) } -fn build_var_object<'js>(this: Ctx<'js>) -> anyhow::Result { +fn build_var_object<'js>(this: Ctx<'js>) -> anyhow::Result> { let var_set = Function::new( this.clone(), MutFn::new(move |cx: Ctx, args: Rest>| { @@ -714,7 +722,7 @@ fn build_var_object<'js>(this: Ctx<'js>) -> anyhow::Result { Ok(var_object) } -fn build_http_object<'js>(this: Ctx<'js>) -> anyhow::Result { +fn build_http_object<'js>(this: Ctx<'js>) -> anyhow::Result> { let http_req = Function::new(this.clone(), |cx: Ctx<'js>, args: Rest| { let req = args .first() @@ -860,7 +868,7 @@ fn build_config_object<'js>(this: Ctx<'js>) -> anyhow::Result> { Ok(config_obj) } -fn build_memory<'js>(this: Ctx<'js>) -> anyhow::Result { +fn build_memory<'js>(this: Ctx<'js>) -> anyhow::Result> { let memory_from_buffer = Function::new(this.clone(), |cx: Ctx<'js>, args: Rest| { let data = args .first() diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index b1ba889..9d74669 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,4 +1,3 @@ -use once_cell::sync::OnceCell; use rquickjs::{ function::Args, object::ObjectKeysIter, Context, Ctx, Function, Object, Runtime, Undefined, Value, @@ -8,8 +7,30 @@ use std::io::Read; mod globals; -static mut CONTEXT: OnceCell = OnceCell::new(); -static mut CALL_ARGS: Vec> = vec![]; +struct Cx(Context); + +unsafe impl Send for Cx {} +unsafe impl Sync for Cx {} + +static CONTEXT: std::sync::OnceLock = std::sync::OnceLock::new(); +static CALL_ARGS: std::sync::Mutex>> = std::sync::Mutex::new(vec![]); + +fn check_exception(this: &Ctx, err: rquickjs::Error) -> anyhow::Error { + let s = match err { + rquickjs::Error::Exception => { + let err = this.catch().into_exception().unwrap(); + let msg = err.message().unwrap_or_default(); + format!("Exception: {}\n{}", msg, err.stack().unwrap_or_default()) + } + err => err.to_string(), + }; + + let mem = extism_pdk::Memory::from_bytes(&s).unwrap(); + unsafe { + extism_pdk::extism::error_set(mem.offset()); + } + anyhow::Error::msg(s) +} #[export_name = "wizer.initialize"] extern "C" fn init() { @@ -20,39 +41,33 @@ extern "C" fn init() { let mut code = String::new(); io::stdin().read_to_string(&mut code).unwrap(); - let _ = context.with(|this| -> Result { - match this.eval(code) { - Ok(()) => (), - Err(e) => return Err(e), - } - Ok(Undefined) - }); - - unsafe { - CONTEXT - .set(context) - .map_err(|_| anyhow::anyhow!("Could not intialize JS Context")) - .unwrap() - } + context + .with(|this| -> Result { + match this.eval(code) { + Ok(()) => (), + Err(err) => return Err(check_exception(&this, err)), + } + Ok(Undefined) + }) + .unwrap(); + let _ = CONTEXT.set(Cx(context)); } -fn js_context() -> &'static Context { - unsafe { - if CONTEXT.get().is_none() { - init() - } - let context = CONTEXT.get_unchecked(); - context +fn js_context() -> Context { + if CONTEXT.get().is_none() { + init() } + let context = CONTEXT.get().unwrap(); + context.0.clone() } fn invoke<'a, T, F: for<'b> Fn(Ctx<'b>, Value<'b>) -> T>( idx: i32, conv: F, ) -> Result { - let call_args = unsafe { CALL_ARGS.pop() }; + let call_args = CALL_ARGS.lock().unwrap().pop(); let context = js_context(); - let result = context.with(|ctx| { + context.with(|ctx| { let call_args = call_args.unwrap(); let args: Args = call_args.iter().fold( Args::new(ctx.clone(), call_args.len()), @@ -90,111 +105,85 @@ fn invoke<'a, T, F: for<'b> Fn(Ctx<'b>, Value<'b>) -> T>( let res = conv(ctx.clone(), r); Ok(res) } - Err(err) => { - let e = format!("{:?}", err); - let mem = extism_pdk::Memory::from_bytes(&e).unwrap(); - unsafe { - extism_pdk::extism::error_set(mem.offset()); - } - Err(err) - } + Err(err) => Err(check_exception(&ctx, err)), } - })?; - Ok(result) + }) } #[no_mangle] pub extern "C" fn __arg_start() { - unsafe { - CALL_ARGS.push(vec![]); - } + CALL_ARGS.lock().unwrap().push(vec![]); } #[no_mangle] pub extern "C" fn __arg_i32(arg: i32) { - unsafe { - CALL_ARGS.last_mut().unwrap().push(ArgType::I32(arg)); - } + CALL_ARGS + .lock() + .unwrap() + .last_mut() + .unwrap() + .push(ArgType::I32(arg)); } #[no_mangle] pub extern "C" fn __arg_i64(arg: i64) { - unsafe { - CALL_ARGS.last_mut().unwrap().push(ArgType::I64(arg)); - } + CALL_ARGS + .lock() + .unwrap() + .last_mut() + .unwrap() + .push(ArgType::I64(arg)); } #[no_mangle] pub extern "C" fn __arg_f32(arg: f32) { - unsafe { - CALL_ARGS.last_mut().unwrap().push(ArgType::F32(arg)); - } + CALL_ARGS + .lock() + .unwrap() + .last_mut() + .unwrap() + .push(ArgType::F32(arg)); } #[no_mangle] pub extern "C" fn __arg_f64(arg: f64) { - unsafe { - CALL_ARGS.last_mut().unwrap().push(ArgType::F64(arg)); - } -} - -macro_rules! unwrap_value { - ($d:expr, $x:expr) => { - #[allow(clippy::blocks_in_conditions)] - match $x { - Ok(x) => x, - Err(e) => { - let err = format!("{:?}", e); - let mem = extism_pdk::Memory::from_bytes(&err).unwrap(); - unsafe { - extism_pdk::extism::error_set(mem.offset()); - } - $d - } - } - }; + CALL_ARGS + .lock() + .unwrap() + .last_mut() + .unwrap() + .push(ArgType::F64(arg)); } #[no_mangle] pub extern "C" fn __invoke_i32(idx: i32) -> i32 { - unwrap_value!( - -1, - invoke(idx, |_ctx, r| r.as_number().unwrap_or_default() as i32) - ) + invoke(idx, |_ctx, r| r.as_number().unwrap_or_default() as i32).unwrap_or(-1) } #[no_mangle] pub extern "C" fn __invoke_i64(idx: i32) -> i64 { - unwrap_value!( - -1, - invoke(idx, |_ctx, r| { - let Some(number) = r.as_big_int() else { - return 0; - }; - number.clone().to_i64().unwrap_or_default() - }) - ) + invoke(idx, |_ctx, r| { + let Some(number) = r.as_big_int() else { + return 0; + }; + number.clone().to_i64().unwrap_or_default() + }) + .unwrap_or(-1) } #[no_mangle] pub extern "C" fn __invoke_f64(idx: i32) -> f64 { - unwrap_value!( - -1.0, - invoke(idx, |_ctx, r| r.as_float().unwrap_or_default()) - ) + invoke(idx, |_ctx, r| r.as_float().unwrap_or_default()).unwrap_or(-1.0) } #[no_mangle] pub extern "C" fn __invoke_f32(idx: i32) -> f32 { - unwrap_value!( - -1.0, - invoke(idx, |_ctx, r| r.as_number().unwrap_or_default() as f32) - ) + invoke(idx, |_ctx, r| r.as_number().unwrap_or_default() as f32).unwrap_or(-1.0) } #[no_mangle] pub extern "C" fn __invoke(idx: i32) { - unwrap_value!((), invoke(idx, |_ctx, _r| ())) + invoke(idx, |_ctx, _r| ()).unwrap() } fn export_names(exports: Object) -> anyhow::Result> { From 0967fbac2a2afdaa281d12fe820b0270d627e6c8 Mon Sep 17 00:00:00 2001 From: Benjamin Eckel Date: Wed, 4 Dec 2024 08:57:20 -0600 Subject: [PATCH 2/8] Fix error swallowing bug Fixes #109 Just a test to repro for now. I tried to upgrade quickjs. Did some other house cleaning while in here. It appears that the problem may be in rquickjs, but maybe we are holding it wrong. Will look with Zach today. --- Makefile | 44 +++++++++++++++++----------- crates/core/Cargo.toml | 2 +- examples/exception/jsconfig.json | 8 +++++ examples/exception/script.d.ts | 3 ++ examples/exception/script.js | 6 ++++ examples/host_funcs/requirements.txt | 2 +- 6 files changed, 46 insertions(+), 19 deletions(-) create mode 100644 examples/exception/jsconfig.json create mode 100644 examples/exception/script.d.ts create mode 100644 examples/exception/script.js diff --git a/Makefile b/Makefile index 4c497c8..cb9798d 100644 --- a/Makefile +++ b/Makefile @@ -21,8 +21,8 @@ core: && npm run build \ && npx -y -p typescript tsc src/index.ts --lib es2020 --declaration --emitDeclarationOnly --outDir dist \ && cd ../.. \ - && cargo build --release --target=wasm32-wasi \ - && wasm-opt --enable-reference-types --enable-bulk-memory --strip -O3 ../../target/wasm32-wasi/release/js_pdk_core.wasm -o ../../target/wasm32-wasi/release/js_pdk_core.wasm \ + && cargo build --release --target=wasm32-wasip1 \ + && wasm-opt --enable-reference-types --enable-bulk-memory --strip -O3 ../../target/wasm32-wasip1/release/js_pdk_core.wasm -o ../../target/wasm32-wasip1/release/js_pdk_core.wasm \ && cd - fmt: fmt-core fmt-cli @@ -30,7 +30,7 @@ fmt: fmt-core fmt-cli fmt-core: cd crates/core/ \ && cargo fmt -- --check \ - && cargo clippy --target=wasm32-wasi -- -D warnings \ + && cargo clippy --target=wasm32-wasip1 -- -D warnings \ && cd - fmt-cli: @@ -50,21 +50,30 @@ clean-wasi-sdk: test: compile-examples @extism call examples/simple_js.wasm greet --wasi --input="Benjamin" @extism call examples/bundled.wasm greet --wasi --input="Benjamin" --allow-host "example.com" -ifeq ($(OS),Windows_NT) - @python3 -m venv ./.venv && \ - ./.venv/Scripts/activate.bat && \ - pip install -r examples/host_funcs/requirements.txt && \ - python examples/host_funcs/host.py examples/host_funcs.wasm && \ - ./.venv/Scripts/deactivate.bat -else - @python3 -m venv ./.venv && \ - . ./.venv/bin/activate && \ - pip install -r examples/host_funcs/requirements.txt && \ - python3 examples/host_funcs/host.py examples/host_funcs.wasm && \ - deactivate -endif +# ifeq ($(OS),Windows_NT) +# @python3 -m venv ./.venv && \ +# ./.venv/Scripts/activate.bat && \ +# pip install -r examples/host_funcs/requirements.txt && \ +# python examples/host_funcs/host.py examples/host_funcs.wasm && \ +# ./.venv/Scripts/deactivate.bat +# else +# @python3 -m venv ./.venv && \ +# . ./.venv/bin/activate && \ +# pip install -r examples/host_funcs/requirements.txt && \ +# python3 examples/host_funcs/host.py examples/host_funcs.wasm && \ +# deactivate +# endif @extism call examples/react.wasm render --wasi @extism call examples/react.wasm setState --input='{"action": "SET_SETTING", "payload": { "backgroundColor": "tomato" }}' --wasi + #@extism call examples/exception.wasm greet --wasi --input="Benjamin" + @error_msg=$$(extism call examples/exception.wasm greet --wasi --input="Benjamin" 2>&1); \ + if echo "$$error_msg" | grep -q "shibboleth"; then \ + echo "Test passed - found expected error"; \ + else \ + echo "Test failed - did not find expected error message"; \ + echo "Got: $$error_msg"; \ + exit 1; \ + fi compile-examples: cli cd examples/react && npm install && npm run build && cd ../.. @@ -72,8 +81,9 @@ compile-examples: cli cd examples/bundled && npm install && npm run build && cd ../.. ./target/release/extism-js examples/host_funcs/script.js -i examples/host_funcs/script.d.ts -o examples/host_funcs.wasm ./target/release/extism-js examples/exports/script.js -i examples/exports/script.d.ts -o examples/exports.wasm + ./target/release/extism-js examples/exception/script.js -i examples/exception/script.d.ts -o examples/exception.wasm kitchen: cd examples/kitchen-sink && npm install && npm run build && cd ../.. ./target/release/extism-js examples/kitchen-sink/dist/index.js -i examples/kitchen-sink/src/index.d.ts -o examples/kitchen-sink.wasm - @extism call examples/kitchen-sink.wasm greet --input "Steve" --wasi --allow-host "*" --config "last_name=Manuel" \ No newline at end of file + @extism call examples/kitchen-sink.wasm greet --input "Steve" --wasi --allow-host "*" --config "last_name=Manuel" diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index de47040..50aef50 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -13,7 +13,7 @@ once_cell = "1.16" anyhow = { workspace = true } quickjs-wasm-rs = "3" chrono = { version = "0.4", default-features = false, features = ["clock"] } -rquickjs = { version = "0.8.1", features = ["array-buffer", "bindgen"]} +rquickjs = { version = "0.8", features = ["array-buffer", "bindgen"]} base64 = "0.22.1" [lib] diff --git a/examples/exception/jsconfig.json b/examples/exception/jsconfig.json new file mode 100644 index 0000000..ca7514f --- /dev/null +++ b/examples/exception/jsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "lib": [], + "types": [ + "../../crates/core/src/prelude" + ] + } +} diff --git a/examples/exception/script.d.ts b/examples/exception/script.d.ts new file mode 100644 index 0000000..9c91c78 --- /dev/null +++ b/examples/exception/script.d.ts @@ -0,0 +1,3 @@ +declare module "main" { + export function greet(): I32; +} diff --git a/examples/exception/script.js b/examples/exception/script.js new file mode 100644 index 0000000..5dbfdb9 --- /dev/null +++ b/examples/exception/script.js @@ -0,0 +1,6 @@ + +function greet() { + throw new Error("I am a JS exception shibboleth") +} + +module.exports = { greet }; diff --git a/examples/host_funcs/requirements.txt b/examples/host_funcs/requirements.txt index ad15621..6836b9c 100644 --- a/examples/host_funcs/requirements.txt +++ b/examples/host_funcs/requirements.txt @@ -1 +1 @@ -extism==1.0.2 +extism==1.0.3 From 0caed1a56b86584c6edc1d5552329d5b6405c61b Mon Sep 17 00:00:00 2001 From: Benjamin Eckel Date: Wed, 4 Dec 2024 11:08:29 -0600 Subject: [PATCH 3/8] use wasip1 target in gh actions --- .github/workflows/ci.yml | 2 +- .github/workflows/publish.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 42944e5..fa49916 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: with: profile: default toolchain: stable - target: wasm32-wasi + target: wasm32-wasip1 default: true - name: Setup Go diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a0529be..ef5b8a1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -18,7 +18,7 @@ jobs: with: profile: default toolchain: stable - target: wasm32-wasi + target: wasm32-wasip1 default: true - name: Get wasm-opt @@ -35,13 +35,13 @@ jobs: run: make core - name: Opt core - run: wasm-opt --enable-reference-types --enable-bulk-memory --strip -O3 target/wasm32-wasi/release/js_pdk_core.wasm -o target/wasm32-wasi/release/js_pdk_core.wasm + run: wasm-opt --enable-reference-types --enable-bulk-memory --strip -O3 target/wasm32-wasip1/release/js_pdk_core.wasm -o target/wasm32-wasip1/release/js_pdk_core.wasm - name: Upload core binary to artifacts uses: actions/upload-artifact@v4 with: name: engine - path: target/wasm32-wasi/release/js_pdk_core.wasm + path: target/wasm32-wasip1/release/js_pdk_core.wasm compile_cli: name: Compile CLI From ba7e31e171e9f0abae8f4d3267f2dd764b1c1bdb Mon Sep 17 00:00:00 2001 From: zach Date: Thu, 5 Dec 2024 15:02:19 -0800 Subject: [PATCH 4/8] fix: finish converting to wasm32-wasip1 --- README.md | 2 +- crates/cli/build.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1e4e9ac..7167ba3 100644 --- a/README.md +++ b/README.md @@ -594,7 +594,7 @@ Before compiling the compiler, you need to install prerequisites. 1. Install Rust using [rustup](https://rustup.rs) 2. Install the WASI target platform via - `rustup target add --toolchain stable wasm32-wasi` + `rustup target add --toolchain stable wasm32-wasip1` 3. Install the wasi sdk using the makefile command: `make download-wasi-sdk` 4. Install [CMake](https://cmake.org/install/) (on macOS with homebrew, `brew install cmake`) diff --git a/crates/cli/build.rs b/crates/cli/build.rs index 3823fce..e5e2d28 100644 --- a/crates/cli/build.rs +++ b/crates/cli/build.rs @@ -32,7 +32,7 @@ fn copy_engine_binary() { if !is_override { engine_path.pop(); engine_path.pop(); - engine_path = engine_path.join("target/wasm32-wasi/release/js_pdk_core.wasm"); + engine_path = engine_path.join("target/wasm32-wasip1/release/js_pdk_core.wasm"); } println!("cargo:rerun-if-changed={}", engine_path.to_str().unwrap()); From dde2cfb7548c98300fd0d4defd7c055dce9b15c8 Mon Sep 17 00:00:00 2001 From: zach Date: Fri, 6 Dec 2024 11:21:03 -0800 Subject: [PATCH 5/8] cleanup: add get_arg closure to handle bigint/number conversion --- crates/core/src/globals.rs | 501 ++++++++++++------------------------- 1 file changed, 155 insertions(+), 346 deletions(-) diff --git a/crates/core/src/globals.rs b/crates/core/src/globals.rs index 3116968..9816056 100644 --- a/crates/core/src/globals.rs +++ b/crates/core/src/globals.rs @@ -254,361 +254,170 @@ fn build_host_object<'js>(this: Ctx<'js>) -> anyhow::Result> { Ok(host_object) } -fn add_host_functions(this: Ctx<'_>) -> anyhow::Result<()> { +fn add_host_functions<'a>(this: Ctx<'a>) -> anyhow::Result<()> { let globals = this.globals(); let host_object = globals.get::<_, Object>("Host")?; let invoke_host = host_object.get::<_, Value>("invokeHost")?; if invoke_host.is_null() || invoke_host.is_undefined() { - let host_invoke_func = Function::new(this.clone(), move |cx, args: Rest>| { - let func_id = args.first().unwrap().as_int().unwrap() as u32; - let len = args.len() - 1; - match len { - 0 => { - let result = unsafe { __invokeHostFunc_0_1(func_id) }; - - Ok(Value::new_float(cx, result as f64)) - } - 1 => { - let ptr = args - .get(1) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - let result = unsafe { __invokeHostFunc_1_1(func_id, ptr as u64) }; - Ok(Value::new_float(cx, result as f64)) - } - 2 => { - let ptr = args - .get(1) - .unwrap() - .as_big_int() + let host_invoke_func = + Function::new(this.clone(), move |cx: Ctx<'a>, args: Rest>| { + let func_id = args.first().unwrap().as_int().unwrap() as u32; + let len = args.len() - 1; + let get_arg = |index: usize| -> i64 { + let x: &Value = args.get(index).unwrap(); + x.as_big_int() .cloned() - .unwrap() - .to_i64() - .unwrap(); - let ptr2 = args - .get(2) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - let result = unsafe { __invokeHostFunc_2_1(func_id, ptr as u64, ptr2 as u64) }; - Ok(Value::new_float(cx, result as f64)) - } - 3 => { - let ptr = args - .get(1) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - let ptr2 = args - .get(2) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - let ptr3 = args - .get(3) - .unwrap() - .as_big_int() - .cloned() - .or_else(|| { - args.get(3) - .cloned() - .and_then(|x| match BigInt::from_value(x) { - Ok(x) => Some(x), - Err(_) => None, - }) + .and_then(|x| match x.to_i64() { + Ok(x) => Some(x), + Err(_) => None, }) - .unwrap() - .to_i64() - .unwrap(); - let result = unsafe { - __invokeHostFunc_3_1(func_id, ptr as u64, ptr2 as u64, ptr3 as u64) - }; - Ok(Value::new_float(cx, result as f64)) - } - 4 => { - let ptr = args - .get(1) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - let ptr2 = args - .get(2) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - let ptr3 = args - .get(3) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - let ptr4 = args - .get(4) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - let result = unsafe { - __invokeHostFunc_4_1( - func_id, - ptr as u64, - ptr2 as u64, - ptr3 as u64, - ptr4 as u64, - ) - }; - Ok(Value::new_float(cx, result as f64)) - } - 5 => { - let ptr = args - .get(1) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - let ptr2 = args - .get(2) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - let ptr3 = args - .get(3) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - let ptr4 = args - .get(4) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - let ptr5 = args - .get(5) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - let result = unsafe { - __invokeHostFunc_5_1( - func_id, - ptr as u64, - ptr2 as u64, - ptr3 as u64, - ptr4 as u64, - ptr5 as u64, - ) - }; - Ok(Value::new_float(cx, result as f64)) - } - n => Err(to_js_error( - cx, - anyhow!("__invokeHostFunc with {n} parameters is not implemented"), - )), - } - })?; - let host_invoke_func0 = Function::new(this.clone(), move |cx: Ctx, args: Rest| { - let func_id = args.first().unwrap().as_int().unwrap() as u32; - let len = args.len() - 1; - match len { - 0 => { - unsafe { __invokeHostFunc_0_0(func_id) }; - } - 1 => { - let ptr = args - .get(1) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - unsafe { __invokeHostFunc_1_0(func_id, ptr as u64) }; - } - 2 => { - let ptr = args - .get(1) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - let ptr2 = args - .get(2) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - unsafe { __invokeHostFunc_2_0(func_id, ptr as u64, ptr2 as u64) }; - } - 3 => { - let ptr = args - .get(1) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - let ptr2 = args - .get(2) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - let ptr3 = args - .get(3) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - unsafe { __invokeHostFunc_3_0(func_id, ptr as u64, ptr2 as u64, ptr3 as u64) }; - } - 4 => { - let ptr = args - .get(1) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - let ptr2 = args - .get(2) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - let ptr3 = args - .get(3) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - let ptr4 = args - .get(4) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - unsafe { - __invokeHostFunc_4_0( - func_id, - ptr as u64, - ptr2 as u64, - ptr3 as u64, - ptr4 as u64, - ) - }; - } - 5 => { - let ptr = args - .get(1) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - let ptr2 = args - .get(2) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - let ptr3 = args - .get(3) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - let ptr4 = args - .get(4) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - let ptr5 = args - .get(5) - .unwrap() - .as_big_int() - .cloned() - .unwrap() - .to_i64() - .unwrap(); - unsafe { - __invokeHostFunc_5_0( - func_id, - ptr as u64, - ptr2 as u64, - ptr3 as u64, - ptr4 as u64, - ptr5 as u64, - ) - }; - } - n => { - return Err(to_js_error( + .or_else(|| x.as_float().or_else(|| x.as_number()).map(|x| x as i64)) + .unwrap() + }; + match len { + 0 => { + let result = unsafe { __invokeHostFunc_0_1(func_id) }; + + Ok(Value::new_float(cx, result as f64)) + } + 1 => { + let ptr = get_arg(1); + let result = unsafe { __invokeHostFunc_1_1(func_id, ptr as u64) }; + Ok(Value::new_float(cx.clone(), result as f64)) + } + 2 => { + let ptr = get_arg(1); + let ptr2 = get_arg(2); + let result = + unsafe { __invokeHostFunc_2_1(func_id, ptr as u64, ptr2 as u64) }; + Ok(Value::new_float(cx, result as f64)) + } + 3 => { + let ptr = get_arg(1); + let ptr2 = get_arg(2); + let ptr3 = get_arg(3); + let result = unsafe { + __invokeHostFunc_3_1(func_id, ptr as u64, ptr2 as u64, ptr3 as u64) + }; + Ok(Value::new_float(cx, result as f64)) + } + 4 => { + let ptr = get_arg(1); + let ptr2 = get_arg(2); + let ptr3 = get_arg(3); + let ptr4 = get_arg(4); + let result = unsafe { + __invokeHostFunc_4_1( + func_id, + ptr as u64, + ptr2 as u64, + ptr3 as u64, + ptr4 as u64, + ) + }; + Ok(Value::new_float(cx, result as f64)) + } + 5 => { + let ptr = get_arg(1); + let ptr2 = get_arg(2); + let ptr3 = get_arg(3); + let ptr4 = get_arg(4); + let ptr5 = get_arg(5); + let result = unsafe { + __invokeHostFunc_5_1( + func_id, + ptr as u64, + ptr2 as u64, + ptr3 as u64, + ptr4 as u64, + ptr5 as u64, + ) + }; + Ok(Value::new_float(cx, result as f64)) + } + n => Err(to_js_error( cx, anyhow!("__invokeHostFunc with {n} parameters is not implemented"), - )) + )), } - } - Ok(Undefined) - })?; + })?; + let host_invoke_func0 = + Function::new(this.clone(), move |cx: Ctx<'_>, args: Rest>| { + let func_id = args.first().unwrap().as_int().unwrap() as u32; + let len = args.len() - 1; + let get_arg = |index: usize| -> i64 { + let x: &Value = args.get(index).unwrap(); + x.as_big_int() + .cloned() + .and_then(|x| match x.to_i64() { + Ok(x) => Some(x), + Err(_) => None, + }) + .or_else(|| x.as_float().or_else(|| x.as_number()).map(|x| x as i64)) + .unwrap() + }; + match len { + 0 => { + unsafe { __invokeHostFunc_0_0(func_id) }; + } + 1 => { + let ptr = get_arg(1); + unsafe { __invokeHostFunc_1_0(func_id, ptr as u64) }; + } + 2 => { + let ptr = get_arg(1); + let ptr2 = get_arg(2); + unsafe { __invokeHostFunc_2_0(func_id, ptr as u64, ptr2 as u64) }; + } + 3 => { + let ptr = get_arg(1); + let ptr2 = get_arg(2); + let ptr3 = get_arg(3); + unsafe { + __invokeHostFunc_3_0(func_id, ptr as u64, ptr2 as u64, ptr3 as u64) + }; + } + 4 => { + let ptr = get_arg(1); + let ptr2 = get_arg(2); + let ptr3 = get_arg(3); + let ptr4 = get_arg(4); + unsafe { + __invokeHostFunc_4_0( + func_id, + ptr as u64, + ptr2 as u64, + ptr3 as u64, + ptr4 as u64, + ) + }; + } + 5 => { + let ptr = get_arg(1); + let ptr2 = get_arg(2); + let ptr3 = get_arg(3); + let ptr4 = get_arg(4); + let ptr5 = get_arg(5); + unsafe { + __invokeHostFunc_5_0( + func_id, + ptr as u64, + ptr2 as u64, + ptr3 as u64, + ptr4 as u64, + ptr5 as u64, + ) + }; + } + n => { + return Err(to_js_error( + cx, + anyhow!("__invokeHostFunc with {n} parameters is not implemented"), + )) + } + } + Ok(Undefined) + })?; host_object.set("invokeFunc", host_invoke_func)?; host_object.set("invokeFunc0", host_invoke_func0)?; } From 1bef6acd2ea28ea56d5270cd40192d13c0f4d1fe Mon Sep 17 00:00:00 2001 From: zach Date: Fri, 6 Dec 2024 11:26:35 -0800 Subject: [PATCH 6/8] cleanup: update bigint usage in __invoke_i64 --- crates/core/src/lib.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 9d74669..aacb775 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -163,10 +163,12 @@ pub extern "C" fn __invoke_i32(idx: i32) -> i32 { #[no_mangle] pub extern "C" fn __invoke_i64(idx: i32) -> i64 { invoke(idx, |_ctx, r| { - let Some(number) = r.as_big_int() else { - return 0; - }; - number.clone().to_i64().unwrap_or_default() + if let Some(number) = r.as_big_int() { + return number.clone().to_i64().unwrap_or_default(); + } else if let Some(number) = r.as_number() { + return number as i64; + } + 0 }) .unwrap_or(-1) } From 107e84beba12e7fc15b9a25ef73549cf0a9261f4 Mon Sep 17 00:00:00 2001 From: zach Date: Fri, 6 Dec 2024 11:33:38 -0800 Subject: [PATCH 7/8] cleanup: make sure argument is converted to bigint --- crates/core/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index aacb775..a38a68e 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -77,7 +77,7 @@ fn invoke<'a, T, F: for<'b> Fn(Ctx<'b>, Value<'b>) -> T>( .push_arg(v) .expect("Should be able to convert i32 to JS arg"), ArgType::I64(v) => args - .push_arg(v) + .push_arg(rquickjs::BigInt::from_i64(ctx.clone(), *v)) .expect("Should be able to convert i64 to JS arg"), ArgType::F32(v) => args .push_arg(v) From 93471bfc98fff140e1f399e1cce08dceec489a9b Mon Sep 17 00:00:00 2001 From: Benjamin Eckel Date: Tue, 10 Dec 2024 09:33:16 -0600 Subject: [PATCH 8/8] comment, bump 1.3.1 --- Cargo.toml | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a0fa097..05128fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = [ ] [workspace.package] -version = "1.3.0" +version = "1.3.1" edition = "2021" authors = ["The Extism Authors"] license = "BSD-Clause-3" diff --git a/Makefile b/Makefile index cb9798d..b0b397a 100644 --- a/Makefile +++ b/Makefile @@ -50,6 +50,7 @@ clean-wasi-sdk: test: compile-examples @extism call examples/simple_js.wasm greet --wasi --input="Benjamin" @extism call examples/bundled.wasm greet --wasi --input="Benjamin" --allow-host "example.com" + # TODO uncomment block after python sdk is updated # ifeq ($(OS),Windows_NT) # @python3 -m venv ./.venv && \ # ./.venv/Scripts/activate.bat && \ @@ -65,7 +66,6 @@ test: compile-examples # endif @extism call examples/react.wasm render --wasi @extism call examples/react.wasm setState --input='{"action": "SET_SETTING", "payload": { "backgroundColor": "tomato" }}' --wasi - #@extism call examples/exception.wasm greet --wasi --input="Benjamin" @error_msg=$$(extism call examples/exception.wasm greet --wasi --input="Benjamin" 2>&1); \ if echo "$$error_msg" | grep -q "shibboleth"; then \ echo "Test passed - found expected error"; \