UNPKG

ember-legacy-class-transform

Version:
323 lines (322 loc) 10.1 kB
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()); } } }