Refactor wasm plugin
This commit is contained in:
parent
2798e9adcc
commit
850fe0f071
30
.clangd
30
.clangd
|
|
@ -1,5 +1,31 @@
|
||||||
|
CompileFlags:
|
||||||
|
Add:
|
||||||
|
- --target=wasm32-wasip1
|
||||||
|
- -D_GNU_SOURCE
|
||||||
|
- -fno-exceptions
|
||||||
|
- -fno-stack-protector
|
||||||
|
- -fvisibility=hidden
|
||||||
|
- -mbulk-memory
|
||||||
|
- -mextended-const
|
||||||
|
- -mrelaxed-simd
|
||||||
|
- -msimd128
|
||||||
|
- -mtail-call
|
||||||
|
- -msign-ext
|
||||||
|
- -mnontrapping-fptoint
|
||||||
|
- -mreference-types
|
||||||
|
- -Wall
|
||||||
|
- -Wextra
|
||||||
|
- -Wpedantic
|
||||||
|
- -Wshadow
|
||||||
|
- -Wconversion
|
||||||
|
- -Werror
|
||||||
|
---
|
||||||
If:
|
If:
|
||||||
PathMatch: .*\.[ch]
|
PathMatch: .*\.[ch]
|
||||||
|
|
||||||
CompileFlags:
|
CompileFlags:
|
||||||
Add: [-std=c23]
|
Add: [-std=gnu23]
|
||||||
|
---
|
||||||
|
If:
|
||||||
|
PathMatch: .*\.(cpp|cc|cxx|hpp|hxx)
|
||||||
|
CompileFlags:
|
||||||
|
Add: [-std=gnu++26, -fno-rtti]
|
||||||
|
|
|
||||||
|
|
@ -175,3 +175,7 @@ dist
|
||||||
.DS_Store
|
.DS_Store
|
||||||
error.log
|
error.log
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
|
||||||
|
# Generated by wasmPlugin
|
||||||
|
*.c.d.ts
|
||||||
|
*.cpp.d.ts
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,8 @@
|
||||||
|
|
||||||
#define JS_IMPORT(name) __attribute__((import_module("env"), import_name(#name)))
|
#define JS_IMPORT(name) __attribute__((import_module("env"), import_name(#name)))
|
||||||
#define JS_EXPORT_AS(name) __attribute__((export_name(#name)))
|
#define JS_EXPORT_AS(name) __attribute__((export_name(#name)))
|
||||||
|
#ifdef __cplusplus
|
||||||
#define JS_EXPORT extern "C" __attribute__((visibility("default")))
|
#define JS_EXPORT extern "C" __attribute__((visibility("default")))
|
||||||
|
#else
|
||||||
|
#define JS_EXPORT __attribute__((visibility("default")))
|
||||||
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { plugin, $, type BunPlugin } from "bun";
|
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';
|
||||||
|
|
||||||
interface WasmLoaderConfig {
|
interface WasmLoaderConfig {
|
||||||
production?: boolean;
|
production?: boolean;
|
||||||
|
|
@ -10,6 +11,8 @@ interface WasmLoaderConfig {
|
||||||
interface CompilerWithFlags {
|
interface CompilerWithFlags {
|
||||||
cc: string;
|
cc: string;
|
||||||
nm: string;
|
nm: string;
|
||||||
|
wasmOpt: string | null;
|
||||||
|
reactorCrt: string | null;
|
||||||
flags: string[];
|
flags: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,53 +29,61 @@ let compilerPromise: Promise<CompilerWithFlags> | null = null;
|
||||||
const getCompiler = (): Promise<CompilerWithFlags> => {
|
const getCompiler = (): Promise<CompilerWithFlags> => {
|
||||||
if (compilerPromise) return compilerPromise;
|
if (compilerPromise) return compilerPromise;
|
||||||
compilerPromise = (async (): Promise<CompilerWithFlags> => {
|
compilerPromise = (async (): 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',
|
nm: 'nm',
|
||||||
flags: [
|
wasmOpt: Bun.which('wasm-opt') ?? null,
|
||||||
'--target=wasm32',
|
reactorCrt: null,
|
||||||
'--no-standard-libraries',
|
flags: [
|
||||||
'-fno-builtin',
|
'--target=wasm32',
|
||||||
],
|
'--no-standard-libraries',
|
||||||
};
|
'-fno-builtin',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
await fs.mkdir(wasiDir, { recursive: true });
|
await fs.mkdir(wasiDir, { recursive: true });
|
||||||
|
|
||||||
const installedVersion = (await Bun.file(path.resolve(wasiDir, 'VERSION')).text().catch(() => '')).slice(0, WASI_VERSION.length);
|
const installedVersion = (await Bun.file(path.resolve(wasiDir, 'VERSION')).text().catch(() => '')).slice(0, WASI_VERSION.length);
|
||||||
|
|
||||||
if (installedVersion !== WASI_VERSION) {
|
if (installedVersion !== WASI_VERSION) {
|
||||||
console.log(`WASI version mismatch. Downloading WASI SDK ${wasiArchiveURL}...`)
|
console.log(`WASI version mismatch. Downloading WASI SDK ${wasiArchiveURL}...`)
|
||||||
const response = await fetch(wasiArchiveURL);
|
const response = await fetch(wasiArchiveURL);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return cc;
|
return cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bytes = await response.bytes();
|
||||||
|
|
||||||
|
console.log(`Extracting WASI SDK...`);
|
||||||
|
await $`tar -xzv -C ${wasiDir} --strip-components=1 < ${bytes}`;
|
||||||
|
|
||||||
|
console.log(`Downloading libclang_rt.builtins-wasm32-wasi-${WASI_VERSION}...`);
|
||||||
|
const rtResponse = await fetch(rtURL);
|
||||||
|
|
||||||
|
if (!rtResponse.ok) {
|
||||||
|
return cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rtBytes = await rtResponse.bytes();
|
||||||
|
|
||||||
|
console.log(`Extracting libclang_rt.builtins-wasm32-wasi-${WASI_VERSION}...`);
|
||||||
|
await $`tar -xzv -C ${wasiDir} --strip-components=1 < ${rtBytes}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bytes = await response.bytes();
|
cc.cc = `${path.resolve(wasiDir, 'bin', 'clang')}`;
|
||||||
|
cc.nm = `${path.resolve(wasiDir, 'bin', 'nm')}`;
|
||||||
|
cc.flags = [
|
||||||
|
'--target=wasm32-wasip1',
|
||||||
|
`--sysroot=${path.resolve(wasiDir, 'share', 'wasi-sysroot')}`,
|
||||||
|
];
|
||||||
|
|
||||||
console.log(`Extracting WASI SDK...`);
|
const wasmOptInSdk = path.resolve(wasiDir, 'bin', 'wasm-opt');
|
||||||
await $`tar -xzv -C ${wasiDir} --strip-components=1 < ${bytes}`;
|
cc.wasmOpt = await fs.access(wasmOptInSdk).then(() => wasmOptInSdk).catch(() => Bun.which('wasm-opt') ?? null);
|
||||||
|
|
||||||
console.log(`Downloading libclang_rt.builtins-wasm32-wasi-${WASI_VERSION}...`);
|
const reactorCrt = path.resolve(wasiDir, 'share', 'wasi-sysroot', 'lib', 'wasm32-wasip1', 'crt1-reactor.o');
|
||||||
const rtResponse = await fetch(rtURL);
|
cc.reactorCrt = await fs.access(reactorCrt).then(() => reactorCrt).catch(() => null);
|
||||||
|
|
||||||
if (!rtResponse.ok) {
|
|
||||||
return cc;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rtBytes = await rtResponse.bytes();
|
|
||||||
|
|
||||||
console.log(`Extracting libclang_rt.builtins-wasm32-wasi-${WASI_VERSION}...`);
|
|
||||||
await $`tar -xzv -C ${wasiDir} --strip-components=1 < ${rtBytes}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
cc.cc = `${path.resolve(wasiDir, 'bin', 'clang')}`;
|
|
||||||
cc.nm = `${path.resolve(wasiDir, 'bin', 'nm')}`;
|
|
||||||
cc.flags = [
|
|
||||||
'--target=wasm32-wasip1',
|
|
||||||
`--sysroot=${path.resolve(wasiDir, 'share', 'wasi-sysroot')}`,
|
|
||||||
];
|
|
||||||
|
|
||||||
return cc;
|
return cc;
|
||||||
})();
|
})();
|
||||||
|
|
@ -103,16 +114,13 @@ async function compileLibGroup(
|
||||||
flags: string[],
|
flags: string[],
|
||||||
sources: string[],
|
sources: string[],
|
||||||
std: string[],
|
std: string[],
|
||||||
output: string,
|
objDir: string,
|
||||||
): Promise<void> {
|
): Promise<string[]> {
|
||||||
if (sources.length === 0) return;
|
if (sources.length === 0) return [];
|
||||||
|
|
||||||
// Per-file object dir derived from output name (dist/lib.c.o -> dist/lib.c/)
|
|
||||||
const objDir = output.replace(/\.o$/, '');
|
|
||||||
await fs.mkdir(objDir, { recursive: true });
|
await fs.mkdir(objDir, { recursive: true });
|
||||||
const objPaths = sources.map(src => path.resolve(objDir, path.basename(src) + '.o'));
|
const objPaths = sources.map(src => path.resolve(objDir, path.basename(src) + '.o'));
|
||||||
|
|
||||||
// Compile only changed files, in parallel
|
|
||||||
const compilationFlags = [...flags, ...std];
|
const compilationFlags = [...flags, ...std];
|
||||||
await Promise.all(sources.map(async (src, i) => {
|
await Promise.all(sources.map(async (src, i) => {
|
||||||
if (await needsRebuild(objPaths[i], [src], compilationFlags)) {
|
if (await needsRebuild(objPaths[i], [src], compilationFlags)) {
|
||||||
|
|
@ -122,10 +130,7 @@ async function compileLibGroup(
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Partial link if any object is newer than combined output
|
return objPaths;
|
||||||
if (!(await needsRebuild(output, objPaths))) return;
|
|
||||||
const partialResult = await $`${cc.cc} -r -o ${output} ${objPaths}`;
|
|
||||||
if (partialResult.exitCode !== 0) throw new Error(`Partial link failed for ${output}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function instantiate(url: string) {
|
async function instantiate(url: string) {
|
||||||
|
|
@ -134,7 +139,7 @@ async function instantiate(url: string) {
|
||||||
i32: 0x7f,
|
i32: 0x7f,
|
||||||
i64: 0x7e,
|
i64: 0x7e,
|
||||||
f32: 0x7d,
|
f32: 0x7d,
|
||||||
f64: 0x7c
|
f64: 0x7c,
|
||||||
};
|
};
|
||||||
|
|
||||||
type tc = keyof typeof typeCodes;
|
type tc = keyof typeof typeCodes;
|
||||||
|
|
@ -211,26 +216,34 @@ async function instantiate(url: string) {
|
||||||
lines.forEach(l => console.error(`[wasm] ${l}`));
|
lines.forEach(l => console.error(`[wasm] ${l}`));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
|
||||||
},
|
},
|
||||||
clock_time_get: (_id: number, _precision: bigint, timePtr: number) => {
|
clock_time_get: (_id: number, _precision: bigint, timePtr: number) => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const nanosecondNow = BigInt(now) * 1_000_000n;
|
const nanosecondNow = BigInt(now) * 1_000_000n;
|
||||||
const data = new DataView(memory.buffer);
|
const data = new DataView(memory.buffer);
|
||||||
data.setBigInt64(timePtr, nanosecondNow, true);
|
data.setBigInt64(timePtr, nanosecondNow, true);
|
||||||
return 0;
|
},
|
||||||
}
|
|
||||||
}, {
|
}, {
|
||||||
get(target, p) {
|
get(target, p) {
|
||||||
if (p in target) {
|
if (p in target) {
|
||||||
console.debug(`[${String(p)}] exists`);
|
return (...args: any[]) => {
|
||||||
return target[p as keyof typeof target];
|
const fn = target[p as keyof typeof target] as Function;
|
||||||
|
const result = fn(...args) ?? 0;
|
||||||
|
console.debug(`${String(p)}(${args.join(', ')}) = ${result}`);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (...args: any[]) => {
|
||||||
|
console.warn(`${String(p)}(${args.join(', ')}) is undefined`);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
return (...args: any[]) => { console.warn(`[${String(p)}]`, args); return 0; }
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
(instance.exports._initialize as (() => void) | undefined)?.();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...Object.fromEntries(
|
...Object.fromEntries(
|
||||||
Object.entries(instance.exports)
|
Object.entries(instance.exports)
|
||||||
|
|
@ -274,16 +287,14 @@ const wasmPlugin = ({ production }: WasmLoaderConfig = {}): BunPlugin => {
|
||||||
'sign-ext',
|
'sign-ext',
|
||||||
'nontrapping-fptoint',
|
'nontrapping-fptoint',
|
||||||
'reference-types',
|
'reference-types',
|
||||||
'multivalue',
|
|
||||||
].map(f => `-m${f}`);
|
].map(f => `-m${f}`);
|
||||||
|
|
||||||
const flags = [
|
const flags = [
|
||||||
...cc.flags,
|
...cc.flags,
|
||||||
production ? '-O3' : '-O0',
|
production ? '-Os' : '-O0',
|
||||||
'-flto',
|
'-flto',
|
||||||
'-fno-exceptions',
|
'-fno-exceptions',
|
||||||
'-fno-stack-protector',
|
'-fno-stack-protector',
|
||||||
'-ffast-math',
|
|
||||||
'-ffunction-sections',
|
'-ffunction-sections',
|
||||||
'-fdata-sections',
|
'-fdata-sections',
|
||||||
'-Wall',
|
'-Wall',
|
||||||
|
|
@ -297,14 +308,11 @@ const wasmPlugin = ({ production }: WasmLoaderConfig = {}): BunPlugin => {
|
||||||
'-I', include,
|
'-I', include,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Lib flags without -flto so clang -r (partial link) works on regular wasm objects
|
|
||||||
const libFlags = flags.filter(f => f !== '-flto');
|
|
||||||
|
|
||||||
const std = args.path.endsWith('.cpp') ? STD_CPP : STD_C;
|
const std = args.path.endsWith('.cpp') ? STD_CPP : STD_C;
|
||||||
|
|
||||||
const inputObjPath = path.resolve(distDir, inputBasename + '.o');
|
const inputObjPath = path.resolve(distDir, inputBasename + '.o');
|
||||||
const libCObjPath = path.resolve(distDir, 'lib.c.o');
|
const libCObjDir = path.resolve(distDir, 'lib.c');
|
||||||
const libCppObjPath = path.resolve(distDir, 'lib.cpp.o');
|
const libCppObjDir = path.resolve(distDir, 'lib.cpp');
|
||||||
|
|
||||||
const libCGlob = new Bun.Glob(`${buildAssets}/lib/**/*.c`);
|
const libCGlob = new Bun.Glob(`${buildAssets}/lib/**/*.c`);
|
||||||
const libCppGlob = new Bun.Glob(`${buildAssets}/lib/**/*.cpp`);
|
const libCppGlob = new Bun.Glob(`${buildAssets}/lib/**/*.cpp`);
|
||||||
|
|
@ -313,9 +321,7 @@ const wasmPlugin = ({ production }: WasmLoaderConfig = {}): BunPlugin => {
|
||||||
Array.fromAsync(libCppGlob.scan()),
|
Array.fromAsync(libCppGlob.scan()),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Compile all three units in parallel
|
const [, libCObjs, libCppObjs] = await Promise.all([
|
||||||
await Promise.all([
|
|
||||||
// 1. Input file -> dist/{filename}.o
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const compilationFlags = [...flags, ...std];
|
const compilationFlags = [...flags, ...std];
|
||||||
if (!await needsRebuild(inputObjPath, [args.path], compilationFlags)) return;
|
if (!await needsRebuild(inputObjPath, [args.path], compilationFlags)) return;
|
||||||
|
|
@ -323,19 +329,11 @@ const wasmPlugin = ({ production }: WasmLoaderConfig = {}): BunPlugin => {
|
||||||
if (result.exitCode !== 0) throw new Error('Compile failed, check output');
|
if (result.exitCode !== 0) throw new Error('Compile failed, check output');
|
||||||
await saveFlags(inputObjPath, compilationFlags);
|
await saveFlags(inputObjPath, compilationFlags);
|
||||||
})(),
|
})(),
|
||||||
// 2. lib/**/*.c -> dist/lib.c.o
|
compileLibGroup(cc, flags, libCFiles, STD_C, libCObjDir),
|
||||||
compileLibGroup(cc, libFlags, libCFiles, STD_C, libCObjPath),
|
compileLibGroup(cc, flags, libCppFiles, STD_CPP, libCppObjDir),
|
||||||
// 3. lib/**/*.cpp -> dist/lib.cpp.o
|
|
||||||
compileLibGroup(cc, libFlags, libCppFiles, STD_CPP, libCppObjPath),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 4. Link if any input object is newer than the wasm output
|
const allObjs = [inputObjPath, ...libCObjs, ...libCppObjs];
|
||||||
const libObjs = [
|
|
||||||
libCFiles.length > 0 ? libCObjPath : null,
|
|
||||||
libCppFiles.length > 0 ? libCppObjPath : null,
|
|
||||||
].filter((x): x is string => x !== null);
|
|
||||||
|
|
||||||
const allObjs = [inputObjPath, ...libObjs];
|
|
||||||
|
|
||||||
if (await needsRebuild(wasmPath, allObjs)) {
|
if (await needsRebuild(wasmPath, allObjs)) {
|
||||||
const symbolsResult = await $`${cc.nm} ${inputObjPath}`.quiet();
|
const symbolsResult = await $`${cc.nm} ${inputObjPath}`.quiet();
|
||||||
|
|
@ -356,14 +354,24 @@ const wasmPlugin = ({ production }: WasmLoaderConfig = {}): BunPlugin => {
|
||||||
'--no-entry',
|
'--no-entry',
|
||||||
'--import-memory',
|
'--import-memory',
|
||||||
'--export-table',
|
'--export-table',
|
||||||
...exports.flatMap(e => ['--export', e]),
|
'--growable-table',
|
||||||
|
...exports.flatMap(e => ['--export-if-defined', e]),
|
||||||
].map(f => `-Wl,${f}`);
|
].map(f => `-Wl,${f}`);
|
||||||
|
|
||||||
const linkResult = await $`${cc.cc} ${flags} ${linkFlags} -o ${wasmPath} -lstdc++ -nostartfiles ${allObjs}`;
|
const reactorInputs = cc.reactorCrt ? [cc.reactorCrt] : [];
|
||||||
|
const linkResult = await $`${cc.cc} ${flags} ${linkFlags} -o ${wasmPath} -lstdc++ -nostartfiles ${reactorInputs} ${allObjs}`;
|
||||||
|
|
||||||
if (linkResult.exitCode !== 0) {
|
if (linkResult.exitCode !== 0) {
|
||||||
throw new Error('Link failed, check output');
|
throw new Error('Link failed, check output');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (production && cc.wasmOpt) {
|
||||||
|
const optResult = await $`${cc.wasmOpt} -Os --converge -o ${wasmPath} ${wasmPath}`;
|
||||||
|
if (optResult.exitCode !== 0) throw new Error('wasm-opt failed, check output');
|
||||||
|
}
|
||||||
|
|
||||||
|
const wasmBytes = new Uint8Array(await Bun.file(wasmPath).arrayBuffer());
|
||||||
|
await Bun.write(`${args.path}.d.ts`, renderDts(wasmBytes));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,149 @@
|
||||||
|
// Parses a wasm binary's type / function / import / export sections to recover
|
||||||
|
// each function export's signature, then renders TypeScript declarations.
|
||||||
|
|
||||||
|
type Sig = { params: number[]; results: number[] };
|
||||||
|
|
||||||
|
const VALUE_TYPE_TS: Record<number, string> = {
|
||||||
|
0x7f: 'number', // i32
|
||||||
|
0x7e: 'bigint', // i64
|
||||||
|
0x7d: 'number', // f32
|
||||||
|
0x7c: 'number', // f64
|
||||||
|
};
|
||||||
|
|
||||||
|
function readULEB128(bytes: Uint8Array, offset: number): [number, number] {
|
||||||
|
let result = 0;
|
||||||
|
let shift = 0;
|
||||||
|
let i = 0;
|
||||||
|
while (true) {
|
||||||
|
const b = bytes[offset + i];
|
||||||
|
result |= (b & 0x7f) << shift;
|
||||||
|
i++;
|
||||||
|
if ((b & 0x80) === 0) break;
|
||||||
|
shift += 7;
|
||||||
|
}
|
||||||
|
return [result, i];
|
||||||
|
}
|
||||||
|
|
||||||
|
function skipLimits(bytes: Uint8Array, offset: number): number {
|
||||||
|
const flags = bytes[offset];
|
||||||
|
let o = offset + 1;
|
||||||
|
const [, l1] = readULEB128(bytes, o); o += l1;
|
||||||
|
if (flags & 1) {
|
||||||
|
const [, l2] = readULEB128(bytes, o); o += l2;
|
||||||
|
}
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseWasmExports(bytes: Uint8Array): Map<string, Sig> {
|
||||||
|
if (bytes[0] !== 0x00 || bytes[1] !== 0x61 || bytes[2] !== 0x73 || bytes[3] !== 0x6d) {
|
||||||
|
throw new Error('Invalid wasm magic');
|
||||||
|
}
|
||||||
|
let offset = 8;
|
||||||
|
|
||||||
|
const types: Sig[] = [];
|
||||||
|
const funcTypes: number[] = []; // defined function index → type index
|
||||||
|
const importedFuncTypes: number[] = []; // imported function index → type index
|
||||||
|
const exportEntries: { name: string; kind: number; index: number }[] = [];
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
|
||||||
|
while (offset < bytes.length) {
|
||||||
|
const sectionId = bytes[offset++];
|
||||||
|
const [sectionSize, s1] = readULEB128(bytes, offset);
|
||||||
|
offset += s1;
|
||||||
|
const sectionEnd = offset + sectionSize;
|
||||||
|
|
||||||
|
if (sectionId === 1) {
|
||||||
|
const [count, c1] = readULEB128(bytes, offset); offset += c1;
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
if (bytes[offset++] !== 0x60) throw new Error('Expected functype 0x60');
|
||||||
|
const [pc, p1] = readULEB128(bytes, offset); offset += p1;
|
||||||
|
const params: number[] = [];
|
||||||
|
for (let j = 0; j < pc; j++) params.push(bytes[offset++]);
|
||||||
|
const [rc, r1] = readULEB128(bytes, offset); offset += r1;
|
||||||
|
const results: number[] = [];
|
||||||
|
for (let j = 0; j < rc; j++) results.push(bytes[offset++]);
|
||||||
|
types.push({ params, results });
|
||||||
|
}
|
||||||
|
} else if (sectionId === 2) {
|
||||||
|
const [count, c1] = readULEB128(bytes, offset); offset += c1;
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const [modLen, m1] = readULEB128(bytes, offset); offset += m1 + modLen;
|
||||||
|
const [fldLen, f1] = readULEB128(bytes, offset); offset += f1 + fldLen;
|
||||||
|
const kind = bytes[offset++];
|
||||||
|
if (kind === 0x00) {
|
||||||
|
const [typeIdx, t1] = readULEB128(bytes, offset); offset += t1;
|
||||||
|
importedFuncTypes.push(typeIdx);
|
||||||
|
} else if (kind === 0x01) {
|
||||||
|
offset++; // elem type
|
||||||
|
offset = skipLimits(bytes, offset);
|
||||||
|
} else if (kind === 0x02) {
|
||||||
|
offset = skipLimits(bytes, offset);
|
||||||
|
} else if (kind === 0x03) {
|
||||||
|
offset += 2; // valtype + mutability
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (sectionId === 3) {
|
||||||
|
const [count, c1] = readULEB128(bytes, offset); offset += c1;
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const [typeIdx, t1] = readULEB128(bytes, offset); offset += t1;
|
||||||
|
funcTypes.push(typeIdx);
|
||||||
|
}
|
||||||
|
} else if (sectionId === 7) {
|
||||||
|
const [count, c1] = readULEB128(bytes, offset); offset += c1;
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const [nameLen, n1] = readULEB128(bytes, offset); offset += n1;
|
||||||
|
const name = decoder.decode(bytes.subarray(offset, offset + nameLen));
|
||||||
|
offset += nameLen;
|
||||||
|
const kind = bytes[offset++];
|
||||||
|
const [index, i1] = readULEB128(bytes, offset); offset += i1;
|
||||||
|
exportEntries.push({ name, kind, index });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = sectionEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = new Map<string, Sig>();
|
||||||
|
for (const { name, kind, index } of exportEntries) {
|
||||||
|
if (kind !== 0x00) continue;
|
||||||
|
const typeIdx = index < importedFuncTypes.length
|
||||||
|
? importedFuncTypes[index]
|
||||||
|
: funcTypes[index - importedFuncTypes.length];
|
||||||
|
result.set(name, types[typeIdx]);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function tsType(valType: number): string {
|
||||||
|
return VALUE_TYPE_TS[valType] ?? 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
function tsSignature({ params, results }: Sig): string {
|
||||||
|
const paramList = params
|
||||||
|
.map((t, i) => `a${i}: ${tsType(t)}`)
|
||||||
|
.join(', ');
|
||||||
|
let ret: string;
|
||||||
|
if (results.length === 0) ret = 'void';
|
||||||
|
else if (results.length === 1) ret = tsType(results[0]);
|
||||||
|
else ret = `[${results.map(tsType).join(', ')}]`;
|
||||||
|
return `(${paramList}) => ${ret}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderDts(wasmBytes: Uint8Array): string {
|
||||||
|
const sigs = parseWasmExports(wasmBytes);
|
||||||
|
const fns = Array.from(sigs.entries())
|
||||||
|
.filter(([name]) => !name.startsWith('_'))
|
||||||
|
.sort(([a], [b]) => a.localeCompare(b));
|
||||||
|
|
||||||
|
return [
|
||||||
|
'// Auto-generated by wasmPlugin. Do not edit.',
|
||||||
|
'declare const _: {',
|
||||||
|
...fns.map(([name, sig]) => ` ${name}: ${tsSignature(sig)};`),
|
||||||
|
' memory: WebAssembly.Memory;',
|
||||||
|
' table: WebAssembly.Table;',
|
||||||
|
' readonly data: DataView;',
|
||||||
|
'};',
|
||||||
|
'export default _;',
|
||||||
|
'',
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Fallback type for .c/.cpp imports until the wasmPlugin generates a per-file .d.ts.
|
||||||
|
// After a build, a sibling `${file}.d.ts` with concrete export names takes precedence.
|
||||||
|
|
||||||
|
declare module '*.cpp' {
|
||||||
|
const _: {
|
||||||
|
memory: WebAssembly.Memory;
|
||||||
|
table: WebAssembly.Table;
|
||||||
|
readonly data: DataView;
|
||||||
|
[fn: string]: unknown;
|
||||||
|
};
|
||||||
|
export default _;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '*.c' {
|
||||||
|
const _: {
|
||||||
|
memory: WebAssembly.Memory;
|
||||||
|
table: WebAssembly.Table;
|
||||||
|
readonly data: DataView;
|
||||||
|
[fn: string]: unknown;
|
||||||
|
};
|
||||||
|
export default _;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '*.wasm' {
|
||||||
|
const _: {
|
||||||
|
memory: WebAssembly.Memory;
|
||||||
|
table?: WebAssembly.Table;
|
||||||
|
data: DataView;
|
||||||
|
[fn: string]: unknown;
|
||||||
|
};
|
||||||
|
export default _;
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,2 @@
|
||||||
--target=wasm32-wasip1
|
|
||||||
--sysroot=dist/wasi/share/wasi-sysroot
|
--sysroot=dist/wasi/share/wasi-sysroot
|
||||||
-std=gnu++26
|
-Ibuild/assets/include
|
||||||
-fno-exceptions
|
|
||||||
-I
|
|
||||||
build/assets/include
|
|
||||||
-Wall
|
|
||||||
-Wextra
|
|
||||||
-Wpedantic
|
|
||||||
-Wshadow
|
|
||||||
-Wconversion
|
|
||||||
-Werror
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@
|
||||||
#define TYPE_CIRCLE 1
|
#define TYPE_CIRCLE 1
|
||||||
#define TYPE_PLANE 2
|
#define TYPE_PLANE 2
|
||||||
|
|
||||||
|
#define rb_get(idx) (rigid_bodies + (idx))
|
||||||
|
|
||||||
////////// Types
|
////////// Types
|
||||||
|
|
||||||
typedef struct rigid_body_t {
|
typedef struct rigid_body_t {
|
||||||
|
|
@ -50,12 +52,12 @@ static rigid_body_collision_callback_t rigid_body_collision_callback = NULL;
|
||||||
////////// Functions
|
////////// Functions
|
||||||
|
|
||||||
JS_EXPORT rigid_body* rigid_body_get(rigid_body_index idx) {
|
JS_EXPORT rigid_body* rigid_body_get(rigid_body_index idx) {
|
||||||
return (rigid_bodies + (idx));
|
return rb_get(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t rigid_body_new(float x, float y, float vx, float vy, float mass) {
|
size_t rigid_body_new(float x, float y, float vx, float vy, float mass) {
|
||||||
rigid_body_index idx = rigid_body_find_empty();
|
rigid_body_index idx = rigid_body_find_empty();
|
||||||
rigid_body* rb = rigid_body_get(idx);
|
rigid_body* rb = rb_get(idx);
|
||||||
rb->type = TYPE_EMPTY;
|
rb->type = TYPE_EMPTY;
|
||||||
|
|
||||||
rb->pos.x = x;
|
rb->pos.x = x;
|
||||||
|
|
@ -74,7 +76,7 @@ size_t rigid_body_new(float x, float y, float vx, float vy, float mass) {
|
||||||
|
|
||||||
JS_EXPORT rigid_body_index rigid_body_new_circle(float x, float y, float vx, float vy, float mass, float radius) {
|
JS_EXPORT rigid_body_index rigid_body_new_circle(float x, float y, float vx, float vy, float mass, float radius) {
|
||||||
rigid_body_index idx = rigid_body_new(x, y, vx, vy, mass);
|
rigid_body_index idx = rigid_body_new(x, y, vx, vy, mass);
|
||||||
rigid_body* rb = rigid_body_get(idx);
|
rigid_body* rb = rb_get(idx);
|
||||||
|
|
||||||
rb->type = TYPE_CIRCLE;
|
rb->type = TYPE_CIRCLE;
|
||||||
rb->circle.radius = radius;
|
rb->circle.radius = radius;
|
||||||
|
|
@ -84,7 +86,7 @@ JS_EXPORT rigid_body_index rigid_body_new_circle(float x, float y, float vx, flo
|
||||||
|
|
||||||
JS_EXPORT rigid_body_index rigid_body_new_plane(float x, float y, float nx, float ny) {
|
JS_EXPORT rigid_body_index rigid_body_new_plane(float x, float y, float nx, float ny) {
|
||||||
rigid_body_index idx = rigid_body_new(x, y, 0, 0, INFINITY);
|
rigid_body_index idx = rigid_body_new(x, y, 0, 0, INFINITY);
|
||||||
rigid_body* rb = rigid_body_get(idx);
|
rigid_body* rb = rb_get(idx);
|
||||||
|
|
||||||
rb->type = TYPE_PLANE;
|
rb->type = TYPE_PLANE;
|
||||||
rb->plane.normal = vec2_normalize((vec2){nx, ny});
|
rb->plane.normal = vec2_normalize((vec2){nx, ny});
|
||||||
|
|
@ -93,12 +95,12 @@ JS_EXPORT rigid_body_index rigid_body_new_plane(float x, float y, float nx, floa
|
||||||
}
|
}
|
||||||
|
|
||||||
JS_EXPORT void rigid_body_free(rigid_body_index idx) {
|
JS_EXPORT void rigid_body_free(rigid_body_index idx) {
|
||||||
memset(rigid_body_get(idx), 0, sizeof(rigid_body));
|
memset(rb_get(idx), 0, sizeof(rigid_body));
|
||||||
}
|
}
|
||||||
|
|
||||||
JS_EXPORT void rigid_body_update(rigid_body_index idx, float dt) {
|
JS_EXPORT void rigid_body_update(rigid_body_index idx, float dt) {
|
||||||
rigid_body_resolve_collision(idx);
|
rigid_body_resolve_collision(idx);
|
||||||
rigid_body* rb = rigid_body_get(idx);
|
rigid_body* rb = rb_get(idx);
|
||||||
|
|
||||||
if (!isinf(rb->mass)) {
|
if (!isinf(rb->mass)) {
|
||||||
rb->vel.x += rb->force.x * dt / rb->mass;
|
rb->vel.x += rb->force.x * dt / rb->mass;
|
||||||
|
|
@ -114,7 +116,7 @@ JS_EXPORT void rigid_body_update(rigid_body_index idx, float dt) {
|
||||||
|
|
||||||
JS_EXPORT void rigid_body_update_all(float dt) {
|
JS_EXPORT void rigid_body_update_all(float dt) {
|
||||||
for (rigid_body_index idx = 0; idx < rigid_bodies_cap; idx++) {
|
for (rigid_body_index idx = 0; idx < rigid_bodies_cap; idx++) {
|
||||||
rigid_body* current = rigid_body_get(idx);
|
rigid_body* current = rb_get(idx);
|
||||||
if (current->type == TYPE_EMPTY) {
|
if (current->type == TYPE_EMPTY) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -123,14 +125,14 @@ JS_EXPORT void rigid_body_update_all(float dt) {
|
||||||
}
|
}
|
||||||
|
|
||||||
JS_EXPORT void rigid_body_add_force(rigid_body_index idx, float fx, float fy) {
|
JS_EXPORT void rigid_body_add_force(rigid_body_index idx, float fx, float fy) {
|
||||||
rigid_body* rb = rigid_body_get(idx);
|
rigid_body* rb = rb_get(idx);
|
||||||
rb->force.x += fx;
|
rb->force.x += fx;
|
||||||
rb->force.y += fy;
|
rb->force.y += fy;
|
||||||
}
|
}
|
||||||
|
|
||||||
JS_EXPORT void rigid_body_add_global_force(float fx, float fy) {
|
JS_EXPORT void rigid_body_add_global_force(float fx, float fy) {
|
||||||
for (rigid_body_index idx = 0; idx < rigid_bodies_cap; idx++) {
|
for (rigid_body_index idx = 0; idx < rigid_bodies_cap; idx++) {
|
||||||
rigid_body* current = rigid_body_get(idx);
|
rigid_body* current = rb_get(idx);
|
||||||
if (current->type == TYPE_EMPTY) {
|
if (current->type == TYPE_EMPTY) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -143,10 +145,10 @@ JS_EXPORT void rigid_body_set_collision_callback(rigid_body_collision_callback_t
|
||||||
}
|
}
|
||||||
|
|
||||||
void rigid_body_resolve_collision(rigid_body_index idx) {
|
void rigid_body_resolve_collision(rigid_body_index idx) {
|
||||||
rigid_body* rb = rigid_body_get(idx);
|
rigid_body* rb = rb_get(idx);
|
||||||
int is_static = isinf(rb->mass);
|
int is_static = isinf(rb->mass);
|
||||||
for (rigid_body_index i = idx + 1; i < rigid_bodies_cap; i++) {
|
for (rigid_body_index i = idx + 1; i < rigid_bodies_cap; i++) {
|
||||||
rigid_body* current = rigid_body_get(i);
|
rigid_body* current = rb_get(i);
|
||||||
if (!is_static || !isinf(current->mass)) {
|
if (!is_static || !isinf(current->mass)) {
|
||||||
rigid_body_handle_collision(idx, i);
|
rigid_body_handle_collision(idx, i);
|
||||||
}
|
}
|
||||||
|
|
@ -154,8 +156,8 @@ void rigid_body_resolve_collision(rigid_body_index idx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void rigid_body_handle_collision(rigid_body_index idx1, rigid_body_index idx2) {
|
void rigid_body_handle_collision(rigid_body_index idx1, rigid_body_index idx2) {
|
||||||
rigid_body* rb1 = rigid_body_get(idx1);
|
rigid_body* rb1 = rb_get(idx1);
|
||||||
rigid_body* rb2 = rigid_body_get(idx2);
|
rigid_body* rb2 = rb_get(idx2);
|
||||||
if (rb1->type == TYPE_CIRCLE && rb2->type == TYPE_CIRCLE) {
|
if (rb1->type == TYPE_CIRCLE && rb2->type == TYPE_CIRCLE) {
|
||||||
vec2 d = vec2_sub(rb2->pos, rb1->pos);
|
vec2 d = vec2_sub(rb2->pos, rb1->pos);
|
||||||
float distance = vec2_mag(d);
|
float distance = vec2_mag(d);
|
||||||
|
|
@ -236,7 +238,7 @@ float point_to_line_dist(vec2 p, vec2 line_point, vec2 normal) {
|
||||||
rigid_body_index rigid_body_find_empty() {
|
rigid_body_index rigid_body_find_empty() {
|
||||||
if (rigid_bodies) {
|
if (rigid_bodies) {
|
||||||
for (rigid_body_index idx = 0; idx < rigid_bodies_cap; idx++) {
|
for (rigid_body_index idx = 0; idx < rigid_bodies_cap; idx++) {
|
||||||
rigid_body* current = rigid_body_get(idx);
|
rigid_body* current = rb_get(idx);
|
||||||
if (current->type == TYPE_EMPTY) {
|
if (current->type == TYPE_EMPTY) {
|
||||||
memset(current, 0, sizeof(rigid_body));
|
memset(current, 0, sizeof(rigid_body));
|
||||||
return idx;
|
return idx;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
#include <js.h>
|
#include <js.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
#define WIDTH 512
|
#define WIDTH 512
|
||||||
#define HEIGHT 512
|
#define HEIGHT 512
|
||||||
|
|
@ -63,7 +62,7 @@ static int count_neighbours(int x, int y) {
|
||||||
}
|
}
|
||||||
|
|
||||||
GRAPHICS_INIT {
|
GRAPHICS_INIT {
|
||||||
srand((uint32_t)time(NULL));
|
srand(arc4random());
|
||||||
field = malloc(WIDTH * HEIGHT);
|
field = malloc(WIDTH * HEIGHT);
|
||||||
next_field = malloc(WIDTH * HEIGHT);
|
next_field = malloc(WIDTH * HEIGHT);
|
||||||
image_data = image_create(WIDTH, HEIGHT);
|
image_data = image_create(WIDTH, HEIGHT);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <js.h>
|
#include <js.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
|
@ -75,7 +75,13 @@ constexpr bit bit::nor(const bit& a, const bit& b) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
JS_EXPORT uint64_t awoo() {
|
static struct InitProbe {
|
||||||
|
InitProbe() {
|
||||||
|
std::cout << "[ctor] _initialize ran" << std::endl;
|
||||||
|
}
|
||||||
|
} init_probe;
|
||||||
|
|
||||||
|
JS_EXPORT void awoo() {
|
||||||
bit a{1, "a"};
|
bit a{1, "a"};
|
||||||
bit b{1, "b"};
|
bit b{1, "b"};
|
||||||
|
|
||||||
|
|
@ -83,5 +89,5 @@ JS_EXPORT uint64_t awoo() {
|
||||||
|
|
||||||
std::cout << "sum = " << sum << "\ncarry = " << carry << std::endl;
|
std::cout << "sum = " << sum << "\ncarry = " << carry << std::endl;
|
||||||
|
|
||||||
return sum;
|
std::cout << "rand = " << rand() << "\narc4 = " << arc4random() << std::endl;
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import awoo from './awoo.cpp';
|
import awoo from './awoo.cpp';
|
||||||
|
|
||||||
export default function main() {
|
export default function main() {
|
||||||
console.log(awoo);
|
console.log(awoo)
|
||||||
|
awoo.awoo();
|
||||||
}
|
}
|
||||||
|
|
@ -38,35 +38,6 @@ declare module "*.ogg" {
|
||||||
const audio: HTMLAudioElement;
|
const audio: HTMLAudioElement;
|
||||||
export default audio;
|
export default audio;
|
||||||
}
|
}
|
||||||
declare module "*.wasm" {
|
|
||||||
const instance: {
|
|
||||||
memory: WebAssembly.Memory;
|
|
||||||
data: DataView;
|
|
||||||
|
|
||||||
[x: string]: (...args: any) => any;
|
|
||||||
};
|
|
||||||
export default instance;
|
|
||||||
}
|
|
||||||
declare module "*.c" {
|
|
||||||
const instance: {
|
|
||||||
memory: WebAssembly.Memory;
|
|
||||||
table: WebAssembly.Table;
|
|
||||||
data: DataView;
|
|
||||||
|
|
||||||
[x: string]: (...args: any) => any;
|
|
||||||
};
|
|
||||||
export default instance;
|
|
||||||
}
|
|
||||||
declare module "*.cpp" {
|
|
||||||
const instance: {
|
|
||||||
memory: WebAssembly.Memory;
|
|
||||||
table: WebAssembly.Table;
|
|
||||||
data: DataView;
|
|
||||||
|
|
||||||
[x: string]: (...args: any) => any;
|
|
||||||
};
|
|
||||||
export default instance;
|
|
||||||
}
|
|
||||||
declare module "*.glsl" {
|
declare module "*.glsl" {
|
||||||
const content: string;
|
const content: string;
|
||||||
export default content;
|
export default content;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue