UNPKG

ember-source

Version:

A JavaScript framework for creating ambitious web applications

276 lines (271 loc) 11 kB
import { getInternalHelperManager, getInternalModifierManager, getInternalComponentManager, capabilityFlagsFrom, getComponentTemplate, managerHasCapability } from '../manager/index.js'; import { templateFactory } from '../opcode-compiler/index.js'; import { enumerate } from '../util/index.js'; import { OPERAND_LEN_MASK, ARG_SHIFT, MACHINE_MASK, TYPE_MASK, InternalComponentCapabilities } from '../vm/index.js'; import { SexpOpcodes as opcodes } from '../wire-format/index.js'; function unwrapTemplate(template) { if ("error" === template.result) throw new Error(`Compile Error: ${template.problem} @ ${template.span.start}..${template.span.end}`); return template; } /** * Default component template, which is a plain yield */ const DEFAULT_TEMPLATE_BLOCK = [[[opcodes.Yield, 1, null]], ["&default"], []], DEFAULT_TEMPLATE = { // random uuid id: "1b32f5c2-7623-43d6-a0ad-9672898920a1", moduleName: "__default__.hbs", block: JSON.stringify(DEFAULT_TEMPLATE_BLOCK), scope: null, isStrictMode: true }, WELL_KNOWN_EMPTY_ARRAY = Object.freeze([]), STARTER_CONSTANTS = [false, true, null, void 0, WELL_KNOWN_EMPTY_ARRAY], WELL_KNOWN_EMPTY_ARRAY_POSITION = STARTER_CONSTANTS.indexOf(WELL_KNOWN_EMPTY_ARRAY); class ConstantsImpl { value(value) { let indexMap = this.indexMap, index = indexMap.get(value); return void 0 === index && (index = this.values.push(value) - 1, indexMap.set(value, index)), index; } array(values) { if (0 === values.length) return WELL_KNOWN_EMPTY_ARRAY_POSITION; let handles = new Array(values.length); for (let i = 0; i < values.length; i++) handles[i] = this.value(values[i]); return this.value(handles); } toPool() { return this.values; } hasHandle(handle) { return this.values.length > handle; } helper(definitionState, // TODO: Add a way to expose resolved name for debugging _resolvedName = null, isOptional) { let handle = this.helperDefinitionCache.get(definitionState); if (void 0 === handle) { let managerOrHelper = getInternalHelperManager(definitionState, isOptional); if (null === managerOrHelper) return this.helperDefinitionCache.set(definitionState, null), null; let helper = "function" == typeof managerOrHelper ? managerOrHelper : managerOrHelper.getHelper(definitionState); handle = this.value(helper), this.helperDefinitionCache.set(definitionState, handle), this.helperDefinitionCount++; } return handle; } modifier(definitionState, resolvedName = null, isOptional) { let handle = this.modifierDefinitionCache.get(definitionState); if (void 0 === handle) { let manager = getInternalModifierManager(definitionState, isOptional); if (null === manager) return this.modifierDefinitionCache.set(definitionState, null), null; let definition = { resolvedName: resolvedName, manager: manager, state: definitionState }; handle = this.value(definition), this.modifierDefinitionCache.set(definitionState, handle), this.modifierDefinitionCount++; } return handle; } component(definitionState, owner, isOptional, debugName) { let definition = this.componentDefinitionCache.get(definitionState); if (void 0 === definition) { let manager = getInternalComponentManager(definitionState, isOptional); if (null === manager) return this.componentDefinitionCache.set(definitionState, null), null; let template, capabilities = capabilityFlagsFrom(manager.getCapabilities(definitionState)), templateFactory = getComponentTemplate(definitionState), compilable = null; template = managerHasCapability(manager, capabilities, InternalComponentCapabilities.dynamicLayout) ? templateFactory?.(owner) : templateFactory?.(owner) ?? this.defaultTemplate, void 0 !== template && (template = unwrapTemplate(template), compilable = managerHasCapability(manager, capabilities, InternalComponentCapabilities.wrapped) ? template.asWrappedLayout() : template.asLayout()), definition = { resolvedName: null, handle: -1, manager: manager, capabilities: capabilities, state: definitionState, compilable: compilable }, definition.handle = this.value(definition), debugName && (definition.debugName = debugName), this.componentDefinitionCache.set(definitionState, definition), this.componentDefinitionCount++; } return definition; } resolvedComponent(resolvedDefinition, resolvedName) { let definition = this.componentDefinitionCache.get(resolvedDefinition); if (void 0 === definition) { let { manager: manager, state: state, template: template } = resolvedDefinition, capabilities = capabilityFlagsFrom(manager.getCapabilities(resolvedDefinition)), compilable = null; managerHasCapability(manager, capabilities, InternalComponentCapabilities.dynamicLayout) || (template = template ?? this.defaultTemplate), null !== template && (template = unwrapTemplate(template), compilable = managerHasCapability(manager, capabilities, InternalComponentCapabilities.wrapped) ? template.asWrappedLayout() : template.asLayout()), definition = { resolvedName: resolvedName, handle: -1, manager: manager, capabilities: capabilities, state: state, compilable: compilable }, definition.handle = this.value(definition), this.componentDefinitionCache.set(resolvedDefinition, definition), this.componentDefinitionCount++; } return definition; } getValue(index) { return this.values[index]; } getArray(index) { let reifiedArrs = this.reifiedArrs, reified = reifiedArrs[index]; if (void 0 === reified) { let names = this.getValue(index); reified = new Array(names.length); for (const [i, name] of enumerate(names)) reified[i] = this.getValue(name); reifiedArrs[index] = reified; } return reified; } constructor() { this.reifiedArrs = { [WELL_KNOWN_EMPTY_ARRAY_POSITION]: WELL_KNOWN_EMPTY_ARRAY }, this.defaultTemplate = templateFactory(DEFAULT_TEMPLATE)(), // Used for tests and debugging purposes, and to be able to analyze large apps // This is why it's enabled even in production this.helperDefinitionCount = 0, this.modifierDefinitionCount = 0, this.componentDefinitionCount = 0, this.values = STARTER_CONSTANTS.slice(), this.indexMap = new Map(this.values.map((value, index) => [value, index])), this.helperDefinitionCache = new WeakMap(), this.modifierDefinitionCache = new WeakMap(), this.componentDefinitionCache = new WeakMap(); } } class RuntimeOpImpl { constructor(heap) { this.heap = heap, this.offset = 0; } get size() { return 1 + ((this.heap.getbyaddr(this.offset) & OPERAND_LEN_MASK) >> ARG_SHIFT); } get isMachine() { return this.heap.getbyaddr(this.offset) & MACHINE_MASK ? 1 : 0; } get type() { return this.heap.getbyaddr(this.offset) & TYPE_MASK; } get op1() { return this.heap.getbyaddr(this.offset + 1); } get op2() { return this.heap.getbyaddr(this.offset + 2); } get op3() { return this.heap.getbyaddr(this.offset + 3); } } /** * The Program Heap is responsible for dynamically allocating * memory in which we read/write the VM's instructions * from/to. When we malloc we pass out a VMHandle, which * is used as an indirect way of accessing the memory during * execution of the VM. Internally we track the different * regions of the memory in an int array known as the table. * * The table 32-bit aligned and has the following layout: * * | ... | hp (u32) | info (u32) | size (u32) | * | ... | Handle | Scope Size | State | Size | * | ... | 32bits | 30bits | 2bits | 32bit | * * With this information we effectively have the ability to * control when we want to free memory. That being said you * can not free during execution as raw address are only * valid during the execution. This means you cannot close * over them as you will have a bad memory access exception. */ class ProgramHeapImpl { constructor() { this.offset = 0, this.handle = 0, this.heap = new Int32Array(1048576), this.handleTable = [], this.handleState = []; } entries() { return this.offset; } pushRaw(value) { this.sizeCheck(), this.heap[this.offset++] = value; } pushOp(item) { this.pushRaw(item); } pushMachine(item) { this.pushRaw(item | MACHINE_MASK); } sizeCheck() { let { heap: heap } = this; if (this.offset === this.heap.length) { let newHeap = new Int32Array(heap.length + 1048576); newHeap.set(heap, 0), this.heap = newHeap; } } getbyaddr(address) { return this.heap[address]; } setbyaddr(address, value) { this.heap[address] = value; } malloc() { // push offset, info, size return this.handleTable.push(this.offset), this.handleTable.length - 1; } finishMalloc(handle) {} size() { return this.offset; } // It is illegal to close over this address, as compaction // may move it. However, it is legal to use this address // multiple times between compactions. getaddr(handle) { return this.handleTable[handle]; } sizeof(handle) { return this.handleTable, -1; } free(handle) { this.handleState[handle] = 1; } /** * The heap uses the [Mark-Compact Algorithm](https://en.wikipedia.org/wiki/Mark-compact_algorithm) to shift * reachable memory to the bottom of the heap and freeable * memory to the top of the heap. When we have shifted all * the reachable memory to the top of the heap, we move the * offset to the next free position. */ compact() { let compactedSize = 0, { handleTable: handleTable, handleState: handleState, heap: heap } = this; for (let i = 0; i < length; i++) { let offset = handleTable[i], size = handleTable[i + 1] - offset, state = handleState[i]; if (2 !== state) if (1 === state) // transition to "already freed" aka "purged" // a good improvement would be to reuse // these slots handleState[i] = 2, compactedSize += size;else if (0 === state) { for (let j = offset; j <= i + size; j++) heap[j - compactedSize] = heap[j]; handleTable[i] = offset - compactedSize; } else 3 === state && (handleTable[i] = offset - compactedSize); } this.offset = this.offset - compactedSize; } } class ProgramImpl { constructor(constants, heap) { this.constants = constants, this.heap = heap, this._opcode = new RuntimeOpImpl(this.heap); } opcode(offset) { return this._opcode.offset = offset, this._opcode; } } function artifacts() { return { constants: new ConstantsImpl(), heap: new ProgramHeapImpl() }; } export { ConstantsImpl, ProgramHeapImpl, ProgramImpl, RuntimeOpImpl, artifacts };