Compare commits
No commits in common. "09aa409389acb01c664f9a6e8e3d39de38b8e76a" and "850fe0f07106a91bffaa2bb6b27ae9ea6a175453" have entirely different histories.
09aa409389
...
850fe0f071
|
|
@ -1,205 +0,0 @@
|
||||||
// Extracts JS_EXPORT-marked functions from a C/C++ source via regex + balanced
|
|
||||||
// paren scanning. Handles function-pointer params (inline and typedef'd),
|
|
||||||
// templates, and JS_EXPORT_AS renames. Limitations: assumes JS_EXPORT markers
|
|
||||||
// and any fn-ptr typedefs they use live in the source file itself, not in
|
|
||||||
// transitively-included headers; doesn't honor #if conditionals.
|
|
||||||
|
|
||||||
export type WasmType = 'i32' | 'i64' | 'f32' | 'f64';
|
|
||||||
|
|
||||||
export interface FnSig {
|
|
||||||
params: WasmType[];
|
|
||||||
results: WasmType[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ExportInfo {
|
|
||||||
name: string;
|
|
||||||
paramNames: string[];
|
|
||||||
// Index → wasm signature, for params that are C function pointers.
|
|
||||||
fnPtrSigs: Map<number, FnSig>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function cTypeToWasm(cType: string): WasmType {
|
|
||||||
const t = cType.trim();
|
|
||||||
if (/[*&]/.test(t) || /\[/.test(t)) return 'i32';
|
|
||||||
if (/\b(int64_t|uint64_t|long\s+long)\b/.test(t)) return 'i64';
|
|
||||||
if (/\bdouble\b/.test(t)) return 'f64';
|
|
||||||
if (/\bfloat\b/.test(t)) return 'f32';
|
|
||||||
return 'i32';
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildFnSig(retType: string, argList: string): FnSig {
|
|
||||||
const ret = retType.trim() === 'void' ? null : cTypeToWasm(retType);
|
|
||||||
const results: WasmType[] = ret ? [ret] : [];
|
|
||||||
|
|
||||||
const params: WasmType[] = [];
|
|
||||||
const trimmed = argList.trim();
|
|
||||||
if (trimmed && trimmed !== 'void') {
|
|
||||||
for (const a of splitArgs(trimmed)) {
|
|
||||||
const typeOnly = a.replace(/\s+\w+\s*$/, '').replace(/\[[^\]]*\]\s*$/, '').trim();
|
|
||||||
params.push(cTypeToWasm(typeOnly));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { params, results };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse a single function-pointer-shaped argument like "void (*cb)(int, float)".
|
|
||||||
// Returns null if `arg` doesn't match the inline fn-ptr pattern.
|
|
||||||
function parseInlineFnPtrSig(arg: string): FnSig | null {
|
|
||||||
const m = arg.match(/^([\s\S]+?)\(\s*\*\s*\w*\s*\)\s*\(([\s\S]*)\)\s*$/);
|
|
||||||
if (!m) return null;
|
|
||||||
return buildFnSig(m[1], m[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find all `typedef <ret> (*<name>)(<args>);` declarations and build a name → sig map.
|
|
||||||
// The retType char class excludes `;{}` so we don't span across nearby struct
|
|
||||||
// declarations or other statements.
|
|
||||||
function findFnPtrTypedefs(src: string): Map<string, FnSig> {
|
|
||||||
const out = new Map<string, FnSig>();
|
|
||||||
const re = /\btypedef\s+([^;{}]+?)\(\s*\*\s*(\w+)\s*\)\s*\(([^)]*)\)\s*;/g;
|
|
||||||
let m: RegExpExecArray | null;
|
|
||||||
while ((m = re.exec(src)) !== null) {
|
|
||||||
out.set(m[2], buildFnSig(m[1], m[3]));
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
function stripComments(src: string): string {
|
|
||||||
return src
|
|
||||||
.replace(/\/\/.*$/gm, '')
|
|
||||||
.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
function splitArgs(argsStr: string): string[] {
|
|
||||||
const trimmed = argsStr.trim();
|
|
||||||
if (!trimmed || trimmed === 'void') return [];
|
|
||||||
|
|
||||||
const parts: string[] = [];
|
|
||||||
let depth = 0;
|
|
||||||
let start = 0;
|
|
||||||
for (let i = 0; i < argsStr.length; i++) {
|
|
||||||
const ch = argsStr[i];
|
|
||||||
if (ch === '(' || ch === '<' || ch === '[') depth++;
|
|
||||||
else if (ch === ')' || ch === '>' || ch === ']') depth--;
|
|
||||||
else if (ch === ',' && depth === 0) {
|
|
||||||
parts.push(argsStr.slice(start, i));
|
|
||||||
start = i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
parts.push(argsStr.slice(start));
|
|
||||||
return parts;
|
|
||||||
}
|
|
||||||
|
|
||||||
function matchParen(src: string, openIdx: number): number {
|
|
||||||
let depth = 1;
|
|
||||||
for (let i = openIdx + 1; i < src.length; i++) {
|
|
||||||
if (src[i] === '(') depth++;
|
|
||||||
else if (src[i] === ')') {
|
|
||||||
depth--;
|
|
||||||
if (depth === 0) return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse one C parameter declaration, recovering the identifier name and (if any)
|
|
||||||
// the function-pointer signature it carries — either via an inline `(*name)(...)`
|
|
||||||
// shape or via a typedef name we've previously collected.
|
|
||||||
function parseParam(
|
|
||||||
arg: string,
|
|
||||||
typedefs: Map<string, FnSig>,
|
|
||||||
): { name: string; fnSig: FnSig | null } {
|
|
||||||
const noDefault = arg.replace(/\s*=\s*[\s\S]*$/, '').trim();
|
|
||||||
if (!noDefault) return { name: '', fnSig: null };
|
|
||||||
|
|
||||||
const inlineSig = parseInlineFnPtrSig(noDefault);
|
|
||||||
if (inlineSig) {
|
|
||||||
const m = noDefault.match(/\(\s*\*\s*(\w+)\s*\)/);
|
|
||||||
return { name: m?.[1] ?? '', fnSig: inlineSig };
|
|
||||||
}
|
|
||||||
|
|
||||||
const nameMatch = noDefault.match(/(\w+)\s*(?:\[[^\]]*\])?\s*$/);
|
|
||||||
const name = nameMatch ? nameMatch[1] : '';
|
|
||||||
|
|
||||||
const typeOnly = noDefault.replace(/\s+\w+\s*$/, '').replace(/\[[^\]]*\]\s*$/, '').trim();
|
|
||||||
const typedefSig = typedefs.get(typeOnly);
|
|
||||||
if (typedefSig) return { name, fnSig: typedefSig };
|
|
||||||
|
|
||||||
return { name, fnSig: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function extractExports(source: string): ExportInfo[] {
|
|
||||||
const src = stripComments(source);
|
|
||||||
const typedefs = findFnPtrTypedefs(src);
|
|
||||||
const out: ExportInfo[] = [];
|
|
||||||
const seen = new Set<string>();
|
|
||||||
const re = /\bJS_EXPORT(_AS)?\b/g;
|
|
||||||
let m: RegExpExecArray | null;
|
|
||||||
|
|
||||||
while ((m = re.exec(src)) !== null) {
|
|
||||||
let pos = m.index + m[0].length;
|
|
||||||
let alias: string | undefined;
|
|
||||||
|
|
||||||
if (m[1]) {
|
|
||||||
const a = /^\s*\(\s*(\w+)\s*\)/.exec(src.slice(pos));
|
|
||||||
if (!a) continue;
|
|
||||||
alias = a[1];
|
|
||||||
pos += a[0].length;
|
|
||||||
}
|
|
||||||
|
|
||||||
let parenOpen = -1;
|
|
||||||
while (pos < src.length) {
|
|
||||||
const ch = src[pos];
|
|
||||||
if (ch === ';' || ch === '{' || ch === '}') break;
|
|
||||||
|
|
||||||
if (src.startsWith('JS_EXPORT_AS', pos)) {
|
|
||||||
const a = /^JS_EXPORT_AS\s*\(\s*(\w+)\s*\)/.exec(src.slice(pos));
|
|
||||||
if (a) {
|
|
||||||
alias = alias ?? a[1];
|
|
||||||
pos += a[0].length;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (src.startsWith('JS_EXPORT', pos) && !src.startsWith('JS_EXPORT_AS', pos)) {
|
|
||||||
pos += 'JS_EXPORT'.length;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (ch === '(') {
|
|
||||||
parenOpen = pos;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
pos++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parenOpen === -1) continue;
|
|
||||||
|
|
||||||
let nameEnd = parenOpen;
|
|
||||||
while (nameEnd > m.index && /\s/.test(src[nameEnd - 1])) nameEnd--;
|
|
||||||
let nameStart = nameEnd;
|
|
||||||
while (nameStart > m.index && /\w/.test(src[nameStart - 1])) nameStart--;
|
|
||||||
const funcName = src.slice(nameStart, nameEnd);
|
|
||||||
if (!funcName) continue;
|
|
||||||
|
|
||||||
const parenClose = matchParen(src, parenOpen);
|
|
||||||
if (parenClose === -1) continue;
|
|
||||||
|
|
||||||
const argParts = splitArgs(src.slice(parenOpen + 1, parenClose));
|
|
||||||
const paramNames: string[] = [];
|
|
||||||
const fnPtrSigs = new Map<number, FnSig>();
|
|
||||||
argParts.forEach((a, i) => {
|
|
||||||
const { name, fnSig } = parseParam(a, typedefs);
|
|
||||||
paramNames.push(name);
|
|
||||||
if (fnSig) fnPtrSigs.set(i, fnSig);
|
|
||||||
});
|
|
||||||
|
|
||||||
const name = alias ?? funcName;
|
|
||||||
if (!seen.has(name)) {
|
|
||||||
seen.add(name);
|
|
||||||
out.push({ name, paramNames, fnPtrSigs });
|
|
||||||
}
|
|
||||||
|
|
||||||
re.lastIndex = parenClose + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { plugin, $, type BunPlugin } from "bun";
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import { renderDts } from './wasmTypes';
|
import { renderDts } from './wasmTypes';
|
||||||
import { extractExports } from './wasmExports';
|
|
||||||
|
|
||||||
interface WasmLoaderConfig {
|
interface WasmLoaderConfig {
|
||||||
production?: boolean;
|
production?: boolean;
|
||||||
|
|
@ -11,6 +10,7 @@ interface WasmLoaderConfig {
|
||||||
|
|
||||||
interface CompilerWithFlags {
|
interface CompilerWithFlags {
|
||||||
cc: string;
|
cc: string;
|
||||||
|
nm: string;
|
||||||
wasmOpt: string | null;
|
wasmOpt: string | null;
|
||||||
reactorCrt: string | null;
|
reactorCrt: string | null;
|
||||||
flags: string[];
|
flags: string[];
|
||||||
|
|
@ -32,6 +32,7 @@ const getCompiler = (): Promise<CompilerWithFlags> => {
|
||||||
const wasiDir = path.resolve(import.meta.dir, '..', '..', 'dist', 'wasi');
|
const wasiDir = path.resolve(import.meta.dir, '..', '..', 'dist', 'wasi');
|
||||||
const cc: CompilerWithFlags = {
|
const cc: CompilerWithFlags = {
|
||||||
cc: 'clang',
|
cc: 'clang',
|
||||||
|
nm: 'nm',
|
||||||
wasmOpt: Bun.which('wasm-opt') ?? null,
|
wasmOpt: Bun.which('wasm-opt') ?? null,
|
||||||
reactorCrt: null,
|
reactorCrt: null,
|
||||||
flags: [
|
flags: [
|
||||||
|
|
@ -72,6 +73,7 @@ const getCompiler = (): Promise<CompilerWithFlags> => {
|
||||||
}
|
}
|
||||||
|
|
||||||
cc.cc = `${path.resolve(wasiDir, 'bin', 'clang')}`;
|
cc.cc = `${path.resolve(wasiDir, 'bin', 'clang')}`;
|
||||||
|
cc.nm = `${path.resolve(wasiDir, 'bin', 'nm')}`;
|
||||||
cc.flags = [
|
cc.flags = [
|
||||||
'--target=wasm32-wasip1',
|
'--target=wasm32-wasip1',
|
||||||
`--sysroot=${path.resolve(wasiDir, 'share', 'wasi-sysroot')}`,
|
`--sysroot=${path.resolve(wasiDir, 'share', 'wasi-sysroot')}`,
|
||||||
|
|
@ -131,10 +133,7 @@ async function compileLibGroup(
|
||||||
return objPaths;
|
return objPaths;
|
||||||
}
|
}
|
||||||
|
|
||||||
type FnSig = { params: ('i32' | 'i64' | 'f32' | 'f64')[]; results: ('i32' | 'i64' | 'f32' | 'f64')[] };
|
async function instantiate(url: string) {
|
||||||
type FnPtrMeta = Record<string, [number, FnSig][]>;
|
|
||||||
|
|
||||||
async function instantiate(url: string, fnPtrs: FnPtrMeta = {}) {
|
|
||||||
if (typeof WebAssembly.Function !== "function") {
|
if (typeof WebAssembly.Function !== "function") {
|
||||||
const typeCodes = {
|
const typeCodes = {
|
||||||
i32: 0x7f,
|
i32: 0x7f,
|
||||||
|
|
@ -245,37 +244,13 @@ async function instantiate(url: string, fnPtrs: FnPtrMeta = {}) {
|
||||||
|
|
||||||
(instance.exports._initialize as (() => void) | undefined)?.();
|
(instance.exports._initialize as (() => void) | undefined)?.();
|
||||||
|
|
||||||
const table = instance.exports.__indirect_function_table as WebAssembly.Table | undefined;
|
|
||||||
const exported: Record<string, unknown> = Object.fromEntries(
|
|
||||||
Object.entries(instance.exports).filter(([k]) => !k.startsWith('_'))
|
|
||||||
);
|
|
||||||
|
|
||||||
// Wrap exports that take function-pointer args so callers can pass JS functions
|
|
||||||
// directly. Same JS function reuses the same table slot across calls.
|
|
||||||
const fnPtrCache = new WeakMap<Function, number>();
|
|
||||||
for (const [name, ptrs] of Object.entries(fnPtrs)) {
|
|
||||||
const orig = exported[name];
|
|
||||||
if (typeof orig !== 'function' || !table) continue;
|
|
||||||
exported[name] = (...args: unknown[]) => {
|
|
||||||
for (const [i, sig] of ptrs) {
|
|
||||||
const v = args[i];
|
|
||||||
if (typeof v !== 'function') continue;
|
|
||||||
let idx = fnPtrCache.get(v);
|
|
||||||
if (idx === undefined) {
|
|
||||||
const wasmFn = new WebAssembly.Function({ parameters: sig.params, results: sig.results }, v as (...a: number[]) => void);
|
|
||||||
idx = table.grow(1, wasmFn);
|
|
||||||
fnPtrCache.set(v, idx);
|
|
||||||
}
|
|
||||||
args[i] = idx;
|
|
||||||
}
|
|
||||||
return (orig as (...a: unknown[]) => unknown)(...args);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...exported,
|
...Object.fromEntries(
|
||||||
|
Object.entries(instance.exports)
|
||||||
|
.filter(([k]) => !k.startsWith('_'))
|
||||||
|
),
|
||||||
memory,
|
memory,
|
||||||
table,
|
table: instance.exports.__indirect_function_table,
|
||||||
get data() { return new DataView(memory.buffer); },
|
get data() { return new DataView(memory.buffer); },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -288,7 +263,12 @@ const wasmPlugin = ({ production }: WasmLoaderConfig = {}): BunPlugin => {
|
||||||
const distDir = path.resolve(import.meta.dir, '..', '..', 'dist');
|
const distDir = path.resolve(import.meta.dir, '..', '..', 'dist');
|
||||||
const inputBasename = path.basename(args.path);
|
const inputBasename = path.basename(args.path);
|
||||||
let wasmPath = path.resolve(distDir, inputBasename.replace(/\.(c|cpp)$/, '.wasm'));
|
let wasmPath = path.resolve(distDir, inputBasename.replace(/\.(c|cpp)$/, '.wasm'));
|
||||||
const fnPtrMeta: Record<string, [number, FnSig][]> = {};
|
let jsContent: string = `
|
||||||
|
${instantiate}
|
||||||
|
const module = await instantiate(new URL($WASM$));
|
||||||
|
|
||||||
|
export default module;
|
||||||
|
`;
|
||||||
if (args.path.endsWith('.wasm')) {
|
if (args.path.endsWith('.wasm')) {
|
||||||
wasmPath = args.path;
|
wasmPath = args.path;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -355,18 +335,17 @@ const wasmPlugin = ({ production }: WasmLoaderConfig = {}): BunPlugin => {
|
||||||
|
|
||||||
const allObjs = [inputObjPath, ...libCObjs, ...libCppObjs];
|
const allObjs = [inputObjPath, ...libCObjs, ...libCppObjs];
|
||||||
|
|
||||||
const exportInfos = extractExports(await Bun.file(args.path).text());
|
|
||||||
for (const e of exportInfos) {
|
|
||||||
if (e.fnPtrSigs.size > 0) {
|
|
||||||
fnPtrMeta[e.name] = [...e.fnPtrSigs.entries()];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (await needsRebuild(wasmPath, allObjs)) {
|
if (await needsRebuild(wasmPath, allObjs)) {
|
||||||
const meta = new Map(exportInfos.map(e => [e.name, {
|
const symbolsResult = await $`${cc.nm} ${inputObjPath}`.quiet();
|
||||||
paramNames: e.paramNames,
|
|
||||||
fnPtrSigs: e.fnPtrSigs,
|
if (symbolsResult.exitCode !== 0) {
|
||||||
}]));
|
throw new Error('nm failed, check output');
|
||||||
|
}
|
||||||
|
|
||||||
|
const exports = symbolsResult.text().split('\n')
|
||||||
|
.map(l => l.match(/^-+ T ([a-z][a-z0-9_]*)/i))
|
||||||
|
.filter(m => m != null)
|
||||||
|
.map(m => m[1]);
|
||||||
|
|
||||||
const linkFlags = [
|
const linkFlags = [
|
||||||
'--strip-all',
|
'--strip-all',
|
||||||
|
|
@ -376,7 +355,7 @@ const wasmPlugin = ({ production }: WasmLoaderConfig = {}): BunPlugin => {
|
||||||
'--import-memory',
|
'--import-memory',
|
||||||
'--export-table',
|
'--export-table',
|
||||||
'--growable-table',
|
'--growable-table',
|
||||||
...exportInfos.flatMap(e => ['--export-if-defined', e.name]),
|
...exports.flatMap(e => ['--export-if-defined', e]),
|
||||||
].map(f => `-Wl,${f}`);
|
].map(f => `-Wl,${f}`);
|
||||||
|
|
||||||
const reactorInputs = cc.reactorCrt ? [cc.reactorCrt] : [];
|
const reactorInputs = cc.reactorCrt ? [cc.reactorCrt] : [];
|
||||||
|
|
@ -392,7 +371,7 @@ const wasmPlugin = ({ production }: WasmLoaderConfig = {}): BunPlugin => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const wasmBytes = new Uint8Array(await Bun.file(wasmPath).arrayBuffer());
|
const wasmBytes = new Uint8Array(await Bun.file(wasmPath).arrayBuffer());
|
||||||
await Bun.write(`${args.path}.d.ts`, renderDts(wasmBytes, meta));
|
await Bun.write(`${args.path}.d.ts`, renderDts(wasmBytes));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -400,16 +379,9 @@ const wasmPlugin = ({ production }: WasmLoaderConfig = {}): BunPlugin => {
|
||||||
const wasmBuffer = Buffer.from(wasmContent).toString('base64');
|
const wasmBuffer = Buffer.from(wasmContent).toString('base64');
|
||||||
const wasmURL = `data:application/wasm;base64,${wasmBuffer}`;
|
const wasmURL = `data:application/wasm;base64,${wasmBuffer}`;
|
||||||
|
|
||||||
const jsContent = `
|
|
||||||
${instantiate}
|
|
||||||
const module = await instantiate(new URL(${JSON.stringify(wasmURL)}), ${JSON.stringify(fnPtrMeta)});
|
|
||||||
|
|
||||||
export default module;
|
|
||||||
`;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
loader: 'js',
|
loader: 'js',
|
||||||
contents: jsContent,
|
contents: jsContent.replace(/new URL\([^)]*\)/, `new URL(${JSON.stringify(wasmURL)})`),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,8 @@
|
||||||
// Parses a wasm binary's type / function / import / export sections to recover
|
// Parses a wasm binary's type / function / import / export sections to recover
|
||||||
// each function export's signature, then renders TypeScript declarations.
|
// each function export's signature, then renders TypeScript declarations.
|
||||||
|
|
||||||
import type { FnSig, WasmType } from './wasmExports';
|
|
||||||
|
|
||||||
type Sig = { params: number[]; results: number[] };
|
type Sig = { params: number[]; results: number[] };
|
||||||
|
|
||||||
export interface ExportMeta {
|
|
||||||
paramNames: string[];
|
|
||||||
fnPtrSigs: Map<number, FnSig>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const VALUE_TYPE_TS: Record<number, string> = {
|
const VALUE_TYPE_TS: Record<number, string> = {
|
||||||
0x7f: 'number', // i32
|
0x7f: 'number', // i32
|
||||||
0x7e: 'bigint', // i64
|
0x7e: 'bigint', // i64
|
||||||
|
|
@ -17,13 +10,6 @@ const VALUE_TYPE_TS: Record<number, string> = {
|
||||||
0x7c: 'number', // f64
|
0x7c: 'number', // f64
|
||||||
};
|
};
|
||||||
|
|
||||||
const WASM_STR_TO_TS: Record<WasmType, string> = {
|
|
||||||
i32: 'number',
|
|
||||||
i64: 'bigint',
|
|
||||||
f32: 'number',
|
|
||||||
f64: 'number',
|
|
||||||
};
|
|
||||||
|
|
||||||
function readULEB128(bytes: Uint8Array, offset: number): [number, number] {
|
function readULEB128(bytes: Uint8Array, offset: number): [number, number] {
|
||||||
let result = 0;
|
let result = 0;
|
||||||
let shift = 0;
|
let shift = 0;
|
||||||
|
|
@ -132,24 +118,9 @@ function tsType(valType: number): string {
|
||||||
return VALUE_TYPE_TS[valType] ?? 'unknown';
|
return VALUE_TYPE_TS[valType] ?? 'unknown';
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderFnPtrType(sig: FnSig): string {
|
function tsSignature({ params, results }: Sig): string {
|
||||||
const params = sig.params.map((t, i) => `a${i}: ${WASM_STR_TO_TS[t]}`).join(', ');
|
|
||||||
const ret = sig.results.length === 0
|
|
||||||
? 'void'
|
|
||||||
: sig.results.length === 1
|
|
||||||
? WASM_STR_TO_TS[sig.results[0]]
|
|
||||||
: `[${sig.results.map(t => WASM_STR_TO_TS[t]).join(', ')}]`;
|
|
||||||
return `(${params}) => ${ret}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function tsSignature({ params, results }: Sig, meta?: ExportMeta): string {
|
|
||||||
const paramList = params
|
const paramList = params
|
||||||
.map((t, i) => {
|
.map((t, i) => `a${i}: ${tsType(t)}`)
|
||||||
const name = meta?.paramNames[i] || `a${i}`;
|
|
||||||
const fnSig = meta?.fnPtrSigs.get(i);
|
|
||||||
if (fnSig) return `${name}: (${renderFnPtrType(fnSig)}) | number`;
|
|
||||||
return `${name}: ${tsType(t)}`;
|
|
||||||
})
|
|
||||||
.join(', ');
|
.join(', ');
|
||||||
let ret: string;
|
let ret: string;
|
||||||
if (results.length === 0) ret = 'void';
|
if (results.length === 0) ret = 'void';
|
||||||
|
|
@ -158,7 +129,7 @@ function tsSignature({ params, results }: Sig, meta?: ExportMeta): string {
|
||||||
return `(${paramList}) => ${ret}`;
|
return `(${paramList}) => ${ret}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderDts(wasmBytes: Uint8Array, meta?: Map<string, ExportMeta>): string {
|
export function renderDts(wasmBytes: Uint8Array): string {
|
||||||
const sigs = parseWasmExports(wasmBytes);
|
const sigs = parseWasmExports(wasmBytes);
|
||||||
const fns = Array.from(sigs.entries())
|
const fns = Array.from(sigs.entries())
|
||||||
.filter(([name]) => !name.startsWith('_'))
|
.filter(([name]) => !name.startsWith('_'))
|
||||||
|
|
@ -167,7 +138,7 @@ export function renderDts(wasmBytes: Uint8Array, meta?: Map<string, ExportMeta>)
|
||||||
return [
|
return [
|
||||||
'// Auto-generated by wasmPlugin. Do not edit.',
|
'// Auto-generated by wasmPlugin. Do not edit.',
|
||||||
'declare const _: {',
|
'declare const _: {',
|
||||||
...fns.map(([name, sig]) => ` ${name}: ${tsSignature(sig, meta?.get(name))};`),
|
...fns.map(([name, sig]) => ` ${name}: ${tsSignature(sig)};`),
|
||||||
' memory: WebAssembly.Memory;',
|
' memory: WebAssembly.Memory;',
|
||||||
' table: WebAssembly.Table;',
|
' table: WebAssembly.Table;',
|
||||||
' readonly data: DataView;',
|
' readonly data: DataView;',
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,8 @@ namespace Physics {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setCollisionCallback(cb: (a: number, b: number) => void) {
|
export function setCollisionCallback(cb: (a: number, b: number) => void) {
|
||||||
E.rigid_body_set_collision_callback(cb);
|
const cbPtr = E.table.grow(1, new WebAssembly.Function({ parameters: ['i32', 'i32'], results: [] }, cb));
|
||||||
|
E.rigid_body_set_collision_callback(cbPtr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,12 @@ constexpr bit bit::nor(const bit& a, const bit& b) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct InitProbe {
|
||||||
|
InitProbe() {
|
||||||
|
std::cout << "[ctor] _initialize ran" << std::endl;
|
||||||
|
}
|
||||||
|
} init_probe;
|
||||||
|
|
||||||
JS_EXPORT void awoo() {
|
JS_EXPORT void awoo() {
|
||||||
bit a{1, "a"};
|
bit a{1, "a"};
|
||||||
bit b{1, "b"};
|
bit b{1, "b"};
|
||||||
|
|
@ -85,7 +91,3 @@ JS_EXPORT void awoo() {
|
||||||
|
|
||||||
std::cout << "rand = " << rand() << "\narc4 = " << arc4random() << std::endl;
|
std::cout << "rand = " << rand() << "\narc4 = " << arc4random() << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
JS_EXPORT_AS(something) void anything(void (*cb)()) {
|
|
||||||
cb();
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -3,5 +3,4 @@ import awoo from './awoo.cpp';
|
||||||
export default function main() {
|
export default function main() {
|
||||||
console.log(awoo)
|
console.log(awoo)
|
||||||
awoo.awoo();
|
awoo.awoo();
|
||||||
awoo.something(() => console.log('called'));
|
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue