From 28590889b08ea1a151a17ebfda641be66d95b765 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Wed, 8 Nov 2023 23:15:51 +0530 Subject: [PATCH] add new readme --- .gitignore | 1 + LICENSE | 3 +- Makefile | 11 ++++-- README.md | 69 +++++++++++++++++++++++++++++++- deno.lock | 53 ------------------------- deno_bindgen_ir/codegen/deno.rs | 10 ++--- example/bench.js | 2 +- example/bindings/bindings.ts | 70 ++++++++++++++++++++++++++++++--- example/bindings_test.ts | 38 ++++++++++-------- example/src/lib.rs | 19 +++++++-- 10 files changed, 185 insertions(+), 91 deletions(-) delete mode 100644 deno.lock diff --git a/.gitignore b/.gitignore index 202f18c..edfb2f6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ target/ example/Cargo.lock bindings.json .DS_Store +deno.lock \ No newline at end of file diff --git a/LICENSE b/LICENSE index 07bf05b..89e76f6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,6 @@ MIT License -Copyright (c) 2021-2022 Divy Srivastava -Copyright (c) 2022 the Deno authors +Copyright (c) 2023 Divy Srivastava Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index f40683e..8feeab7 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,11 @@ fmt: cargo fmt deno fmt --ignore=target/,example/target/,example/bindings/ -test: - cd example && deno run -A ../cli.ts && deno test -A --unstable +build: + cargo build -bench: - cd example && deno run -A ../cli.ts && deno bench -A --unstable bench.js +test: build + cd example && ../target/debug/deno_bindgen -o bindings/bindings.ts && deno test -A --unstable + +bench: build + cd example && ../target/debug/deno_bindgen -o bindings/bindings.ts && deno bench -A --unstable bench.js diff --git a/README.md b/README.md index 425ac34..7e531b1 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,86 @@ -## `deno_bindgen` +# `deno_bindgen` - + This tool aims to simplify glue code generation for Deno FFI libraries written in Rust. +## Install + +Install the command-line via `cargo`: + +```bash +cargo install deno_bindgen_cli +``` + +## Usage + ```rust use deno_bindgen::deno_bindgen; +// Export `add` function to JavaScript. #[deno_bindgen] fn add(a: u32, b: u32) -> u32 { a + b } ``` +Use the exported functions directly in ESM with TypeScript typings + ```typescript import { add } from "@ffi/example"; add(1, 2); +``` + +## Design + +The tool is designed to make it very easy to write high performance FFI +bindings. A lot of the things have been redesigned in `0.10` to prevent perf +footguns. + +TypeScript types are generated and supported OOTB. + +All class handles support disposing memory via the Explicit Resource Management +API (`using`). + +```rust +#[deno_bindgen] +pub struct Foo; + +#[deno_bindgen] +impl Foo { + #[constructor] + pub fn new() -> Self { + Self + } + + pub fn bar(&self) { + // ... + } +} +``` + +```js +import { Foo } from "@ffi/example"; + +{ + using foo = new Foo(); + foo.bar(); + // foo is disposed here... +} +``` + +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 +cpu: Apple M1 +runtime: deno 1.38.0 (aarch64-apple-darwin) + +file:///Users/divy/gh/deno_bindgen/example/bench.js +benchmark time (avg) iter/s (min … max) p75 p99 p995 +--------------------------------------------------------------- ----------------------------- +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.lock b/deno.lock deleted file mode 100644 index bb75278..0000000 --- a/deno.lock +++ /dev/null @@ -1,53 +0,0 @@ -{ - "version": "3", - "redirects": { - "https://deno.land/std/testing/asserts.ts": "https://deno.land/std@0.178.0/testing/asserts.ts" - }, - "remote": { - "https://deno.land/std@0.132.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74", - "https://deno.land/std@0.132.0/_util/os.ts": "49b92edea1e82ba295ec946de8ffd956ed123e2948d9bd1d3e901b04e4307617", - "https://deno.land/std@0.132.0/flags/mod.ts": "430cf2d1c26e00286373b2647ebdca637f7558505e88e9c108a4742cd184c916", - "https://deno.land/std@0.132.0/fs/_util.ts": "0fb24eb4bfebc2c194fb1afdb42b9c3dda12e368f43e8f2321f84fc77d42cb0f", - "https://deno.land/std@0.132.0/fs/ensure_dir.ts": "9dc109c27df4098b9fc12d949612ae5c9c7169507660dcf9ad90631833209d9d", - "https://deno.land/std@0.132.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3", - "https://deno.land/std@0.132.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09", - "https://deno.land/std@0.132.0/path/_util.ts": "c1e9686d0164e29f7d880b2158971d805b6e0efc3110d0b3e24e4b8af2190d2b", - "https://deno.land/std@0.132.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633", - "https://deno.land/std@0.132.0/path/glob.ts": "cb5255638de1048973c3e69e420c77dc04f75755524cb3b2e160fe9277d939ee", - "https://deno.land/std@0.132.0/path/mod.ts": "4275129bb766f0e475ecc5246aa35689eeade419d72a48355203f31802640be7", - "https://deno.land/std@0.132.0/path/posix.ts": "663e4a6fe30a145f56aa41a22d95114c4c5582d8b57d2d7c9ed27ad2c47636bb", - "https://deno.land/std@0.132.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9", - "https://deno.land/std@0.132.0/path/win32.ts": "e7bdf63e8d9982b4d8a01ef5689425c93310ece950e517476e22af10f41a136e", - "https://deno.land/std@0.178.0/fmt/colors.ts": "938c5d44d889fb82eff6c358bea8baa7e85950a16c9f6dae3ec3a7a729164471", - "https://deno.land/std@0.178.0/testing/_diff.ts": "1a3c044aedf77647d6cac86b798c6417603361b66b54c53331b312caeb447aea", - "https://deno.land/std@0.178.0/testing/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7", - "https://deno.land/std@0.178.0/testing/asserts.ts": "984ab0bfb3faeed92ffaa3a6b06536c66811185328c5dd146257c702c41b01ab", - "https://deno.land/std@0.97.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58", - "https://deno.land/std@0.97.0/_util/os.ts": "e282950a0eaa96760c0cf11e7463e66babd15ec9157d4c9ed49cc0925686f6a7", - "https://deno.land/std@0.97.0/encoding/base64.ts": "eecae390f1f1d1cae6f6c6d732ede5276bf4b9cd29b1d281678c054dc5cc009e", - "https://deno.land/std@0.97.0/encoding/hex.ts": "f952e0727bddb3b2fd2e6889d104eacbd62e92091f540ebd6459317a61932d9b", - "https://deno.land/std@0.97.0/fs/_util.ts": "f2ce811350236ea8c28450ed822a5f42a0892316515b1cd61321dec13569c56b", - "https://deno.land/std@0.97.0/fs/ensure_dir.ts": "b7c103dc41a3d1dbbb522bf183c519c37065fdc234831a4a0f7d671b1ed5fea7", - "https://deno.land/std@0.97.0/fs/exists.ts": "b0d2e31654819cc2a8d37df45d6b14686c0cc1d802e9ff09e902a63e98b85a00", - "https://deno.land/std@0.97.0/hash/_wasm/hash.ts": "cb6ad1ab429f8ac9d6eae48f3286e08236d662e1a2e5cfd681ba1c0f17375895", - "https://deno.land/std@0.97.0/hash/_wasm/wasm.js": "94b1b997ae6fb4e6d2156bcea8f79cfcd1e512a91252b08800a92071e5e84e1a", - "https://deno.land/std@0.97.0/hash/mod.ts": "5d032bd34186cda2f8d17fc122d621430953a6030d4b3f11172004715e3e2441", - "https://deno.land/std@0.97.0/path/_constants.ts": "1247fee4a79b70c89f23499691ef169b41b6ccf01887a0abd131009c5581b853", - "https://deno.land/std@0.97.0/path/_interface.ts": "1fa73b02aaa24867e481a48492b44f2598cd9dfa513c7b34001437007d3642e4", - "https://deno.land/std@0.97.0/path/_util.ts": "2e06a3b9e79beaf62687196bd4b60a4c391d862cfa007a20fc3a39f778ba073b", - "https://deno.land/std@0.97.0/path/common.ts": "eaf03d08b569e8a87e674e4e265e099f237472b6fd135b3cbeae5827035ea14a", - "https://deno.land/std@0.97.0/path/glob.ts": "314ad9ff263b895795208cdd4d5e35a44618ca3c6dd155e226fb15d065008652", - "https://deno.land/std@0.97.0/path/mod.ts": "4465dc494f271b02569edbb4a18d727063b5dbd6ed84283ff906260970a15d12", - "https://deno.land/std@0.97.0/path/posix.ts": "f56c3c99feb47f30a40ce9d252ef6f00296fa7c0fcb6dd81211bdb3b8b99ca3b", - "https://deno.land/std@0.97.0/path/separator.ts": "8fdcf289b1b76fd726a508f57d3370ca029ae6976fcde5044007f062e643ff1c", - "https://deno.land/std@0.97.0/path/win32.ts": "77f7b3604e0de40f3a7c698e8a79e7f601dc187035a1c21cb1e596666ce112f8", - "https://deno.land/x/cache@0.2.13/cache.ts": "4005aad54fb9aac9ff02526ffa798032e57f2d7966905fdeb7949263b1c95f2f", - "https://deno.land/x/cache@0.2.13/deps.ts": "6f14e76a1a09f329e3f3830c6e72bd10b53a89a75769d5ea886e5d8603e503e6", - "https://deno.land/x/cache@0.2.13/directories.ts": "ef48531cab3f827252e248596d15cede0de179a2fb15392ae24cf8034519994f", - "https://deno.land/x/cache@0.2.13/file.ts": "5abe7d80c6ac594c98e66eb4262962139f48cd9c49dbe2a77e9608760508a09a", - "https://deno.land/x/cache@0.2.13/file_fetcher.ts": "5c793cc83a5b9377679ec313b2a2321e51bf7ed15380fa82d387f1cdef3b924f", - "https://deno.land/x/cache@0.2.13/helpers.ts": "d1545d6432277b7a0b5ea254d1c51d572b6452a8eadd9faa7ad9c5586a1725c4", - "https://deno.land/x/cache@0.2.13/mod.ts": "3188250d3a013ef6c9eb060e5284cf729083af7944a29e60bb3d8597dd20ebcd", - "https://deno.land/x/dprint@0.2.0/mod.ts": "89ddd655e1b3a0b294f433c296fa01875037eed078b53aa60af755a25f128074" - } -} diff --git a/deno_bindgen_ir/codegen/deno.rs b/deno_bindgen_ir/codegen/deno.rs index d076412..3017e29 100644 --- a/deno_bindgen_ir/codegen/deno.rs +++ b/deno_bindgen_ir/codegen/deno.rs @@ -180,7 +180,7 @@ impl<'a> Codegen<'a> { allow_empty: bool, callback: impl Fn(&mut W, &[T]) -> Result<()>, nesting_spaces: usize, - delim: (char, char), + delim: (char, &str), ) -> Result<()> { let (start, end) = delim; write!(writer, "{start}")?; @@ -218,7 +218,7 @@ impl<'a> Codegen<'a> { Ok(()) }, 0, - ('(', ')'), + ('(', ")"), )?; let ret_ty = TypeScriptType::from(symbol.return_type); writeln!( @@ -249,7 +249,7 @@ impl<'a> Codegen<'a> { Ok(()) }, 2, - ('(', ')'), + ('(', ")"), )?; if let Some(ret_transform) = maybe_ret_transform { @@ -345,7 +345,7 @@ impl<'a> Codegen<'a> { Ok(()) }, 4, - ('(', ')'), + ('(', ")"), )?; writeln!(writer, "\n }}")?; @@ -353,7 +353,7 @@ impl<'a> Codegen<'a> { Ok(()) }, 0, - ('{', '}'), + ('{', "}\n\n"), )?; } } diff --git a/example/bench.js b/example/bench.js index f971170..ed45ae9 100644 --- a/example/bench.js +++ b/example/bench.js @@ -1,6 +1,6 @@ import { add, bytelen } from "./bindings/bindings.ts"; -// Optimized fast paths: Deno.bench("add", () => add(1, 2)); + const b = new Uint8Array([1, 2, 3, 4]); Deno.bench("bytelen", () => bytelen(b)); diff --git a/example/bindings/bindings.ts b/example/bindings/bindings.ts index 4dc8cb4..ae9e230 100644 --- a/example/bindings/bindings.ts +++ b/example/bindings/bindings.ts @@ -14,11 +14,25 @@ const { symbols } = dlopen('./target/debug/libdeno_bindgen_test.dylib', { result: 'i32', nonblocking: false }, - add2: { + __Input_new: { parameters: [ 'i32', 'i32', ], + result: 'pointer', + nonblocking: false + }, + __Input_dealloc: { + parameters: [ + 'pointer', + ], + result: 'void', + nonblocking: false + }, + add2: { + parameters: [ + 'pointer', + ], result: 'i32', nonblocking: false }, @@ -108,14 +122,59 @@ export function add( ) } -export function add2( +function __Input_new( arg0: number, arg1: number, -): number { - return symbols.add2( +): Input { + const ret = symbols.__Input_new( arg0, arg1, ) + return Input.__constructor(ret); +} + +function __Input_dealloc( + arg0: Deno.PointerObject | null, +): void { + return symbols.__Input_dealloc( + arg0, + ) +} + +export class Input { + ptr: Deno.PointerObject | null = null; + + static __constructor(ptr: Deno.PointerObject | null) { + const self = Object.create(Input.prototype); + self.ptr = ptr; + return self; + } + + [Symbol.dispose]() { + this.dealloc(); + this.ptr = null; + } + + constructor(arg0: number, arg1: number) { + return __Input_new( + arg0, + arg1, + ) + } + + dealloc(): void { + return __Input_dealloc( + this.ptr, + ) + } +} + +export function add2( + arg0: Input, +): number { + return symbols.add2( + arg0.ptr, + ) } export function bytelen( @@ -238,4 +297,5 @@ export class Foo { this.ptr, ) } -} \ No newline at end of file +} + diff --git a/example/bindings_test.ts b/example/bindings_test.ts index 4ba630e..2f6993f 100644 --- a/example/bindings_test.ts +++ b/example/bindings_test.ts @@ -1,22 +1,28 @@ import { add, add2, - bytelen, buf_mut, + bytelen, cstr, - strlen, - non_blocking, - make_foo, + Foo, inc_foo, - Foo, + Input, + make_foo, + non_blocking, + strlen, } from "./bindings/bindings.ts"; -import { assert, assertEquals } from "https://deno.land/std@0.178.0/testing/asserts.ts"; +import { + assert, + assertEquals, +} from "https://deno.land/std@0.178.0/testing/asserts.ts"; Deno.test({ name: "add#test", fn: () => { assertEquals(add(1, 2), 3); - assertEquals(add2(-1, 1), 0); + + using input = new Input(-1, 1); + assertEquals(add2(input), 0); }, }); @@ -33,7 +39,7 @@ Deno.test({ const buf = new Uint8Array(1); buf_mut(buf); assertEquals(buf[0], 99); - } + }, }); Deno.test({ @@ -68,15 +74,15 @@ Deno.test({ assert(foo instanceof Foo); assertEquals(foo.bar(1), 43); }, -}) +}); Deno.test({ name: "Foo#constructor", fn() { const foo = new Foo(42); assertEquals(foo.bar(1), 43); - } -}) + }, +}); Deno.test({ name: "Foo#using", @@ -84,18 +90,18 @@ Deno.test({ using foo = new Foo(1); foo.inc(); assertEquals(foo.bar(1), 3); - } + }, }); Deno.test({ name: "Foo#using explicit", fn() { using foo = make_foo(); - + // Multiple dipose calls are nop. foo[Symbol.dispose](); foo[Symbol.dispose](); - } + }, }); Deno.test({ @@ -104,5 +110,5 @@ Deno.test({ using foo = new Foo(22); inc_foo(foo); assertEquals(foo.bar(0), 23); - } -}) \ No newline at end of file + }, +}); diff --git a/example/src/lib.rs b/example/src/lib.rs index a334370..6fbc208 100644 --- a/example/src/lib.rs +++ b/example/src/lib.rs @@ -1,14 +1,27 @@ use deno_bindgen::deno_bindgen; -// Test "primitives" #[deno_bindgen] fn add(a: i32, b: i32) -> i32 { a + b } #[deno_bindgen] -fn add2(a: i32, b: i32) -> i32 { - a + b +struct Input { + a: i32, + b: i32, +} + +#[deno_bindgen] +impl Input { + #[constructor] + fn new(a: i32, b: i32) -> Input { + Input { a, b } + } +} + +#[deno_bindgen] +fn add2(input: &Input) -> i32 { + input.a + input.b } #[deno_bindgen]