Skip to content

Commit

Permalink
fix: native backend working with FFI
Browse files Browse the repository at this point in the history
  • Loading branch information
b-fuze committed Nov 7, 2021
1 parent a83fc01 commit 0f8432b
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 61 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
- name: Install rust
uses: hecrj/setup-rust-action@v1
with:
rust-version: "1.50.0"
rust-version: "1.56.1"

- name: Log versions
run: |
Expand Down
73 changes: 45 additions & 28 deletions deno-dom-native.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import {
prepare,
PerpareOptions,
} from "https://raw.githubusercontent.com/b-fuze/deno-plugin-prepare/master/mod.ts";
import { Plug } from "https://deno.land/x/plug/mod.ts";
import { register } from "./src/parser.ts";

const nativeEnv = "DENO_DOM_PLUGIN";
Expand All @@ -12,43 +9,63 @@ try {
denoNativePluginPath = Deno.env.get(nativeEnv);
} catch {}

// These types are only to deal with `as const` `readonly` shenanigans
type DeepWriteable<T> = { -readonly [P in keyof T]: DeepWriteable<T[P]> };
const _symbols = {
deno_dom_usize_len: { parameters: [], result: "usize" },
deno_dom_parse_sync: { parameters: ["buffer", "usize", "buffer"], result: "void" },
deno_dom_parse_frag_sync: { parameters: ["buffer", "usize", "buffer"], result: "void" },
deno_dom_is_big_endian: { parameters: [], result: "u32" },
deno_dom_copy_buf: { parameters: ["buffer", "buffer"], result: "void" },
} as const;
const symbols = _symbols as DeepWriteable<typeof _symbols>;

let dylib: Deno.DynamicLibrary<typeof symbols>;

if (denoNativePluginPath) {
// Open native plugin and register the native `parse` function
Deno.openPlugin(denoNativePluginPath);
// Load the plugin locally
dylib = Deno.dlopen(denoNativePluginPath, symbols);
} else {
const releaseUrl = "https://github.com/b-fuze/deno-dom/releases/download/v0.1.12-alpha";
const pluginOptions: PerpareOptions = {
name: "test_plugin",
// Download the plugin
dylib = await Plug.prepare({
name: "plugin",
url: "https://github.com/b-fuze/deno-dom/releases/download/v0.1.12-alpha/",
}, symbols);
}

// Whether to output log. Optional, default is true
printLog: false,
const utf8Encoder = new TextEncoder();
const utf8Decoder = new TextDecoder();
const usizeBytes = dylib.symbols.deno_dom_usize_len() as number;
const isBigEndian = Boolean(dylib.symbols.deno_dom_is_big_endian() as number);

// Whether to use locally cached files. Optional, default is true
// checkCache: true,
const dylibParseSync = dylib.symbols.deno_dom_parse_sync.bind(dylib.symbols);
const dylibParseFragSync = dylib.symbols.deno_dom_parse_frag_sync.bind(dylib.symbols);

// Support "http://", "https://", "file://"
urls: {
darwin: releaseUrl + "/libplugin.dylib",
windows: releaseUrl + "/plugin.dll",
linux: releaseUrl + "/libplugin.so",
},
};
// Reused for each invocation. Not thread safe, but JS isn't multithreaded
// anyways.
const returnBufSizeLenRaw = new ArrayBuffer(usizeBytes * 2);
const returnBufSizeLen = new Uint8Array(returnBufSizeLenRaw);

await prepare(pluginOptions);
}
function genericParse(
parser: (srcBuf: Uint8Array, srcLength: number, returnBuf: Uint8Array) => void,
srcHtml: string,
): string {
const encodedHtml = utf8Encoder.encode(srcHtml);
parser(encodedHtml, encodedHtml.length, returnBufSizeLen);

interface DenoCore {
opSync(op: string, data1?: unknown, data2?: unknown): unknown;
};
const outBufSize = Number(new DataView(returnBufSizeLenRaw).getBigUint64(0, !isBigEndian));
const outBuf = new Uint8Array(outBufSize);
dylib.symbols.deno_dom_copy_buf(returnBufSizeLen.slice(usizeBytes), outBuf);

const core = <DenoCore> (<any> <unknown> Deno).core;
return utf8Decoder.decode(outBuf);
}

