UNPKG

js-slang

Version:

Javascript-based implementations of Source, written in Typescript

180 lines 6.39 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.assemble = void 0; const buffer_1 = require("../utils/buffer"); const opcodes_1 = require("./opcodes"); const SVM_MAGIC = 0x5005acad; const MAJOR_VER = 0; const MINOR_VER = 0; let UTF8_ENCODER; function writeHeader(b, entrypoint, constantCount) { b.cursor = 0; b.putU(32, SVM_MAGIC); b.putU(16, MAJOR_VER); b.putU(16, MINOR_VER); b.putU(32, entrypoint); b.putU(32, constantCount); } function writeStringConstant(b, s) { if (UTF8_ENCODER === undefined) { UTF8_ENCODER = new TextEncoder(); } const sBytes = UTF8_ENCODER.encode(s); b.align(4); b.putU(16, 1); b.putU(32, sBytes.byteLength + 1); b.putA(sBytes); b.putU(8, 0); } function serialiseFunction(f) { const [stackSize, envSize, numArgs, code] = f; const holes = []; const b = new buffer_1.default(); b.putU(8, stackSize); b.putU(8, envSize); b.putU(8, numArgs); b.putU(8, 0); // padding const instrOffsets = code .map(i => (0, opcodes_1.getInstructionSize)(i[0])) .reduce((ss, s) => (ss.push(ss[ss.length - 1] + s), ss), [0]); for (const [instr, index] of code.map((i1, i2) => [i1, i2])) { if (instr[0] < 0 || instr[0] > opcodes_1.OPCODE_MAX) { throw new Error(`Invalid opcode ${instr[0].toString()}`); } const opcode = instr[0]; b.putU(8, opcode); switch (opcode) { case opcodes_1.default.LDCI: case opcodes_1.default.LGCI: if (!Number.isInteger(instr[1])) { throw new Error(`Non-integral operand to LDCI/LDGI: ${instr[1]} (this is a compiler bug)`); } b.putI(32, instr[1]); break; case opcodes_1.default.LDCF32: case opcodes_1.default.LGCF32: b.putF(32, instr[1]); break; case opcodes_1.default.LDCF64: case opcodes_1.default.LGCF64: b.putF(64, instr[1]); break; case opcodes_1.default.LGCS: holes.push({ offset: b.cursor, referent: ['string', instr[1]] }); b.putU(32, 0); break; case opcodes_1.default.NEWC: holes.push({ offset: b.cursor, referent: ['function', instr[1][0]] }); b.putU(32, 0); break; case opcodes_1.default.LDLG: case opcodes_1.default.LDLF: case opcodes_1.default.LDLB: case opcodes_1.default.STLG: case opcodes_1.default.STLF: case opcodes_1.default.STLB: case opcodes_1.default.CALL: case opcodes_1.default.CALLT: case opcodes_1.default.NEWENV: case opcodes_1.default.NEWCP: case opcodes_1.default.NEWCV: b.putU(8, instr[1]); break; case opcodes_1.default.LDPG: case opcodes_1.default.LDPF: case opcodes_1.default.LDPB: case opcodes_1.default.STPG: case opcodes_1.default.STPF: case opcodes_1.default.STPB: case opcodes_1.default.CALLP: case opcodes_1.default.CALLTP: case opcodes_1.default.CALLV: case opcodes_1.default.CALLTV: b.putU(8, instr[1]); b.putU(8, instr[2]); break; case opcodes_1.default.BRF: case opcodes_1.default.BRT: case opcodes_1.default.BR: const offset = instrOffsets[index + instr[1]] - instrOffsets[index + 1]; b.putI(32, offset); break; case opcodes_1.default.JMP: throw new Error('JMP assembling not implemented'); } } const binary = b.asArray(); if (binary.byteLength - 4 !== instrOffsets[instrOffsets.length - 1]) { throw new Error(`Assembler bug: calculated function length ${instrOffsets[instrOffsets.length - 1]} is different from actual length ${binary.byteLength - 4}`); } return { binary: b.asArray(), holes, finalOffset: null }; } function assemble(p) { const [entrypointIndex, jsonFns] = p; // serialise all the functions const imFns = jsonFns.map(fn => serialiseFunction(fn)); // collect all string constants const uniqueStrings = [ ...new Set([].concat(...imFns.map(fn => fn.holes .filter(hole => hole.referent[0] === 'string') .map(hole => hole.referent[1])))) ]; const bin = new buffer_1.default(); // skip header for now bin.cursor = 0x10; // write all the strings, and store their positions const stringMap = new Map(); for (const str of uniqueStrings) { bin.align(4); stringMap.set(str, bin.cursor); writeStringConstant(bin, str); } // layout the functions, but don't actually write them yet const fnStartOffset = bin.cursor; for (const fn of imFns) { bin.align(4); fn.finalOffset = bin.cursor; bin.cursor += fn.binary.byteLength; } // now fill in the holes for (const fn of imFns) { const view = new DataView(fn.binary.buffer); for (const hole of fn.holes) { let offset; if (hole.referent[0] === 'string') { offset = stringMap.get(hole.referent[1]); } else { offset = imFns[hole.referent[1]].finalOffset; } if (!offset) { throw new Error(`Assembler bug: missing string/function: ${JSON.stringify(hole)}`); } view.setUint32(hole.offset, offset, true); } } // now we write the functions bin.cursor = fnStartOffset; for (const fn of imFns) { bin.align(4); if (bin.cursor !== fn.finalOffset) { throw new Error('Assembler bug: function offset changed'); } bin.putA(fn.binary); } bin.cursor = 0; writeHeader(bin, imFns[entrypointIndex].finalOffset, uniqueStrings.length); return bin.asArray(); } exports.assemble = assemble; //# sourceMappingURL=svml-assembler.js.map