Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add run-wasm and --include-all-builtins #188

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
30 changes: 23 additions & 7 deletions compiler/codegen.js
Original file line number Diff line number Diff line change
Expand Up @@ -1227,7 +1227,10 @@ const asmFunc = (name, { wasm, params = [], typedParams = false, locals: localTy
}

for (const x in _data) {
if (data.find(y => y.page === x)) return;
if (data.find(y => y.page === x)) {
console.warn('page for', x, 'already exists');
return;
}
data.push({ page: x, bytes: _data[x] });
}

Expand All @@ -1254,19 +1257,27 @@ const asmFunc = (name, { wasm, params = [], typedParams = false, locals: localTy
else wasm = asmFuncToAsm(func, wasm);
}

let baseGlobalIdx, i = 0;
let globalRefs = [], i = 0;
for (const type of globalTypes) {
if (baseGlobalIdx === undefined) baseGlobalIdx = globals['#ind'];

globals[globalNames[i] ?? `${name}_global_${i}`] = { idx: globals['#ind']++, type, init: globalInits[i] ?? 0 };
let nm = globalNames[i] ?? `${name}_global_${i}`;
if (globals[nm]) {
globalRefs.push(globals[nm].idx);
continue;
}
globalRefs.push(globals['#ind']);
globals[nm] = { idx: globals['#ind']++, type, init: globalInits[i] ?? 0 };
i++;
}

if (globalTypes.length !== 0) {
// offset global ops for base global idx
for (const inst of wasm) {
for (let i = 0; i < wasm.length; i++) {
const inst = wasm[i];
if (inst[0] === Opcodes.global_get || inst[0] === Opcodes.global_set) {
inst[1] += baseGlobalIdx;
if (inst[1] >= globalRefs.length) {
throw new Error('in builtin ' + name + ': global_get / global_set out of range (' + inst[1] + ' for count ' + globalRefs.length + ')');
}
wasm[i] = [ inst[0], globalRefs[inst[1]] ];
}
}
}
Expand Down Expand Up @@ -6205,6 +6216,11 @@ const generateFunc = (scope, decl, forceNoExpr = false) => {
const preface = wasm;
wasm = generate(func, body);
wasm.unshift(...preface);
if (Prefs.includeAllBuiltins) {
for (let x in builtinFuncs) {
includeBuiltin(func, x);
}
}

if (func.generator) {
// make generator at the start
Expand Down
21 changes: 21 additions & 0 deletions compiler/encoding.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,4 +202,25 @@ export const ieee754_binary64_into = (value, buffer) => {
const data = new Uint8Array(new Float64Array([ value ]).buffer);
for (let i = 0; i < 8; i++) buffer.push(data[i]);
// buffer.push(...new Uint8Array(new Float64Array([ value ]).buffer));
};

export const readUnsignedLEB128FromBuffer = (buffer, index) => {
let value = 0;
let shift = 0;
while (true) {
let b = buffer[index];
value |= (b & 0x7F) << shift;
if ((value & 0x80) == 0) {
return [ value, index + 1 ];
}
shift += 7;
}
};

export const readStringFromBuffer = (buffer, index) => {
let length;
[ length, index ] = readUnsignedLEB128FromBuffer(buffer, index);
let slice = buffer.subarray(index, index + length);
let decoder = new TextDecoder();
return [ decoder.decode(slice), index + length ];
};
9 changes: 9 additions & 0 deletions compiler/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,15 @@ export default (code, module = undefined) => {

if (Prefs.optFuncs || Prefs.f) logFuncs(funcs, globals, exceptions);

if (Prefs.sizeLog) {
console.log('Functions ordered by size:');
const sortedFuncs = funcs.toSorted((x, y) => y.wasm.length - x.wasm.length);
const toShow = Math.min(funcs.length, Prefs.sizeLogSize ?? 10);
for (let i = 0; i < toShow; i++) {
console.log((i + 1) + '. ' + sortedFuncs[i].name + ' with ' + sortedFuncs[i].wasm.length + ' instructions');
}
}

if (Prefs.compileAllocLog) {
const wasmPages = Math.ceil((pages.size * pageSize) / 65536);
const bytes = wasmPages * 65536;
Expand Down
4 changes: 2 additions & 2 deletions compiler/precompile.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const compile = async (file, _funcs) => {
let first = source.slice(0, source.indexOf('\n'));

if (first.startsWith('export default')) {
source = await (await import(file)).default();
source = await (await import('file://' + file)).default();
first = source.slice(0, source.indexOf('\n'));
}

Expand Down Expand Up @@ -180,7 +180,7 @@ const precompile = async () => {

let funcs = [];
let fileCount = 0;
for (const file of fs.readdirSync(dir)) {
for (const file of fs.readdirSync(dir).toSorted()) {
if (file.endsWith('.d.ts')) continue;
fileCount++;

Expand Down
90 changes: 85 additions & 5 deletions compiler/wrap.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { encodeVector, encodeLocal } from './encoding.js';
import { encodeVector, encodeLocal, readUnsignedLEB128FromBuffer, readStringFromBuffer } from './encoding.js';
import { Section, Valtype } from './wasmSpec.js';
import { importedFuncs } from './builtins.js';
import compile from './index.js';
import decompile from './decompile.js';
Expand Down Expand Up @@ -126,6 +127,7 @@ ${flags & 0b0001 ? ` get func idx: ${get}
}

case TYPES.function: {
if (!funcs) return function () {};
let func;
if (value < 0) {
func = importedFuncs[value + importedFuncs.length];
Expand Down Expand Up @@ -181,7 +183,7 @@ ${flags & 0b0001 ? ` get func idx: ${get}
}

case TYPES.symbol: {
const page = pages.get('symbol.ts/descStore');
const page = pages?.get('symbol.ts/descStore');
if (!page) return Symbol();

const descStore = page * pageSize;
Expand Down Expand Up @@ -347,8 +349,6 @@ export default (source, module = undefined, customImports = {}, print = str => p

globalThis.porfDebugInfo = { funcs, globals };

// fs.writeFileSync('out.wasm', Buffer.from(wasm));

times.push(performance.now() - t1);
if (Prefs.profileCompiler && !globalThis.onProgress) console.log(`\u001b[1mcompiled in ${times[0].toFixed(2)}ms\u001b[0m`);

Expand Down Expand Up @@ -551,4 +551,84 @@ export default (source, module = undefined, customImports = {}, print = str => p
}

return { exports, wasm, times, pages, c };
};
};

// TODO: integrate with default
export const fromWasm = (wasm, print = str => process.stdout.write(str)) => {
let instance, memory;
try {
const module = new WebAssembly.Module(wasm);
instance = new WebAssembly.Instance(module, {
'': {
p: i => print(i.toString()),
c: i => print(String.fromCharCode(Number(i))),
t: () => performance.now(),
u: () => performance.timeOrigin,
y: () => {},
z: () => {},
w: (ind, outPtr) => { // readArgv
let args = process.argv.slice(2);
args = args.slice(args.findIndex(x => !x.startsWith('-')) + 1);

const str = args[ind - 1];
if (!str) return -1;

writeByteStr(memory, outPtr, str);
return str.length;
},
q: (pathPtr, outPtr) => { // readFile
try {
const path = pathPtr === 0 ? 0 : readByteStr(memory, pathPtr);
const contents = fs.readFileSync(path, 'utf8');
writeByteStr(memory, outPtr, contents);
return contents.length;
} catch {
return -1;
}
},
b: () => {
debugger;
}
}
});
} catch (e) {
throw e;
}
const exports = {};

const exceptTag = instance.exports['0'];
memory = instance.exports['$'];

for (const x in instance.exports) {
if (x === '0') continue;
if (x === '$') {
exports.$ = instance.exports.$;
continue;
}

const wasm = instance.exports[x];
exports[x === 'm' ? 'main' : x] = function() {
try {
const ret = wasm.apply(this, arguments);
if (ret == null) return undefined;

return porfToJSValue({ memory }, ret[0], ret[1]);
} catch (e) {
if (e.is && e.is(exceptTag)) {
const value = e.getArg(exceptTag, 0);

try {
let type = e.getArg(exceptTag, 1);
e = porfToJSValue({ memory }, value, type);
} catch (e2) {
e = new Error('Porffor error id: ' + value);
}
}

throw e;
}
};
}

return { exports, wasm };
};
25 changes: 23 additions & 2 deletions runner/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ if (process.argv.includes('--help')) {
console.log(`\n\x1B[1mCommands:\x1B[0m`);
for (const [ cmd, [ color, desc ] ] of Object.entries({
run: [ 34, 'Run a JS file' ],
wasm: [ 34, 'Compile a JS file to a Wasm binary\n' ],
wasm: [ 34, 'Compile a JS file to a Wasm binary' ],
'run-wasm': [ 34, 'Run a compiled Wasm binary\n' ],

c: [ 31, 'Compile a JS file to C source code' ],
native: [ 31, 'Compile a JS file to a native binary\n' ],
Expand Down Expand Up @@ -59,7 +60,7 @@ const done = async () => {
};

let file = process.argv.slice(2).find(x => x[0] !== '-');
if (['precompile', 'run', 'wasm', 'native', 'c', 'profile', 'debug', 'debug-wasm'].includes(file)) {
if (['precompile', 'run', 'wasm', 'run-wasm', 'native', 'c', 'profile', 'debug', 'debug-wasm'].includes(file)) {
// remove this arg
process.argv.splice(process.argv.indexOf(file), 1);

Expand All @@ -78,6 +79,26 @@ if (['precompile', 'run', 'wasm', 'native', 'c', 'profile', 'debug', 'debug-wasm
await done();
}

if (file === 'run-wasm') {
file = process.argv.slice(2).find(x => x[0] !== '-');
const buffer = fs.readFileSync(file);

let runStart;
const fromWasm = (await import('../compiler/wrap.js')).fromWasm;
try {
const out = fromWasm(buffer);
runStart = performance.now();
out.exports.main();
} catch (e) {
let out = e;
if (Object.getPrototypeOf(e).message != null) out = `${e.constructor.name}${e.message != null ? `: ${e.message}` : ''}`;
if (e instanceof Error && process.argv.includes('-d')) throw e;
console.error(e);
}
if (process.argv.includes('-t')) console.log(`execution time: ${(performance.now() - runStart).toFixed(2)}ms`);
process.exit();
}

if (['wasm', 'native', 'c'].includes(file)) {
process.argv.push(`--target=${file}`);
}
Expand Down