diff --git a/Cargo.lock b/Cargo.lock index 030f542..ba523b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,6 +47,38 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12024c4645c97566567129c204f65d5815a8c9aecf30fcbe682b2fe034996d36" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "clap" version = "2.34.0" @@ -77,6 +109,7 @@ dependencies = [ name = "deno_bindgen_cli" version = "0.1.0" dependencies = [ + "cargo_metadata", "deno_bindgen_ir", "dlopen2", "structopt", @@ -147,9 +180,9 @@ dependencies = [ [[package]] name = "itoa" -version = "0.4.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "lazy_static" @@ -260,31 +293,40 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +dependencies = [ + "serde", +] + [[package]] name = "serde" -version = "1.0.127" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f03b9878abf6d14e6779d3f24f07b2cfa90352cfec4acc5aab8f1ac7f146fae8" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.127" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a024926d3432516606328597e0f224a51355a493b49fdd67e9209187cbe55ecc" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", - "syn 1.0.74", + "syn 2.0.39", ] [[package]] name = "serde_json" -version = "1.0.66" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -352,6 +394,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "unicode-ident" version = "1.0.12" diff --git a/README.md b/README.md index 7e531b1..168ad57 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,9 @@ import { Foo } from "@ffi/example"; } ``` -High performance. Codegen tries its best to take the fastest possible path for all bindings as-if they were written by hand to properly leverage the power of the Deno FFI JIT calls. +High performance. Codegen tries its best to take the fastest possible path for +all bindings as-if they were written by hand to properly leverage the power of +the Deno FFI JIT calls. ``` > make bench @@ -83,4 +85,4 @@ benchmark time (avg) iter/s (min … max) p75 --------------------------------------------------------------- ----------------------------- add 6.88 ns/iter 145,297,626.6 (6.78 ns … 13.33 ns) 6.81 ns 8.22 ns 9.4 ns bytelen 8.05 ns/iter 124,278,976.3 (7.81 ns … 18.1 ns) 8.09 ns 10.39 ns 11.64 ns -``` \ No newline at end of file +``` diff --git a/deno_bindgen_cli/Cargo.toml b/deno_bindgen_cli/Cargo.toml index 5fb072d..b71d64c 100644 --- a/deno_bindgen_cli/Cargo.toml +++ b/deno_bindgen_cli/Cargo.toml @@ -18,4 +18,5 @@ path = "./main.rs" [dependencies] structopt = "0.3.26" deno_bindgen_ir = { path = "../deno_bindgen_ir", version = "0.1.0" } -dlopen2 = "0.6.1" \ No newline at end of file +dlopen2 = "0.6.1" +cargo_metadata = "0.18.1" \ No newline at end of file diff --git a/deno_bindgen_cli/cargo.rs b/deno_bindgen_cli/cargo.rs index 6484e65..84848e9 100644 --- a/deno_bindgen_cli/cargo.rs +++ b/deno_bindgen_cli/cargo.rs @@ -1,4 +1,13 @@ -use std::{io::Result, path::Path, process::Command}; +use std::{ + io::Result, + path::{Path, PathBuf}, + process::{Command, Stdio}, +}; + +pub struct Artifact { + pub path: PathBuf, + pub manifest_path: PathBuf, +} #[derive(Default)] pub struct Build { @@ -15,18 +24,43 @@ impl Build { self } - pub fn build(self, path: &Path) -> Result<()> { + pub fn build(self, path: &Path) -> Result { let mut cmd = Command::new("cargo"); - cmd.current_dir(path).arg("build").arg("--lib"); + cmd + .current_dir(path) + .arg("build") + .arg("--lib") + .arg("--message-format=json") + .stdout(Stdio::piped()); if self.release { cmd.arg("--release"); } let status = cmd.status()?; - + let output = cmd.output()?; if status.success() { - Ok(()) + let reader = std::io::BufReader::new(output.stdout.as_slice()); + for message in cargo_metadata::Message::parse_stream(reader) { + match message.unwrap() { + cargo_metadata::Message::CompilerArtifact(artifact) => { + if artifact.target.kind.contains(&"cdylib".to_string()) { + return Ok(Artifact { + path: PathBuf::from(artifact.filenames[0].to_string()), + manifest_path: PathBuf::from( + artifact.manifest_path.to_string(), + ), + }); + } + } + _ => {} + } + } + + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "failed to parse cargo output", + ))? } else { println!( "failed to execute `cargo`: exited with {}\n full command: {:?}", @@ -37,3 +71,16 @@ impl Build { } } } + +pub fn metadata(path: &Path) -> Result { + let metadata = cargo_metadata::MetadataCommand::new() + .manifest_path(path) + .exec() + .map_err(|e| { + println!("failed to execute `cargo metadata`: {}", e); + std::process::exit(1); + }) + .unwrap(); + + Ok(metadata.root_package().unwrap().name.clone()) +} diff --git a/deno_bindgen_cli/dlfcn.rs b/deno_bindgen_cli/dlfcn.rs index 966153a..8be69f3 100644 --- a/deno_bindgen_cli/dlfcn.rs +++ b/deno_bindgen_cli/dlfcn.rs @@ -10,6 +10,7 @@ struct Api { pub unsafe fn load_and_init( path: &Path, out: Option, + lazy_init: bool, ) -> std::io::Result<()> { let cont: Container = Container::load(path).map_err(|e| { std::io::Error::new( @@ -21,6 +22,8 @@ pub unsafe fn load_and_init( cont.init_deno_bindgen(deno_bindgen_ir::codegen::Options { target: deno_bindgen_ir::codegen::Target::Deno, out, + local_dylib_path: path.to_path_buf(), + lazy_init, }); Ok(()) diff --git a/deno_bindgen_cli/main.rs b/deno_bindgen_cli/main.rs index 6bb0955..2ac78ee 100644 --- a/deno_bindgen_cli/main.rs +++ b/deno_bindgen_cli/main.rs @@ -1,5 +1,6 @@ use std::path::PathBuf; +use cargo::Artifact; use structopt::StructOpt; mod cargo; @@ -14,19 +15,25 @@ struct Opt { #[structopt(short, long)] out: Option, + + #[structopt(short, long)] + lazy_init: bool, } fn main() -> std::io::Result<()> { let opt = Opt::from_args(); let cwd = std::env::current_dir().unwrap(); - cargo::Build::new().release(opt.release).build(&cwd)?; + let Artifact { + path, + manifest_path, + } = cargo::Build::new().release(opt.release).build(&cwd)?; + + let name = cargo::metadata(&manifest_path)?; + println!("Initializing {name}"); unsafe { - dlfcn::load_and_init( - &cwd.join("target/debug/libdeno_bindgen_test.dylib"), - opt.out, - )? + dlfcn::load_and_init(&PathBuf::from(path), opt.out, opt.lazy_init)? }; Ok(()) } diff --git a/deno_bindgen_ir/codegen/deno.rs b/deno_bindgen_ir/codegen/deno.rs index 3017e29..ed818e7 100644 --- a/deno_bindgen_ir/codegen/deno.rs +++ b/deno_bindgen_ir/codegen/deno.rs @@ -1,6 +1,7 @@ use std::{ borrow::Cow, io::{Result, Write}, + path::Path, }; use super::Generator; @@ -110,22 +111,68 @@ impl From for DenoFfiType { pub struct Codegen<'a> { symbols: &'a [Inventory], + target: &'a Path, + lazy: bool, } impl<'a> Codegen<'a> { - pub fn new(symbols: &'a [Inventory]) -> Self { - Self { symbols } + pub fn new(symbols: &'a [Inventory], target: &'a Path, lazy: bool) -> Self { + Self { + symbols, + target, + lazy, + } } fn dlopen(&self, writer: &mut W) -> Result<()> { + if self.lazy { + return self.lazy_dlopen(writer); + } writeln!(writer, "const {{ dlopen }} = Deno;\n")?; - - writeln!( - writer, - "const {{ symbols }} = dlopen('./target/debug/libdeno_bindgen_test.dylib', {{" - )?; + let target = self.target.to_string_lossy(); + writeln!(writer, "const {{ symbols }} = dlopen('{target}', {{")?; self.write_symbols(writer)?; writeln!(writer, "}});\n")?; + + Ok(()) + } + + fn lazy_dlopen(&self, writer: &mut W) -> Result<()> { + writeln!(writer, "let symbols: any;\n")?; + let target = self.target.to_string_lossy(); + writeln!(writer, "export function load(path: string = '{target}') {{")?; + writeln!(writer, " const {{ dlopen }} = Deno;\n")?; + writeln!(writer, " const {{ symbols: symbols_ }} = dlopen(path, {{")?; + struct WrapperWriter<'a, W: Write> { + writer: &'a mut W, + indent: usize, + } + impl Write for WrapperWriter<'_, W> { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + // Find newlines and indent them. + for byte in buf { + if *byte == b'\n' { + self.writer.write_all(b"\n")?; + self.writer.write_all(&vec![b' '; self.indent])?; + } else { + self.writer.write_all(&[*byte])?; + } + } + + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.writer.flush() + } + } + write!(writer, " ")?; + let mut wr = WrapperWriter { writer, indent: 2 }; + self.write_symbols(&mut wr)?; + writeln!(wr, "}});\n")?; + write!(wr, "symbols = symbols_;")?; + writeln!(writer, "\n}}\n")?; + Ok(()) } diff --git a/deno_bindgen_ir/codegen/mod.rs b/deno_bindgen_ir/codegen/mod.rs index 6c39349..8f475b0 100644 --- a/deno_bindgen_ir/codegen/mod.rs +++ b/deno_bindgen_ir/codegen/mod.rs @@ -7,6 +7,8 @@ mod deno; pub struct Options { pub target: Target, pub out: Option, + pub local_dylib_path: PathBuf, + pub lazy_init: bool, } pub enum Target { @@ -22,7 +24,9 @@ pub fn generate( opt: Options, ) -> std::io::Result<()> { let mut codegen = match opt.target { - Target::Deno => deno::Codegen::new(symbols), + Target::Deno => { + deno::Codegen::new(symbols, &opt.local_dylib_path, opt.lazy_init) + } }; if let Some(out) = opt.out { diff --git a/example/bindings/bindings.ts b/example/bindings/bindings.ts index ae9e230..97e765e 100644 --- a/example/bindings/bindings.ts +++ b/example/bindings/bindings.ts @@ -3,114 +3,120 @@ // This file is automatically generated by deno_bindgen. // Do not edit this file directly. -const { dlopen } = Deno; - -const { symbols } = dlopen('./target/debug/libdeno_bindgen_test.dylib', { - add: { - parameters: [ - 'i32', - 'i32', - ], - result: 'i32', - nonblocking: false - }, - __Input_new: { - parameters: [ - 'i32', - 'i32', - ], - result: 'pointer', - nonblocking: false - }, - __Input_dealloc: { - parameters: [ - 'pointer', - ], - result: 'void', - nonblocking: false - }, - add2: { - parameters: [ - 'pointer', - ], - result: 'i32', - nonblocking: false - }, - bytelen: { - parameters: [ - 'buffer', - 'usize', - ], - result: 'u32', - nonblocking: false - }, - buf_mut: { - parameters: [ - 'buffer', - 'usize', - ], - result: 'void', - nonblocking: false - }, - cstr: { - parameters: [], - result: 'pointer', - nonblocking: false - }, - strlen: { - parameters: [ - 'pointer', - ], - result: 'u32', - nonblocking: false - }, - non_blocking: { - parameters: [], - result: 'i32', - nonblocking: true - }, - make_foo: { - parameters: [], - result: 'pointer', - nonblocking: false - }, - inc_foo: { - parameters: [ - 'pointer', - ], - result: 'void', - nonblocking: false - }, - __Foo_new: { - parameters: [ - 'u32', - ], - result: 'pointer', - nonblocking: false - }, - __Foo_inc: { - parameters: [ - 'pointer', - ], - result: 'void', - nonblocking: false - }, - __Foo_bar: { - parameters: [ - 'pointer', - 'u32', - ], - result: 'u32', - nonblocking: false - }, - __Foo_dealloc: { - parameters: [ - 'pointer', - ], - result: 'void', - nonblocking: false - }, -}); +let symbols: any; + +export function load(path: string = '/Users/divy/gh/deno_bindgen/example/target/debug/libdeno_bindgen_test.dylib') { + const { dlopen } = Deno; + + const { symbols: symbols_ } = dlopen(path, { + add: { + parameters: [ + 'i32', + 'i32', + ], + result: 'i32', + nonblocking: false + }, + __Input_new: { + parameters: [ + 'i32', + 'i32', + ], + result: 'pointer', + nonblocking: false + }, + __Input_dealloc: { + parameters: [ + 'pointer', + ], + result: 'void', + nonblocking: false + }, + add2: { + parameters: [ + 'pointer', + ], + result: 'i32', + nonblocking: false + }, + bytelen: { + parameters: [ + 'buffer', + 'usize', + ], + result: 'u32', + nonblocking: false + }, + buf_mut: { + parameters: [ + 'buffer', + 'usize', + ], + result: 'void', + nonblocking: false + }, + cstr: { + parameters: [], + result: 'pointer', + nonblocking: false + }, + strlen: { + parameters: [ + 'pointer', + ], + result: 'u32', + nonblocking: false + }, + non_blocking: { + parameters: [], + result: 'i32', + nonblocking: true + }, + make_foo: { + parameters: [], + result: 'pointer', + nonblocking: false + }, + inc_foo: { + parameters: [ + 'pointer', + ], + result: 'void', + nonblocking: false + }, + __Foo_new: { + parameters: [ + 'u32', + ], + result: 'pointer', + nonblocking: false + }, + __Foo_inc: { + parameters: [ + 'pointer', + ], + result: 'void', + nonblocking: false + }, + __Foo_bar: { + parameters: [ + 'pointer', + 'u32', + ], + result: 'u32', + nonblocking: false + }, + __Foo_dealloc: { + parameters: [ + 'pointer', + ], + result: 'void', + nonblocking: false + }, + }); + + symbols = symbols_; +} export function add( arg0: number,