Skip to content

Commit e96b417

Browse files
authored
Merge pull request #3835 from dusk-network/ffi-datadriver
data-driver: add standard wasm implementation
2 parents 891ca60 + 5052e69 commit e96b417

File tree

20 files changed

+657
-204
lines changed

20 files changed

+657
-204
lines changed

Cargo.lock

Lines changed: 0 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,6 @@ tracing-subscriber = "0.3.18"
163163
tungstenite = "0.21"
164164
url = "2.5.2"
165165
version_check = "0.9.5"
166-
wasm-bindgen = "0.2"
167166
zeroize = { version = "1.8.1", default-features = false }
168167
zip = "0.5.13"
169168

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ wasm: setup-compiler ## Generate the WASM for all the contracts and wallet-core
2121
data-drivers: ## Build the data-driver WASM files
2222
$(MAKE) -C ./data-drivers wasm
2323

24+
data-drivers-js: ## Build the data-driver WASM files (with alloc support for JS)
25+
$(MAKE) -C ./data-drivers wasm-js
26+
2427
contracts: setup-compiler ## Execute the test for all contracts
2528
$(MAKE) -j1 -C ./contracts all
2629

data-drivers/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ help: ## Display this help screen
99

1010
test: $(SUBDIRS) ## Run all the tests in the subfolder
1111

12+
wasm-js: $(SUBDIRS) ## Build the data-driver WASM files (with alloc support for JS)
13+
1214
wasm: $(SUBDIRS) ## Build the data-driver WASM files
1315

1416
clippy: $(SUBDIRS) ## Run clippy

data-drivers/data-driver/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,7 @@ bytecheck = { workspace = true }
1414
# serde support dependencies
1515
serde = { workspace = true }
1616
serde_json = { workspace = true }
17+
18+
[features]
19+
wasm-export = []
20+
alloc = []

data-drivers/data-driver/Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@ help: ## Display this help screen
88

99
wasm: ## Noop
1010

11+
wasm-js: ## Noop
12+
1113
test: ## Perform the contract tests defined in the host module
1214
@cargo test --release
1315

1416
clippy: ## Run clippy
1517
@cargo clippy --release -- -D warnings
1618
@cargo clippy --release --target wasm32-unknown-unknown -- -D warnings
19+
@cargo clippy --release --target wasm32-unknown-unknown --features wasm-export -- -D warnings
20+
@cargo clippy --release --target wasm32-unknown-unknown --features wasm-export,alloc -- -D warnings
1721

1822
doc: ## Run doc gen
1923
@cargo doc --release

