From e5f2386f3bb5aa8278607146bf01b893a82ab41a Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Mon, 6 Nov 2023 11:52:33 +0530 Subject: [PATCH 01/15] experiment: rewrite macro state --- Cargo.toml | 1 + README.md | 3 +- cli2.ts | 74 +++++++++ deno.lock | 53 +++++++ deno_bindgen_macro/src/attrs.rs | 6 +- deno_bindgen_macro/src/derive_fn.rs | 15 +- deno_bindgen_macro/src/derive_struct.rs | 18 +-- deno_bindgen_macro/src/docs.rs | 4 +- deno_bindgen_macro/src/lib.rs | 55 ++++--- deno_bindgen_macro/src/meta.rs | 52 +++++- example/bindings/bindings.ts | 200 +++++++++++++----------- 11 files changed, 337 insertions(+), 144 deletions(-) create mode 100644 cli2.ts create mode 100644 deno.lock diff --git a/Cargo.toml b/Cargo.toml index 1646b64..f3f6720 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "2" members = [ "deno_bindgen_macro", "deno_bindgen", diff --git a/README.md b/README.md index 3f4dd65..3b490cc 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,8 @@ in Rust. ### QuickStart -Annotate on top of Rust `fn`, `struct` and `enum` to make them available to Deno. +Annotate on top of Rust `fn`, `struct` and `enum` to make them available to +Deno. ```rust // add.rs diff --git a/cli2.ts b/cli2.ts new file mode 100644 index 0000000..878fbe8 --- /dev/null +++ b/cli2.ts @@ -0,0 +1,74 @@ +import { parse } from "https://deno.land/std@0.132.0/flags/mod.ts"; + +const flags = parse(Deno.args, { "--": true }); +const release = !!flags.release; + +function build() { + const args = ["build"]; + if (release) args.push("--release"); + args.push(...flags["--"]); + const proc = new Deno.Command("cargo", { args, stderr: "inherit" }); + return proc.outputSync(); +} + +function nm() { + // Run `nm` to get the symbols from the compiled library. + const args = [ + "nm", + "--format=bsd", + "--defined-only", + "target/debug/libdeno_bindgen_test.dylib", + ]; + if (release) args.push("--demangle"); + const proc = new Deno.Command("nm", { args, stdout: "piped" }); + const output = proc.outputSync(); + const stdout = new TextDecoder().decode(output.stdout); + const symbols = stdout.split("\n").filter((s) => s.length > 0).slice(1); + + const symbols2 = symbols.map((s) => { + const [addr, ty, name] = s.split(" "); + return { addr, ty, name }; + }).filter((s) => s.name.startsWith("___de90_")); + return symbols2; +} + +function run_init(symbols) { + const symbols_obj = {}; + symbols.forEach(({ name }) => { + symbols_obj[name.slice(1)] = { + parameters: ["buffer", "buffer"], + result: "pointer", + }; + }); + + const lib = Deno.dlopen("./target/debug/libdeno_bindgen_test.dylib", symbols_obj); + const params = new Uint8Array(20); + const result = new Uint8Array(1); + const processed = []; + for (const fn in lib.symbols) { + const name_ptr = lib.symbols[fn](params, result); + const name = Deno.UnsafePointerView.getCString(name_ptr); + processed.push({ name, params: [...params].map(p => C_TYPE[p]), result: C_TYPE[result[0]] }) + } + return processed; +} + +const C_TYPE = ["A", "B"]; + +function codegen(symbols) { + let code = ''; + for (let i = 0; i < symbols.length; i++) { + const { name, params, result } = symbols[i]; + const params_str = params.map((p, j) => `p${j}: ${p}`).join(', '); + const params_idents = params.map((p, j) => `p${j}`).join(', '); + code += `export function ${name}(${params_str}): ${result} { return lib.${name}(${params_idents}); }\n`; + } + + console.log(code) +} + +build(); +const symbols = nm(); +const processed = run_init(symbols); +codegen(processed); + diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..bb75278 --- /dev/null +++ b/deno.lock @@ -0,0 +1,53 @@ +{ + "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_macro/src/attrs.rs b/deno_bindgen_macro/src/attrs.rs index ba30b5a..a19028b 100644 --- a/deno_bindgen_macro/src/attrs.rs +++ b/deno_bindgen_macro/src/attrs.rs @@ -1,11 +1,7 @@ // Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. -use syn::Attribute; -use syn::Lit; -use syn::Meta; -use syn::NestedMeta; - use inflector::Inflector; +use syn::{Attribute, Lit, Meta, NestedMeta}; #[derive(Debug)] pub enum SerdeAttr { diff --git a/deno_bindgen_macro/src/derive_fn.rs b/deno_bindgen_macro/src/derive_fn.rs index a06242c..71dc78b 100644 --- a/deno_bindgen_macro/src/derive_fn.rs +++ b/deno_bindgen_macro/src/derive_fn.rs @@ -1,15 +1,10 @@ // Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. -use crate::meta::Glue; -use crate::meta::Symbol; -use crate::meta::Type; -use syn::AttributeArgs; -use syn::FnArg; -use syn::ItemFn; -use syn::Meta; -use syn::NestedMeta; -use syn::PathArguments; -use syn::ReturnType; +use syn::{ + AttributeArgs, FnArg, ItemFn, Meta, NestedMeta, PathArguments, ReturnType, +}; + +use crate::meta::{Glue, Symbol, Type}; pub fn process_function( function: ItemFn, diff --git a/deno_bindgen_macro/src/derive_struct.rs b/deno_bindgen_macro/src/derive_struct.rs index bba442b..2d729e9 100644 --- a/deno_bindgen_macro/src/derive_struct.rs +++ b/deno_bindgen_macro/src/derive_struct.rs @@ -1,16 +1,14 @@ // Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. -use crate::attrs::get_serde_attrs; -use crate::attrs::SerdeAttr; -use crate::docs::get_docs; -use crate::meta::Glue; - use std::collections::HashMap; -use syn::ext::IdentExt; -use syn::Data; -use syn::DataStruct; -use syn::DeriveInput; -use syn::Fields; + +use syn::{ext::IdentExt, Data, DataStruct, DeriveInput, Fields}; + +use crate::{ + attrs::{get_serde_attrs, SerdeAttr}, + docs::get_docs, + meta::Glue, +}; macro_rules! variant_instance { ( $variant:path, $iterator:expr ) => { diff --git a/deno_bindgen_macro/src/docs.rs b/deno_bindgen_macro/src/docs.rs index 4670cbb..938b01a 100644 --- a/deno_bindgen_macro/src/docs.rs +++ b/deno_bindgen_macro/src/docs.rs @@ -1,8 +1,6 @@ // Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. -use syn::Attribute; -use syn::Lit; -use syn::Meta; +use syn::{Attribute, Lit, Meta}; pub fn get_docs(attrs: &Vec) -> String { let mut doc: Vec = vec![]; diff --git a/deno_bindgen_macro/src/lib.rs b/deno_bindgen_macro/src/lib.rs index 39cbbac..5871289 100644 --- a/deno_bindgen_macro/src/lib.rs +++ b/deno_bindgen_macro/src/lib.rs @@ -1,16 +1,15 @@ // Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. -use proc_macro::TokenStream; -use quote::format_ident; -use quote::quote; -use std::env; -use std::fs::OpenOptions; -use std::io::Read; -use std::io::Write; -use std::path::Path; -use syn::parse_macro_input; -use syn::parse_quote; -use syn::ItemFn; +use std::{ + env, + fs::OpenOptions, + io::{Read, Write}, + path::Path, +}; + +use proc_macro::{TokenStream}; +use quote::{format_ident,quote}; +use syn::{parse_macro_input, parse_quote, ItemFn}; mod attrs; mod derive_fn; @@ -18,10 +17,11 @@ mod derive_struct; mod docs; mod meta; -use crate::derive_fn::process_function; -use crate::derive_struct::process_struct; -use crate::meta::Glue; -use crate::meta::Type; +use crate::{ + derive_fn::process_function, + derive_struct::process_struct, + meta::{Glue, Type}, +}; #[cfg(target_endian = "little")] const ENDIANNESS: bool = true; @@ -72,7 +72,7 @@ pub fn deno_bindgen(attr: TokenStream, input: TokenStream) -> TokenStream { let mut input_idents = vec![]; let mut c_index = 0; - for parameter in symbol.parameters { + for parameter in &symbol.parameters { match parameter { Type::StructEnum { .. } => { let ident = format_ident!("arg{}", c_index.to_string()); @@ -136,7 +136,7 @@ pub fn deno_bindgen(attr: TokenStream, input: TokenStream) -> TokenStream { // TODO _ => { let ident = format_ident!("arg{}", c_index.to_string()); - let ty = syn::Type::from(parameter); + let ty = syn::Type::from(parameter.clone()); params.push(quote! { #ident: #ty }); input_idents.push(ident); } @@ -189,7 +189,7 @@ pub fn deno_bindgen(attr: TokenStream, input: TokenStream) -> TokenStream { (ty, transformer) } Type::Ptr => (parse_quote! { *const u8 }, quote! { result }), - _ => (syn::Type::from(symbol.result), quote! { result }), + _ => (syn::Type::from(symbol.result.clone()), quote! { result }), }; let name = &func.sig.ident; @@ -205,7 +205,9 @@ pub fn deno_bindgen(attr: TokenStream, input: TokenStream) -> TokenStream { metafile .write_all(&serde_json::to_vec(&metadata).unwrap()) .unwrap(); - + let xname = format_ident!("__de90_{}", name.to_string()); + let xparams = symbol.parameters.iter().map(|p| meta::CType::from(p) as u8).map(|p| quote!(#p)).collect::>(); + let xresult = meta::CType::from(&symbol.result) as u8; TokenStream::from(quote! { #[no_mangle] pub extern "C" fn #name <'sym> (#(#params,) *) -> #result { @@ -214,6 +216,21 @@ pub fn deno_bindgen(attr: TokenStream, input: TokenStream) -> TokenStream { let result = __inner_impl(#(#input_idents, ) *); #transformer } + + #[no_mangle] + pub unsafe extern "C" fn #xname ( + params: *mut u8, + result: *mut u8, + ) -> *const u8 { + let mut idx = 0; + let params = ::std::slice::from_raw_parts_mut(params, 200); + #( + params[idx] = #xparams; + idx += 1; + )*; + *result = #xresult; + concat!(stringify!(#name), "\0").as_ptr() + } }) } Err(_) => { diff --git a/deno_bindgen_macro/src/meta.rs b/deno_bindgen_macro/src/meta.rs index 247cb66..f7c6ed7 100644 --- a/deno_bindgen_macro/src/meta.rs +++ b/deno_bindgen_macro/src/meta.rs @@ -1,8 +1,8 @@ // Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. -use serde::Deserialize; -use serde::Serialize; use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; use syn::parse_quote; #[derive(Serialize, Deserialize, Clone)] @@ -38,6 +38,54 @@ pub enum Type { }, } +#[repr(u8)] +pub enum CType { + /// Straight forward types supported + /// by Deno's FFI + I8, + U8, + I16, + U16, + I32, + U32, + I64, + U64, + F32, + F64, + Usize, + Isize, + Void, + Buffer, + BufferMut, + Str, + Ptr, +} + +impl From<&Type> for CType { + fn from(ty: &Type) -> Self { + match ty { + Type::I8 => CType::I8, + Type::U8 => CType::U8, + Type::I16 => CType::I16, + Type::U16 => CType::U16, + Type::I32 => CType::I32, + Type::U32 => CType::U32, + Type::I64 => CType::I64, + Type::U64 => CType::U64, + Type::F32 => CType::F32, + Type::F64 => CType::F64, + Type::Usize => CType::Usize, + Type::Isize => CType::Isize, + Type::Void => CType::Void, + Type::Buffer => CType::Buffer, + Type::BufferMut => CType::BufferMut, + Type::Str => CType::Str, + Type::Ptr => CType::Ptr, + Type::StructEnum { .. } => CType::Ptr, + } + } +} + impl From for syn::Type { fn from(ty: Type) -> Self { match ty { diff --git a/example/bindings/bindings.ts b/example/bindings/bindings.ts index e33007b..68516c2 100644 --- a/example/bindings/bindings.ts +++ b/example/bindings/bindings.ts @@ -19,106 +19,118 @@ function readPointer(v: any): Uint8Array { return buf } -const url = new URL("../target/release", import.meta.url) +const url = new URL("../target/debug", import.meta.url) -import { dlopen, FetchOptions } from "https://deno.land/x/plug@1.0.1/mod.ts" -let uri = url.toString() +let uri = url.pathname if (!uri.endsWith("/")) uri += "/" -let darwin: string | { aarch64: string; x86_64: string } = uri +// https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibrarya#parameters +if (Deno.build.os === "windows") { + uri = uri.replace(/\//g, "\\") + // Remove leading slash + if (uri.startsWith("\\")) { + uri = uri.slice(1) + } +} -const opts: FetchOptions = { - name: "deno_bindgen_test", - url: { - darwin, - windows: uri, - linux: uri, - }, - suffixes: { - darwin: { - aarch64: "_arm64", +const { symbols } = Deno.dlopen( + { + darwin: uri + "libdeno_bindgen_test.dylib", + windows: uri + "deno_bindgen_test.dll", + linux: uri + "libdeno_bindgen_test.so", + freebsd: uri + "libdeno_bindgen_test.so", + netbsd: uri + "libdeno_bindgen_test.so", + aix: uri + "libdeno_bindgen_test.so", + solaris: uri + "libdeno_bindgen_test.so", + illumos: uri + "libdeno_bindgen_test.so", + }[Deno.build.os], + { + add: { parameters: ["i32", "i32"], result: "i32", nonblocking: false }, + add2: { + parameters: ["buffer", "usize"], + result: "i32", + nonblocking: false, + }, + add3: { parameters: ["f32", "f32"], result: "f32", nonblocking: false }, + add4: { parameters: ["f64", "f64"], result: "f64", nonblocking: false }, + add5: { + parameters: ["buffer", "usize", "buffer", "usize"], + result: "buffer", + nonblocking: false, + }, + add6: { + parameters: ["buffer", "usize", "buffer", "usize"], + result: "buffer", + nonblocking: false, + }, + sleep: { parameters: ["u64"], result: "void", nonblocking: true }, + test_buf: { + parameters: ["buffer", "usize"], + result: "u8", + nonblocking: false, + }, + test_buffer_return: { + parameters: ["buffer", "usize"], + result: "buffer", + nonblocking: false, + }, + test_buffer_return_async: { + parameters: ["buffer", "usize"], + result: "buffer", + nonblocking: true, + }, + test_hashmap: { parameters: [], result: "buffer", nonblocking: false }, + test_lifetime: { + parameters: ["buffer", "usize"], + result: "usize", + nonblocking: false, + }, + test_manual_ptr: { parameters: [], result: "buffer", nonblocking: false }, + test_manual_ptr_async: { + parameters: [], + result: "buffer", + nonblocking: true, + }, + test_mixed: { + parameters: ["isize", "buffer", "usize"], + result: "i32", + nonblocking: false, + }, + test_mixed_order: { + parameters: ["i32", "buffer", "usize", "i32"], + result: "i32", + nonblocking: false, + }, + test_mut_buf: { + parameters: ["buffer", "usize"], + result: "void", + nonblocking: false, + }, + test_output: { parameters: [], result: "buffer", nonblocking: false }, + test_output_async: { parameters: [], result: "buffer", nonblocking: true }, + test_reserved_field: { + parameters: [], + result: "buffer", + nonblocking: false, + }, + test_serde: { + parameters: ["buffer", "usize"], + result: "u8", + nonblocking: false, + }, + test_str: { + parameters: ["buffer", "usize"], + result: "void", + nonblocking: false, + }, + test_str_ret: { parameters: [], result: "buffer", nonblocking: false }, + test_tag_and_content: { + parameters: ["buffer", "usize"], + result: "i32", + nonblocking: false, }, }, - cache: "use", -} -const { symbols } = await dlopen(opts, { - add: { parameters: ["i32", "i32"], result: "i32", nonblocking: false }, - add2: { parameters: ["buffer", "usize"], result: "i32", nonblocking: false }, - add3: { parameters: ["f32", "f32"], result: "f32", nonblocking: false }, - add4: { parameters: ["f64", "f64"], result: "f64", nonblocking: false }, - add5: { - parameters: ["buffer", "usize", "buffer", "usize"], - result: "buffer", - nonblocking: false, - }, - add6: { - parameters: ["buffer", "usize", "buffer", "usize"], - result: "buffer", - nonblocking: false, - }, - sleep: { parameters: ["u64"], result: "void", nonblocking: true }, - test_buf: { - parameters: ["buffer", "usize"], - result: "u8", - nonblocking: false, - }, - test_buffer_return: { - parameters: ["buffer", "usize"], - result: "buffer", - nonblocking: false, - }, - test_buffer_return_async: { - parameters: ["buffer", "usize"], - result: "buffer", - nonblocking: true, - }, - test_hashmap: { parameters: [], result: "buffer", nonblocking: false }, - test_lifetime: { - parameters: ["buffer", "usize"], - result: "usize", - nonblocking: false, - }, - test_manual_ptr: { parameters: [], result: "buffer", nonblocking: false }, - test_manual_ptr_async: { - parameters: [], - result: "buffer", - nonblocking: true, - }, - test_mixed: { - parameters: ["isize", "buffer", "usize"], - result: "i32", - nonblocking: false, - }, - test_mixed_order: { - parameters: ["i32", "buffer", "usize", "i32"], - result: "i32", - nonblocking: false, - }, - test_mut_buf: { - parameters: ["buffer", "usize"], - result: "void", - nonblocking: false, - }, - test_output: { parameters: [], result: "buffer", nonblocking: false }, - test_output_async: { parameters: [], result: "buffer", nonblocking: true }, - test_reserved_field: { parameters: [], result: "buffer", nonblocking: false }, - test_serde: { - parameters: ["buffer", "usize"], - result: "u8", - nonblocking: false, - }, - test_str: { - parameters: ["buffer", "usize"], - result: "void", - nonblocking: false, - }, - test_str_ret: { parameters: [], result: "buffer", nonblocking: false }, - test_tag_and_content: { - parameters: ["buffer", "usize"], - result: "i32", - nonblocking: false, - }, -}) +) /** * Doc comment for `Input` struct. * ...testing multiline From b6cabc938ae57fde4e53f4c7af7d30144abb8a6a Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Tue, 7 Nov 2023 19:28:39 +0530 Subject: [PATCH 02/15] basic support --- Cargo.lock | 275 +++++++++++++++- Cargo.toml | 2 + README.md | 189 +---------- deno_bindgen/Cargo.toml | 2 + deno_bindgen/lib.rs | 62 +--- deno_bindgen_cli/Cargo.toml | 21 ++ deno_bindgen_cli/cargo.rs | 39 +++ deno_bindgen_cli/dlfcn.rs | 27 ++ deno_bindgen_cli/main.rs | 32 ++ deno_bindgen_ir/Cargo.toml | 20 ++ deno_bindgen_ir/codegen/deno.rs | 226 +++++++++++++ deno_bindgen_ir/codegen/mod.rs | 36 +++ deno_bindgen_ir/lib.rs | 164 ++++++++++ deno_bindgen_macro/Cargo.toml | 7 +- deno_bindgen_macro/src/attrs.rs | 101 ------ deno_bindgen_macro/src/derive_fn.rs | 203 ------------ deno_bindgen_macro/src/derive_struct.rs | 272 ---------------- deno_bindgen_macro/src/docs.rs | 25 -- deno_bindgen_macro/src/fn_.rs | 177 ++++++++++ deno_bindgen_macro/src/lib.rs | 267 ++------------- deno_bindgen_macro/src/meta.rs | 126 -------- deno_bindgen_macro/src/util.rs | 20 ++ example/Cargo.toml | 1 + example/bench.js | 10 +- example/bindings/bindings.ts | 412 ++++++------------------ example/bindings_test.ts | 242 ++------------ example/src/lib.rs | 225 +------------ 27 files changed, 1218 insertions(+), 1965 deletions(-) create mode 100644 deno_bindgen_cli/Cargo.toml create mode 100644 deno_bindgen_cli/cargo.rs create mode 100644 deno_bindgen_cli/dlfcn.rs create mode 100644 deno_bindgen_cli/main.rs create mode 100644 deno_bindgen_ir/Cargo.toml create mode 100644 deno_bindgen_ir/codegen/deno.rs create mode 100644 deno_bindgen_ir/codegen/mod.rs create mode 100644 deno_bindgen_ir/lib.rs delete mode 100644 deno_bindgen_macro/src/attrs.rs delete mode 100644 deno_bindgen_macro/src/derive_fn.rs delete mode 100644 deno_bindgen_macro/src/derive_struct.rs delete mode 100644 deno_bindgen_macro/src/docs.rs create mode 100644 deno_bindgen_macro/src/fn_.rs delete mode 100644 deno_bindgen_macro/src/meta.rs create mode 100644 deno_bindgen_macro/src/util.rs diff --git a/Cargo.lock b/Cargo.lock index 9dd1519..030f542 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,25 +21,128 @@ dependencies = [ "memchr", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + [[package]] name = "deno_bindgen" version = "0.8.1" dependencies = [ + "deno_bindgen_ir", "deno_bindgen_macro", + "linkme", "serde", "serde_json", ] +[[package]] +name = "deno_bindgen_cli" +version = "0.1.0" +dependencies = [ + "deno_bindgen_ir", + "dlopen2", + "structopt", +] + +[[package]] +name = "deno_bindgen_ir" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "deno_bindgen_macro" version = "0.8.1" dependencies = [ "Inflector", + "deno_bindgen_ir", "proc-macro2", "quote", "serde", "serde_json", - "syn", + "syn 2.0.39", +] + +[[package]] +name = "dlopen2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bc2c7ed06fd72a8513ded8d0d2f6fd2655a85d6885c48cae8625d80faf28c03" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", ] [[package]] @@ -54,26 +157,82 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "libc" +version = "0.2.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "linkme" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ed2ee9464ff9707af8e9ad834cffa4802f072caad90639c583dd3c62e6e608" +dependencies = [ + "linkme-impl", +] + +[[package]] +name = "linkme-impl" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba125974b109d512fccbc6c0244e7580143e460895dfd6ea7f8bbb692fd94396" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "memchr" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.74", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" -version = "1.0.28" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] name = "quote" -version = "1.0.9" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -118,7 +277,7 @@ checksum = "a024926d3432516606328597e0f224a51355a493b49fdd67e9209187cbe55ecc" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.74", ] [[package]] @@ -132,6 +291,36 @@ dependencies = [ "serde", ] +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "structopt" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.74", +] + [[package]] name = "syn" version = "1.0.74" @@ -143,8 +332,80 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "syn" +version = "2.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index f3f6720..a7fd056 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,5 +3,7 @@ resolver = "2" members = [ "deno_bindgen_macro", "deno_bindgen", + "deno_bindgen_ir", + "deno_bindgen_cli" ] exclude = ["example/"] \ No newline at end of file diff --git a/README.md b/README.md index 3b490cc..425ac34 100644 --- a/README.md +++ b/README.md @@ -5,196 +5,17 @@ This tool aims to simplify glue code generation for Deno FFI libraries written in Rust. -### QuickStart - -Annotate on top of Rust `fn`, `struct` and `enum` to make them available to -Deno. - ```rust -// add.rs use deno_bindgen::deno_bindgen; #[deno_bindgen] -pub struct Input { - a: i32, - b: i32, -} - -#[deno_bindgen] -fn add(input: Input) -> i32 { - input.a + input.b +fn add(a: u32, b: u32) -> u32 { + a + b } ``` -Invoke the CLI to compile and generate bindings: - -```shell -$ deno_bindgen -``` - -And finally import the generated bindings in your JS - ```typescript -// add.ts -import { add } from "./bindings/bindings.ts"; - -add({ a: 1, b: 2 }); // 3 -``` - -### Installation - -- Install the `deno_bindgen` CLI with Deno. - -```shell -deno install -Afrq -n deno_bindgen https://deno.land/x/deno_bindgen/cli.ts -``` - -Add the following dependencies to your crate. - -```toml -# Cargo.toml -[dependencies] -deno_bindgen = "0.8.1" -serde = { version = "1", features = ["derive"] } -``` - -Change your `crate-type` to `cdylib` and set your package name as well. - -```toml -[lib] -name = "___" -crate-type = ["cdylib"] -``` - -### Bindings - -Put `#[deno_bindgen]` on top of a "serde-deriavable" struct, enum or fn. - -#### `struct` (named fields) - -These transform into Typescript `type`s. - -```rust -// lib.rs -#[deno_bindgen] -pub struct A { - b: Vec>, -} -``` - -becomes: - -```typescript -// bindings/bindings.ts -export type A = { - b: Array>; -}; -``` - -#### `enum` - -Enums become `type` unions in Typescript. - -```rust -#[deno_bindgen] -pub enum Event { - Quit, - MouseMove { - x: i32, - y: i32, - } -} -``` - -becomes: - -```typescript -export type Enum = - | "quit" - | { - mouse_move: { - x: number; - y: number; - }; - }; -``` - -### `fn` - -Functions are exposed through the FFI boundaries. - -```rust -#[deno_bindgen] -fn greet(name: &str) { - println!("Hello, {}!", name); -} -``` - -becomes: - -```typescript -export function greet(name: string) { - // ... glue code for calling the - // symbol. -} -``` - -Notes - -- Use `#[deno_bindgen(non_blocking)]` attribute to call symbol without blocking - JS event loop. Exposed as an async funtion from bindings. - -- Rust doc comments transform to JS docs. - ```rust - #[deno_bindgen] - pub struct Me { - /// My name... - /// ...it is - name: String, - } - ``` - becomes: - ```typescript - export type Me = { - /** - * My name... - * ...it is - */ - name: string; - }; - ``` - -- If the argument type of Rust is f32, the calculation result may be different.\ - Number in Java Script is float64, when data is passed to Rust, it becomes - float32, so the number may change.\ - e.g: `1.3 + 1.5` will be `2.799999952316284` - -### CLI - -The `deno_bindgen` CLI tool provides the following flags: - -- Pass `--release` to create a release build. - -- `--release=URL` will load library artifacts from a remote location. This is - useful for updating bindings for end users after a release: - - ```shell - deno_bindgen --release=https://github.com/littledivy/deno_sdl2/releases/download/0.2-alpha.1 - ``` - - Under the hood this uses [`x/plug`](https://deno.land/x/plug) to fetch and - cache the artifact. - - Artifacts must be following the remote asset naming scheme, as follows: - - | OS | Arch | Naming | - | ------- | ------ | ------------------- | - | Windows | x86_64 | name.dll | - | Linux | x86_64 | libname.so | - | MacOS | x86_64 | libname.dylib | - | MacOS | arm64 | libname_arm64.dylib | +import { add } from "@ffi/example"; -- Flags after `--` will be passed to `cargo build`. Example: - ```shell - deno_bindgen -- --features "cool_stuff" - ``` +add(1, 2); +``` \ No newline at end of file diff --git a/deno_bindgen/Cargo.toml b/deno_bindgen/Cargo.toml index a58989c..44a2fde 100644 --- a/deno_bindgen/Cargo.toml +++ b/deno_bindgen/Cargo.toml @@ -16,5 +16,7 @@ path = "./lib.rs" [dependencies] deno_bindgen_macro = { path = "../deno_bindgen_macro", version = "0.8.1" } +deno_bindgen_ir = { path = "../deno_bindgen_ir", version = "0.1.0" } serde = { version = "1", features = ["derive"] } serde_json = "1" +linkme = "0.3" \ No newline at end of file diff --git a/deno_bindgen/lib.rs b/deno_bindgen/lib.rs index d181f8f..523d2ef 100644 --- a/deno_bindgen/lib.rs +++ b/deno_bindgen/lib.rs @@ -1,53 +1,15 @@ // Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. - -//! ## deno_bindgen -//! This tool aims to simply types & glue code generation for FFI -//! libraries written in Rust. -//! -//! ### Usage -//! Add `serde` and `deno_bindgen` dependency to your crate. -//! -//! ``` -//! use deno_bindgen::deno_bindgen; -//! -//! #[deno_bindgen] -//! pub struct Input { -//! /// Doc comments are transformed into -//! /// jsdocs. -//! a: Vec>, -//! } -//! -//! #[deno_bindgen(non_blocking)] -//! pub fn say_hello(message: &str) { -//! println!("{}", message); -//! } -//! ``` -//! -//! Generated bindings will look like this: -//! ``` -//! // bindings/binding.ts -//! -//! // ... -//! -//! type Input = { -//! /** -//! * Doc comments are transformed into -//! * jsdocs. -//! **/ -//! a: Array>; -//! }; -//! -//! export async function say_hello(message: string) { -//! // ... -//! } -//! ``` -//! These bindings contain nessecary code to open the shared library, -//! define symbols and expose type definitions. -//! They can be simply imported into Deno code: -//! ``` -//! import { say_hello } from "./bindings/bindings.ts"; -//! await say_hello("Demn!") -//! ``` -//! pub use ::serde_json; +use deno_bindgen_ir::codegen::Options; +pub use deno_bindgen_ir::*; pub use deno_bindgen_macro::deno_bindgen; +pub use linkme; +use linkme::distributed_slice; + +#[distributed_slice] +pub static INVENTORY: [Symbol]; + +#[no_mangle] +fn init_deno_bindgen(opt: Options) { + deno_bindgen_ir::codegen::generate(&INVENTORY, opt).unwrap(); +} diff --git a/deno_bindgen_cli/Cargo.toml b/deno_bindgen_cli/Cargo.toml new file mode 100644 index 0000000..5fb072d --- /dev/null +++ b/deno_bindgen_cli/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "deno_bindgen_cli" +version = "0.1.0" +description = "This tool aims to simplify glue code generation for Deno FFI libraries written in Rust." +documentation = "https://docs.rs/deno_bindgen" +homepage = "https://github.com/denoland/deno_bindgen" +repository = "https://github.com/denoland/deno_bindgen" +keywords = ["deno", "ffi", "bindgen", "bindings", "macro"] +categories = ["development-tools::ffi", "development-tools"] +readme = "../README.md" +license = "MIT" +edition = "2021" + +[[bin]] +name = "deno_bindgen" +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 diff --git a/deno_bindgen_cli/cargo.rs b/deno_bindgen_cli/cargo.rs new file mode 100644 index 0000000..6484e65 --- /dev/null +++ b/deno_bindgen_cli/cargo.rs @@ -0,0 +1,39 @@ +use std::{io::Result, path::Path, process::Command}; + +#[derive(Default)] +pub struct Build { + release: bool, +} + +impl Build { + pub fn new() -> Self { + Self::default() + } + + pub fn release(mut self, release: bool) -> Self { + self.release = release; + self + } + + pub fn build(self, path: &Path) -> Result<()> { + let mut cmd = Command::new("cargo"); + cmd.current_dir(path).arg("build").arg("--lib"); + + if self.release { + cmd.arg("--release"); + } + + let status = cmd.status()?; + + if status.success() { + Ok(()) + } else { + println!( + "failed to execute `cargo`: exited with {}\n full command: {:?}", + status, cmd, + ); + + std::process::exit(1); + } + } +} diff --git a/deno_bindgen_cli/dlfcn.rs b/deno_bindgen_cli/dlfcn.rs new file mode 100644 index 0000000..966153a --- /dev/null +++ b/deno_bindgen_cli/dlfcn.rs @@ -0,0 +1,27 @@ +use std::path::{Path, PathBuf}; + +use dlopen2::wrapper::{Container, WrapperApi}; + +#[derive(WrapperApi)] +struct Api { + init_deno_bindgen: unsafe fn(opt: deno_bindgen_ir::codegen::Options), +} + +pub unsafe fn load_and_init( + path: &Path, + out: Option, +) -> std::io::Result<()> { + let cont: Container = Container::load(path).map_err(|e| { + std::io::Error::new( + std::io::ErrorKind::Other, + format!("failed to load library: {}", e), + ) + })?; + + cont.init_deno_bindgen(deno_bindgen_ir::codegen::Options { + target: deno_bindgen_ir::codegen::Target::Deno, + out, + }); + + Ok(()) +} diff --git a/deno_bindgen_cli/main.rs b/deno_bindgen_cli/main.rs new file mode 100644 index 0000000..6bb0955 --- /dev/null +++ b/deno_bindgen_cli/main.rs @@ -0,0 +1,32 @@ +use std::path::PathBuf; + +use structopt::StructOpt; + +mod cargo; +mod dlfcn; + +#[derive(Debug, StructOpt)] +#[structopt(name = "deno_bindgen_cli", about = "A CLI for deno_bindgen")] +struct Opt { + #[structopt(short, long)] + /// Build in release mode + release: bool, + + #[structopt(short, long)] + out: Option, +} + +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)?; + + unsafe { + dlfcn::load_and_init( + &cwd.join("target/debug/libdeno_bindgen_test.dylib"), + opt.out, + )? + }; + Ok(()) +} diff --git a/deno_bindgen_ir/Cargo.toml b/deno_bindgen_ir/Cargo.toml new file mode 100644 index 0000000..f32a7e2 --- /dev/null +++ b/deno_bindgen_ir/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "deno_bindgen_ir" +version = "0.1.0" +description = "This tool aims to simplify glue code generation for Deno FFI libraries written in Rust." +documentation = "https://docs.rs/deno_bindgen" +homepage = "https://github.com/denoland/deno_bindgen" +repository = "https://github.com/denoland/deno_bindgen" +keywords = ["deno", "ffi", "bindgen", "bindings", "macro"] +categories = ["development-tools::ffi", "development-tools"] +readme = "../README.md" +license = "MIT" +edition = "2021" + +[lib] +path = "./lib.rs" + +[dependencies] +quote = "1.0" +proc-macro2 = "1.0" +syn = { version = "2.0", features = ["full", "extra-traits"] } \ No newline at end of file diff --git a/deno_bindgen_ir/codegen/deno.rs b/deno_bindgen_ir/codegen/deno.rs new file mode 100644 index 0000000..64b5cfb --- /dev/null +++ b/deno_bindgen_ir/codegen/deno.rs @@ -0,0 +1,226 @@ +use std::{ + borrow::Cow, + io::{Result, Write}, +}; + +use super::Generator; +use crate::{Symbol, Type}; + +struct TypeScriptType<'a>(&'a str); + +impl std::fmt::Display for TypeScriptType<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.0)?; + Ok(()) + } +} + +impl TypeScriptType<'_> { + fn into_raw<'a>(&self, ident: &'a str) -> Cow<'a, str> { + match self { + Self("Uint8Array") => { + Cow::Owned(format!("{ident},\n {ident}.byteLength")) + } + _ => Cow::Borrowed(ident), + } + } + + fn apply_promise(&self, non_blocking: bool) -> Cow<'_, str> { + if non_blocking { + Cow::Owned(format!("Promise<{}>", self.0)) + } else { + Cow::Borrowed(self.0) + } + } +} + +impl From for TypeScriptType<'_> { + fn from(value: Type) -> Self { + Self(match value { + Type::Void => "void", + Type::Uint8 + | Type::Uint16 + | Type::Uint32 + | Type::Uint64 + | Type::Int8 + | Type::Int16 + | Type::Int32 + | Type::Int64 + | Type::Float32 + | Type::Float64 => "number", + Type::Pointer => "Deno.PointerObject | null", + Type::Buffer => "Uint8Array", + }) + } +} + +struct DenoFfiType(String); + +impl std::fmt::Display for DenoFfiType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.0)?; + Ok(()) + } +} + +impl From for DenoFfiType { + fn from(value: Type) -> Self { + let ty = match value { + Type::Void => "void", + Type::Uint8 => "u8", + Type::Uint16 => "u16", + Type::Uint32 => "u32", + Type::Uint64 => "u64", + Type::Int8 => "i8", + Type::Int16 => "i16", + Type::Int32 => "i32", + Type::Int64 => "i64", + Type::Float32 => "f32", + Type::Float64 => "f64", + Type::Pointer => "pointer", + Type::Buffer => "buffer", + }; + + let mut raw = format!("'{}'", ty); + + // Complex types. + if value == Type::Buffer { + raw.push_str(",\n 'usize'"); + } + + Self(raw) + } +} + +pub struct Codegen<'a> { + symbols: &'a [Symbol], +} + +impl<'a> Codegen<'a> { + pub fn new(symbols: &'a [Symbol]) -> Self { + Self { symbols } + } + + fn dlopen(&self, writer: &mut W) -> Result<()> { + writeln!(writer, "const {{ dlopen }} = Deno;\n")?; + + writeln!( + writer, + "const {{ symbols }} = dlopen('./target/debug/libdeno_bindgen_test.dylib', {{" + )?; + self.write_symbols(writer)?; + writeln!(writer, "}});\n")?; + Ok(()) + } + + fn write_symbols(&self, writer: &mut W) -> Result<()> { + fn format_bracket( + writer: &mut W, + items: &[T], + callback: impl Fn(&mut W, &[T]) -> Result<()>, + ) -> Result<()> { + write!(writer, "[")?; + if items.len() > 0 { + write!(writer, "\n")?; + callback(writer, items)?; + writeln!(writer, " ],")?; + } else { + writeln!(writer, "],")?; + } + + Ok(()) + } + + for symbol in self.symbols { + writeln!(writer, " {}: {{", symbol.name)?; + write!(writer, " parameters: ")?; + format_bracket(writer, symbol.parameters, |writer, parameters| { + for parameter in parameters { + writeln!(writer, " {},", DenoFfiType::from(*parameter))?; + } + Ok(()) + })?; + writeln!( + writer, + " result: {},", + DenoFfiType::from(symbol.return_type) + )?; + writeln!(writer, " nonblocking: {}", symbol.non_blocking)?; + writeln!(writer, " }},")?; + } + + Ok(()) + } + + fn exports(&self, writer: &mut W) -> Result<()> { + fn format_paren( + writer: &mut W, + items: &[T], + callback: impl Fn(&mut W, &[T]) -> Result<()>, + nesting_spaces: usize, + ) -> Result<()> { + write!(writer, "(")?; + if items.len() > 0 { + write!(writer, "\n")?; + callback(writer, items)?; + write!(writer, "{:indent$})", "", indent = nesting_spaces)?; + } else { + write!(writer, ")")?; + } + + Ok(()) + } + + for symbol in self.symbols { + write!(writer, "export function {}", symbol.name)?; + format_paren(writer, symbol.parameters, |writer, parameters| { + for (i, parameter) in parameters.iter().enumerate() { + writeln!( + writer, + " arg{}: {},", + i, + TypeScriptType::from(*parameter) + )?; + } + Ok(()) + }, 0)?; + writeln!(writer, ": {} {{", TypeScriptType::from(symbol.return_type).apply_promise(symbol.non_blocking))?; + write!(writer, " return symbols.{}", symbol.name)?; + format_paren(writer, symbol.parameters, |writer, parameters| { + for (i, parameter) in parameters.iter().enumerate() { + let ident = format!("arg{}", i); + writeln!( + writer, + " {},", + TypeScriptType::from(*parameter).into_raw(&ident) + )?; + } + Ok(()) + }, 2)?; + + writeln!(writer, "\n}}\n")?; + } + + Ok(()) + } +} + +impl<'a> Generator for Codegen<'a> { + fn generate(&mut self, mut writer: W) -> Result<()> { + fn prelude(writer: &mut W) -> Result<()> { + writeln!(writer, "// deno-lint-ignore-file\n")?; + writeln!( + writer, + "// This file is automatically generated by deno_bindgen." + )?; + writeln!(writer, "// Do not edit this file directly.\n")?; + Ok(()) + } + + prelude(&mut writer)?; + self.dlopen(&mut writer)?; + self.exports(&mut writer)?; + + Ok(()) + } +} diff --git a/deno_bindgen_ir/codegen/mod.rs b/deno_bindgen_ir/codegen/mod.rs new file mode 100644 index 0000000..b3c2698 --- /dev/null +++ b/deno_bindgen_ir/codegen/mod.rs @@ -0,0 +1,36 @@ +use std::path::PathBuf; + +use crate::Symbol; + +mod deno; + +pub struct Options { + pub target: Target, + pub out: Option, +} + +pub enum Target { + Deno, +} + +pub trait Generator { + fn generate(&mut self, writer: W) -> std::io::Result<()>; +} + +pub fn generate( + symbols: &'static [Symbol], + opt: Options, +) -> std::io::Result<()> { + let mut codegen = match opt.target { + Target::Deno => deno::Codegen::new(symbols), + }; + + if let Some(out) = opt.out { + let mut writer = std::fs::File::create(out)?; + codegen.generate(&mut writer)?; + return Ok(()); + } + + let writer = std::io::stdout(); + codegen.generate(writer) +} diff --git a/deno_bindgen_ir/lib.rs b/deno_bindgen_ir/lib.rs new file mode 100644 index 0000000..56ab6ac --- /dev/null +++ b/deno_bindgen_ir/lib.rs @@ -0,0 +1,164 @@ +use proc_macro2::Ident; +use quote::{format_ident, quote, ToTokens}; +use syn::{parse_quote, Pat}; + +pub mod codegen; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)] +pub enum Type { + #[default] + Void, + Uint8, + Uint16, + Uint32, + Uint64, + Int8, + Int16, + Int32, + Int64, + Float32, + Float64, + Pointer, + Buffer, +} + +pub type RawTypes = &'static [Type]; + +impl Type { + pub fn raw(&self) -> RawTypes { + match self { + Self::Buffer => &[Self::Pointer, Self::Uint32], + Self::Pointer => &[Self::Pointer], + _ => &[], + } + } + + pub fn is_number(&self) -> bool { + !matches!(self, Self::Void | Self::Pointer | Self::Buffer) + } + + pub fn apply_transform( + &self, + name: &mut Box, + args: &[Ident], + ) -> Option { + match self { + Self::Buffer => { + let pointer = &args[0]; + let length = &args[1]; + Some(quote! { + let #name = unsafe { + std::slice::from_raw_parts_mut(#pointer as _, #length as usize) + }; + }) + } + Self::Pointer => { + let pointer = &args[0]; + Some(quote! { + let #name = #pointer as _; + }) + } + _ => None, + } + } + + pub fn to_ident(&self) -> syn::Type { + match self { + Self::Void => parse_quote!(deno_bindgen::Type::Void), + Self::Uint8 => parse_quote!(deno_bindgen::Type::Uint8), + Self::Uint16 => parse_quote!(deno_bindgen::Type::Uint16), + Self::Uint32 => parse_quote!(deno_bindgen::Type::Uint32), + Self::Uint64 => parse_quote!(deno_bindgen::Type::Uint64), + Self::Int8 => parse_quote!(deno_bindgen::Type::Int8), + Self::Int16 => parse_quote!(deno_bindgen::Type::Int16), + Self::Int32 => parse_quote!(deno_bindgen::Type::Int32), + Self::Int64 => parse_quote!(deno_bindgen::Type::Int64), + Self::Float32 => parse_quote!(deno_bindgen::Type::Float32), + Self::Float64 => parse_quote!(deno_bindgen::Type::Float64), + Self::Pointer => parse_quote!(deno_bindgen::Type::Pointer), + Self::Buffer => parse_quote!(deno_bindgen::Type::Buffer), + } + } +} + +impl ToTokens for Type { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let ty = match self { + Self::Void => quote! { () }, + Self::Uint8 => quote! { u8 }, + Self::Uint16 => quote! { u16 }, + Self::Uint32 => quote! { u32 }, + Self::Uint64 => quote! { u64 }, + Self::Int8 => quote! { i8 }, + Self::Int16 => quote! { i16 }, + Self::Int32 => quote! { i32 }, + Self::Int64 => quote! { i64 }, + Self::Float32 => quote! { f32 }, + Self::Float64 => quote! { f64 }, + Self::Pointer => quote! { *const () }, + Self::Buffer => quote! { *mut u8 }, + }; + + tokens.extend(ty); + } +} + +#[derive(Debug)] +pub struct Symbol { + pub name: &'static str, + pub parameters: &'static [Type], + pub return_type: Type, + pub non_blocking: bool, +} + +pub struct SymbolBuilder { + name: Ident, + parameters: Vec, + return_type: Type, + non_blocking: bool, +} + +impl SymbolBuilder { + pub fn new(name: Ident) -> Self { + Self { + name, + parameters: Vec::new(), + return_type: Default::default(), + non_blocking: false, + } + } + + pub fn push(&mut self, ty: Type) { + self.parameters.push(ty); + } + + pub fn return_type(&mut self, ty: Type) { + self.return_type = ty; + } + + pub fn non_blocking(&mut self, non_blocking: bool) { + self.non_blocking = non_blocking; + } +} + +impl ToTokens for SymbolBuilder { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let parameters = &self + .parameters + .iter() + .map(|ty| ty.to_ident()) + .collect::>(); + let return_type = &self.return_type.to_ident(); + let non_blocking = &self.non_blocking; + let name = &self.name; + + tokens.extend(quote! { + deno_bindgen::Symbol { + name: stringify!(#name), + parameters: &[#(#parameters),*], + return_type: #return_type, + non_blocking: #non_blocking, + } + }); + } +} diff --git a/deno_bindgen_macro/Cargo.toml b/deno_bindgen_macro/Cargo.toml index 04d464e..32ff6e0 100644 --- a/deno_bindgen_macro/Cargo.toml +++ b/deno_bindgen_macro/Cargo.toml @@ -15,9 +15,10 @@ edition = "2021" proc-macro = true [dependencies] -proc-macro2 = "1" -quote = "1" -syn = { version = "1", features = ["full", "extra-traits"] } +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "2.0", features = ["full", "extra-traits"] } serde = { version = "1.0.59", features = ["derive"] } serde_json = "1.0.59" Inflector = "0.11.4" +deno_bindgen_ir = { path = "../deno_bindgen_ir", version = "0.1.0" } diff --git a/deno_bindgen_macro/src/attrs.rs b/deno_bindgen_macro/src/attrs.rs deleted file mode 100644 index a19028b..0000000 --- a/deno_bindgen_macro/src/attrs.rs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. - -use inflector::Inflector; -use syn::{Attribute, Lit, Meta, NestedMeta}; - -#[derive(Debug)] -pub enum SerdeAttr { - RenameAll(String), - TagAndContent(String, String), -} - -impl SerdeAttr { - pub fn transform(&self, s: &str) -> Option { - match self { - SerdeAttr::RenameAll(t) => match t.as_ref() { - "lowercase" => Some(s.to_lowercase()), - "UPPERCASE" => Some(s.to_uppercase()), - "camelCase" => Some(s.to_camel_case()), - "snake_case" => Some(s.to_snake_case()), - "PascalCase" => Some(s.to_pascal_case()), - "SCREAMING_SNAKE_CASE" => Some(s.to_screaming_snake_case()), - _ => panic!("Invalid attribute value: {}", s), - }, - _ => None, - } - } -} - -pub fn get_serde_attrs(attrs: &Vec) -> Vec { - attrs - .iter() - .filter(|i| i.path.is_ident("serde")) - .flat_map(|attr| match attr.parse_meta() { - Ok(Meta::List(l)) => l.nested.iter().find_map(|meta| match meta { - NestedMeta::Meta(Meta::NameValue(v)) => match v.path.get_ident() { - Some(id) => match id.to_string().as_ref() { - // #[serde(rename_all = "UPPERCASE")] - "rename_all" => match &v.lit { - Lit::Str(s) => Some(SerdeAttr::RenameAll(s.value())), - _ => None, - }, - // #[serde(tag = "key", content = "value")] - "tag" => match &v.lit { - Lit::Str(s) => { - let tag = s.value(); - - let lit = l.nested.iter().find_map(|meta| match meta { - NestedMeta::Meta(Meta::NameValue(v)) => { - match v.path.is_ident("content") { - true => Some(&v.lit), - false => None, - } - } - _ => None, - }); - - match lit { - Some(Lit::Str(s)) => { - let content = s.value(); - Some(SerdeAttr::TagAndContent(tag, content)) - } - _ => panic!("Missing `content` attribute with `tag`."), - } - } - _ => None, - }, - // #[serde(content = "value", tag = "key")] - "content" => match &v.lit { - Lit::Str(s) => { - let content = s.value(); - - let lit = l.nested.iter().find_map(|meta| match meta { - NestedMeta::Meta(Meta::NameValue(v)) => { - match v.path.is_ident("tag") { - true => Some(&v.lit), - false => None, - } - } - _ => None, - }); - - match lit { - Some(Lit::Str(s)) => { - let tag = s.value(); - Some(SerdeAttr::TagAndContent(tag, content)) - } - _ => panic!("Missing `tag` attribute with `content`."), - } - } - _ => None, - }, - _ => None, - }, - _ => None, - }, - _ => None, - }), - _ => None, - }) - .collect::>() -} diff --git a/deno_bindgen_macro/src/derive_fn.rs b/deno_bindgen_macro/src/derive_fn.rs deleted file mode 100644 index 71dc78b..0000000 --- a/deno_bindgen_macro/src/derive_fn.rs +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. - -use syn::{ - AttributeArgs, FnArg, ItemFn, Meta, NestedMeta, PathArguments, ReturnType, -}; - -use crate::meta::{Glue, Symbol, Type}; - -pub fn process_function( - function: ItemFn, - attr: AttributeArgs, - metadata: &mut Glue, -) -> Result { - let params = &function.sig.inputs; - let mut parameters = Vec::with_capacity(params.len()); - - for param in params.iter() { - match param { - FnArg::Typed(ref val) => { - let val = val.clone(); - let ty = match *val.ty { - syn::Type::Path(ref ty) => { - let segment = ty.path.segments.first().unwrap(); - let ident = segment.ident.to_string(); - match ident.as_str() { - "i8" => Type::I8, - "u8" => Type::U8, - "i16" => Type::I16, - "u16" => Type::U16, - "i32" => Type::I32, - "u32" => Type::U32, - "i64" => Type::I64, - "u64" => Type::U64, - "usize" => Type::Usize, - "isize" => Type::Isize, - "f32" => Type::F32, - "f64" => Type::F64, - _ => { - metadata.type_defs.get(&ident).expect(&format!( - "Type definition not found for `{}` identifier", - &ident, - )); - - Type::StructEnum { ident } - } - } - } - syn::Type::Reference(ref ty) => match *ty.elem { - syn::Type::Path(ref ty) => { - let segment = ty.path.segments.first().unwrap(); - let ident = segment.ident.to_string(); - - match ident.as_str() { - "str" => Type::Str, - _ => unimplemented!(), - } - } - syn::Type::Slice(ref slice) => match *slice.elem { - syn::Type::Path(ref path) => { - let segment = path.path.segments.first().unwrap(); - let ident = segment.ident.to_string(); - - match ident.as_str() { - "u8" => { - if ty.mutability.is_some() { - Type::BufferMut - } else { - Type::Buffer - } - } - _ => unimplemented!(), - } - } - _ => unimplemented!(), - }, - _ => unimplemented!(), - }, - _ => unimplemented!(), - }; - - parameters.push(ty); - } - _ => unimplemented!(), - } - } - - let result = match &function.sig.output { - ReturnType::Default => Type::Void, - ReturnType::Type(_, ref ty) => match ty.as_ref() { - syn::Type::Ptr(_) => Type::Ptr, - syn::Type::Path(ref ty) => { - let segment = ty.path.segments.first().unwrap(); - let ident = segment.ident.to_string(); - - match ident.as_str() { - "i8" => Type::I8, - "u8" => Type::U8, - "i16" => Type::I16, - "u16" => Type::U16, - "i32" => Type::I32, - "u32" => Type::U32, - "i64" => Type::I64, - "u64" => Type::U64, - "usize" => Type::Usize, - "isize" => Type::Isize, - "f32" => Type::F32, - "f64" => Type::F64, - // This isn't a &str but i really but - // don't want to add another type for just owned strings. - "String" => Type::Str, - "Box" => { - let mut buf = false; - let mut gn = String::default(); - if let PathArguments::AngleBracketed(args) = &segment.arguments { - if let Some(syn::GenericArgument::Type(syn::Type::Slice(args))) = - &args.args.first() - { - if let syn::Type::Path(args) = &*args.elem { - if let Some(args) = args.path.segments.first() { - gn = args.ident.to_string(); - if gn == "u8" { - buf = true; - } - } - } - } - } - if buf { - Type::Buffer - } else { - panic!("{}<{}> return type not supported by Deno FFI", ident, gn) - } - } - "Vec" => { - let mut buf = false; - let mut gn = String::default(); - if let PathArguments::AngleBracketed(args) = &segment.arguments { - if let Some(syn::GenericArgument::Type(syn::Type::Path(args))) = - &args.args.first() - { - if let Some(args) = &args.path.segments.first() { - gn = args.ident.to_string(); - if gn == "u8" { - buf = true; - } - } - } - } - if buf { - Type::Buffer - } else { - panic!("{}<{}> return type not supported by Deno FFI", ident, gn) - } - } - _ => match metadata.type_defs.get(&ident) { - Some(_) => Type::StructEnum { ident }, - None => panic!("{} return type not supported by Deno FFI", ident), - }, - } - } - syn::Type::Reference(ref ty) => match *ty.elem { - syn::Type::Slice(ref slice) => match *slice.elem { - syn::Type::Path(ref path) => { - let segment = path.path.segments.first().unwrap(); - let ident = segment.ident.to_string(); - - match ident.as_str() { - "u8" => { - if ty.mutability.is_some() { - // https://github.com/denoland/deno_bindgen/issues/39 - panic!("Mutable slices are not mutable from JS"); - } else { - Type::Buffer - } - } - _ => unimplemented!(), - } - } - _ => unimplemented!(), - }, - _ => unimplemented!(), - }, - _ => unimplemented!(), - }, - }; - - let symbol_name = function.sig.ident.to_string(); - let non_blocking = match attr.get(0).as_ref() { - Some(NestedMeta::Meta(Meta::Path(ref attr_ident))) => { - attr_ident.is_ident("non_blocking") - } - _ => false, - }; - - let symbol = Symbol { - parameters, - result, - non_blocking, - }; - metadata.symbols.insert(symbol_name, symbol.clone()); - - Ok(symbol) -} diff --git a/deno_bindgen_macro/src/derive_struct.rs b/deno_bindgen_macro/src/derive_struct.rs deleted file mode 100644 index 2d729e9..0000000 --- a/deno_bindgen_macro/src/derive_struct.rs +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. - -use std::collections::HashMap; - -use syn::{ext::IdentExt, Data, DataStruct, DeriveInput, Fields}; - -use crate::{ - attrs::{get_serde_attrs, SerdeAttr}, - docs::get_docs, - meta::Glue, -}; - -macro_rules! variant_instance { - ( $variant:path, $iterator:expr ) => { - $iterator - .filter_map(|val| { - if let $variant(ref f1, ref f2) = *val { - Some((f1, f2)) - } else { - None - } - }) - .next() - }; -} - -pub fn process_struct( - metadata: &mut Glue, - input: DeriveInput, -) -> Result<(), String> { - match &input.data { - Data::Struct(DataStruct { - fields: Fields::Named(fields), - .. - }) => { - let fields = &fields.named; - - let name = &input.ident; - let mut fmap = HashMap::new(); - let mut typescript: Vec = vec![]; - - let serde_attrs = get_serde_attrs(&input.attrs); - - for field in fields.iter() { - let mut ident = field - .ident - .as_ref() - .expect("Field without ident") - // Strips the raw marker `r#`, if present. - .unraw() - .to_string(); - - match field.ty { - syn::Type::Path(ref ty) => { - let segment = &ty.path.segments.first().unwrap(); - let ty = segment.ident.to_string(); - fmap.insert(ident.clone(), ty); - } - syn::Type::Reference(ref ty) => { - assert!(!ty.mutability.is_some()); - assert!(ty.lifetime.is_some()); - match *ty.elem { - syn::Type::Path(ref ty) => { - let segment = &ty.path.segments.first().unwrap(); - let ty = segment.ident.to_string(); - fmap.insert(ident.clone(), ty); - } - _ => unimplemented!(), - } - } - _ => unimplemented!(), - }; - - for attr in &serde_attrs { - if let Some(i) = attr.transform(&ident) { - ident = i; - } - } - - let doc_str = get_docs(&field.attrs); - typescript.push(format!( - "{} {}: {};", - doc_str, - ident, - types_to_ts(&field.ty) - )); - } - - metadata.type_defs.insert(name.to_string(), fmap.clone()); - - let doc_str = get_docs(&input.attrs); - let typescript = format!( - "{}export type {} = {{\n {}\n}};", - doc_str, - name, - typescript.join("\n") - ); - metadata.ts_types.insert(name.to_string(), typescript); - Ok(()) - } - Data::Enum(syn::DataEnum { variants, .. }) => { - let name = &input.ident; - let mut typescript: Vec = vec![]; - - for variant in variants { - let mut variant_fields: Vec = vec![]; - let fields = &variant.fields; - - let serde_attrs = get_serde_attrs(&input.attrs); - for field in fields { - let mut ident = field - .ident - .as_ref() - .expect("Field without ident") - // Strips the raw marker `r#`, if present. - .unraw() - .to_string(); - - for attr in &serde_attrs { - if let Some(i) = attr.transform(&ident) { - ident = i; - } - } - - let doc_str = get_docs(&field.attrs); - variant_fields.push(format!( - "{} {}: {};", - doc_str, - ident, - types_to_ts(&field.ty) - )); - } - - let mut ident = variant.ident.to_string(); - - // Perform #[serde] attribute transformers. - // This excludes `tag` and `content` attributes. - // They require special treatment during codegen. - for attr in &serde_attrs { - if let Some(i) = attr.transform(&ident) { - ident = i; - } - } - - let doc_str = get_docs(&variant.attrs); - - let variant_str = if variant_fields.len() > 0 { - let tag_content = - variant_instance!(SerdeAttr::TagAndContent, serde_attrs.iter()); - - match tag_content { - None => { - format!( - "{} {{ {}: {{\n {}\n}} }}", - doc_str, - &ident, - variant_fields.join("\n") - ) - } - Some((tag, content)) => { - // // $jsdoc - // { - // $tag: $ident, - // $content: { ...$fields } - // } - format!( - "{} {{ {}: \"{}\", {}: {{ {} }} }}", - doc_str, - &tag, - &ident, - &content, - variant_fields.join("\n") - ) - } - } - } else { - format!("{} \"{}\"", doc_str, &ident) - }; - - typescript.push(variant_str); - } - - // TODO: `type_defs` in favor of `ts_types` - metadata.type_defs.insert(name.to_string(), HashMap::new()); - - let doc_str = get_docs(&input.attrs); - let typescript = format!( - "{}export type {} = {};", - doc_str, - name, - typescript.join(" |\n") - ); - metadata.ts_types.insert(name.to_string(), typescript); - Ok(()) - } - _ => unimplemented!(), - } -} - -fn types_to_ts(ty: &syn::Type) -> String { - match ty { - syn::Type::Array(_) => String::from("any"), - syn::Type::Ptr(_) => String::from("any"), - syn::Type::Reference(ref ty) => types_to_ts(&ty.elem), - syn::Type::Path(ref ty) => { - // std::alloc::Vec => Vec - let segment = &ty.path.segments.last().unwrap(); - let ty = segment.ident.to_string(); - let mut generics: Vec = vec![]; - let generic_params = &segment.arguments; - match generic_params { - &syn::PathArguments::AngleBracketed(ref args) => { - for p in &args.args { - let ty = match p { - syn::GenericArgument::Type(ty) => types_to_ts(ty), - syn::GenericArgument::Lifetime(_) => continue, - _ => unimplemented!(), - }; - generics.push(ty); - } - } - &syn::PathArguments::None => {} - _ => unimplemented!(), - }; - - match ty.as_ref() { - "Option" => format!( - "{} | undefined | null", - rs_to_ts(generics.first().unwrap().as_ref()) - ), - _ => { - if generics.len() > 0 { - let root_ty = rs_to_ts(&ty); - let generic_str = generics - .iter() - .map(|g| rs_to_ts(g)) - .collect::>() - .join(", "); - format!("{}<{}>", root_ty, generic_str) - } else { - rs_to_ts(&ty).to_string() - } - } - } - } - _ => unimplemented!(), - } -} - -fn rs_to_ts(ty: &str) -> &str { - match ty { - "i8" => "number", - "i16" => "number", - "i32" => "number", - "i64" => "number", - "u8" => "number", - "u16" => "number", - "u32" => "number", - "u64" => "number", - "usize" => "number", - "bool" => "boolean", - "String" => "string", - "str" => "string", - "f32" => "number", - "f64" => "number", - "HashMap" => "Record", - "Vec" => "Array", - "HashSet" => "Array", - "Value" => "any", - a @ _ => a, - } -} diff --git a/deno_bindgen_macro/src/docs.rs b/deno_bindgen_macro/src/docs.rs deleted file mode 100644 index 938b01a..0000000 --- a/deno_bindgen_macro/src/docs.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. - -use syn::{Attribute, Lit, Meta}; - -pub fn get_docs(attrs: &Vec) -> String { - let mut doc: Vec = vec![]; - for attr in attrs { - if let Ok(Meta::NameValue(meta)) = attr.parse_meta() { - if !meta.path.is_ident("doc") { - continue; - } - if let Lit::Str(lit) = meta.lit { - doc.push(lit.value()); - } - } - } - - let doc_str = if doc.len() > 0 { - format!("/**\n *{}\n **/\n", doc.join("\n *")) - } else { - String::new() - }; - - doc_str -} diff --git a/deno_bindgen_macro/src/fn_.rs b/deno_bindgen_macro/src/fn_.rs new file mode 100644 index 0000000..10bede6 --- /dev/null +++ b/deno_bindgen_macro/src/fn_.rs @@ -0,0 +1,177 @@ +use std::path::Path; + +use deno_bindgen_ir::{Symbol, SymbolBuilder, Type}; +use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; +use quote::quote; +use syn::{ + parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, FnArg, + ItemFn, PatType, ReturnType, TypePath, TypePtr, TypeReference, TypeSlice, +}; + +use crate::{util::{Error, Result}, FnAttributes}; + +fn parse_type(ty: &Box) -> Result { + match **ty { + syn::Type::Path(TypePath { ref path, .. }) => { + if let Some(ident) = path.get_ident() { + match ident.to_string().as_str() { + "u8" => return Ok(Type::Uint8), + "u16" => return Ok(Type::Uint16), + "u32" => return Ok(Type::Uint32), + "u64" => return Ok(Type::Uint64), + "i8" => return Ok(Type::Int8), + "i16" => return Ok(Type::Int16), + "i32" => return Ok(Type::Int32), + "i64" => return Ok(Type::Int64), + "f32" => return Ok(Type::Float32), + "f64" => return Ok(Type::Float64), + "usize" => return Ok(Type::Uint64), + "isize" => return Ok(Type::Int64), + _ => return Err(Error::UnsupportedType), + } + } + + Err(Error::UnsupportedType) + } + syn::Type::Reference(TypeReference { ref elem, .. }) => { + if let syn::Type::Slice(TypeSlice { ref elem, .. }) = *elem.as_ref() { + if parse_type(elem)?.is_number() { + return Ok(Type::Buffer); + } + } + + Err(Error::UnsupportedType) + } + + syn::Type::Ptr(TypePtr { .. }) => Ok(Type::Pointer), + _ => Err(Error::UnsupportedType), + } +} + +pub fn handle(fn_: ItemFn, attrs: FnAttributes) -> Result { + if fn_.sig.asyncness.is_some() { + return Err(Error::Asyncness); + } + + if fn_.sig.receiver().is_some() { + return Err(Error::Reciever); + } + + // TODO: check right ABI + + let mut ffi_fn = fn_.clone(); + ffi_fn.sig.abi.get_or_insert_with(|| { + parse_quote!( + extern "C" + ) + }); + + let mut inputs: Punctuated = Punctuated::new(); + let mut transforms: Vec = Vec::new(); + + let mut symbol = SymbolBuilder::new(fn_.sig.ident.clone()); + symbol.non_blocking(attrs.non_blocking); + + // Cannot use enumerate here, there can be multiple raw args per type. + let mut i = 0; + for arg in ffi_fn.sig.inputs.iter_mut() { + match *arg { + FnArg::Receiver(_) => unreachable!(), + FnArg::Typed(PatType { + ref mut pat, + ref mut ty, + .. + }) => { + let ty = parse_type(ty)?; + symbol.push(ty); + + const X_ARG_PREFIX: &str = "__arg_"; + // Divide the type into its raw components. + let raw_ty = ty.raw(); + + // Complex types, that need transforms. + let mut idents = Vec::new(); + for ty in raw_ty { + let arg_name = Ident::new( + &format!("{}{}", X_ARG_PREFIX, i), + Span::mixed_site().located_at(pat.span()), + ); + inputs.push(parse_quote!(#arg_name: #ty)); + idents.push(arg_name); + i += 1; + } + + // Apply the transform. + if let Some(transform) = ty.apply_transform(pat, &idents) { + transforms.push(transform); + } + + // Simple type. + if raw_ty.len() == 0 { + inputs.push(arg.clone()); + i += 1; + } + } + } + } + + let ret_ident = Ident::new("ret", Span::mixed_site()); + let mut ret = Box::new(syn::Pat::Ident(syn::PatIdent { + attrs: Vec::new(), + by_ref: None, + mutability: None, + ident: ret_ident.clone(), + subpat: None, + })); + let mut ret_transform = TokenStream2::new(); + match ffi_fn.sig.output { + ReturnType::Default => {} + ReturnType::Type(_, ref mut ty) => { + let t = parse_type(ty)?; + + if let Some(transform) = t.apply_transform(&mut ret, &[ret_ident.clone()]) + { + ret_transform = transform; + } + + symbol.return_type(t); + **ty = parse_quote!(#t) + } + } + + let idents = ffi_fn + .sig + .inputs + .iter() + .map(|arg| match arg { + FnArg::Receiver(_) => unreachable!(), + FnArg::Typed(PatType { ref pat, .. }) => match &**pat { + syn::Pat::Ident(ident) => ident.ident.clone(), + _ => unreachable!(), + }, + }) + .collect::>(); + + let name = fn_.sig.ident.clone(); + ffi_fn.sig.inputs = inputs; + + ffi_fn.block = parse_quote!({ + #fn_ + + #(#transforms)* + + let #ret_ident = #name(#(#idents),*); + #ret_transform + #ret_ident + }); + + Ok(quote::quote! { + const _: () = { + #[deno_bindgen::linkme::distributed_slice(deno_bindgen::INVENTORY)] + pub static _A: deno_bindgen::Symbol = #symbol; + }; + + #[no_mangle] + #ffi_fn + }) +} diff --git a/deno_bindgen_macro/src/lib.rs b/deno_bindgen_macro/src/lib.rs index 5871289..be37273 100644 --- a/deno_bindgen_macro/src/lib.rs +++ b/deno_bindgen_macro/src/lib.rs @@ -1,250 +1,35 @@ // Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. -use std::{ - env, - fs::OpenOptions, - io::{Read, Write}, - path::Path, -}; +use deno_bindgen_ir::Symbol; +use proc_macro::TokenStream; +use quote::{format_ident, quote}; +use syn::{parse_macro_input, parse_quote, ItemFn, meta::ParseNestedMeta}; -use proc_macro::{TokenStream}; -use quote::{format_ident,quote}; -use syn::{parse_macro_input, parse_quote, ItemFn}; +mod fn_; +mod util; -mod attrs; -mod derive_fn; -mod derive_struct; -mod docs; -mod meta; - -use crate::{ - derive_fn::process_function, - derive_struct::process_struct, - meta::{Glue, Type}, -}; - -#[cfg(target_endian = "little")] -const ENDIANNESS: bool = true; - -#[cfg(target_endian = "big")] -const ENDIANNESS: bool = false; - -#[proc_macro_attribute] -pub fn deno_bindgen(attr: TokenStream, input: TokenStream) -> TokenStream { - let metafile_path: String = match env::var("OUT_DIR") { - Ok(out_dir) => Path::new(&out_dir) - .join("bindings.json") - .into_os_string() - .into_string() - .unwrap(), - Err(_e) => String::from("bindings.json"), - }; - - let mut metadata: Glue = - match OpenOptions::new().read(true).open(metafile_path.as_str()) { - Ok(mut fd) => { - let mut meta = String::new(); - fd.read_to_string(&mut meta) - .expect("Error reading meta file"); - - serde_json::from_str(&meta).unwrap_or_default() - } - Err(_) => Glue { - little_endian: ENDIANNESS, - name: env::var("CARGO_CRATE_NAME").unwrap_or_default(), - ..Default::default() - }, - }; - - let mut metafile = OpenOptions::new() - .write(true) - .create(true) - .open(metafile_path.as_str()) - .expect("Error opening meta file"); - - match syn::parse::(input.clone()) { - Ok(func) => { - let attr = parse_macro_input!(attr as syn::AttributeArgs); - let symbol = process_function(func.clone(), attr, &mut metadata).unwrap(); - - let mut params = vec![]; - let mut overrides = vec![]; - let mut input_idents = vec![]; - let mut c_index = 0; - - for parameter in &symbol.parameters { - match parameter { - Type::StructEnum { .. } => { - let ident = format_ident!("arg{}", c_index.to_string()); - params.push(quote! { #ident: *const u8 }); - - c_index += 1; - let len_ident = format_ident!("arg{}", c_index.to_string()); - params.push(quote! { #len_ident: usize }); - - overrides.push(quote! { - let buf = unsafe { - ::std::slice::from_raw_parts(#ident, #len_ident) - }; - let #ident = deno_bindgen::serde_json::from_slice(buf).unwrap(); - }); - - input_idents.push(ident); - } - Type::Str | Type::Buffer | Type::BufferMut => { - let ident = format_ident!("arg{}", c_index.to_string()); - match parameter { - Type::Str | Type::Buffer => { - params.push(quote! { #ident: *const u8 }) - } - Type::BufferMut => params.push(quote! { #ident: *mut u8 }), - _ => unreachable!(), - }; - - c_index += 1; - let len_ident = format_ident!("arg{}", c_index.to_string()); - params.push(quote! { #len_ident: usize }); - - let return_type = match parameter { - Type::Str => quote! { ::std::str::from_utf8(buf).unwrap() }, - Type::Buffer | Type::BufferMut => quote! { buf }, - _ => unreachable!(), - }; - - let buf_expr = match parameter { - Type::Str | Type::Buffer => { - quote! { let buf = ::std::slice::from_raw_parts(#ident, #len_ident); } - } - Type::BufferMut => { - // https://github.com/littledivy/deno_bindgen/issues/26 - // *mut u8 should never outlive the symbol call. This can lead to UB. - quote! { let mut buf: &'sym mut [u8] = ::std::slice::from_raw_parts_mut(#ident, #len_ident); - } - } - _ => unreachable!(), - }; - - overrides.push(quote! { - let #ident = unsafe { - #buf_expr - #return_type - }; - }); - - input_idents.push(ident); - } - // TODO - _ => { - let ident = format_ident!("arg{}", c_index.to_string()); - let ty = syn::Type::from(parameter.clone()); - params.push(quote! { #ident: #ty }); - input_idents.push(ident); - } - }; - - c_index += 1; - } - - let (result, transformer) = match symbol.result { - Type::Buffer - // Note that this refers to an owned String - // and not a `&str` - | Type::Str => { - let ty = parse_quote! { *const u8 }; - let slice = match symbol.result { - Type::Str => quote! { - result.as_bytes() - }, - _ => quote! { result } - }; - let transformer = quote! { - let length = (result.len() as u32).to_be_bytes(); - let mut v = length.to_vec(); - v.extend_from_slice(&#slice); - - ::std::mem::forget(result); - let result = v.as_ptr(); - // Leak the result to JS land. - ::std::mem::forget(v); - result - }; - - (ty, transformer) - } - Type::StructEnum { .. } => { - let ty = parse_quote! { *const u8 }; - let transformer = quote! { - let json = deno_bindgen::serde_json::to_string(&result).expect("Failed to serialize as JSON"); - let encoded = json.into_bytes(); - let length = (encoded.len() as u32).to_be_bytes(); - let mut v = length.to_vec(); - v.extend(encoded.clone()); - - let ret = v.as_ptr(); - // Leak the result to JS land. - ::std::mem::forget(v); - ret - }; - - (ty, transformer) - } - Type::Ptr => (parse_quote! { *const u8 }, quote! { result }), - _ => (syn::Type::from(symbol.result.clone()), quote! { result }), - }; - - let name = &func.sig.ident; - let fn_inputs = &func.sig.inputs; - let fn_output = &func.sig.output; - let fn_generics = &func.sig.generics; - let fn_block = &func.block; - - let overrides = overrides - .iter() - .fold(quote! {}, |acc, new| quote! { #acc #new }); - - metafile - .write_all(&serde_json::to_vec(&metadata).unwrap()) - .unwrap(); - let xname = format_ident!("__de90_{}", name.to_string()); - let xparams = symbol.parameters.iter().map(|p| meta::CType::from(p) as u8).map(|p| quote!(#p)).collect::>(); - let xresult = meta::CType::from(&symbol.result) as u8; - TokenStream::from(quote! { - #[no_mangle] - pub extern "C" fn #name <'sym> (#(#params,) *) -> #result { - fn __inner_impl #fn_generics (#fn_inputs) #fn_output #fn_block - #overrides - let result = __inner_impl(#(#input_idents, ) *); - #transformer - } +#[derive(Default)] +pub(crate) struct FnAttributes { + pub(crate) non_blocking: bool, +} - #[no_mangle] - pub unsafe extern "C" fn #xname ( - params: *mut u8, - result: *mut u8, - ) -> *const u8 { - let mut idx = 0; - let params = ::std::slice::from_raw_parts_mut(params, 200); - #( - params[idx] = #xparams; - idx += 1; - )*; - *result = #xresult; - concat!(stringify!(#name), "\0").as_ptr() - } - }) +impl FnAttributes { + fn parse(&mut self, meta: ParseNestedMeta) -> syn::parse::Result<()> { + if meta.path.is_ident("non_blocking") { + self.non_blocking = true; + Ok(()) + } else { + Err(meta.error("unsupported attribute")) } - Err(_) => { - let input = syn::parse_macro_input!(input as syn::DeriveInput); - process_struct(&mut metadata, input.clone()).unwrap(); + } +} - metafile - .write_all(&serde_json::to_vec(&metadata).unwrap()) - .unwrap(); +#[proc_macro_attribute] +pub fn deno_bindgen(args: TokenStream, input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as ItemFn); + let mut attrs = FnAttributes::default(); + let attrs_parser = syn::meta::parser(|meta| attrs.parse(meta)); + parse_macro_input!(args with attrs_parser); - TokenStream::from(quote! { - #[derive(::serde::Deserialize,::serde::Serialize)] - #input - }) - } - } + fn_::handle(input, attrs).unwrap().into() } diff --git a/deno_bindgen_macro/src/meta.rs b/deno_bindgen_macro/src/meta.rs deleted file mode 100644 index f7c6ed7..0000000 --- a/deno_bindgen_macro/src/meta.rs +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. - -use std::collections::HashMap; - -use serde::{Deserialize, Serialize}; -use syn::parse_quote; - -#[derive(Serialize, Deserialize, Clone)] -#[serde(rename_all = "lowercase")] -pub enum Type { - /// Straight forward types supported - /// by Deno's FFI - I8, - U8, - I16, - U16, - I32, - U32, - I64, - U64, - F32, - F64, - Usize, - Isize, - Void, - - /// Types that pave way for - /// serializers. buffers <3 - Buffer, - BufferMut, - Str, - Ptr, - - /// Not-so straightforward types that - /// `deno_bingen` maps to. - StructEnum { - ident: String, - }, -} - -#[repr(u8)] -pub enum CType { - /// Straight forward types supported - /// by Deno's FFI - I8, - U8, - I16, - U16, - I32, - U32, - I64, - U64, - F32, - F64, - Usize, - Isize, - Void, - Buffer, - BufferMut, - Str, - Ptr, -} - -impl From<&Type> for CType { - fn from(ty: &Type) -> Self { - match ty { - Type::I8 => CType::I8, - Type::U8 => CType::U8, - Type::I16 => CType::I16, - Type::U16 => CType::U16, - Type::I32 => CType::I32, - Type::U32 => CType::U32, - Type::I64 => CType::I64, - Type::U64 => CType::U64, - Type::F32 => CType::F32, - Type::F64 => CType::F64, - Type::Usize => CType::Usize, - Type::Isize => CType::Isize, - Type::Void => CType::Void, - Type::Buffer => CType::Buffer, - Type::BufferMut => CType::BufferMut, - Type::Str => CType::Str, - Type::Ptr => CType::Ptr, - Type::StructEnum { .. } => CType::Ptr, - } - } -} - -impl From for syn::Type { - fn from(ty: Type) -> Self { - match ty { - Type::I8 => parse_quote! { i8 }, - Type::U8 => parse_quote! { u8 }, - Type::I16 => parse_quote! { i16 }, - Type::U16 => parse_quote! { u16 }, - Type::I32 => parse_quote! { i32 }, - Type::U32 => parse_quote! { u32 }, - Type::I64 => parse_quote! { i64 }, - Type::U64 => parse_quote! { u64 }, - Type::F32 => parse_quote! { f32 }, - Type::F64 => parse_quote! { f64 }, - Type::Usize => parse_quote! { usize }, - Type::Isize => parse_quote! { isize }, - Type::Void => parse_quote! { () }, - _ => unreachable!(), - } - } -} - -#[derive(Serialize, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Symbol { - pub parameters: Vec, - pub result: Type, - pub non_blocking: bool, -} - -#[derive(Serialize, Deserialize, Default)] -#[serde(rename_all = "camelCase")] -pub struct Glue { - pub name: String, - pub little_endian: bool, - pub symbols: HashMap, - pub type_defs: HashMap>, - pub ts_types: HashMap, -} diff --git a/deno_bindgen_macro/src/util.rs b/deno_bindgen_macro/src/util.rs new file mode 100644 index 0000000..ec5c196 --- /dev/null +++ b/deno_bindgen_macro/src/util.rs @@ -0,0 +1,20 @@ +#[derive(Debug)] +pub enum Error { + Asyncness, + Reciever, + UnsupportedType, +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Error::Asyncness => write!(f, "async functions are not supported"), + Error::Reciever => write!(f, "methods are not supported"), + Error::UnsupportedType => write!(f, "unsupported type"), + } + } +} + +impl std::error::Error for Error {} + +pub type Result = std::result::Result; diff --git a/example/Cargo.toml b/example/Cargo.toml index 8bc71e7..539b116 100644 --- a/example/Cargo.toml +++ b/example/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" [dependencies] deno_bindgen = { path = "../deno_bindgen/" } serde = { version = "1", features = ["derive"] } +linkme = "0.3" [lib] name = "deno_bindgen_test" diff --git a/example/bench.js b/example/bench.js index 9250eaf..f971170 100644 --- a/example/bench.js +++ b/example/bench.js @@ -1,12 +1,6 @@ -import { add, test_buf, test_serde, test_str } from "./bindings/bindings.ts"; +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("test_buf", () => test_buf(b)); - -// Unoptimized paths: -Deno.bench("test_str", () => test_str("hello")); -Deno.bench("test_serde", () => { - test_serde({ arr: ["IT", "WORKS"] }); -}); +Deno.bench("bytelen", () => bytelen(b)); diff --git a/example/bindings/bindings.ts b/example/bindings/bindings.ts index 68516c2..dd32ce8 100644 --- a/example/bindings/bindings.ts +++ b/example/bindings/bindings.ts @@ -1,339 +1,113 @@ -// Auto-generated with deno_bindgen -function encode(v: string | Uint8Array): Uint8Array { - if (typeof v !== "string") return v - return new TextEncoder().encode(v) -} - -function decode(v: Uint8Array): string { - return new TextDecoder().decode(v) -} - -// deno-lint-ignore no-explicit-any -function readPointer(v: any): Uint8Array { - const ptr = new Deno.UnsafePointerView(v) - const lengthBe = new Uint8Array(4) - const view = new DataView(lengthBe.buffer) - ptr.copyInto(lengthBe, 0) - const buf = new Uint8Array(view.getUint32(0)) - ptr.copyInto(buf, 4) - return buf -} - -const url = new URL("../target/debug", import.meta.url) +// deno-lint-ignore-file -let uri = url.pathname -if (!uri.endsWith("/")) uri += "/" +// This file is automatically generated by deno_bindgen. +// Do not edit this file directly. -// https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibrarya#parameters -if (Deno.build.os === "windows") { - uri = uri.replace(/\//g, "\\") - // Remove leading slash - if (uri.startsWith("\\")) { - uri = uri.slice(1) - } -} +const { dlopen } = Deno; -const { symbols } = Deno.dlopen( - { - darwin: uri + "libdeno_bindgen_test.dylib", - windows: uri + "deno_bindgen_test.dll", - linux: uri + "libdeno_bindgen_test.so", - freebsd: uri + "libdeno_bindgen_test.so", - netbsd: uri + "libdeno_bindgen_test.so", - aix: uri + "libdeno_bindgen_test.so", - solaris: uri + "libdeno_bindgen_test.so", - illumos: uri + "libdeno_bindgen_test.so", - }[Deno.build.os], - { - add: { parameters: ["i32", "i32"], result: "i32", nonblocking: false }, - add2: { - parameters: ["buffer", "usize"], - result: "i32", - nonblocking: false, - }, - add3: { parameters: ["f32", "f32"], result: "f32", nonblocking: false }, - add4: { parameters: ["f64", "f64"], result: "f64", nonblocking: false }, - add5: { - parameters: ["buffer", "usize", "buffer", "usize"], - result: "buffer", - nonblocking: false, - }, - add6: { - parameters: ["buffer", "usize", "buffer", "usize"], - result: "buffer", - nonblocking: false, - }, - sleep: { parameters: ["u64"], result: "void", nonblocking: true }, - test_buf: { - parameters: ["buffer", "usize"], - result: "u8", - nonblocking: false, - }, - test_buffer_return: { - parameters: ["buffer", "usize"], - result: "buffer", - nonblocking: false, - }, - test_buffer_return_async: { - parameters: ["buffer", "usize"], - result: "buffer", - nonblocking: true, - }, - test_hashmap: { parameters: [], result: "buffer", nonblocking: false }, - test_lifetime: { - parameters: ["buffer", "usize"], - result: "usize", - nonblocking: false, - }, - test_manual_ptr: { parameters: [], result: "buffer", nonblocking: false }, - test_manual_ptr_async: { - parameters: [], - result: "buffer", - nonblocking: true, - }, - test_mixed: { - parameters: ["isize", "buffer", "usize"], - result: "i32", - nonblocking: false, - }, - test_mixed_order: { - parameters: ["i32", "buffer", "usize", "i32"], - result: "i32", - nonblocking: false, - }, - test_mut_buf: { - parameters: ["buffer", "usize"], - result: "void", - nonblocking: false, - }, - test_output: { parameters: [], result: "buffer", nonblocking: false }, - test_output_async: { parameters: [], result: "buffer", nonblocking: true }, - test_reserved_field: { - parameters: [], - result: "buffer", - nonblocking: false, - }, - test_serde: { - parameters: ["buffer", "usize"], - result: "u8", - nonblocking: false, - }, - test_str: { - parameters: ["buffer", "usize"], - result: "void", - nonblocking: false, - }, - test_str_ret: { parameters: [], result: "buffer", nonblocking: false }, - test_tag_and_content: { - parameters: ["buffer", "usize"], - result: "i32", - nonblocking: false, - }, +const { symbols } = dlopen('./target/debug/libdeno_bindgen_test.dylib', { + add: { + parameters: [ + 'i32', + 'i32', + ], + result: 'i32', + nonblocking: false }, -) -/** - * Doc comment for `Input` struct. - * ...testing multiline - */ -export type Input = { - /** - * Doc comments get - * transformed to JS doc - * comments. - */ - a: number - b: number -} -export type MyStruct = { - arr: Array -} -export type OptionStruct = { - maybe: string | undefined | null -} -export type PlainEnum = - | { - a: { - _a: string - } - } - | "b" - | "c" -export type TagAndContent = - | { key: "A"; value: { b: number } } - | { key: "C"; value: { d: number } } -export type TestLifetimeEnums = { - Text: { - _text: string - } -} -export type TestLifetimeWrap = { - _a: TestLifetimeEnums -} -export type TestLifetimes = { - text: string -} -export type TestReservedField = { - type: number - ref: number -} -export type WithRecord = { - my_map: Record -} -export function add(a0: number, a1: number) { - const rawResult = symbols.add(a0, a1) - const result = rawResult - return result -} -export function add2(a0: Input) { - const a0_buf = encode(JSON.stringify(a0)) - - const rawResult = symbols.add2(a0_buf, a0_buf.byteLength) - const result = rawResult - return result -} -export function add3(a0: number, a1: number) { - const rawResult = symbols.add3(a0, a1) - const result = rawResult - return result -} -export function add4(a0: number, a1: number) { - const rawResult = symbols.add4(a0, a1) - const result = rawResult - return result -} -export function add5(a0: Uint8Array, a1: Uint8Array) { - const a0_buf = encode(a0) - const a1_buf = encode(a1) - - const rawResult = symbols.add5( - a0_buf, - a0_buf.byteLength, - a1_buf, - a1_buf.byteLength, + add2: { + parameters: [ + 'i32', + 'i32', + ], + 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 + }, +}); + +export function add( + arg0: number, + arg1: number, +): number { + return symbols.add( + arg0, + arg1, ) - const result = readPointer(rawResult) - return result } -export function add6(a0: Uint8Array, a1: Uint8Array) { - const a0_buf = encode(a0) - const a1_buf = encode(a1) - const rawResult = symbols.add6( - a0_buf, - a0_buf.byteLength, - a1_buf, - a1_buf.byteLength, +export function add2( + arg0: number, + arg1: number, +): number { + return symbols.add2( + arg0, + arg1, ) - const result = readPointer(rawResult) - return result -} -export function sleep(a0: bigint) { - const rawResult = symbols.sleep(a0) - const result = rawResult - return result -} -export function test_buf(a0: Uint8Array) { - const a0_buf = encode(a0) - - const rawResult = symbols.test_buf(a0_buf, a0_buf.byteLength) - const result = rawResult - return result } -export function test_buffer_return(a0: Uint8Array) { - const a0_buf = encode(a0) - const rawResult = symbols.test_buffer_return(a0_buf, a0_buf.byteLength) - const result = readPointer(rawResult) - return result -} -export function test_buffer_return_async(a0: Uint8Array) { - const a0_buf = encode(a0) - - const rawResult = symbols.test_buffer_return_async(a0_buf, a0_buf.byteLength) - const result = rawResult.then(readPointer) - return result -} -export function test_hashmap() { - const rawResult = symbols.test_hashmap() - const result = readPointer(rawResult) - return JSON.parse(decode(result)) as WithRecord -} -export function test_lifetime(a0: TestLifetimes) { - const a0_buf = encode(JSON.stringify(a0)) - - const rawResult = symbols.test_lifetime(a0_buf, a0_buf.byteLength) - const result = rawResult - return result -} -export function test_manual_ptr() { - const rawResult = symbols.test_manual_ptr() - const result = readPointer(rawResult) - return result -} -export function test_manual_ptr_async() { - const rawResult = symbols.test_manual_ptr_async() - const result = rawResult.then(readPointer) - return result +export function bytelen( + arg0: Uint8Array, +): number { + return symbols.bytelen( + arg0, + arg0.byteLength, + ) } -export function test_mixed(a0: bigint, a1: Input) { - const a1_buf = encode(JSON.stringify(a1)) - const rawResult = symbols.test_mixed(a0, a1_buf, a1_buf.byteLength) - const result = rawResult - return result +export function buf_mut( + arg0: Uint8Array, +): void { + return symbols.buf_mut( + arg0, + arg0.byteLength, + ) } -export function test_mixed_order(a0: number, a1: Input, a2: number) { - const a1_buf = encode(JSON.stringify(a1)) - const rawResult = symbols.test_mixed_order(a0, a1_buf, a1_buf.byteLength, a2) - const result = rawResult - return result +export function cstr(): Deno.PointerObject | null { + return symbols.cstr() } -export function test_mut_buf(a0: Uint8Array) { - const a0_buf = encode(a0) - const rawResult = symbols.test_mut_buf(a0_buf, a0_buf.byteLength) - const result = rawResult - return result -} -export function test_output() { - const rawResult = symbols.test_output() - const result = readPointer(rawResult) - return JSON.parse(decode(result)) as Input -} -export function test_output_async() { - const rawResult = symbols.test_output_async() - const result = rawResult.then(readPointer) - return result.then(r => JSON.parse(decode(r))) as Promise -} -export function test_reserved_field() { - const rawResult = symbols.test_reserved_field() - const result = readPointer(rawResult) - return JSON.parse(decode(result)) as TestReservedField +export function strlen( + arg0: Deno.PointerObject | null, +): number { + return symbols.strlen( + arg0, + ) } -export function test_serde(a0: MyStruct) { - const a0_buf = encode(JSON.stringify(a0)) - const rawResult = symbols.test_serde(a0_buf, a0_buf.byteLength) - const result = rawResult - return result +export function non_blocking(): Promise { + return symbols.non_blocking() } -export function test_str(a0: string) { - const a0_buf = encode(a0) - const rawResult = symbols.test_str(a0_buf, a0_buf.byteLength) - const result = rawResult - return result -} -export function test_str_ret() { - const rawResult = symbols.test_str_ret() - const result = readPointer(rawResult) - return decode(result) -} -export function test_tag_and_content(a0: TagAndContent) { - const a0_buf = encode(JSON.stringify(a0)) - - const rawResult = symbols.test_tag_and_content(a0_buf, a0_buf.byteLength) - const result = rawResult - return result -} diff --git a/example/bindings_test.ts b/example/bindings_test.ts index 343baeb..d4f58a4 100644 --- a/example/bindings_test.ts +++ b/example/bindings_test.ts @@ -1,31 +1,11 @@ import { add, add2, - add3, - add4, - add5, - add6, - OptionStruct, - sleep, - test_buf, - test_buffer_return, - test_buffer_return_async, - test_hashmap, - test_lifetime, - test_manual_ptr, - test_manual_ptr_async, - test_mixed, - test_mixed_order, - test_mut_buf, - test_output, - test_output_async, - test_reserved_field, - test_serde, - test_str, - test_str_ret, - test_tag_and_content, - TestReservedField, - WithRecord, + bytelen, + buf_mut, + cstr, + strlen, + non_blocking, } from "./bindings/bindings.ts"; import { assert, assertEquals } from "https://deno.land/std/testing/asserts.ts"; @@ -33,222 +13,48 @@ Deno.test({ name: "add#test", fn: () => { assertEquals(add(1, 2), 3); - assertEquals(add(-1, 1), 0); + assertEquals(add2(-1, 1), 0); }, }); Deno.test({ - name: "add2#test", + name: "bytelen#test", fn: () => { - assertEquals(add2({ a: 1, b: 2 }), 3); + assertEquals(bytelen(new TextEncoder().encode("hello")), 5); }, }); Deno.test({ - name: "add#test3", + name: "buf_mut#test", fn: () => { - // If the argument type of Rust is f32, the calculation result may be different. - // Number in Java Script is float64, when data is passed to Rust, it becomes float32, so the number may change. - // e.g: `1.3 + 1.5` will be `2.799999952316284` - assertEquals(add3(1.5, 1.5), 3.0); - }, + const buf = new Uint8Array(1); + buf_mut(buf); + assertEquals(buf[0], 99); + } }); Deno.test({ - name: "add#test4", + name: "cstr#test", fn: () => { - assertEquals(add4(1.5, 1.3), 2.8); + const ptr = cstr(); + const str = Deno.UnsafePointerView.getCString(ptr!); + assertEquals(str, "Hello, World!"); }, }); Deno.test({ - name: "add#test5", + name: "strlen#test", fn: () => { - assertEquals( - add5(new Uint8Array([1, 2]), new Uint8Array([3, 4])), - new Uint8Array([1, 2, 3, 4]), - ); + const ptr = strlen(cstr()); + assertEquals(ptr, 13); }, }); -Deno.test({ - name: "add#test6", - fn: () => { - assertEquals( - add6(new Uint8Array([1, 2]), new Uint8Array([3, 4])), - new Uint8Array([1, 2, 3, 4]), - ); - }, -}); Deno.test({ - name: "sleep#test", + name: "non_blocking#test", fn: async () => { - const now = performance.now(); - await sleep(50n); - assert(performance.now() - now > 50); - }, -}); - -Deno.test({ - name: "test_mixed#test", - fn: () => { - assertEquals(test_mixed(10n, { a: 10, b: 20 }), 20); + const result = await non_blocking(); + assertEquals(result, 42); }, -}); - -Deno.test({ - name: "test_serde#test", - fn: () => { - assertEquals(test_serde({ arr: ["IT", "WORKS"] }), 1); - }, -}); - -Deno.test({ - name: "test_mixed_order#test", - fn: () => { - assertEquals(test_mixed_order(10, { a: 10, b: 0 }, 10), 30); - }, -}); - -Deno.test({ - name: "test_options#test", - fn: () => { - let opts: OptionStruct = { maybe: " " }; - opts.maybe = null; - opts.maybe = undefined; - }, -}); - -Deno.test({ - name: "test_str#test", - fn: () => { - let str = "Hello, World!"; - test_str(str); - }, -}); - -Deno.test({ - name: "test_buf#test", - fn: () => { - let buf = new Uint8Array([1, 0, 1]); - assertEquals(test_buf(buf), 1); - }, -}); - -Deno.test({ - name: "test_mut_buf#test", - fn: () => { - let u8 = new Uint8Array([0, 1, 2]); - assertEquals(u8[0], 0); - - test_mut_buf(u8); - assertEquals(u8[0], 69); - }, -}); - -Deno.test({ - name: "test_lifetime_struct#test", - fn: () => { - const TEXT = "Hello, World!"; - assertEquals(test_lifetime({ text: TEXT }), TEXT.length); - }, -}); - -Deno.test({ - name: "test_tag_and_content#test", - fn: () => { - assertEquals(test_tag_and_content({ key: "A", value: { b: 10 } }), 10); - - // test_tag_and_content returns -1 when enum variant isn't TagAndContent::A - assertEquals(test_tag_and_content({ key: "C", value: { d: 10 } }), -1); - }, -}); - -Deno.test({ - name: "test_buffer_return#test", - fn: () => { - const buf = test_buffer_return(new Uint8Array([1, 2, 3])); - - assertEquals(buf.byteLength, 3); - assertEquals(buf[0], 1); - assertEquals(buf[1], 2); - assertEquals(buf[2], 3); - }, -}); - -Deno.test({ - name: "test_buffer_return_async#test", - fn: async () => { - const buf = await test_buffer_return_async(new Uint8Array([1, 2, 3])); - - assertEquals(buf.byteLength, 3); - assertEquals(buf[0], 1); - assertEquals(buf[1], 2); - assertEquals(buf[2], 3); - }, -}); - -Deno.test({ - name: "test_manual_ptr#test", - fn: () => { - const buf = test_manual_ptr(); - const val = new TextDecoder().decode(buf); - - assertEquals(val, "test"); - }, -}); - -Deno.test({ - name: "test_manual_ptr_async#test", - fn: async () => { - const buf = await test_manual_ptr_async(); - const val = new TextDecoder().decode(buf); - - assertEquals(val, "test"); - }, -}); - -Deno.test({ - name: "test_output#test", - fn: () => { - const obj = test_output(); - - assertEquals(obj.a, 1); - assertEquals(obj.b, 2); - }, -}); - -Deno.test({ - name: "test_output_async#test", - fn: async () => { - const obj = await test_output_async(); - - assertEquals(obj.a, 3); - assertEquals(obj.b, 4); - }, -}); - -Deno.test({ - name: "test_reserved_field#test", - fn: () => { - const obj = test_reserved_field(); - - assertEquals(obj.type, 1); - assertEquals(obj.ref, 2); - }, -}); - -Deno.test({ - name: "test_str_ret#test", - fn: () => { - assertEquals(test_str_ret(), "🦕"); - }, -}); - -Deno.test({ - name: "test_hashmap#test", - fn: () => { - assertEquals(test_hashmap(), { my_map: { key: "value" } } as WithRecord); - }, -}); +}); \ No newline at end of file diff --git a/example/src/lib.rs b/example/src/lib.rs index 75c0aca..57ed067 100644 --- a/example/src/lib.rs +++ b/example/src/lib.rs @@ -1,5 +1,4 @@ use deno_bindgen::deno_bindgen; -use std::collections::HashMap; // Test "primitives" #[deno_bindgen] @@ -7,228 +6,38 @@ fn add(a: i32, b: i32) -> i32 { a + b } -// Test Structs #[deno_bindgen] -/// Doc comment for `Input` struct. -/// ...testing multiline -pub struct Input { - /// Doc comments get - /// transformed to JS doc - /// comments. - a: i32, - b: i32, -} - -#[deno_bindgen] -fn add2(input: Input) -> i32 { - input.a + input.b -} - -#[deno_bindgen] -fn add3(a: f32, b: f32) -> f32 { +fn add2(a: i32, b: i32) -> i32 { a + b } #[deno_bindgen] -fn add4(a: f64, b: f64) -> f64 { - a + b -} - -#[deno_bindgen] -fn add5(a: &[u8], b: &[u8]) -> Vec { - [a, b].concat() -} - -#[deno_bindgen] -fn add6(a: &[u8], b: &[u8]) -> Box<[u8]> { - [a, b].concat().into() -} - -// Test mixed types -#[deno_bindgen] -fn test_mixed(a: isize, b: Input) -> i32 { - a as i32 + b.a -} - -// Test mixed type codegen order -#[deno_bindgen] -fn test_mixed_order(a: i32, b: Input, c: i32) -> i32 { - a + b.a + c -} - -// Test serde support -#[deno_bindgen] -struct MyStruct { - arr: Vec, -} - -#[deno_bindgen] -fn test_serde(s: MyStruct) -> u8 { - if s.arr.contains(&"WORKS".to_string()) { - return 1; - } - 0 -} - -// Typescript codegen tests -#[deno_bindgen] -struct OptionStruct { - #[allow(dead_code)] - maybe: Option, -} - -// Test non_blocking -#[deno_bindgen(non_blocking)] -fn sleep(ms: u64) { - std::thread::sleep(std::time::Duration::from_millis(ms)); -} - -// Test other buffer dependent -// types. -#[deno_bindgen] -fn test_str(_s: &str) {} - -#[deno_bindgen] -fn test_buf(b: &[u8]) -> u8 { - b[0] -} - -#[deno_bindgen] -#[serde(rename_all = "lowercase")] -enum PlainEnum { - A { _a: String }, - B, - C, -} - -// Test mut buffer -#[deno_bindgen] -fn test_mut_buf(buf: &mut [u8]) { - buf[0] = 69; -} - -// Test mut buffer prevent return -// #[deno_bindgen] -// fn test_mut_buf_ret(buf: &mut [u8]) -> &mut [u8] { -// buf -// } - -// Test mut buffer musn't outlive symbol call -// #[deno_bindgen] -// fn test_mut_buf_outlive(_: &'static mut [u8]) { -// -// } - -#[deno_bindgen] -struct TestLifetimes<'l> { - text: &'l str, +fn bytelen(b: &[u8]) -> u32 { + b.len() as u32 } #[deno_bindgen] -enum TestLifetimeEnums<'a> { - Text { _text: &'a str }, +fn buf_mut(b: &mut [u8]) { + b[0] = 99; } #[deno_bindgen] -struct TestLifetimeWrap<'a> { - #[serde(borrow)] - _a: TestLifetimeEnums<'a>, +fn cstr() -> *const u8 { + b"Hello, World!\0".as_ptr() } #[deno_bindgen] -fn test_lifetime<'l>(s: TestLifetimes<'l>) -> usize { - s.text.len() -} - -#[deno_bindgen] -#[serde(tag = "key", content = "value")] -pub enum TagAndContent { - A { b: i32 }, - C { d: i32 }, -} - -#[deno_bindgen] -fn test_tag_and_content(arg: TagAndContent) -> i32 { - if let TagAndContent::A { b } = arg { - b - } else { - -1 +fn strlen(s: *const u8) -> u32 { + let mut len = 0; + unsafe { + while *s.add(len as usize) != 0 { + len += 1; + } } -} - -#[deno_bindgen] -fn test_buffer_return(buf: &[u8]) -> &[u8] { - buf -} - -#[deno_bindgen(non_blocking)] -fn test_buffer_return_async(buf: &[u8]) -> &[u8] { - buf -} - -#[deno_bindgen] -fn test_manual_ptr() -> *const u8 { - let result = String::from("test").into_bytes(); - let length = (result.len() as u32).to_be_bytes(); - let mut v = length.to_vec(); - v.extend(result.clone()); - - let ret = v.as_ptr(); - // Leak the result to JS land. - ::std::mem::forget(v); - ret -} - -#[deno_bindgen(non_blocking)] -fn test_manual_ptr_async() -> *const u8 { - let result = String::from("test").into_bytes(); - let length = (result.len() as u32).to_be_bytes(); - let mut v = length.to_vec(); - v.extend(result.clone()); - - let ret = v.as_ptr(); - // Leak the result to JS land. - ::std::mem::forget(v); - ret -} - -#[deno_bindgen] -fn test_output() -> Input { - Input { a: 1, b: 2 } + len } #[deno_bindgen(non_blocking)] -fn test_output_async() -> Input { - Input { a: 3, b: 4 } -} - -#[deno_bindgen] -struct TestReservedField { - r#type: u8, - r#ref: u8, -} - -#[deno_bindgen] -fn test_reserved_field() -> TestReservedField { - TestReservedField { - r#type: 1, - r#ref: 2, - } -} - -#[deno_bindgen] -fn test_str_ret() -> String { - String::from("🦕") -} - -#[deno_bindgen] -pub struct WithRecord { - my_map: HashMap, -} - -#[deno_bindgen] -fn test_hashmap() -> WithRecord { - let mut map = HashMap::new(); - map.insert("key".to_string(), "value".to_string()); - WithRecord { my_map: map } -} +fn non_blocking() -> i32 { + 42 +} \ No newline at end of file From 0ff9b3e8273d5e2f3744c24828b1d86b230eaf16 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Tue, 7 Nov 2023 22:40:19 +0530 Subject: [PATCH 03/15] impl classes initial sketch --- deno_bindgen/lib.rs | 6 +- deno_bindgen_ir/codegen/deno.rs | 109 +++++++++++++++++++----------- deno_bindgen_ir/codegen/mod.rs | 4 +- deno_bindgen_ir/inventory.rs | 13 ++++ deno_bindgen_ir/lib.rs | 5 +- deno_bindgen_macro/src/fn_.rs | 7 +- deno_bindgen_macro/src/impl_.rs | 46 +++++++++++++ deno_bindgen_macro/src/lib.rs | 22 ++++-- deno_bindgen_macro/src/struct_.rs | 25 +++++++ deno_bindgen_macro/src/util.rs | 4 ++ example/bindings_test.ts | 6 +- example/s.ts | 28 ++++++++ example/src/lib.rs | 12 ++++ 13 files changed, 232 insertions(+), 55 deletions(-) create mode 100644 deno_bindgen_ir/inventory.rs create mode 100644 deno_bindgen_macro/src/impl_.rs create mode 100644 deno_bindgen_macro/src/struct_.rs create mode 100644 example/s.ts diff --git a/deno_bindgen/lib.rs b/deno_bindgen/lib.rs index 523d2ef..e4b6e1f 100644 --- a/deno_bindgen/lib.rs +++ b/deno_bindgen/lib.rs @@ -7,7 +7,11 @@ pub use linkme; use linkme::distributed_slice; #[distributed_slice] -pub static INVENTORY: [Symbol]; +pub static INVENTORY: [Inventory]; + +pub trait BindgenType { + fn type_name() -> &'static str; +} #[no_mangle] fn init_deno_bindgen(opt: Options) { diff --git a/deno_bindgen_ir/codegen/deno.rs b/deno_bindgen_ir/codegen/deno.rs index 64b5cfb..0818c2b 100644 --- a/deno_bindgen_ir/codegen/deno.rs +++ b/deno_bindgen_ir/codegen/deno.rs @@ -3,8 +3,10 @@ use std::{ io::{Result, Write}, }; +use syn::token::In; + use super::Generator; -use crate::{Symbol, Type}; +use crate::{inventory::Inventory, Symbol, Type}; struct TypeScriptType<'a>(&'a str); @@ -93,11 +95,11 @@ impl From for DenoFfiType { } pub struct Codegen<'a> { - symbols: &'a [Symbol], + symbols: &'a [Inventory], } impl<'a> Codegen<'a> { - pub fn new(symbols: &'a [Symbol]) -> Self { + pub fn new(symbols: &'a [Inventory]) -> Self { Self { symbols } } @@ -132,21 +134,26 @@ impl<'a> Codegen<'a> { } for symbol in self.symbols { - writeln!(writer, " {}: {{", symbol.name)?; - write!(writer, " parameters: ")?; - format_bracket(writer, symbol.parameters, |writer, parameters| { - for parameter in parameters { - writeln!(writer, " {},", DenoFfiType::from(*parameter))?; + match symbol { + Inventory::Symbol(symbol) => { + writeln!(writer, " {}: {{", symbol.name)?; + write!(writer, " parameters: ")?; + format_bracket(writer, symbol.parameters, |writer, parameters| { + for parameter in parameters { + writeln!(writer, " {},", DenoFfiType::from(*parameter))?; + } + Ok(()) + })?; + writeln!( + writer, + " result: {},", + DenoFfiType::from(symbol.return_type) + )?; + writeln!(writer, " nonblocking: {}", symbol.non_blocking)?; + writeln!(writer, " }},")?; } - Ok(()) - })?; - writeln!( - writer, - " result: {},", - DenoFfiType::from(symbol.return_type) - )?; - writeln!(writer, " nonblocking: {}", symbol.non_blocking)?; - writeln!(writer, " }},")?; + _ => {} + } } Ok(()) @@ -166,39 +173,59 @@ impl<'a> Codegen<'a> { write!(writer, "{:indent$})", "", indent = nesting_spaces)?; } else { write!(writer, ")")?; - } + } Ok(()) } for symbol in self.symbols { - write!(writer, "export function {}", symbol.name)?; - format_paren(writer, symbol.parameters, |writer, parameters| { - for (i, parameter) in parameters.iter().enumerate() { + match symbol { + Inventory::Symbol(symbol) => { + write!(writer, "export function {}", symbol.name)?; + format_paren( + writer, + symbol.parameters, + |writer, parameters| { + for (i, parameter) in parameters.iter().enumerate() { + writeln!( + writer, + " arg{}: {},", + i, + TypeScriptType::from(*parameter) + )?; + } + Ok(()) + }, + 0, + )?; writeln!( writer, - " arg{}: {},", - i, - TypeScriptType::from(*parameter) + ": {} {{", + TypeScriptType::from(symbol.return_type) + .apply_promise(symbol.non_blocking) + )?; + write!(writer, " return symbols.{}", symbol.name)?; + format_paren( + writer, + symbol.parameters, + |writer, parameters| { + for (i, parameter) in parameters.iter().enumerate() { + let ident = format!("arg{}", i); + writeln!( + writer, + " {},", + TypeScriptType::from(*parameter).into_raw(&ident) + )?; + } + Ok(()) + }, + 2, )?; - } - Ok(()) - }, 0)?; - writeln!(writer, ": {} {{", TypeScriptType::from(symbol.return_type).apply_promise(symbol.non_blocking))?; - write!(writer, " return symbols.{}", symbol.name)?; - format_paren(writer, symbol.parameters, |writer, parameters| { - for (i, parameter) in parameters.iter().enumerate() { - let ident = format!("arg{}", i); - writeln!( - writer, - " {},", - TypeScriptType::from(*parameter).into_raw(&ident) - )?; - } - Ok(()) - }, 2)?; - writeln!(writer, "\n}}\n")?; + writeln!(writer, "\n}}\n")?; + } + _ => {} + } } Ok(()) diff --git a/deno_bindgen_ir/codegen/mod.rs b/deno_bindgen_ir/codegen/mod.rs index b3c2698..6c39349 100644 --- a/deno_bindgen_ir/codegen/mod.rs +++ b/deno_bindgen_ir/codegen/mod.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use crate::Symbol; +use crate::{inventory::Inventory, Symbol}; mod deno; @@ -18,7 +18,7 @@ pub trait Generator { } pub fn generate( - symbols: &'static [Symbol], + symbols: &'static [Inventory], opt: Options, ) -> std::io::Result<()> { let mut codegen = match opt.target { diff --git a/deno_bindgen_ir/inventory.rs b/deno_bindgen_ir/inventory.rs new file mode 100644 index 0000000..8609608 --- /dev/null +++ b/deno_bindgen_ir/inventory.rs @@ -0,0 +1,13 @@ +use crate::Symbol; + +#[derive(Debug)] +pub struct Struct { + pub name: &'static str, + pub constructor: Option, + pub methods: &'static [Symbol], +} + +pub enum Inventory { + Symbol(Symbol), + Struct(Struct), +} diff --git a/deno_bindgen_ir/lib.rs b/deno_bindgen_ir/lib.rs index 56ab6ac..cede4c7 100644 --- a/deno_bindgen_ir/lib.rs +++ b/deno_bindgen_ir/lib.rs @@ -1,8 +1,11 @@ use proc_macro2::Ident; -use quote::{format_ident, quote, ToTokens}; +use quote::{quote, ToTokens}; use syn::{parse_quote, Pat}; pub mod codegen; +pub mod inventory; + +pub use inventory::Inventory; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)] pub enum Type { diff --git a/deno_bindgen_macro/src/fn_.rs b/deno_bindgen_macro/src/fn_.rs index 10bede6..9a2240a 100644 --- a/deno_bindgen_macro/src/fn_.rs +++ b/deno_bindgen_macro/src/fn_.rs @@ -8,7 +8,10 @@ use syn::{ ItemFn, PatType, ReturnType, TypePath, TypePtr, TypeReference, TypeSlice, }; -use crate::{util::{Error, Result}, FnAttributes}; +use crate::{ + util::{Error, Result}, + FnAttributes, +}; fn parse_type(ty: &Box) -> Result { match **ty { @@ -168,7 +171,7 @@ pub fn handle(fn_: ItemFn, attrs: FnAttributes) -> Result { Ok(quote::quote! { const _: () = { #[deno_bindgen::linkme::distributed_slice(deno_bindgen::INVENTORY)] - pub static _A: deno_bindgen::Symbol = #symbol; + pub static _A: deno_bindgen::Inventory = deno_bindgen::Inventory::Symbol(#symbol); }; #[no_mangle] diff --git a/deno_bindgen_macro/src/impl_.rs b/deno_bindgen_macro/src/impl_.rs new file mode 100644 index 0000000..199c511 --- /dev/null +++ b/deno_bindgen_macro/src/impl_.rs @@ -0,0 +1,46 @@ +use proc_macro2::TokenStream as TokenStream2; +use syn::ItemImpl; + +use crate::util::{self, Result}; + +pub fn handle(impl_: ItemImpl) -> Result { + if impl_.generics.params.first().is_some() { + return Err(util::Error::Generics); + } + + if impl_.generics.where_clause.is_some() { + return Err(util::Error::WhereClause); + } + + let self_ty = match *impl_.self_ty { + syn::Type::Path(ref type_path) => type_path.path.clone(), + _ => return Err(util::Error::UnsupportedType), + }; + + let ref ty_str @ _ = self_ty.get_ident().unwrap(); + + // TODO: + // - create a new quoted function for each method and codegen using fn_::handle + // where first arg is self ptr and rest are method args + // - constructor is a simply special case with no self ptr. + // - we also need to be aware of &mut self and Self types. + + Ok(quote::quote! { + #impl_ + + const _: () = { + // Assert that the type implements `BindgenType`. + const fn _assert_impl() {} + _assert_impl::<#ty_str>(); + + #[deno_bindgen::linkme::distributed_slice(deno_bindgen::INVENTORY)] + pub static _B: deno_bindgen::Inventory = deno_bindgen::Inventory::Struct( + deno_bindgen::inventory::Struct { + name: stringify!(#ty_str), + constructor: None, + methods: &[], + } + ); + }; + }) +} diff --git a/deno_bindgen_macro/src/lib.rs b/deno_bindgen_macro/src/lib.rs index be37273..be5230f 100644 --- a/deno_bindgen_macro/src/lib.rs +++ b/deno_bindgen_macro/src/lib.rs @@ -3,9 +3,13 @@ use deno_bindgen_ir::Symbol; use proc_macro::TokenStream; use quote::{format_ident, quote}; -use syn::{parse_macro_input, parse_quote, ItemFn, meta::ParseNestedMeta}; +use syn::{ + meta::ParseNestedMeta, parse2, parse_macro_input, parse_quote, Item, ItemFn, +}; mod fn_; +mod impl_; +mod struct_; mod util; #[derive(Default)] @@ -26,10 +30,16 @@ impl FnAttributes { #[proc_macro_attribute] pub fn deno_bindgen(args: TokenStream, input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as ItemFn); - let mut attrs = FnAttributes::default(); - let attrs_parser = syn::meta::parser(|meta| attrs.parse(meta)); - parse_macro_input!(args with attrs_parser); + match parse2::(input.into()).unwrap() { + Item::Fn(input) => { + let mut attrs = FnAttributes::default(); + let attrs_parser = syn::meta::parser(|meta| attrs.parse(meta)); + parse_macro_input!(args with attrs_parser); - fn_::handle(input, attrs).unwrap().into() + fn_::handle(input, attrs).unwrap().into() + } + Item::Struct(input) => struct_::handle(input).unwrap().into(), + Item::Impl(input) => impl_::handle(input).unwrap().into(), + _ => panic!("only functions are supported"), + } } diff --git a/deno_bindgen_macro/src/struct_.rs b/deno_bindgen_macro/src/struct_.rs new file mode 100644 index 0000000..9034bdc --- /dev/null +++ b/deno_bindgen_macro/src/struct_.rs @@ -0,0 +1,25 @@ +use proc_macro2::TokenStream as TokenStream2; +use syn::ItemStruct; + +use crate::util::{self, Result}; + +pub fn handle(struct_: ItemStruct) -> Result { + if struct_.generics.params.first().is_some() { + return Err(util::Error::Generics); + } + + if struct_.generics.where_clause.is_some() { + return Err(util::Error::WhereClause); + } + + let ref ty_str @ _ = struct_.ident; + Ok(quote::quote! { + #struct_ + + impl ::deno_bindgen::BindgenType for #ty_str { + fn type_name() -> &'static str { + stringify!(#ty_str) + } + } + }) +} diff --git a/deno_bindgen_macro/src/util.rs b/deno_bindgen_macro/src/util.rs index ec5c196..4ed673e 100644 --- a/deno_bindgen_macro/src/util.rs +++ b/deno_bindgen_macro/src/util.rs @@ -3,6 +3,8 @@ pub enum Error { Asyncness, Reciever, UnsupportedType, + Generics, + WhereClause, } impl std::fmt::Display for Error { @@ -11,6 +13,8 @@ impl std::fmt::Display for Error { Error::Asyncness => write!(f, "async functions are not supported"), Error::Reciever => write!(f, "methods are not supported"), Error::UnsupportedType => write!(f, "unsupported type"), + Error::Generics => write!(f, "generics are not supported"), + Error::WhereClause => write!(f, "where clauses are not supported"), } } } diff --git a/example/bindings_test.ts b/example/bindings_test.ts index d4f58a4..92107ab 100644 --- a/example/bindings_test.ts +++ b/example/bindings_test.ts @@ -50,11 +50,13 @@ Deno.test({ }, }); - Deno.test({ name: "non_blocking#test", fn: async () => { const result = await non_blocking(); assertEquals(result, 42); }, -}); \ No newline at end of file +}); + +// struct (glorified pointers) +// impl on struct \ No newline at end of file diff --git a/example/s.ts b/example/s.ts new file mode 100644 index 0000000..c420a67 --- /dev/null +++ b/example/s.ts @@ -0,0 +1,28 @@ +class Example { + ptr: Deno.PointerObject | null = null; + + static from_ptr(ptr: Deno.PointerObject | null) { + const obj = Object.create(Example.prototype); + obj.ptr = ptr; + + return obj; + } + + constructor() { + // ... + } + + close() { + ffi_free(this.ptr); + } + + [Symbol.dispose]() { + this.close(); + } +} + +export function make_example(): Example { + return Example.from_ptr(ffi_make_example()); +} + +using obj = make_example(); diff --git a/example/src/lib.rs b/example/src/lib.rs index 57ed067..2f0383a 100644 --- a/example/src/lib.rs +++ b/example/src/lib.rs @@ -40,4 +40,16 @@ fn strlen(s: *const u8) -> u32 { #[deno_bindgen(non_blocking)] fn non_blocking() -> i32 { 42 +} + +#[deno_bindgen] +pub struct Foo { + internal: i32, +} + +#[deno_bindgen] +impl Foo { + fn new() -> Self { + Self { internal: 42 } + } } \ No newline at end of file From 25c52f223bd089eefb26d16fc3b2ce489fa0938d Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Wed, 8 Nov 2023 16:58:55 +0530 Subject: [PATCH 04/15] impl part 2 --- deno_bindgen_ir/codegen/deno.rs | 101 +++++++++++++++++++++++++++++--- deno_bindgen_ir/lib.rs | 9 +++ deno_bindgen_macro/src/fn_.rs | 30 +++++++--- deno_bindgen_macro/src/impl_.rs | 44 +++++++++++++- deno_bindgen_macro/src/lib.rs | 1 + example/bindings/bindings.ts | 55 +++++++++++++++++ example/src/lib.rs | 8 ++- 7 files changed, 224 insertions(+), 24 deletions(-) diff --git a/deno_bindgen_ir/codegen/deno.rs b/deno_bindgen_ir/codegen/deno.rs index 0818c2b..a1e89a0 100644 --- a/deno_bindgen_ir/codegen/deno.rs +++ b/deno_bindgen_ir/codegen/deno.rs @@ -3,10 +3,11 @@ use std::{ io::{Result, Write}, }; -use syn::token::In; - use super::Generator; -use crate::{inventory::Inventory, Symbol, Type}; +use crate::{ + inventory::{Inventory, Struct}, + Symbol, Type, +}; struct TypeScriptType<'a>(&'a str); @@ -163,16 +164,19 @@ impl<'a> Codegen<'a> { fn format_paren( writer: &mut W, items: &[T], + allow_empty: bool, callback: impl Fn(&mut W, &[T]) -> Result<()>, nesting_spaces: usize, + delim: (char, char), ) -> Result<()> { - write!(writer, "(")?; - if items.len() > 0 { + let (start, end) = delim; + write!(writer, "{start}")?; + if items.len() > 0 || allow_empty { write!(writer, "\n")?; callback(writer, items)?; - write!(writer, "{:indent$})", "", indent = nesting_spaces)?; + write!(writer, "{:indent$}{end}", "", indent = nesting_spaces)?; } else { - write!(writer, ")")?; + write!(writer, "{end}")?; } Ok(()) @@ -181,10 +185,14 @@ impl<'a> Codegen<'a> { for symbol in self.symbols { match symbol { Inventory::Symbol(symbol) => { - write!(writer, "export function {}", symbol.name)?; + if !symbol.internal { + write!(writer, "export ")?; + } + write!(writer, "function {}", symbol.name)?; format_paren( writer, symbol.parameters, + false, |writer, parameters| { for (i, parameter) in parameters.iter().enumerate() { writeln!( @@ -197,6 +205,7 @@ impl<'a> Codegen<'a> { Ok(()) }, 0, + ('(', ')'), )?; writeln!( writer, @@ -208,6 +217,7 @@ impl<'a> Codegen<'a> { format_paren( writer, symbol.parameters, + false, |writer, parameters| { for (i, parameter) in parameters.iter().enumerate() { let ident = format!("arg{}", i); @@ -220,11 +230,84 @@ impl<'a> Codegen<'a> { Ok(()) }, 2, + ('(', ')'), )?; writeln!(writer, "\n}}\n")?; } - _ => {} + Inventory::Struct(Struct { + name, + methods, + constructor, + }) => { + write!(writer, "export class {name} ")?; + + format_paren( + writer, + &methods, + false, + |writer, methods| { + writeln!(writer, " ptr: Deno.PointerObject | null = null;\n")?; + + writeln!( + writer, + " static __constructor(ptr: Deno.PointerObject) {{" + )?; + writeln!( + writer, + " const self = Object.create({name}.prototype);" + )?; + writeln!(writer, " self.ptr = ptr;")?; + writeln!(writer, " return self;")?; + writeln!(writer, " }}")?; + + for method in methods { + // Skip the first argument, which is always the pointer to the struct. + let parameters = &method.parameters[1..]; + writeln!( + writer, + "\n {name}({parameters}): {return_type} {{", + name = method.name, + parameters = parameters + .iter() + .enumerate() + .map(|(i, parameter)| { + format!("arg{}: {}", i, TypeScriptType::from(*parameter)) + }) + .collect::>() + .join(", "), + return_type = TypeScriptType::from(method.return_type) + )?; + + write!(writer, " return {}", method.name)?; + format_paren( + writer, + parameters, + true, + |writer, parameters| { + writeln!(writer, " this.ptr,",)?; + for (i, parameter) in parameters.iter().enumerate() { + let ident = format!("arg{}", i); + writeln!( + writer, + " {},", + TypeScriptType::from(*parameter).into_raw(&ident) + )?; + } + Ok(()) + }, + 4, + ('(', ')'), + )?; + + writeln!(writer, "\n }}")?; + } + Ok(()) + }, + 0, + ('{', '}'), + )?; + } } } diff --git a/deno_bindgen_ir/lib.rs b/deno_bindgen_ir/lib.rs index cede4c7..bcc9567 100644 --- a/deno_bindgen_ir/lib.rs +++ b/deno_bindgen_ir/lib.rs @@ -112,6 +112,7 @@ pub struct Symbol { pub parameters: &'static [Type], pub return_type: Type, pub non_blocking: bool, + pub internal: bool, } pub struct SymbolBuilder { @@ -119,6 +120,7 @@ pub struct SymbolBuilder { parameters: Vec, return_type: Type, non_blocking: bool, + internal: bool, } impl SymbolBuilder { @@ -128,6 +130,7 @@ impl SymbolBuilder { parameters: Vec::new(), return_type: Default::default(), non_blocking: false, + internal: false, } } @@ -142,6 +145,10 @@ impl SymbolBuilder { pub fn non_blocking(&mut self, non_blocking: bool) { self.non_blocking = non_blocking; } + + pub fn internal(&mut self, internal: bool) { + self.internal = internal; + } } impl ToTokens for SymbolBuilder { @@ -154,6 +161,7 @@ impl ToTokens for SymbolBuilder { let return_type = &self.return_type.to_ident(); let non_blocking = &self.non_blocking; let name = &self.name; + let internal = &self.internal; tokens.extend(quote! { deno_bindgen::Symbol { @@ -161,6 +169,7 @@ impl ToTokens for SymbolBuilder { parameters: &[#(#parameters),*], return_type: #return_type, non_blocking: #non_blocking, + internal: #internal, } }); } diff --git a/deno_bindgen_macro/src/fn_.rs b/deno_bindgen_macro/src/fn_.rs index 9a2240a..3e1d850 100644 --- a/deno_bindgen_macro/src/fn_.rs +++ b/deno_bindgen_macro/src/fn_.rs @@ -51,7 +51,10 @@ fn parse_type(ty: &Box) -> Result { } } -pub fn handle(fn_: ItemFn, attrs: FnAttributes) -> Result { +pub fn handle_inner( + fn_: ItemFn, + attrs: FnAttributes, +) -> Result<(TokenStream2, SymbolBuilder)> { if fn_.sig.asyncness.is_some() { return Err(Error::Asyncness); } @@ -74,6 +77,7 @@ pub fn handle(fn_: ItemFn, attrs: FnAttributes) -> Result { let mut symbol = SymbolBuilder::new(fn_.sig.ident.clone()); symbol.non_blocking(attrs.non_blocking); + symbol.internal(attrs.internal); // Cannot use enumerate here, there can be multiple raw args per type. let mut i = 0; @@ -168,13 +172,21 @@ pub fn handle(fn_: ItemFn, attrs: FnAttributes) -> Result { #ret_ident }); - Ok(quote::quote! { - const _: () = { - #[deno_bindgen::linkme::distributed_slice(deno_bindgen::INVENTORY)] - pub static _A: deno_bindgen::Inventory = deno_bindgen::Inventory::Symbol(#symbol); - }; + Ok(( + quote::quote! { + const _: () = { + #[deno_bindgen::linkme::distributed_slice(deno_bindgen::INVENTORY)] + pub static _A: deno_bindgen::Inventory = deno_bindgen::Inventory::Symbol(#symbol); + }; + + #[no_mangle] + #ffi_fn + }, + symbol, + )) +} - #[no_mangle] - #ffi_fn - }) +pub fn handle(fn_: ItemFn, attrs: FnAttributes) -> Result { + let (ffi_fn, _) = handle_inner(fn_, attrs)?; + Ok(ffi_fn) } diff --git a/deno_bindgen_macro/src/impl_.rs b/deno_bindgen_macro/src/impl_.rs index 199c511..27e6f6d 100644 --- a/deno_bindgen_macro/src/impl_.rs +++ b/deno_bindgen_macro/src/impl_.rs @@ -1,5 +1,5 @@ use proc_macro2::TokenStream as TokenStream2; -use syn::ItemImpl; +use syn::{parse_quote, ImplItemFn, ItemImpl, parse, punctuated::Punctuated}; use crate::util::{self, Result}; @@ -19,6 +19,44 @@ pub fn handle(impl_: ItemImpl) -> Result { let ref ty_str @ _ = self_ty.get_ident().unwrap(); + let mut methods = Vec::new(); + let mut syms = Punctuated::::new(); + for item in impl_.items.iter() { + match item { + syn::ImplItem::Fn(ImplItemFn { sig, .. }) => { + if sig.receiver().is_some() { + let ref method_name = sig.ident; + let ref out = sig.output; + let inputs = sig.inputs.iter().skip(1).collect::>(); + let idents = inputs + .iter() + .map(|arg| match arg { + syn::FnArg::Receiver(_) => unreachable!(), + syn::FnArg::Typed(pat_type) => match &*pat_type.pat { + syn::Pat::Ident(ident) => ident.ident.clone(), + _ => unreachable!(), + }, + }) + .collect::>(); + let method = parse_quote! { + fn #method_name (self_: *mut #ty_str, #(#inputs),*) #out { + let self_ = unsafe { &mut *self_ }; + self_. #method_name (#(#idents),*) + } + }; + + let (generated, sym) = crate::fn_::handle_inner(method, crate::FnAttributes { + internal: true, + ..Default::default() + })?; + methods.push(generated); + syms.push(quote::quote! { #sym }); + } + } + _ => {} + } + } + // TODO: // - create a new quoted function for each method and codegen using fn_::handle // where first arg is self ptr and rest are method args @@ -27,7 +65,7 @@ pub fn handle(impl_: ItemImpl) -> Result { Ok(quote::quote! { #impl_ - + #(#methods)* const _: () = { // Assert that the type implements `BindgenType`. const fn _assert_impl() {} @@ -38,7 +76,7 @@ pub fn handle(impl_: ItemImpl) -> Result { deno_bindgen::inventory::Struct { name: stringify!(#ty_str), constructor: None, - methods: &[], + methods: &[#syms], } ); }; diff --git a/deno_bindgen_macro/src/lib.rs b/deno_bindgen_macro/src/lib.rs index be5230f..9f4a39b 100644 --- a/deno_bindgen_macro/src/lib.rs +++ b/deno_bindgen_macro/src/lib.rs @@ -15,6 +15,7 @@ mod util; #[derive(Default)] pub(crate) struct FnAttributes { pub(crate) non_blocking: bool, + pub(crate) internal: bool, } impl FnAttributes { diff --git a/example/bindings/bindings.ts b/example/bindings/bindings.ts index dd32ce8..8a39b96 100644 --- a/example/bindings/bindings.ts +++ b/example/bindings/bindings.ts @@ -55,6 +55,21 @@ const { symbols } = dlopen('./target/debug/libdeno_bindgen_test.dylib', { result: 'i32', nonblocking: true }, + foo: { + parameters: [ + 'pointer', + ], + result: 'void', + nonblocking: false + }, + bar: { + parameters: [ + 'pointer', + 'u32', + ], + result: 'u32', + nonblocking: false + }, }); export function add( @@ -111,3 +126,43 @@ export function non_blocking(): Promise { return symbols.non_blocking() } +function foo( + arg0: Deno.PointerObject | null, +): void { + return symbols.foo( + arg0, + ) +} + +function bar( + arg0: Deno.PointerObject | null, + arg1: number, +): number { + return symbols.bar( + arg0, + arg1, + ) +} + +export class Foo { + ptr: Deno.PointerObject | null = null; + + static __constructor(ptr: Deno.PointerObject) { + const self = Object.create(Foo.prototype); + self.ptr = ptr; + return self; + } + + foo(): void { + return foo( + this.ptr, + ) + } + + bar(arg0: number): number { + return bar( + this.ptr, + arg0, + ) + } +} \ No newline at end of file diff --git a/example/src/lib.rs b/example/src/lib.rs index 2f0383a..0b09951 100644 --- a/example/src/lib.rs +++ b/example/src/lib.rs @@ -44,12 +44,14 @@ fn non_blocking() -> i32 { #[deno_bindgen] pub struct Foo { - internal: i32, + internal: u32, } #[deno_bindgen] impl Foo { - fn new() -> Self { - Self { internal: 42 } + fn foo(&self) {} + + fn bar(&self, a: u32) -> u32 { + self.internal + a } } \ No newline at end of file From 4af56761dc75cbe35508d10dac6bb59f15cd6ba9 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Wed, 8 Nov 2023 22:04:16 +0530 Subject: [PATCH 05/15] constructors work --- cli.ts | 99 ----------- cli2.ts | 74 -------- codegen.ts | 300 -------------------------------- deno_bindgen_ir/codegen/deno.rs | 106 +++++++---- deno_bindgen_ir/lib.rs | 51 +++++- deno_bindgen_macro/src/fn_.rs | 13 +- deno_bindgen_macro/src/impl_.rs | 85 ++++++--- deno_bindgen_macro/src/lib.rs | 2 + deno_bindgen_macro/src/util.rs | 2 + example/bindings/bindings.ts | 42 ++++- example/bindings_test.ts | 20 ++- example/src/lib.rs | 10 ++ 12 files changed, 260 insertions(+), 544 deletions(-) delete mode 100644 cli.ts delete mode 100644 cli2.ts delete mode 100644 codegen.ts diff --git a/cli.ts b/cli.ts deleted file mode 100644 index 620dfe0..0000000 --- a/cli.ts +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. - -import { ensureDir } from "https://deno.land/std@0.132.0/fs/ensure_dir.ts"; -import { parse } from "https://deno.land/std@0.132.0/flags/mod.ts"; -import { join } from "https://deno.land/std@0.132.0/path/mod.ts"; -import { relative } from "https://deno.land/std@0.132.0/path/mod.ts"; -import { codegen } from "./codegen.ts"; - -const flags = parse(Deno.args, { "--": true }); -const release = !!flags.release; - -const metafile = join( - Deno.env.get("OUT_DIR") || await findRelativeTarget(), - "bindings.json", -); - -function build() { - const args = ["build"]; - if (release) args.push("--release"); - args.push(...flags["--"]); - console.log( - "[deno_bindgen] Command:", - ["cargo", ...args].map((s) => JSON.stringify(s)).join(" "), - ); - const proc = new Deno.Command("cargo", { args, stderr: "inherit" }); - return proc.output(); -} - -async function findRelativeTarget() { - const p = new Deno.Command("cargo", { - args: ["metadata", "--format-version", "1"], - stdout: "piped", - }); - const output = await p.output(); - const metadata = JSON.parse(new TextDecoder().decode(output.stdout)); - return relative(Deno.cwd(), metadata.workspace_root); -} - -let source = null; -async function generate() { - let conf; - try { - conf = JSON.parse(await Deno.readTextFile(metafile)); - } catch (_) { - console.log(`[deno_bindgen] metafile not found at '${metafile}'`); - // Nothing to update. - return; - } - - let cargoTarget = Deno.env.get("CARGO_TARGET_DIR"); - if (!cargoTarget) cargoTarget = "../target"; - - const pkgName = conf.name; - const fetchPrefix = typeof flags.release == "string" - ? flags.release - : await findRelativeTarget() + [cargoTarget, release ? "release" : "debug"] - .join("/"); - - source = "// Auto-generated with deno_bindgen\n"; - source += codegen( - fetchPrefix, - pkgName, - conf.typeDefs, - conf.tsTypes, - conf.symbols, - { - le: conf.littleEndian, - release, - releaseURL: flags.release, - }, - ); - - await Deno.remove(metafile); -} - -try { - await Deno.remove(metafile); -} catch { - // no op -} - -console.log("[deno_bindgen] Building..."); -const status = await build().catch((_) => Deno.removeSync(metafile)); -if (status?.success || typeof flags.release == "string") { - await generate(); - if (source) { - await ensureDir("bindings"); - await Deno.writeTextFile("bindings/bindings.ts", source); - console.log("[deno_bindgen] Written at 'bindings/bindings.ts'"); - } else { - console.log( - "[deno_bindgen] bindings.ts not generated: nothing to generate", - ); - } -} else { - console.log("[deno_bindgen] Build failed with code", status?.code); -} - -Deno.exit(status?.code || 0); diff --git a/cli2.ts b/cli2.ts deleted file mode 100644 index 878fbe8..0000000 --- a/cli2.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { parse } from "https://deno.land/std@0.132.0/flags/mod.ts"; - -const flags = parse(Deno.args, { "--": true }); -const release = !!flags.release; - -function build() { - const args = ["build"]; - if (release) args.push("--release"); - args.push(...flags["--"]); - const proc = new Deno.Command("cargo", { args, stderr: "inherit" }); - return proc.outputSync(); -} - -function nm() { - // Run `nm` to get the symbols from the compiled library. - const args = [ - "nm", - "--format=bsd", - "--defined-only", - "target/debug/libdeno_bindgen_test.dylib", - ]; - if (release) args.push("--demangle"); - const proc = new Deno.Command("nm", { args, stdout: "piped" }); - const output = proc.outputSync(); - const stdout = new TextDecoder().decode(output.stdout); - const symbols = stdout.split("\n").filter((s) => s.length > 0).slice(1); - - const symbols2 = symbols.map((s) => { - const [addr, ty, name] = s.split(" "); - return { addr, ty, name }; - }).filter((s) => s.name.startsWith("___de90_")); - return symbols2; -} - -function run_init(symbols) { - const symbols_obj = {}; - symbols.forEach(({ name }) => { - symbols_obj[name.slice(1)] = { - parameters: ["buffer", "buffer"], - result: "pointer", - }; - }); - - const lib = Deno.dlopen("./target/debug/libdeno_bindgen_test.dylib", symbols_obj); - const params = new Uint8Array(20); - const result = new Uint8Array(1); - const processed = []; - for (const fn in lib.symbols) { - const name_ptr = lib.symbols[fn](params, result); - const name = Deno.UnsafePointerView.getCString(name_ptr); - processed.push({ name, params: [...params].map(p => C_TYPE[p]), result: C_TYPE[result[0]] }) - } - return processed; -} - -const C_TYPE = ["A", "B"]; - -function codegen(symbols) { - let code = ''; - for (let i = 0; i < symbols.length; i++) { - const { name, params, result } = symbols[i]; - const params_str = params.map((p, j) => `p${j}: ${p}`).join(', '); - const params_idents = params.map((p, j) => `p${j}`).join(', '); - code += `export function ${name}(${params_str}): ${result} { return lib.${name}(${params_idents}); }\n`; - } - - console.log(code) -} - -build(); -const symbols = nm(); -const processed = run_init(symbols); -codegen(processed); - diff --git a/codegen.ts b/codegen.ts deleted file mode 100644 index f7a9b96..0000000 --- a/codegen.ts +++ /dev/null @@ -1,300 +0,0 @@ -// deno-lint-ignore-file no-explicit-any -// Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. - -import { - createFromBuffer, - GlobalConfiguration, -} from "https://deno.land/x/dprint@0.2.0/mod.ts"; -import * as Cache from "https://deno.land/x/cache@0.2.13/mod.ts"; - -Cache.configure({ directory: Cache.options.directory }); -const cache = Cache.namespace("deno_bindgen_cli"); - -const globalConfig: GlobalConfiguration = { - indentWidth: 2, - lineWidth: 80, -}; - -const file = await cache.cache( - "https://plugins.dprint.dev/typescript-0.57.0.wasm", -); - -const tsFormatter = createFromBuffer(Deno.readFileSync(file.path)); - -tsFormatter.setConfig(globalConfig, { - semiColons: "asi", -}); - -const Type: Record = { - void: "null", - i8: "number", - u8: "number", - i16: "number", - u16: "number", - i32: "number", - u32: "number", - i64: "bigint", - u64: "bigint", - usize: "bigint", - isize: "bigint", - f32: "number", - f64: "number", -}; - -const BufferTypes: Record = { - str: "string", - buffer: "Uint8Array", - buffermut: "Uint8Array", - ptr: "Uint8Array", -}; - -enum Encoder { - JsonStringify = "JSON.stringify", - None = "", -} - -const BufferTypeEncoders: Record = { - str: Encoder.None, - buffer: Encoder.None, - buffermut: Encoder.None, - ptr: Encoder.None, -}; - -type TypeDef = Record>; - -function resolveType(typeDefs: TypeDef, type: any): string { - const t = typeof type == "string" ? type : type.structenum.ident; - if (Type[t] !== undefined) return Type[t]; - if (BufferTypes[t] !== undefined) return BufferTypes[t]; - if (Object.keys(typeDefs).find((f) => f == t) !== undefined) { - return t; - } - return "any"; -} - -function resolveDlopenParameter(typeDefs: TypeDef, type: any): string { - const t = typeof type == "string" ? type : type.structenum.ident; - if (Type[t] !== undefined) return t; - if (BufferTypes[t] !== undefined) { - return "buffer"; - } - if ( - Object.keys(typeDefs).find((f) => f == t) !== undefined - ) { - return "buffer"; - } else { - return "pointer"; - } - // deno-lint-ignore no-unreachable - throw new TypeError(`Type not supported: ${t}`); -} - -type Sig = Record< - string, - { - parameters: any[]; - result: string; - nonBlocking?: boolean; - } ->; - -type Options = { - le?: boolean; - release?: boolean; - releaseURL: string | undefined; -}; - -function isTypeDef(p: any) { - return typeof p !== "string"; -} - -function isBufferType(p: any) { - return isTypeDef(p) || BufferTypes[p] !== undefined; -} - -function needsPointer(p: any) { - return isBufferType(p) && p !== "buffer" && p !== "buffermut"; -} - -// TODO(@littledivy): factor out options in an interface -export function codegen( - fetchPrefix: string, - name: string, - decl: TypeDef, - typescript: Record, - signature: Sig, - options?: Options, -) { - signature = Object.keys(signature) - .sort() - .reduce( - (acc, key) => ({ - ...acc, - [key]: signature[key], - }), - {}, - ); - - return tsFormatter.formatText( - "bindings.ts", - ` -function encode(v: string | Uint8Array): Uint8Array { - if (typeof v !== "string") return v; - return new TextEncoder().encode(v); -} - -function decode(v: Uint8Array): string { - return new TextDecoder().decode(v); -} - -// deno-lint-ignore no-explicit-any -function readPointer(v: any): Uint8Array { - const ptr = new Deno.UnsafePointerView(v); - const lengthBe = new Uint8Array(4); - const view = new DataView(lengthBe.buffer); - ptr.copyInto(lengthBe, 0); - const buf = new Uint8Array(view.getUint32(0)); - ptr.copyInto(buf, 4); - return buf; -} - -const url = new URL("${fetchPrefix}", import.meta.url); -${ - typeof options?.releaseURL === "string" - ? ` -import { dlopen, FetchOptions } from "https://deno.land/x/plug@1.0.1/mod.ts"; -let uri = url.toString(); -if (!uri.endsWith("/")) uri += "/"; - -let darwin: string | { aarch64: string; x86_64: string } = uri; - -const opts: FetchOptions = { - name: "${name}", - url: { - darwin, - windows: uri, - linux: uri, - }, - suffixes: { - darwin: { - aarch64: "_arm64", - }, - }, - cache: ${!!options?.release ? '"use"' : '"reloadAll"'}, -}; -const { symbols } = await dlopen(opts, { - ` - : ` -let uri = url.pathname; -if (!uri.endsWith("/")) uri += "/"; - -// https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibrarya#parameters -if (Deno.build.os === "windows") { - uri = uri.replace(/\\//g, "\\\\"); - // Remove leading slash - if (uri.startsWith("\\\\")) { - uri = uri.slice(1); - } -} - -const { symbols } = Deno.dlopen({ - darwin: uri + "lib${name}.dylib", - windows: uri + "${name}.dll", - linux: uri + "lib${name}.so", - freebsd: uri + "lib${name}.so", - netbsd: uri + "lib${name}.so", - aix: uri + "lib${name}.so", - solaris: uri + "lib${name}.so", - illumos: uri + "lib${name}.so", -}[Deno.build.os], {` - } - ${ - Object.keys(signature) - .map( - (sig) => - `${sig}: { parameters: [ ${ - signature[sig].parameters - .map((p) => { - const ffiParam = resolveDlopenParameter(decl, p); - // FIXME: Dupe logic here. - return `"${ffiParam}"${isBufferType(p) ? `, "usize"` : ""}`; - }) - .join(", ") - } ], result: "${ - resolveDlopenParameter( - decl, - signature[sig].result, - ) - }", nonblocking: ${String(!!signature[sig].nonBlocking)} }`, - ) - .join(", ") - } }); -${ - Object.keys(decl) - .sort() - .map((def) => typescript[def]) - .join("\n") - } -${ - Object.keys(signature) - .map((sig) => { - const { parameters, result, nonBlocking } = signature[sig]; - - return `export function ${sig}(${ - parameters - .map((p, i) => `a${i}: ${resolveType(decl, p)}`) - .join(",") - }) { - ${ - parameters - .map((p, i) => - isBufferType(p) - ? `const a${i}_buf = encode(${ - BufferTypeEncoders[p] ?? Encoder.JsonStringify - }(a${i}));` - : null - ) - .filter((c) => c !== null) - .join("\n") - } - - const rawResult = symbols.${sig}(${ - parameters - .map((p, i) => (isBufferType(p) - ? `a${i}_buf, a${i}_buf.byteLength` - : `a${i}`) - ) - .join(", ") - }); - ${ - isBufferType(result) - ? nonBlocking - ? `const result = rawResult.then(readPointer);` - : `const result = readPointer(rawResult);` - : "const result = rawResult;" - }; - ${ - isTypeDef(result) - ? nonBlocking - ? `return result.then(r => JSON.parse(decode(r))) as Promise<${ - resolveType( - decl, - result, - ) - }>;` - : `return JSON.parse(decode(result)) as ${ - resolveType(decl, result) - };` - : result == "str" - ? nonBlocking - ? "return result.then(decode);" - : "return decode(result);" - : "return result;" - }; -}`; - }) - .join("\n") - } - `, - ); -} diff --git a/deno_bindgen_ir/codegen/deno.rs b/deno_bindgen_ir/codegen/deno.rs index a1e89a0..1b5715c 100644 --- a/deno_bindgen_ir/codegen/deno.rs +++ b/deno_bindgen_ir/codegen/deno.rs @@ -9,7 +9,8 @@ use crate::{ Symbol, Type, }; -struct TypeScriptType<'a>(&'a str); +// (ident, is_custom_type) +struct TypeScriptType<'a>(&'a str, bool); impl std::fmt::Display for TypeScriptType<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -21,13 +22,20 @@ impl std::fmt::Display for TypeScriptType<'_> { impl TypeScriptType<'_> { fn into_raw<'a>(&self, ident: &'a str) -> Cow<'a, str> { match self { - Self("Uint8Array") => { + Self("Uint8Array", false) => { Cow::Owned(format!("{ident},\n {ident}.byteLength")) } _ => Cow::Borrowed(ident), } } + fn from_raw<'a>(&self, ident: &'a str) -> Option { + match self { + Self(ty_str, true) => Some(format!("{ty_str}.__constructor({ident})")), + _ => None, + } + } + fn apply_promise(&self, non_blocking: bool) -> Cow<'_, str> { if non_blocking { Cow::Owned(format!("Promise<{}>", self.0)) @@ -39,21 +47,25 @@ impl TypeScriptType<'_> { impl From for TypeScriptType<'_> { fn from(value: Type) -> Self { - Self(match value { - Type::Void => "void", - Type::Uint8 - | Type::Uint16 - | Type::Uint32 - | Type::Uint64 - | Type::Int8 - | Type::Int16 - | Type::Int32 - | Type::Int64 - | Type::Float32 - | Type::Float64 => "number", - Type::Pointer => "Deno.PointerObject | null", - Type::Buffer => "Uint8Array", - }) + Self( + (match value { + Type::Void => "void", + Type::Uint8 + | Type::Uint16 + | Type::Uint32 + | Type::Uint64 + | Type::Int8 + | Type::Int16 + | Type::Int32 + | Type::Int64 + | Type::Float32 + | Type::Float64 => "number", + Type::Pointer => "Deno.PointerObject | null", + Type::Buffer => "Uint8Array", + Type::CustomType(name) => name, + }), + matches!(value, Type::CustomType(_)), + ) } } @@ -80,7 +92,7 @@ impl From for DenoFfiType { Type::Int64 => "i64", Type::Float32 => "f32", Type::Float64 => "f64", - Type::Pointer => "pointer", + Type::CustomType(..) | Type::Pointer => "pointer", Type::Buffer => "buffer", }; @@ -207,13 +219,19 @@ impl<'a> Codegen<'a> { 0, ('(', ')'), )?; + let ret_ty = TypeScriptType::from(symbol.return_type); writeln!( writer, ": {} {{", - TypeScriptType::from(symbol.return_type) - .apply_promise(symbol.non_blocking) + ret_ty.apply_promise(symbol.non_blocking) )?; - write!(writer, " return symbols.{}", symbol.name)?; + let maybe_ret_transform = ret_ty.from_raw("ret"); + if maybe_ret_transform.is_some() { + write!(writer, " const ret = ")?; + } else { + write!(writer, " return ")?; + } + write!(writer, "symbols.{}", symbol.name)?; format_paren( writer, symbol.parameters, @@ -233,6 +251,9 @@ impl<'a> Codegen<'a> { ('(', ')'), )?; + if let Some(ret_transform) = maybe_ret_transform { + write!(writer, "\n return {ret_transform};")?; + } writeln!(writer, "\n}}\n")?; } Inventory::Struct(Struct { @@ -251,7 +272,7 @@ impl<'a> Codegen<'a> { writeln!( writer, - " static __constructor(ptr: Deno.PointerObject) {{" + " static __constructor(ptr: Deno.PointerObject | null) {{" )?; writeln!( writer, @@ -262,12 +283,23 @@ impl<'a> Codegen<'a> { writeln!(writer, " }}")?; for method in methods { - // Skip the first argument, which is always the pointer to the struct. - let parameters = &method.parameters[1..]; - writeln!( + let mut parameters = method.parameters; + + if !method.is_constructor { + // Skip the self ptr argument. + parameters = &method.parameters[1..]; + } + + let method_name = if method.is_constructor { + "constructor" + } else { + &method.name + }; + + write!( writer, - "\n {name}({parameters}): {return_type} {{", - name = method.name, + "\n {name}({parameters})", + name = method_name, parameters = parameters .iter() .enumerate() @@ -276,16 +308,27 @@ impl<'a> Codegen<'a> { }) .collect::>() .join(", "), - return_type = TypeScriptType::from(method.return_type) )?; - write!(writer, " return {}", method.name)?; + if !method.is_constructor { + let return_type = TypeScriptType::from(method.return_type); + writeln!(writer, ": {return_type} {{")?; + } else { + // Typescript doesn't allow constructors to have a return type. + writeln!(writer, " {{")?; + } + + // Apply name mangling. + write!(writer, " return __{}_{}", name, method.name)?; format_paren( writer, parameters, - true, + !method.is_constructor, |writer, parameters| { - writeln!(writer, " this.ptr,",)?; + if !method.is_constructor { + writeln!(writer, " this.ptr,",)?; + } + for (i, parameter) in parameters.iter().enumerate() { let ident = format!("arg{}", i); writeln!( @@ -294,6 +337,7 @@ impl<'a> Codegen<'a> { TypeScriptType::from(*parameter).into_raw(&ident) )?; } + Ok(()) }, 4, diff --git a/deno_bindgen_ir/lib.rs b/deno_bindgen_ir/lib.rs index bcc9567..c813a88 100644 --- a/deno_bindgen_ir/lib.rs +++ b/deno_bindgen_ir/lib.rs @@ -23,6 +23,8 @@ pub enum Type { Float64, Pointer, Buffer, + + CustomType(&'static str), } pub type RawTypes = &'static [Type]; @@ -31,16 +33,19 @@ impl Type { pub fn raw(&self) -> RawTypes { match self { Self::Buffer => &[Self::Pointer, Self::Uint32], - Self::Pointer => &[Self::Pointer], + Self::Pointer | Self::CustomType(..) => &[Self::Pointer], _ => &[], } } pub fn is_number(&self) -> bool { - !matches!(self, Self::Void | Self::Pointer | Self::Buffer) + !matches!( + self, + Self::Void | Self::Pointer | Self::Buffer | Self::CustomType(_) + ) } - pub fn apply_transform( + pub fn apply_arg_transform( &self, name: &mut Box, args: &[Ident], @@ -55,6 +60,12 @@ impl Type { }; }) } + Self::CustomType(_) => { + let pointer = &args[0]; + Some(quote! { + let #name = unsafe { &mut *(#pointer as *mut _) }; + }) + } Self::Pointer => { let pointer = &args[0]; Some(quote! { @@ -65,7 +76,23 @@ impl Type { } } - pub fn to_ident(&self) -> syn::Type { + pub fn apply_ret_transform( + &self, + name: &mut Box, + arg: Ident, + ) -> Option { + match self { + Self::Pointer => Some(quote! { + let #name = #arg as _; + }), + Self::CustomType(_) => Some(quote! { + let #name = Box::into_raw(Box::new(#arg)) as *mut _; + }), + _ => None, + } + } + + pub fn to_ident(&self) -> syn::Expr { match self { Self::Void => parse_quote!(deno_bindgen::Type::Void), Self::Uint8 => parse_quote!(deno_bindgen::Type::Uint8), @@ -80,6 +107,7 @@ impl Type { Self::Float64 => parse_quote!(deno_bindgen::Type::Float64), Self::Pointer => parse_quote!(deno_bindgen::Type::Pointer), Self::Buffer => parse_quote!(deno_bindgen::Type::Buffer), + Self::CustomType(s) => parse_quote!(deno_bindgen::Type::CustomType(#s)), } } } @@ -98,7 +126,7 @@ impl ToTokens for Type { Self::Int64 => quote! { i64 }, Self::Float32 => quote! { f32 }, Self::Float64 => quote! { f64 }, - Self::Pointer => quote! { *const () }, + Self::CustomType(_) | Self::Pointer => quote! { *const () }, Self::Buffer => quote! { *mut u8 }, }; @@ -113,6 +141,7 @@ pub struct Symbol { pub return_type: Type, pub non_blocking: bool, pub internal: bool, + pub is_constructor: bool, } pub struct SymbolBuilder { @@ -121,6 +150,7 @@ pub struct SymbolBuilder { return_type: Type, non_blocking: bool, internal: bool, + is_constructor: bool, } impl SymbolBuilder { @@ -131,9 +161,14 @@ impl SymbolBuilder { return_type: Default::default(), non_blocking: false, internal: false, + is_constructor: false, } } + pub fn set_name(&mut self, name: Ident) { + self.name = name; + } + pub fn push(&mut self, ty: Type) { self.parameters.push(ty); } @@ -149,6 +184,10 @@ impl SymbolBuilder { pub fn internal(&mut self, internal: bool) { self.internal = internal; } + + pub fn is_constructor(&mut self, is_constructor: bool) { + self.is_constructor = is_constructor; + } } impl ToTokens for SymbolBuilder { @@ -162,6 +201,7 @@ impl ToTokens for SymbolBuilder { let non_blocking = &self.non_blocking; let name = &self.name; let internal = &self.internal; + let is_constructor = &self.is_constructor; tokens.extend(quote! { deno_bindgen::Symbol { @@ -170,6 +210,7 @@ impl ToTokens for SymbolBuilder { return_type: #return_type, non_blocking: #non_blocking, internal: #internal, + is_constructor: #is_constructor, } }); } diff --git a/deno_bindgen_macro/src/fn_.rs b/deno_bindgen_macro/src/fn_.rs index 3e1d850..d019db4 100644 --- a/deno_bindgen_macro/src/fn_.rs +++ b/deno_bindgen_macro/src/fn_.rs @@ -30,7 +30,12 @@ fn parse_type(ty: &Box) -> Result { "f64" => return Ok(Type::Float64), "usize" => return Ok(Type::Uint64), "isize" => return Ok(Type::Int64), - _ => return Err(Error::UnsupportedType), + ty_str => { + return Ok(Type::CustomType( + // yeah, don't worry about it. + Box::leak(ty_str.to_string().into_boxed_str()), + )) + } } } @@ -78,6 +83,7 @@ pub fn handle_inner( let mut symbol = SymbolBuilder::new(fn_.sig.ident.clone()); symbol.non_blocking(attrs.non_blocking); symbol.internal(attrs.internal); + symbol.is_constructor(attrs.constructor); // Cannot use enumerate here, there can be multiple raw args per type. let mut i = 0; @@ -109,7 +115,7 @@ pub fn handle_inner( } // Apply the transform. - if let Some(transform) = ty.apply_transform(pat, &idents) { + if let Some(transform) = ty.apply_arg_transform(pat, &idents) { transforms.push(transform); } @@ -136,7 +142,8 @@ pub fn handle_inner( ReturnType::Type(_, ref mut ty) => { let t = parse_type(ty)?; - if let Some(transform) = t.apply_transform(&mut ret, &[ret_ident.clone()]) + if let Some(transform) = + t.apply_ret_transform(&mut ret, ret_ident.clone()) { ret_transform = transform; } diff --git a/deno_bindgen_macro/src/impl_.rs b/deno_bindgen_macro/src/impl_.rs index 27e6f6d..1d58efb 100644 --- a/deno_bindgen_macro/src/impl_.rs +++ b/deno_bindgen_macro/src/impl_.rs @@ -1,9 +1,10 @@ use proc_macro2::TokenStream as TokenStream2; -use syn::{parse_quote, ImplItemFn, ItemImpl, parse, punctuated::Punctuated}; +use quote::format_ident; +use syn::{parse, parse_quote, punctuated::Punctuated, ImplItemFn, ItemImpl}; use crate::util::{self, Result}; -pub fn handle(impl_: ItemImpl) -> Result { +pub fn handle(mut impl_: ItemImpl) -> Result { if impl_.generics.params.first().is_some() { return Err(util::Error::Generics); } @@ -21,37 +22,79 @@ pub fn handle(impl_: ItemImpl) -> Result { let mut methods = Vec::new(); let mut syms = Punctuated::::new(); - for item in impl_.items.iter() { + for item in impl_.items.iter_mut() { match item { - syn::ImplItem::Fn(ImplItemFn { sig, .. }) => { - if sig.receiver().is_some() { - let ref method_name = sig.ident; - let ref out = sig.output; - let inputs = sig.inputs.iter().skip(1).collect::>(); - let idents = inputs - .iter() + syn::ImplItem::Fn(ImplItemFn { sig, attrs, .. }) => { + let mut is_constructor = false; + if let Some(attr) = attrs.first() { + let path = attr.path(); + is_constructor = path.is_ident("constructor"); + + attrs.clear(); + } + + // TODO: Add common name magling util. + let method_name = sig.ident.clone(); + let mangled_name = format_ident!("__{}_{}", ty_str, method_name); + // ... + let ref out = sig.output; + let inputs = sig.inputs.iter(); + + fn idents_with_skip<'a>( + arg: syn::punctuated::Iter<'a, syn::FnArg>, + skip: usize, + ) -> Vec<&'a syn::Ident> { + arg + .skip(skip) .map(|arg| match arg { syn::FnArg::Receiver(_) => unreachable!(), syn::FnArg::Typed(pat_type) => match &*pat_type.pat { - syn::Pat::Ident(ident) => ident.ident.clone(), + syn::Pat::Ident(ident) => &ident.ident, _ => unreachable!(), }, }) - .collect::>(); - let method = parse_quote! { - fn #method_name (self_: *mut #ty_str, #(#inputs),*) #out { + .collect::>() + } + + let method = if sig.receiver().is_some() { + let idents = idents_with_skip(inputs.clone(), 1); + // First argument is the receiver, we skip it. + let inputs = inputs.skip(1); + + parse_quote! { + #[allow(non_snake_case)] + fn #mangled_name (self_: *mut #ty_str, #(#inputs),*) #out { let self_ = unsafe { &mut *self_ }; self_. #method_name (#(#idents),*) } - }; - - let (generated, sym) = crate::fn_::handle_inner(method, crate::FnAttributes { + } + } else if is_constructor { + let idents = idents_with_skip(inputs.clone(), 1); + parse_quote!( + #[allow(non_snake_case)] + fn #mangled_name (#(#inputs),*) #out { + #ty_str:: #method_name (#(#idents),*) + } + ) + } else { + return Err(util::Error::MissingReceiver); + }; + + let (generated, mut sym) = crate::fn_::handle_inner( + method, + crate::FnAttributes { internal: true, + constructor: is_constructor, ..Default::default() - })?; - methods.push(generated); - syms.push(quote::quote! { #sym }); - } + }, + )?; + + // Set method name to the original name as the + // managed name is used for the internal symbol. + sym.set_name(method_name); + + methods.push(generated); + syms.push(quote::quote! { #sym }); } _ => {} } diff --git a/deno_bindgen_macro/src/lib.rs b/deno_bindgen_macro/src/lib.rs index 9f4a39b..800d6dd 100644 --- a/deno_bindgen_macro/src/lib.rs +++ b/deno_bindgen_macro/src/lib.rs @@ -15,6 +15,8 @@ mod util; #[derive(Default)] pub(crate) struct FnAttributes { pub(crate) non_blocking: bool, + pub(crate) constructor: bool, + pub(crate) internal: bool, } diff --git a/deno_bindgen_macro/src/util.rs b/deno_bindgen_macro/src/util.rs index 4ed673e..811047f 100644 --- a/deno_bindgen_macro/src/util.rs +++ b/deno_bindgen_macro/src/util.rs @@ -5,6 +5,7 @@ pub enum Error { UnsupportedType, Generics, WhereClause, + MissingReceiver, } impl std::fmt::Display for Error { @@ -15,6 +16,7 @@ impl std::fmt::Display for Error { Error::UnsupportedType => write!(f, "unsupported type"), Error::Generics => write!(f, "generics are not supported"), Error::WhereClause => write!(f, "where clauses are not supported"), + Error::MissingReceiver => write!(f, "missing receiver"), } } } diff --git a/example/bindings/bindings.ts b/example/bindings/bindings.ts index 8a39b96..d09c84d 100644 --- a/example/bindings/bindings.ts +++ b/example/bindings/bindings.ts @@ -55,14 +55,24 @@ const { symbols } = dlopen('./target/debug/libdeno_bindgen_test.dylib', { result: 'i32', nonblocking: true }, - foo: { + make_foo: { + parameters: [], + result: 'pointer', + nonblocking: false + }, + __Foo_new: { + parameters: [], + result: 'pointer', + nonblocking: false + }, + __Foo_foo: { parameters: [ 'pointer', ], result: 'void', nonblocking: false }, - bar: { + __Foo_bar: { parameters: [ 'pointer', 'u32', @@ -126,19 +136,29 @@ export function non_blocking(): Promise { return symbols.non_blocking() } -function foo( +export function make_foo(): Foo { + const ret = symbols.make_foo() + return Foo.__constructor(ret); +} + +function __Foo_new(): Foo { + const ret = symbols.__Foo_new() + return Foo.__constructor(ret); +} + +function __Foo_foo( arg0: Deno.PointerObject | null, ): void { - return symbols.foo( + return symbols.__Foo_foo( arg0, ) } -function bar( +function __Foo_bar( arg0: Deno.PointerObject | null, arg1: number, ): number { - return symbols.bar( + return symbols.__Foo_bar( arg0, arg1, ) @@ -147,20 +167,24 @@ function bar( export class Foo { ptr: Deno.PointerObject | null = null; - static __constructor(ptr: Deno.PointerObject) { + static __constructor(ptr: Deno.PointerObject | null) { const self = Object.create(Foo.prototype); self.ptr = ptr; return self; } + constructor() { + return __Foo_new() + } + foo(): void { - return foo( + return __Foo_foo( this.ptr, ) } bar(arg0: number): number { - return bar( + return __Foo_bar( this.ptr, arg0, ) diff --git a/example/bindings_test.ts b/example/bindings_test.ts index 92107ab..e6cd06b 100644 --- a/example/bindings_test.ts +++ b/example/bindings_test.ts @@ -6,6 +6,8 @@ import { cstr, strlen, non_blocking, + make_foo, + Foo, } from "./bindings/bindings.ts"; import { assert, assertEquals } from "https://deno.land/std/testing/asserts.ts"; @@ -58,5 +60,19 @@ Deno.test({ }, }); -// struct (glorified pointers) -// impl on struct \ No newline at end of file +Deno.test({ + name: "make_foo#test", + fn: () => { + const foo = make_foo(); + assert(foo instanceof Foo); + assertEquals(foo.bar(1), 43); + }, +}) + +Deno.test({ + name: "Foo#constructor", + fn() { + const foo = new Foo(); + assertEquals(foo.bar(1), 43); + } +}) \ No newline at end of file diff --git a/example/src/lib.rs b/example/src/lib.rs index 0b09951..1f83345 100644 --- a/example/src/lib.rs +++ b/example/src/lib.rs @@ -42,6 +42,11 @@ fn non_blocking() -> i32 { 42 } +#[deno_bindgen] +fn make_foo() -> Foo { + Foo { internal: 42 } +} + #[deno_bindgen] pub struct Foo { internal: u32, @@ -49,6 +54,11 @@ pub struct Foo { #[deno_bindgen] impl Foo { + #[constructor] + fn new() -> Foo { + Foo { internal: 42 } + } + fn foo(&self) {} fn bar(&self, a: u32) -> u32 { From 2c0492c65981784f7edaa21df9a30028e4f4b7e2 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Wed, 8 Nov 2023 22:39:22 +0530 Subject: [PATCH 06/15] pass by ref done --- deno_bindgen_ir/codegen/deno.rs | 18 +++++---- deno_bindgen_ir/inventory.rs | 1 - deno_bindgen_ir/lib.rs | 1 + deno_bindgen_macro/src/fn_.rs | 17 ++++++-- deno_bindgen_macro/src/impl_.rs | 35 +++++++++++++---- example/bindings/bindings.ts | 69 ++++++++++++++++++++++++++++----- example/bindings_test.ts | 36 +++++++++++++++-- example/s.ts | 28 ------------- example/src/lib.rs | 13 +++++-- 9 files changed, 154 insertions(+), 64 deletions(-) delete mode 100644 example/s.ts diff --git a/deno_bindgen_ir/codegen/deno.rs b/deno_bindgen_ir/codegen/deno.rs index 1b5715c..d076412 100644 --- a/deno_bindgen_ir/codegen/deno.rs +++ b/deno_bindgen_ir/codegen/deno.rs @@ -25,6 +25,7 @@ impl TypeScriptType<'_> { Self("Uint8Array", false) => { Cow::Owned(format!("{ident},\n {ident}.byteLength")) } + Self(_, true) => Cow::Owned(format!("{ident}.ptr")), _ => Cow::Borrowed(ident), } } @@ -48,7 +49,7 @@ impl TypeScriptType<'_> { impl From for TypeScriptType<'_> { fn from(value: Type) -> Self { Self( - (match value { + match value { Type::Void => "void", Type::Uint8 | Type::Uint16 @@ -63,7 +64,7 @@ impl From for TypeScriptType<'_> { Type::Pointer => "Deno.PointerObject | null", Type::Buffer => "Uint8Array", Type::CustomType(name) => name, - }), + }, matches!(value, Type::CustomType(_)), ) } @@ -256,11 +257,7 @@ impl<'a> Codegen<'a> { } writeln!(writer, "\n}}\n")?; } - Inventory::Struct(Struct { - name, - methods, - constructor, - }) => { + Inventory::Struct(Struct { name, methods }) => { write!(writer, "export class {name} ")?; format_paren( @@ -270,6 +267,7 @@ impl<'a> Codegen<'a> { |writer, methods| { writeln!(writer, " ptr: Deno.PointerObject | null = null;\n")?; + // Internal constructor. writeln!( writer, " static __constructor(ptr: Deno.PointerObject | null) {{" @@ -280,6 +278,12 @@ impl<'a> Codegen<'a> { )?; writeln!(writer, " self.ptr = ptr;")?; writeln!(writer, " return self;")?; + writeln!(writer, " }}\n")?; + + // Dispose method (explicit resource management) + writeln!(writer, " [Symbol.dispose]() {{")?; + writeln!(writer, " this.dealloc();")?; + writeln!(writer, " this.ptr = null;")?; writeln!(writer, " }}")?; for method in methods { diff --git a/deno_bindgen_ir/inventory.rs b/deno_bindgen_ir/inventory.rs index 8609608..5a6cf96 100644 --- a/deno_bindgen_ir/inventory.rs +++ b/deno_bindgen_ir/inventory.rs @@ -3,7 +3,6 @@ use crate::Symbol; #[derive(Debug)] pub struct Struct { pub name: &'static str, - pub constructor: Option, pub methods: &'static [Symbol], } diff --git a/deno_bindgen_ir/lib.rs b/deno_bindgen_ir/lib.rs index c813a88..646bcbc 100644 --- a/deno_bindgen_ir/lib.rs +++ b/deno_bindgen_ir/lib.rs @@ -63,6 +63,7 @@ impl Type { Self::CustomType(_) => { let pointer = &args[0]; Some(quote! { + debug_assert!(!#pointer.is_null()); let #name = unsafe { &mut *(#pointer as *mut _) }; }) } diff --git a/deno_bindgen_macro/src/fn_.rs b/deno_bindgen_macro/src/fn_.rs index d019db4..0691b6c 100644 --- a/deno_bindgen_macro/src/fn_.rs +++ b/deno_bindgen_macro/src/fn_.rs @@ -13,6 +13,11 @@ use crate::{ FnAttributes, }; +fn custom_type(ty: &str) -> Type { + // yeah, don't worry about it. + Type::CustomType(Box::leak(ty.to_string().into_boxed_str())) +} + fn parse_type(ty: &Box) -> Result { match **ty { syn::Type::Path(TypePath { ref path, .. }) => { @@ -31,10 +36,7 @@ fn parse_type(ty: &Box) -> Result { "usize" => return Ok(Type::Uint64), "isize" => return Ok(Type::Int64), ty_str => { - return Ok(Type::CustomType( - // yeah, don't worry about it. - Box::leak(ty_str.to_string().into_boxed_str()), - )) + return Ok(custom_type(ty_str)); } } } @@ -48,6 +50,13 @@ fn parse_type(ty: &Box) -> Result { } } + if let syn::Type::Path(TypePath { ref path, .. }) = *elem.as_ref() { + if let Some(ident) = path.get_ident() { + let ref ty_str = ident.to_string(); + return Ok(custom_type(ty_str)); + } + } + Err(Error::UnsupportedType) } diff --git a/deno_bindgen_macro/src/impl_.rs b/deno_bindgen_macro/src/impl_.rs index 1d58efb..37ad435 100644 --- a/deno_bindgen_macro/src/impl_.rs +++ b/deno_bindgen_macro/src/impl_.rs @@ -1,6 +1,6 @@ use proc_macro2::TokenStream as TokenStream2; use quote::format_ident; -use syn::{parse, parse_quote, punctuated::Punctuated, ImplItemFn, ItemImpl}; +use syn::{parse_quote, punctuated::Punctuated, ImplItemFn, ItemImpl}; use crate::util::{self, Result}; @@ -69,7 +69,7 @@ pub fn handle(mut impl_: ItemImpl) -> Result { } } } else if is_constructor { - let idents = idents_with_skip(inputs.clone(), 1); + let idents = idents_with_skip(inputs.clone(), 0); parse_quote!( #[allow(non_snake_case)] fn #mangled_name (#(#inputs),*) #out { @@ -100,11 +100,31 @@ pub fn handle(mut impl_: ItemImpl) -> Result { } } - // TODO: - // - create a new quoted function for each method and codegen using fn_::handle - // where first arg is self ptr and rest are method args - // - constructor is a simply special case with no self ptr. - // - we also need to be aware of &mut self and Self types. + // Generate a dealloc method. + { + let ident = format_ident!("__{}_dealloc", ty_str); + let dispose = parse_quote! { + #[allow(non_snake_case)] + fn #ident(self_: *mut #ty_str) { + if self_.is_null() { + return; + } + unsafe { drop(Box::from_raw(self_)) } + } + }; + let (generated, mut sym) = crate::fn_::handle_inner( + dispose, + crate::FnAttributes { + internal: true, + ..Default::default() + }, + )?; + + sym.set_name(format_ident!("dealloc")); + + methods.push(generated); + syms.push(quote::quote! { #sym }); + } Ok(quote::quote! { #impl_ @@ -118,7 +138,6 @@ pub fn handle(mut impl_: ItemImpl) -> Result { pub static _B: deno_bindgen::Inventory = deno_bindgen::Inventory::Struct( deno_bindgen::inventory::Struct { name: stringify!(#ty_str), - constructor: None, methods: &[#syms], } ); diff --git a/example/bindings/bindings.ts b/example/bindings/bindings.ts index d09c84d..4dc8cb4 100644 --- a/example/bindings/bindings.ts +++ b/example/bindings/bindings.ts @@ -60,12 +60,21 @@ const { symbols } = dlopen('./target/debug/libdeno_bindgen_test.dylib', { result: 'pointer', nonblocking: false }, + inc_foo: { + parameters: [ + 'pointer', + ], + result: 'void', + nonblocking: false + }, __Foo_new: { - parameters: [], + parameters: [ + 'u32', + ], result: 'pointer', nonblocking: false }, - __Foo_foo: { + __Foo_inc: { parameters: [ 'pointer', ], @@ -80,6 +89,13 @@ const { symbols } = dlopen('./target/debug/libdeno_bindgen_test.dylib', { result: 'u32', nonblocking: false }, + __Foo_dealloc: { + parameters: [ + 'pointer', + ], + result: 'void', + nonblocking: false + }, }); export function add( @@ -141,15 +157,27 @@ export function make_foo(): Foo { return Foo.__constructor(ret); } -function __Foo_new(): Foo { - const ret = symbols.__Foo_new() +export function inc_foo( + arg0: Foo, +): void { + return symbols.inc_foo( + arg0.ptr, + ) +} + +function __Foo_new( + arg0: number, +): Foo { + const ret = symbols.__Foo_new( + arg0, + ) return Foo.__constructor(ret); } -function __Foo_foo( +function __Foo_inc( arg0: Deno.PointerObject | null, ): void { - return symbols.__Foo_foo( + return symbols.__Foo_inc( arg0, ) } @@ -164,6 +192,14 @@ function __Foo_bar( ) } +function __Foo_dealloc( + arg0: Deno.PointerObject | null, +): void { + return symbols.__Foo_dealloc( + arg0, + ) +} + export class Foo { ptr: Deno.PointerObject | null = null; @@ -173,12 +209,19 @@ export class Foo { return self; } - constructor() { - return __Foo_new() + [Symbol.dispose]() { + this.dealloc(); + this.ptr = null; + } + + constructor(arg0: number) { + return __Foo_new( + arg0, + ) } - foo(): void { - return __Foo_foo( + inc(): void { + return __Foo_inc( this.ptr, ) } @@ -189,4 +232,10 @@ export class Foo { arg0, ) } + + dealloc(): void { + return __Foo_dealloc( + this.ptr, + ) + } } \ No newline at end of file diff --git a/example/bindings_test.ts b/example/bindings_test.ts index e6cd06b..4ba630e 100644 --- a/example/bindings_test.ts +++ b/example/bindings_test.ts @@ -7,9 +7,10 @@ import { strlen, non_blocking, make_foo, - Foo, + inc_foo, + Foo, } from "./bindings/bindings.ts"; -import { assert, assertEquals } from "https://deno.land/std/testing/asserts.ts"; +import { assert, assertEquals } from "https://deno.land/std@0.178.0/testing/asserts.ts"; Deno.test({ name: "add#test", @@ -72,7 +73,36 @@ Deno.test({ Deno.test({ name: "Foo#constructor", fn() { - const foo = new Foo(); + const foo = new Foo(42); assertEquals(foo.bar(1), 43); } +}) + +Deno.test({ + name: "Foo#using", + fn() { + 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({ + name: "inc_foo#test", + fn: () => { + using foo = new Foo(22); + inc_foo(foo); + assertEquals(foo.bar(0), 23); + } }) \ No newline at end of file diff --git a/example/s.ts b/example/s.ts deleted file mode 100644 index c420a67..0000000 --- a/example/s.ts +++ /dev/null @@ -1,28 +0,0 @@ -class Example { - ptr: Deno.PointerObject | null = null; - - static from_ptr(ptr: Deno.PointerObject | null) { - const obj = Object.create(Example.prototype); - obj.ptr = ptr; - - return obj; - } - - constructor() { - // ... - } - - close() { - ffi_free(this.ptr); - } - - [Symbol.dispose]() { - this.close(); - } -} - -export function make_example(): Example { - return Example.from_ptr(ffi_make_example()); -} - -using obj = make_example(); diff --git a/example/src/lib.rs b/example/src/lib.rs index 1f83345..a334370 100644 --- a/example/src/lib.rs +++ b/example/src/lib.rs @@ -47,6 +47,11 @@ fn make_foo() -> Foo { Foo { internal: 42 } } +#[deno_bindgen] +fn inc_foo(foo: &mut Foo) { + foo.internal += 1; +} + #[deno_bindgen] pub struct Foo { internal: u32, @@ -55,11 +60,13 @@ pub struct Foo { #[deno_bindgen] impl Foo { #[constructor] - fn new() -> Foo { - Foo { internal: 42 } + fn new(internal: u32) -> Foo { + Foo { internal } } - fn foo(&self) {} + fn inc(&mut self) { + self.internal += 1; + } fn bar(&self, a: u32) -> u32 { self.internal + a From e1d0f669f0bb90172cf6544de3d0aa76593e749d Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Wed, 8 Nov 2023 23:15:51 +0530 Subject: [PATCH 07/15] 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] From 6761e2586494a3c42e6e71a101ea0894ff8fefb5 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sun, 12 Nov 2023 11:41:42 +0530 Subject: [PATCH 08/15] lazy init --- Cargo.lock | 80 ++++++++++-- README.md | 6 +- deno_bindgen_cli/Cargo.toml | 3 +- deno_bindgen_cli/cargo.rs | 57 +++++++- deno_bindgen_cli/dlfcn.rs | 3 + deno_bindgen_cli/main.rs | 17 ++- deno_bindgen_ir/codegen/deno.rs | 61 ++++++++- deno_bindgen_ir/codegen/mod.rs | 6 +- example/bindings/bindings.ts | 222 ++++++++++++++++---------------- 9 files changed, 317 insertions(+), 138 deletions(-) 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, From 587ebceb2e45de1f95e4866f052d0ee2146d3941 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sun, 12 Nov 2023 12:01:12 +0530 Subject: [PATCH 09/15] stuff --- .rustfmt.toml | 11 +- Makefile | 4 +- README.md | 22 +++ deno_bindgen_cli/cargo.rs | 10 +- deno_bindgen_cli/dlfcn.rs | 6 +- deno_bindgen_ir/codegen/deno.rs | 16 +- deno_bindgen_ir/codegen/mod.rs | 2 +- deno_bindgen_ir/lib.rs | 6 +- deno_bindgen_macro/src/fn_.rs | 35 ++-- deno_bindgen_macro/src/impl_.rs | 8 +- deno_bindgen_macro/src/lib.rs | 9 +- deno_bindgen_macro/src/struct_.rs | 3 +- example/bindings/{bindings.ts => mod.ts} | 222 +++++++++++------------ example/bindings_test.ts | 2 +- 14 files changed, 189 insertions(+), 167 deletions(-) rename example/bindings/{bindings.ts => mod.ts} (58%) diff --git a/.rustfmt.toml b/.rustfmt.toml index de76f84..d039a95 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,11 +1,4 @@ -edition = "2021" max_width = 80 tab_spaces = 2 -unstable_features = true -condense_wildcard_suffixes = true -newline_style = "Unix" -use_field_init_shorthand = true -use_try_shorthand = true -imports_granularity = "Crate" -group_imports = "StdExternalCrate" -reorder_imports = true +edition = "2021" +imports_granularity = "Item" \ No newline at end of file diff --git a/Makefile b/Makefile index 8feeab7..57207dd 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ build: cargo build test: build - cd example && ../target/debug/deno_bindgen -o bindings/bindings.ts && deno test -A --unstable + cd example && ../target/debug/deno_bindgen -o bindings/mod.ts && deno test -A --unstable bench: build - cd example && ../target/debug/deno_bindgen -o bindings/bindings.ts && deno bench -A --unstable bench.js + cd example && ../target/debug/deno_bindgen -o bindings/mod.ts && deno bench -A --unstable bench.js diff --git a/README.md b/README.md index 168ad57..63240e8 100644 --- a/README.md +++ b/README.md @@ -86,3 +86,25 @@ 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 ``` + +## Publishing + +By default, deno_bindgen generates bindings for local development. To publish a +cross-platform binding, you can use the `--lazy-init` flag, this gives you full +control on how you want to host pre-built shared libraries and pull them in at +runtime. + +```bash +deno_bindgen --release --lazy-init +``` + +```typescript +import { add, load } from "./example/mod.ts"; +import { cache } from "https://deno.land/x/cache/mod.ts"; + +// Download the shared library from a CDN +const file = await cache("https://example.com/example.so"); +load(file.path); + +add(1, 2); +``` diff --git a/deno_bindgen_cli/cargo.rs b/deno_bindgen_cli/cargo.rs index 84848e9..13f2751 100644 --- a/deno_bindgen_cli/cargo.rs +++ b/deno_bindgen_cli/cargo.rs @@ -1,8 +1,8 @@ -use std::{ - io::Result, - path::{Path, PathBuf}, - process::{Command, Stdio}, -}; +use std::io::Result; +use std::path::Path; +use std::path::PathBuf; +use std::process::Command; +use std::process::Stdio; pub struct Artifact { pub path: PathBuf, diff --git a/deno_bindgen_cli/dlfcn.rs b/deno_bindgen_cli/dlfcn.rs index 8be69f3..5d0621a 100644 --- a/deno_bindgen_cli/dlfcn.rs +++ b/deno_bindgen_cli/dlfcn.rs @@ -1,6 +1,8 @@ -use std::path::{Path, PathBuf}; +use std::path::Path; +use std::path::PathBuf; -use dlopen2::wrapper::{Container, WrapperApi}; +use dlopen2::wrapper::Container; +use dlopen2::wrapper::WrapperApi; #[derive(WrapperApi)] struct Api { diff --git a/deno_bindgen_ir/codegen/deno.rs b/deno_bindgen_ir/codegen/deno.rs index ed818e7..c3aa431 100644 --- a/deno_bindgen_ir/codegen/deno.rs +++ b/deno_bindgen_ir/codegen/deno.rs @@ -1,14 +1,12 @@ -use std::{ - borrow::Cow, - io::{Result, Write}, - path::Path, -}; +use std::borrow::Cow; +use std::io::Result; +use std::io::Write; +use std::path::Path; use super::Generator; -use crate::{ - inventory::{Inventory, Struct}, - Symbol, Type, -}; +use crate::inventory::Inventory; +use crate::inventory::Struct; +use crate::Type; // (ident, is_custom_type) struct TypeScriptType<'a>(&'a str, bool); diff --git a/deno_bindgen_ir/codegen/mod.rs b/deno_bindgen_ir/codegen/mod.rs index 8f475b0..4211fbd 100644 --- a/deno_bindgen_ir/codegen/mod.rs +++ b/deno_bindgen_ir/codegen/mod.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use crate::{inventory::Inventory, Symbol}; +use crate::inventory::Inventory; mod deno; diff --git a/deno_bindgen_ir/lib.rs b/deno_bindgen_ir/lib.rs index 646bcbc..1cf7312 100644 --- a/deno_bindgen_ir/lib.rs +++ b/deno_bindgen_ir/lib.rs @@ -1,6 +1,8 @@ use proc_macro2::Ident; -use quote::{quote, ToTokens}; -use syn::{parse_quote, Pat}; +use quote::quote; +use quote::ToTokens; +use syn::parse_quote; +use syn::Pat; pub mod codegen; pub mod inventory; diff --git a/deno_bindgen_macro/src/fn_.rs b/deno_bindgen_macro/src/fn_.rs index 0691b6c..e320398 100644 --- a/deno_bindgen_macro/src/fn_.rs +++ b/deno_bindgen_macro/src/fn_.rs @@ -1,17 +1,24 @@ -use std::path::Path; - -use deno_bindgen_ir::{Symbol, SymbolBuilder, Type}; -use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; -use quote::quote; -use syn::{ - parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, FnArg, - ItemFn, PatType, ReturnType, TypePath, TypePtr, TypeReference, TypeSlice, -}; - -use crate::{ - util::{Error, Result}, - FnAttributes, -}; +use deno_bindgen_ir::SymbolBuilder; +use deno_bindgen_ir::Type; +use proc_macro2::Ident; +use proc_macro2::Span; +use proc_macro2::TokenStream as TokenStream2; +use syn::parse_quote; +use syn::punctuated::Punctuated; +use syn::spanned::Spanned; +use syn::token::Comma; +use syn::FnArg; +use syn::ItemFn; +use syn::PatType; +use syn::ReturnType; +use syn::TypePath; +use syn::TypePtr; +use syn::TypeReference; +use syn::TypeSlice; + +use crate::util::Error; +use crate::util::Result; +use crate::FnAttributes; fn custom_type(ty: &str) -> Type { // yeah, don't worry about it. diff --git a/deno_bindgen_macro/src/impl_.rs b/deno_bindgen_macro/src/impl_.rs index 37ad435..dcd6a8e 100644 --- a/deno_bindgen_macro/src/impl_.rs +++ b/deno_bindgen_macro/src/impl_.rs @@ -1,8 +1,12 @@ use proc_macro2::TokenStream as TokenStream2; use quote::format_ident; -use syn::{parse_quote, punctuated::Punctuated, ImplItemFn, ItemImpl}; +use syn::parse_quote; +use syn::punctuated::Punctuated; +use syn::ImplItemFn; +use syn::ItemImpl; -use crate::util::{self, Result}; +use crate::util::Result; +use crate::util::{self}; pub fn handle(mut impl_: ItemImpl) -> Result { if impl_.generics.params.first().is_some() { diff --git a/deno_bindgen_macro/src/lib.rs b/deno_bindgen_macro/src/lib.rs index 800d6dd..2f12d88 100644 --- a/deno_bindgen_macro/src/lib.rs +++ b/deno_bindgen_macro/src/lib.rs @@ -1,11 +1,10 @@ // Copyright 2020-2021 the Deno authors. All rights reserved. MIT license. -use deno_bindgen_ir::Symbol; use proc_macro::TokenStream; -use quote::{format_ident, quote}; -use syn::{ - meta::ParseNestedMeta, parse2, parse_macro_input, parse_quote, Item, ItemFn, -}; +use syn::meta::ParseNestedMeta; +use syn::parse2; +use syn::parse_macro_input; +use syn::Item; mod fn_; mod impl_; diff --git a/deno_bindgen_macro/src/struct_.rs b/deno_bindgen_macro/src/struct_.rs index 9034bdc..0891261 100644 --- a/deno_bindgen_macro/src/struct_.rs +++ b/deno_bindgen_macro/src/struct_.rs @@ -1,7 +1,8 @@ use proc_macro2::TokenStream as TokenStream2; use syn::ItemStruct; -use crate::util::{self, Result}; +use crate::util::Result; +use crate::util::{self}; pub fn handle(struct_: ItemStruct) -> Result { if struct_.generics.params.first().is_some() { diff --git a/example/bindings/bindings.ts b/example/bindings/mod.ts similarity index 58% rename from example/bindings/bindings.ts rename to example/bindings/mod.ts index 97e765e..5789ecc 100644 --- a/example/bindings/bindings.ts +++ b/example/bindings/mod.ts @@ -3,120 +3,114 @@ // This file is automatically generated by deno_bindgen. // Do not edit this file directly. -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_; -} +const { dlopen } = Deno; + +const { symbols } = dlopen('/Users/divy/gh/deno_bindgen/example/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 + }, +}); export function add( arg0: number, diff --git a/example/bindings_test.ts b/example/bindings_test.ts index 2f6993f..dc46723 100644 --- a/example/bindings_test.ts +++ b/example/bindings_test.ts @@ -10,7 +10,7 @@ import { make_foo, non_blocking, strlen, -} from "./bindings/bindings.ts"; +} from "./bindings/mod.ts"; import { assert, assertEquals, From c40e5ccecf0b0048f94d6fddafce4f66790681b1 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sun, 12 Nov 2023 13:03:13 +0530 Subject: [PATCH 10/15] Add tests --- Cargo.lock | 101 ++++++++++++++++++ Makefile | 1 + README.md | 2 +- deno_bindgen/Cargo.toml | 5 +- .../tests/compile_fail/impl_registration.rs | 15 +++ .../compile_fail/impl_registration.stderr | 12 +++ .../tests/compile_fail/struct_by_val.rs | 20 ++++ .../tests/compile_fail/struct_by_val.stderr | 15 +++ .../tests/pass/constructor_override.rs | 22 ++++ deno_bindgen/tests/pass/simple.rs | 63 +++++++++++ deno_bindgen/tests/ui.rs | 11 ++ deno_bindgen_macro/Cargo.toml | 4 + deno_bindgen_macro/src/lib.rs | 32 ++++++ deno_bindgen_macro/tests/fn/add.test.out.rs | 19 ++++ deno_bindgen_macro/tests/fn/add.test.rs | 1 + .../tests/fn/buffer.test.out.rs | 24 +++++ deno_bindgen_macro/tests/fn/buffer.test.rs | 7 ++ .../tests/fn/pointer.test.out.rs | 21 ++++ deno_bindgen_macro/tests/fn/pointer.test.rs | 3 + 19 files changed, 376 insertions(+), 2 deletions(-) create mode 100644 deno_bindgen/tests/compile_fail/impl_registration.rs create mode 100644 deno_bindgen/tests/compile_fail/impl_registration.stderr create mode 100644 deno_bindgen/tests/compile_fail/struct_by_val.rs create mode 100644 deno_bindgen/tests/compile_fail/struct_by_val.stderr create mode 100644 deno_bindgen/tests/pass/constructor_override.rs create mode 100644 deno_bindgen/tests/pass/simple.rs create mode 100644 deno_bindgen/tests/ui.rs create mode 100644 deno_bindgen_macro/tests/fn/add.test.out.rs create mode 100644 deno_bindgen_macro/tests/fn/add.test.rs create mode 100644 deno_bindgen_macro/tests/fn/buffer.test.out.rs create mode 100644 deno_bindgen_macro/tests/fn/buffer.test.rs create mode 100644 deno_bindgen_macro/tests/fn/pointer.test.out.rs create mode 100644 deno_bindgen_macro/tests/fn/pointer.test.rs diff --git a/Cargo.lock b/Cargo.lock index ba523b3..8407e0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + [[package]] name = "atty" version = "0.2.14" @@ -41,6 +47,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "basic-toml" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f2139706359229bfa8f19142ac1155b4b80beafb7a60471ac5dd109d4a19778" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -103,6 +118,7 @@ dependencies = [ "linkme", "serde", "serde_json", + "trybuild", ] [[package]] @@ -130,11 +146,13 @@ version = "0.8.1" dependencies = [ "Inflector", "deno_bindgen_ir", + "prettyplease", "proc-macro2", "quote", "serde", "serde_json", "syn 2.0.39", + "testing_macros", ] [[package]] @@ -160,6 +178,12 @@ dependencies = [ "syn 2.0.39", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "heck" version = "0.3.3" @@ -228,6 +252,27 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "pmutil" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn 2.0.39", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -287,6 +332,12 @@ version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +[[package]] +name = "relative-path" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c707298afce11da2efef2f600116fa93ffa7a032b5d7b628aa17711ec81383ca" + [[package]] name = "ryu" version = "1.0.5" @@ -385,6 +436,32 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termcolor" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "testing_macros" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1c15b796025051a07f1ac695ee0cac0883f05a0d510c9d171ef8d31a992e6a5" +dependencies = [ + "anyhow", + "glob", + "once_cell", + "pmutil", + "proc-macro2", + "quote", + "regex", + "relative-path", + "syn 2.0.39", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -414,6 +491,21 @@ dependencies = [ "syn 2.0.39", ] +[[package]] +name = "trybuild" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196a58260a906cedb9bf6d8034b6379d0c11f552416960452f267402ceeddff1" +dependencies = [ + "basic-toml", + "glob", + "once_cell", + "serde", + "serde_derive", + "serde_json", + "termcolor", +] + [[package]] name = "unicode-ident" version = "1.0.12" @@ -466,6 +558,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Makefile b/Makefile index 57207dd..c246c02 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ build: cargo build test: build + cargo test cd example && ../target/debug/deno_bindgen -o bindings/mod.ts && deno test -A --unstable bench: build diff --git a/README.md b/README.md index 63240e8..3e5427a 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ fn add(a: u32, b: u32) -> u32 { Use the exported functions directly in ESM with TypeScript typings ```typescript -import { add } from "@ffi/example"; +import { add } from "./bindings/mod.ts"; add(1, 2); ``` diff --git a/deno_bindgen/Cargo.toml b/deno_bindgen/Cargo.toml index 44a2fde..df535ad 100644 --- a/deno_bindgen/Cargo.toml +++ b/deno_bindgen/Cargo.toml @@ -19,4 +19,7 @@ deno_bindgen_macro = { path = "../deno_bindgen_macro", version = "0.8.1" } deno_bindgen_ir = { path = "../deno_bindgen_ir", version = "0.1.0" } serde = { version = "1", features = ["derive"] } serde_json = "1" -linkme = "0.3" \ No newline at end of file +linkme = "0.3" + +[dev-dependencies] +trybuild = "1.0.85" \ No newline at end of file diff --git a/deno_bindgen/tests/compile_fail/impl_registration.rs b/deno_bindgen/tests/compile_fail/impl_registration.rs new file mode 100644 index 0000000..607c6fb --- /dev/null +++ b/deno_bindgen/tests/compile_fail/impl_registration.rs @@ -0,0 +1,15 @@ +use deno_bindgen::deno_bindgen; + +// struct Foo is not "registered" in the inventory, so it `impl Foo` +// is not allowed. +struct Foo; + +#[deno_bindgen] +impl Foo { + #[constructor] + fn new() -> Foo { + Foo + } +} + +fn main() {} \ No newline at end of file diff --git a/deno_bindgen/tests/compile_fail/impl_registration.stderr b/deno_bindgen/tests/compile_fail/impl_registration.stderr new file mode 100644 index 0000000..19ae001 --- /dev/null +++ b/deno_bindgen/tests/compile_fail/impl_registration.stderr @@ -0,0 +1,12 @@ +error[E0277]: the trait bound `Foo: BindgenType` is not satisfied + --> tests/compile_fail/impl_registration.rs:8:6 + | +8 | impl Foo { + | ^^^ the trait `BindgenType` is not implemented for `Foo` + | +note: required by a bound in `_assert_impl` + --> tests/compile_fail/impl_registration.rs:7:1 + | +7 | #[deno_bindgen] + | ^^^^^^^^^^^^^^^ required by this bound in `_assert_impl` + = note: this error originates in the attribute macro `deno_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/deno_bindgen/tests/compile_fail/struct_by_val.rs b/deno_bindgen/tests/compile_fail/struct_by_val.rs new file mode 100644 index 0000000..207c7ed --- /dev/null +++ b/deno_bindgen/tests/compile_fail/struct_by_val.rs @@ -0,0 +1,20 @@ +use deno_bindgen::deno_bindgen; + +#[deno_bindgen] +struct Foo; + +#[deno_bindgen] +impl Foo { + #[constructor] + fn new() -> Foo { + Foo + } +} + +#[deno_bindgen] +fn foo(_foo: Foo) {} // Fail + +#[deno_bindgen] +fn foo2(_foo: &mut Foo) {} // Pass + +fn main() {} \ No newline at end of file diff --git a/deno_bindgen/tests/compile_fail/struct_by_val.stderr b/deno_bindgen/tests/compile_fail/struct_by_val.stderr new file mode 100644 index 0000000..d5021a3 --- /dev/null +++ b/deno_bindgen/tests/compile_fail/struct_by_val.stderr @@ -0,0 +1,15 @@ +error[E0308]: mismatched types + --> tests/compile_fail/struct_by_val.rs:15:8 + | +15 | fn foo(_foo: Foo) {} // Fail + | --- ^^^^ expected `Foo`, found `&mut _` + | | + | arguments to this function are incorrect + | + = note: expected struct `Foo` + found mutable reference `&mut _` +note: function defined here + --> tests/compile_fail/struct_by_val.rs:15:4 + | +15 | fn foo(_foo: Foo) {} // Fail + | ^^^ --------- diff --git a/deno_bindgen/tests/pass/constructor_override.rs b/deno_bindgen/tests/pass/constructor_override.rs new file mode 100644 index 0000000..92392df --- /dev/null +++ b/deno_bindgen/tests/pass/constructor_override.rs @@ -0,0 +1,22 @@ +use deno_bindgen::deno_bindgen; + +#[deno_bindgen] +struct Input { + a: i32, + b: i32, +} + +#[deno_bindgen] +impl Input { + #[constructor] + fn new(a: i32, b: i32) -> Input { + Input { a, b } + } + + #[constructor] + fn new2() -> Input { + Input { a: 0, b: 0 } + } +} + +fn main() {} \ No newline at end of file diff --git a/deno_bindgen/tests/pass/simple.rs b/deno_bindgen/tests/pass/simple.rs new file mode 100644 index 0000000..a646466 --- /dev/null +++ b/deno_bindgen/tests/pass/simple.rs @@ -0,0 +1,63 @@ +use deno_bindgen_macro::deno_bindgen; + +#[deno_bindgen] +fn add(a: i32, b: i32) -> i32 { + a + b +} + +#[deno_bindgen] +fn buf_mut(b: &mut [u8]) { + b[0] = 99; +} + +#[deno_bindgen] +fn cstr() -> *const u8 { + b"Hello, World!\0".as_ptr() +} + +#[deno_bindgen] +fn strlen(s: *const u8) -> u32 { + let mut len = 0; + unsafe { + while *s.add(len as usize) != 0 { + len += 1; + } + } + len +} + +#[deno_bindgen(non_blocking)] +fn non_blocking() -> i32 { + 42 +} + +#[deno_bindgen] +struct Foo { + internal: i32, +} + +#[deno_bindgen] +impl Foo { + #[constructor] + fn new(internal: i32) -> Foo { + Foo { internal } + } + + fn bar(&self) -> i32 { + 42 + } + + fn baz(&self, a: i32) -> i32 { + a + } + + fn qux(&self, a: i32, b: i32) -> i32 { + a + b + } + + fn quux(&mut self) { + self.internal += 1; + } +} + +fn main() {} \ No newline at end of file diff --git a/deno_bindgen/tests/ui.rs b/deno_bindgen/tests/ui.rs new file mode 100644 index 0000000..688afe2 --- /dev/null +++ b/deno_bindgen/tests/ui.rs @@ -0,0 +1,11 @@ +#[test] +fn ui_pass() { + let t = trybuild::TestCases::new(); + t.pass("tests/pass/*.rs"); +} + +#[test] +fn ui_compile_fail() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/compile_fail/*.rs"); +} diff --git a/deno_bindgen_macro/Cargo.toml b/deno_bindgen_macro/Cargo.toml index 32ff6e0..736f529 100644 --- a/deno_bindgen_macro/Cargo.toml +++ b/deno_bindgen_macro/Cargo.toml @@ -22,3 +22,7 @@ serde = { version = "1.0.59", features = ["derive"] } serde_json = "1.0.59" Inflector = "0.11.4" deno_bindgen_ir = { path = "../deno_bindgen_ir", version = "0.1.0" } + +[dev-dependencies] +prettyplease = "0.2.15" +testing_macros = "0.2.11" \ No newline at end of file diff --git a/deno_bindgen_macro/src/lib.rs b/deno_bindgen_macro/src/lib.rs index 2f12d88..ddb3709 100644 --- a/deno_bindgen_macro/src/lib.rs +++ b/deno_bindgen_macro/src/lib.rs @@ -45,3 +45,35 @@ pub fn deno_bindgen(args: TokenStream, input: TokenStream) -> TokenStream { _ => panic!("only functions are supported"), } } + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + #[testing_macros::fixture("tests/fn/*.test.rs")] + fn test_codegen_fn(input: PathBuf) { + let update_expected = std::env::var("UPDATE_EXPECTED").is_ok(); + + let source = + std::fs::read_to_string(&input).expect("failed to read test case"); + let item_fn = syn::parse_str::(&source) + .expect("failed to parse test case"); + + let tokens = crate::fn_::handle(item_fn, Default::default()).unwrap(); + let tree = syn::parse2(tokens).unwrap(); + let actual = prettyplease::unparse(&tree); + + let expected_out = input.with_extension("out.rs"); + if update_expected { + std::fs::write(expected_out, actual) + .expect("Failed to write expectation file"); + } else { + let expected = std::fs::read_to_string(expected_out) + .expect("Failed to read expectation file"); + assert_eq!( + expected, actual, + "Failed to match expectation. Use UPDATE_EXPECTED=1." + ); + } + } +} diff --git a/deno_bindgen_macro/tests/fn/add.test.out.rs b/deno_bindgen_macro/tests/fn/add.test.out.rs new file mode 100644 index 0000000..7a70eb1 --- /dev/null +++ b/deno_bindgen_macro/tests/fn/add.test.out.rs @@ -0,0 +1,19 @@ +const _: () = { + #[deno_bindgen::linkme::distributed_slice(deno_bindgen::INVENTORY)] + pub static _A: deno_bindgen::Inventory = deno_bindgen::Inventory::Symbol(deno_bindgen::Symbol { + name: stringify!(add), + parameters: &[deno_bindgen::Type::Int32, deno_bindgen::Type::Int32], + return_type: deno_bindgen::Type::Int32, + non_blocking: false, + internal: false, + is_constructor: false, + }); +}; +#[no_mangle] +extern "C" fn add(a: i32, b: i32) -> i32 { + fn add(a: i32, b: i32) -> i32 { + a + b + } + let ret = add(a, b); + ret +} diff --git a/deno_bindgen_macro/tests/fn/add.test.rs b/deno_bindgen_macro/tests/fn/add.test.rs new file mode 100644 index 0000000..52d1530 --- /dev/null +++ b/deno_bindgen_macro/tests/fn/add.test.rs @@ -0,0 +1 @@ +fn add(a: i32, b: i32) -> i32 { a + b } \ No newline at end of file diff --git a/deno_bindgen_macro/tests/fn/buffer.test.out.rs b/deno_bindgen_macro/tests/fn/buffer.test.out.rs new file mode 100644 index 0000000..8916651 --- /dev/null +++ b/deno_bindgen_macro/tests/fn/buffer.test.out.rs @@ -0,0 +1,24 @@ +const _: () = { + #[deno_bindgen::linkme::distributed_slice(deno_bindgen::INVENTORY)] + pub static _A: deno_bindgen::Inventory = deno_bindgen::Inventory::Symbol(deno_bindgen::Symbol { + name: stringify!(write_hello), + parameters: &[deno_bindgen::Type::Buffer], + return_type: deno_bindgen::Type::Void, + non_blocking: false, + internal: false, + is_constructor: false, + }); +}; +#[no_mangle] +extern "C" fn write_hello(__arg_0: *const (), __arg_1: u32) { + fn write_hello(buf: &mut [u8]) { + buf[0] = b'H'; + buf[1] = b'e'; + buf[2] = b'l'; + buf[3] = b'l'; + buf[4] = b'o'; + } + let buf = unsafe { std::slice::from_raw_parts_mut(__arg_0 as _, __arg_1 as usize) }; + let ret = write_hello(buf); + ret +} diff --git a/deno_bindgen_macro/tests/fn/buffer.test.rs b/deno_bindgen_macro/tests/fn/buffer.test.rs new file mode 100644 index 0000000..5ef14e2 --- /dev/null +++ b/deno_bindgen_macro/tests/fn/buffer.test.rs @@ -0,0 +1,7 @@ +fn write_hello(buf: &mut [u8]) { + buf[0] = b'H'; + buf[1] = b'e'; + buf[2] = b'l'; + buf[3] = b'l'; + buf[4] = b'o'; +} \ No newline at end of file diff --git a/deno_bindgen_macro/tests/fn/pointer.test.out.rs b/deno_bindgen_macro/tests/fn/pointer.test.out.rs new file mode 100644 index 0000000..fb7f12c --- /dev/null +++ b/deno_bindgen_macro/tests/fn/pointer.test.out.rs @@ -0,0 +1,21 @@ +const _: () = { + #[deno_bindgen::linkme::distributed_slice(deno_bindgen::INVENTORY)] + pub static _A: deno_bindgen::Inventory = deno_bindgen::Inventory::Symbol(deno_bindgen::Symbol { + name: stringify!(is_utf8), + parameters: &[deno_bindgen::Type::Pointer, deno_bindgen::Type::Uint64], + return_type: deno_bindgen::Type::Int32, + non_blocking: false, + internal: false, + is_constructor: false, + }); +}; +#[no_mangle] +extern "C" fn is_utf8(__arg_0: *const (), len: usize) -> i32 { + fn is_utf8(ptr: *const u8, len: usize) -> i32 { + std::str::from_utf8(unsafe { std::slice::from_raw_parts(ptr, len) }).is_ok() + as i32 + } + let ptr = __arg_0 as _; + let ret = is_utf8(ptr, len); + ret +} diff --git a/deno_bindgen_macro/tests/fn/pointer.test.rs b/deno_bindgen_macro/tests/fn/pointer.test.rs new file mode 100644 index 0000000..bc9ffab --- /dev/null +++ b/deno_bindgen_macro/tests/fn/pointer.test.rs @@ -0,0 +1,3 @@ +fn is_utf8(ptr: *const u8, len: usize) -> i32 { + std::str::from_utf8(unsafe { std::slice::from_raw_parts(ptr, len) }).is_ok() as i32 +} \ No newline at end of file From e19ca24b15bb5f2a2432e3c920a59f936e7fec4a Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sun, 12 Nov 2023 13:04:47 +0530 Subject: [PATCH 11/15] x --- Cargo.toml | 2 +- Makefile | 6 +- e2e_test/Cargo.lock | 203 ++++++++++++++++++++ {example => e2e_test}/Cargo.toml | 4 +- {example => e2e_test}/bench.js | 0 {example => e2e_test}/bindings/mod.ts | 256 +++++++++++++------------ {example => e2e_test}/bindings_test.ts | 0 {example => e2e_test}/src/lib.rs | 0 8 files changed, 338 insertions(+), 133 deletions(-) create mode 100644 e2e_test/Cargo.lock rename {example => e2e_test}/Cargo.toml (78%) rename {example => e2e_test}/bench.js (100%) rename {example => e2e_test}/bindings/mod.ts (56%) rename {example => e2e_test}/bindings_test.ts (100%) rename {example => e2e_test}/src/lib.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index a7fd056..b06e6aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,4 @@ members = [ "deno_bindgen_ir", "deno_bindgen_cli" ] -exclude = ["example/"] \ No newline at end of file +exclude = ["e2e_test/"] \ No newline at end of file diff --git a/Makefile b/Makefile index c246c02..6d4c5e1 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,13 @@ fmt: cargo fmt - deno fmt --ignore=target/,example/target/,example/bindings/ + deno fmt --ignore=target/,e2e_test/target/,e2e_test/bindings/ build: cargo build test: build cargo test - cd example && ../target/debug/deno_bindgen -o bindings/mod.ts && deno test -A --unstable + cd e2e_test && ../target/debug/deno_bindgen -o bindings/mod.ts && deno test -A --unstable bench: build - cd example && ../target/debug/deno_bindgen -o bindings/mod.ts && deno bench -A --unstable bench.js + cd e2e_test && ../target/debug/deno_bindgen -o bindings/mod.ts && deno bench -A --unstable bench.js diff --git a/e2e_test/Cargo.lock b/e2e_test/Cargo.lock new file mode 100644 index 0000000..de1faa5 --- /dev/null +++ b/e2e_test/Cargo.lock @@ -0,0 +1,203 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "deno_bindgen" +version = "0.8.1" +dependencies = [ + "deno_bindgen_ir", + "deno_bindgen_macro", + "linkme", + "serde", + "serde_json", +] + +[[package]] +name = "deno_bindgen_e2e" +version = "0.1.0" +dependencies = [ + "deno_bindgen", + "linkme", + "serde", +] + +[[package]] +name = "deno_bindgen_ir" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "deno_bindgen_macro" +version = "0.8.1" +dependencies = [ + "Inflector", + "deno_bindgen_ir", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "linkme" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ed2ee9464ff9707af8e9ad834cffa4802f072caad90639c583dd3c62e6e608" +dependencies = [ + "linkme-impl", +] + +[[package]] +name = "linkme-impl" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba125974b109d512fccbc6c0244e7580143e460895dfd6ea7f8bbb692fd94396" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "serde" +version = "1.0.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "2.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/example/Cargo.toml b/e2e_test/Cargo.toml similarity index 78% rename from example/Cargo.toml rename to e2e_test/Cargo.toml index 539b116..fb8f652 100644 --- a/example/Cargo.toml +++ b/e2e_test/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "deno_bindgen_test" +name = "deno_bindgen_e2e" version = "0.1.0" edition = "2018" @@ -9,5 +9,5 @@ serde = { version = "1", features = ["derive"] } linkme = "0.3" [lib] -name = "deno_bindgen_test" +name = "deno_bindgen_e2e" crate-type = ["cdylib"] diff --git a/example/bench.js b/e2e_test/bench.js similarity index 100% rename from example/bench.js rename to e2e_test/bench.js diff --git a/example/bindings/mod.ts b/e2e_test/bindings/mod.ts similarity index 56% rename from example/bindings/mod.ts rename to e2e_test/bindings/mod.ts index 5789ecc..df8b54e 100644 --- a/example/bindings/mod.ts +++ b/e2e_test/bindings/mod.ts @@ -5,112 +5,115 @@ const { dlopen } = Deno; -const { symbols } = dlopen('/Users/divy/gh/deno_bindgen/example/target/debug/libdeno_bindgen_test.dylib', { - add: { - parameters: [ - 'i32', - 'i32', - ], - result: 'i32', - nonblocking: false +const { symbols } = dlopen( + "/Users/divy/gh/deno_bindgen/e2e_test/target/debug/libdeno_bindgen_e2e.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, + }, }, - __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 - }, -}); +); export function add( arg0: number, @@ -119,7 +122,7 @@ export function add( return symbols.add( arg0, arg1, - ) + ); } function __Input_new( @@ -129,7 +132,7 @@ function __Input_new( const ret = symbols.__Input_new( arg0, arg1, - ) + ); return Input.__constructor(ret); } @@ -138,7 +141,7 @@ function __Input_dealloc( ): void { return symbols.__Input_dealloc( arg0, - ) + ); } export class Input { @@ -159,13 +162,13 @@ export class Input { return __Input_new( arg0, arg1, - ) + ); } dealloc(): void { return __Input_dealloc( this.ptr, - ) + ); } } @@ -174,7 +177,7 @@ export function add2( ): number { return symbols.add2( arg0.ptr, - ) + ); } export function bytelen( @@ -183,7 +186,7 @@ export function bytelen( return symbols.bytelen( arg0, arg0.byteLength, - ) + ); } export function buf_mut( @@ -192,11 +195,11 @@ export function buf_mut( return symbols.buf_mut( arg0, arg0.byteLength, - ) + ); } export function cstr(): Deno.PointerObject | null { - return symbols.cstr() + return symbols.cstr(); } export function strlen( @@ -204,15 +207,15 @@ export function strlen( ): number { return symbols.strlen( arg0, - ) + ); } export function non_blocking(): Promise { - return symbols.non_blocking() + return symbols.non_blocking(); } export function make_foo(): Foo { - const ret = symbols.make_foo() + const ret = symbols.make_foo(); return Foo.__constructor(ret); } @@ -221,7 +224,7 @@ export function inc_foo( ): void { return symbols.inc_foo( arg0.ptr, - ) + ); } function __Foo_new( @@ -229,7 +232,7 @@ function __Foo_new( ): Foo { const ret = symbols.__Foo_new( arg0, - ) + ); return Foo.__constructor(ret); } @@ -238,7 +241,7 @@ function __Foo_inc( ): void { return symbols.__Foo_inc( arg0, - ) + ); } function __Foo_bar( @@ -248,7 +251,7 @@ function __Foo_bar( return symbols.__Foo_bar( arg0, arg1, - ) + ); } function __Foo_dealloc( @@ -256,7 +259,7 @@ function __Foo_dealloc( ): void { return symbols.__Foo_dealloc( arg0, - ) + ); } export class Foo { @@ -276,26 +279,25 @@ export class Foo { constructor(arg0: number) { return __Foo_new( arg0, - ) + ); } inc(): void { return __Foo_inc( this.ptr, - ) + ); } bar(arg0: number): number { return __Foo_bar( this.ptr, arg0, - ) + ); } dealloc(): void { return __Foo_dealloc( this.ptr, - ) + ); } } - diff --git a/example/bindings_test.ts b/e2e_test/bindings_test.ts similarity index 100% rename from example/bindings_test.ts rename to e2e_test/bindings_test.ts diff --git a/example/src/lib.rs b/e2e_test/src/lib.rs similarity index 100% rename from example/src/lib.rs rename to e2e_test/src/lib.rs From 407a93093f365d54087fc6a4b8716dbee0177972 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sun, 12 Nov 2023 13:39:25 +0530 Subject: [PATCH 12/15] Add lsusb example --- Cargo.toml | 2 +- Makefile | 2 +- deno_bindgen_cli/cargo.rs | 12 +- deno_bindgen_cli/main.rs | 10 +- e2e_test/bench.js | 8 +- e2e_test/bindings/mod.ts | 256 +++++++++++++++++++------------------- example/Cargo.toml | 15 +++ example/README.md | 13 ++ example/lib.rs | 71 +++++++++++ example/lsusb.ts | 4 + example/mod.ts | 203 ++++++++++++++++++++++++++++++ 11 files changed, 456 insertions(+), 140 deletions(-) create mode 100644 example/Cargo.toml create mode 100644 example/README.md create mode 100644 example/lib.rs create mode 100644 example/lsusb.ts create mode 100644 example/mod.ts diff --git a/Cargo.toml b/Cargo.toml index b06e6aa..d37f4f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,4 @@ members = [ "deno_bindgen_ir", "deno_bindgen_cli" ] -exclude = ["e2e_test/"] \ No newline at end of file +exclude = ["e2e_test/", "example/"] \ No newline at end of file diff --git a/Makefile b/Makefile index 6d4c5e1..49b61df 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ fmt: cargo fmt - deno fmt --ignore=target/,e2e_test/target/,e2e_test/bindings/ + deno fmt --ignore=target/,e2e_test/target/,e2e_test/bindings/,example/target/ build: cargo build diff --git a/deno_bindgen_cli/cargo.rs b/deno_bindgen_cli/cargo.rs index 13f2751..cca48fc 100644 --- a/deno_bindgen_cli/cargo.rs +++ b/deno_bindgen_cli/cargo.rs @@ -41,11 +41,12 @@ impl Build { let output = cmd.output()?; if status.success() { let reader = std::io::BufReader::new(output.stdout.as_slice()); + let mut artifacts = vec![]; 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 { + artifacts.push(Artifact { path: PathBuf::from(artifact.filenames[0].to_string()), manifest_path: PathBuf::from( artifact.manifest_path.to_string(), @@ -57,6 +58,12 @@ impl Build { } } + // TODO: Fix. Not an ideal way to get the artifact of the desired crate, but it + // works for most case. + if let Some(artifact) = artifacts.pop() { + return Ok(artifact); + } + Err(std::io::Error::new( std::io::ErrorKind::Other, "failed to parse cargo output", @@ -72,9 +79,8 @@ impl Build { } } -pub fn metadata(path: &Path) -> Result { +pub fn metadata() -> Result { let metadata = cargo_metadata::MetadataCommand::new() - .manifest_path(path) .exec() .map_err(|e| { println!("failed to execute `cargo metadata`: {}", e); diff --git a/deno_bindgen_cli/main.rs b/deno_bindgen_cli/main.rs index 2ac78ee..808d762 100644 --- a/deno_bindgen_cli/main.rs +++ b/deno_bindgen_cli/main.rs @@ -24,16 +24,16 @@ fn main() -> std::io::Result<()> { let opt = Opt::from_args(); let cwd = std::env::current_dir().unwrap(); - let Artifact { - path, - manifest_path, - } = cargo::Build::new().release(opt.release).build(&cwd)?; + let Artifact { path, .. } = + cargo::Build::new().release(opt.release).build(&cwd)?; - let name = cargo::metadata(&manifest_path)?; + let name = cargo::metadata()?; println!("Initializing {name}"); unsafe { dlfcn::load_and_init(&PathBuf::from(path), opt.out, opt.lazy_init)? }; + + println!("Ready {name}"); Ok(()) } diff --git a/e2e_test/bench.js b/e2e_test/bench.js index ed45ae9..10d472c 100644 --- a/e2e_test/bench.js +++ b/e2e_test/bench.js @@ -1,6 +1,12 @@ -import { add, bytelen } from "./bindings/bindings.ts"; +import { add, bytelen, Foo, make_foo } from "./bindings/mod.ts"; Deno.bench("add", () => add(1, 2)); const b = new Uint8Array([1, 2, 3, 4]); Deno.bench("bytelen", () => bytelen(b)); + +Deno.bench("make_foo", () => make_foo(21)); +Deno.bench("new Foo", () => new Foo(21)); + +const foo = new Foo(21); +Deno.bench("Foo#bar", () => foo.bar(1)); diff --git a/e2e_test/bindings/mod.ts b/e2e_test/bindings/mod.ts index df8b54e..4071862 100644 --- a/e2e_test/bindings/mod.ts +++ b/e2e_test/bindings/mod.ts @@ -5,115 +5,112 @@ const { dlopen } = Deno; -const { symbols } = dlopen( - "/Users/divy/gh/deno_bindgen/e2e_test/target/debug/libdeno_bindgen_e2e.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, - }, +const { symbols } = dlopen('/Users/divy/gh/deno_bindgen/e2e_test/target/debug/libdeno_bindgen_e2e.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 + }, +}); export function add( arg0: number, @@ -122,7 +119,7 @@ export function add( return symbols.add( arg0, arg1, - ); + ) } function __Input_new( @@ -132,7 +129,7 @@ function __Input_new( const ret = symbols.__Input_new( arg0, arg1, - ); + ) return Input.__constructor(ret); } @@ -141,7 +138,7 @@ function __Input_dealloc( ): void { return symbols.__Input_dealloc( arg0, - ); + ) } export class Input { @@ -162,13 +159,13 @@ export class Input { return __Input_new( arg0, arg1, - ); + ) } dealloc(): void { return __Input_dealloc( this.ptr, - ); + ) } } @@ -177,7 +174,7 @@ export function add2( ): number { return symbols.add2( arg0.ptr, - ); + ) } export function bytelen( @@ -186,7 +183,7 @@ export function bytelen( return symbols.bytelen( arg0, arg0.byteLength, - ); + ) } export function buf_mut( @@ -195,11 +192,11 @@ export function buf_mut( return symbols.buf_mut( arg0, arg0.byteLength, - ); + ) } export function cstr(): Deno.PointerObject | null { - return symbols.cstr(); + return symbols.cstr() } export function strlen( @@ -207,15 +204,15 @@ export function strlen( ): number { return symbols.strlen( arg0, - ); + ) } export function non_blocking(): Promise { - return symbols.non_blocking(); + return symbols.non_blocking() } export function make_foo(): Foo { - const ret = symbols.make_foo(); + const ret = symbols.make_foo() return Foo.__constructor(ret); } @@ -224,7 +221,7 @@ export function inc_foo( ): void { return symbols.inc_foo( arg0.ptr, - ); + ) } function __Foo_new( @@ -232,7 +229,7 @@ function __Foo_new( ): Foo { const ret = symbols.__Foo_new( arg0, - ); + ) return Foo.__constructor(ret); } @@ -241,7 +238,7 @@ function __Foo_inc( ): void { return symbols.__Foo_inc( arg0, - ); + ) } function __Foo_bar( @@ -251,7 +248,7 @@ function __Foo_bar( return symbols.__Foo_bar( arg0, arg1, - ); + ) } function __Foo_dealloc( @@ -259,7 +256,7 @@ function __Foo_dealloc( ): void { return symbols.__Foo_dealloc( arg0, - ); + ) } export class Foo { @@ -279,25 +276,26 @@ export class Foo { constructor(arg0: number) { return __Foo_new( arg0, - ); + ) } inc(): void { return __Foo_inc( this.ptr, - ); + ) } bar(arg0: number): number { return __Foo_bar( this.ptr, arg0, - ); + ) } dealloc(): void { return __Foo_dealloc( this.ptr, - ); + ) } } + diff --git a/example/Cargo.toml b/example/Cargo.toml new file mode 100644 index 0000000..9b7aada --- /dev/null +++ b/example/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "usb_example" +version = "0.1.0" +edition = "2018" + +[dependencies] +deno_bindgen = { path = "../deno_bindgen/" } +webusb = "0.5.0" +serde = { version = "1", features = ["derive"] } +linkme = "0.3" + +[lib] +name = "deno_usb" +path = "./lib.rs" +crate-type = ["cdylib"] diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..4435c77 --- /dev/null +++ b/example/README.md @@ -0,0 +1,13 @@ +## `deno_usb_example` + +```bash +$> deno_bindgen -o mod.ts + Finished dev [unoptimized + debuginfo] target(s) in 0.02s +Initializing usb_example +Ready usb_example + +$> deno run --allow-ffi --unstable lsusb.ts +Product Name: G102 LIGHTSYNC Gaming Mouse +Vendor ID: 1133 +Product ID: 49298 +``` diff --git a/example/lib.rs b/example/lib.rs new file mode 100644 index 0000000..6620944 --- /dev/null +++ b/example/lib.rs @@ -0,0 +1,71 @@ +use deno_bindgen::deno_bindgen; + +#[deno_bindgen] +pub struct Context { + context: webusb::Context, +} + +#[deno_bindgen] +impl Context { + #[constructor] + pub fn init() -> Context { + let context = webusb::Context::init().expect("Unable to create context"); + Context { context } + } + + pub fn lsusb(&self) { + let devices = self.context.devices().expect("Unable to get devices"); + for device in devices { + if let Some(name) = device.product_name { + println!("Product Name: {}", name); + } + + println!("Vendor ID: {}", device.vendor_id); + println!("Product ID: {}\n", device.product_id); + } + } + + pub fn open(&mut self, vendor_id: u16, product_id: u16) -> Device { + let devices = self.context.devices().expect("Unable to get devices"); + let mut device = devices + .into_iter() + .find(|d| d.vendor_id == vendor_id && d.product_id == product_id) + .expect("Device not found."); + + device.open().expect("Unable to open device."); + + Device { device } + } +} + +#[deno_bindgen] +pub struct Device { + device: webusb::UsbDevice, +} + +impl Drop for Device { + fn drop(&mut self) { + self.device.close().expect("Unable to close device."); + } +} + +#[deno_bindgen] +impl Device { + pub fn claim_interface(&mut self, interface_number: u8) { + self + .device + .claim_interface(interface_number) + .expect("Unable to claim interface."); + } + + pub fn select_alternate_interface( + &mut self, + interface_number: u8, + alternate_setting: u8, + ) { + self + .device + .select_alternate_interface(interface_number, alternate_setting) + .expect("Unable to select alternate interface."); + } +} diff --git a/example/lsusb.ts b/example/lsusb.ts new file mode 100644 index 0000000..061c8c1 --- /dev/null +++ b/example/lsusb.ts @@ -0,0 +1,4 @@ +import { Context } from "./mod.ts"; + +const context = new Context(); +context.lsusb(); diff --git a/example/mod.ts b/example/mod.ts new file mode 100644 index 0000000..43aa009 --- /dev/null +++ b/example/mod.ts @@ -0,0 +1,203 @@ +// deno-lint-ignore-file + +// This file is automatically generated by deno_bindgen. +// Do not edit this file directly. + +const { dlopen } = Deno; + +const { symbols } = dlopen( + "/Users/divy/gh/deno_bindgen/example/target/debug/libdeno_usb.dylib", + { + __Context_init: { + parameters: [], + result: "pointer", + nonblocking: false, + }, + __Context_lsusb: { + parameters: [ + "pointer", + ], + result: "void", + nonblocking: false, + }, + __Context_open: { + parameters: [ + "pointer", + "u16", + "u16", + ], + result: "pointer", + nonblocking: false, + }, + __Context_dealloc: { + parameters: [ + "pointer", + ], + result: "void", + nonblocking: false, + }, + __Device_claim_interface: { + parameters: [ + "pointer", + "u8", + ], + result: "void", + nonblocking: false, + }, + __Device_select_alternate_interface: { + parameters: [ + "pointer", + "u8", + "u8", + ], + result: "void", + nonblocking: false, + }, + __Device_dealloc: { + parameters: [ + "pointer", + ], + result: "void", + nonblocking: false, + }, + }, +); + +function __Context_init(): Context { + const ret = symbols.__Context_init(); + return Context.__constructor(ret); +} + +function __Context_lsusb( + arg0: Deno.PointerObject | null, +): void { + return symbols.__Context_lsusb( + arg0, + ); +} + +function __Context_open( + arg0: Deno.PointerObject | null, + arg1: number, + arg2: number, +): Device { + const ret = symbols.__Context_open( + arg0, + arg1, + arg2, + ); + return Device.__constructor(ret); +} + +function __Context_dealloc( + arg0: Deno.PointerObject | null, +): void { + return symbols.__Context_dealloc( + arg0, + ); +} + +export class Context { + ptr: Deno.PointerObject | null = null; + + static __constructor(ptr: Deno.PointerObject | null) { + const self = Object.create(Context.prototype); + self.ptr = ptr; + return self; + } + + [Symbol.dispose]() { + this.dealloc(); + this.ptr = null; + } + + constructor() { + return __Context_init(); + } + + lsusb(): void { + return __Context_lsusb( + this.ptr, + ); + } + + open(arg0: number, arg1: number): Device { + return __Context_open( + this.ptr, + arg0, + arg1, + ); + } + + dealloc(): void { + return __Context_dealloc( + this.ptr, + ); + } +} + +function __Device_claim_interface( + arg0: Deno.PointerObject | null, + arg1: number, +): void { + return symbols.__Device_claim_interface( + arg0, + arg1, + ); +} + +function __Device_select_alternate_interface( + arg0: Deno.PointerObject | null, + arg1: number, + arg2: number, +): void { + return symbols.__Device_select_alternate_interface( + arg0, + arg1, + arg2, + ); +} + +function __Device_dealloc( + arg0: Deno.PointerObject | null, +): void { + return symbols.__Device_dealloc( + arg0, + ); +} + +export class Device { + ptr: Deno.PointerObject | null = null; + + static __constructor(ptr: Deno.PointerObject | null) { + const self = Object.create(Device.prototype); + self.ptr = ptr; + return self; + } + + [Symbol.dispose]() { + this.dealloc(); + this.ptr = null; + } + + claim_interface(arg0: number): void { + return __Device_claim_interface( + this.ptr, + arg0, + ); + } + + select_alternate_interface(arg0: number, arg1: number): void { + return __Device_select_alternate_interface( + this.ptr, + arg0, + arg1, + ); + } + + dealloc(): void { + return __Device_dealloc( + this.ptr, + ); + } +} From 91338d129e2e6a85ec6cb8596a43a0b19eb6cc6d Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sun, 12 Nov 2023 13:42:29 +0530 Subject: [PATCH 13/15] Update CI --- .github/workflows/ci.yml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7062169..e514353 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,15 +35,19 @@ jobs: components: rustfmt, clippy - name: Build run: cargo build --locked --release - - name: Build Example (debug) - working-directory: ./example + - name: Test (debug) + working-directory: ./e2e run: | - deno run -A ../cli.ts + ../target/release/deno_bindgen -o bindings/mod.ts deno test -A --unstable - - name: Build Example (release) - working-directory: ./example + - name: Test (release) + working-directory: ./e2e shell: bash run: | rm -rf target - deno run -A ../cli.ts --release=../target/release + ../target/release/deno_bindgen -o bindings/mod.ts --release deno test -A --unstable + - name: Bench + working-directory: ./e2e + shell: bash + run: deno bench -A --unstable From 8a9f41c7b973dcd86289f777bbd5c21c06b24d98 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sun, 12 Nov 2023 13:46:32 +0530 Subject: [PATCH 14/15] x --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e514353..b90bb21 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,18 +36,18 @@ jobs: - name: Build run: cargo build --locked --release - name: Test (debug) - working-directory: ./e2e + working-directory: ./e2e_test run: | ../target/release/deno_bindgen -o bindings/mod.ts deno test -A --unstable - name: Test (release) - working-directory: ./e2e + working-directory: ./e2e_test shell: bash run: | rm -rf target ../target/release/deno_bindgen -o bindings/mod.ts --release deno test -A --unstable - name: Bench - working-directory: ./e2e + working-directory: ./e2e_test shell: bash run: deno bench -A --unstable From 515aa8b69636620e50965e153bae668263338f63 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sun, 12 Nov 2023 13:48:50 +0530 Subject: [PATCH 15/15] bump deno to 1.38.1 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b90bb21..f76dd15 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: matrix: os: [ windows-latest, macos-latest, ubuntu-latest ] toolchain: [nightly] - deno_version: [1.33.1] + deno_version: [1.38.1] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2