js-slang
Version:
Javascript-based implementations of Source, written in Typescript
896 lines • 37.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.compileToIns = exports.compilePreludeToIns = exports.compileForConcurrent = void 0;
const constants_1 = require("../constants");
const errors_1 = require("../errors/errors");
const parser_1 = require("../parser/parser");
const vm_prelude_1 = require("../stdlib/vm.prelude");
const create = require("../utils/ast/astCreator");
const walkers_1 = require("../utils/walkers");
const opcodes_1 = require("./opcodes");
const VALID_UNARY_OPERATORS = new Map([
['!', opcodes_1.default.NOTG],
['-', opcodes_1.default.NEGG]
]);
const VALID_BINARY_OPERATORS = new Map([
['+', opcodes_1.default.ADDG],
['-', opcodes_1.default.SUBG],
['*', opcodes_1.default.MULG],
['/', opcodes_1.default.DIVG],
['%', opcodes_1.default.MODG],
['<', opcodes_1.default.LTG],
['>', opcodes_1.default.GTG],
['<=', opcodes_1.default.LEG],
['>=', opcodes_1.default.GEG],
['===', opcodes_1.default.EQG],
['!==', opcodes_1.default.NEQG]
]);
// Array of function headers in the compiled program
let SVMFunctions = [];
function updateFunction(index, stackSize, ins) {
const f = SVMFunctions[index];
f[0] = stackSize;
f[3] = ins;
}
// Individual function's machine code
let functionCode = [];
// three insert functions (nullary, unary, binary)
function addNullaryInstruction(opCode) {
const ins = [opCode];
functionCode.push(ins);
}
function addUnaryInstruction(opCode, arg1) {
const ins = [opCode, arg1];
functionCode.push(ins);
}
function addBinaryInstruction(opCode, arg1, arg2) {
const ins = [opCode, arg1, arg2];
functionCode.push(ins);
}
// toCompile stack keeps track of remaining compiler work:
// these are function bodies that still need to be compiled
let toCompile = [];
function popToCompile() {
const next = toCompile.pop();
if (!next) {
throw Error('Unable to compile');
}
return next;
}
function pushToCompile(task) {
toCompile.push(task);
}
// to compile a function body, we need an index table
// to get the environment indices for each name
// (parameters, globals and locals)
// Each compile function returns the max operand stack
// size needed for running the code. When compilation of
// a function body is done, the function continueToCompile
// writes the max operand stack size and the address of the
// function body to the given addresses.
// must ensure body passed in is something that has an array of nodes
// Program or BlockStatement
function makeToCompileTask(body, functionAddress, indexTable) {
return [body, functionAddress, indexTable];
}
function toCompileTaskBody(toCompileTask) {
return toCompileTask[0];
}
function toCompileTaskFunctionAddress(toCompileTask) {
return toCompileTask[1];
}
function toCompileTaskIndexTable(toCompileTask) {
return toCompileTask[2];
}
// indexTable keeps track of environment addresses
// assigned to names
function makeEmptyIndexTable() {
return [];
}
function makeIndexTableWithPrimitivesAndInternals(vmInternalFunctions) {
const names = new Map();
for (let i = 0; i < vm_prelude_1.PRIMITIVE_FUNCTION_NAMES.length; i++) {
const name = vm_prelude_1.PRIMITIVE_FUNCTION_NAMES[i];
names.set(name, { index: i, isVar: false, type: 'primitive' });
}
if (vmInternalFunctions) {
for (let i = 0; i < vmInternalFunctions.length; i++) {
const name = vmInternalFunctions[i];
names.set(name, { index: i, isVar: false, type: 'internal' });
}
}
return extendIndexTable(makeEmptyIndexTable(), names);
}
function extendIndexTable(indexTable, names) {
return indexTable.concat([names]);
}
function indexOf(indexTable, node) {
const name = node.name;
for (let i = indexTable.length - 1; i >= 0; i--) {
if (indexTable[i].has(name)) {
const envLevel = indexTable.length - 1 - i;
const { index, isVar, type } = indexTable[i].get(name);
return { envLevel, index, isVar, type };
}
}
throw new errors_1.UndefinedVariable(name, node);
}
// a small complication: the toplevel function
// needs to return the value of the last statement
let toplevel = true;
const toplevelReturnNodes = new Set([
'Literal',
'UnaryExpression',
'BinaryExpression',
'CallExpression',
'Identifier',
'ArrayExpression',
'LogicalExpression',
'MemberExpression',
'AssignmentExpression',
'ArrowFunctionExpression',
'IfStatement',
'VariableDeclaration'
]);
function continueToCompile() {
while (toCompile.length !== 0) {
const nextToCompile = popToCompile();
const functionAddress = toCompileTaskFunctionAddress(nextToCompile);
const indexTable = toCompileTaskIndexTable(nextToCompile);
const body = toCompileTaskBody(nextToCompile);
body.isFunctionBlock = true;
const { maxStackSize } = compile(body, indexTable, true);
const functionIndex = functionAddress[0];
updateFunction(functionIndex, maxStackSize, functionCode);
functionCode = [];
toplevel = false;
}
}
// extracts all name declarations within a function or block,
// renaming every declaration if rename is true.
// if rename is true, rename to name_line_col and recursively rename identifiers in ast if no same scope declaration
// (check for variable, function declaration in each block. Check for params in each function call)
// for any duplicates, rename recursively within scope
// recurse for any blocks with rename = true
function extractAndRenameNames(baseNode, names, rename = true) {
// get all declared names of current scope and keep track of names to rename
const namesToRename = new Map();
for (const stmt of baseNode.body) {
if (stmt.type === 'VariableDeclaration') {
const node = stmt;
let name = node.declarations[0].id.name;
if (rename) {
const loc = (node.loc ?? constants_1.UNKNOWN_LOCATION).start;
const oldName = name;
do {
name = `${name}-${loc.line}-${loc.column}`;
} while (names.has(name));
namesToRename.set(oldName, name);
}
const isVar = node.kind === 'let';
const index = names.size;
names.set(name, { index, isVar });
}
else if (stmt.type === 'FunctionDeclaration') {
const node = stmt;
if (node.id === null) {
throw new Error('Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.');
}
let name = node.id.name;
if (rename) {
const loc = (node.loc ?? constants_1.UNKNOWN_LOCATION).start;
const oldName = name;
do {
name = `${name}-${loc.line}-${loc.column}`;
} while (names.has(name));
namesToRename.set(oldName, name);
}
const isVar = false;
const index = names.size;
names.set(name, { index, isVar });
}
}
// rename all references within blocks if nested block does not redeclare name
renameVariables(baseNode, namesToRename);
// recurse for blocks. Need to manually add all cases to recurse
for (const stmt of baseNode.body) {
if (stmt.type === 'BlockStatement') {
const node = stmt;
extractAndRenameNames(node, names, true);
}
if (stmt.type === 'IfStatement') {
let nextAlt = stmt;
while (nextAlt.type === 'IfStatement') {
// if else if...
const { consequent, alternate } = nextAlt;
extractAndRenameNames(consequent, names, true);
// Source spec must have alternate
nextAlt = alternate;
}
extractAndRenameNames(nextAlt, names, true);
}
if (stmt.type === 'WhileStatement') {
extractAndRenameNames(stmt.body, names, true);
}
}
return names;
}
// rename variables if nested scope does not redeclare names
// redeclaration occurs on VariableDeclaration and FunctionDeclaration
function renameVariables(baseNode, namesToRename) {
if (namesToRename.size === 0)
return;
let baseScope = true;
function recurseBlock(node, inactive, c) {
// get names in current scope
const locals = getLocalsInScope(node);
// add names to state
const oldActive = new Set(inactive);
for (const name of locals) {
inactive.add(name);
}
// recurse
for (const stmt of node.body) {
c(stmt, inactive);
}
// reset state to normal
for (const name of locals) {
if (oldActive.has(name)) {
continue;
}
inactive.delete(name); // delete if not in old scope
}
}
(0, walkers_1.recursive)(baseNode, new Set(), {
VariablePattern(node, inactive, _c) {
// for declarations
const name = node.name;
if (inactive.has(name)) {
return;
}
if (namesToRename.has(name)) {
node.name = namesToRename.get(name);
}
},
Identifier(node, inactive, _c) {
// for lone references
const name = node.name;
if (inactive.has(name)) {
return;
}
if (namesToRename.has(name)) {
node.name = namesToRename.get(name);
}
},
BlockStatement(node, inactive, c) {
if (baseScope) {
baseScope = false;
for (const stmt of node.body) {
c(stmt, inactive);
}
}
else {
recurseBlock(node, inactive, c);
}
},
IfStatement(node, inactive, c) {
c(node.test, inactive);
let nextAlt = node;
while (nextAlt.type === 'IfStatement') {
const { consequent, alternate } = nextAlt;
recurseBlock(consequent, inactive, c);
c(nextAlt.test, inactive);
nextAlt = alternate;
}
recurseBlock(nextAlt, inactive, c);
},
Function(node, inactive, c) {
if (node.type === 'FunctionDeclaration') {
if (node.id === null) {
throw new Error('Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.');
}
c(node.id, inactive);
}
const oldActive = new Set(inactive);
const locals = new Set();
for (const param of node.params) {
const id = param;
locals.add(id.name);
}
for (const name of locals) {
inactive.add(name);
}
c(node.body, inactive, node.type === 'ArrowFunctionExpression' && node.expression ? 'Expression' : 'Statement');
for (const name of locals) {
if (oldActive.has(name)) {
continue;
}
inactive.delete(name); // delete if not in old scope
}
},
WhileStatement(node, inactive, c) {
c(node.test, inactive);
recurseBlock(node.body, inactive, c);
}
});
}
function getLocalsInScope(node) {
const locals = new Set();
for (const stmt of node.body) {
if (stmt.type === 'VariableDeclaration') {
const name = stmt.declarations[0].id.name;
locals.add(name);
}
else if (stmt.type === 'FunctionDeclaration') {
if (stmt.id === null) {
throw new Error('Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.');
}
const name = stmt.id.name;
locals.add(name);
}
}
return locals;
}
function compileArguments(exprs, indexTable) {
let maxStackSize = 0;
for (let i = 0; i < exprs.length; i++) {
const { maxStackSize: curExpSize } = compile(exprs[i], indexTable, false);
maxStackSize = Math.max(i + curExpSize, maxStackSize);
}
return maxStackSize;
}
// tuple of loop type, breaks, continues and continueDestinationIndex
// break and continue need to know how much to offset for the branch
// instruction. When compiling the individual instruction, that info
// is not available, so need to keep track of the break and continue
// instruction's index to update the offset when the compiler finishes
// compiling the loop. We need to keep track of continue destination as
// a for loop needs to know where the assignment instructions are.
// This works because of the way a for loop is transformed to a while loop.
// If the loop is a for loop, the last statement in the while loop block
// is always the assignment expression
let loopTracker = [];
const LOOP_TYPE = 0;
const BREAK_INDEX = 1;
const CONT_INDEX = 2;
const CONT_DEST_INDEX = 3;
// used to compile block bodies
function compileStatements(node, indexTable, insertFlag) {
const statements = node.body;
let maxStackSize = 0;
for (let i = 0; i < statements.length; i++) {
if (node.isLoopBlock &&
i === statements.length - 1 &&
loopTracker[loopTracker.length - 1][LOOP_TYPE] === 'for') {
loopTracker[loopTracker.length - 1][CONT_DEST_INDEX] = functionCode.length;
}
const { maxStackSize: curExprSize } = compile(statements[i], indexTable, i === statements.length - 1 ? insertFlag : false);
if (i !== statements.length - 1 || node.isLoopBlock) {
addNullaryInstruction(opcodes_1.default.POPG);
}
maxStackSize = Math.max(maxStackSize, curExprSize);
}
if (statements.length === 0 && !node.isLoopBlock) {
addNullaryInstruction(opcodes_1.default.LGCU);
if (insertFlag || node.isFunctionBlock) {
addNullaryInstruction(opcodes_1.default.RETG);
}
maxStackSize++;
}
return { maxStackSize, insertFlag: false };
}
// each compiler should return a maxStackSize
const compilers = {
// wrapper
Program(node, indexTable, insertFlag) {
node = node;
return compileStatements(node, indexTable, insertFlag);
},
// wrapper
BlockStatement(node, indexTable, insertFlag) {
node = node;
return compileStatements(node, indexTable, insertFlag);
},
// wrapper
ExpressionStatement(node, indexTable, insertFlag) {
node = node;
return compile(node.expression, indexTable, insertFlag);
},
IfStatement(node, indexTable, insertFlag) {
const { test, consequent, alternate } = node;
const { maxStackSize: m1 } = compile(test, indexTable, false);
addUnaryInstruction(opcodes_1.default.BRF, NaN);
const BRFIndex = functionCode.length - 1;
const { maxStackSize: m2 } = compile(consequent, indexTable, false);
addUnaryInstruction(opcodes_1.default.BR, NaN);
const BRIndex = functionCode.length - 1;
functionCode[BRFIndex][1] = functionCode.length - BRFIndex;
// source spec: must have alternate
const { maxStackSize: m3 } = compile(alternate, indexTable, false);
functionCode[BRIndex][1] = functionCode.length - BRIndex;
const maxStackSize = Math.max(m1, m2, m3);
return { maxStackSize, insertFlag };
},
// wrapper, compile as an arrow function expression instead
FunctionDeclaration(node, indexTable, insertFlag) {
if (node.id === null) {
throw new Error('Encountered a FunctionDeclaration node without an identifier. This should have been caught when parsing.');
}
return compile(create.constantDeclaration(node.id.name, create.arrowFunctionExpression(node.params, node.body)), indexTable, insertFlag);
},
VariableDeclaration(node, indexTable, insertFlag) {
// only supports const / let
node = node;
if (node.kind === 'const' || node.kind === 'let') {
// assumes left side can only be name
// source spec: only 1 declaration at a time
const id = node.declarations[0].id;
const { envLevel, index } = indexOf(indexTable, id);
const { maxStackSize } = compile(node.declarations[0].init, indexTable, false);
if (envLevel === 0) {
addUnaryInstruction(opcodes_1.default.STLG, index);
}
else {
// this should never happen
addBinaryInstruction(opcodes_1.default.STPG, index, envLevel);
}
addNullaryInstruction(opcodes_1.default.LGCU);
return { maxStackSize, insertFlag };
}
throw Error('Invalid declaration');
},
// handled by insertFlag in compile function
ReturnStatement(node, indexTable, _insertFlag) {
node = node;
if (loopTracker.length > 0) {
throw Error('return not allowed in loops');
}
const { maxStackSize } = compile(node.argument, indexTable, false, true);
return { maxStackSize, insertFlag: true };
},
// Three types of calls, normal function calls declared by the Source program,
// primitive function calls that are predefined, and internal calls.
// We differentiate them with callType.
CallExpression(node, indexTable, insertFlag, isTailCallPosition = false) {
node = node;
let maxStackOperator = 0;
let callType = 'normal';
let callValue = NaN;
if (node.callee.type === 'Identifier') {
const callee = node.callee;
const { envLevel, index, type } = indexOf(indexTable, callee);
if (type === 'primitive' || type === 'internal') {
callType = type;
callValue = index;
}
else if (envLevel === 0) {
addUnaryInstruction(opcodes_1.default.LDLG, index);
}
else {
addBinaryInstruction(opcodes_1.default.LDPG, index, envLevel);
}
}
else {
;
({ maxStackSize: maxStackOperator } = compile(node.callee, indexTable, false));
}
let maxStackOperands = compileArguments(node.arguments, indexTable);
if (callType === 'primitive') {
addBinaryInstruction(isTailCallPosition ? opcodes_1.default.CALLTP : opcodes_1.default.CALLP, callValue, node.arguments.length);
}
else if (callType === 'internal') {
addBinaryInstruction(isTailCallPosition ? opcodes_1.default.CALLTV : opcodes_1.default.CALLV, callValue, node.arguments.length);
}
else {
// normal call. only normal function calls have the function on the stack
addUnaryInstruction(isTailCallPosition ? opcodes_1.default.CALLT : opcodes_1.default.CALL, node.arguments.length);
maxStackOperands++;
}
// need at least 1 stack slot for the return value!
return { maxStackSize: Math.max(maxStackOperator, maxStackOperands, 1), insertFlag };
},
UnaryExpression(node, indexTable, insertFlag) {
node = node;
if (VALID_UNARY_OPERATORS.has(node.operator)) {
const opCode = VALID_UNARY_OPERATORS.get(node.operator);
const { maxStackSize } = compile(node.argument, indexTable, false);
addNullaryInstruction(opCode);
return { maxStackSize, insertFlag };
}
throw Error('Unsupported operation');
},
BinaryExpression(node, indexTable, insertFlag) {
node = node;
if (VALID_BINARY_OPERATORS.has(node.operator)) {
const opCode = VALID_BINARY_OPERATORS.get(node.operator);
const { maxStackSize: m1 } = compile(node.left, indexTable, false);
const { maxStackSize: m2 } = compile(node.right, indexTable, false);
addNullaryInstruction(opCode);
return { maxStackSize: Math.max(m1, 1 + m2), insertFlag };
}
throw Error('Unsupported operation');
},
// convert logical expressions to conditional expressions
LogicalExpression(node, indexTable, insertFlag, isTailCallPosition = false) {
node = node;
if (node.operator === '&&') {
const { maxStackSize } = compile(create.conditionalExpression(node.left, node.right, create.literal(false)), indexTable, false, isTailCallPosition);
return { maxStackSize, insertFlag };
}
else if (node.operator === '||') {
const { maxStackSize } = compile(create.conditionalExpression(node.left, create.literal(true), node.right), indexTable, false, isTailCallPosition);
return { maxStackSize, insertFlag };
}
throw Error('Unsupported operation');
},
ConditionalExpression(node, indexTable, insertFlag, isTailCallPosition = false) {
const { test, consequent, alternate } = node;
const { maxStackSize: m1 } = compile(test, indexTable, false);
addUnaryInstruction(opcodes_1.default.BRF, NaN);
const BRFIndex = functionCode.length - 1;
const { maxStackSize: m2 } = compile(consequent, indexTable, insertFlag, isTailCallPosition);
let BRIndex = NaN;
if (!insertFlag) {
addUnaryInstruction(opcodes_1.default.BR, NaN);
BRIndex = functionCode.length - 1;
}
functionCode[BRFIndex][1] = functionCode.length - BRFIndex;
const { maxStackSize: m3 } = compile(alternate, indexTable, insertFlag, isTailCallPosition);
if (!insertFlag) {
functionCode[BRIndex][1] = functionCode.length - BRIndex;
}
const maxStackSize = Math.max(m1, m2, m3);
return { maxStackSize, insertFlag: false };
},
ArrowFunctionExpression(node, indexTable, insertFlag) {
node = node;
// node.body is either a block statement or a single node to return
const bodyNode = node.body.type === 'BlockStatement'
? node.body
: create.blockStatement([create.returnStatement(node.body)]);
const names = new Map();
for (let param of node.params) {
param = param;
const index = names.size;
names.set(param.name, { index, isVar: true });
}
extractAndRenameNames(bodyNode, names);
const extendedIndexTable = extendIndexTable(indexTable, names);
const newSVMFunction = [NaN, names.size, node.params.length, []];
const functionIndex = SVMFunctions.length;
SVMFunctions.push(newSVMFunction);
pushToCompile(makeToCompileTask(bodyNode, [functionIndex], extendedIndexTable));
addUnaryInstruction(opcodes_1.default.NEWC, [functionIndex]);
return { maxStackSize: 1, insertFlag };
},
Identifier(node, indexTable, insertFlag) {
node = node;
let envLevel;
let index;
let type;
try {
;
({ envLevel, index, type } = indexOf(indexTable, node));
if (type === 'primitive') {
addUnaryInstruction(opcodes_1.default.NEWCP, index);
}
else if (type === 'internal') {
addUnaryInstruction(opcodes_1.default.NEWCV, index);
}
else if (envLevel === 0) {
addUnaryInstruction(opcodes_1.default.LDLG, index);
}
else {
addBinaryInstruction(opcodes_1.default.LDPG, index, envLevel);
}
}
catch (error) {
// only possible to have UndefinedVariable error
const matches = vm_prelude_1.CONSTANT_PRIMITIVES.filter(f => f[0] === error.name);
if (matches.length === 0) {
throw error;
}
if (typeof matches[0][1] === 'number') {
// for NaN and Infinity
addUnaryInstruction(opcodes_1.default.LGCF32, matches[0][1]);
}
else if (matches[0][1] === undefined) {
addNullaryInstruction(opcodes_1.default.LGCU);
}
else {
throw Error('Unknown primitive constant');
}
}
return { maxStackSize: 1, insertFlag };
},
// string, boolean, number or null
Literal(node, indexTable, insertFlag) {
node = node;
const value = node.value;
if (value === null) {
addNullaryInstruction(opcodes_1.default.LGCN);
}
else {
switch (typeof value) {
case 'boolean':
if (value) {
addNullaryInstruction(opcodes_1.default.LGCB1);
}
else {
addNullaryInstruction(opcodes_1.default.LGCB0);
}
break;
case 'number': // need to adjust depending on target
// LGCI takes a signed 32-bit integer operand (hence the range)
if (Number.isInteger(value) && -2147483648 <= value && value <= 2147483647) {
addUnaryInstruction(opcodes_1.default.LGCI, value);
}
else {
addUnaryInstruction(opcodes_1.default.LGCF64, value);
}
break;
case 'string':
addUnaryInstruction(opcodes_1.default.LGCS, value);
break;
default:
throw Error('Unsupported literal');
}
}
return { maxStackSize: 1, insertFlag };
},
// array declarations
ArrayExpression(node, indexTable, insertFlag) {
node = node;
addNullaryInstruction(opcodes_1.default.NEWA);
const elements = node.elements;
let maxStackSize = 1;
for (let i = 0; i < elements.length; i++) {
// special case when element wasnt specified
// i.e. [,]. Treat as undefined element
if (elements[i] === null) {
continue;
}
// keep the array in the stack
addNullaryInstruction(opcodes_1.default.DUP);
addUnaryInstruction(opcodes_1.default.LGCI, i);
const { maxStackSize: m1 } = compile(elements[i], indexTable, false);
addNullaryInstruction(opcodes_1.default.STAG);
maxStackSize = Math.max(1 + 2 + m1, maxStackSize);
}
return { maxStackSize, insertFlag };
},
AssignmentExpression(node, indexTable, insertFlag) {
node = node;
if (node.left.type === 'Identifier') {
const { envLevel, index, isVar } = indexOf(indexTable, node.left);
if (!isVar) {
throw new errors_1.ConstAssignment(node.left, node.left.name);
}
const { maxStackSize } = compile(node.right, indexTable, false);
if (envLevel === 0) {
addUnaryInstruction(opcodes_1.default.STLG, index);
}
else {
addBinaryInstruction(opcodes_1.default.STPG, index, envLevel);
}
addNullaryInstruction(opcodes_1.default.LGCU);
return { maxStackSize, insertFlag };
}
else if (node.left.type === 'MemberExpression' && node.left.computed === true) {
// case for array member assignment
const { maxStackSize: m1 } = compile(node.left.object, indexTable, false);
const { maxStackSize: m2 } = compile(node.left.property, indexTable, false);
const { maxStackSize: m3 } = compile(node.right, indexTable, false);
addNullaryInstruction(opcodes_1.default.STAG);
addNullaryInstruction(opcodes_1.default.LGCU);
return { maxStackSize: Math.max(m1, 1 + m2, 2 + m3), insertFlag };
}
// property assignments are not supported
throw Error('Invalid Assignment');
},
ForStatement(_node, _indexTable, _insertFlag) {
throw Error('Unsupported operation');
},
// Loops need to have their own environment due to closures
WhileStatement(node, indexTable, insertFlag) {
node = node;
const isFor = node.isFor;
const condIndex = functionCode.length;
const { maxStackSize: m1 } = compile(node.test, indexTable, false);
addUnaryInstruction(opcodes_1.default.BRF, NaN);
const BRFIndex = functionCode.length - 1;
loopTracker.push([isFor ? 'for' : 'while', [], [], NaN]);
// Add environment for loop and run in new environment
const locals = extractAndRenameNames(node.body, new Map());
addUnaryInstruction(opcodes_1.default.NEWENV, locals.size);
const extendedIndexTable = extendIndexTable(indexTable, locals);
const body = node.body;
body.isLoopBlock = true;
const { maxStackSize: m2 } = compile(body, extendedIndexTable, false);
if (!isFor) {
// for while loops, the `continue` statement should branch here
loopTracker[loopTracker.length - 1][CONT_DEST_INDEX] = functionCode.length;
}
addNullaryInstruction(opcodes_1.default.POPENV);
const endLoopIndex = functionCode.length;
addUnaryInstruction(opcodes_1.default.BR, condIndex - endLoopIndex);
functionCode[BRFIndex][1] = functionCode.length - BRFIndex;
// update BR instructions within loop
const curLoop = loopTracker.pop();
for (const b of curLoop[BREAK_INDEX]) {
functionCode[b][1] = functionCode.length - b;
}
for (const c of curLoop[CONT_INDEX]) {
functionCode[c][1] = curLoop[CONT_DEST_INDEX] - c;
}
addNullaryInstruction(opcodes_1.default.LGCU);
return { maxStackSize: Math.max(m1, m2), insertFlag };
},
BreakStatement(node, indexTable, insertFlag) {
// keep track of break instruction
addNullaryInstruction(opcodes_1.default.POPENV);
loopTracker[loopTracker.length - 1][BREAK_INDEX].push(functionCode.length);
addUnaryInstruction(opcodes_1.default.BR, NaN);
return { maxStackSize: 0, insertFlag };
},
ContinueStatement(node, indexTable, insertFlag) {
// keep track of continue instruction
// no need to POPENV as continue will go to the end of the while loop
loopTracker[loopTracker.length - 1][CONT_INDEX].push(functionCode.length);
addUnaryInstruction(opcodes_1.default.BR, NaN);
return { maxStackSize: 0, insertFlag };
},
ObjectExpression(_node, _indexTable, _insertFlag) {
throw Error('Unsupported operation');
},
MemberExpression(node, indexTable, insertFlag) {
node = node;
if (node.computed) {
const { maxStackSize: m1 } = compile(node.object, indexTable, false);
const { maxStackSize: m2 } = compile(node.property, indexTable, false);
addNullaryInstruction(opcodes_1.default.LDAG);
return { maxStackSize: Math.max(m1, 1 + m2), insertFlag };
}
// properties are not supported
throw Error('Unsupported operation');
},
Property(_node, _indexTable, _insertFlag) {
throw Error('Unsupported operation');
},
DebuggerStatement(_node, _indexTable, _insertFlag) {
throw Error('Unsupported operation');
}
};
function compile(expr, indexTable, insertFlag, isTailCallPosition = false) {
const compiler = compilers[expr.type];
if (!compiler) {
throw Error('Unsupported operation');
}
const { maxStackSize: temp, insertFlag: newInsertFlag } = compiler(expr, indexTable, insertFlag, isTailCallPosition);
let maxStackSize = temp;
// insertFlag decides whether we need to introduce a RETG instruction. For some functions
// where return is not specified, there is an implicit "return undefined", which we do here.
// Source programs should return the last evaluated statement, which is what toplevel handles.
// TODO: Don't emit an unnecessary RETG after a tail call. (This is harmless, but wastes an instruction.)
// (There are unnecessary RETG for many cases at the top level)
// TODO: Source programs should return last evaluated statement.
if (newInsertFlag) {
if (expr.type === 'ReturnStatement') {
addNullaryInstruction(opcodes_1.default.RETG);
}
else if (toplevel && toplevelReturnNodes.has(expr.type)) {
// conditional expressions already handled
addNullaryInstruction(opcodes_1.default.RETG);
}
else if (expr.type === 'Program' ||
expr.type === 'ExpressionStatement' ||
expr.type === 'BlockStatement' ||
expr.type === 'FunctionDeclaration') {
// do nothing for wrapper nodes
}
else {
maxStackSize += 1;
addNullaryInstruction(opcodes_1.default.LGCU);
addNullaryInstruction(opcodes_1.default.RETG);
}
}
return { maxStackSize, insertFlag: newInsertFlag };
}
function compileForConcurrent(program, context) {
// assume vmPrelude is always a correct program
const prelude = compilePreludeToIns((0, parser_1.parse)(vm_prelude_1.vmPrelude, context));
(0, vm_prelude_1.generatePrimitiveFunctionCode)(prelude);
const vmInternalFunctions = vm_prelude_1.INTERNAL_FUNCTIONS.map(([name]) => name);
return compileToIns(program, prelude, vmInternalFunctions);
}
exports.compileForConcurrent = compileForConcurrent;
function compilePreludeToIns(program) {
// reset variables
SVMFunctions = [];
functionCode = [];
toCompile = [];
loopTracker = [];
toplevel = true;
transformForLoopsToWhileLoops(program);
// don't rename names at the top level, because we need them for linking
const locals = extractAndRenameNames(program, new Map(), false);
const topFunction = [NaN, locals.size, 0, []];
const topFunctionIndex = 0; // GE + # primitive func
SVMFunctions[topFunctionIndex] = topFunction;
const extendedTable = extendIndexTable(makeIndexTableWithPrimitivesAndInternals(), locals);
pushToCompile(makeToCompileTask(program, [topFunctionIndex], extendedTable));
continueToCompile();
return [0, SVMFunctions];
}
exports.compilePreludeToIns = compilePreludeToIns;
function compileToIns(program, prelude, vmInternalFunctions) {
// reset variables
SVMFunctions = [];
functionCode = [];
toCompile = [];
loopTracker = [];
toplevel = true;
transformForLoopsToWhileLoops(program);
insertEmptyElseBlocks(program);
const locals = extractAndRenameNames(program, new Map());
const topFunction = [NaN, locals.size, 0, []];
if (prelude) {
SVMFunctions.push(...prelude[1]);
}
const topFunctionIndex = prelude ? vm_prelude_1.PRIMITIVE_FUNCTION_NAMES.length + 1 : 0; // GE + # primitive func
SVMFunctions[topFunctionIndex] = topFunction;
const extendedTable = extendIndexTable(makeIndexTableWithPrimitivesAndInternals(vmInternalFunctions), locals);
pushToCompile(makeToCompileTask(program, [topFunctionIndex], extendedTable));
continueToCompile();
return [0, SVMFunctions];
}
exports.compileToIns = compileToIns;
// transform according to Source 3 spec. Refer to spec for the way of transformation
function transformForLoopsToWhileLoops(program) {
(0, walkers_1.simple)(program, {
ForStatement(node) {
const { test, body, init, update } = node;
let forLoopBody = body;
// Source spec: init must be present
if (init.type === 'VariableDeclaration') {
const loopVarName = init.declarations[0].id
.name;
// loc is used for renaming. It doesn't matter if we use the same location, as the
// renaming function will notice that they are the same, and rename it further so that
// there aren't any clashes.
const loc = init.loc;
const copyOfLoopVarName = 'copy-of-' + loopVarName;
const innerBlock = create.blockStatement([
create.constantDeclaration(loopVarName, create.identifier(copyOfLoopVarName), loc),
body
]);
forLoopBody = create.blockStatement([
create.constantDeclaration(copyOfLoopVarName, create.identifier(loopVarName), loc),
innerBlock
]);
}
const assignment1 = init && init.type === 'VariableDeclaration'
? init
: create.expressionStatement(init);
const assignment2 = create.expressionStatement(update);
const newLoopBody = create.blockStatement([forLoopBody, assignment2]);
const newLoop = create.whileStatement(newLoopBody, test);
newLoop.isFor = true;
const newBlockBody = [assignment1, newLoop];
node = node;
node.body = newBlockBody;
node.type = 'BlockStatement';
}
});
}
function insertEmptyElseBlocks(program) {
(0, walkers_1.simple)(program, {
IfStatement(node) {
node.alternate ?? (node.alternate = {
type: 'BlockStatement',
body: []
});
}
});
}
//# sourceMappingURL=svml-compiler.js.map