diff --git a/src/runtime/javascript/jspgf.js b/src/runtime/javascript/jspgf.js index 7e6c72907..55c85e10c 100644 --- a/src/runtime/javascript/jspgf.js +++ b/src/runtime/javascript/jspgf.js @@ -4,13 +4,12 @@ async function mkAPI() { - var asm = null; + const sizeof_GuMapItor = 4; + const offsetof_GuMapItor_fn = 0; - function createExportWrapper(name) { - return function() { - return asm[name].apply(null, arguments); - }; - } + var asm = null; + var wasmTable = null; + var freeTableIndexes = []; function setErrNo(value) { HEAP32[asm.__errno_location() >> 2] = value; @@ -133,6 +132,117 @@ async function mkAPI() { } }; + // Wraps a JS function as a wasm function with a given signature. + function convertJsFunctionToWasm(func, sig) { + + // If the type reflection proposal is available, use the new + // "WebAssembly.Function" constructor. + // Otherwise, construct a minimal wasm module importing the JS function and + // re-exporting it. + if (typeof WebAssembly.Function == "function") { + var typeNames = { + 'i': 'i32', + 'j': 'i64', + 'f': 'f32', + 'd': 'f64' + }; + var type = { + parameters: [], + results: sig[0] == 'v' ? [] : [typeNames[sig[0]]] + }; + for (var i = 1; i < sig.length; ++i) { + type.parameters.push(typeNames[sig[i]]); + } + return new WebAssembly.Function(type, func); + } + + // The module is static, with the exception of the type section, which is + // generated based on the signature passed in. + var typeSection = [ + 0x01, // id: section, + 0x00, // length: 0 (placeholder) + 0x01, // count: 1 + 0x60, // form: func + ]; + var sigRet = sig.slice(0, 1); + var sigParam = sig.slice(1); + var typeCodes = { + 'i': 0x7f, // i32 + 'j': 0x7e, // i64 + 'f': 0x7d, // f32 + 'd': 0x7c, // f64 + }; + + // Parameters, length + signatures + typeSection.push(sigParam.length); + for (var i = 0; i < sigParam.length; ++i) { + typeSection.push(typeCodes[sigParam[i]]); + } + + // Return values, length + signatures + // With no multi-return in MVP, either 0 (void) or 1 (anything else) + if (sigRet == 'v') { + typeSection.push(0x00); + } else { + typeSection = typeSection.concat([0x01, typeCodes[sigRet]]); + } + + // Write the overall length of the type section back into the section header + // (excepting the 2 bytes for the section id and length) + typeSection[1] = typeSection.length - 2; + + // Rest of the module is static + var bytes = new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, // magic ("\0asm") + 0x01, 0x00, 0x00, 0x00, // version: 1 + ].concat(typeSection, [ + 0x02, 0x07, // import section + // (import "e" "f" (func 0 (type 0))) + 0x01, 0x01, 0x65, 0x01, 0x66, 0x00, 0x00, + 0x07, 0x05, // export section + // (export "f" (func 0 (type 0))) + 0x01, 0x01, 0x66, 0x00, 0x00, + ])); + + // We can compile this wasm module synchronously because it is very small. + // This accepts an import (at "e.f"), that it reroutes to an export (at "f") + var module = new WebAssembly.Module(bytes); + var instance = new WebAssembly.Instance(module, { + 'e': {'f': func} + }); + var wrappedFunc = instance.exports['f']; + return wrappedFunc; + } + + function addFunction(func, sig) { + func = convertJsFunctionToWasm(func, sig); + + let index; + + // Reuse a free index if there is one, otherwise grow. + if (freeTableIndexes.length) { + index = freeTableIndexes.pop(); + } else { + // Grow the table + try { + wasmTable.grow(1); + } catch (err) { + if (!(err instanceof RangeError)) { + throw err; + } + throw 'Unable to grow wasm table. Set ALLOW_TABLE_GROWTH.'; + } + index = wasmTable.length - 1; + } + + wasmTable.set(index, func); + return index; + } + + function removeFunction(index) { + freeTableIndexes.push(index); + } + const response = await fetch("pgf.wasm", { credentials: 'same-origin' }); const info = { @@ -148,6 +258,7 @@ async function mkAPI() { const result = await WebAssembly.instantiateStreaming(response, info); asm = result["instance"].exports; + wasmTable = asm['__indirect_function_table']; const buf = asm['memory'].buffer; const HEAP8 = new Int8Array(buf); const HEAP16 = new Int16Array(buf); @@ -292,16 +403,22 @@ async function mkAPI() { asm.gu_pool_free(pool); }); - function PGF(pgf,pool) { - this.pgf = pgf; - this.pool = pool; + function PGF(pgfPtr,pool) { + this.pgfPtr = pgfPtr; + this.pool = pool; + this.languages = {}; registry.register(this,pool); } PGF.prototype.abstractName = function() { - const namePtr = asm.pgf_abstract_name(this.pgf); + const namePtr = asm.pgf_abstract_name(this.pgfPtr); return UTF8ToString(namePtr); } + function Concr(pgf,name) { + this.pgf = pgf; + this.name = name; + } + async function readPGF(pgfURL) { const response = await fetch(pgfURL); urlData[pgfURL] = new Int8Array(await response.arrayBuffer()); @@ -311,14 +428,29 @@ async function mkAPI() { const tmp_pool = asm.gu_new_pool(); const err = asm.gu_new_exn(tmp_pool); const strPtr = allocateUTF8(tmp_pool,pgfURL); - const pgf = asm.pgf_read(strPtr,pool,err); + const pgfPtr = asm.pgf_read(strPtr,pool,err); if (checkError(err)) { asm.gu_pool_free(tmp_pool); throw new Error('Cannot read PGF'); } + const pgf = new PGF(pgfPtr,pool); + + const itor = asm.gu_malloc(tmp_pool,sizeof_GuMapItor); + const fn = + addFunction( + (itor,namePtr,ptr,err) => { + const name = UTF8ToString(namePtr); + pgf.languages[name] = new Concr(pgf,name); + }, + "viiii" + ); + HEAP32[(itor+offsetof_GuMapItor_fn) >> 2] = fn; + asm.pgf_iter_languages(pgfPtr,itor,err); + removeFunction(fn); + asm.gu_pool_free(tmp_pool); - return new PGF(pgf,pool); + return pgf; } function Expr(expr,pool) {