diff --git a/.github/workflows/check-all-features.yml b/.github/workflows/check-all-features.yml index ab91b3b7..b51ff936 100644 --- a/.github/workflows/check-all-features.yml +++ b/.github/workflows/check-all-features.yml @@ -15,21 +15,11 @@ jobs: strategy: fail-fast: false matrix: - features: - [ - 'napi3', - 'napi4', - 'napi5', - 'napi6', - 'napi7', - 'napi8', - 'experimental', - 'async', - 'chrono_date', - 'latin1', - 'full', - ] - + settings: + - features: 'napi1,napi2,napi3,napi4,napi5,napi6,napi7,napi8,experimental,async,chrono_date,latin1,full' + package: 'napi' + - features: 'compat-mode,strict,type-def,noop,full,default' + package: 'napi-derive' name: stable - ubuntu-latest runs-on: ubuntu-latest @@ -47,10 +37,7 @@ jobs: path: | ~/.cargo/registry ~/.cargo/git - key: stable-check-ubuntu-latest-cargo-cache-features-${{ matrix.features }} + key: stable-check-ubuntu-latest-cargo-cache-features-${{ matrix.settings.package }}-${{ matrix.features }} - name: Check build - uses: actions-rs/cargo@v1 - with: - command: check - args: -p napi --no-default-features --features ${{ matrix.features }} + run: cargo check -p ${{ matrix.settings.package }} -F ${{ matrix.settings.features }} diff --git a/cli/src/build.ts b/cli/src/build.ts index 052623e7..466c20d8 100644 --- a/cli/src/build.ts +++ b/cli/src/build.ts @@ -311,10 +311,6 @@ export class BuildCommand extends Command { rustflags.push('-C link-arg=-s') } - if (rustflags.length > 0) { - additionalEnv['RUSTFLAGS'] = rustflags.join(' ') - } - let useZig = false if (this.useZig || isCrossForLinux || isCrossForMacOS) { try { @@ -452,6 +448,22 @@ export class BuildCommand extends Command { tsConstEnum: tsConstEnumFromConfig, } = getNapiConfig(this.configFileName) const tsConstEnum = this.constEnum ?? tsConstEnumFromConfig + if (triple.platform === 'wasi') { + try { + const emnapiDir = require.resolve('emnapi') + const linkDir = join(emnapiDir, '..', 'lib', 'wasm32-wasi') + additionalEnv['EMNAPI_LINK_DIR'] = linkDir + rustflags.push('-Z wasi-exec-model=reactor') + } catch (e) { + const err = new Error(`Could not find emnapi, please install emnapi`) + err.cause = e + throw err + } + } + if (rustflags.length > 0) { + additionalEnv['RUSTFLAGS'] = rustflags.join(' ') + } + let cargoArtifactName = this.cargoName if (!cargoArtifactName) { if (this.bin) { @@ -479,23 +491,30 @@ export class BuildCommand extends Command { } else { debug(`Dylib name: ${chalk.greenBright(cargoArtifactName)}`) } - + const cwdSha = createHash('sha256') + .update(process.cwd()) + .digest('hex') + .substring(0, 8) const intermediateTypeFile = join( tmpdir(), - `${cargoArtifactName}-${createHash('sha256') - .update(process.cwd()) - .digest('hex') - .substring(0, 8)}.napi_type_def.tmp`, + `${cargoArtifactName}-${cwdSha}.napi_type_def.tmp`, + ) + const intermediateWasiRegisterFile = join( + tmpdir(), + `${cargoArtifactName}-${cwdSha}.napi_wasi_register.tmp`, ) debug(`intermediate type def file: ${intermediateTypeFile}`) + const commandEnv = { + ...process.env, + ...additionalEnv, + TYPE_DEF_TMP_PATH: intermediateTypeFile, + WASI_REGISTER_TMP_PATH: intermediateWasiRegisterFile, + } + try { execSync(cargoCommand, { - env: { - ...process.env, - ...additionalEnv, - TYPE_DEF_TMP_PATH: intermediateTypeFile, - }, + env: commandEnv, stdio: 'inherit', cwd, }) @@ -606,18 +625,6 @@ export class BuildCommand extends Command { this.dts ?? 'index.d.ts', ) - if (this.pipe) { - const pipeCommand = `${this.pipe} ${dtsFilePath}` - console.info(`Run ${chalk.green(pipeCommand)}`) - try { - execSync(pipeCommand, { stdio: 'inherit', env: process.env }) - } catch (e) { - console.warn( - chalk.bgYellowBright('Pipe the dts file to command failed'), - e, - ) - } - } const jsBindingFilePath = this.jsBinding && this.jsBinding !== 'false' && @@ -640,7 +647,7 @@ export class BuildCommand extends Command { const pipeCommand = `${this.pipe} ${jsBindingFilePath}` console.info(`Run ${chalk.green(pipeCommand)}`) try { - execSync(pipeCommand, { stdio: 'inherit', env: process.env }) + execSync(pipeCommand, { stdio: 'inherit', env: commandEnv }) } catch (e) { console.warn( chalk.bgYellowBright('Pipe the js binding file to command failed'), diff --git a/cli/src/js-binding-template.ts b/cli/src/js-binding-template.ts index 1d336c22..55be45ce 100644 --- a/cli/src/js-binding-template.ts +++ b/cli/src/js-binding-template.ts @@ -1,7 +1,13 @@ export const createJsBinding = ( localName: string, pkgName: string, -) => `const { existsSync, readFileSync } = require('fs') +) => `/* tslint:disable */ +/* eslint-disable */ +/* prettier-ignore */ + +/* auto-generated by NAPI-RS */ + +const { existsSync, readFileSync } = require('fs') const { join } = require('path') const { platform, arch } = process diff --git a/cli/src/new/.gitignore-template.ts b/cli/src/new/gitignore-template.ts similarity index 100% rename from cli/src/new/.gitignore-template.ts rename to cli/src/new/gitignore-template.ts diff --git a/cli/src/new/index.ts b/cli/src/new/index.ts index 61919acd..669e6f8f 100644 --- a/cli/src/new/index.ts +++ b/cli/src/new/index.ts @@ -10,10 +10,10 @@ import { debugFactory } from '../debug' import { DefaultPlatforms } from '../parse-triple' import { spawn } from '../spawn' -import { GitIgnore } from './.gitignore-template' import { createCargoContent } from './cargo' import { createCargoConfig } from './cargo-config' import { createGithubActionsCIYml } from './ci-yml' +import { GitIgnore } from './gitignore-template' import { LibRs } from './lib-rs' import { NPMIgnoreFiles } from './npmignore' import { createPackageJson } from './package' diff --git a/cli/src/parse-triple.ts b/cli/src/parse-triple.ts index a5da6662..987b9867 100644 --- a/cli/src/parse-triple.ts +++ b/cli/src/parse-triple.ts @@ -14,6 +14,7 @@ type NodeJSArch = | 'x32' | 'x64' | 'universal' + | 'wasm32' const CpuToNodeArch: { [index: string]: NodeJSArch } = { x86_64: 'x64', @@ -41,7 +42,7 @@ export const UniArchsByPlatform: Record = { } export interface PlatformDetail { - platform: NodeJS.Platform + platform: NodeJS.Platform | 'wasi' platformArchABI: string arch: NodeJSArch raw: string diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 3cbfcda9..80f10db7 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -1,3 +1,5 @@ +use std::sync::atomic::AtomicUsize; + use proc_macro2::{Ident, Span, TokenStream}; use crate::BindgenResult; @@ -12,6 +14,12 @@ pub const PROPERTY_ATTRIBUTE_WRITABLE: i32 = 1 << 0; pub const PROPERTY_ATTRIBUTE_ENUMERABLE: i32 = 1 << 1; pub const PROPERTY_ATTRIBUTE_CONFIGURABLE: i32 = 1 << 2; +static REGISTER_INDEX: AtomicUsize = AtomicUsize::new(0); + +thread_local! { + pub static REGISTER_IDENTS: std::cell::RefCell> = std::cell::RefCell::new(Vec::new()); +} + pub trait TryToTokens { fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()>; @@ -29,7 +37,11 @@ fn get_intermediate_ident(name: &str) -> Ident { } fn get_register_ident(name: &str) -> Ident { - let new_name = format!("__napi_register__{}", name); + let new_name = format!( + "__napi_register__{}_{}", + name, + REGISTER_INDEX.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + ); Ident::new(&new_name, Span::call_site()) } diff --git a/crates/backend/src/codegen/const.rs b/crates/backend/src/codegen/const.rs index 9828608a..fe67f79e 100644 --- a/crates/backend/src/codegen/const.rs +++ b/crates/backend/src/codegen/const.rs @@ -30,6 +30,9 @@ impl NapiConst { self.name.span(), ); let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref()); + crate::codegen::REGISTER_IDENTS.with(|c| { + c.borrow_mut().push(register_name.to_string()); + }); quote! { #[allow(non_snake_case)] #[allow(clippy::all)] @@ -38,11 +41,19 @@ impl NapiConst { } #[allow(non_snake_case)] #[allow(clippy::all)] - #[cfg(all(not(test), not(feature = "noop")))] + #[cfg(all(not(test), not(feature = "noop"), not(target_arch = "wasm32")))] #[napi::bindgen_prelude::ctor] fn #register_name() { napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name_lit, #cb_name); } + + #[allow(non_snake_case)] + #[allow(clippy::all)] + #[cfg(all(not(test), not(feature = "noop"), target_arch = "wasm32"))] + #[no_mangle] + unsafe extern "C" fn #register_name() { + napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name_lit, #cb_name); + } } } } diff --git a/crates/backend/src/codegen/enum.rs b/crates/backend/src/codegen/enum.rs index a69e299a..27dd3346 100644 --- a/crates/backend/src/codegen/enum.rs +++ b/crates/backend/src/codegen/enum.rs @@ -130,6 +130,10 @@ impl NapiEnum { let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref()); + crate::codegen::REGISTER_IDENTS.with(|c| { + c.borrow_mut().push(register_name.to_string()); + }); + quote! { #[allow(non_snake_case)] #[allow(clippy::all)] @@ -150,11 +154,18 @@ impl NapiEnum { } #[allow(non_snake_case)] #[allow(clippy::all)] - #[cfg(all(not(test), not(feature = "noop")))] + #[cfg(all(not(test), not(feature = "noop"), not(target_arch = "wasm32")))] #[napi::bindgen_prelude::ctor] fn #register_name() { napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name_lit, #callback_name); } + #[allow(non_snake_case)] + #[allow(clippy::all)] + #[cfg(all(not(test), not(feature = "noop"), target_arch = "wasm32"))] + #[no_mangle] + extern "C" fn #register_name() { + napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name_lit, #callback_name); + } } } } diff --git a/crates/backend/src/codegen/fn.rs b/crates/backend/src/codegen/fn.rs index dd78ea07..caf0acf2 100644 --- a/crates/backend/src/codegen/fn.rs +++ b/crates/backend/src/codegen/fn.rs @@ -474,7 +474,7 @@ impl NapiFn { match self.fn_self { Some(FnSelf::Value) => { // impossible, panic! in parser - unimplemented!(); + unreachable!(); } Some(FnSelf::Ref) | Some(FnSelf::MutRef) => quote! { this.#name }, None => match &self.parent { @@ -552,11 +552,14 @@ impl NapiFn { } else { let name_str = self.name.to_string(); let js_name = format!("{}\0", &self.js_name); - let name_len = js_name.len(); + let name_len = self.js_name.len(); let module_register_name = get_register_ident(&name_str); let intermediate_ident = get_intermediate_ident(&name_str); let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref()); let cb_name = Ident::new(&format!("{}_js_function", name_str), Span::call_site()); + crate::codegen::REGISTER_IDENTS.with(|c| { + c.borrow_mut().push(module_register_name.to_string()); + }); quote! { #[allow(non_snake_case)] #[allow(clippy::all)] @@ -566,7 +569,7 @@ impl NapiFn { napi::bindgen_prelude::check_status!( napi::bindgen_prelude::sys::napi_create_function( env, - #js_name.as_ptr() as *const _, + #js_name.as_ptr().cast(), #name_len, Some(#intermediate_ident), std::ptr::null_mut(), @@ -581,11 +584,19 @@ impl NapiFn { #[allow(clippy::all)] #[allow(non_snake_case)] - #[cfg(all(not(test), not(feature = "noop")))] + #[cfg(all(not(test), not(feature = "noop"), not(target_arch = "wasm32")))] #[napi::bindgen_prelude::ctor] fn #module_register_name() { napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name, #cb_name); } + + #[allow(clippy::all)] + #[allow(non_snake_case)] + #[cfg(all(not(test), not(feature = "noop"), target_arch = "wasm32"))] + #[no_mangle] + extern "C" fn #module_register_name() { + napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name, #cb_name); + } } } } diff --git a/crates/backend/src/codegen/struct.rs b/crates/backend/src/codegen/struct.rs index f8aa2221..eaf38521 100644 --- a/crates/backend/src/codegen/struct.rs +++ b/crates/backend/src/codegen/struct.rs @@ -760,14 +760,25 @@ impl NapiStruct { props.push(prop); } let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref()); + crate::codegen::REGISTER_IDENTS.with(|c| { + c.borrow_mut().push(struct_register_name.to_string()); + }); quote! { #[allow(non_snake_case)] #[allow(clippy::all)] - #[cfg(all(not(test), not(feature = "noop")))] + #[cfg(all(not(test), not(feature = "noop"), not(target_arch = "wasm32")))] #[napi::bindgen_prelude::ctor] fn #struct_register_name() { napi::__private::register_class(#name_str, #js_mod_ident, #js_name, vec![#(#props),*]); } + + #[allow(non_snake_case)] + #[allow(clippy::all)] + #[cfg(all(not(test), not(feature = "noop"), target_arch = "wasm32"))] + #[no_mangle] + extern "C" fn #struct_register_name() { + napi::__private::register_class(#name_str, #js_mod_ident, #js_name, vec![#(#props),*]); + } } } @@ -866,7 +877,11 @@ impl NapiImpl { let mut props: Vec<_> = props.into_iter().collect(); props.sort_by_key(|(_, prop)| prop.to_string()); let props = props.into_iter().map(|(_, prop)| prop); + let props_wasm = props.clone(); let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref()); + crate::codegen::REGISTER_IDENTS.with(|c| { + c.borrow_mut().push(register_name.to_string()); + }); Ok(quote! { #[allow(non_snake_case)] #[allow(clippy::all)] @@ -874,11 +889,17 @@ impl NapiImpl { use super::*; #(#methods)* - #[cfg(all(not(test), not(feature = "noop")))] + #[cfg(all(not(test), not(feature = "noop"), not(target_arch = "wasm32")))] #[napi::bindgen_prelude::ctor] fn #register_name() { napi::__private::register_class(#name_str, #js_mod_ident, #js_name, vec![#(#props),*]); } + + #[cfg(all(not(test), not(feature = "noop"), target_arch = "wasm32"))] + #[no_mangle] + extern "C" fn #register_name() { + napi::__private::register_class(#name_str, #js_mod_ident, #js_name, vec![#(#props_wasm),*]); + } } }) } diff --git a/crates/macro/src/lib.rs b/crates/macro/src/lib.rs index 9e8293f0..f9fc6dd9 100644 --- a/crates/macro/src/lib.rs +++ b/crates/macro/src/lib.rs @@ -11,16 +11,20 @@ extern crate quote; #[cfg(not(feature = "noop"))] use std::env; -use std::sync::atomic::{AtomicBool, Ordering}; +#[cfg(not(feature = "noop"))] +use std::fs; +#[cfg(not(feature = "noop"))] +use std::io::Write; #[cfg(all(feature = "type-def", not(feature = "noop")))] -use std::{ - fs, - io::{BufWriter, Result as IOResult, Write}, -}; +use std::io::{BufWriter, Result as IOResult}; +#[cfg(not(feature = "noop"))] +use std::sync::atomic::{AtomicBool, Ordering}; use napi_derive_backend::BindgenResult; #[cfg(not(feature = "noop"))] use napi_derive_backend::TryToTokens; +#[cfg(not(feature = "noop"))] +use napi_derive_backend::REGISTER_IDENTS; #[cfg(all(feature = "type-def", not(feature = "noop")))] use napi_derive_backend::{ToTypeDef, TypeDef}; #[cfg(not(feature = "noop"))] @@ -36,6 +40,7 @@ use syn::Item; #[cfg(feature = "compat-mode")] use syn::{fold::Fold, parse_macro_input, ItemFn}; +#[cfg(not(feature = "noop"))] /// a flag indicate whether or never at least one `napi` macro has been expanded. /// ```ignore /// if BUILT_FLAG @@ -64,8 +69,18 @@ pub fn napi(attr: RawStream, input: RawStream) -> RawStream { #[cfg(feature = "type-def")] if let Ok(type_def_file) = env::var("TYPE_DEF_TMP_PATH") { if let Err(_e) = fs::remove_file(type_def_file) { - // should only input in debug mode - // println!("Failed to manipulate type def file: {:?}", e); + #[cfg(debug_assertions)] + { + println!("Failed to manipulate type def file: {:?}", _e); + } + } + } + if let Ok(wasi_register_file) = env::var("WASI_REGISTER_TMP_PATH") { + if let Err(_e) = fs::remove_file(wasi_register_file) { + #[cfg(debug_assertions)] + { + println!("Failed to manipulate wasi register file: {:?}", _e); + } } } } @@ -75,6 +90,15 @@ pub fn napi(attr: RawStream, input: RawStream) -> RawStream { if env::var("DEBUG_GENERATED_CODE").is_ok() { println!("{}", tokens); } + REGISTER_IDENTS.with(|idents| { + if let Ok(wasi_register_file) = env::var("WASI_REGISTER_TMP_PATH") { + let mut file = + fs::File::create(wasi_register_file).expect("Create wasi register file failed"); + file + .write_all(format!("{:?}", idents.borrow()).as_bytes()) + .expect("Write wasi register file failed"); + } + }); tokens.into() } Err(diagnostic) => { @@ -338,7 +362,7 @@ pub fn module_exports(_attr: RawStream, input: RawStream) -> RawStream { }; let register = quote! { - #[napi::bindgen_prelude::ctor] + #[cfg_attr(not(target_arch = "wasm32"), napi::bindgen_prelude::ctor)] fn __napi__explicit_module_register() { unsafe fn register(raw_env: napi::sys::napi_env, raw_exports: napi::sys::napi_value) -> napi::Result<()> { use napi::{Env, JsObject, NapiValue}; diff --git a/crates/napi/Cargo.toml b/crates/napi/Cargo.toml index 37a77ad0..f991d328 100644 --- a/crates/napi/Cargo.toml +++ b/crates/napi/Cargo.toml @@ -69,10 +69,16 @@ version = "0.8" optional = true version = "0.4" -[dependencies.tokio] -features = ["rt", "rt-multi-thread", "sync"] -optional = true -version = "1" +[target.'cfg(target_arch = "wasm32")'.dependencies] +tokio = { version = "1", optional = true, features = ["rt", "sync"] } +napi-derive = { path = "../macro", version = "2.10.1", default-features = false } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1", optional = true, features = [ + "rt", + "rt-multi-thread", + "sync", +] } [dependencies.serde] optional = true diff --git a/crates/napi/src/async_work.rs b/crates/napi/src/async_work.rs index 49a26bb6..0acd3a84 100644 --- a/crates/napi/src/async_work.rs +++ b/crates/napi/src/async_work.rs @@ -77,7 +77,7 @@ pub fn run( complete:: as unsafe extern "C" fn(env: sys::napi_env, status: sys::napi_status, data: *mut c_void), ), - result as *mut _ as *mut c_void, + (result as *mut AsyncWork).cast(), &mut result.napi_async_work, ) })?; @@ -91,10 +91,8 @@ pub fn run( }) } -#[allow(clippy::non_send_fields_in_send_ty)] -unsafe impl Send for AsyncWork {} - -unsafe impl Sync for AsyncWork {} +unsafe impl Send for AsyncWork {} +unsafe impl Sync for AsyncWork {} /// env here is the same with the one in `CallContext`. /// So it actually could do nothing here, because `execute` function is called in the other thread mostly. diff --git a/crates/napi/src/bindgen_runtime/js_values/arraybuffer.rs b/crates/napi/src/bindgen_runtime/js_values/arraybuffer.rs index 214bc0fb..be4fc66f 100644 --- a/crates/napi/src/bindgen_runtime/js_values/arraybuffer.rs +++ b/crates/napi/src/bindgen_runtime/js_values/arraybuffer.rs @@ -7,7 +7,7 @@ use std::sync::{ Arc, }; -#[cfg(feature = "napi4")] +#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] use crate::bindgen_prelude::{CUSTOM_GC_TSFN, CUSTOM_GC_TSFN_CLOSED, MAIN_THREAD_ID}; pub use crate::js_values::TypedArrayType; use crate::{check_status, sys, Error, Result, Status}; @@ -66,7 +66,7 @@ macro_rules! impl_typed_array { fn drop(&mut self) { if Arc::strong_count(&self.drop_in_vm) == 1 { if let Some((ref_, env)) = self.raw { - #[cfg(feature = "napi4")] + #[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] { if CUSTOM_GC_TSFN_CLOSED.load(std::sync::atomic::Ordering::SeqCst) { return; diff --git a/crates/napi/src/bindgen_runtime/js_values/buffer.rs b/crates/napi/src/bindgen_runtime/js_values/buffer.rs index d133e8b3..ff249e42 100644 --- a/crates/napi/src/bindgen_runtime/js_values/buffer.rs +++ b/crates/napi/src/bindgen_runtime/js_values/buffer.rs @@ -9,7 +9,7 @@ use std::sync::Arc; #[cfg(all(debug_assertions, not(windows)))] use std::sync::Mutex; -#[cfg(feature = "napi4")] +#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] use crate::bindgen_prelude::{CUSTOM_GC_TSFN, CUSTOM_GC_TSFN_CLOSED, MAIN_THREAD_ID}; use crate::{bindgen_prelude::*, check_status, sys, Result, ValueType}; @@ -34,7 +34,7 @@ impl Drop for Buffer { fn drop(&mut self) { if Arc::strong_count(&self.ref_count) == 1 { if let Some((ref_, env)) = self.raw { - #[cfg(feature = "napi4")] + #[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] { if CUSTOM_GC_TSFN_CLOSED.load(std::sync::atomic::Ordering::SeqCst) { return; diff --git a/crates/napi/src/bindgen_runtime/js_values/promise.rs b/crates/napi/src/bindgen_runtime/js_values/promise.rs index 09fe1fd8..3605c5fa 100644 --- a/crates/napi/src/bindgen_runtime/js_values/promise.rs +++ b/crates/napi/src/bindgen_runtime/js_values/promise.rs @@ -1,30 +1,33 @@ -use std::ffi::CStr; +use std::ffi::{c_void, CStr}; use std::future; use std::pin::Pin; use std::ptr; use std::sync::{ - atomic::{AtomicBool, Ordering}, + atomic::{AtomicBool, AtomicPtr, Ordering}, Arc, }; -use std::task::{Context, Poll}; +use std::task::{Context, Poll, Waker}; -use tokio::sync::oneshot::{channel, Receiver, Sender}; - -use crate::{check_status, sys, Error, JsUnknown, NapiValue, Result, Status}; +use crate::{check_status, sys, Error, JsUnknown, NapiValue, Result}; use super::{FromNapiValue, TypeName, ValidateNapiValue}; -pub struct Promise { - value: Pin>>>, - aborted: Arc, +struct PromiseInner { + value: AtomicPtr>, + waker: AtomicPtr, + aborted: AtomicBool, } -impl Drop for Promise { +impl Drop for PromiseInner { fn drop(&mut self) { self.aborted.store(true, Ordering::SeqCst); } } +pub struct Promise { + inner: Arc>, +} + impl TypeName for Promise { fn type_name() -> &'static str { "Promise" @@ -100,9 +103,13 @@ impl FromNapiValue for Promise { )?; let mut promise_after_then = ptr::null_mut(); let mut then_js_cb = ptr::null_mut(); - let (tx, rx) = channel(); - let aborted = Arc::new(AtomicBool::new(false)); - let tx_ptr = Box::into_raw(Box::new((tx, aborted.clone()))); + let promise_inner = PromiseInner { + value: AtomicPtr::new(ptr::null_mut()), + waker: AtomicPtr::new(ptr::null_mut()), + aborted: AtomicBool::new(false), + }; + let shared_inner = Arc::new(promise_inner); + let context_ptr = Arc::into_raw(shared_inner.clone()); check_status!( unsafe { sys::napi_create_function( @@ -110,7 +117,7 @@ impl FromNapiValue for Promise { then_c_string.as_ptr(), 4, Some(then_callback::), - tx_ptr.cast(), + context_ptr as *mut c_void, &mut then_js_cb, ) }, @@ -145,7 +152,7 @@ impl FromNapiValue for Promise { catch_c_string.as_ptr(), 5, Some(catch_callback::), - tx_ptr.cast(), + context_ptr as *mut c_void, &mut catch_js_cb, ) }, @@ -165,8 +172,7 @@ impl FromNapiValue for Promise { "Failed to call catch method" )?; Ok(Promise { - value: Box::pin(rx), - aborted, + inner: shared_inner, }) } } @@ -174,13 +180,19 @@ impl FromNapiValue for Promise { impl future::Future for Promise { type Output = Result; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.value.as_mut().poll(cx) { - Poll::Pending => Poll::Pending, - Poll::Ready(v) => Poll::Ready( - v.map_err(|e| Error::new(Status::GenericFailure, format!("{}", e))) - .and_then(|v| unsafe { *Box::from_raw(v) }.map_err(Error::from)), - ), + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.inner.value.load(Ordering::Relaxed).is_null() { + if self.inner.waker.load(Ordering::Acquire).is_null() { + self.inner.waker.store( + Box::into_raw(Box::new(cx.waker().clone())), + Ordering::Release, + ); + } + Poll::Pending + } else { + Poll::Ready( + unsafe { Box::from_raw(self.inner.value.load(Ordering::Relaxed)) }.map_err(Error::from), + ) } } } @@ -206,15 +218,20 @@ unsafe extern "C" fn then_callback( get_cb_status == sys::Status::napi_ok, "Get callback info from Promise::then failed" ); - let (sender, aborted) = - *unsafe { Box::from_raw(data as *mut (Sender<*mut Result>, Arc)) }; + let PromiseInner { + value, + waker, + aborted, + } = &*unsafe { Arc::from_raw(data as *mut PromiseInner) }; if aborted.load(Ordering::SeqCst) { return this; } let resolve_value_t = Box::new(unsafe { T::from_napi_value(env, resolved_value[0]) }); - sender - .send(Box::into_raw(resolve_value_t)) - .expect("Send Promise resolved value error"); + value.store(Box::into_raw(resolve_value_t), Ordering::Relaxed); + let waker = waker.load(Ordering::Acquire); + if !waker.is_null() { + unsafe { Box::from_raw(waker) }.wake(); + } this } @@ -241,15 +258,23 @@ unsafe extern "C" fn catch_callback( "Get callback info from Promise::catch failed" ); let rejected_value = rejected_value[0]; - let (sender, aborted) = - *unsafe { Box::from_raw(data as *mut (Sender<*mut Result>, Arc)) }; + let PromiseInner { + value, + waker, + aborted, + } = &*unsafe { Arc::from_raw(data as *mut PromiseInner) }; if aborted.load(Ordering::SeqCst) { return this; } - sender - .send(Box::into_raw(Box::new(Err(Error::from(unsafe { + value.store( + Box::into_raw(Box::new(Err(Error::from(unsafe { JsUnknown::from_raw_unchecked(env, rejected_value) - }))))) - .expect("Send Promise resolved value error"); + })))), + Ordering::Relaxed, + ); + let waker = waker.load(Ordering::Acquire); + if !waker.is_null() { + unsafe { Box::from_raw(waker) }.wake(); + } this } diff --git a/crates/napi/src/bindgen_runtime/module_register.rs b/crates/napi/src/bindgen_runtime/module_register.rs index ff4b8e1c..5d463bfa 100644 --- a/crates/napi/src/bindgen_runtime/module_register.rs +++ b/crates/napi/src/bindgen_runtime/module_register.rs @@ -117,14 +117,14 @@ static IS_FIRST_MODULE: AtomicBool = AtomicBool::new(true); static FIRST_MODULE_REGISTERED: AtomicBool = AtomicBool::new(false); static REGISTERED_CLASSES: Lazy = Lazy::new(Default::default); static FN_REGISTER_MAP: Lazy = Lazy::new(Default::default); -#[cfg(feature = "napi4")] +#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] pub(crate) static CUSTOM_GC_TSFN: AtomicPtr = AtomicPtr::new(ptr::null_mut()); -#[cfg(feature = "napi4")] +#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] // CustomGC ThreadsafeFunction may be deleted during the process exit. // And there may still some Buffer alive after that. pub(crate) static CUSTOM_GC_TSFN_CLOSED: AtomicBool = AtomicBool::new(false); -#[cfg(feature = "napi4")] +#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] pub(crate) static MAIN_THREAD_ID: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); @@ -288,6 +288,15 @@ fn load_host() { } } +#[cfg(target_arch = "wasm32")] +#[no_mangle] +unsafe extern "C" fn napi_register_wasm_v1( + env: sys::napi_env, + exports: sys::napi_value, +) -> sys::napi_value { + unsafe { napi_register_module_v1(env, exports) } +} + #[no_mangle] unsafe extern "C" fn napi_register_module_v1( env: sys::napi_env, @@ -488,7 +497,7 @@ unsafe extern "C" fn napi_register_module_v1( ) }; } - #[cfg(feature = "napi4")] + #[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] create_custom_gc(env); FIRST_MODULE_REGISTERED.store(true, Ordering::SeqCst); exports @@ -511,7 +520,7 @@ pub(crate) unsafe extern "C" fn noop( ptr::null_mut() } -#[cfg(feature = "napi4")] +#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] fn create_custom_gc(env: sys::napi_env) { use std::os::raw::c_char; @@ -572,13 +581,13 @@ fn create_custom_gc(env: sys::napi_env) { MAIN_THREAD_ID.get_or_init(|| std::thread::current().id()); } -#[cfg(feature = "napi4")] +#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] #[allow(unused)] unsafe extern "C" fn empty(env: sys::napi_env, info: sys::napi_callback_info) -> sys::napi_value { ptr::null_mut() } -#[cfg(feature = "napi4")] +#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] #[allow(unused)] unsafe extern "C" fn custom_gc_finalize( env: sys::napi_env, @@ -588,7 +597,7 @@ unsafe extern "C" fn custom_gc_finalize( CUSTOM_GC_TSFN_CLOSED.store(true, Ordering::SeqCst); } -#[cfg(feature = "napi4")] +#[cfg(all(feature = "napi4", not(target_arch = "wasm32")))] // recycle the ArrayBuffer/Buffer Reference if the ArrayBuffer/Buffer is not dropped on the main thread extern "C" fn custom_gc( env: sys::napi_env, diff --git a/crates/napi/src/env.rs b/crates/napi/src/env.rs index 71828f7b..e076d43b 100644 --- a/crates/napi/src/env.rs +++ b/crates/napi/src/env.rs @@ -567,7 +567,7 @@ impl Env { pub fn create_function_from_closure(&self, name: &str, callback: F) -> Result where F: 'static + Fn(crate::CallContext<'_>) -> Result, - R: NapiRaw, + R: ToNapiValue, { use crate::CallContext; let boxed_callback = Box::new(callback); @@ -582,7 +582,7 @@ impl Env { name.as_ptr(), len, Some({ - unsafe extern "C" fn trampoline) -> Result>( + unsafe extern "C" fn trampoline) -> Result>( raw_env: sys::napi_env, cb_info: sys::napi_callback_info, ) -> sys::napi_value { @@ -636,7 +636,8 @@ impl Env { }; let env = &mut unsafe { Env::from_raw(raw_env) }; let ctx = CallContext::new(env, cb_info, raw_this, raw_args, raw_args.len()); - closure(ctx).map(|ret: R| unsafe { ret.raw() }) + closure(ctx) + .and_then(|ret: R| unsafe { ::to_napi_value(env.0, ret) }) })) .map_err(|e| { Error::from_reason(format!( diff --git a/crates/napi/src/threadsafe_function.rs b/crates/napi/src/threadsafe_function.rs index 8d1f8784..28850903 100644 --- a/crates/napi/src/threadsafe_function.rs +++ b/crates/napi/src/threadsafe_function.rs @@ -674,7 +674,7 @@ unsafe extern "C" fn call_js_cb( let message_length = message.len(); unsafe { sys::napi_fatal_error( - "threadsafe_function.rs:573\0".as_ptr().cast(), + "threadsafe_function.rs:642\0".as_ptr().cast(), 26, CString::new(message).unwrap().into_raw(), message_length, diff --git a/crates/napi/src/tokio_runtime.rs b/crates/napi/src/tokio_runtime.rs index a024bf38..53d397f4 100644 --- a/crates/napi/src/tokio_runtime.rs +++ b/crates/napi/src/tokio_runtime.rs @@ -6,8 +6,19 @@ use tokio::runtime::Runtime; use crate::{sys, JsDeferred, JsUnknown, NapiValue, Result}; pub(crate) static mut RT: Lazy> = Lazy::new(|| { - let runtime = tokio::runtime::Runtime::new().expect("Create tokio runtime failed"); - Some(runtime) + #[cfg(not(target_arch = "wasm32"))] + { + let runtime = tokio::runtime::Runtime::new().expect("Create tokio runtime failed"); + Some(runtime) + } + + #[cfg(target_arch = "wasm32")] + { + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .ok() + } }); #[cfg(windows)] @@ -74,14 +85,20 @@ pub fn execute_tokio_future< ) -> Result { let (deferred, promise) = JsDeferred::new(env)?; - spawn(async move { + let inner = async move { match fut.await { Ok(v) => deferred.resolve(|env| { resolver(env.raw(), v).map(|v| unsafe { JsUnknown::from_raw_unchecked(env.raw(), v) }) }), Err(e) => deferred.reject(e), } - }); + }; + + #[cfg(not(target_arch = "wasm32"))] + spawn(inner); + + #[cfg(target_arch = "wasm32")] + block_on(inner); Ok(promise.0.value) }