feat: export registers in wasm32 target (#1529)

This commit is contained in:
LongYinan 2023-03-20 18:42:27 +08:00 committed by GitHub
parent 5605bdf7fc
commit 550ef7c3cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 278 additions and 131 deletions

View file

@ -15,21 +15,11 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
features: settings:
[ - features: 'napi1,napi2,napi3,napi4,napi5,napi6,napi7,napi8,experimental,async,chrono_date,latin1,full'
'napi3', package: 'napi'
'napi4', - features: 'compat-mode,strict,type-def,noop,full,default'
'napi5', package: 'napi-derive'
'napi6',
'napi7',
'napi8',
'experimental',
'async',
'chrono_date',
'latin1',
'full',
]
name: stable - ubuntu-latest name: stable - ubuntu-latest
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -47,10 +37,7 @@ jobs:
path: | path: |
~/.cargo/registry ~/.cargo/registry
~/.cargo/git ~/.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 - name: Check build
uses: actions-rs/cargo@v1 run: cargo check -p ${{ matrix.settings.package }} -F ${{ matrix.settings.features }}
with:
command: check
args: -p napi --no-default-features --features ${{ matrix.features }}

View file

@ -311,10 +311,6 @@ export class BuildCommand extends Command {
rustflags.push('-C link-arg=-s') rustflags.push('-C link-arg=-s')
} }
if (rustflags.length > 0) {
additionalEnv['RUSTFLAGS'] = rustflags.join(' ')
}
let useZig = false let useZig = false
if (this.useZig || isCrossForLinux || isCrossForMacOS) { if (this.useZig || isCrossForLinux || isCrossForMacOS) {
try { try {
@ -452,6 +448,22 @@ export class BuildCommand extends Command {
tsConstEnum: tsConstEnumFromConfig, tsConstEnum: tsConstEnumFromConfig,
} = getNapiConfig(this.configFileName) } = getNapiConfig(this.configFileName)
const tsConstEnum = this.constEnum ?? tsConstEnumFromConfig 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 let cargoArtifactName = this.cargoName
if (!cargoArtifactName) { if (!cargoArtifactName) {
if (this.bin) { if (this.bin) {
@ -479,23 +491,30 @@ export class BuildCommand extends Command {
} else { } else {
debug(`Dylib name: ${chalk.greenBright(cargoArtifactName)}`) debug(`Dylib name: ${chalk.greenBright(cargoArtifactName)}`)
} }
const cwdSha = createHash('sha256')
const intermediateTypeFile = join(
tmpdir(),
`${cargoArtifactName}-${createHash('sha256')
.update(process.cwd()) .update(process.cwd())
.digest('hex') .digest('hex')
.substring(0, 8)}.napi_type_def.tmp`, .substring(0, 8)
const intermediateTypeFile = join(
tmpdir(),
`${cargoArtifactName}-${cwdSha}.napi_type_def.tmp`,
)
const intermediateWasiRegisterFile = join(
tmpdir(),
`${cargoArtifactName}-${cwdSha}.napi_wasi_register.tmp`,
) )
debug(`intermediate type def file: ${intermediateTypeFile}`) debug(`intermediate type def file: ${intermediateTypeFile}`)
try { const commandEnv = {
execSync(cargoCommand, {
env: {
...process.env, ...process.env,
...additionalEnv, ...additionalEnv,
TYPE_DEF_TMP_PATH: intermediateTypeFile, TYPE_DEF_TMP_PATH: intermediateTypeFile,
}, WASI_REGISTER_TMP_PATH: intermediateWasiRegisterFile,
}
try {
execSync(cargoCommand, {
env: commandEnv,
stdio: 'inherit', stdio: 'inherit',
cwd, cwd,
}) })
@ -606,18 +625,6 @@ export class BuildCommand extends Command {
this.dts ?? 'index.d.ts', 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 = const jsBindingFilePath =
this.jsBinding && this.jsBinding &&
this.jsBinding !== 'false' && this.jsBinding !== 'false' &&
@ -640,7 +647,7 @@ export class BuildCommand extends Command {
const pipeCommand = `${this.pipe} ${jsBindingFilePath}` const pipeCommand = `${this.pipe} ${jsBindingFilePath}`
console.info(`Run ${chalk.green(pipeCommand)}`) console.info(`Run ${chalk.green(pipeCommand)}`)
try { try {
execSync(pipeCommand, { stdio: 'inherit', env: process.env }) execSync(pipeCommand, { stdio: 'inherit', env: commandEnv })
} catch (e) { } catch (e) {
console.warn( console.warn(
chalk.bgYellowBright('Pipe the js binding file to command failed'), chalk.bgYellowBright('Pipe the js binding file to command failed'),

View file

@ -1,7 +1,13 @@
export const createJsBinding = ( export const createJsBinding = (
localName: string, localName: string,
pkgName: 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 { join } = require('path')
const { platform, arch } = process const { platform, arch } = process

View file

@ -10,10 +10,10 @@ import { debugFactory } from '../debug'
import { DefaultPlatforms } from '../parse-triple' import { DefaultPlatforms } from '../parse-triple'
import { spawn } from '../spawn' import { spawn } from '../spawn'
import { GitIgnore } from './.gitignore-template'
import { createCargoContent } from './cargo' import { createCargoContent } from './cargo'
import { createCargoConfig } from './cargo-config' import { createCargoConfig } from './cargo-config'
import { createGithubActionsCIYml } from './ci-yml' import { createGithubActionsCIYml } from './ci-yml'
import { GitIgnore } from './gitignore-template'
import { LibRs } from './lib-rs' import { LibRs } from './lib-rs'
import { NPMIgnoreFiles } from './npmignore' import { NPMIgnoreFiles } from './npmignore'
import { createPackageJson } from './package' import { createPackageJson } from './package'

View file

@ -14,6 +14,7 @@ type NodeJSArch =
| 'x32' | 'x32'
| 'x64' | 'x64'
| 'universal' | 'universal'
| 'wasm32'
const CpuToNodeArch: { [index: string]: NodeJSArch } = { const CpuToNodeArch: { [index: string]: NodeJSArch } = {
x86_64: 'x64', x86_64: 'x64',
@ -41,7 +42,7 @@ export const UniArchsByPlatform: Record<string, NodeJSArch[]> = {
} }
export interface PlatformDetail { export interface PlatformDetail {
platform: NodeJS.Platform platform: NodeJS.Platform | 'wasi'
platformArchABI: string platformArchABI: string
arch: NodeJSArch arch: NodeJSArch
raw: string raw: string

View file

@ -1,3 +1,5 @@
use std::sync::atomic::AtomicUsize;
use proc_macro2::{Ident, Span, TokenStream}; use proc_macro2::{Ident, Span, TokenStream};
use crate::BindgenResult; 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_ENUMERABLE: i32 = 1 << 1;
pub const PROPERTY_ATTRIBUTE_CONFIGURABLE: i32 = 1 << 2; pub const PROPERTY_ATTRIBUTE_CONFIGURABLE: i32 = 1 << 2;
static REGISTER_INDEX: AtomicUsize = AtomicUsize::new(0);
thread_local! {
pub static REGISTER_IDENTS: std::cell::RefCell<Vec<String>> = std::cell::RefCell::new(Vec::new());
}
pub trait TryToTokens { pub trait TryToTokens {
fn try_to_tokens(&self, tokens: &mut TokenStream) -> BindgenResult<()>; 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 { 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()) Ident::new(&new_name, Span::call_site())
} }

View file

@ -30,6 +30,9 @@ impl NapiConst {
self.name.span(), self.name.span(),
); );
let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref()); 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! { quote! {
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[allow(clippy::all)] #[allow(clippy::all)]
@ -38,11 +41,19 @@ impl NapiConst {
} }
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[allow(clippy::all)] #[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] #[napi::bindgen_prelude::ctor]
fn #register_name() { fn #register_name() {
napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name_lit, #cb_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);
}
} }
} }
} }

View file

@ -130,6 +130,10 @@ impl NapiEnum {
let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref()); 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! { quote! {
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[allow(clippy::all)] #[allow(clippy::all)]
@ -150,11 +154,18 @@ impl NapiEnum {
} }
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[allow(clippy::all)] #[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] #[napi::bindgen_prelude::ctor]
fn #register_name() { fn #register_name() {
napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name_lit, #callback_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);
}
} }
} }
} }

View file

@ -474,7 +474,7 @@ impl NapiFn {
match self.fn_self { match self.fn_self {
Some(FnSelf::Value) => { Some(FnSelf::Value) => {
// impossible, panic! in parser // impossible, panic! in parser
unimplemented!(); unreachable!();
} }
Some(FnSelf::Ref) | Some(FnSelf::MutRef) => quote! { this.#name }, Some(FnSelf::Ref) | Some(FnSelf::MutRef) => quote! { this.#name },
None => match &self.parent { None => match &self.parent {
@ -552,11 +552,14 @@ impl NapiFn {
} else { } else {
let name_str = self.name.to_string(); let name_str = self.name.to_string();
let js_name = format!("{}\0", &self.js_name); 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 module_register_name = get_register_ident(&name_str);
let intermediate_ident = get_intermediate_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 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()); 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! { quote! {
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[allow(clippy::all)] #[allow(clippy::all)]
@ -566,7 +569,7 @@ impl NapiFn {
napi::bindgen_prelude::check_status!( napi::bindgen_prelude::check_status!(
napi::bindgen_prelude::sys::napi_create_function( napi::bindgen_prelude::sys::napi_create_function(
env, env,
#js_name.as_ptr() as *const _, #js_name.as_ptr().cast(),
#name_len, #name_len,
Some(#intermediate_ident), Some(#intermediate_ident),
std::ptr::null_mut(), std::ptr::null_mut(),
@ -581,11 +584,19 @@ impl NapiFn {
#[allow(clippy::all)] #[allow(clippy::all)]
#[allow(non_snake_case)] #[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] #[napi::bindgen_prelude::ctor]
fn #module_register_name() { fn #module_register_name() {
napi::bindgen_prelude::register_module_export(#js_mod_ident, #js_name, #cb_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);
}
} }
} }
} }

View file

@ -760,14 +760,25 @@ impl NapiStruct {
props.push(prop); props.push(prop);
} }
let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref()); 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! { quote! {
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[allow(clippy::all)] #[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] #[napi::bindgen_prelude::ctor]
fn #struct_register_name() { fn #struct_register_name() {
napi::__private::register_class(#name_str, #js_mod_ident, #js_name, vec![#(#props),*]); 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(); let mut props: Vec<_> = props.into_iter().collect();
props.sort_by_key(|(_, prop)| prop.to_string()); props.sort_by_key(|(_, prop)| prop.to_string());
let props = props.into_iter().map(|(_, prop)| prop); 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()); 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! { Ok(quote! {
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[allow(clippy::all)] #[allow(clippy::all)]
@ -874,11 +889,17 @@ impl NapiImpl {
use super::*; use super::*;
#(#methods)* #(#methods)*
#[cfg(all(not(test), not(feature = "noop")))] #[cfg(all(not(test), not(feature = "noop"), not(target_arch = "wasm32")))]
#[napi::bindgen_prelude::ctor] #[napi::bindgen_prelude::ctor]
fn #register_name() { fn #register_name() {
napi::__private::register_class(#name_str, #js_mod_ident, #js_name, vec![#(#props),*]); 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),*]);
}
} }
}) })
} }

View file

@ -11,16 +11,20 @@ extern crate quote;
#[cfg(not(feature = "noop"))] #[cfg(not(feature = "noop"))]
use std::env; 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")))] #[cfg(all(feature = "type-def", not(feature = "noop")))]
use std::{ use std::io::{BufWriter, Result as IOResult};
fs, #[cfg(not(feature = "noop"))]
io::{BufWriter, Result as IOResult, Write}, use std::sync::atomic::{AtomicBool, Ordering};
};
use napi_derive_backend::BindgenResult; use napi_derive_backend::BindgenResult;
#[cfg(not(feature = "noop"))] #[cfg(not(feature = "noop"))]
use napi_derive_backend::TryToTokens; use napi_derive_backend::TryToTokens;
#[cfg(not(feature = "noop"))]
use napi_derive_backend::REGISTER_IDENTS;
#[cfg(all(feature = "type-def", not(feature = "noop")))] #[cfg(all(feature = "type-def", not(feature = "noop")))]
use napi_derive_backend::{ToTypeDef, TypeDef}; use napi_derive_backend::{ToTypeDef, TypeDef};
#[cfg(not(feature = "noop"))] #[cfg(not(feature = "noop"))]
@ -36,6 +40,7 @@ use syn::Item;
#[cfg(feature = "compat-mode")] #[cfg(feature = "compat-mode")]
use syn::{fold::Fold, parse_macro_input, ItemFn}; 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. /// a flag indicate whether or never at least one `napi` macro has been expanded.
/// ```ignore /// ```ignore
/// if BUILT_FLAG /// if BUILT_FLAG
@ -64,8 +69,18 @@ pub fn napi(attr: RawStream, input: RawStream) -> RawStream {
#[cfg(feature = "type-def")] #[cfg(feature = "type-def")]
if let Ok(type_def_file) = env::var("TYPE_DEF_TMP_PATH") { if let Ok(type_def_file) = env::var("TYPE_DEF_TMP_PATH") {
if let Err(_e) = fs::remove_file(type_def_file) { if let Err(_e) = fs::remove_file(type_def_file) {
// should only input in debug mode #[cfg(debug_assertions)]
// println!("Failed to manipulate type def file: {:?}", e); {
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() { if env::var("DEBUG_GENERATED_CODE").is_ok() {
println!("{}", tokens); 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() tokens.into()
} }
Err(diagnostic) => { Err(diagnostic) => {
@ -338,7 +362,7 @@ pub fn module_exports(_attr: RawStream, input: RawStream) -> RawStream {
}; };
let register = quote! { let register = quote! {
#[napi::bindgen_prelude::ctor] #[cfg_attr(not(target_arch = "wasm32"), napi::bindgen_prelude::ctor)]
fn __napi__explicit_module_register() { fn __napi__explicit_module_register() {
unsafe fn register(raw_env: napi::sys::napi_env, raw_exports: napi::sys::napi_value) -> napi::Result<()> { unsafe fn register(raw_env: napi::sys::napi_env, raw_exports: napi::sys::napi_value) -> napi::Result<()> {
use napi::{Env, JsObject, NapiValue}; use napi::{Env, JsObject, NapiValue};

View file

@ -69,10 +69,16 @@ version = "0.8"
optional = true optional = true
version = "0.4" version = "0.4"
[dependencies.tokio] [target.'cfg(target_arch = "wasm32")'.dependencies]
features = ["rt", "rt-multi-thread", "sync"] tokio = { version = "1", optional = true, features = ["rt", "sync"] }
optional = true napi-derive = { path = "../macro", version = "2.10.1", default-features = false }
version = "1"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1", optional = true, features = [
"rt",
"rt-multi-thread",
"sync",
] }
[dependencies.serde] [dependencies.serde]
optional = true optional = true

View file

@ -77,7 +77,7 @@ pub fn run<T: Task>(
complete::<T> complete::<T>
as unsafe extern "C" fn(env: sys::napi_env, status: sys::napi_status, data: *mut c_void), 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<T>).cast(),
&mut result.napi_async_work, &mut result.napi_async_work,
) )
})?; })?;
@ -91,10 +91,8 @@ pub fn run<T: Task>(
}) })
} }
#[allow(clippy::non_send_fields_in_send_ty)] unsafe impl<T: Task + Send> Send for AsyncWork<T> {}
unsafe impl<T: Task> Send for AsyncWork<T> {} unsafe impl<T: Task + Sync> Sync for AsyncWork<T> {}
unsafe impl<T: Task> Sync for AsyncWork<T> {}
/// env here is the same with the one in `CallContext`. /// 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. /// So it actually could do nothing here, because `execute` function is called in the other thread mostly.

View file

@ -7,7 +7,7 @@ use std::sync::{
Arc, 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}; use crate::bindgen_prelude::{CUSTOM_GC_TSFN, CUSTOM_GC_TSFN_CLOSED, MAIN_THREAD_ID};
pub use crate::js_values::TypedArrayType; pub use crate::js_values::TypedArrayType;
use crate::{check_status, sys, Error, Result, Status}; use crate::{check_status, sys, Error, Result, Status};
@ -66,7 +66,7 @@ macro_rules! impl_typed_array {
fn drop(&mut self) { fn drop(&mut self) {
if Arc::strong_count(&self.drop_in_vm) == 1 { if Arc::strong_count(&self.drop_in_vm) == 1 {
if let Some((ref_, env)) = self.raw { 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) { if CUSTOM_GC_TSFN_CLOSED.load(std::sync::atomic::Ordering::SeqCst) {
return; return;

View file

@ -9,7 +9,7 @@ use std::sync::Arc;
#[cfg(all(debug_assertions, not(windows)))] #[cfg(all(debug_assertions, not(windows)))]
use std::sync::Mutex; 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::{CUSTOM_GC_TSFN, CUSTOM_GC_TSFN_CLOSED, MAIN_THREAD_ID};
use crate::{bindgen_prelude::*, check_status, sys, Result, ValueType}; use crate::{bindgen_prelude::*, check_status, sys, Result, ValueType};
@ -34,7 +34,7 @@ impl Drop for Buffer {
fn drop(&mut self) { fn drop(&mut self) {
if Arc::strong_count(&self.ref_count) == 1 { if Arc::strong_count(&self.ref_count) == 1 {
if let Some((ref_, env)) = self.raw { 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) { if CUSTOM_GC_TSFN_CLOSED.load(std::sync::atomic::Ordering::SeqCst) {
return; return;

View file

@ -1,30 +1,33 @@
use std::ffi::CStr; use std::ffi::{c_void, CStr};
use std::future; use std::future;
use std::pin::Pin; use std::pin::Pin;
use std::ptr; use std::ptr;
use std::sync::{ use std::sync::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, AtomicPtr, Ordering},
Arc, 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};
use crate::{check_status, sys, Error, JsUnknown, NapiValue, Result, Status};
use super::{FromNapiValue, TypeName, ValidateNapiValue}; use super::{FromNapiValue, TypeName, ValidateNapiValue};
pub struct Promise<T: FromNapiValue> { struct PromiseInner<T: FromNapiValue> {
value: Pin<Box<Receiver<*mut Result<T>>>>, value: AtomicPtr<Result<T>>,
aborted: Arc<AtomicBool>, waker: AtomicPtr<Waker>,
aborted: AtomicBool,
} }
impl<T: FromNapiValue> Drop for Promise<T> { impl<T: FromNapiValue> Drop for PromiseInner<T> {
fn drop(&mut self) { fn drop(&mut self) {
self.aborted.store(true, Ordering::SeqCst); self.aborted.store(true, Ordering::SeqCst);
} }
} }
pub struct Promise<T: FromNapiValue> {
inner: Arc<PromiseInner<T>>,
}
impl<T: FromNapiValue> TypeName for Promise<T> { impl<T: FromNapiValue> TypeName for Promise<T> {
fn type_name() -> &'static str { fn type_name() -> &'static str {
"Promise" "Promise"
@ -100,9 +103,13 @@ impl<T: FromNapiValue> FromNapiValue for Promise<T> {
)?; )?;
let mut promise_after_then = ptr::null_mut(); let mut promise_after_then = ptr::null_mut();
let mut then_js_cb = ptr::null_mut(); let mut then_js_cb = ptr::null_mut();
let (tx, rx) = channel(); let promise_inner = PromiseInner {
let aborted = Arc::new(AtomicBool::new(false)); value: AtomicPtr::new(ptr::null_mut()),
let tx_ptr = Box::into_raw(Box::new((tx, aborted.clone()))); 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!( check_status!(
unsafe { unsafe {
sys::napi_create_function( sys::napi_create_function(
@ -110,7 +117,7 @@ impl<T: FromNapiValue> FromNapiValue for Promise<T> {
then_c_string.as_ptr(), then_c_string.as_ptr(),
4, 4,
Some(then_callback::<T>), Some(then_callback::<T>),
tx_ptr.cast(), context_ptr as *mut c_void,
&mut then_js_cb, &mut then_js_cb,
) )
}, },
@ -145,7 +152,7 @@ impl<T: FromNapiValue> FromNapiValue for Promise<T> {
catch_c_string.as_ptr(), catch_c_string.as_ptr(),
5, 5,
Some(catch_callback::<T>), Some(catch_callback::<T>),
tx_ptr.cast(), context_ptr as *mut c_void,
&mut catch_js_cb, &mut catch_js_cb,
) )
}, },
@ -165,8 +172,7 @@ impl<T: FromNapiValue> FromNapiValue for Promise<T> {
"Failed to call catch method" "Failed to call catch method"
)?; )?;
Ok(Promise { Ok(Promise {
value: Box::pin(rx), inner: shared_inner,
aborted,
}) })
} }
} }
@ -174,13 +180,19 @@ impl<T: FromNapiValue> FromNapiValue for Promise<T> {
impl<T: FromNapiValue> future::Future for Promise<T> { impl<T: FromNapiValue> future::Future for Promise<T> {
type Output = Result<T>; type Output = Result<T>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.value.as_mut().poll(cx) { if self.inner.value.load(Ordering::Relaxed).is_null() {
Poll::Pending => Poll::Pending, if self.inner.waker.load(Ordering::Acquire).is_null() {
Poll::Ready(v) => Poll::Ready( self.inner.waker.store(
v.map_err(|e| Error::new(Status::GenericFailure, format!("{}", e))) Box::into_raw(Box::new(cx.waker().clone())),
.and_then(|v| unsafe { *Box::from_raw(v) }.map_err(Error::from)), 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<T: FromNapiValue>(
get_cb_status == sys::Status::napi_ok, get_cb_status == sys::Status::napi_ok,
"Get callback info from Promise::then failed" "Get callback info from Promise::then failed"
); );
let (sender, aborted) = let PromiseInner {
*unsafe { Box::from_raw(data as *mut (Sender<*mut Result<T>>, Arc<AtomicBool>)) }; value,
waker,
aborted,
} = &*unsafe { Arc::from_raw(data as *mut PromiseInner<T>) };
if aborted.load(Ordering::SeqCst) { if aborted.load(Ordering::SeqCst) {
return this; return this;
} }
let resolve_value_t = Box::new(unsafe { T::from_napi_value(env, resolved_value[0]) }); let resolve_value_t = Box::new(unsafe { T::from_napi_value(env, resolved_value[0]) });
sender value.store(Box::into_raw(resolve_value_t), Ordering::Relaxed);
.send(Box::into_raw(resolve_value_t)) let waker = waker.load(Ordering::Acquire);
.expect("Send Promise resolved value error"); if !waker.is_null() {
unsafe { Box::from_raw(waker) }.wake();
}
this this
} }
@ -241,15 +258,23 @@ unsafe extern "C" fn catch_callback<T: FromNapiValue>(
"Get callback info from Promise::catch failed" "Get callback info from Promise::catch failed"
); );
let rejected_value = rejected_value[0]; let rejected_value = rejected_value[0];
let (sender, aborted) = let PromiseInner {
*unsafe { Box::from_raw(data as *mut (Sender<*mut Result<T>>, Arc<AtomicBool>)) }; value,
waker,
aborted,
} = &*unsafe { Arc::from_raw(data as *mut PromiseInner<T>) };
if aborted.load(Ordering::SeqCst) { if aborted.load(Ordering::SeqCst) {
return this; return this;
} }
sender value.store(
.send(Box::into_raw(Box::new(Err(Error::from(unsafe { Box::into_raw(Box::new(Err(Error::from(unsafe {
JsUnknown::from_raw_unchecked(env, rejected_value) 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 this
} }

View file

@ -117,14 +117,14 @@ static IS_FIRST_MODULE: AtomicBool = AtomicBool::new(true);
static FIRST_MODULE_REGISTERED: AtomicBool = AtomicBool::new(false); static FIRST_MODULE_REGISTERED: AtomicBool = AtomicBool::new(false);
static REGISTERED_CLASSES: Lazy<RegisteredClassesMap> = Lazy::new(Default::default); static REGISTERED_CLASSES: Lazy<RegisteredClassesMap> = Lazy::new(Default::default);
static FN_REGISTER_MAP: Lazy<FnRegisterMap> = Lazy::new(Default::default); static FN_REGISTER_MAP: Lazy<FnRegisterMap> = Lazy::new(Default::default);
#[cfg(feature = "napi4")] #[cfg(all(feature = "napi4", not(target_arch = "wasm32")))]
pub(crate) static CUSTOM_GC_TSFN: AtomicPtr<sys::napi_threadsafe_function__> = pub(crate) static CUSTOM_GC_TSFN: AtomicPtr<sys::napi_threadsafe_function__> =
AtomicPtr::new(ptr::null_mut()); 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. // CustomGC ThreadsafeFunction may be deleted during the process exit.
// And there may still some Buffer alive after that. // And there may still some Buffer alive after that.
pub(crate) static CUSTOM_GC_TSFN_CLOSED: AtomicBool = AtomicBool::new(false); 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<ThreadId> = pub(crate) static MAIN_THREAD_ID: once_cell::sync::OnceCell<ThreadId> =
once_cell::sync::OnceCell::new(); 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] #[no_mangle]
unsafe extern "C" fn napi_register_module_v1( unsafe extern "C" fn napi_register_module_v1(
env: sys::napi_env, 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); create_custom_gc(env);
FIRST_MODULE_REGISTERED.store(true, Ordering::SeqCst); FIRST_MODULE_REGISTERED.store(true, Ordering::SeqCst);
exports exports
@ -511,7 +520,7 @@ pub(crate) unsafe extern "C" fn noop(
ptr::null_mut() ptr::null_mut()
} }
#[cfg(feature = "napi4")] #[cfg(all(feature = "napi4", not(target_arch = "wasm32")))]
fn create_custom_gc(env: sys::napi_env) { fn create_custom_gc(env: sys::napi_env) {
use std::os::raw::c_char; 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()); MAIN_THREAD_ID.get_or_init(|| std::thread::current().id());
} }
#[cfg(feature = "napi4")] #[cfg(all(feature = "napi4", not(target_arch = "wasm32")))]
#[allow(unused)] #[allow(unused)]
unsafe extern "C" fn empty(env: sys::napi_env, info: sys::napi_callback_info) -> sys::napi_value { unsafe extern "C" fn empty(env: sys::napi_env, info: sys::napi_callback_info) -> sys::napi_value {
ptr::null_mut() ptr::null_mut()
} }
#[cfg(feature = "napi4")] #[cfg(all(feature = "napi4", not(target_arch = "wasm32")))]
#[allow(unused)] #[allow(unused)]
unsafe extern "C" fn custom_gc_finalize( unsafe extern "C" fn custom_gc_finalize(
env: sys::napi_env, env: sys::napi_env,
@ -588,7 +597,7 @@ unsafe extern "C" fn custom_gc_finalize(
CUSTOM_GC_TSFN_CLOSED.store(true, Ordering::SeqCst); 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 // recycle the ArrayBuffer/Buffer Reference if the ArrayBuffer/Buffer is not dropped on the main thread
extern "C" fn custom_gc( extern "C" fn custom_gc(
env: sys::napi_env, env: sys::napi_env,

View file

@ -567,7 +567,7 @@ impl Env {
pub fn create_function_from_closure<R, F>(&self, name: &str, callback: F) -> Result<JsFunction> pub fn create_function_from_closure<R, F>(&self, name: &str, callback: F) -> Result<JsFunction>
where where
F: 'static + Fn(crate::CallContext<'_>) -> Result<R>, F: 'static + Fn(crate::CallContext<'_>) -> Result<R>,
R: NapiRaw, R: ToNapiValue,
{ {
use crate::CallContext; use crate::CallContext;
let boxed_callback = Box::new(callback); let boxed_callback = Box::new(callback);
@ -582,7 +582,7 @@ impl Env {
name.as_ptr(), name.as_ptr(),
len, len,
Some({ Some({
unsafe extern "C" fn trampoline<R: NapiRaw, F: Fn(CallContext<'_>) -> Result<R>>( unsafe extern "C" fn trampoline<R: ToNapiValue, F: Fn(CallContext<'_>) -> Result<R>>(
raw_env: sys::napi_env, raw_env: sys::napi_env,
cb_info: sys::napi_callback_info, cb_info: sys::napi_callback_info,
) -> sys::napi_value { ) -> sys::napi_value {
@ -636,7 +636,8 @@ impl Env {
}; };
let env = &mut unsafe { Env::from_raw(raw_env) }; let env = &mut unsafe { Env::from_raw(raw_env) };
let ctx = CallContext::new(env, cb_info, raw_this, raw_args, raw_args.len()); 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 { <R as ToNapiValue>::to_napi_value(env.0, ret) })
})) }))
.map_err(|e| { .map_err(|e| {
Error::from_reason(format!( Error::from_reason(format!(

View file

@ -674,7 +674,7 @@ unsafe extern "C" fn call_js_cb<T: 'static, V: ToNapiValue, R, ES>(
let message_length = message.len(); let message_length = message.len();
unsafe { unsafe {
sys::napi_fatal_error( sys::napi_fatal_error(
"threadsafe_function.rs:573\0".as_ptr().cast(), "threadsafe_function.rs:642\0".as_ptr().cast(),
26, 26,
CString::new(message).unwrap().into_raw(), CString::new(message).unwrap().into_raw(),
message_length, message_length,

View file

@ -6,8 +6,19 @@ use tokio::runtime::Runtime;
use crate::{sys, JsDeferred, JsUnknown, NapiValue, Result}; use crate::{sys, JsDeferred, JsUnknown, NapiValue, Result};
pub(crate) static mut RT: Lazy<Option<Runtime>> = Lazy::new(|| { pub(crate) static mut RT: Lazy<Option<Runtime>> = Lazy::new(|| {
#[cfg(not(target_arch = "wasm32"))]
{
let runtime = tokio::runtime::Runtime::new().expect("Create tokio runtime failed"); let runtime = tokio::runtime::Runtime::new().expect("Create tokio runtime failed");
Some(runtime) Some(runtime)
}
#[cfg(target_arch = "wasm32")]
{
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.ok()
}
}); });
#[cfg(windows)] #[cfg(windows)]
@ -74,14 +85,20 @@ pub fn execute_tokio_future<
) -> Result<sys::napi_value> { ) -> Result<sys::napi_value> {
let (deferred, promise) = JsDeferred::new(env)?; let (deferred, promise) = JsDeferred::new(env)?;
spawn(async move { let inner = async move {
match fut.await { match fut.await {
Ok(v) => deferred.resolve(|env| { Ok(v) => deferred.resolve(|env| {
resolver(env.raw(), v).map(|v| unsafe { JsUnknown::from_raw_unchecked(env.raw(), v) }) resolver(env.raw(), v).map(|v| unsafe { JsUnknown::from_raw_unchecked(env.raw(), v) })
}), }),
Err(e) => deferred.reject(e), Err(e) => deferred.reject(e),
} }
}); };
#[cfg(not(target_arch = "wasm32"))]
spawn(inner);
#[cfg(target_arch = "wasm32")]
block_on(inner);
Ok(promise.0.value) Ok(promise.0.value)
} }