mirror of
https://github.com/denoland/deno.git
synced 2024-12-23 15:49:44 -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"]
|
||||
path = test_util/wpt
|
||||
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 = [
|
||||
"deno_core",
|
||||
"dlopen",
|
||||
"dynasmrt",
|
||||
"libffi",
|
||||
"serde",
|
||||
"tokio",
|
||||
|
@ -1467,6 +1468,32 @@ version = "1.0.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "ecdsa"
|
||||
version = "0.14.1"
|
||||
|
@ -2657,6 +2684,15 @@ version = "2.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a79b39c93a7a5a27eeaf9a23b5ff43f1b9e0ad6b1cdd441140ae53c35613fc7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.5"
|
||||
|
@ -4590,6 +4626,7 @@ dependencies = [
|
|||
name = "test_ffi"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"pretty_assertions",
|
||||
"test_util",
|
||||
]
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ path = "lib.rs"
|
|||
[dependencies]
|
||||
deno_core = { version = "0.149.0", path = "../../core" }
|
||||
dlopen = "0.1.8"
|
||||
dynasmrt = "1.2.3"
|
||||
libffi = "3.0.0"
|
||||
serde = { version = "1.0.129", features = ["derive"] }
|
||||
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_v8;
|
||||
use deno_core::v8;
|
||||
use deno_core::v8::fast_api;
|
||||
use deno_core::Extension;
|
||||
use deno_core::OpState;
|
||||
use deno_core::Resource;
|
||||
|
@ -39,15 +38,11 @@ use std::ptr;
|
|||
use std::rc::Rc;
|
||||
use std::sync::mpsc::sync_channel;
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
mod jit_trampoline;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
mod tcc;
|
||||
mod fast_call;
|
||||
|
||||
#[cfg(not(target_pointer_width = "64"))]
|
||||
compile_error!("platform not supported");
|
||||
|
||||
// Assert assumptions made in `prelude.h`
|
||||
const _: () = {
|
||||
assert!(size_of::<c_char>() == 1);
|
||||
assert!(size_of::<c_short>() == 2);
|
||||
|
@ -90,8 +85,6 @@ struct Symbol {
|
|||
ptr: libffi::middle::CodePtr,
|
||||
parameter_types: Vec<NativeType>,
|
||||
result_type: NativeType,
|
||||
// This is dead code only on Windows
|
||||
#[allow(dead_code)]
|
||||
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 {
|
||||
matches!(
|
||||
rv,
|
||||
|
@ -796,42 +745,6 @@ fn make_sync_fn<'s>(
|
|||
scope: &mut v8::HandleScope<'s>,
|
||||
sym: Box<Symbol>,
|
||||
) -> 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 builder = v8::FunctionTemplate::builder(
|
||||
|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());
|
||||
|
||||
let func = if let Some(fast_ffi_templ) = fast_ffi_templ {
|
||||
builder.build_fast(scope, &fast_ffi_templ, None)
|
||||
let mut fast_call_alloc = 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 {
|
||||
builder.build(scope)
|
||||
};
|
||||
|
@ -904,12 +826,12 @@ fn make_sync_fn<'s>(
|
|||
Box::new(move |_| {
|
||||
// SAFETY: This is never called twice. pointer obtained
|
||||
// from Box::into_raw, hence, satisfies memory layout requirements.
|
||||
unsafe {
|
||||
Box::from_raw(sym);
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
if let Some(fast_allocations) = fast_allocations {
|
||||
Box::from_raw(fast_allocations as *mut jit_trampoline::Allocation);
|
||||
}
|
||||
let _ = unsafe { Box::from_raw(sym) };
|
||||
if let Some(fast_call_ptr) = fast_call_alloc {
|
||||
// fast-call compiled trampoline is unmapped when the MMAP handle is dropped
|
||||
// SAFETY: This is never called twice. pointer obtained
|
||||
// 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"]
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.2.1"
|
||||
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
|
||||
#[no_mangle]
|
||||
pub extern "C" fn nop() {}
|
||||
|
|
|
@ -80,6 +80,10 @@ fn basic() {
|
|||
579.912\n\
|
||||
true\n\
|
||||
false\n\
|
||||
579.9119873046875\n\
|
||||
579.9119873046875\n\
|
||||
579.912\n\
|
||||
579.912\n\
|
||||
579\n\
|
||||
8589934590\n\
|
||||
-8589934590\n\
|
||||
|
@ -105,6 +109,14 @@ fn basic() {
|
|||
buf: [1, 2, 3, 4, 5, 6, 7, 8]\n\
|
||||
logCallback\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_2 cleared\n\
|
||||
Thread safe call counter: 0\n\
|
||||
|
@ -120,6 +132,8 @@ fn basic() {
|
|||
uint32Array[0]: 42\n\
|
||||
uint32Array[0] after mutation: 55\n\
|
||||
Static ptr value after mutation: 55\n\
|
||||
2264956937\n\
|
||||
2264956937\n\
|
||||
Correct number of resources\n";
|
||||
assert_eq!(stdout, expected);
|
||||
assert_eq!(stderr, "");
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import { assertEquals } from "https://deno.land/std@0.149.0/testing/asserts.ts";
|
||||
import {
|
||||
assertThrows,
|
||||
assert,
|
||||
} from "../../test_util/std/testing/asserts.ts";
|
||||
|
||||
const targetDir = Deno.execPath().replace(/[^\/\\]+$/, "");
|
||||
|
@ -175,6 +176,22 @@ const dylib = Deno.dlopen(libPath, {
|
|||
result: "void",
|
||||
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
|
||||
"static_u32": {
|
||||
type: "u32",
|
||||
|
@ -191,6 +208,7 @@ const dylib = Deno.dlopen(libPath, {
|
|||
"static_char": {
|
||||
type: "pointer",
|
||||
},
|
||||
"hash": { parameters: ["buffer", "u32"], result: "u32" },
|
||||
});
|
||||
const { symbols } = dylib;
|
||||
|
||||
|
@ -210,11 +228,7 @@ function returnBuffer() { return return_buffer(); };
|
|||
returnBuffer();
|
||||
%OptimizeFunctionOnNextCall(returnBuffer);
|
||||
const ptr0 = returnBuffer();
|
||||
|
||||
const status = %GetOptimizationStatus(returnBuffer);
|
||||
if (!(status & (1 << 4))) {
|
||||
throw new Error("returnBuffer is not optimized");
|
||||
}
|
||||
assertIsOptimized(returnBuffer);
|
||||
|
||||
dylib.symbols.print_pointer(ptr0, 8);
|
||||
const ptrView = new Deno.UnsafePointerView(ptr0);
|
||||
|
@ -266,17 +280,10 @@ const { add_u32, add_usize_fast } = symbols;
|
|||
function addU32Fast(a, b) {
|
||||
return add_u32(a, b);
|
||||
};
|
||||
|
||||
%PrepareFunctionForOptimization(addU32Fast);
|
||||
console.log(addU32Fast(123, 456));
|
||||
%OptimizeFunctionOnNextCall(addU32Fast);
|
||||
console.log(addU32Fast(123, 456));
|
||||
testOptimized(addU32Fast, () => addU32Fast(123, 456));
|
||||
|
||||
function addU64Fast(a, b) { return add_usize_fast(a, b); };
|
||||
%PrepareFunctionForOptimization(addU64Fast);
|
||||
console.log(addU64Fast(2, 3));
|
||||
%OptimizeFunctionOnNextCall(addU64Fast);
|
||||
console.log(addU64Fast(2, 3));
|
||||
testOptimized(addU64Fast, () => addU64Fast(2, 3));
|
||||
|
||||
console.log(dylib.symbols.add_i32(123, 456));
|
||||
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, 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
|
||||
console.log(await dylib.symbols.add_i32_nonblocking(123, 456));
|
||||
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.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(
|
||||
{ parameters: [], result: "void" },
|
||||
() => {
|
||||
|
@ -502,6 +552,12 @@ try {
|
|||
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() {
|
||||
dylib.close();
|
||||
throwCallback.close();
|
||||
|
@ -526,4 +582,23 @@ After: ${postStr}`,
|
|||
}
|
||||
|
||||
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"
|
||||
|
||||
[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