data-drivers/data-driver/loader.mjs

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
5+
6+
export async function loadDriverWasm(bytes) {
7+
const wasmModule = await WebAssembly.instantiate(bytes, { env: {} });
8+
const exports = wasmModule.instance.exports;
9+
const memory = exports.memory;
10+
11+
const textEncoder = new TextEncoder();
12+
const textDecoder = new TextDecoder();
13+
14+
// -- Helpers --
15+
function allocAndWriteString(str) {
16+
const bytes = textEncoder.encode(str);
17+
const ptr = exports.alloc(bytes.length);
18+
new Uint8Array(memory.buffer, ptr, bytes.length).set(bytes);
19+
return [ptr, bytes.length];
20+
}
21+
22+
function allocAndWriteBytes(bytes) {
23+
const ptr = exports.alloc(bytes.length);
24+
new Uint8Array(memory.buffer, ptr, bytes.length).set(bytes);
25+
return [ptr, bytes.length];
26+
}
27+
28+
function readBuffer(ptr, bufSize) {
29+
const view = new DataView(memory.buffer, ptr, 4);
30+
const actualSize = view.getUint32(0, true);
31+
32+
if (actualSize > bufSize - 4) {
33+
exports.dealloc(ptr, bufSize);
34+
throw new Error(`Invalid output size: ${actualSize}`);
35+
}
36+
37+
const data = new Uint8Array(memory.buffer, ptr + 4, actualSize);
38+
const result = data.slice(); // clone to detach from WASM memory
39+
return result;
40+
}
41+
42+
function runWithOutputBuffer(fn) {
43+
const outSize = 64 * 1024; // 64 KB default buffer
44+
const outPtr = exports.alloc(outSize);
45+
try {
46+
const code = fn(outPtr, outSize);
47+
if (code !== 0) {
48+
const errMsg = _internalGetLastError();
49+
throw new Error(`FFI call failed (${code}): ${errMsg}`);
50+
}
51+
return readBuffer(outPtr, outSize);
52+
} finally {
53+
exports.dealloc(outPtr, outSize);
54+
}
55+
}
56+
57+
function _internalGetLastError() {
58+
const outSize = 1024;
59+
const outPtr = exports.alloc(outSize);
60+
try {
61+
exports.get_last_error(outPtr, outSize);
62+
const result = readBuffer(outPtr, outSize);
63+
return textDecoder.decode(result);
64+
} finally {
65+
exports.dealloc(outPtr, outSize);
66+
}
67+
}
68+
69+
return {
70+
/** Initializes the contract driver */
71+
init: () => exports.init(),
72+
73+
/** Encodes JSON input into RKYV bytes */
74+
encodeInputFn: (fnName, json) => {
75+
const [fnPtr, fnLen] = allocAndWriteString(fnName);
76+
const [jsonPtr, jsonLen] = allocAndWriteString(json);
77+
78+
const result = runWithOutputBuffer((outPtr, outSize) =>
79+
exports.encode_input_fn(fnPtr, fnLen, jsonPtr, jsonLen, outPtr, outSize)
80+
);
81+
82+
exports.dealloc(fnPtr, fnLen);
83+
exports.dealloc(jsonPtr, jsonLen);
84+
85+
return result; // returns Uint8Array
86+
},
87+
88+
/** Decodes RKYV input bytes into JSON */
89+
decodeInputFn: (fnName, rkyvBytes) => {
90+
const [fnPtr, fnLen] = allocAndWriteString(fnName);
91+
const [rkyvPtr, rkyvLen] = allocAndWriteBytes(rkyvBytes);
92+
93+
const result = runWithOutputBuffer((outPtr, outSize) =>
94+
exports.decode_input_fn(fnPtr, fnLen, rkyvPtr, rkyvLen, outPtr, outSize)
95+
);
96+
97+
exports.dealloc(fnPtr, fnLen);
98+
exports.dealloc(rkyvPtr, rkyvLen);
99+
100+
return JSON.parse(textDecoder.decode(result));
101+
},
102+
103+
/** Decodes RKYV output bytes into JSON */
104+
decodeOutputFn: (fnName, rkyvBytes) => {
105+
const [fnPtr, fnLen] = allocAndWriteString(fnName);
106+
const [rkyvPtr, rkyvLen] = allocAndWriteBytes(rkyvBytes);
107+
108+
const result = runWithOutputBuffer((outPtr, outSize) =>
109+
exports.decode_output_fn(fnPtr, fnLen, rkyvPtr, rkyvLen, outPtr, outSize)
110+
);
111+
112+
exports.dealloc(fnPtr, fnLen);
113+
exports.dealloc(rkyvPtr, rkyvLen);
114+
115+
return JSON.parse(textDecoder.decode(result));
116+
},
117+
118+
/** Decodes RKYV event bytes into JSON */
119+
decodeEvent: (eventName, rkyvBytes) => {
120+
const [eventPtr, eventLen] = allocAndWriteString(eventName);
121+
const [rkyvPtr, rkyvLen] = allocAndWriteBytes(rkyvBytes);
122+
123+
const result = runWithOutputBuffer((outPtr, outSize) =>
124+
exports.decode_event(eventPtr, eventLen, rkyvPtr, rkyvLen, outPtr, outSize)
125+
);
126+
127+
exports.dealloc(eventPtr, eventLen);
128+
exports.dealloc(rkyvPtr, rkyvLen);
129+
130+
return JSON.parse(textDecoder.decode(result));
131+
},
132+
133+
/** Returns the contract's JSON schema */
134+
getSchema: () => {
135+
const result = runWithOutputBuffer((outPtr, outSize) =>
136+
exports.get_schema(outPtr, outSize)
137+
);
138+
return JSON.parse(textDecoder.decode(result));
139+
},
140+
141+
/** Returns the contract's JSON schema */
142+
getVersion: () => {
143+
const result = runWithOutputBuffer((outPtr, outSize) =>
144+
exports.get_version(outPtr, outSize)
145+
);
146+
return textDecoder.decode(result);
147+
},
148+
};
149+
}

data-drivers/data-driver/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ extern crate alloc;
1818

1919
mod error;
2020

21+
#[cfg(all(target_family = "wasm", feature = "wasm-export"))]
22+
pub mod wasm;
23+
24+
#[cfg(all(target_family = "wasm", feature = "alloc"))]
25+
mod mem;
26+
2127
use alloc::string::ToString;
2228
use alloc::vec;
2329
use alloc::vec::Vec;

data-drivers/data-driver/src/mem.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
//
5+
// Copyright (c) DUSK NETWORK. All rights reserved.
6+
7+
use alloc::vec::Vec;
8+
9+
/// Allocate memory inside WASM for JS to write into.
10+
/// Returns a pointer to a buffer of size `size`.
11+
#[no_mangle]
12+
pub extern "C" fn alloc(size: usize) -> *mut u8 {
13+
let mut buf = Vec::with_capacity(size);
14+
let ptr = buf.as_mut_ptr();
15+
core::mem::forget(buf);
16+
ptr
17+
}
18+
19+
/// Deallocate memory previously allocated with `alloc`.
20+
///
21+
/// # Safety
22+
/// The pointer must have been returned by `alloc` with the same `size`.
23+
/// The memory must not have been previously deallocated.
24+
/// After calling this function, the pointer must no longer be used.
25+
#[no_mangle]
26+
pub unsafe extern "C" fn dealloc(ptr: *mut u8, size: usize) {
27+
drop(Vec::from_raw_parts(ptr, size, size));
28+
}

0 commit comments

Comments
 (0)