ember-legacy-class-transform
Version:
The default blueprint for ember-cli addons.
323 lines (322 loc) • 10.1 kB
JavaScript
import { Register } from '../opcodes';
import { Scope } from '../environment';
import { Stack, LinkedList, ListSlice, expect, typePos } from '@glimmer/util';
import { combineSlice } from '@glimmer/reference';
import { LabelOpcode, JumpIfNotModifiedOpcode, DidModifyOpcode } from '../compiled/opcodes/vm';
import { ListBlockOpcode, TryOpcode } from './update';
import RenderResult from './render-result';
import { APPEND_OPCODES } from '../opcodes';
export class EvaluationStack {
constructor(stack, fp, sp) {
this.stack = stack;
this.fp = fp;
this.sp = sp;
if (false) {
Object.seal(this);
}
}
static empty() {
return new this([], 0, -1);
}
static restore(snapshot) {
return new this(snapshot.slice(), 0, snapshot.length - 1);
}
isEmpty() {
return this.sp === -1;
}
push(value) {
this.stack[++this.sp] = value;
}
dup(position = this.sp) {
this.push(this.stack[position]);
}
pop(n = 1) {
let top = this.stack[this.sp];
this.sp -= n;
return top;
}
peek() {
return this.stack[this.sp];
}
fromBase(offset) {
return this.stack[this.fp - offset];
}
fromTop(offset) {
return this.stack[this.sp - offset];
}
capture(items) {
let end = this.sp + 1;
let start = end - items;
return this.stack.slice(start, end);
}
reset() {
this.stack.length = 0;
}
toArray() {
return this.stack.slice(this.fp, this.sp + 1);
}
}
export default class VM {
constructor(env, scope, dynamicScope, elementStack) {
this.env = env;
this.elementStack = elementStack;
this.dynamicScopeStack = new Stack();
this.scopeStack = new Stack();
this.updatingOpcodeStack = new Stack();
this.cacheGroups = new Stack();
this.listBlockStack = new Stack();
this.stack = EvaluationStack.empty();
/* Registers */
this.pc = -1;
this.ra = -1;
this.s0 = null;
this.s1 = null;
this.t0 = null;
this.t1 = null;
this.env = env;
this.heap = env.program.heap;
this.constants = env.program.constants;
this.elementStack = elementStack;
this.scopeStack.push(scope);
this.dynamicScopeStack.push(dynamicScope);
}
get fp() {
return this.stack.fp;
}
set fp(fp) {
this.stack.fp = fp;
}
get sp() {
return this.stack.sp;
}
set sp(sp) {
this.stack.sp = sp;
}
// Fetch a value from a register onto the stack
fetch(register) {
this.stack.push(this[Register[register]]);
}
// Load a value from the stack into a register
load(register) {
this[Register[register]] = this.stack.pop();
}
// Fetch a value from a register
fetchValue(register) {
return this[Register[register]];
}
// Load a value into a register
loadValue(register, value) {
this[Register[register]] = value;
}
// Start a new frame and save $ra and $fp on the stack
pushFrame() {
this.stack.push(this.ra);
this.stack.push(this.fp);
this.fp = this.sp - 1;
}
// Restore $ra, $sp and $fp
popFrame() {
this.sp = this.fp - 1;
this.ra = this.stack.fromBase(0);
this.fp = this.stack.fromBase(-1);
}
// Jump to an address in `program`
goto(offset) {
this.pc = typePos(this.pc + offset);
}
// Save $pc into $ra, then jump to a new address in `program` (jal in MIPS)
call(handle) {
let pc = this.heap.getaddr(handle);
this.ra = this.pc;
this.pc = pc;
}
// Put a specific `program` address in $ra
returnTo(offset) {
this.ra = typePos(this.pc + offset);
}
// Return to the `program` address stored in $ra
return() {
this.pc = this.ra;
}
static initial(env, self, dynamicScope, elementStack, program) {
let scope = Scope.root(self, program.symbolTable.symbols.length);
let vm = new VM(env, scope, dynamicScope, elementStack);
vm.pc = vm.heap.getaddr(program.handle);
vm.updatingOpcodeStack.push(new LinkedList());
return vm;
}
capture(args) {
return {
dynamicScope: this.dynamicScope(),
env: this.env,
scope: this.scope(),
stack: this.stack.capture(args)
};
}
beginCacheGroup() {
this.cacheGroups.push(this.updating().tail());
}
commitCacheGroup() {
// JumpIfNotModified(END)
// (head)
// (....)
// (tail)
// DidModify
// END: Noop
let END = new LabelOpcode("END");
let opcodes = this.updating();
let marker = this.cacheGroups.pop();
let head = marker ? opcodes.nextNode(marker) : opcodes.head();
let tail = opcodes.tail();
let tag = combineSlice(new ListSlice(head, tail));
let guard = new JumpIfNotModifiedOpcode(tag, END);
opcodes.insertBefore(guard, head);
opcodes.append(new DidModifyOpcode(guard));
opcodes.append(END);
}
enter(args) {
let updating = new LinkedList();
let state = this.capture(args);
let tracker = this.elements().pushUpdatableBlock();
let tryOpcode = new TryOpcode(this.heap.gethandle(this.pc), state, tracker, updating);
this.didEnter(tryOpcode);
}
iterate(memo, value) {
let stack = this.stack;
stack.push(value);
stack.push(memo);
let state = this.capture(2);
let tracker = this.elements().pushUpdatableBlock();
// let ip = this.ip;
// this.ip = end + 4;
// this.frames.push(ip);
return new TryOpcode(this.heap.gethandle(this.pc), state, tracker, new LinkedList());
}
enterItem(key, opcode) {
this.listBlock().map[key] = opcode;
this.didEnter(opcode);
}
enterList(relativeStart) {
let updating = new LinkedList();
let state = this.capture(0);
let tracker = this.elements().pushBlockList(updating);
let artifacts = this.stack.peek().artifacts;
let start = this.heap.gethandle(typePos(this.pc + relativeStart));
let opcode = new ListBlockOpcode(start, state, tracker, updating, artifacts);
this.listBlockStack.push(opcode);
this.didEnter(opcode);
}
didEnter(opcode) {
this.updateWith(opcode);
this.updatingOpcodeStack.push(opcode.children);
}
exit() {
this.elements().popBlock();
this.updatingOpcodeStack.pop();
let parent = this.updating().tail();
parent.didInitializeChildren();
}
exitList() {
this.exit();
this.listBlockStack.pop();
}
updateWith(opcode) {
this.updating().append(opcode);
}
listBlock() {
return expect(this.listBlockStack.current, 'expected a list block');
}
updating() {
return expect(this.updatingOpcodeStack.current, 'expected updating opcode on the updating opcode stack');
}
elements() {
return this.elementStack;
}
scope() {
return expect(this.scopeStack.current, 'expected scope on the scope stack');
}
dynamicScope() {
return expect(this.dynamicScopeStack.current, 'expected dynamic scope on the dynamic scope stack');
}
pushChildScope() {
this.scopeStack.push(this.scope().child());
}
pushCallerScope(childScope = false) {
let callerScope = expect(this.scope().getCallerScope(), 'pushCallerScope is called when a caller scope is present');
this.scopeStack.push(childScope ? callerScope.child() : callerScope);
}
pushDynamicScope() {
let child = this.dynamicScope().child();
this.dynamicScopeStack.push(child);
return child;
}
pushRootScope(size, bindCaller) {
let scope = Scope.sized(size);
if (bindCaller) scope.bindCallerScope(this.scope());
this.scopeStack.push(scope);
return scope;
}
popScope() {
this.scopeStack.pop();
}
popDynamicScope() {
this.dynamicScopeStack.pop();
}
newDestroyable(d) {
this.elements().newDestroyable(d);
}
/// SCOPE HELPERS
getSelf() {
return this.scope().getSelf();
}
referenceForSymbol(symbol) {
return this.scope().getSymbol(symbol);
}
/// EXECUTION
execute(start, initialize) {
this.pc = this.heap.getaddr(start);
if (initialize) initialize(this);
let result;
while (true) {
result = this.next();
if (result.done) break;
}
return result.value;
}
next() {
let { env, updatingOpcodeStack, elementStack } = this;
let opcode = this.nextStatement(env);
let result;
if (opcode !== null) {
APPEND_OPCODES.evaluate(this, opcode, opcode.type);
result = { done: false, value: null };
} else {
// Unload the stack
this.stack.reset();
result = {
done: true,
value: new RenderResult(env, expect(updatingOpcodeStack.pop(), 'there should be a final updating opcode stack'), elementStack.popBlock())
};
}
return result;
}
nextStatement(env) {
let { pc } = this;
if (pc === -1) {
return null;
}
let program = env.program;
this.pc += 4;
return program.opcode(pc);
}
evaluateOpcode(opcode) {
APPEND_OPCODES.evaluate(this, opcode, opcode.type);
}
bindDynamicScope(names) {
let scope = this.dynamicScope();
for (let i = names.length - 1; i >= 0; i--) {
let name = this.constants.getString(names[i]);
scope.set(name, this.stack.pop());
}
}
}