microvium
Version:
A compact, embeddable scripting engine for microcontrollers for executing small scripts written in a subset of JavaScript.
85 lines • 4.24 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.pass3_computeSlotAccessors = void 0;
const utils_1 = require("../../utils");
function pass3_computeSlotAccessors(state) {
/*
The instructions LoadScoped and StoreScoped take a _relative_ index, that in
some sense "overflows" from the current scope into the parent scope. This
function computes the relative indices.
*/
const { model: { references }, } = state;
for (const reference of references.values()) {
reference.access = getAccessForReference(reference);
}
function getAccessForReference(reference) {
(0, utils_1.hardAssert)(!reference.access);
const resolvesTo = reference.resolvesTo;
if (resolvesTo.type === 'FreeVariable') {
return {
type: 'GlobalSlot',
name: resolvesTo.name,
};
}
if (resolvesTo.type === 'RootLevelThis') {
// A `this` reference that hits the root scope without finding a binding must just be represented as `undefined`
return {
type: 'ConstUndefinedAccess'
};
}
// Otherwise, the reference resolves to a binding
const binding = resolvesTo.binding;
(0, utils_1.hardAssert)(binding);
// All bindings must have slots if they are used, and we're in this function
// because the binding is used (it has a reference to it). Note that the
// definition of "used" here is quite conservative. In the current
// implementation, all bindings with a self-reference are considered to be
// "used" since they have a reference to themselves. The only bindings that
// can be "unused" are the parameters
const slot = binding.slot;
if (!slot)
(0, utils_1.unexpected)();
switch (slot.type) {
case 'LocalSlot': return slot;
case 'GlobalSlot': return slot;
case 'ArgumentSlot': return slot;
case 'ModuleImportExportSlot': return slot;
case 'ClosureSlot': {
// Start at the nearest scope and work backwards
let scope = reference.nearestScope;
const targetScope = binding.scope;
let relativeIndex = 0;
// While we're not in the scope containing the variable, move to the parent scope
while (scope !== targetScope) {
if (scope.closureSlots) {
// I don't think there's any case where a scope has closure slots
// but has the same instance count as the parent
(0, utils_1.hardAssert)(!scope.sameInstanceCountAsParent);
relativeIndex += scope.closureSlots.length;
}
// If we're stepping from a function scope to its parent scope, and
// the function closure is not embedded in the parent, then we need
// to step over the 2-slot closure allocation itself to get to the
// parent.
if (scope.type === 'FunctionScope' && !scope.embeddedInParentSlot) {
relativeIndex += 2;
}
// In order for us to hop from the child to the parent function,
// we'll need to have a reference to the parent scope at runtime,
// which means the function we're hopping from must itself be a
// closure.
(0, utils_1.hardAssert)(scope.type !== 'FunctionScope' || scope.functionIsClosure);
scope = scope.parent || (0, utils_1.unexpected)();
}
relativeIndex += slot.index;
return {
type: 'ClosureSlotAccess',
relativeIndex
};
}
default: (0, utils_1.assertUnreachable)(slot);
}
}
}
exports.pass3_computeSlotAccessors = pass3_computeSlotAccessors;
//# sourceMappingURL=pass-3-compute-accessors.js.map