1
0
Fork 0
tsgames/build/wasmPlugin.ts

106 lines
5.6 KiB
TypeScript

import { plugin, $, type BunPlugin } from "bun";
import path from 'path';
interface WasmLoaderConfig {
production?: boolean;
portable?: boolean;
}
const wasmPlugin = ({ production, portable }: WasmLoaderConfig = {}): BunPlugin => {
const p: BunPlugin = {
name: "WASM loader",
async setup(build) {
build.onLoad({ filter: /\.(c(pp)?|wasm)$/ }, async (args) => {
let wasmPath = path.resolve(import.meta.dir, '..', 'dist', 'tmp.wasm');
let jsContent: string = `
async function instantiate(url) {
const memory = new WebAssembly.Memory({
initial: 32,
});
let data = new DataView(memory.buffer);
const decoder = new TextDecoder();
const getString = (ptr) => {
const view = new Uint8Array(memory.buffer, ptr);
const start = ptr;
const end = ptr + view.indexOf(0);
return decoder.decode(memory.buffer.slice(start, end));
};
const { instance } = await WebAssembly.instantiateStreaming(fetch(url), {
env: {
memory,
log(format, argsPtr) {
format = getString(format);
let buf = '';
let isFormat = false;
let w = 4;
const align = (a) => argsPtr += (argsPtr % a);
for (const c of format) {
if (!isFormat && c !== '%') {
buf += c;
} else if (c === '%') {
isFormat = true;
} else switch(c) {
case '%': buf += '%'; isFormat = false; break;
case 's': align(4); buf += getString(data.getInt32(argsPtr, true)); argsPtr += 4; isFormat = false; break;
case 'd': align(w); buf += data[w === 4 ? 'getInt32': 'getBigInt64'](argsPtr, true); argsPtr += w; isFormat = false; break;
case 'x': align(w); buf += data[w === 4 ? 'getInt32': 'getBigInt64'](argsPtr, true).toString(16); argsPtr += w; isFormat = false; break;
case 'o': align(w); buf += data[w === 4 ? 'getInt32': 'getBigInt64'](argsPtr, true).toString(8); argsPtr += w; isFormat = false; break;
case 'u': align(w); buf += data[w === 4 ? 'getUint32': 'getBigUint64'](argsPtr, true); argsPtr += w; isFormat = false; break;
case 'f': align(8); buf += data.getFloat64(argsPtr, true); argsPtr += 8; isFormat = false; break;
case 'c': align(4); buf += String.fromCharCode(data.getInt32(argsPtr, true)); argsPtr += 4; isFormat = false; break;
case 'l': w = 8; break;
}
}
console.log('[wasm] ' + buf);
},
grow(blocks) {
if (blocks > 0) {
memory.grow(blocks);
data = new DataView(memory.buffer);
}
}
}
});
return {
...instance.exports,
memory,
get data() { return data; },
};
}
const module = await instantiate(new URL($WASM$));
if (typeof module.__srand === 'function') module.__srand(BigInt(Date.now()));
export default module;
`;
if (args.path.endsWith('.wasm')) {
wasmPath = args.path;
} else {
const buildAssets = path.resolve(import.meta.dir, 'assets');
const stdlib = `${buildAssets}/stdlib.c`;
const opt = production ? '-O3' : '-O0';
const result = await $`clang --target=wasm32 ${opt} -flto -fno-builtin --no-standard-libraries -I ${buildAssets} -Wall -Wextra -Wpedantic -Werror -Wl,--lto-O3 -Wl,--no-entry -Wl,--import-memory -o ${wasmPath} ${args.path} ${stdlib}`;
if (result.exitCode !== 0) {
throw new Error('Compile failed, check output');
}
}
const wasmContent = await Bun.file(wasmPath).arrayBuffer();
const wasmBuffer = Buffer.from(wasmContent).toString('base64');
const wasmURL = `data:application/wasm;base64,${wasmBuffer}`;
return {
loader: 'js',
contents: jsContent.replace(/new URL\([^)]*\)/, `new URL(${JSON.stringify(wasmURL)})`),
};
});
}
};
plugin(p);
return p;
};
export default wasmPlugin;