forked from denoland/deno
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: return serde_v8 to main repo (denoland#12500)
Reduces fragmentation, avoids version drift and facilitates coordinating serde_v8 and op-layer changes
- Loading branch information
Showing
25 changed files
with
3,273 additions
and
1 deletion.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ members = [ | |
"cli", | ||
"core", | ||
"runtime", | ||
"serde_v8", | ||
"test_ffi", | ||
"test_util", | ||
"ext/broadcast_channel", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. | ||
[package] | ||
name = "serde_v8" | ||
version = "0.15.0" | ||
authors = ["the Deno authors"] | ||
edition = "2018" | ||
license = "MIT" | ||
readme = "README.md" | ||
repository = "https://github.com/denoland/deno" | ||
description = "Rust to V8 serialization and deserialization" | ||
|
||
[dependencies] | ||
rusty_v8 = "0.32.0" | ||
serde = { version = "1.0.130", features = ["derive"] } | ||
|
||
[dev-dependencies] | ||
bencher = "0.1" | ||
serde_json = "1.0.64" | ||
|
||
[[example]] | ||
name = "basic" | ||
|
||
[[bench]] | ||
name = "de" | ||
harness = false | ||
|
||
[[bench]] | ||
name = "ser" | ||
harness = false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# serde_v8 | ||
|
||
Author: Aaron O'Mullan <[email protected]> | ||
|
||
Serde support for encoding/decoding (rusty_)v8 values. | ||
|
||
Broadly `serde_v8` aims to provide an expressive but ~maximally efficient | ||
encoding layer to biject rust & v8/js values. It's a core component of deno's | ||
op-layer and is used to encode/decode all non-buffer values. | ||
|
||
**Original issue:** | ||
[denoland/deno#9540](https://github.com/denoland/deno/issues/9540) | ||
|
||
## Quickstart | ||
|
||
`serde_v8` fits naturally into the serde ecosystem, so if you've already used | ||
`serde` or `serde_json`, `serde_v8`'s API should be very familiar. | ||
|
||
`serde_v8` exposes two key-functions: | ||
|
||
- `to_v8`: maps `rust->v8`, similar to `serde_json::to_string`, ... | ||
- `from_v8`: maps `v8->rust`, similar to `serde_json::from_str`, ... | ||
|
||
## Best practices | ||
|
||
Whilst `serde_v8` is compatible with `serde_json::Value` it's important to keep | ||
in mind that `serde_json::Value` is essentially a loosely-typed value (think | ||
nested HashMaps), so when writing ops we recommend directly using rust | ||
structs/tuples or primitives, since mapping to `serde_json::Value` will add | ||
extra overhead and result in slower ops. | ||
|
||
I also recommend avoiding unecessary "wrappers", if your op takes a single-keyed | ||
struct, consider unwrapping that as a plain value unless you plan to add fields | ||
in the near-future. | ||
|
||
Instead of returning "nothing" via `Ok(json!({}))`, change your return type to | ||
rust's unit type `()` and returning `Ok(())`, `serde_v8` will efficiently encode | ||
that as a JS `null`. | ||
|
||
## Advanced features | ||
|
||
If you need to mix rust & v8 values in structs/tuples, you can use the special | ||
`serde_v8::Value` type, which will passthrough the original v8 value untouched | ||
when encoding/decoding. | ||
|
||
## TODO | ||
|
||
- [ ] Experiment with KeyCache to optimize struct keys | ||
- [ ] Experiment with external v8 strings | ||
- [ ] Explore using | ||
[json-stringifier.cc](https://chromium.googlesource.com/v8/v8/+/refs/heads/master/src/json/json-stringifier.cc)'s | ||
fast-paths for arrays | ||
- [ ] Improve tests to test parity with `serde_json` (should be mostly | ||
interchangeable) | ||
- [ ] Consider a `Payload` type that's deserializable by itself (holds scope & | ||
value) | ||
- [ ] Ensure we return errors instead of panicking on `.unwrap()`s |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. | ||
use bencher::{benchmark_group, benchmark_main, Bencher}; | ||
|
||
use rusty_v8 as v8; | ||
use std::convert::TryFrom; | ||
|
||
use serde::Deserialize; | ||
|
||
use serde_v8::utils::{js_exec, v8_do}; | ||
|
||
#[derive(Debug, Deserialize, PartialEq)] | ||
struct MathOp { | ||
arg1: u64, | ||
arg2: u64, | ||
operator: Option<String>, | ||
} | ||
|
||
fn dedo( | ||
code: &str, | ||
f: impl FnOnce(&mut v8::HandleScope, v8::Local<v8::Value>), | ||
) { | ||
v8_do(|| { | ||
let isolate = &mut v8::Isolate::new(v8::CreateParams::default()); | ||
let handle_scope = &mut v8::HandleScope::new(isolate); | ||
let context = v8::Context::new(handle_scope); | ||
let scope = &mut v8::ContextScope::new(handle_scope, context); | ||
let v = js_exec(scope, code); | ||
|
||
f(scope, v); | ||
}) | ||
} | ||
|
||
fn dedo_json(code: &str, f: impl FnOnce(String)) { | ||
let code = format!("JSON.stringify({})", code); | ||
dedo(&code[..], |scope, v| { | ||
let s: String = serde_v8::from_v8(scope, v).unwrap(); | ||
f(s); | ||
}) | ||
} | ||
|
||
fn de_struct_v8(b: &mut Bencher) { | ||
dedo("({arg1: 10, arg2: 123 })", |scope, obj| { | ||
let mut total = 0; | ||
b.iter(move || { | ||
let op: MathOp = serde_v8::from_v8(scope, obj).unwrap(); | ||
total = total + op.arg1 + op.arg2; | ||
}); | ||
}); | ||
} | ||
|
||
fn de_struct_v8_opt(b: &mut Bencher) { | ||
dedo("({arg1: 10, arg2: 123 })", |scope, v| { | ||
let k_arg1 = v8::String::new(scope, "arg1").unwrap().into(); | ||
let k_arg2 = v8::String::new(scope, "arg2").unwrap().into(); | ||
let obj = v8::Local::<v8::Object>::try_from(v).unwrap(); | ||
let mut total = 0; | ||
b.iter(move || { | ||
let v_arg1 = obj.get(scope, k_arg1).unwrap(); | ||
let v_arg2 = obj.get(scope, k_arg2).unwrap(); | ||
let op = MathOp { | ||
arg1: serde_v8::from_v8(scope, v_arg1).unwrap(), | ||
arg2: serde_v8::from_v8(scope, v_arg2).unwrap(), | ||
operator: None, | ||
}; | ||
total = total + op.arg1 + op.arg2; | ||
}); | ||
}); | ||
} | ||
|
||
fn de_struct_json(b: &mut Bencher) { | ||
dedo_json("({arg1: 10, arg2: 123 })", |s| { | ||
let mut total = 0; | ||
b.iter(move || { | ||
let op: MathOp = serde_json::from_str(&s).unwrap(); | ||
total = total + op.arg1 + op.arg2; | ||
}); | ||
}); | ||
} | ||
|
||
fn de_struct_json_deopt(b: &mut Bencher) { | ||
// JSON.stringify() in loop (semi-simulating ABI loop) | ||
dedo("({arg1: 10, arg2: 123 })", |scope, obj| { | ||
let mut total = 0; | ||
b.iter(move || { | ||
let mut scope = v8::HandleScope::new(scope); | ||
let s = v8::json::stringify(&mut scope, obj).unwrap(); | ||
let rs = s.to_rust_string_lossy(&mut scope); | ||
let op: MathOp = serde_json::from_str(&rs).unwrap(); | ||
total = total + op.arg1 + op.arg2; | ||
}); | ||
}); | ||
} | ||
|
||
macro_rules! dualbench { | ||
($v8_fn:ident, $json_fn:ident, $src:expr, $t:ty) => { | ||
fn $v8_fn(b: &mut Bencher) { | ||
dedo($src, |scope, v| { | ||
b.iter(move || { | ||
let _: $t = serde_v8::from_v8(scope, v).unwrap(); | ||
}); | ||
}); | ||
} | ||
|
||
fn $json_fn(b: &mut Bencher) { | ||
dedo_json($src, |s| { | ||
b.iter(move || { | ||
let _: $t = serde_json::from_str(&s).unwrap(); | ||
}); | ||
}); | ||
} | ||
}; | ||
} | ||
|
||
dualbench!(de_bool_v8, de_bool_json, "true", bool); | ||
dualbench!(de_int_v8, de_int_json, "12345", u32); | ||
dualbench!( | ||
de_array_v8, | ||
de_array_json, | ||
"[1,2,3,4,5,6,7,8,9,10]", | ||
Vec<u32> | ||
); | ||
dualbench!(de_str_v8, de_str_json, "'hello world'", String); | ||
dualbench!(de_tuple_v8, de_tuple_json, "[1,false]", (u8, bool)); | ||
|
||
fn de_tuple_v8_opt(b: &mut Bencher) { | ||
dedo("[1,false]", |scope, obj| { | ||
let arr = v8::Local::<v8::Array>::try_from(obj).unwrap(); | ||
let obj = v8::Local::<v8::Object>::from(arr); | ||
|
||
b.iter(move || { | ||
let v1 = obj.get_index(scope, 0).unwrap(); | ||
let v2 = obj.get_index(scope, 1).unwrap(); | ||
let _: (u8, bool) = ( | ||
serde_v8::from_v8(scope, v1).unwrap(), | ||
serde_v8::from_v8(scope, v2).unwrap(), | ||
); | ||
}); | ||
}); | ||
} | ||
|
||
benchmark_group!( | ||
benches, | ||
de_struct_v8, | ||
de_struct_v8_opt, | ||
de_struct_json, | ||
de_struct_json_deopt, | ||
de_bool_v8, | ||
de_bool_json, | ||
de_int_v8, | ||
de_int_json, | ||
de_array_v8, | ||
de_array_json, | ||
de_str_v8, | ||
de_str_json, | ||
de_tuple_v8, | ||
de_tuple_json, | ||
de_tuple_v8_opt, | ||
); | ||
|
||
benchmark_main!(benches); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. | ||
use bencher::{benchmark_group, benchmark_main, Bencher}; | ||
|
||
use rusty_v8 as v8; | ||
|
||
use serde::Serialize; | ||
|
||
use serde_v8::utils::v8_do; | ||
|
||
#[derive(Serialize)] | ||
struct MathOp { | ||
arg1: u64, | ||
arg2: u64, | ||
operator: Option<String>, | ||
} | ||
|
||
fn serdo(f: impl FnOnce(&mut v8::HandleScope)) { | ||
v8_do(|| { | ||
let isolate = &mut v8::Isolate::new(v8::CreateParams::default()); | ||
let handle_scope = &mut v8::HandleScope::new(isolate); | ||
let context = v8::Context::new(handle_scope); | ||
let scope = &mut v8::ContextScope::new(handle_scope, context); | ||
|
||
f(scope); | ||
}) | ||
} | ||
|
||
macro_rules! dualbench { | ||
($v8_fn:ident, $json_fn:ident, $src:expr) => { | ||
fn $v8_fn(b: &mut Bencher) { | ||
serdo(|scope| { | ||
let v = $src; | ||
b.iter(move || { | ||
let _ = serde_v8::to_v8(scope, &v).unwrap(); | ||
}); | ||
}); | ||
} | ||
|
||
fn $json_fn(b: &mut Bencher) { | ||
let v = $src; | ||
b.iter(move || { | ||
let _ = serde_json::to_string(&v).unwrap(); | ||
}); | ||
} | ||
}; | ||
} | ||
|
||
dualbench!( | ||
ser_struct_v8, | ||
ser_struct_json, | ||
MathOp { | ||
arg1: 10, | ||
arg2: 123, | ||
operator: None | ||
} | ||
); | ||
dualbench!(ser_bool_v8, ser_bool_json, true); | ||
dualbench!(ser_int_v8, ser_int_json, 12345); | ||
dualbench!( | ||
ser_array_v8, | ||
ser_array_json, | ||
vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10] | ||
); | ||
dualbench!(ser_str_v8, ser_str_json, "hello world"); | ||
dualbench!(ser_tuple_v8, ser_tuple_json, (1, false)); | ||
|
||
fn ser_struct_v8_manual(b: &mut Bencher) { | ||
serdo(|scope| { | ||
let v = MathOp { | ||
arg1: 10, | ||
arg2: 123, | ||
operator: None, | ||
}; | ||
b.iter(|| { | ||
let obj = v8::Object::new(scope); | ||
let k1 = v8::String::new(scope, "arg1").unwrap(); | ||
let k2 = v8::String::new(scope, "arg2").unwrap(); | ||
let k3 = v8::String::new(scope, "operator").unwrap(); | ||
// let k1 = v8::String::new_from_utf8(scope, "arg1".as_ref(), v8::NewStringType::Internalized).unwrap(); | ||
// let k2 = v8::String::new_from_utf8(scope, "arg2".as_ref(), v8::NewStringType::Internalized).unwrap(); | ||
// let k3 = v8::String::new_from_utf8(scope, "operator".as_ref(), v8::NewStringType::Internalized).unwrap(); | ||
let v1 = v8::Number::new(scope, v.arg1 as f64); | ||
let v2 = v8::Number::new(scope, v.arg2 as f64); | ||
let v3 = v8::null(scope); | ||
obj.set(scope, k1.into(), v1.into()).unwrap(); | ||
obj.set(scope, k2.into(), v2.into()).unwrap(); | ||
obj.set(scope, k3.into(), v3.into()).unwrap(); | ||
}); | ||
}); | ||
} | ||
|
||
benchmark_group!( | ||
benches, | ||
ser_struct_v8, | ||
ser_struct_json, | ||
ser_bool_v8, | ||
ser_bool_json, | ||
ser_int_v8, | ||
ser_int_json, | ||
ser_array_v8, | ||
ser_array_json, | ||
ser_str_v8, | ||
ser_str_json, | ||
ser_tuple_v8, | ||
ser_tuple_json, | ||
ser_struct_v8_manual, | ||
); | ||
benchmark_main!(benches); |
Oops, something went wrong.