-
Notifications
You must be signed in to change notification settings - Fork 44
Add BigInt support and more nj-core support for N-API v7. #97
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
dist |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
[package] | ||
name = "nj-example-bigint" | ||
version = "0.1.0" | ||
authors = ["fluvio.io"] | ||
edition = "2018" | ||
|
||
|
||
[lib] | ||
crate-type = ["cdylib"] | ||
|
||
|
||
[dependencies] | ||
node-bindgen = { path = "../.."} | ||
|
||
|
||
[build-dependencies] | ||
node-bindgen = { path = "../../", features = ["build"] } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
all: build | ||
|
||
build: | ||
nj-cli build | ||
|
||
test: build | ||
node test.js | ||
|
||
|
||
clean: | ||
rm -rf dist | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
manual JS callback |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
fn main() { | ||
node_bindgen::build::configure(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
use node_bindgen::derive::node_bindgen; | ||
use node_bindgen::core::bigint::BigInt; | ||
|
||
// example where we multiply a big_int. | ||
#[node_bindgen] | ||
fn multiply_big_int(arg: BigInt) -> BigInt { | ||
println!("bigint arg: {}", arg); | ||
arg*2 | ||
} | ||
|
||
// Test that we can go across the FFI without screwing up the bits. | ||
#[node_bindgen] | ||
fn do_nothing(arg: BigInt) -> BigInt { | ||
println!("bigint arg: {}", arg); | ||
arg | ||
} | ||
|
||
// Test out the signage conversion | ||
#[node_bindgen] | ||
fn go_negative(arg: BigInt) -> BigInt { | ||
println!("bigint arg: {}", arg); | ||
-1*arg | ||
} | ||
|
||
// Test out that we can return a u64 which is automatically converted to a bigint. | ||
#[node_bindgen] | ||
fn return_u64(arg: u32) -> u64 { | ||
println!("bigint arg: {}", arg); | ||
arg as u64 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
const assert = require('assert'); | ||
|
||
let addon = require('./dist'); | ||
|
||
const hugeBin = BigInt("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") | ||
|
||
// Test that we can go back and forth through the FFI | ||
assert.equal(addon.doNothing(hugeBin), hugeBin); | ||
|
||
// Test out some signage | ||
assert.equal(addon.goNegative(hugeBin), hugeBin*BigInt(-1)); | ||
|
||
// How about multiplication? | ||
assert.equal(addon.multiplyBigInt(hugeBin), hugeBin*(BigInt(2))); | ||
|
||
// Basic tests from js-env | ||
assert.equal(addon.multiplyBigInt(BigInt(-5)), BigInt(-10)); | ||
assert.equal(addon.multiplyBigInt(BigInt(5)), BigInt(10)); | ||
assert.equal(addon.multiplyBigInt(BigInt(5)), BigInt(10)); | ||
|
||
// Rust's u64s turn into JS BigInts. | ||
assert.equal(addon.returnU64(5), BigInt(5)); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
use std::ptr; | ||
use log::trace; | ||
|
||
use crate::TryIntoJs; | ||
use crate::JSValue; | ||
use crate::sys::napi_value; | ||
use crate::val::JsEnv; | ||
use crate::NjError; | ||
|
||
pub use num_bigint::*; | ||
|
||
impl<'a> JSValue<'a> for BigInt { | ||
fn convert_to_rust(env: &'a JsEnv, js_value: napi_value) -> Result<Self, NjError> { | ||
trace!("Converting JS BigInt to Rust!"); | ||
|
||
env.assert_type(js_value, crate::sys::napi_valuetype_napi_bigint)?; | ||
let mut word_count: usize = 0; | ||
|
||
// https://nodejs.org/api/n-api.html#n_api_napi_get_value_bigint_words | ||
// Frist call is to figure out how long of a vec to make. | ||
crate::napi_call_result!(crate::sys::napi_get_value_bigint_words( | ||
env.inner(), | ||
js_value, | ||
ptr::null_mut(), | ||
&mut word_count, | ||
ptr::null_mut(), | ||
))?; | ||
|
||
// Now we actually get the sign and the vector. | ||
let mut napi_buffer: Vec<u64> = vec![0; word_count]; | ||
let mut sign = 0; | ||
|
||
crate::napi_call_result!(crate::sys::napi_get_value_bigint_words( | ||
env.inner(), | ||
js_value, | ||
&mut sign, | ||
&mut word_count, | ||
napi_buffer.as_mut_ptr(), | ||
))?; | ||
|
||
// BigInt is initialized via a little endian &[u8] so we need to build the u8s from the | ||
// u64s | ||
let mut bytes: Vec<u8> = Vec::new(); | ||
for i in &napi_buffer { | ||
bytes.extend_from_slice(&i.to_le_bytes()); | ||
} | ||
|
||
// The N-API documentation on the signs is lacking. | ||
let sign = match sign { | ||
0 => Sign::Plus, | ||
1 => Sign::Minus, | ||
_ => unreachable!(), | ||
}; | ||
let res = BigInt::from_bytes_le(sign, &bytes); | ||
trace!( | ||
"Converted JS BigInt to Rust! words: {:#X?}, bytes: {:#?}, len: {:?}, bigint: {:#?}", | ||
napi_buffer, | ||
bytes, | ||
bytes.len(), | ||
res | ||
); | ||
Ok(res) | ||
} | ||
} | ||
|
||
impl TryIntoJs for BigInt { | ||
fn try_to_js(self, env: &JsEnv) -> Result<napi_value, NjError> { | ||
let (sign, bytes) = self.to_bytes_le(); | ||
let mut words: Vec<u64> = Vec::new(); | ||
use std::cmp::min; | ||
|
||
// bytes can be non-multiples of 8. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was tempted to use something like byteorder for this because byte mangling like this can be error prone. Also, writing this took way longer that I wanted (and will for the next person). I'm open adding it but as we've seen with rust-bindgen, it'll add more compile time to whatever depends on this project. |
||
for i in 0..(bytes.len()/8 + 1) { | ||
let mut slice : [u8; 8] = [0;8]; | ||
|
||
// https://stackoverflow.com/a/29784723 seems to be the least bad way to convert a Vec | ||
// slice into an array :/ | ||
for (place, element) in slice.iter_mut().zip(bytes[i*8..min((i+1)*8, bytes.len())].iter()) { | ||
*place = *element; | ||
} | ||
words.push(u64::from_le_bytes(slice)); | ||
} | ||
let sign = match sign { | ||
Sign::Minus => 1, | ||
Sign::Plus | Sign::NoSign => 0, | ||
}; | ||
let word_count = words.len(); | ||
|
||
trace!( | ||
"Converted Rust BigInt to JS Bigint: {:#?}!, bytes: {:#?}, len: {:?}, words: {:#?}, word_count {:#?}, sign: {:#?}", | ||
self, | ||
bytes, | ||
bytes.len(), | ||
words, | ||
word_count, | ||
sign, | ||
); | ||
|
||
let mut napi_buffer = ptr::null_mut(); | ||
|
||
// https://nodejs.org/api/n-api.html#n_api_napi_create_bigint_words | ||
crate::napi_call_result!(crate::sys::napi_create_bigint_words( | ||
env.inner(), | ||
sign, | ||
word_count, | ||
words.as_ptr(), | ||
&mut napi_buffer | ||
))?; | ||
Ok(napi_buffer) | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.