mirror of
https://github.com/denoland/deno.git
synced 2024-12-24 08:09:08 -05:00
feat(ext/ffi): Implement FFI fast-call trampoline with Dynasmrt (#15305)
This commit is contained in:
parent
7b072a2b7d
commit
dd428d1dc8
15 changed files with 2282 additions and 601 deletions
4
.gitmodules
vendored
4
.gitmodules
vendored
|
@ -9,6 +9,4 @@
|
||||||
[submodule "test_util/wpt"]
|
[submodule "test_util/wpt"]
|
||||||
path = test_util/wpt
|
path = test_util/wpt
|
||||||
url = https://github.com/web-platform-tests/wpt.git
|
url = https://github.com/web-platform-tests/wpt.git
|
||||||
[submodule "ext/ffi/tinycc"]
|
|
||||||
path = ext/ffi/tinycc
|
|
||||||
url = https://github.com/TinyCC/tinycc
|
|
||||||
|
|
37
Cargo.lock
generated
37
Cargo.lock
generated
|
@ -1035,6 +1035,7 @@ version = "0.54.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deno_core",
|
"deno_core",
|
||||||
"dlopen",
|
"dlopen",
|
||||||
|
"dynasmrt",
|
||||||
"libffi",
|
"libffi",
|
||||||
"serde",
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -1467,6 +1468,32 @@ version = "1.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28"
|
checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dynasm"
|
||||||
|
version = "1.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "add9a102807b524ec050363f09e06f1504214b0e1c7797f64261c891022dce8b"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"byteorder",
|
||||||
|
"lazy_static",
|
||||||
|
"proc-macro-error",
|
||||||
|
"proc-macro2 1.0.39",
|
||||||
|
"quote 1.0.18",
|
||||||
|
"syn 1.0.96",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dynasmrt"
|
||||||
|
version = "1.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "64fba5a42bd76a17cad4bfa00de168ee1cbfa06a5e8ce992ae880218c05641a9"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"dynasm",
|
||||||
|
"memmap2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ecdsa"
|
name = "ecdsa"
|
||||||
version = "0.14.1"
|
version = "0.14.1"
|
||||||
|
@ -2657,6 +2684,15 @@ version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memmap2"
|
||||||
|
version = "0.5.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3a79b39c93a7a5a27eeaf9a23b5ff43f1b9e0ad6b1cdd441140ae53c35613fc7"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memoffset"
|
name = "memoffset"
|
||||||
version = "0.6.5"
|
version = "0.6.5"
|
||||||
|
@ -4590,6 +4626,7 @@ dependencies = [
|
||||||
name = "test_ffi"
|
name = "test_ffi"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"pretty_assertions",
|
||||||
"test_util",
|
"test_util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ path = "lib.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
deno_core = { version = "0.149.0", path = "../../core" }
|
deno_core = { version = "0.149.0", path = "../../core" }
|
||||||
dlopen = "0.1.8"
|
dlopen = "0.1.8"
|
||||||
|
dynasmrt = "1.2.3"
|
||||||
libffi = "3.0.0"
|
libffi = "3.0.0"
|
||||||
serde = { version = "1.0.129", features = ["derive"] }
|
serde = { version = "1.0.129", features = ["derive"] }
|
||||||
tokio = { version = "1.17", features = ["full"] }
|
tokio = { version = "1.17", features = ["full"] }
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
fn build_tcc() {
|
|
||||||
use std::env;
|
|
||||||
|
|
||||||
{
|
|
||||||
// TODO(@littledivy): Windows support for fast call.
|
|
||||||
// let tcc_path = root
|
|
||||||
// .parent()
|
|
||||||
// .unwrap()
|
|
||||||
// .to_path_buf()
|
|
||||||
// .parent()
|
|
||||||
// .unwrap()
|
|
||||||
// .to_path_buf()
|
|
||||||
// .join("third_party")
|
|
||||||
// .join("prebuilt")
|
|
||||||
// .join("win");
|
|
||||||
// println!("cargo:rustc-link-search=native={}", tcc_path.display());
|
|
||||||
}
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
{
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::process::exit;
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
let root = PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR")));
|
|
||||||
let tcc_src = root.join("tinycc");
|
|
||||||
dbg!(&tcc_src);
|
|
||||||
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
|
|
||||||
let mut configure = Command::new(tcc_src.join("configure"));
|
|
||||||
configure.current_dir(&out_dir);
|
|
||||||
configure.args(&["--enable-static", "--extra-cflags=-fPIC -O3 -g -static"]);
|
|
||||||
let status = configure.status().unwrap();
|
|
||||||
if !status.success() {
|
|
||||||
eprintln!("Fail to configure: {:?}", status);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut make = Command::new("make");
|
|
||||||
make.current_dir(&out_dir).arg(format!(
|
|
||||||
"-j{}",
|
|
||||||
env::var("NUM_JOBS").unwrap_or_else(|_| String::from("1"))
|
|
||||||
));
|
|
||||||
make.args(&["libtcc.a"]);
|
|
||||||
let status = make.status().unwrap();
|
|
||||||
|
|
||||||
if !status.success() {
|
|
||||||
eprintln!("Fail to make: {:?}", status);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
println!("cargo:rustc-link-search=native={}", out_dir.display());
|
|
||||||
println!("cargo:rerun-if-changed={}", tcc_src.display());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
fn main() {}
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
fn main() {
|
|
||||||
use std::env;
|
|
||||||
|
|
||||||
if let Ok(tcc_path) = env::var("TCC_PATH") {
|
|
||||||
println!("cargo:rustc-link-search=native={}", tcc_path);
|
|
||||||
} else {
|
|
||||||
build_tcc();
|
|
||||||
}
|
|
||||||
println!("cargo:rustc-link-lib=static=tcc");
|
|
||||||
}
|
|
2065
ext/ffi/fast_call.rs
Normal file
2065
ext/ffi/fast_call.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,263 +0,0 @@
|
||||||
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
|
||||||
|
|
||||||
use crate::NativeType;
|
|
||||||
use crate::{tcc::Compiler, Symbol};
|
|
||||||
use std::ffi::c_void;
|
|
||||||
use std::ffi::CString;
|
|
||||||
use std::fmt::Write as _;
|
|
||||||
use std::mem::size_of;
|
|
||||||
|
|
||||||
const _: () = assert!(size_of::<fn()>() == size_of::<usize>());
|
|
||||||
|
|
||||||
pub(crate) struct Allocation {
|
|
||||||
pub addr: *mut c_void,
|
|
||||||
_ctx: Compiler,
|
|
||||||
_sym: Box<Symbol>,
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! cstr {
|
|
||||||
($st:expr) => {
|
|
||||||
&CString::new($st).unwrap()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn native_arg_to_c(ty: &NativeType) -> &'static str {
|
|
||||||
match ty {
|
|
||||||
NativeType::Bool => "bool",
|
|
||||||
NativeType::U8 | NativeType::U16 | NativeType::U32 => "uint32_t",
|
|
||||||
NativeType::I8 | NativeType::I16 | NativeType::I32 => "int32_t",
|
|
||||||
NativeType::Void => "void",
|
|
||||||
NativeType::F32 => "float",
|
|
||||||
NativeType::F64 => "double",
|
|
||||||
NativeType::U64 => "uint64_t",
|
|
||||||
NativeType::I64 => "int64_t",
|
|
||||||
NativeType::ISize => "intptr_t",
|
|
||||||
NativeType::USize => "uintptr_t",
|
|
||||||
NativeType::Buffer => "struct FastApiTypedArray*",
|
|
||||||
NativeType::Function | NativeType::Pointer => "void*",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn native_to_c(ty: &NativeType) -> &'static str {
|
|
||||||
match ty {
|
|
||||||
NativeType::Bool => "bool",
|
|
||||||
NativeType::U8 => "uint8_t",
|
|
||||||
NativeType::U16 => "uint16_t",
|
|
||||||
NativeType::U32 => "uint32_t",
|
|
||||||
NativeType::I8 => "int8_t",
|
|
||||||
NativeType::I16 => "uint16_t",
|
|
||||||
NativeType::I32 => "int32_t",
|
|
||||||
NativeType::Void => "void",
|
|
||||||
NativeType::F32 => "float",
|
|
||||||
NativeType::F64 => "double",
|
|
||||||
NativeType::U64 => "uint64_t",
|
|
||||||
NativeType::I64 => "int64_t",
|
|
||||||
NativeType::ISize => "intptr_t",
|
|
||||||
NativeType::USize => "uintptr_t",
|
|
||||||
NativeType::Pointer | NativeType::Buffer | NativeType::Function => "void*",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn codegen(sym: &crate::Symbol) -> String {
|
|
||||||
let mut c = String::from(include_str!("prelude.h"));
|
|
||||||
let needs_unwrap = crate::needs_unwrap(sym.result_type);
|
|
||||||
|
|
||||||
// Return type of the FFI call.
|
|
||||||
let ffi_ret = native_to_c(&sym.result_type);
|
|
||||||
// Return type of the trampoline.
|
|
||||||
let ret = if needs_unwrap { "void" } else { ffi_ret };
|
|
||||||
|
|
||||||
// extern <return_type> func(
|
|
||||||
let _ = write!(c, "\nextern {ffi_ret} func(");
|
|
||||||
// <param_type> p0, <param_type> p1, ...);
|
|
||||||
for (i, ty) in sym.parameter_types.iter().enumerate() {
|
|
||||||
if i > 0 {
|
|
||||||
c += ", ";
|
|
||||||
}
|
|
||||||
c += native_to_c(ty);
|
|
||||||
let _ = write!(c, " p{i}");
|
|
||||||
}
|
|
||||||
c += ");\n\n";
|
|
||||||
|
|
||||||
// void* recv, <param_type> p0, <param_type> p1, ...);
|
|
||||||
c += ret;
|
|
||||||
c += " func_trampoline(";
|
|
||||||
c += "void* recv";
|
|
||||||
for (i, ty) in sym.parameter_types.iter().enumerate() {
|
|
||||||
c += ", ";
|
|
||||||
c += native_arg_to_c(ty);
|
|
||||||
let _ = write!(c, " p{i}");
|
|
||||||
}
|
|
||||||
if needs_unwrap {
|
|
||||||
let _ = write!(c, ", struct FastApiTypedArray* const p_ret");
|
|
||||||
}
|
|
||||||
c += ") {\n";
|
|
||||||
// func(p0, p1, ...);
|
|
||||||
let mut call_s = String::from("func(");
|
|
||||||
{
|
|
||||||
for (i, ty) in sym.parameter_types.iter().enumerate() {
|
|
||||||
if i > 0 {
|
|
||||||
call_s += ", ";
|
|
||||||
}
|
|
||||||
if matches!(ty, NativeType::Buffer) {
|
|
||||||
let _ = write!(call_s, "p{i}->data");
|
|
||||||
} else {
|
|
||||||
let _ = write!(call_s, "p{i}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
call_s += ");\n";
|
|
||||||
}
|
|
||||||
if needs_unwrap {
|
|
||||||
// <return_type> r = func(p0, p1, ...);
|
|
||||||
// ((<return_type>*)p_ret->data)[0] = r;
|
|
||||||
let _ = write!(c, " {ffi_ret} r = {call_s}");
|
|
||||||
let _ = writeln!(c, " (({ffi_ret}*)p_ret->data)[0] = r;");
|
|
||||||
} else {
|
|
||||||
// return func(p0, p1, ...);
|
|
||||||
let _ = write!(c, " return {call_s}");
|
|
||||||
}
|
|
||||||
c += "}\n\n";
|
|
||||||
c
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn gen_trampoline(
|
|
||||||
sym: Box<crate::Symbol>,
|
|
||||||
) -> Result<Box<Allocation>, ()> {
|
|
||||||
let mut ctx = Compiler::new()?;
|
|
||||||
ctx.set_options(cstr!("-nostdlib"));
|
|
||||||
// SAFETY: symbol satisfies ABI requirement.
|
|
||||||
unsafe { ctx.add_symbol(cstr!("func"), sym.ptr.0 as *const c_void) };
|
|
||||||
let c = codegen(&sym);
|
|
||||||
ctx.compile_string(cstr!(c))?;
|
|
||||||
let alloc = Allocation {
|
|
||||||
addr: ctx.relocate_and_get_symbol(cstr!("func_trampoline"))?,
|
|
||||||
_ctx: ctx,
|
|
||||||
_sym: sym,
|
|
||||||
};
|
|
||||||
Ok(Box::new(alloc))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use libffi::middle::Type;
|
|
||||||
use std::ptr::null_mut;
|
|
||||||
|
|
||||||
fn codegen(parameters: Vec<NativeType>, ret: NativeType) -> String {
|
|
||||||
let sym = Box::new(crate::Symbol {
|
|
||||||
cif: libffi::middle::Cif::new(vec![], Type::void()),
|
|
||||||
ptr: libffi::middle::CodePtr(null_mut()),
|
|
||||||
parameter_types: parameters,
|
|
||||||
result_type: ret,
|
|
||||||
can_callback: false,
|
|
||||||
});
|
|
||||||
super::codegen(&sym)
|
|
||||||
}
|
|
||||||
|
|
||||||
const PRELUDE: &str = include_str!("prelude.h");
|
|
||||||
fn assert_codegen(expected: String, actual: &str) {
|
|
||||||
assert_eq!(expected, format!("{PRELUDE}\n{}", actual))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_gen_trampoline() {
|
|
||||||
assert_codegen(
|
|
||||||
codegen(vec![], NativeType::Void),
|
|
||||||
"extern void func();\n\n\
|
|
||||||
void func_trampoline(void* recv) {\
|
|
||||||
\n return func();\n\
|
|
||||||
}\n\n",
|
|
||||||
);
|
|
||||||
assert_codegen(
|
|
||||||
codegen(vec![NativeType::U32, NativeType::U32], NativeType::U32),
|
|
||||||
"extern uint32_t func(uint32_t p0, uint32_t p1);\n\n\
|
|
||||||
uint32_t func_trampoline(void* recv, uint32_t p0, uint32_t p1) {\
|
|
||||||
\n return func(p0, p1);\n\
|
|
||||||
}\n\n",
|
|
||||||
);
|
|
||||||
assert_codegen(
|
|
||||||
codegen(vec![NativeType::I32, NativeType::I32], NativeType::I32),
|
|
||||||
"extern int32_t func(int32_t p0, int32_t p1);\n\n\
|
|
||||||
int32_t func_trampoline(void* recv, int32_t p0, int32_t p1) {\
|
|
||||||
\n return func(p0, p1);\n\
|
|
||||||
}\n\n",
|
|
||||||
);
|
|
||||||
assert_codegen(
|
|
||||||
codegen(vec![NativeType::F32, NativeType::F32], NativeType::F32),
|
|
||||||
"extern float func(float p0, float p1);\n\n\
|
|
||||||
float func_trampoline(void* recv, float p0, float p1) {\
|
|
||||||
\n return func(p0, p1);\n\
|
|
||||||
}\n\n",
|
|
||||||
);
|
|
||||||
assert_codegen(
|
|
||||||
codegen(vec![NativeType::F64, NativeType::F64], NativeType::F64),
|
|
||||||
"extern double func(double p0, double p1);\n\n\
|
|
||||||
double func_trampoline(void* recv, double p0, double p1) {\
|
|
||||||
\n return func(p0, p1);\n\
|
|
||||||
}\n\n",
|
|
||||||
);
|
|
||||||
assert_codegen(
|
|
||||||
codegen(vec![NativeType::Buffer, NativeType::U32], NativeType::U32),
|
|
||||||
"extern uint32_t func(void* p0, uint32_t p1);\n\n\
|
|
||||||
uint32_t func_trampoline(void* recv, struct FastApiTypedArray* p0, uint32_t p1) {\
|
|
||||||
\n return func(p0->data, p1);\n\
|
|
||||||
}\n\n",
|
|
||||||
);
|
|
||||||
assert_codegen(
|
|
||||||
codegen(vec![NativeType::Buffer, NativeType::Buffer], NativeType::U32),
|
|
||||||
"extern uint32_t func(void* p0, void* p1);\n\n\
|
|
||||||
uint32_t func_trampoline(void* recv, struct FastApiTypedArray* p0, struct FastApiTypedArray* p1) {\
|
|
||||||
\n return func(p0->data, p1->data);\n\
|
|
||||||
}\n\n",
|
|
||||||
);
|
|
||||||
assert_codegen(
|
|
||||||
codegen(vec![], NativeType::U64),
|
|
||||||
"extern uint64_t func();\n\n\
|
|
||||||
void func_trampoline(void* recv, struct FastApiTypedArray* const p_ret) {\
|
|
||||||
\n uint64_t r = func();\
|
|
||||||
\n ((uint64_t*)p_ret->data)[0] = r;\n\
|
|
||||||
}\n\n",
|
|
||||||
);
|
|
||||||
assert_codegen(
|
|
||||||
codegen(vec![NativeType::Buffer, NativeType::Buffer], NativeType::U64),
|
|
||||||
"extern uint64_t func(void* p0, void* p1);\n\n\
|
|
||||||
void func_trampoline(void* recv, struct FastApiTypedArray* p0, struct FastApiTypedArray* p1, struct FastApiTypedArray* const p_ret) {\
|
|
||||||
\n uint64_t r = func(p0->data, p1->data);\
|
|
||||||
\n ((uint64_t*)p_ret->data)[0] = r;\n\
|
|
||||||
}\n\n",
|
|
||||||
);
|
|
||||||
assert_codegen(
|
|
||||||
codegen(vec![NativeType::Pointer, NativeType::Pointer], NativeType::U64),
|
|
||||||
"extern uint64_t func(void* p0, void* p1);\n\n\
|
|
||||||
void func_trampoline(void* recv, void* p0, void* p1, struct FastApiTypedArray* const p_ret) {\
|
|
||||||
\n uint64_t r = func(p0, p1);\
|
|
||||||
\n ((uint64_t*)p_ret->data)[0] = r;\n\
|
|
||||||
}\n\n",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_gen_trampoline_implicit_cast() {
|
|
||||||
assert_codegen(
|
|
||||||
codegen(vec![NativeType::I8, NativeType::U8], NativeType::I8),
|
|
||||||
"extern int8_t func(int8_t p0, uint8_t p1);\n\n\
|
|
||||||
int8_t func_trampoline(void* recv, int32_t p0, uint32_t p1) {\
|
|
||||||
\n return func(p0, p1);\n\
|
|
||||||
}\n\n",
|
|
||||||
);
|
|
||||||
assert_codegen(
|
|
||||||
codegen(vec![NativeType::ISize, NativeType::U64], NativeType::Void),
|
|
||||||
"extern void func(intptr_t p0, uint64_t p1);\n\n\
|
|
||||||
void func_trampoline(void* recv, intptr_t p0, uint64_t p1) {\
|
|
||||||
\n return func(p0, p1);\n\
|
|
||||||
}\n\n",
|
|
||||||
);
|
|
||||||
assert_codegen(
|
|
||||||
codegen(vec![NativeType::USize, NativeType::USize], NativeType::U32),
|
|
||||||
"extern uint32_t func(uintptr_t p0, uintptr_t p1);\n\n\
|
|
||||||
uint32_t func_trampoline(void* recv, uintptr_t p0, uintptr_t p1) {\
|
|
||||||
\n return func(p0, p1);\n\
|
|
||||||
}\n\n",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
114
ext/ffi/lib.rs
114
ext/ffi/lib.rs
|
@ -14,7 +14,6 @@ use deno_core::serde_json::json;
|
||||||
use deno_core::serde_json::Value;
|
use deno_core::serde_json::Value;
|
||||||
use deno_core::serde_v8;
|
use deno_core::serde_v8;
|
||||||
use deno_core::v8;
|
use deno_core::v8;
|
||||||
use deno_core::v8::fast_api;
|
|
||||||
use deno_core::Extension;
|
use deno_core::Extension;
|
||||||
use deno_core::OpState;
|
use deno_core::OpState;
|
||||||
use deno_core::Resource;
|
use deno_core::Resource;
|
||||||
|
@ -39,15 +38,11 @@ use std::ptr;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::mpsc::sync_channel;
|
use std::sync::mpsc::sync_channel;
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
mod fast_call;
|
||||||
mod jit_trampoline;
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
mod tcc;
|
|
||||||
|
|
||||||
#[cfg(not(target_pointer_width = "64"))]
|
#[cfg(not(target_pointer_width = "64"))]
|
||||||
compile_error!("platform not supported");
|
compile_error!("platform not supported");
|
||||||
|
|
||||||
// Assert assumptions made in `prelude.h`
|
|
||||||
const _: () = {
|
const _: () = {
|
||||||
assert!(size_of::<c_char>() == 1);
|
assert!(size_of::<c_char>() == 1);
|
||||||
assert!(size_of::<c_short>() == 2);
|
assert!(size_of::<c_short>() == 2);
|
||||||
|
@ -90,8 +85,6 @@ struct Symbol {
|
||||||
ptr: libffi::middle::CodePtr,
|
ptr: libffi::middle::CodePtr,
|
||||||
parameter_types: Vec<NativeType>,
|
parameter_types: Vec<NativeType>,
|
||||||
result_type: NativeType,
|
result_type: NativeType,
|
||||||
// This is dead code only on Windows
|
|
||||||
#[allow(dead_code)]
|
|
||||||
can_callback: bool,
|
can_callback: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -729,50 +722,6 @@ where
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FfiFastCallTemplate {
|
|
||||||
args: Box<[fast_api::Type]>,
|
|
||||||
ret: fast_api::CType,
|
|
||||||
symbol_ptr: *const c_void,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fast_api::FastFunction for FfiFastCallTemplate {
|
|
||||||
fn function(&self) -> *const c_void {
|
|
||||||
self.symbol_ptr
|
|
||||||
}
|
|
||||||
|
|
||||||
fn args(&self) -> &'static [fast_api::Type] {
|
|
||||||
Box::leak(self.args.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn return_type(&self) -> fast_api::CType {
|
|
||||||
self.ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&NativeType> for fast_api::Type {
|
|
||||||
fn from(native_type: &NativeType) -> Self {
|
|
||||||
match native_type {
|
|
||||||
NativeType::Bool => fast_api::Type::Bool,
|
|
||||||
NativeType::U8 | NativeType::U16 | NativeType::U32 => {
|
|
||||||
fast_api::Type::Uint32
|
|
||||||
}
|
|
||||||
NativeType::I8 | NativeType::I16 | NativeType::I32 => {
|
|
||||||
fast_api::Type::Int32
|
|
||||||
}
|
|
||||||
NativeType::F32 => fast_api::Type::Float32,
|
|
||||||
NativeType::F64 => fast_api::Type::Float64,
|
|
||||||
NativeType::Void => fast_api::Type::Void,
|
|
||||||
NativeType::I64 => fast_api::Type::Int64,
|
|
||||||
NativeType::U64 => fast_api::Type::Uint64,
|
|
||||||
NativeType::ISize => fast_api::Type::Int64,
|
|
||||||
NativeType::USize | NativeType::Pointer | NativeType::Function => {
|
|
||||||
fast_api::Type::Uint64
|
|
||||||
}
|
|
||||||
NativeType::Buffer => fast_api::Type::TypedArray(fast_api::CType::Uint8),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn needs_unwrap(rv: NativeType) -> bool {
|
fn needs_unwrap(rv: NativeType) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
rv,
|
rv,
|
||||||
|
@ -796,42 +745,6 @@ fn make_sync_fn<'s>(
|
||||||
scope: &mut v8::HandleScope<'s>,
|
scope: &mut v8::HandleScope<'s>,
|
||||||
sym: Box<Symbol>,
|
sym: Box<Symbol>,
|
||||||
) -> v8::Local<'s, v8::Function> {
|
) -> v8::Local<'s, v8::Function> {
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
let mut fast_ffi_templ: Option<FfiFastCallTemplate> = None;
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
let fast_ffi_templ: Option<FfiFastCallTemplate> = None;
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
let mut fast_allocations: Option<*mut ()> = None;
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
|
||||||
if !sym.can_callback {
|
|
||||||
let needs_unwrap = needs_unwrap(sym.result_type);
|
|
||||||
let ret = match needs_unwrap {
|
|
||||||
true => fast_api::Type::Void,
|
|
||||||
false => fast_api::Type::from(&sym.result_type),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut args = sym
|
|
||||||
.parameter_types
|
|
||||||
.iter()
|
|
||||||
.map(|t| t.into())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
// recv
|
|
||||||
args.insert(0, fast_api::Type::V8Value);
|
|
||||||
if needs_unwrap {
|
|
||||||
args.push(fast_api::Type::TypedArray(fast_api::CType::Int32));
|
|
||||||
}
|
|
||||||
let symbol_trampoline =
|
|
||||||
jit_trampoline::gen_trampoline(sym.clone()).expect("gen_trampoline");
|
|
||||||
fast_ffi_templ = Some(FfiFastCallTemplate {
|
|
||||||
args: args.into_boxed_slice(),
|
|
||||||
ret: (&ret).into(),
|
|
||||||
symbol_ptr: symbol_trampoline.addr,
|
|
||||||
});
|
|
||||||
fast_allocations = Some(Box::into_raw(symbol_trampoline) as *mut ());
|
|
||||||
}
|
|
||||||
|
|
||||||
let sym = Box::leak(sym);
|
let sym = Box::leak(sym);
|
||||||
let builder = v8::FunctionTemplate::builder(
|
let builder = v8::FunctionTemplate::builder(
|
||||||
|scope: &mut v8::HandleScope,
|
|scope: &mut v8::HandleScope,
|
||||||
|
@ -891,8 +804,17 @@ fn make_sync_fn<'s>(
|
||||||
)
|
)
|
||||||
.data(v8::External::new(scope, sym as *mut Symbol as *mut _).into());
|
.data(v8::External::new(scope, sym as *mut Symbol as *mut _).into());
|
||||||
|
|
||||||
let func = if let Some(fast_ffi_templ) = fast_ffi_templ {
|
let mut fast_call_alloc = None;
|
||||||
builder.build_fast(scope, &fast_ffi_templ, None)
|
|
||||||
|
let func = if fast_call::is_compatible(sym) {
|
||||||
|
let trampoline = fast_call::compile_trampoline(sym);
|
||||||
|
let func = builder.build_fast(
|
||||||
|
scope,
|
||||||
|
&fast_call::make_template(sym, &trampoline),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
fast_call_alloc = Some(Box::into_raw(Box::new(trampoline)));
|
||||||
|
func
|
||||||
} else {
|
} else {
|
||||||
builder.build(scope)
|
builder.build(scope)
|
||||||
};
|
};
|
||||||
|
@ -904,12 +826,12 @@ fn make_sync_fn<'s>(
|
||||||
Box::new(move |_| {
|
Box::new(move |_| {
|
||||||
// SAFETY: This is never called twice. pointer obtained
|
// SAFETY: This is never called twice. pointer obtained
|
||||||
// from Box::into_raw, hence, satisfies memory layout requirements.
|
// from Box::into_raw, hence, satisfies memory layout requirements.
|
||||||
unsafe {
|
let _ = unsafe { Box::from_raw(sym) };
|
||||||
Box::from_raw(sym);
|
if let Some(fast_call_ptr) = fast_call_alloc {
|
||||||
#[cfg(not(target_os = "windows"))]
|
// fast-call compiled trampoline is unmapped when the MMAP handle is dropped
|
||||||
if let Some(fast_allocations) = fast_allocations {
|
// SAFETY: This is never called twice. pointer obtained
|
||||||
Box::from_raw(fast_allocations as *mut jit_trampoline::Allocation);
|
// from Box::into_raw, hence, satisfies memory layout requirements.
|
||||||
}
|
let _ = unsafe { Box::from_raw(fast_call_ptr) };
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
|
||||||
|
|
||||||
/* Boolean type */
|
|
||||||
|
|
||||||
#ifndef _STDBOOL_H
|
|
||||||
#define _STDBOOL_H
|
|
||||||
|
|
||||||
#define bool _Bool
|
|
||||||
#define true 1
|
|
||||||
#define false 0
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Exact integral types. */
|
|
||||||
|
|
||||||
/* Signed. */
|
|
||||||
typedef signed char int8_t;
|
|
||||||
typedef short int int16_t;
|
|
||||||
typedef int int32_t;
|
|
||||||
typedef long int int64_t;
|
|
||||||
|
|
||||||
/* Unsigned. */
|
|
||||||
typedef unsigned char uint8_t;
|
|
||||||
typedef unsigned short int uint16_t;
|
|
||||||
typedef unsigned int uint32_t;
|
|
||||||
typedef unsigned long int uint64_t;
|
|
||||||
|
|
||||||
/* Types for `void *' pointers. */
|
|
||||||
typedef long int intptr_t;
|
|
||||||
typedef unsigned long int uintptr_t;
|
|
||||||
|
|
||||||
// https://source.chromium.org/chromium/chromium/src/+/main:v8/include/v8-fast-api-calls.h;l=336
|
|
||||||
struct FastApiTypedArray {
|
|
||||||
uintptr_t length_;
|
|
||||||
void* data;
|
|
||||||
};
|
|
116
ext/ffi/tcc.rs
116
ext/ffi/tcc.rs
|
@ -1,116 +0,0 @@
|
||||||
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
ffi::CStr,
|
|
||||||
marker::PhantomData,
|
|
||||||
os::raw::{c_char, c_int, c_void},
|
|
||||||
ptr::null_mut,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct TCCState {
|
|
||||||
_unused: [u8; 0],
|
|
||||||
}
|
|
||||||
pub const TCC_OUTPUT_MEMORY: i32 = 1;
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
pub fn tcc_new() -> *mut TCCState;
|
|
||||||
pub fn tcc_delete(s: *mut TCCState);
|
|
||||||
pub fn tcc_set_options(s: *mut TCCState, str: *const c_char);
|
|
||||||
pub fn tcc_compile_string(s: *mut TCCState, buf: *const c_char) -> c_int;
|
|
||||||
pub fn tcc_add_symbol(
|
|
||||||
s: *mut TCCState,
|
|
||||||
name: *const c_char,
|
|
||||||
val: *const c_void,
|
|
||||||
) -> c_int;
|
|
||||||
pub fn tcc_set_output_type(s: *mut TCCState, output_type: c_int) -> c_int;
|
|
||||||
pub fn tcc_relocate(s1: *mut TCCState, ptr: *mut c_void) -> c_int;
|
|
||||||
pub fn tcc_get_symbol(s: *mut TCCState, name: *const c_char) -> *mut c_void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compilation context.
|
|
||||||
pub struct Compiler {
|
|
||||||
inner: *mut TCCState,
|
|
||||||
_phantom: PhantomData<TCCState>,
|
|
||||||
pub bin: Option<Vec<u8>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Compiler {
|
|
||||||
pub fn new() -> Result<Self, ()> {
|
|
||||||
// SAFETY: There is one context per thread.
|
|
||||||
let inner = unsafe { tcc_new() };
|
|
||||||
if inner.is_null() {
|
|
||||||
Err(())
|
|
||||||
} else {
|
|
||||||
let ret =
|
|
||||||
// SAFETY: set output to memory.
|
|
||||||
unsafe { tcc_set_output_type(inner, TCC_OUTPUT_MEMORY as c_int) };
|
|
||||||
assert_eq!(ret, 0);
|
|
||||||
Ok(Self {
|
|
||||||
inner,
|
|
||||||
_phantom: PhantomData,
|
|
||||||
bin: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_options(&mut self, option: &CStr) -> &mut Self {
|
|
||||||
// SAFETY: option is a null-terminated C string.
|
|
||||||
unsafe {
|
|
||||||
tcc_set_options(self.inner, option.as_ptr());
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn compile_string(&mut self, p: &CStr) -> Result<(), ()> {
|
|
||||||
// SAFETY: p is a null-terminated C string.
|
|
||||||
let ret = unsafe { tcc_compile_string(self.inner, p.as_ptr()) };
|
|
||||||
if ret == 0 {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Safety
|
|
||||||
/// Symbol need satisfy ABI requirement.
|
|
||||||
pub unsafe fn add_symbol(&mut self, sym: &CStr, val: *const c_void) {
|
|
||||||
// SAFETY: sym is a null-terminated C string.
|
|
||||||
let ret = tcc_add_symbol(self.inner, sym.as_ptr(), val);
|
|
||||||
assert_eq!(ret, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn relocate_and_get_symbol(
|
|
||||||
&mut self,
|
|
||||||
sym: &CStr,
|
|
||||||
) -> Result<*mut c_void, ()> {
|
|
||||||
// SAFETY: pass null ptr to get required length
|
|
||||||
let len = unsafe { tcc_relocate(self.inner, null_mut()) };
|
|
||||||
if len == -1 {
|
|
||||||
return Err(());
|
|
||||||
};
|
|
||||||
let mut bin = Vec::with_capacity(len as usize);
|
|
||||||
let ret =
|
|
||||||
// SAFETY: bin is allocated up to len.
|
|
||||||
unsafe { tcc_relocate(self.inner, bin.as_mut_ptr() as *mut c_void) };
|
|
||||||
if ret != 0 {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
// SAFETY: if ret == 0, bin is initialized.
|
|
||||||
unsafe {
|
|
||||||
bin.set_len(len as usize);
|
|
||||||
}
|
|
||||||
self.bin = Some(bin);
|
|
||||||
// SAFETY: sym is a null-terminated C string.
|
|
||||||
let addr = unsafe { tcc_get_symbol(self.inner, sym.as_ptr()) };
|
|
||||||
Ok(addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Compiler {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
// SAFETY: delete state from tcc_new()
|
|
||||||
unsafe { tcc_delete(self.inner) };
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit afc136262e93ae85fb3643005b36dbfc30d99c42
|
|
|
@ -11,4 +11,5 @@ publish = false
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
pretty_assertions = "1.2.1"
|
||||||
test_util = { path = "../test_util" }
|
test_util = { path = "../test_util" }
|
||||||
|
|
|
@ -258,6 +258,60 @@ pub extern "C" fn call_stored_function_thread_safe_and_log() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn log_many_parameters(
|
||||||
|
a: u8,
|
||||||
|
b: u16,
|
||||||
|
c: u32,
|
||||||
|
d: u64,
|
||||||
|
e: f64,
|
||||||
|
f: f32,
|
||||||
|
g: i64,
|
||||||
|
h: i32,
|
||||||
|
i: i16,
|
||||||
|
j: i8,
|
||||||
|
k: isize,
|
||||||
|
l: usize,
|
||||||
|
m: f64,
|
||||||
|
n: f32,
|
||||||
|
o: f64,
|
||||||
|
p: f32,
|
||||||
|
q: f64,
|
||||||
|
r: f32,
|
||||||
|
s: f64,
|
||||||
|
) {
|
||||||
|
println!("{a} {b} {c} {d} {e} {f} {g} {h} {i} {j} {k} {l} {m} {n} {o} {p} {q} {r} {s}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn cast_u8_u32(x: u8) -> u32 {
|
||||||
|
x as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn cast_u32_u8(x: u32) -> u8 {
|
||||||
|
x as u8
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn add_many_u16(
|
||||||
|
a: u16,
|
||||||
|
b: u16,
|
||||||
|
c: u16,
|
||||||
|
d: u16,
|
||||||
|
e: u16,
|
||||||
|
f: u16,
|
||||||
|
g: u16,
|
||||||
|
h: u16,
|
||||||
|
i: u16,
|
||||||
|
j: u16,
|
||||||
|
k: u16,
|
||||||
|
l: u16,
|
||||||
|
m: u16,
|
||||||
|
) -> u16 {
|
||||||
|
a + b + c + d + e + f + g + h + i + j + k + l + m
|
||||||
|
}
|
||||||
|
|
||||||
// FFI performance helper functions
|
// FFI performance helper functions
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn nop() {}
|
pub extern "C" fn nop() {}
|
||||||
|
|
|
@ -80,6 +80,10 @@ fn basic() {
|
||||||
579.912\n\
|
579.912\n\
|
||||||
true\n\
|
true\n\
|
||||||
false\n\
|
false\n\
|
||||||
|
579.9119873046875\n\
|
||||||
|
579.9119873046875\n\
|
||||||
|
579.912\n\
|
||||||
|
579.912\n\
|
||||||
579\n\
|
579\n\
|
||||||
8589934590\n\
|
8589934590\n\
|
||||||
-8589934590\n\
|
-8589934590\n\
|
||||||
|
@ -105,6 +109,14 @@ fn basic() {
|
||||||
buf: [1, 2, 3, 4, 5, 6, 7, 8]\n\
|
buf: [1, 2, 3, 4, 5, 6, 7, 8]\n\
|
||||||
logCallback\n\
|
logCallback\n\
|
||||||
30\n\
|
30\n\
|
||||||
|
255 65535 4294967295 4294967296 123.456 789.876 -1 -2 -3 -4 -1000 1000 12345.67891 12345.679 12345.67891 12345.679 12345.67891 12345.679 12345.67891\n\
|
||||||
|
255 65535 4294967295 4294967296 123.456 789.876 -1 -2 -3 -4 -1000 1000 12345.67891 12345.679 12345.67891 12345.679 12345.67891 12345.679 12345.67891\n\
|
||||||
|
0\n\
|
||||||
|
0\n\
|
||||||
|
0\n\
|
||||||
|
0\n\
|
||||||
|
78\n\
|
||||||
|
78\n\
|
||||||
STORED_FUNCTION cleared\n\
|
STORED_FUNCTION cleared\n\
|
||||||
STORED_FUNCTION_2 cleared\n\
|
STORED_FUNCTION_2 cleared\n\
|
||||||
Thread safe call counter: 0\n\
|
Thread safe call counter: 0\n\
|
||||||
|
@ -120,6 +132,8 @@ fn basic() {
|
||||||
uint32Array[0]: 42\n\
|
uint32Array[0]: 42\n\
|
||||||
uint32Array[0] after mutation: 55\n\
|
uint32Array[0] after mutation: 55\n\
|
||||||
Static ptr value after mutation: 55\n\
|
Static ptr value after mutation: 55\n\
|
||||||
|
2264956937\n\
|
||||||
|
2264956937\n\
|
||||||
Correct number of resources\n";
|
Correct number of resources\n";
|
||||||
assert_eq!(stdout, expected);
|
assert_eq!(stdout, expected);
|
||||||
assert_eq!(stderr, "");
|
assert_eq!(stderr, "");
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
import { assertEquals } from "https://deno.land/std@0.149.0/testing/asserts.ts";
|
import { assertEquals } from "https://deno.land/std@0.149.0/testing/asserts.ts";
|
||||||
import {
|
import {
|
||||||
assertThrows,
|
assertThrows,
|
||||||
|
assert,
|
||||||
} from "../../test_util/std/testing/asserts.ts";
|
} from "../../test_util/std/testing/asserts.ts";
|
||||||
|
|
||||||
const targetDir = Deno.execPath().replace(/[^\/\\]+$/, "");
|
const targetDir = Deno.execPath().replace(/[^\/\\]+$/, "");
|
||||||
|
@ -175,6 +176,22 @@ const dylib = Deno.dlopen(libPath, {
|
||||||
result: "void",
|
result: "void",
|
||||||
callback: true,
|
callback: true,
|
||||||
},
|
},
|
||||||
|
log_many_parameters: {
|
||||||
|
parameters: ["u8", "u16", "u32", "u64", "f64", "f32", "i64", "i32", "i16", "i8", "isize", "usize", "f64", "f32", "f64", "f32", "f64", "f32", "f64"],
|
||||||
|
result: "void",
|
||||||
|
},
|
||||||
|
cast_u8_u32: {
|
||||||
|
parameters: ["u8"],
|
||||||
|
result: "u32",
|
||||||
|
},
|
||||||
|
cast_u32_u8: {
|
||||||
|
parameters: ["u32"],
|
||||||
|
result: "u8",
|
||||||
|
},
|
||||||
|
add_many_u16: {
|
||||||
|
parameters: ["u16", "u16", "u16", "u16", "u16", "u16", "u16", "u16", "u16", "u16", "u16", "u16", "u16"],
|
||||||
|
result: "u16",
|
||||||
|
},
|
||||||
// Statics
|
// Statics
|
||||||
"static_u32": {
|
"static_u32": {
|
||||||
type: "u32",
|
type: "u32",
|
||||||
|
@ -191,6 +208,7 @@ const dylib = Deno.dlopen(libPath, {
|
||||||
"static_char": {
|
"static_char": {
|
||||||
type: "pointer",
|
type: "pointer",
|
||||||
},
|
},
|
||||||
|
"hash": { parameters: ["buffer", "u32"], result: "u32" },
|
||||||
});
|
});
|
||||||
const { symbols } = dylib;
|
const { symbols } = dylib;
|
||||||
|
|
||||||
|
@ -210,11 +228,7 @@ function returnBuffer() { return return_buffer(); };
|
||||||
returnBuffer();
|
returnBuffer();
|
||||||
%OptimizeFunctionOnNextCall(returnBuffer);
|
%OptimizeFunctionOnNextCall(returnBuffer);
|
||||||
const ptr0 = returnBuffer();
|
const ptr0 = returnBuffer();
|
||||||
|
assertIsOptimized(returnBuffer);
|
||||||
const status = %GetOptimizationStatus(returnBuffer);
|
|
||||||
if (!(status & (1 << 4))) {
|
|
||||||
throw new Error("returnBuffer is not optimized");
|
|
||||||
}
|
|
||||||
|
|
||||||
dylib.symbols.print_pointer(ptr0, 8);
|
dylib.symbols.print_pointer(ptr0, 8);
|
||||||
const ptrView = new Deno.UnsafePointerView(ptr0);
|
const ptrView = new Deno.UnsafePointerView(ptr0);
|
||||||
|
@ -266,17 +280,10 @@ const { add_u32, add_usize_fast } = symbols;
|
||||||
function addU32Fast(a, b) {
|
function addU32Fast(a, b) {
|
||||||
return add_u32(a, b);
|
return add_u32(a, b);
|
||||||
};
|
};
|
||||||
|
testOptimized(addU32Fast, () => addU32Fast(123, 456));
|
||||||
%PrepareFunctionForOptimization(addU32Fast);
|
|
||||||
console.log(addU32Fast(123, 456));
|
|
||||||
%OptimizeFunctionOnNextCall(addU32Fast);
|
|
||||||
console.log(addU32Fast(123, 456));
|
|
||||||
|
|
||||||
function addU64Fast(a, b) { return add_usize_fast(a, b); };
|
function addU64Fast(a, b) { return add_usize_fast(a, b); };
|
||||||
%PrepareFunctionForOptimization(addU64Fast);
|
testOptimized(addU64Fast, () => addU64Fast(2, 3));
|
||||||
console.log(addU64Fast(2, 3));
|
|
||||||
%OptimizeFunctionOnNextCall(addU64Fast);
|
|
||||||
console.log(addU64Fast(2, 3));
|
|
||||||
|
|
||||||
console.log(dylib.symbols.add_i32(123, 456));
|
console.log(dylib.symbols.add_i32(123, 456));
|
||||||
console.log(dylib.symbols.add_u64(0xffffffffn, 0xffffffffn));
|
console.log(dylib.symbols.add_u64(0xffffffffn, 0xffffffffn));
|
||||||
|
@ -294,6 +301,16 @@ console.log(dylib.symbols.add_f64(123.123, 456.789));
|
||||||
console.log(dylib.symbols.and(true, true));
|
console.log(dylib.symbols.and(true, true));
|
||||||
console.log(dylib.symbols.and(true, false));
|
console.log(dylib.symbols.and(true, false));
|
||||||
|
|
||||||
|
function addF32Fast(a, b) {
|
||||||
|
return dylib.symbols.add_f32(a, b);
|
||||||
|
};
|
||||||
|
testOptimized(addF32Fast, () => addF32Fast(123.123, 456.789));
|
||||||
|
|
||||||
|
function addF64Fast(a, b) {
|
||||||
|
return dylib.symbols.add_f64(a, b);
|
||||||
|
};
|
||||||
|
testOptimized(addF64Fast, () => addF64Fast(123.123, 456.789));
|
||||||
|
|
||||||
// Test adders as nonblocking calls
|
// Test adders as nonblocking calls
|
||||||
console.log(await dylib.symbols.add_i32_nonblocking(123, 456));
|
console.log(await dylib.symbols.add_i32_nonblocking(123, 456));
|
||||||
console.log(await dylib.symbols.add_u64_nonblocking(0xffffffffn, 0xffffffffn));
|
console.log(await dylib.symbols.add_u64_nonblocking(0xffffffffn, 0xffffffffn));
|
||||||
|
@ -437,6 +454,39 @@ call_stored_function();
|
||||||
dylib.symbols.store_function_2(add10Callback.pointer);
|
dylib.symbols.store_function_2(add10Callback.pointer);
|
||||||
dylib.symbols.call_stored_function_2(20);
|
dylib.symbols.call_stored_function_2(20);
|
||||||
|
|
||||||
|
function logManyParametersFast(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s) {
|
||||||
|
return symbols.log_many_parameters(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s);
|
||||||
|
};
|
||||||
|
testOptimized(
|
||||||
|
logManyParametersFast,
|
||||||
|
() => logManyParametersFast(
|
||||||
|
255, 65535, 4294967295, 4294967296, 123.456, 789.876, -1, -2, -3, -4, -1000, 1000,
|
||||||
|
12345.678910, 12345.678910, 12345.678910, 12345.678910, 12345.678910, 12345.678910, 12345.678910
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Some ABIs rely on the convention to zero/sign-extend arguments by the caller to optimize the callee function.
|
||||||
|
// If the trampoline did not zero/sign-extend arguments, this would return 256 instead of the expected 0 (in optimized builds)
|
||||||
|
function castU8U32Fast(x) { return symbols.cast_u8_u32(x); };
|
||||||
|
testOptimized(castU8U32Fast, () => castU8U32Fast(256));
|
||||||
|
|
||||||
|
// Some ABIs rely on the convention to expect garbage in the bits beyond the size of the return value to optimize the callee function.
|
||||||
|
// If the trampoline did not zero/sign-extend the return value, this would return 256 instead of the expected 0 (in optimized builds)
|
||||||
|
function castU32U8Fast(x) { return symbols.cast_u32_u8(x); };
|
||||||
|
testOptimized(castU32U8Fast, () => castU32U8Fast(256));
|
||||||
|
|
||||||
|
// Generally the trampoline tail-calls into the FFI function, but in certain cases (e.g. when returning 8 or 16 bit integers)
|
||||||
|
// the tail call is not possible and a new stack frame must be created. We need enough parameters to have some on the stack
|
||||||
|
function addManyU16Fast(a, b, c, d, e, f, g, h, i, j, k, l, m) {
|
||||||
|
return symbols.add_many_u16(a, b, c, d, e, f, g, h, i, j, k, l, m);
|
||||||
|
};
|
||||||
|
// N.B. V8 does not currently follow Aarch64 Apple's calling convention.
|
||||||
|
// The current implementation of the JIT trampoline follows the V8 incorrect calling convention. This test covers the use-case
|
||||||
|
// and is expected to fail once Deno uses a V8 version with the bug fixed.
|
||||||
|
// The V8 bug is being tracked in https://bugs.chromium.org/p/v8/issues/detail?id=13171
|
||||||
|
testOptimized(addManyU16Fast, () => addManyU16Fast(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12));
|
||||||
|
|
||||||
|
|
||||||
const nestedCallback = new Deno.UnsafeCallback(
|
const nestedCallback = new Deno.UnsafeCallback(
|
||||||
{ parameters: [], result: "void" },
|
{ parameters: [], result: "void" },
|
||||||
() => {
|
() => {
|
||||||
|
@ -502,6 +552,12 @@ try {
|
||||||
console.log("Invalid UTF-8 characters to `v8::String`:", charView.getCString());
|
console.log("Invalid UTF-8 characters to `v8::String`:", charView.getCString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const bytes = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||||
|
function hash() { return dylib.symbols.hash(bytes, bytes.byteLength); };
|
||||||
|
|
||||||
|
testOptimized(hash, () => hash());
|
||||||
|
|
||||||
(function cleanup() {
|
(function cleanup() {
|
||||||
dylib.close();
|
dylib.close();
|
||||||
throwCallback.close();
|
throwCallback.close();
|
||||||
|
@ -526,4 +582,23 @@ After: ${postStr}`,
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Correct number of resources");
|
console.log("Correct number of resources");
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
function assertIsOptimized(fn) {
|
||||||
|
const status = % GetOptimizationStatus(fn);
|
||||||
|
assert(status & (1 << 4), `expected ${fn.name} to be optimized, but wasn't`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testOptimized(fn, callback) {
|
||||||
|
%PrepareFunctionForOptimization(fn);
|
||||||
|
const r1 = callback();
|
||||||
|
if (r1 !== undefined) {
|
||||||
|
console.log(r1);
|
||||||
|
}
|
||||||
|
%OptimizeFunctionOnNextCall(fn);
|
||||||
|
const r2 = callback();
|
||||||
|
if (r2 !== undefined) {
|
||||||
|
console.log(r2);
|
||||||
|
}
|
||||||
|
assertIsOptimized(fn);
|
||||||
|
}
|
|
@ -40,4 +40,4 @@ tokio-tungstenite = "0.16"
|
||||||
pty = "0.2.2"
|
pty = "0.2.2"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winapi = { version = "0.3.9", features = ["consoleapi", "handleapi", "namedpipeapi", "winbase", "winerror"] }
|
winapi = { version = "0.3.9", features = ["consoleapi", "synchapi", "handleapi", "namedpipeapi", "winbase", "winerror"] }
|
||||||
|
|
Loading…
Reference in a new issue