function parse(html: string): string {
return core.opSync("deno_dom_parse_sync", html) as string;
return genericParse(dylibParseSync, html);
}

function parseFrag(html: string): string {
return core.opSync("deno_dom_parse_frag_sync", html) as string;
return genericParse(dylibParseFragSync, html);
}

// Register parse function
Expand Down
5 changes: 1 addition & 4 deletions html-parser/plugin/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
[package]
name = "plugin"
version = "0.1.0"
authors = ["b-fuze <[email protected]>"]
edition = "2018"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
crate-type = ["cdylib"]

[dependencies]
futures = "0.3.5"
deno_core = "0.87.0"
core = { path = "../core" }
90 changes: 62 additions & 28 deletions html-parser/plugin/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,70 @@
use deno_core::ZeroCopyBuf;
use deno_core::op_sync;
use deno_core::OpState;
use deno_core::Extension;
use deno_core::error::AnyError;
use futures::future::FutureExt;
use core::parse as parse_rs;
use core::parse_frag as parse_frag_rs;
use core::parse;
use core::parse_frag;

#[no_mangle]
pub fn init() -> Extension {
// force ci
Extension::builder()
.ops(vec![
("deno_dom_parse_sync", op_sync(deno_dom_parse_sync)),
("deno_dom_parse_frag_sync", op_sync(deno_dom_parse_frag_sync))
])
.build()
pub extern "C" fn deno_dom_usize_len() -> usize {
std::mem::size_of::<usize>()
}

fn deno_dom_parse_sync(
_state: &mut OpState,
data_str: String,
_zero_copy: Option<ZeroCopyBuf>,
) -> Result<String, AnyError> {
Ok(parse_rs(data_str))
#[no_mangle]
pub extern "C" fn deno_dom_is_big_endian() -> u32 {
if cfg!(target_endian = "big") {
1
} else {
0
}
}

#[no_mangle]
pub extern "C" fn deno_dom_parse_sync(src_buf: *mut u8, src_len: usize, dest_buf_size_ptr: *mut usize) {
let src_html = unsafe {
String::from_raw_parts(src_buf, src_len, src_len)
};
let dest_buf_meta = unsafe {
std::slice::from_raw_parts_mut(
dest_buf_size_ptr,
std::mem::size_of::<usize>() * 2,
)
};

let parsed = Box::new(parse(src_html.clone()));
dest_buf_meta[0] = parsed.len();
dest_buf_meta[1] = Box::into_raw(parsed) as usize;

std::mem::forget(src_html);
std::mem::forget(dest_buf_meta);
}

fn deno_dom_parse_frag_sync(
_state: &mut OpState,
data_str: String,
_zero_copy: Option<ZeroCopyBuf>,
) -> Result<String, AnyError> {
Ok(parse_frag_rs(data_str))
#[no_mangle]
pub extern "C" fn deno_dom_parse_frag_sync(src_buf: *mut u8, src_len: usize, dest_buf_size_ptr: *mut usize) {
let src_html = unsafe {
String::from_raw_parts(src_buf, src_len, src_len)
};
let dest_buf_meta = unsafe {
std::slice::from_raw_parts_mut(
dest_buf_size_ptr,
std::mem::size_of::<usize>() * 2,
)
};

let parsed = Box::new(parse_frag(src_html.clone()));
dest_buf_meta[0] = parsed.len();
dest_buf_meta[1] = Box::into_raw(parsed) as usize;

std::mem::forget(src_html);
std::mem::forget(dest_buf_meta);
}

#[no_mangle]
pub extern "C" fn deno_dom_copy_buf(src_buf_ptr_bytes: *const u8, _dest_buf_ptr: *mut u8) {
let src_buf_ptr_bytes_owned = unsafe { (*(src_buf_ptr_bytes as *const [u8; 8])).clone() };
let src_buf_ptr = if cfg!(target_endian = "big") {
usize::from_be_bytes(src_buf_ptr_bytes_owned)
} else {
usize::from_le_bytes(src_buf_ptr_bytes_owned)
};

let src_string = unsafe { Box::from_raw(src_buf_ptr as *mut String) };
let dest_slice = unsafe { std::slice::from_raw_parts_mut(_dest_buf_ptr, src_string.len()) };
dest_slice.copy_from_slice(src_string.as_bytes());
}

0 comments on commit 0f8432b

Please sign in to comment.