diff --git a/packages/@glimmer-workspace/benchmark-env/lib/benchmark/create-registry.ts b/packages/@glimmer-workspace/benchmark-env/lib/benchmark/create-registry.ts index 0bb4a87e31..1bdbee6d19 100644 --- a/packages/@glimmer-workspace/benchmark-env/lib/benchmark/create-registry.ts +++ b/packages/@glimmer-workspace/benchmark-env/lib/benchmark/create-registry.ts @@ -5,6 +5,7 @@ import type { InternalModifierManager, ModifierDefinitionState, ResolvedComponentDefinition, + SimpleDocument, SimpleElement, } from '@glimmer/interfaces'; import { @@ -13,11 +14,13 @@ import { setInternalHelperManager, setInternalModifierManager, } from '@glimmer/manager'; -import { programCompilationContext } from '@glimmer/opcode-compiler'; +import { EvaluationContextImpl } from '@glimmer/opcode-compiler'; import { artifacts, RuntimeOpImpl } from '@glimmer/program'; +import { runtimeContext } from '@glimmer/runtime'; import type { UpdateBenchmark } from '../interfaces'; +import createEnvDelegate from './create-env-delegate'; import renderBenchmark from './render-benchmark'; export interface Registry { @@ -77,9 +80,15 @@ export default function createRegistry(): Registry { setInternalModifierManager(manager, modifier); modifiers.set(name, modifier); }, - render: (entry, args, element, isIteractive) => { + render: (entry, args, element, isInteractive) => { const sharedArtifacts = artifacts(); - const context = programCompilationContext( + const document = element.ownerDocument as SimpleDocument; + const envDelegate = createEnvDelegate(isInteractive ?? true); + const runtime = runtimeContext( + { + document, + }, + envDelegate, sharedArtifacts, { lookupHelper: (name) => helpers.get(name) ?? null, @@ -88,25 +97,20 @@ export default function createRegistry(): Registry { lookupBuiltInHelper: () => null, lookupBuiltInModifier: () => null, - }, - (heap) => new RuntimeOpImpl(heap) + } + ); + + const context = new EvaluationContextImpl( + sharedArtifacts, + (heap) => new RuntimeOpImpl(heap), + runtime ); const component = components.get(entry); if (!component) { throw new Error(`missing ${entry} component`); } - return renderBenchmark( - sharedArtifacts, - context, - { - lookupComponent: () => null, - }, - component, - args, - element as SimpleElement, - isIteractive - ); + return renderBenchmark(context, component, args, element as SimpleElement); }, }; } diff --git a/packages/@glimmer-workspace/benchmark-env/lib/benchmark/render-benchmark.ts b/packages/@glimmer-workspace/benchmark-env/lib/benchmark/render-benchmark.ts index d58e263f61..36820a949d 100644 --- a/packages/@glimmer-workspace/benchmark-env/lib/benchmark/render-benchmark.ts +++ b/packages/@glimmer-workspace/benchmark-env/lib/benchmark/render-benchmark.ts @@ -1,47 +1,32 @@ import type { - CompileTimeCompilationContext, Dict, + EvaluationContext, ResolvedComponentDefinition, - RuntimeArtifacts, - RuntimeResolver, SimpleElement, } from '@glimmer/interfaces'; -import { NewElementBuilder, renderComponent, renderSync, runtimeContext } from '@glimmer/runtime'; +import { NewTreeBuilder, renderComponent, renderSync } from '@glimmer/runtime'; import type { UpdateBenchmark } from '../interfaces'; -import createEnvDelegate, { registerResult } from './create-env-delegate'; +import { registerResult } from './create-env-delegate'; import { measureRender } from './util'; export default async function renderBenchmark( - artifacts: RuntimeArtifacts, - context: CompileTimeCompilationContext, - runtimeResolver: RuntimeResolver, + context: EvaluationContext, component: ResolvedComponentDefinition, args: Dict, - element: SimpleElement, - isInteractive = true + element: SimpleElement ): Promise { let resolveRender: (() => void) | undefined; await measureRender('render', 'renderStart', 'renderEnd', () => { - const document = element.ownerDocument; - const envDelegate = createEnvDelegate(isInteractive); - const runtime = runtimeContext( - { - document, - }, - envDelegate, - artifacts, - runtimeResolver - ); - const env = runtime.env; + const env = context.env; const cursor = { element, nextSibling: null }; - const treeBuilder = NewElementBuilder.forInitialRender(env, cursor); + const treeBuilder = NewTreeBuilder.forInitialRender(env, cursor); const result = renderSync( env, - renderComponent(runtime, treeBuilder, context, {}, component.state, args) + renderComponent(context, treeBuilder, {}, component.state, args) ); registerResult(result, () => { diff --git a/packages/@glimmer-workspace/benchmark-env/lib/benchmark/util.ts b/packages/@glimmer-workspace/benchmark-env/lib/benchmark/util.ts index 79763e3d26..d0a848fa54 100644 --- a/packages/@glimmer-workspace/benchmark-env/lib/benchmark/util.ts +++ b/packages/@glimmer-workspace/benchmark-env/lib/benchmark/util.ts @@ -1,7 +1,7 @@ -import type { CompileTimeCompilationContext, CompileTimeComponent } from '@glimmer/interfaces'; +import type { CompileTimeComponent, EvaluationContext } from '@glimmer/interfaces'; import { unwrapHandle } from '@glimmer/debug-util'; -export function compileEntry(entry: CompileTimeComponent, context: CompileTimeCompilationContext) { +export function compileEntry(entry: CompileTimeComponent, context: EvaluationContext) { return unwrapHandle(entry.compilable!.compile(context)); } diff --git a/packages/@glimmer-workspace/integration-tests/lib/modes/jit/compilation-context.ts b/packages/@glimmer-workspace/integration-tests/lib/modes/jit/compilation-context.ts index d9b4059427..a4f8d235b9 100644 --- a/packages/@glimmer-workspace/integration-tests/lib/modes/jit/compilation-context.ts +++ b/packages/@glimmer-workspace/integration-tests/lib/modes/jit/compilation-context.ts @@ -1,5 +1,5 @@ import type { - CompileTimeResolver, + ClassicResolver, HelperDefinitionState, ModifierDefinitionState, Nullable, @@ -8,7 +8,7 @@ import type { import type { TestJitRuntimeResolver } from './resolver'; -export default class JitCompileTimeLookup implements CompileTimeResolver { +export default class JitCompileTimeLookup implements ClassicResolver { constructor(private resolver: TestJitRuntimeResolver) {} lookupHelper(name: string): Nullable { diff --git a/packages/@glimmer-workspace/integration-tests/lib/modes/jit/delegate.ts b/packages/@glimmer-workspace/integration-tests/lib/modes/jit/delegate.ts index fec627f2cb..197e1881e5 100644 --- a/packages/@glimmer-workspace/integration-tests/lib/modes/jit/delegate.ts +++ b/packages/@glimmer-workspace/integration-tests/lib/modes/jit/delegate.ts @@ -1,27 +1,26 @@ import type { CapturedRenderNode, - CompileTimeCompilationContext, Cursor, Dict, DynamicScope, - ElementBuilder, ElementNamespace, Environment, + EvaluationContext, HandleResult, Helper, Nullable, RenderResult, - RuntimeContext, SimpleDocument, SimpleDocumentFragment, SimpleElement, SimpleText, + TreeBuilder, } from '@glimmer/interfaces'; import type { Reference } from '@glimmer/reference'; import type { CurriedValue, EnvironmentDelegate } from '@glimmer/runtime'; import type { ASTPluginBuilder, PrecompileOptions } from '@glimmer/syntax'; import { castToBrowser, castToSimple, expect, unwrapTemplate } from '@glimmer/debug-util'; -import { programCompilationContext } from '@glimmer/opcode-compiler'; +import { EvaluationContextImpl } from '@glimmer/opcode-compiler'; import { artifacts, RuntimeOpImpl } from '@glimmer/program'; import { createConstRef } from '@glimmer/reference'; import { @@ -58,24 +57,20 @@ import { TestJitRegistry } from './registry'; import { renderTemplate } from './render'; import { TestJitRuntimeResolver } from './resolver'; -export interface JitTestDelegateContext { - runtime: RuntimeContext; - program: CompileTimeCompilationContext; -} - export function JitDelegateContext( doc: SimpleDocument, resolver: TestJitRuntimeResolver, env: EnvironmentDelegate -): JitTestDelegateContext { +): EvaluationContext { let sharedArtifacts = artifacts(); - let context = programCompilationContext( + let runtime = runtimeContext( + { document: doc }, + env, sharedArtifacts, - new JitCompileTimeLookup(resolver), - (heap) => new RuntimeOpImpl(heap) + new JitCompileTimeLookup(resolver) ); - let runtime = runtimeContext({ document: doc }, env, sharedArtifacts, resolver); - return { runtime, program: context }; + + return new EvaluationContextImpl(sharedArtifacts, (heap) => new RuntimeOpImpl(heap), runtime); } export class JitRenderDelegate implements RenderDelegate { @@ -86,7 +81,7 @@ export class JitRenderDelegate implements RenderDelegate { protected resolver: TestJitRuntimeResolver; private plugins: ASTPluginBuilder[] = []; - private _context: JitTestDelegateContext | null = null; + private _context: Nullable = null; private self: Nullable = null; private doc: SimpleDocument; private env: EnvironmentDelegate; @@ -108,7 +103,7 @@ export class JitRenderDelegate implements RenderDelegate { this.registry.register('helper', 'concat', concat); } - get context(): JitTestDelegateContext { + get context(): EvaluationContext { if (this._context === null) { this._context = JitDelegateContext(this.doc, this.resolver, this.env); } @@ -118,7 +113,7 @@ export class JitRenderDelegate implements RenderDelegate { getCapturedRenderTree(): CapturedRenderNode[] { return expect( - this.context.runtime.env.debugRenderTree, + this.context.env.debugRenderTree, 'Attempted to capture the DebugRenderTree during tests, but it was not created. Did you enable it in the environment?' ).capture(); } @@ -191,7 +186,7 @@ export class JitRenderDelegate implements RenderDelegate { registerInternalHelper(this.registry, name, helper); } - getElementBuilder(env: Environment, cursor: Cursor): ElementBuilder { + getElementBuilder(env: Environment, cursor: Cursor): TreeBuilder { return clientBuilder(env, cursor); } @@ -206,13 +201,13 @@ export class JitRenderDelegate implements RenderDelegate { compileTemplate(template: string): HandleResult { let compiled = preprocess(template, this.precompileOptions); - return unwrapTemplate(compiled).asLayout().compile(this.context.program); + return unwrapTemplate(compiled).asLayout().compile(this.context); } renderTemplate(template: string, context: Dict, element: SimpleElement): RenderResult { let cursor = { element, nextSibling: null }; - let { env } = this.context.runtime; + let { env } = this.context; return renderTemplate( template, @@ -230,11 +225,11 @@ export class JitRenderDelegate implements RenderDelegate { dynamicScope?: DynamicScope ): RenderResult { let cursor = { element, nextSibling: null }; - let { program, runtime } = this.context; - let builder = this.getElementBuilder(runtime.env, cursor); - let iterator = renderComponent(runtime, builder, program, {}, component, args, dynamicScope); + let { env } = this.context; + let builder = this.getElementBuilder(env, cursor); + let iterator = renderComponent(this.context, builder, {}, component, args, dynamicScope); - return renderSync(runtime.env, iterator); + return renderSync(env, iterator); } private get precompileOptions(): PrecompileOptions { diff --git a/packages/@glimmer-workspace/integration-tests/lib/modes/jit/render.ts b/packages/@glimmer-workspace/integration-tests/lib/modes/jit/render.ts index 2ac9c1c151..c400dfa2dd 100644 --- a/packages/@glimmer-workspace/integration-tests/lib/modes/jit/render.ts +++ b/packages/@glimmer-workspace/integration-tests/lib/modes/jit/render.ts @@ -1,29 +1,20 @@ -import type { ElementBuilder, RenderResult } from '@glimmer/interfaces'; +import type { EvaluationContext, RenderResult, TreeBuilder } from '@glimmer/interfaces'; import type { Reference } from '@glimmer/reference'; import type { PrecompileOptions } from '@glimmer/syntax'; import { unwrapTemplate } from '@glimmer/debug-util'; import { renderMain, renderSync } from '@glimmer/runtime'; -import type { JitTestDelegateContext } from './delegate'; - import { preprocess } from '../../compile'; export function renderTemplate( src: string, - { runtime, program }: JitTestDelegateContext, + context: EvaluationContext, self: Reference, - builder: ElementBuilder, + builder: TreeBuilder, options?: PrecompileOptions ): RenderResult { let template = preprocess(src, options); - let iterator = renderMain( - runtime, - program, - {}, - self, - builder, - unwrapTemplate(template).asLayout() - ); - return renderSync(runtime.env, iterator); + let iterator = renderMain(context, {}, self, builder, unwrapTemplate(template).asLayout()); + return renderSync(context.env, iterator); } diff --git a/packages/@glimmer-workspace/integration-tests/lib/modes/jit/resolver.ts b/packages/@glimmer-workspace/integration-tests/lib/modes/jit/resolver.ts index 6cf6b2613b..9d708fa490 100644 --- a/packages/@glimmer-workspace/integration-tests/lib/modes/jit/resolver.ts +++ b/packages/@glimmer-workspace/integration-tests/lib/modes/jit/resolver.ts @@ -1,14 +1,14 @@ import type { + ClassicResolver, HelperDefinitionState, ModifierDefinitionState, Nullable, ResolvedComponentDefinition, - RuntimeResolver, } from '@glimmer/interfaces'; import type { TestJitRegistry } from './registry'; -export class TestJitRuntimeResolver implements RuntimeResolver { +export class TestJitRuntimeResolver implements ClassicResolver { constructor(private registry: TestJitRegistry) {} lookupHelper(name: string): Nullable { diff --git a/packages/@glimmer-workspace/integration-tests/lib/modes/rehydration/builder.ts b/packages/@glimmer-workspace/integration-tests/lib/modes/rehydration/builder.ts index 5d02075f8b..0017a8d6c4 100644 --- a/packages/@glimmer-workspace/integration-tests/lib/modes/rehydration/builder.ts +++ b/packages/@glimmer-workspace/integration-tests/lib/modes/rehydration/builder.ts @@ -1,4 +1,4 @@ -import type { Cursor, ElementBuilder, Environment, SimpleNode } from '@glimmer/interfaces'; +import type { Cursor, Environment, SimpleNode, TreeBuilder } from '@glimmer/interfaces'; import { COMMENT_NODE, ELEMENT_NODE } from '@glimmer/constants'; import { RehydrateBuilder } from '@glimmer/runtime'; @@ -23,6 +23,6 @@ export class DebugRehydrationBuilder extends RehydrateBuilder { } } -export function debugRehydration(env: Environment, cursor: Cursor): ElementBuilder { +export function debugRehydration(env: Environment, cursor: Cursor): TreeBuilder { return DebugRehydrationBuilder.forInitialRender(env, cursor); } diff --git a/packages/@glimmer-workspace/integration-tests/lib/modes/rehydration/delegate.ts b/packages/@glimmer-workspace/integration-tests/lib/modes/rehydration/delegate.ts index 7f8e4e6606..9faef8a24d 100644 --- a/packages/@glimmer-workspace/integration-tests/lib/modes/rehydration/delegate.ts +++ b/packages/@glimmer-workspace/integration-tests/lib/modes/rehydration/delegate.ts @@ -1,9 +1,9 @@ import type { Cursor, Dict, - ElementBuilder, ElementNamespace, Environment, + EvaluationContext, Helper, Nullable, RenderResult, @@ -12,6 +12,7 @@ import type { SimpleElement, SimpleNode, SimpleText, + TreeBuilder, } from '@glimmer/interfaces'; import type { Reference } from '@glimmer/reference'; import type { ASTPluginBuilder, PrecompileOptions } from '@glimmer/syntax'; @@ -26,7 +27,6 @@ import type { UserHelper } from '../../helpers'; import type { TestModifierConstructor } from '../../modifiers'; import type RenderDelegate from '../../render-delegate'; import type { RenderDelegateOptions } from '../../render-delegate'; -import type { JitTestDelegateContext } from '../jit/delegate'; import type { DebugRehydrationBuilder } from './builder'; import { BaseEnv } from '../../base-env'; @@ -53,8 +53,8 @@ export class RehydrationDelegate implements RenderDelegate { private plugins: ASTPluginBuilder[] = []; - public clientEnv: JitTestDelegateContext; - public serverEnv: JitTestDelegateContext; + public clientContext: EvaluationContext; + public serverContext: EvaluationContext; private clientResolver: TestJitRuntimeResolver; private serverResolver: TestJitRuntimeResolver; @@ -75,12 +75,12 @@ export class RehydrationDelegate implements RenderDelegate { this.clientDoc = castToSimple(document); this.clientRegistry = new TestJitRegistry(); this.clientResolver = new TestJitRuntimeResolver(this.clientRegistry); - this.clientEnv = JitDelegateContext(this.clientDoc, this.clientResolver, delegate); + this.clientContext = JitDelegateContext(this.clientDoc, this.clientResolver, delegate); this.serverDoc = createHTMLDocument(); this.serverRegistry = new TestJitRegistry(); this.serverResolver = new TestJitRuntimeResolver(this.serverRegistry); - this.serverEnv = JitDelegateContext(this.serverDoc, this.serverResolver, delegate); + this.serverContext = JitDelegateContext(this.serverDoc, this.serverResolver, delegate); } getInitialElement(): SimpleElement { @@ -103,7 +103,7 @@ export class RehydrationDelegate implements RenderDelegate { return this.clientDoc.createDocumentFragment(); } - getElementBuilder(env: Environment, cursor: Cursor): ElementBuilder { + getElementBuilder(env: Environment, cursor: Cursor): TreeBuilder { if (cursor.element instanceof Node) { return debugRehydration(env, cursor); } @@ -119,12 +119,12 @@ export class RehydrationDelegate implements RenderDelegate { ): string { element = element || this.serverDoc.createElement('div'); let cursor = { element, nextSibling: null }; - let { env } = this.serverEnv.runtime; + let { env } = this.serverContext; // Emulate server-side render renderTemplate( template, - this.serverEnv, + this.serverContext, this.getSelf(env, context), this.getElementBuilder(env, cursor), this.precompileOptions @@ -147,7 +147,7 @@ export class RehydrationDelegate implements RenderDelegate { } renderClientSide(template: string, context: Dict, element: SimpleElement): RenderResult { - let env = this.clientEnv.runtime.env; + let { env } = this.clientContext; this.self = null; // Client-side rehydration @@ -155,7 +155,7 @@ export class RehydrationDelegate implements RenderDelegate { let builder = this.getElementBuilder(env, cursor) as DebugRehydrationBuilder; let result = renderTemplate( template, - this.clientEnv, + this.clientContext, this.getSelf(env, context), builder, this.precompileOptions diff --git a/packages/@glimmer-workspace/integration-tests/lib/modes/rehydration/partial-rehydration-delegate.ts b/packages/@glimmer-workspace/integration-tests/lib/modes/rehydration/partial-rehydration-delegate.ts index ca3e2ab488..60b8783f5f 100644 --- a/packages/@glimmer-workspace/integration-tests/lib/modes/rehydration/partial-rehydration-delegate.ts +++ b/packages/@glimmer-workspace/integration-tests/lib/modes/rehydration/partial-rehydration-delegate.ts @@ -16,13 +16,13 @@ export class PartialRehydrationDelegate extends RehydrationDelegate { element: SimpleElement ): RenderResult { let cursor = { element, nextSibling: null }; - let { program, runtime } = this.clientEnv; - let builder = this.getElementBuilder(runtime.env, cursor) as DebugRehydrationBuilder; + let context = this.clientContext; + let builder = this.getElementBuilder(context.env, cursor) as DebugRehydrationBuilder; let component = this.clientRegistry.lookupComponent(name)!; - let iterator = renderComponent(runtime, builder, program, {}, component.state, args); + let iterator = renderComponent(context, builder, {}, component.state, args); - const result = renderSync(runtime.env, iterator); + const result = renderSync(context.env, iterator); this.rehydrationStats = { clearedNodes: builder.clearedNodes, @@ -34,14 +34,14 @@ export class PartialRehydrationDelegate extends RehydrationDelegate { renderComponentServerSide(name: string, args: Dict): string { const element = this.serverDoc.createElement('div'); let cursor = { element, nextSibling: null }; - let { program, runtime } = this.serverEnv; - let builder = this.getElementBuilder(runtime.env, cursor); + let context = this.serverContext; + let builder = this.getElementBuilder(context.env, cursor); let component = this.serverRegistry.lookupComponent(name)!; - let iterator = renderComponent(runtime, builder, program, {}, component.state, args); + let iterator = renderComponent(context, builder, {}, component.state, args); - renderSync(runtime.env, iterator); + renderSync(context.env, iterator); return this.serialize(element); } diff --git a/packages/@glimmer-workspace/integration-tests/lib/render-delegate.ts b/packages/@glimmer-workspace/integration-tests/lib/render-delegate.ts index a2fdd35750..7b0514dde3 100644 --- a/packages/@glimmer-workspace/integration-tests/lib/render-delegate.ts +++ b/packages/@glimmer-workspace/integration-tests/lib/render-delegate.ts @@ -2,7 +2,6 @@ import type { Cursor, Dict, DynamicScope, - ElementBuilder, ElementNamespace, Environment, Helper, @@ -11,6 +10,7 @@ import type { SimpleDocumentFragment, SimpleElement, SimpleText, + TreeBuilder, } from '@glimmer/interfaces'; import type { Reference } from '@glimmer/reference'; import type { EnvironmentDelegate } from '@glimmer/runtime'; @@ -56,6 +56,6 @@ export default interface RenderDelegate { element: SimpleElement, dynamicScope?: DynamicScope ): RenderResult; - getElementBuilder(env: Environment, cursor: Cursor): ElementBuilder; + getElementBuilder(env: Environment, cursor: Cursor): TreeBuilder; getSelf(env: Environment, context: unknown): Reference; } diff --git a/packages/@glimmer-workspace/integration-tests/lib/suites/custom-dom-helper.ts b/packages/@glimmer-workspace/integration-tests/lib/suites/custom-dom-helper.ts index 498ca6fe90..e6881e59ba 100644 --- a/packages/@glimmer-workspace/integration-tests/lib/suites/custom-dom-helper.ts +++ b/packages/@glimmer-workspace/integration-tests/lib/suites/custom-dom-helper.ts @@ -1,4 +1,4 @@ -import type { Cursor, ElementBuilder, Environment } from '@glimmer/interfaces'; +import type { Cursor, Environment, TreeBuilder } from '@glimmer/interfaces'; import { precompile } from '@glimmer/compiler'; import { NodeDOMTreeConstruction, serializeBuilder } from '@glimmer/node'; @@ -39,7 +39,7 @@ export class CompilationTests extends RenderTest { export class JitSerializationDelegate extends NodeJitRenderDelegate { static override style = 'jit serialization'; - override getElementBuilder(env: Environment, cursor: Cursor): ElementBuilder { + override getElementBuilder(env: Environment, cursor: Cursor): TreeBuilder { return serializeBuilder(env, cursor); } } diff --git a/packages/@glimmer-workspace/integration-tests/test/chaos-rehydration-test.ts b/packages/@glimmer-workspace/integration-tests/test/chaos-rehydration-test.ts index 8925838d82..69c6b5ebec 100644 --- a/packages/@glimmer-workspace/integration-tests/test/chaos-rehydration-test.ts +++ b/packages/@glimmer-workspace/integration-tests/test/chaos-rehydration-test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-enum-comparison */ import type { Dict, Nullable, SimpleElement } from '@glimmer/interfaces'; import { COMMENT_NODE, ELEMENT_NODE } from '@glimmer/constants'; import { castToBrowser, castToSimple, expect } from '@glimmer/debug-util'; diff --git a/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts b/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts index 3ede754dd5..90381e73ac 100644 --- a/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts +++ b/packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts @@ -254,12 +254,8 @@ class DebugRenderTreeTest extends RenderTest { args: (actual) => { const args = { positional: [], named: { arg: 'first', arg2: { error } } }; this.assert.deepEqual(actual, args); - this.assert.ok( - !this.delegate.context.runtime.env.isArgumentCaptureError!(actual.named['arg']) - ); - this.assert.ok( - this.delegate.context.runtime.env.isArgumentCaptureError!(actual.named['arg2']) - ); + this.assert.ok(!this.delegate.context.env.isArgumentCaptureError!(actual.named['arg'])); + this.assert.ok(this.delegate.context.env.isArgumentCaptureError!(actual.named['arg2'])); return true; }, instance: (instance: EmberishCurlyComponent) => (instance as any).arg === 'first', diff --git a/packages/@glimmer/debug-util/lib/platform-utils.ts b/packages/@glimmer/debug-util/lib/platform-utils.ts index 2bb81683bb..9aa6d007d5 100644 --- a/packages/@glimmer/debug-util/lib/platform-utils.ts +++ b/packages/@glimmer/debug-util/lib/platform-utils.ts @@ -10,14 +10,15 @@ export function unwrap(val: Maybe): T { return val as T; } -export const expect = ( - LOCAL_DEBUG - ? (value: T, _message: string) => value - : (val: T, message: string): Present => { - if (LOCAL_DEBUG) if (val === null || val === undefined) throw new Error(message); - return val as Present; - } -) as (value: T, message: string) => NonNullable; +export const expect = (LOCAL_DEBUG + ? (value: T, _message: string) => value + : (val: T, message: string): Present => { + if (LOCAL_DEBUG) if (val === null || val === undefined) throw new Error(message); + return val as Present; + }) as (value: T, message: string) => NonNullable as ( + value: T, + message: string +) => Present; export const unreachable = LOCAL_DEBUG ? () => {} diff --git a/packages/@glimmer/debug/lib/debug.ts b/packages/@glimmer/debug/lib/debug.ts index d9aae6f60f..42004ad31b 100644 --- a/packages/@glimmer/debug/lib/debug.ts +++ b/packages/@glimmer/debug/lib/debug.ts @@ -1,11 +1,11 @@ import type { + CompilationContext, CompileTimeConstants, Dict, Maybe, Recast, ResolutionTimeConstants, RuntimeOp, - TemplateCompilationContext, } from '@glimmer/interfaces'; import { decodeHandle, decodeImmediate } from '@glimmer/constants'; import { LOCAL_TRACE_LOGGING } from '@glimmer/local-debug-flags'; @@ -21,21 +21,20 @@ export interface DebugConstants { getArray(value: number): T[]; } -export function debugSlice(context: TemplateCompilationContext, start: number, end: number) { +export function debugSlice(context: CompilationContext, start: number, end: number) { if (LOCAL_TRACE_LOGGING) { LOCAL_LOGGER.group(`%c${start}:${end}`, 'color: #999'); - let heap = context.program.heap; - let opcode = context.program.createOp(heap); + const constants = context.evaluation.program.constants; + + let heap = context.evaluation.program.heap; + let opcode = context.evaluation.createOp(heap); let _size = 0; for (let i = start; i < end; i = i + _size) { opcode.offset = i; let [name, params] = debug( - context.program.constants as Recast< - CompileTimeConstants & ResolutionTimeConstants, - DebugConstants - >, + constants as Recast, opcode, opcode.isMachine )!; diff --git a/packages/@glimmer/interfaces/index.d.ts b/packages/@glimmer/interfaces/index.d.ts index 4126dc5a10..ea94423790 100644 --- a/packages/@glimmer/interfaces/index.d.ts +++ b/packages/@glimmer/interfaces/index.d.ts @@ -15,7 +15,6 @@ export * from './lib/managers.js'; export * from './lib/program.js'; export * from './lib/references.js'; export * from './lib/runtime.js'; -export * from './lib/runtime/vm.js'; export * from './lib/serialize.js'; export * from './lib/stack.js'; export * from './lib/tags.js'; diff --git a/packages/@glimmer/interfaces/lib/dom/attributes.d.ts b/packages/@glimmer/interfaces/lib/dom/attributes.d.ts index 82adb3bcb6..3518828281 100644 --- a/packages/@glimmer/interfaces/lib/dom/attributes.d.ts +++ b/packages/@glimmer/interfaces/lib/dom/attributes.d.ts @@ -12,23 +12,30 @@ import type { SimpleText, } from './simple.js'; -export interface LiveBlock extends Bounds { +export interface AppendingBlock extends Bounds { openElement(element: SimpleElement): void; closeElement(): void; didAppendNode(node: SimpleNode): void; didAppendBounds(bounds: Bounds): void; - finalize(stack: ElementBuilder): void; + finalize(stack: TreeBuilder): void; } -export interface SimpleLiveBlock extends LiveBlock { - parentElement(): SimpleElement; - firstNode(): SimpleNode; - lastNode(): SimpleNode; -} - -export type RemoteLiveBlock = SimpleLiveBlock; - -export interface UpdatableBlock extends SimpleLiveBlock { +/** + * A `FixedBlock` is a block that is only rendered once, during initial render. Its *children* may + * change during the updating phase, and this may cause its *bounds* to change, but the block itself + * remains stable. + */ +export interface FixedBlock extends AppendingBlock {} + +/** + * A `ResettableBlock` can be reset during the updating phase and rendered again. + * + * This occurs for two reasons: + * + * 1. The block represents an element in a list, and the element has been removed + * 2. The block represents a conditional, and the condition has changed + */ +export interface ResettableBlock extends FixedBlock { reset(env: Environment): Nullable; } @@ -37,8 +44,8 @@ export interface DOMStack { element: SimpleElement, guid: string, insertBefore: Maybe - ): RemoteLiveBlock; - popRemoteElement(): RemoteLiveBlock; + ): FixedBlock; + popRemoteElement(): FixedBlock; popElement(): void; openElement(tag: string, _operations?: ElementOperations): SimpleElement; flushElement(modifiers: Nullable): void; @@ -77,7 +84,7 @@ export interface TreeOperations { declare const CURSOR_STACK: unique symbol; export type CursorStackSymbol = typeof CURSOR_STACK; -export interface ElementBuilder extends Cursor, DOMStack, TreeOperations { +export interface TreeBuilder extends Cursor, DOMStack, TreeOperations { [CURSOR_STACK]: Stack; nextSibling: Nullable; @@ -87,12 +94,12 @@ export interface ElementBuilder extends Cursor, DOMStack, TreeOperations { element: SimpleElement; hasBlocks: boolean; - debugBlocks(): LiveBlock[]; + debugBlocks(): AppendingBlock[]; - pushSimpleBlock(): LiveBlock; - pushUpdatableBlock(): UpdatableBlock; - pushBlockList(list: Bounds[]): LiveBlock; - popBlock(): LiveBlock; + pushAppendingBlock(): AppendingBlock; + pushResettableBlock(): ResettableBlock; + pushBlockList(list: Bounds[]): AppendingBlock; + popBlock(): AppendingBlock; didAppendBounds(bounds: Bounds): void; } @@ -105,6 +112,6 @@ export interface AttributeCursor { export interface AttributeOperation { attribute: AttributeCursor; - set(dom: ElementBuilder, value: unknown, env: Environment): void; + set(dom: TreeBuilder, value: unknown, env: Environment): void; update(value: unknown, env: Environment): void; } diff --git a/packages/@glimmer/interfaces/lib/managers/internal/component.d.ts b/packages/@glimmer/interfaces/lib/managers/internal/component.d.ts index 3ac36eb4b9..b75da461da 100644 --- a/packages/@glimmer/interfaces/lib/managers/internal/component.d.ts +++ b/packages/@glimmer/interfaces/lib/managers/internal/component.d.ts @@ -2,6 +2,7 @@ import type { ComponentInstanceState, PreparedArguments } from '../../components import type { Destroyable, Nullable } from '../../core.js'; import type { Bounds } from '../../dom/bounds.js'; import type { SimpleElement } from '../../dom/simple.js'; +import type { ClassicResolver } from '../../program.js'; import type { Reference } from '../../references.js'; import type { Owner } from '../../runtime.js'; import type { CapturedArguments, VMArguments } from '../../runtime/arguments.js'; @@ -9,7 +10,6 @@ import type { RenderNode } from '../../runtime/debug-render-tree.js'; import type { ElementOperations } from '../../runtime/element.js'; import type { Environment } from '../../runtime/environment.js'; import type { DynamicScope } from '../../runtime/scope.js'; -import type { RuntimeResolver } from '../../serialize.js'; import type { CompilableProgram } from '../../template.js'; import type { ProgramSymbolTable } from '../../tier1/symbol-table.js'; @@ -237,7 +237,7 @@ export interface WithUpdateHook export interface WithDynamicLayout< I = ComponentInstanceState, - R extends RuntimeResolver = RuntimeResolver, + R extends ClassicResolver = ClassicResolver, > extends InternalComponentManager { // Return the compiled layout to use for this component. This is called // *after* the component instance has been created, because you might diff --git a/packages/@glimmer/interfaces/lib/program.d.ts b/packages/@glimmer/interfaces/lib/program.d.ts index 3500ca61d2..536e98c19e 100644 --- a/packages/@glimmer/interfaces/lib/program.d.ts +++ b/packages/@glimmer/interfaces/lib/program.d.ts @@ -1,12 +1,13 @@ import type { Encoder } from './compile/index.js'; import type { ComponentDefinition, ComponentDefinitionState } from './components.js'; -import type { HelperDefinitionState } from './runtime.js'; +import type { Nullable } from './core.js'; +import type { Environment, HelperDefinitionState, Owner, Program } from './runtime.js'; import type { ModifierDefinitionState } from './runtime/modifier.js'; -import type { CompileTimeResolver, ResolvedComponentDefinition } from './serialize.js'; -import type { ContainingMetadata, STDLib, Template } from './template.js'; +import type { ResolvedComponentDefinition } from './serialize.js'; +import type { BlockMetadata, STDLib, Template } from './template.js'; import type { SomeVmOp, VmMachineOp, VmOp } from './vm-opcodes.js'; -export type CreateRuntimeOp = (heap: CompileTimeHeap) => RuntimeOp; +export type CreateRuntimeOp = (heap: ProgramHeap) => RuntimeOp; export interface RuntimeOp { offset: number; @@ -24,11 +25,7 @@ export interface SerializedHeap { handle: number; } -export interface OpcodeHeap { - getbyaddr(address: number): number; -} - -export interface CompileTimeHeap extends OpcodeHeap { +export interface ProgramHeap { pushRaw(value: number): void; pushOp(name: VmOp, op1?: number, op2?: number, op3?: number): void; pushMachine(name: VmMachineOp, op1?: number, op2?: number, op3?: number): void; @@ -44,25 +41,36 @@ export interface CompileTimeHeap extends OpcodeHeap { setbyaddr(address: number, value: number): void; } -export interface RuntimeHeap extends OpcodeHeap { - getaddr(handle: number): number; - sizeof(handle: number): number; -} - -export interface CompileTimeCompilationContext { - // The offsets to stdlib functions +/** + * The `EvaluationContext` is the context that remains the same across all of the templates and + * evaluations in a single program. + * + * Note that multiple programs can co-exist on the same page, sharing tracking logic (and the + * global tracking context) but having different *evaluation* contexts. + */ +export interface EvaluationContext { + /** + * The program's environment, which contains customized framework behavior. + */ + readonly env: Environment; + /** + * The compiled program itself: the constants and heap + */ + readonly program: Program; + /** + * The offsets to stdlib functions + */ readonly stdlib: STDLib; - + /** + * A framework-specified resolver for resolving free variables in classic templates. + * + * A strict component can invoke a classic component and vice versa, but only classic components + * will use the resolver. If no resolver is available in the `ProgramContext`, only strict components + * will compile and run. + */ + readonly resolver: Nullable; + // Create a runtime op from the heap readonly createOp: CreateRuntimeOp; - - // Interned constants - readonly constants: CompileTimeConstants & ResolutionTimeConstants; - - // The mechanism of resolving names to values at compile-time - readonly resolver: CompileTimeResolver; - - // The heap that the program is serializing into - readonly heap: CompileTimeHeap; } /** @@ -70,10 +78,10 @@ export interface CompileTimeCompilationContext { * along the static information associated with the entire * template when compiling blocks nested inside of it. */ -export interface TemplateCompilationContext { - readonly program: CompileTimeCompilationContext; +export interface CompilationContext { + readonly evaluation: EvaluationContext; readonly encoder: Encoder; - readonly meta: ContainingMetadata; + readonly meta: BlockMetadata; } export type EMPTY_ARRAY = Array>; @@ -134,14 +142,21 @@ export interface ResolutionTimeConstants { ): ComponentDefinition; } -export interface RuntimeConstants { +export interface ReadonlyConstants { getValue(handle: number): T; getArray(handle: number): T[]; } -export type JitConstants = CompileTimeConstants & ResolutionTimeConstants & RuntimeConstants; +export type ProgramConstants = CompileTimeConstants & ResolutionTimeConstants & ReadonlyConstants; + +export interface ClassicResolver { + lookupHelper?(name: string, owner: O): Nullable; + lookupModifier?(name: string, owner: O): Nullable; + lookupComponent?(name: string, owner: O): Nullable; -export interface CompileTimeArtifacts { - heap: CompileTimeHeap; - constants: CompileTimeConstants & ResolutionTimeConstants; + // TODO: These are used to lookup keywords that are implemented as helpers/modifiers. + // We should try to figure out a cleaner way to do this. + lookupBuiltInHelper?(name: string): Nullable; + lookupBuiltInModifier?(name: string): Nullable; + lookupComponent?(name: string, owner: O): Nullable; } diff --git a/packages/@glimmer/interfaces/lib/runtime.d.ts b/packages/@glimmer/interfaces/lib/runtime.d.ts index 33a51844ad..e1dde92c94 100644 --- a/packages/@glimmer/interfaces/lib/runtime.d.ts +++ b/packages/@glimmer/interfaces/lib/runtime.d.ts @@ -3,8 +3,11 @@ export * from './runtime/debug-render-tree.js'; export * from './runtime/element.js'; export * from './runtime/environment.js'; export * from './runtime/helper.js'; +export * from './runtime/local-debug.js'; export * from './runtime/modifier.js'; export * from './runtime/owner.js'; export * from './runtime/render.js'; export * from './runtime/runtime.js'; export * from './runtime/scope.js'; +export * from './runtime/vm.js'; +export * from './runtime/vm-state.js'; diff --git a/packages/@glimmer/interfaces/lib/runtime/environment.d.ts b/packages/@glimmer/interfaces/lib/runtime/environment.d.ts index 25750fb5f5..26d0a76e1e 100644 --- a/packages/@glimmer/interfaces/lib/runtime/environment.d.ts +++ b/packages/@glimmer/interfaces/lib/runtime/environment.d.ts @@ -8,8 +8,10 @@ import type { import type { Nullable } from '../core.js'; import type { GlimmerTreeChanges, GlimmerTreeConstruction } from '../dom/changes.js'; import type { WithCreateInstance } from '../managers.js'; +import type { ClassicResolver } from '../program.js'; import type { DebugRenderTree } from './debug-render-tree.js'; import type { ModifierInstance } from './modifier.js'; +import type { Program } from './runtime.js'; export interface EnvironmentOptions { document?: SimpleDocument; @@ -48,3 +50,9 @@ export interface Environment { // eslint-disable-next-line @typescript-eslint/no-explicit-any isArgumentCaptureError?: ((error: any) => boolean) | undefined; } + +export interface RuntimeOptions { + readonly env: Environment; + readonly program: Program; + readonly resolver: Nullable; +} diff --git a/packages/@glimmer/interfaces/lib/runtime/local-debug.d.ts b/packages/@glimmer/interfaces/lib/runtime/local-debug.d.ts new file mode 100644 index 0000000000..a85cff2b20 --- /dev/null +++ b/packages/@glimmer/interfaces/lib/runtime/local-debug.d.ts @@ -0,0 +1,11 @@ +import type { Nullable } from '../core.js'; +import type { BlockMetadata } from '../template.js'; + +type Handle = number; + +export interface DebugTemplates { + readonly active: Nullable; + register(handle: Handle, metadata: BlockMetadata): void; + willCall(handle: Handle): void; + return(): void; +} diff --git a/packages/@glimmer/interfaces/lib/runtime/runtime.d.ts b/packages/@glimmer/interfaces/lib/runtime/runtime.d.ts index e1adcbb980..949a958e99 100644 --- a/packages/@glimmer/interfaces/lib/runtime/runtime.d.ts +++ b/packages/@glimmer/interfaces/lib/runtime/runtime.d.ts @@ -1,28 +1,13 @@ -import type { JitConstants, RuntimeHeap, RuntimeOp } from '../program.js'; -import type { RuntimeResolver } from '../serialize.js'; -import type { Environment } from './environment.js'; +import type { ProgramConstants, ProgramHeap, RuntimeOp } from '../program.js'; -/** - The Runtime is the set of static structures that contain the compiled - code and any host configuration. - - The contents of the Runtime do not change as the VM executes, unlike - the VM state. - */ -export interface RuntimeContext { - env: Environment; - program: RuntimeProgram; - resolver: RuntimeResolver; -} - -export interface RuntimeProgram { - readonly constants: JitConstants; - readonly heap: RuntimeHeap; +export interface Program { + readonly constants: ProgramConstants; + readonly heap: ProgramHeap; opcode(offset: number): RuntimeOp; } export interface RuntimeArtifacts { - readonly constants: JitConstants; - readonly heap: RuntimeHeap; + readonly constants: ProgramConstants; + readonly heap: ProgramHeap; } diff --git a/packages/@glimmer/interfaces/lib/runtime/vm-state.d.ts b/packages/@glimmer/interfaces/lib/runtime/vm-state.d.ts new file mode 100644 index 0000000000..30ab689c19 --- /dev/null +++ b/packages/@glimmer/interfaces/lib/runtime/vm-state.d.ts @@ -0,0 +1,11 @@ +export type SyscallRegisters = [ + $pc: null, + $ra: null, + $fp: null, + $sp: null, + $s0: unknown, + $s1: unknown, + $t0: unknown, + $t1: unknown, + $v0: unknown, +]; diff --git a/packages/@glimmer/interfaces/lib/serialize.d.ts b/packages/@glimmer/interfaces/lib/serialize.d.ts index ee6a89dd8e..1ae01d17a3 100644 --- a/packages/@glimmer/interfaces/lib/serialize.d.ts +++ b/packages/@glimmer/interfaces/lib/serialize.d.ts @@ -56,7 +56,6 @@ import type { } from './components.js'; import type { Nullable } from './core.js'; import type { InternalComponentManager } from './managers.js'; -import type { HelperDefinitionState, ModifierDefinitionState, Owner } from './runtime.js'; import type { CompilableProgram, Template } from './template.js'; export interface CompileTimeComponent { @@ -74,18 +73,3 @@ export interface ResolvedComponentDefinition< manager: M; template: Template | null; } - -export interface CompileTimeResolver { - lookupHelper(name: string, owner: O): Nullable; - lookupModifier(name: string, owner: O): Nullable; - lookupComponent(name: string, owner: O): Nullable; - - // TODO: These are used to lookup keywords that are implemented as helpers/modifiers. - // We should try to figure out a cleaner way to do this. - lookupBuiltInHelper(name: string): Nullable; - lookupBuiltInModifier(name: string): Nullable; -} - -export interface RuntimeResolver { - lookupComponent(name: string, owner: O): Nullable; -} diff --git a/packages/@glimmer/interfaces/lib/template.d.ts b/packages/@glimmer/interfaces/lib/template.d.ts index 6dd154779a..013120f952 100644 --- a/packages/@glimmer/interfaces/lib/template.d.ts +++ b/packages/@glimmer/interfaces/lib/template.d.ts @@ -3,7 +3,7 @@ import type { EncoderError } from './compile/encoder.js'; import type { Operand, SerializedInlineBlock, SerializedTemplateBlock } from './compile/index.js'; import type { Nullable } from './core.js'; import type { InternalComponentCapabilities } from './managers/internal/component.js'; -import type { CompileTimeCompilationContext, ConstantPool, SerializedHeap } from './program.js'; +import type { ConstantPool, EvaluationContext, SerializedHeap } from './program.js'; import type { Owner } from './runtime.js'; import type { BlockSymbolTable, ProgramSymbolTable, SymbolTable } from './tier1/symbol-table.js'; @@ -93,7 +93,18 @@ export interface NamedBlocks { names: string[]; } -export interface ContainingMetadata { +export interface CompilerArtifacts { + heap: SerializedHeap; + constants: ConstantPool; +} + +export interface CompilableTemplate { + symbolTable: S; + meta: BlockMetadata; + compile(context: EvaluationContext): HandleResult; +} + +export interface BlockMetadata { evalSymbols: Nullable; upvars: Nullable; debugSymbols?: string[] | undefined; @@ -103,13 +114,3 @@ export interface ContainingMetadata { owner: Owner | null; size: number; } - -export interface CompilerArtifacts { - heap: SerializedHeap; - constants: ConstantPool; -} - -export interface CompilableTemplate { - symbolTable: S; - compile(context: CompileTimeCompilationContext): HandleResult; -} diff --git a/packages/@glimmer/node/lib/serialize-builder.ts b/packages/@glimmer/node/lib/serialize-builder.ts index 15080c3257..1d2f7e8c36 100644 --- a/packages/@glimmer/node/lib/serialize-builder.ts +++ b/packages/@glimmer/node/lib/serialize-builder.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-enum-comparison */ import type { Bounds, - ElementBuilder, Environment, Maybe, ModifierInstance, @@ -9,16 +8,17 @@ import type { SimpleElement, SimpleNode, SimpleText, + TreeBuilder, } from '@glimmer/interfaces'; import type { RemoteLiveBlock } from '@glimmer/runtime'; -import { ConcreteBounds, NewElementBuilder } from '@glimmer/runtime'; +import { ConcreteBounds, NewTreeBuilder } from '@glimmer/runtime'; const TEXT_NODE = 3; const NEEDS_EXTRA_CLOSE = new WeakMap(); function currentNode( - cursor: ElementBuilder | { element: SimpleElement; nextSibling: SimpleNode } + cursor: TreeBuilder | { element: SimpleElement; nextSibling: SimpleNode } ): Nullable { let { element, nextSibling } = cursor; @@ -29,7 +29,7 @@ function currentNode( } } -class SerializeBuilder extends NewElementBuilder implements ElementBuilder { +class SerializeBuilder extends NewTreeBuilder implements TreeBuilder { private serializeBlockDepth = 0; override __openBlock(): void { @@ -143,6 +143,6 @@ class SerializeBuilder extends NewElementBuilder implements ElementBuilder { export function serializeBuilder( env: Environment, cursor: { element: SimpleElement; nextSibling: Nullable } -): ElementBuilder { +): TreeBuilder { return SerializeBuilder.forInitialRender(env, cursor); } diff --git a/packages/@glimmer/opcode-compiler/lib/compilable-template.ts b/packages/@glimmer/opcode-compiler/lib/compilable-template.ts index 471877b60c..129c711dfa 100644 --- a/packages/@glimmer/opcode-compiler/lib/compilable-template.ts +++ b/packages/@glimmer/opcode-compiler/lib/compilable-template.ts @@ -1,11 +1,11 @@ import type { + BlockMetadata, BlockSymbolTable, BuilderOp, CompilableBlock, CompilableProgram, CompilableTemplate, - CompileTimeCompilationContext, - ContainingMetadata, + EvaluationContext, HandleResult, HighLevelOp, LayoutWithContext, @@ -34,7 +34,7 @@ class CompilableTemplateImpl implements CompilableTemplat constructor( readonly statements: WireFormat.Statement[], - readonly meta: ContainingMetadata, + readonly meta: BlockMetadata, // Part of CompilableTemplate readonly symbolTable: S, // Used for debugging @@ -42,7 +42,7 @@ class CompilableTemplateImpl implements CompilableTemplat ) {} // Part of CompilableTemplate - compile(context: CompileTimeCompilationContext): HandleResult { + compile(context: EvaluationContext): HandleResult { return maybeCompile(this, context); } } @@ -62,7 +62,7 @@ export function compilable(layout: LayoutWithContext, moduleName: string): Compi function maybeCompile( compilable: CompilableTemplateImpl, - context: CompileTimeCompilationContext + context: EvaluationContext ): HandleResult { if (compilable.compiled !== null) return compilable.compiled; @@ -78,19 +78,16 @@ function maybeCompile( export function compileStatements( statements: Statement[], - meta: ContainingMetadata, - syntaxContext: CompileTimeCompilationContext + meta: BlockMetadata, + syntaxContext: EvaluationContext ): HandleResult { let sCompiler = STATEMENTS; let context = templateCompilationContext(syntaxContext, meta); - let { - encoder, - program: { constants, resolver }, - } = context; + let { encoder, evaluation } = context; function pushOp(...op: BuilderOp | HighLevelOp | HighLevelStatementOp) { - encodeOp(encoder, constants, resolver, meta, op as BuilderOp | HighLevelOp); + encodeOp(encoder, evaluation, meta, op as BuilderOp | HighLevelOp); } for (const statement of statements) { @@ -108,7 +105,7 @@ export function compileStatements( export function compilableBlock( block: SerializedInlineBlock | SerializedBlock, - containing: ContainingMetadata + containing: BlockMetadata ): CompilableBlock { return new CompilableTemplateImpl(block[0], containing, { parameters: block[1] || (EMPTY_ARRAY as number[]), diff --git a/packages/@glimmer/opcode-compiler/lib/compiler.ts b/packages/@glimmer/opcode-compiler/lib/compiler.ts index fb259fae9e..726931e4bf 100644 --- a/packages/@glimmer/opcode-compiler/lib/compiler.ts +++ b/packages/@glimmer/opcode-compiler/lib/compiler.ts @@ -1,14 +1,14 @@ -import type { HandleResult, TemplateCompilationContext } from '@glimmer/interfaces'; +import type { CompilationContext, HandleResult } from '@glimmer/interfaces'; import { debugSlice } from '@glimmer/debug'; import { extractHandle } from '@glimmer/debug-util'; import { LOCAL_TRACE_LOGGING } from '@glimmer/local-debug-flags'; -export let debugCompiler: (context: TemplateCompilationContext, handle: HandleResult) => void; +export let debugCompiler: (context: CompilationContext, handle: HandleResult) => void; if (LOCAL_TRACE_LOGGING) { - debugCompiler = (context: TemplateCompilationContext, result: HandleResult) => { + debugCompiler = (context: CompilationContext, result: HandleResult) => { let handle = extractHandle(result); - let { heap } = context.program; + let { heap } = context.evaluation.program; let start = heap.getaddr(handle); let end = start + heap.sizeof(handle); diff --git a/packages/@glimmer/opcode-compiler/lib/opcode-builder/context.ts b/packages/@glimmer/opcode-compiler/lib/opcode-builder/context.ts index f82b8f61ce..3f5bc12b5b 100644 --- a/packages/@glimmer/opcode-compiler/lib/opcode-builder/context.ts +++ b/packages/@glimmer/opcode-compiler/lib/opcode-builder/context.ts @@ -1,31 +1,15 @@ -import type { - CompileTimeArtifacts, - CompileTimeCompilationContext, - CompileTimeResolver, - ContainingMetadata, - CreateRuntimeOp, - TemplateCompilationContext, -} from '@glimmer/interfaces'; +import type { BlockMetadata, CompilationContext, EvaluationContext } from '@glimmer/interfaces'; -import { CompileTimeCompilationContextImpl } from '../program-context'; import { EncoderImpl } from './encoder'; -export function programCompilationContext( - artifacts: CompileTimeArtifacts, - resolver: CompileTimeResolver, - createOp: CreateRuntimeOp -): CompileTimeCompilationContext { - return new CompileTimeCompilationContextImpl(artifacts, resolver, createOp); -} - export function templateCompilationContext( - program: CompileTimeCompilationContext, - meta: ContainingMetadata -): TemplateCompilationContext { - let encoder = new EncoderImpl(program.heap, meta, program.stdlib); + evaluation: EvaluationContext, + meta: BlockMetadata +): CompilationContext { + let encoder = new EncoderImpl(evaluation.program.heap, meta, evaluation.stdlib); return { - program, + evaluation, encoder, meta, }; diff --git a/packages/@glimmer/opcode-compiler/lib/opcode-builder/encoder.ts b/packages/@glimmer/opcode-compiler/lib/opcode-builder/encoder.ts index 0305c0f96e..c02c8bdf0e 100644 --- a/packages/@glimmer/opcode-compiler/lib/opcode-builder/encoder.ts +++ b/packages/@glimmer/opcode-compiler/lib/opcode-builder/encoder.ts @@ -1,18 +1,17 @@ import type { + BlockMetadata, BuilderOp, BuilderOpcode, CompileTimeConstants, - CompileTimeHeap, - CompileTimeResolver, - ContainingMetadata, Dict, Encoder, EncoderError, + EvaluationContext, HandleResult, HighLevelOp, InstructionEncoder, Operand, - ResolutionTimeConstants, + ProgramHeap, SingleBuilderOperand, STDLib, } from '@glimmer/interfaces'; @@ -45,7 +44,7 @@ export class Labels { this.targets.push({ at, target }); } - patch(heap: CompileTimeHeap): void { + patch(heap: ProgramHeap): void { let { targets, labels } = this; for (const { at, target } of targets) { @@ -60,11 +59,15 @@ export class Labels { export function encodeOp( encoder: Encoder, - constants: CompileTimeConstants & ResolutionTimeConstants, - resolver: CompileTimeResolver, - meta: ContainingMetadata, + context: EvaluationContext, + meta: BlockMetadata, op: BuilderOp | HighLevelOp ): void { + let { + program: { constants }, + resolver, + } = context; + if (isBuilderOpcode(op[0])) { let [type, ...operands] = op; encoder.push(constants, type, ...(operands as SingleBuilderOperand[])); @@ -124,8 +127,8 @@ export class EncoderImpl implements Encoder { private handle: number; constructor( - private heap: CompileTimeHeap, - private meta: ContainingMetadata, + private heap: ProgramHeap, + private meta: BlockMetadata, private stdlib?: STDLib ) { this.handle = heap.malloc(); diff --git a/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/resolution.ts b/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/resolution.ts index 704299e71a..63457647e0 100644 --- a/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/resolution.ts +++ b/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/resolution.ts @@ -1,9 +1,10 @@ import type { - CompileTimeConstants, - CompileTimeResolver, - ContainingMetadata, + BlockMetadata, + ClassicResolver, Expressions, + Nullable, Owner, + ProgramConstants, ResolutionTimeConstants, ResolveComponentOp, ResolveComponentOrHelperOp, @@ -45,12 +46,12 @@ export const isGetFreeComponentOrHelper = makeResolutionTypeVerifier( SexpOpcodes.GetFreeAsComponentOrHelperHead ); -interface ResolvedContainingMetadata extends ContainingMetadata { +interface ResolvedBlockMetadata extends BlockMetadata { owner: Owner; upvars: string[]; } -function assertResolverInvariants(meta: ContainingMetadata): ResolvedContainingMetadata { +function assertResolverInvariants(meta: BlockMetadata): ResolvedBlockMetadata { if (import.meta.env.DEV) { if (!meta.upvars) { throw new Error( @@ -65,7 +66,7 @@ function assertResolverInvariants(meta: ContainingMetadata): ResolvedContainingM } } - return meta as unknown as ResolvedContainingMetadata; + return meta as unknown as ResolvedBlockMetadata; } /** @@ -74,9 +75,9 @@ function assertResolverInvariants(meta: ContainingMetadata): ResolvedContainingM * */ export function resolveComponent( - resolver: CompileTimeResolver, - constants: CompileTimeConstants & ResolutionTimeConstants, - meta: ContainingMetadata, + resolver: Nullable, + constants: ProgramConstants, + meta: BlockMetadata, [, expr, then]: ResolveComponentOp ): void { assert(isGetFreeComponent(expr), 'Attempted to resolve a component with incorrect opcode'); @@ -111,7 +112,7 @@ export function resolveComponent( let { upvars, owner } = assertResolverInvariants(meta); let name = unwrap(upvars[expr[1]]); - let definition = resolver.lookupComponent(name, owner)!; + let definition = resolver?.lookupComponent?.(name, owner) ?? null; if (import.meta.env.DEV && (typeof definition !== 'object' || definition === null)) { assert(!meta.isStrictMode, 'Strict mode errors should already be handled at compile time'); @@ -121,7 +122,7 @@ export function resolveComponent( ); } - then(constants.resolvedComponent(definition, name)); + then(constants.resolvedComponent(definition!, name)); } } @@ -130,9 +131,9 @@ export function resolveComponent( * (helper arg) */ export function resolveHelper( - resolver: CompileTimeResolver, - constants: CompileTimeConstants & ResolutionTimeConstants, - meta: ContainingMetadata, + resolver: Nullable, + constants: ProgramConstants, + meta: BlockMetadata, [, expr, then]: ResolveHelperOp ): void { assert(isGetFreeHelper(expr), 'Attempted to resolve a helper with incorrect opcode'); @@ -154,7 +155,7 @@ export function resolveHelper( let { upvars, owner } = assertResolverInvariants(meta); let name = unwrap(upvars[expr[1]]); - let helper = resolver.lookupHelper(name, owner)!; + let helper = resolver?.lookupHelper?.(name, owner) ?? null; if (import.meta.env.DEV && helper === null) { assert(!meta.isStrictMode, 'Strict mode errors should already be handled at compile time'); @@ -164,7 +165,7 @@ export function resolveHelper( ); } - then(constants.helper(helper, name)); + then(constants.helper(helper!, name)); } } @@ -174,9 +175,9 @@ export function resolveHelper( * */ export function resolveModifier( - resolver: CompileTimeResolver, - constants: CompileTimeConstants & ResolutionTimeConstants, - meta: ContainingMetadata, + resolver: Nullable, + constants: ProgramConstants, + meta: BlockMetadata, [, expr, then]: ResolveModifierOp ): void { assert(isGetFreeModifier(expr), 'Attempted to resolve a modifier with incorrect opcode'); @@ -193,7 +194,7 @@ export function resolveModifier( } else if (type === SexpOpcodes.GetStrictKeyword) { let { upvars } = assertResolverInvariants(meta); let name = unwrap(upvars[expr[1]]); - let modifier = resolver.lookupBuiltInModifier(name); + let modifier = resolver?.lookupBuiltInModifier?.(name) ?? null; if (import.meta.env.DEV && modifier === null) { assert(!meta.isStrictMode, 'Strict mode errors should already be handled at compile time'); @@ -207,7 +208,7 @@ export function resolveModifier( } else { let { upvars, owner } = assertResolverInvariants(meta); let name = unwrap(upvars[expr[1]]); - let modifier = resolver.lookupModifier(name, owner)!; + let modifier = resolver?.lookupModifier?.(name, owner) ?? null; if (import.meta.env.DEV && modifier === null) { assert(!meta.isStrictMode, 'Strict mode errors should already be handled at compile time'); @@ -217,7 +218,7 @@ export function resolveModifier( ); } - then(constants.modifier(modifier)); + then(constants.modifier(modifier!)); } } @@ -225,9 +226,9 @@ export function resolveModifier( * {{component-or-helper arg}} */ export function resolveComponentOrHelper( - resolver: CompileTimeResolver, - constants: CompileTimeConstants & ResolutionTimeConstants, - meta: ContainingMetadata, + resolver: Nullable, + constants: ProgramConstants, + meta: BlockMetadata, [, expr, { ifComponent, ifHelper }]: ResolveComponentOrHelperOp ): void { assert( @@ -282,12 +283,12 @@ export function resolveComponentOrHelper( let { upvars, owner } = assertResolverInvariants(meta); let name = unwrap(upvars[expr[1]]); - let definition = resolver.lookupComponent(name, owner); + let definition = resolver?.lookupComponent?.(name, owner) ?? null; if (definition !== null) { ifComponent(constants.resolvedComponent(definition, name)); } else { - let helper = resolver.lookupHelper(name, owner); + let helper = resolver?.lookupHelper?.(name, owner) ?? null; if (import.meta.env.DEV && helper === null) { assert(!meta.isStrictMode, 'Strict mode errors should already be handled at compile time'); @@ -306,9 +307,9 @@ export function resolveComponentOrHelper( * {{maybeHelperOrComponent}} */ export function resolveOptionalComponentOrHelper( - resolver: CompileTimeResolver, - constants: CompileTimeConstants & ResolutionTimeConstants, - meta: ContainingMetadata, + resolver: Nullable, + constants: ProgramConstants, + meta: BlockMetadata, [, expr, { ifComponent, ifHelper, ifValue }]: ResolveOptionalComponentOrHelperOp ): void { assert( @@ -361,14 +362,14 @@ export function resolveOptionalComponentOrHelper( let { upvars, owner } = assertResolverInvariants(meta); let name = unwrap(upvars[expr[1]]); - let definition = resolver.lookupComponent(name, owner); + let definition = resolver?.lookupComponent?.(name, owner) ?? null; if (definition !== null) { ifComponent(constants.resolvedComponent(definition, name)); return; } - let helper = resolver.lookupHelper(name, owner); + let helper = resolver?.lookupHelper?.(name, owner) ?? null; if (helper !== null) { ifHelper(constants.helper(helper, name)); @@ -378,15 +379,15 @@ export function resolveOptionalComponentOrHelper( function lookupBuiltInHelper( expr: Expressions.GetStrictFree, - resolver: CompileTimeResolver, - meta: ContainingMetadata, + resolver: Nullable, + meta: BlockMetadata, constants: ResolutionTimeConstants, type: string ): number { let { upvars } = assertResolverInvariants(meta); let name = unwrap(upvars[expr[1]]); - let helper = resolver.lookupBuiltInHelper(name); + let helper = resolver?.lookupBuiltInHelper?.(name) ?? null; if (import.meta.env.DEV && helper === null) { assert(!meta.isStrictMode, 'Strict mode errors should already be handled at compile time'); diff --git a/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/shared.ts b/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/shared.ts index 1526dad5ab..b3febe10b5 100644 --- a/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/shared.ts +++ b/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/shared.ts @@ -1,5 +1,5 @@ import type { - ContainingMetadata, + BlockMetadata, LayoutWithContext, NamedBlocks, Nullable, @@ -105,7 +105,7 @@ export function CompilePositional( return positional.length; } -export function meta(layout: LayoutWithContext): ContainingMetadata { +export function meta(layout: LayoutWithContext): BlockMetadata { let [, symbols, , upvars, debugSymbols] = layout.block; return { diff --git a/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/stdlib.ts b/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/stdlib.ts index ce11df4eba..72feb8f2fc 100644 --- a/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/stdlib.ts +++ b/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/stdlib.ts @@ -1,9 +1,4 @@ -import type { - BuilderOp, - CompileTimeCompilationContext, - ContainingMetadata, - HighLevelOp, -} from '@glimmer/interfaces'; +import type { BlockMetadata, BuilderOp, EvaluationContext, HighLevelOp } from '@glimmer/interfaces'; import { VM_APPEND_DOCUMENT_FRAGMENT_OP, VM_APPEND_HTML_OP, @@ -100,7 +95,7 @@ export function StdAppend( ); } -export function compileStd(context: CompileTimeCompilationContext): StdLib { +export function compileStd(context: EvaluationContext): StdLib { let mainHandle = build(context, (op) => main(op)); let trustingGuardedNonDynamicAppend = build(context, (op) => StdAppend(op, true, null)); let cautiousGuardedNonDynamicAppend = build(context, (op) => StdAppend(op, false, null)); @@ -121,7 +116,7 @@ export function compileStd(context: CompileTimeCompilationContext): StdLib { ); } -export const STDLIB_META: ContainingMetadata = { +export const STDLIB_META: BlockMetadata = { evalSymbols: null, upvars: null, moduleName: 'stdlib', @@ -133,15 +128,11 @@ export const STDLIB_META: ContainingMetadata = { size: 0, }; -function build( - program: CompileTimeCompilationContext, - builder: (op: PushStatementOp) => void -): number { - let { constants, heap, resolver } = program; - let encoder = new EncoderImpl(heap, STDLIB_META); +function build(evaluation: EvaluationContext, builder: (op: PushStatementOp) => void): number { + let encoder = new EncoderImpl(evaluation.program.heap, STDLIB_META); function pushOp(...op: BuilderOp | HighLevelOp | HighLevelStatementOp) { - encodeOp(encoder, constants, resolver, STDLIB_META, op as BuilderOp | HighLevelOp); + encodeOp(encoder, evaluation, STDLIB_META, op as BuilderOp | HighLevelOp); } builder(pushOp); diff --git a/packages/@glimmer/opcode-compiler/lib/program-context.ts b/packages/@glimmer/opcode-compiler/lib/program-context.ts index 23bdb3a2e2..1214eb303c 100644 --- a/packages/@glimmer/opcode-compiler/lib/program-context.ts +++ b/packages/@glimmer/opcode-compiler/lib/program-context.ts @@ -1,28 +1,40 @@ import type { - CompileTimeArtifacts, - CompileTimeCompilationContext, - CompileTimeConstants, - CompileTimeHeap, - CompileTimeResolver, + ClassicResolver, CreateRuntimeOp, - ResolutionTimeConstants, + Environment, + EvaluationContext, + Nullable, + Program, + ProgramConstants, + ProgramHeap, + RuntimeArtifacts, + RuntimeOptions, STDLib, } from '@glimmer/interfaces'; import { compileStd } from './opcode-builder/helpers/stdlib'; -export class CompileTimeCompilationContextImpl implements CompileTimeCompilationContext { - readonly constants: CompileTimeConstants & ResolutionTimeConstants; - readonly heap: CompileTimeHeap; +export class EvaluationContextImpl implements EvaluationContext { + readonly constants: ProgramConstants; + readonly heap: ProgramHeap; + readonly resolver: Nullable; readonly stdlib: STDLib; + readonly createOp: CreateRuntimeOp; + readonly env: Environment; + readonly program: Program; constructor( - { constants, heap }: CompileTimeArtifacts, - readonly resolver: CompileTimeResolver, - readonly createOp: CreateRuntimeOp + { constants, heap }: RuntimeArtifacts, + createOp: CreateRuntimeOp, + runtime: RuntimeOptions ) { this.constants = constants; this.heap = heap; + this.resolver = runtime.resolver; + this.createOp = createOp; + this.env = runtime.env; + this.program = runtime.program; + this.stdlib = compileStd(this); } } diff --git a/packages/@glimmer/opcode-compiler/lib/wrapped-component.ts b/packages/@glimmer/opcode-compiler/lib/wrapped-component.ts index 48a69d5794..b57c2d8fd3 100644 --- a/packages/@glimmer/opcode-compiler/lib/wrapped-component.ts +++ b/packages/@glimmer/opcode-compiler/lib/wrapped-component.ts @@ -1,7 +1,8 @@ import type { + BlockMetadata, BuilderOp, CompilableProgram, - CompileTimeCompilationContext, + EvaluationContext, HandleResult, HighLevelOp, LayoutWithContext, @@ -22,6 +23,7 @@ export class WrappedBuilder implements CompilableProgram { public symbolTable: ProgramSymbolTable; private compiled: Nullable = null; private attrsBlockNumber: number; + readonly meta: BlockMetadata; constructor( private layout: LayoutWithContext, @@ -44,21 +46,20 @@ export class WrappedBuilder implements CompilableProgram { hasEval, symbols, }; + + this.meta = meta(layout); } - compile(syntax: CompileTimeCompilationContext): HandleResult { + compile(syntax: EvaluationContext): HandleResult { if (this.compiled !== null) return this.compiled; let m = meta(this.layout); let context = templateCompilationContext(syntax, m); - let { - encoder, - program: { constants, resolver }, - } = context; + let { encoder, evaluation } = context; function pushOp(...op: BuilderOp | HighLevelOp | HighLevelStatementOp) { - encodeOp(encoder, constants, resolver, m, op as BuilderOp | HighLevelOp); + encodeOp(encoder, evaluation, m, op as BuilderOp | HighLevelOp); } WrappedComponent(pushOp, this.layout, this.attrsBlockNumber); diff --git a/packages/@glimmer/program/lib/constants.ts b/packages/@glimmer/program/lib/constants.ts index a65f52fbd9..221f04b4ca 100644 --- a/packages/@glimmer/program/lib/constants.ts +++ b/packages/@glimmer/program/lib/constants.ts @@ -1,13 +1,11 @@ import type { - CompileTimeConstants, ComponentDefinition, ComponentDefinitionState, ConstantPool, HelperDefinitionState, ModifierDefinitionState, - ResolutionTimeConstants, + ProgramConstants, ResolvedComponentDefinition, - RuntimeConstants, Template, } from '@glimmer/interfaces'; import { constants } from '@glimmer/constants'; @@ -30,14 +28,33 @@ const WELL_KNOWN_EMPTY_ARRAY: unknown = Object.freeze([]); const STARTER_CONSTANTS = constants(WELL_KNOWN_EMPTY_ARRAY); const WELL_KNOWN_EMPTY_ARRAY_POSITION: number = STARTER_CONSTANTS.indexOf(WELL_KNOWN_EMPTY_ARRAY); -export class CompileTimeConstantImpl implements CompileTimeConstants { - // `0` means NULL +export class ConstantsImpl implements ProgramConstants { + protected reifiedArrs: { [key: number]: unknown[] } = { + [WELL_KNOWN_EMPTY_ARRAY_POSITION]: WELL_KNOWN_EMPTY_ARRAY as unknown[], + }; + + defaultTemplate: Template = 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 + helperDefinitionCount = 0; + modifierDefinitionCount = 0; + componentDefinitionCount = 0; - protected values: unknown[] = STARTER_CONSTANTS.slice(); - protected indexMap: Map = new Map( + private values: unknown[] = STARTER_CONSTANTS.slice(); + private indexMap: Map = new Map( this.values.map((value, index) => [value, index]) ); + private helperDefinitionCache = new WeakMap(); + + private modifierDefinitionCache = new WeakMap(); + + private componentDefinitionCache = new WeakMap< + ComponentDefinitionState | ResolvedComponentDefinition, + ComponentDefinition | null + >(); + value(value: unknown) { let indexMap = this.indexMap; let index = indexMap.get(value); @@ -67,55 +84,6 @@ export class CompileTimeConstantImpl implements CompileTimeConstants { toPool(): ConstantPool { return this.values; } -} - -export class RuntimeConstantsImpl implements RuntimeConstants { - protected values: unknown[]; - - constructor(pool: ConstantPool) { - this.values = pool; - } - - getValue(handle: number) { - return this.values[handle] as T; - } - - getArray(value: number): T[] { - let handles = this.getValue(value); - let reified: T[] = new Array(handles.length); - - for (const [i, n] of enumerate(handles)) { - reified[i] = this.getValue(n); - } - - return reified; - } -} - -export class ConstantsImpl - extends CompileTimeConstantImpl - implements RuntimeConstants, ResolutionTimeConstants -{ - protected reifiedArrs: { [key: number]: unknown[] } = { - [WELL_KNOWN_EMPTY_ARRAY_POSITION]: WELL_KNOWN_EMPTY_ARRAY as unknown[], - }; - - defaultTemplate: Template = 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 - helperDefinitionCount = 0; - modifierDefinitionCount = 0; - componentDefinitionCount = 0; - - private helperDefinitionCache = new WeakMap(); - - private modifierDefinitionCache = new WeakMap(); - - private componentDefinitionCache = new WeakMap< - ComponentDefinitionState | ResolvedComponentDefinition, - ComponentDefinition | null - >(); helper( definitionState: HelperDefinitionState, diff --git a/packages/@glimmer/program/lib/helpers.ts b/packages/@glimmer/program/lib/helpers.ts index fa5030e9ac..2c9d630687 100644 --- a/packages/@glimmer/program/lib/helpers.ts +++ b/packages/@glimmer/program/lib/helpers.ts @@ -1,11 +1,11 @@ -import type { CompileTimeArtifacts, RuntimeArtifacts } from '@glimmer/interfaces'; +import type { RuntimeArtifacts } from '@glimmer/interfaces'; import { ConstantsImpl } from './constants'; -import { HeapImpl } from './program'; +import { ProgramHeapImpl } from './program'; -export function artifacts(): CompileTimeArtifacts & RuntimeArtifacts { +export function artifacts(): RuntimeArtifacts { return { constants: new ConstantsImpl(), - heap: new HeapImpl(), + heap: new ProgramHeapImpl(), }; } diff --git a/packages/@glimmer/program/lib/opcode.ts b/packages/@glimmer/program/lib/opcode.ts index 4e983728ca..c7c9a4736e 100644 --- a/packages/@glimmer/program/lib/opcode.ts +++ b/packages/@glimmer/program/lib/opcode.ts @@ -1,9 +1,9 @@ -import type { OpcodeHeap, RuntimeOp, SomeVmOp } from '@glimmer/interfaces'; +import type { ProgramHeap, RuntimeOp, SomeVmOp } from '@glimmer/interfaces'; import { ARG_SHIFT, MACHINE_MASK, OPERAND_LEN_MASK, TYPE_MASK } from '@glimmer/vm'; export class RuntimeOpImpl implements RuntimeOp { public offset = 0; - constructor(readonly heap: OpcodeHeap) {} + constructor(readonly heap: ProgramHeap) {} get size() { let rawType = this.heap.getbyaddr(this.offset); diff --git a/packages/@glimmer/program/lib/program.ts b/packages/@glimmer/program/lib/program.ts index a8860aab9c..7d4f538841 100644 --- a/packages/@glimmer/program/lib/program.ts +++ b/packages/@glimmer/program/lib/program.ts @@ -1,12 +1,11 @@ import type { - CompileTimeHeap, - JitConstants, - RuntimeHeap, - RuntimeProgram, + Program, + ProgramConstants, + ProgramHeap, SerializedHeap, StdLibOperand, } from '@glimmer/interfaces'; -import { expect, unwrap } from '@glimmer/debug-util'; +import { unwrap } from '@glimmer/debug-util'; import { LOCAL_DEBUG } from '@glimmer/local-debug-flags'; import { MACHINE_MASK } from '@glimmer/vm'; @@ -24,36 +23,6 @@ export type StdlibPlaceholder = [number, StdLibOperand]; const PAGE_SIZE = 0x100000; -export class RuntimeHeapImpl implements RuntimeHeap { - private heap: Int32Array; - private table: number[]; - - constructor(serializedHeap: SerializedHeap) { - let { buffer, table } = serializedHeap; - this.heap = new Int32Array(buffer); - this.table = table; - } - - // 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: number): number { - return unwrap(this.table[handle]); - } - - getbyaddr(address: number): number { - return expect(this.heap[address], 'Access memory out of bounds of the heap'); - } - - sizeof(handle: number): number { - return sizeof(this.table, handle); - } -} - -export function hydrateHeap(serializedHeap: SerializedHeap): RuntimeHeap { - return new RuntimeHeapImpl(serializedHeap); -} - /** * The Heap is responsible for dynamically allocating * memory in which we read/write the VM's instructions @@ -74,7 +43,7 @@ export function hydrateHeap(serializedHeap: SerializedHeap): RuntimeHeap { * valid during the execution. This means you cannot close * over them as you will have a bad memory access exception. */ -export class HeapImpl implements CompileTimeHeap, RuntimeHeap { +export class ProgramHeapImpl implements ProgramHeap { offset = 0; private heap: Int32Array; @@ -202,14 +171,14 @@ export class HeapImpl implements CompileTimeHeap, RuntimeHeap { } } -export class RuntimeProgramImpl implements RuntimeProgram { +export class ProgramImpl implements Program { [key: number]: never; private _opcode: RuntimeOpImpl; constructor( - public constants: JitConstants, - public heap: RuntimeHeap + public constants: ProgramConstants, + public heap: ProgramHeap ) { this._opcode = new RuntimeOpImpl(this.heap); } diff --git a/packages/@glimmer/program/test/heap-test.ts b/packages/@glimmer/program/test/heap-test.ts index 7803ae9397..275c194ba6 100644 --- a/packages/@glimmer/program/test/heap-test.ts +++ b/packages/@glimmer/program/test/heap-test.ts @@ -1,10 +1,10 @@ -import { HeapImpl } from '@glimmer/program'; +import { ProgramHeapImpl } from '@glimmer/program'; QUnit.module('Heap'); QUnit.test('Can grow', (assert) => { let size = 0x100000; - let heap = new HeapImpl(); + let heap = new ProgramHeapImpl(); let i = 0; diff --git a/packages/@glimmer/runtime/index.ts b/packages/@glimmer/runtime/index.ts index fe7c70a4bf..c722e45102 100644 --- a/packages/@glimmer/runtime/index.ts +++ b/packages/@glimmer/runtime/index.ts @@ -58,7 +58,7 @@ export { } from './lib/vm/attributes/dynamic'; export { clientBuilder, - NewElementBuilder, + NewTreeBuilder, RemoteLiveBlock, UpdatableBlockImpl, } from './lib/vm/element-builder'; diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts index ba37b283fa..56135682bd 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts @@ -65,6 +65,7 @@ import { CheckProgramSymbolTable, CheckRegister, CheckString, + CheckSyscallRegister, } from '@glimmer/debug'; import { assert, debugToString, expect, unwrap, unwrapTemplate } from '@glimmer/debug-util'; import { registerDestructor } from '@glimmer/destroyable'; @@ -172,7 +173,7 @@ APPEND_OPCODES.add(VM_RESOLVE_DYNAMIC_COMPONENT_OP, (vm, { op1: _isStrict }) => ); } - let resolvedDefinition = resolveComponent(vm.runtime.resolver, constants, component, owner); + let resolvedDefinition = resolveComponent(vm.context.resolver, constants, component, owner); definition = expect(resolvedDefinition, `Could not find a component named "${component}"`); } else if (isCurriedValue(component)) { @@ -290,7 +291,7 @@ APPEND_OPCODES.add(VM_PREPARE_ARGS_OP, (vm, { op1: register }) => { if (resolved === true) { definition = resolvedDefinition as ComponentDefinition; } else if (typeof resolvedDefinition === 'string') { - let resolvedValue = vm.runtime.resolver.lookupComponent(resolvedDefinition, owner); + let resolvedValue = vm.context.resolver?.lookupComponent?.(resolvedDefinition, owner) ?? null; definition = constants.resolvedComponent( expect(resolvedValue, 'BUG: expected resolved component'), @@ -448,7 +449,7 @@ APPEND_OPCODES.add(VM_BEGIN_COMPONENT_TRANSACTION_OP, (vm, { op1: register }) => } vm.beginCacheGroup(name); - vm.elements().pushSimpleBlock(); + vm.tree().pushAppendingBlock(); }); APPEND_OPCODES.add(VM_PUT_COMPONENT_OPERATIONS_OP, (vm) => { @@ -533,7 +534,7 @@ export class ComponentElementOperations implements ElementOperations { return; } - let { element, constructing } = vm.elements(); + let { element, constructing } = vm.tree(); let name = definition.resolvedName ?? manager.getDebugName(definition.state); let instance = manager.getDebugInstance(state); @@ -614,11 +615,9 @@ function setDeferredAttr( trusting = false ) { if (typeof value === 'string') { - vm.elements().setStaticAttribute(name, value, namespace); + vm.tree().setStaticAttribute(name, value, namespace); } else { - let attribute = vm - .elements() - .setDynamicAttribute(name, valueForRef(value), trusting, namespace); + let attribute = vm.tree().setDynamicAttribute(name, valueForRef(value), trusting, namespace); if (!isConstRef(value)) { vm.updateWith(new UpdateDynamicAttributeOpcode(value, attribute, vm.env)); } @@ -636,7 +635,7 @@ APPEND_OPCODES.add(VM_DID_CREATE_ELEMENT_OP, (vm, { op1: register }) => { (manager as WithElementHook).didCreateElement( state, - expect(vm.elements().constructing, `Expected a constructing element in DidCreateOpcode`), + expect(vm.tree().constructing, `Expected a constructing element in DidCreateOpcode`), operations ); }); @@ -674,7 +673,8 @@ APPEND_OPCODES.add(VM_GET_COMPONENT_SELF_OP, (vm, { op1: register, op2: _names } 'BUG: No template was found for this component, and the component did not have the dynamic layout capability' ); - compilable = manager.getDynamicLayout(state, vm.runtime.resolver); + let resolver = vm.context.resolver; + compilable = resolver === null ? null : manager.getDynamicLayout(state, resolver); if (compilable !== null) { moduleName = compilable.moduleName; @@ -760,7 +760,8 @@ APPEND_OPCODES.add(VM_GET_COMPONENT_LAYOUT_OP, (vm, { op1: register }) => { 'BUG: No template was found for this component, and the component did not have the dynamic layout capability' ); - compilable = manager.getDynamicLayout(instance.state, vm.runtime.resolver); + let resolver = vm.context.resolver; + compilable = resolver === null ? null : manager.getDynamicLayout(instance.state, resolver); if (compilable === null) { if (managerHasCapability(manager, capabilities, InternalComponentCapabilities.wrapped)) { @@ -793,7 +794,7 @@ APPEND_OPCODES.add(VM_MAIN_OP, (vm, { op1: register }) => { lookup: null, }; - vm.loadValue(check(register, CheckRegister), state); + vm.loadValue(check(register, CheckSyscallRegister), state); }); APPEND_OPCODES.add(VM_POPULATE_LAYOUT_OP, (vm, { op1: register }) => { @@ -898,7 +899,7 @@ APPEND_OPCODES.add(VM_INVOKE_COMPONENT_LAYOUT_OP, (vm, { op1: register }) => { APPEND_OPCODES.add(VM_DID_RENDER_LAYOUT_OP, (vm, { op1: register }) => { let instance = check(vm.fetchValue(check(register, CheckRegister)), CheckComponentInstance); let { manager, state, capabilities } = instance; - let bounds = vm.elements().popBlock(); + let bounds = vm.tree().popBlock(); if (vm.env.debugRenderTree !== undefined) { if (hasCustomDebugRenderTreeLifecycle(manager)) { diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/content.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/content.ts index a13fbbe353..733b252a5d 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/content.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/content.ts @@ -98,7 +98,7 @@ APPEND_OPCODES.add(VM_APPEND_HTML_OP, (vm) => { let rawValue = valueForRef(reference); let value = isEmpty(rawValue) ? '' : String(rawValue); - vm.elements().appendDynamicHTML(value); + vm.tree().appendDynamicHTML(value); }); APPEND_OPCODES.add(VM_APPEND_SAFE_HTML_OP, (vm) => { @@ -107,7 +107,7 @@ APPEND_OPCODES.add(VM_APPEND_SAFE_HTML_OP, (vm) => { let rawValue = check(valueForRef(reference), CheckSafeString).toHTML(); let value = isEmpty(rawValue) ? '' : check(rawValue, CheckString); - vm.elements().appendDynamicHTML(value); + vm.tree().appendDynamicHTML(value); }); APPEND_OPCODES.add(VM_APPEND_TEXT_OP, (vm) => { @@ -116,7 +116,7 @@ APPEND_OPCODES.add(VM_APPEND_TEXT_OP, (vm) => { let rawValue = valueForRef(reference); let value = isEmpty(rawValue) ? '' : String(rawValue); - let node = vm.elements().appendDynamicText(value); + let node = vm.tree().appendDynamicText(value); if (!isConstRef(reference)) { vm.updateWith(new DynamicTextContent(node, reference, value)); @@ -128,7 +128,7 @@ APPEND_OPCODES.add(VM_APPEND_DOCUMENT_FRAGMENT_OP, (vm) => { let value = check(valueForRef(reference), CheckDocumentFragment); - vm.elements().appendDynamicFragment(value); + vm.tree().appendDynamicFragment(value); }); APPEND_OPCODES.add(VM_APPEND_NODE_OP, (vm) => { @@ -136,5 +136,5 @@ APPEND_OPCODES.add(VM_APPEND_NODE_OP, (vm) => { let value = check(valueForRef(reference), CheckNode); - vm.elements().appendDynamicNode(value); + vm.tree().appendDynamicNode(value); }); diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts index d3beb752e2..2460e01a30 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts @@ -52,20 +52,20 @@ import { CheckArguments, CheckOperations, CheckReference } from './-debug-strip' import { Assert } from './vm'; APPEND_OPCODES.add(VM_TEXT_OP, (vm, { op1: text }) => { - vm.elements().appendText(vm.constants.getValue(text)); + vm.tree().appendText(vm.constants.getValue(text)); }); APPEND_OPCODES.add(VM_COMMENT_OP, (vm, { op1: text }) => { - vm.elements().appendComment(vm.constants.getValue(text)); + vm.tree().appendComment(vm.constants.getValue(text)); }); APPEND_OPCODES.add(VM_OPEN_ELEMENT_OP, (vm, { op1: tag }) => { - vm.elements().openElement(vm.constants.getValue(tag)); + vm.tree().openElement(vm.constants.getValue(tag)); }); APPEND_OPCODES.add(VM_OPEN_DYNAMIC_ELEMENT_OP, (vm) => { let tagName = check(valueForRef(check(vm.stack.pop(), CheckReference)), CheckString); - vm.elements().openElement(tagName); + vm.tree().openElement(tagName); }); APPEND_OPCODES.add(VM_PUSH_REMOTE_ELEMENT_OP, (vm) => { @@ -85,7 +85,7 @@ APPEND_OPCODES.add(VM_PUSH_REMOTE_ELEMENT_OP, (vm) => { vm.updateWith(new Assert(insertBeforeRef)); } - let block = vm.elements().pushRemoteElement(element, guid, insertBefore); + let block = vm.tree().pushRemoteElement(element, guid, insertBefore); if (block) vm.associateDestroyable(block); if (vm.env.debugRenderTree !== undefined) { @@ -111,7 +111,7 @@ APPEND_OPCODES.add(VM_PUSH_REMOTE_ELEMENT_OP, (vm) => { }); APPEND_OPCODES.add(VM_POP_REMOTE_ELEMENT_OP, (vm) => { - let bounds = vm.elements().popRemoteElement(); + let bounds = vm.tree().popRemoteElement(); if (vm.env.debugRenderTree !== undefined) { // The RemoteLiveBlock is also its bounds @@ -128,11 +128,11 @@ APPEND_OPCODES.add(VM_FLUSH_ELEMENT_OP, (vm) => { vm.loadValue($t0, null); } - vm.elements().flushElement(modifiers); + vm.tree().flushElement(modifiers); }); APPEND_OPCODES.add(VM_CLOSE_ELEMENT_OP, (vm) => { - let modifiers = vm.elements().closeElement(); + let modifiers = vm.tree().closeElement(); if (modifiers !== null) { modifiers.forEach((modifier) => { @@ -157,7 +157,7 @@ APPEND_OPCODES.add(VM_MODIFIER_OP, (vm, { op1: handle }) => { let { manager } = definition; - let { constructing } = vm.elements(); + let { constructing } = vm.tree(); let capturedArgs = args.capture(); let state = manager.create( @@ -198,7 +198,7 @@ APPEND_OPCODES.add(VM_DYNAMIC_MODIFIER_OP, (vm) => { let args = check(stack.pop(), CheckArguments).capture(); let { positional: outerPositional, named: outerNamed } = args; - let { constructing } = vm.elements(); + let { constructing } = vm.tree(); let initialOwner = vm.getOwner(); let instanceRef = createComputeRef(() => { @@ -375,7 +375,7 @@ APPEND_OPCODES.add(VM_STATIC_ATTR_OP, (vm, { op1: _name, op2: _value, op3: _name let value = vm.constants.getValue(_value); let namespace = _namespace ? vm.constants.getValue(_namespace) : null; - vm.elements().setStaticAttribute(name, value, namespace); + vm.tree().setStaticAttribute(name, value, namespace); }); APPEND_OPCODES.add(VM_DYNAMIC_ATTR_OP, (vm, { op1: _name, op2: _trusting, op3: _namespace }) => { @@ -385,7 +385,7 @@ APPEND_OPCODES.add(VM_DYNAMIC_ATTR_OP, (vm, { op1: _name, op2: _trusting, op3: _ let value = valueForRef(reference); let namespace = _namespace ? vm.constants.getValue(_namespace) : null; - let attribute = vm.elements().setDynamicAttribute(name, value, trusting, namespace); + let attribute = vm.tree().setDynamicAttribute(name, value, trusting, namespace); if (!isConstRef(reference)) { vm.updateWith(new UpdateDynamicAttributeOpcode(reference, attribute, vm.env)); diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts index c435e85d45..dcc208145e 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts @@ -77,7 +77,7 @@ APPEND_OPCODES.add(VM_CURRY_OP, (vm, { op1: type, op2: _isStrict }) => { let capturedArgs = check(stack.pop(), CheckCapturedArguments); let owner = vm.getOwner(); - let resolver = vm.runtime.resolver; + let resolver = vm.context.resolver; let isStrict = false; diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/lists.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/lists.ts index 44af2c478b..5aba9f348e 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/lists.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/lists.ts @@ -21,7 +21,7 @@ APPEND_OPCODES.add(VM_ENTER_LIST_OP, (vm, { op1: relativeStart, op2: elseTarget if (iterator.isEmpty() === true) { // TODO: Fix this offset, should be accurate - vm.goto(elseTarget + 1); + vm.lowlevel.goto(elseTarget + 1); } else { vm.enterList(iteratorRef, relativeStart); vm.stack.push(iterator); @@ -40,6 +40,6 @@ APPEND_OPCODES.add(VM_ITERATE_OP, (vm, { op1: breaks }) => { if (item !== null) { vm.registerItem(vm.enterItem(item)); } else { - vm.goto(breaks); + vm.lowlevel.goto(breaks); } }); diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts index ae7e236b75..a2e5ccabff 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts @@ -192,7 +192,7 @@ APPEND_OPCODES.add(VM_INVOKE_YIELD_OP, (vm) => { `Expected both handle and table to be null if either is null` ); // To balance the pop{Frame,Scope} - vm.pushFrame(); + vm.lowlevel.pushFrame(); vm.pushScope(scope ?? vm.scope()); return; @@ -214,7 +214,7 @@ APPEND_OPCODES.add(VM_INVOKE_YIELD_OP, (vm) => { } } - vm.pushFrame(); + vm.lowlevel.pushFrame(); vm.pushScope(invokingScope); vm.call(handle); @@ -226,11 +226,11 @@ APPEND_OPCODES.add(VM_JUMP_IF_OP, (vm, { op1: target }) => { if (isConstRef(reference)) { if (value === true) { - vm.goto(target); + vm.lowlevel.goto(target); } } else { if (value === true) { - vm.goto(target); + vm.lowlevel.goto(target); } vm.updateWith(new Assert(reference)); @@ -243,11 +243,11 @@ APPEND_OPCODES.add(VM_JUMP_UNLESS_OP, (vm, { op1: target }) => { if (isConstRef(reference)) { if (value === false) { - vm.goto(target); + vm.lowlevel.goto(target); } } else { if (value === false) { - vm.goto(target); + vm.lowlevel.goto(target); } vm.updateWith(new Assert(reference)); @@ -258,7 +258,7 @@ APPEND_OPCODES.add(VM_JUMP_EQ_OP, (vm, { op1: target, op2: comparison }) => { let other = check(vm.stack.peek(), CheckNumber); if (other === comparison) { - vm.goto(target); + vm.lowlevel.goto(target); } }); diff --git a/packages/@glimmer/runtime/lib/component/resolve.ts b/packages/@glimmer/runtime/lib/component/resolve.ts index 60fc600946..0657d5a6de 100644 --- a/packages/@glimmer/runtime/lib/component/resolve.ts +++ b/packages/@glimmer/runtime/lib/component/resolve.ts @@ -1,22 +1,23 @@ import type { + ClassicResolver, ComponentDefinition, Nullable, Owner, ResolutionTimeConstants, - RuntimeResolver, } from '@glimmer/interfaces'; import { expect } from '@glimmer/debug-util'; export function resolveComponent( - resolver: RuntimeResolver, + resolver: Nullable, constants: ResolutionTimeConstants, name: string, owner: Owner | null ): Nullable { - let definition = resolver.lookupComponent( - name, - expect(owner, 'BUG: expected owner when looking up component') - ); + let definition = + resolver?.lookupComponent?.( + name, + expect(owner, 'BUG: expected owner when looking up component') + ) ?? null; if (import.meta.env.DEV && !definition) { throw new Error( diff --git a/packages/@glimmer/runtime/lib/environment.ts b/packages/@glimmer/runtime/lib/environment.ts index 93b030647b..4b10c7bef0 100644 --- a/packages/@glimmer/runtime/lib/environment.ts +++ b/packages/@glimmer/runtime/lib/environment.ts @@ -1,19 +1,19 @@ import type { + ClassicResolver, ComponentInstanceWithCreate, Environment, EnvironmentOptions, + EvaluationContext, GlimmerTreeChanges, GlimmerTreeConstruction, ModifierInstance, Nullable, RuntimeArtifacts, - RuntimeContext, - RuntimeResolver, Transaction, TransactionSymbol, } from '@glimmer/interfaces'; import { assert, expect } from '@glimmer/debug-util'; -import { RuntimeProgramImpl } from '@glimmer/program'; +import { ProgramImpl } from '@glimmer/program'; import { track, updateTag } from '@glimmer/validator'; import DebugRenderTree from './debug-render-tree'; @@ -203,12 +203,12 @@ export function runtimeContext( options: EnvironmentOptions, delegate: EnvironmentDelegate, artifacts: RuntimeArtifacts, - resolver: RuntimeResolver -): RuntimeContext { + resolver: ClassicResolver +): Pick { return { env: new EnvironmentImpl(options, delegate), - program: new RuntimeProgramImpl(artifacts.constants, artifacts.heap), - resolver: resolver, + program: new ProgramImpl(artifacts.constants, artifacts.heap), + resolver, }; } diff --git a/packages/@glimmer/runtime/lib/opcodes.ts b/packages/@glimmer/runtime/lib/opcodes.ts index b3a55db008..241e555489 100644 --- a/packages/@glimmer/runtime/lib/opcodes.ts +++ b/packages/@glimmer/runtime/lib/opcodes.ts @@ -154,7 +154,7 @@ if (import.meta.env.VM_LOCAL_DEV) { } if (LOCAL_TRACE_LOGGING) { - const { lowlevel, registers } = unwrap(vm.debug); + const { lowlevel, registers } = debug; LOCAL_LOGGER.debug( '%c -> pc: %d, ra: %d, fp: %d, sp: %d, s0: %O, s1: %O, t0: %O, t1: %O, v0: %O', 'color: orange', @@ -169,7 +169,7 @@ if (import.meta.env.VM_LOCAL_DEV) { registers[$v0] ); LOCAL_LOGGER.debug('%c -> eval stack', 'color: red', vm.stack.toArray()); - LOCAL_LOGGER.debug('%c -> block stack', 'color: magenta', vm.elements().debugBlocks()); + LOCAL_LOGGER.debug('%c -> block stack', 'color: magenta', vm.tree().debugBlocks()); LOCAL_LOGGER.debug( '%c -> destructor stack', 'color: violet', @@ -188,10 +188,10 @@ if (import.meta.env.VM_LOCAL_DEV) { LOCAL_LOGGER.debug( '%c -> elements', 'color: blue', - vm.elements()[CURSOR_STACK].current!.element + vm.tree()[CURSOR_STACK].current!.element ); - LOCAL_LOGGER.debug('%c -> constructing', 'color: aqua', vm.elements()['constructing']); + LOCAL_LOGGER.debug('%c -> constructing', 'color: aqua', vm.tree()['constructing']); } } }, diff --git a/packages/@glimmer/runtime/lib/references/curry-value.ts b/packages/@glimmer/runtime/lib/references/curry-value.ts index 98497dba0a..1fed4acd47 100644 --- a/packages/@glimmer/runtime/lib/references/curry-value.ts +++ b/packages/@glimmer/runtime/lib/references/curry-value.ts @@ -1,11 +1,11 @@ import type { CapturedArguments, + ClassicResolver, CurriedType, Dict, Maybe, Nullable, Owner, - RuntimeResolver, } from '@glimmer/interfaces'; import type { Reference } from '@glimmer/reference'; import { CURRIED_COMPONENT } from '@glimmer/constants'; @@ -20,7 +20,7 @@ export default function createCurryRef( inner: Reference, owner: Owner, args: Nullable, - resolver: RuntimeResolver, + resolver: Nullable, isStrict: boolean ) { let lastValue: Maybe | string, curriedDefinition: object | string | null; @@ -45,10 +45,11 @@ export default function createCurryRef( ); } - let resolvedDefinition = expect( - resolver, - 'BUG: expected resolver for curried component definitions' - ).lookupComponent(value, owner); + let resolvedDefinition = + expect( + resolver, + 'BUG: expected resolver for curried component definitions' + ).lookupComponent?.(value, owner) ?? null; if (!resolvedDefinition) { throw new Error( diff --git a/packages/@glimmer/runtime/lib/render.ts b/packages/@glimmer/runtime/lib/render.ts index a0e65894e4..8d61b08ccf 100644 --- a/packages/@glimmer/runtime/lib/render.ts +++ b/packages/@glimmer/runtime/lib/render.ts @@ -1,15 +1,14 @@ import type { CompilableProgram, - CompileTimeCompilationContext, ComponentDefinitionState, DynamicScope, - ElementBuilder, Environment, + EvaluationContext, Owner, RenderResult, RichIteratorResult, - RuntimeContext, TemplateIterator, + TreeBuilder, } from '@glimmer/interfaces'; import type { Reference } from '@glimmer/reference'; import { expect, unwrapHandle } from '@glimmer/debug-util'; @@ -44,22 +43,20 @@ export function renderSync(env: Environment, iterator: TemplateIterator): Render } export function renderMain( - runtime: RuntimeContext, - context: CompileTimeCompilationContext, + context: EvaluationContext, owner: Owner, self: Reference, - treeBuilder: ElementBuilder, + treeBuilder: TreeBuilder, layout: CompilableProgram, dynamicScope: DynamicScope = new DynamicScopeImpl() ): TemplateIterator { let handle = unwrapHandle(layout.compile(context)); let numSymbols = layout.symbolTable.symbols.length; - let vm = VM.initial(runtime, context, { - self, + let vm = VM.initial(context, { + scope: { self, size: numSymbols }, dynamicScope, - treeBuilder, + tree: treeBuilder, handle, - numSymbols, owner, }); return new TemplateIteratorImpl(vm); @@ -67,7 +64,7 @@ export function renderMain( function renderInvocation( vm: VM, - context: CompileTimeCompilationContext, + context: EvaluationContext, owner: Owner, definition: ComponentDefinitionState, args: Record @@ -82,7 +79,7 @@ function renderInvocation( let reified = vm.constants.component(definition, owner, undefined, '{ROOT}'); - vm.pushFrame(); + vm.lowlevel.pushFrame(); // Push blocks on to the stack, three stack values per block for (let i = 0; i < 3 * blockNames.length; i++) { @@ -116,19 +113,19 @@ function renderInvocation( } export function renderComponent( - runtime: RuntimeContext, - treeBuilder: ElementBuilder, - context: CompileTimeCompilationContext, + context: EvaluationContext, + treeBuilder: TreeBuilder, owner: Owner, definition: ComponentDefinitionState, args: Record = {}, dynamicScope: DynamicScope = new DynamicScopeImpl() ): TemplateIterator { - let vm = VM.empty( - runtime, - { treeBuilder, handle: context.stdlib.main, dynamicScope, owner }, - context - ); + let vm = VM.empty(context, { + tree: treeBuilder, + handle: context.stdlib.main, + dynamicScope, + owner, + }); return renderInvocation(vm, context, owner, definition, recordToReference(args)); } diff --git a/packages/@glimmer/runtime/lib/scope.ts b/packages/@glimmer/runtime/lib/scope.ts index f55cdec6cc..1c9e8d8842 100644 --- a/packages/@glimmer/runtime/lib/scope.ts +++ b/packages/@glimmer/runtime/lib/scope.ts @@ -41,25 +41,33 @@ export function isScopeReference(s: ScopeSlot): s is Reference { return true; } +export interface ScopeOptions { + /** @default {UNDEFINED_REFERENCE} */ + self: Reference; + /** @default {0} */ + size?: number | undefined; +} + export class ScopeImpl implements Scope { - static root(self: Reference, size = 0, owner: Owner): Scope { + static root(owner: Owner, { self, size = 0 }: ScopeOptions): Scope { let refs: Reference[] = new Array(size + 1).fill(UNDEFINED_REFERENCE); - return new ScopeImpl(refs, owner, null, null).init({ self }); + return new ScopeImpl(owner, refs, null, null).init({ self }); } - static sized(size = 0, owner: Owner): Scope { + static sized(owner: Owner, size = 0): Scope { let refs: Reference[] = new Array(size + 1).fill(UNDEFINED_REFERENCE); - return new ScopeImpl(refs, owner, null, null); + return new ScopeImpl(owner, refs, null, null); } constructor( + readonly owner: Owner, // the 0th slot is `self` readonly slots: Array, - readonly owner: Owner, + // a single program can mix owners via curried components, and the state lives on root scopes private callerScope: Scope | null, - // named arguments and blocks passed to a layout that uses debugger + // named arguments and blocks passed to a layout that uses eval private debuggerScope: Dict | null ) {} @@ -114,7 +122,7 @@ export class ScopeImpl implements Scope { } child(): Scope { - return new ScopeImpl(this.slots.slice(), this.owner, this.callerScope, this.debuggerScope); + return new ScopeImpl(this.owner, this.slots.slice(), this.callerScope, this.debuggerScope); } private get(index: number): T { diff --git a/packages/@glimmer/runtime/lib/vm/append.ts b/packages/@glimmer/runtime/lib/vm/append.ts index 832d75a1da..7cb6e892f0 100644 --- a/packages/@glimmer/runtime/lib/vm/append.ts +++ b/packages/@glimmer/runtime/lib/vm/append.ts @@ -1,37 +1,25 @@ import type { + BlockMetadata, CompilableTemplate, - CompileTimeCompilationContext, + DebugTemplates, Destroyable, DynamicScope, - ElementBuilder, Environment, - JitConstants, + EvaluationContext, Nullable, Owner, + Program, + ProgramConstants, RenderResult, RichIteratorResult, - RuntimeContext, - RuntimeHeap, - RuntimeProgram, Scope, + SyscallRegisters, + TreeBuilder, UpdatingOpcode, - VM as PublicVM, } from '@glimmer/interfaces'; import type { RuntimeOpImpl } from '@glimmer/program'; import type { OpaqueIterationItem, OpaqueIterator, Reference } from '@glimmer/reference'; -import type { - $fp, - $ra, - $s0, - $s1, - $sp, - $t0, - $t1, - $v0, - MachineRegister, - Register, - SyscallRegister, -} from '@glimmer/vm'; +import type { MachineRegister, Register, SyscallRegister } from '@glimmer/vm'; import { expect, unwrapHandle } from '@glimmer/debug-util'; import { associateDestroyableChild } from '@glimmer/destroyable'; import { assertGlobalContextWasSet } from '@glimmer/global-context'; @@ -42,9 +30,10 @@ import { beginTrackFrame, endTrackFrame, resetTracking } from '@glimmer/validato import { $pc, isLowLevelRegister } from '@glimmer/vm'; import type { DebugState } from '../opcodes'; +import type { ScopeOptions } from '../scope'; import type { LiveBlockList } from './element-builder'; import type { EvaluationStack } from './stack'; -import type { BlockOpcode, ResumableVMState, VMState } from './update'; +import type { BlockOpcode, VMState } from './update'; import { BeginTrackFrameOpcode, @@ -57,44 +46,72 @@ import { VMArgumentsImpl } from './arguments'; import { LowLevelVM } from './low-level'; import RenderResultImpl from './render-result'; import EvaluationStackImpl from './stack'; -import { ListBlockOpcode, ListItemOpcode, ResumableVMStateImpl, TryOpcode } from './update'; +import { ListBlockOpcode, ListItemOpcode, TryOpcode } from './update'; class Stacks { + readonly drop: object = {}; + readonly scope = new Stack(); readonly dynamicScope = new Stack(); readonly updating = new Stack(); readonly cache = new Stack(); readonly list = new Stack(); + readonly destroyable = new Stack(); + + constructor(scope: Scope, dynamicScope: DynamicScope) { + this.scope.push(scope); + this.dynamicScope.push(dynamicScope); + this.destroyable.push(this.drop); + } } -interface SyscallRegisters { - [$pc]: null; - [$ra]: null; - [$fp]: null; - [$sp]: null; - [$s0]: unknown; - [$s1]: unknown; - [$t0]: unknown; - [$t1]: unknown; - [$v0]: unknown; +type Handle = number; + +let DebugTemplatesImpl: undefined | (new () => DebugTemplates); + +if (LOCAL_DEBUG) { + DebugTemplatesImpl = class DebugTemplatesImpl implements DebugTemplates { + readonly #templates: Map = new Map(); + #active: Handle[] = []; + + willCall(handle: Handle): void { + this.#active.push(handle); + } + + return(): void { + this.#active.pop(); + } + + get active(): BlockMetadata | null { + const current = this.#active.at(-1); + return current ? this.#templates.get(current) ?? null : null; + } + + register(handle: Handle, metadata: BlockMetadata): void { + this.#templates.set(handle, metadata); + } + }; } interface DebugVmState { - readonly stacks: Stacks; - readonly destroyableStack: Stack; - readonly constants: JitConstants; - readonly lowlevel: LowLevelVM; - readonly registers: SyscallRegisters; + context: EvaluationContext; + trace: { templates: DebugTemplates }; + lowlevel: LowLevelVM; + registers: SyscallRegisters; + destroyableStack: Stack; + stacks: Stacks; } -export class VM implements PublicVM { - readonly #stacks = new Stacks(); - readonly #heap: RuntimeHeap; +export class VM { + readonly #stacks: Stacks; readonly #destructor: object; readonly #destroyableStack = new Stack(); - readonly constants: JitConstants; + readonly context: EvaluationContext; + readonly #tree: TreeBuilder; + readonly args: VMArgumentsImpl; readonly lowlevel: LowLevelVM; + readonly debug?: DebugVmState; get stack(): EvaluationStack { @@ -109,21 +126,63 @@ export class VM implements PublicVM { #registers: SyscallRegisters = [null, null, null, null, null, null, null, null, null]; - // Fetch a value from a register onto the stack + /** + * Fetch a value from a syscall register onto the stack. + * + * ## Opcodes + * + * - Append: `Fetch` + * + * ## State changes + * + * [!] push Eval Stack <- $register + */ fetch(register: SyscallRegister): void { let value = this.fetchValue(register); this.stack.push(value); } - // Load a value from the stack into a register + /** + * Load a value from the stack into a syscall register. + * + * ## Opcodes + * + * - Append: `Load` + * + * ## State changes + * + * [!] pop Eval Stack -> `value` + * [$] $register <- `value` + */ load(register: SyscallRegister) { let value = this.stack.pop(); this.loadValue(register, value); } - // Fetch a value from a register + /** + * Load a value into a syscall register. + * + * ## State changes + * + * [$] $register <- `value` + * + * @utility + */ + loadValue(register: SyscallRegister, value: T): void { + this.#registers[register] = value; + } + + /** + * Fetch a value from a register (machine or syscall). + * + * ## State changes + * + * [ ] get $register + * + * @utility + */ fetchValue(register: MachineRegister): number; fetchValue(register: Register): T; fetchValue(register: Register | MachineRegister): unknown { @@ -134,48 +193,23 @@ export class VM implements PublicVM { return this.#registers[register]; } - // Load a value into a register - - loadValue(register: Register | MachineRegister, value: T): void { - if (isLowLevelRegister(register)) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - this.lowlevel.loadRegister(register, value as any as number); - } else { - this.#registers[register] = value; - } - } - - /** - * Migrated to Inner - */ - - // Start a new frame and save $ra and $fp on the stack - pushFrame() { - this.lowlevel.pushFrame(); - } - - // Restore $ra, $sp and $fp - popFrame() { - this.lowlevel.popFrame(); - } - - // Jump to an address in `program` - goto(offset: number) { - this.lowlevel.goto(offset); - } - // Save $pc into $ra, then jump to a new address in `program` (jal in MIPS) - call(handle: number) { - this.lowlevel.call(handle); - } + call(handle: number | null) { + if (handle !== null) { + if (LOCAL_DEBUG) { + this.debug?.trace.templates.willCall(handle); + } - // Put a specific `program` address in $ra - returnTo(offset: number) { - this.lowlevel.returnTo(offset); + this.lowlevel.call(handle); + } } // Return to the `program` address stored in $ra return() { + if (LOCAL_DEBUG) { + this.debug?.trace.templates.return(); + } + this.lowlevel.return(); } @@ -184,28 +218,25 @@ export class VM implements PublicVM { */ constructor( - readonly runtime: RuntimeContext, - { pc, scope, dynamicScope, stack }: VMState, - private readonly elementStack: ElementBuilder, - readonly context: CompileTimeCompilationContext + { pc, scope, dynamicScope, stack }: ClosureState, + tree: TreeBuilder, + context: EvaluationContext ) { if (import.meta.env.DEV) { assertGlobalContextWasSet!(); } - this.resume = initVM(context); let evalStack = EvaluationStackImpl.restore(stack, pc); - this.#heap = this.program.heap; - this.constants = this.program.constants; - this.elementStack = elementStack; - this.#stacks.scope.push(scope); - this.#stacks.dynamicScope.push(dynamicScope); + this.context = context; + this.#tree = tree; + + this.#stacks = new Stacks(scope, dynamicScope); + this.args = new VMArgumentsImpl(); this.lowlevel = new LowLevelVM( evalStack, - this.#heap, - runtime.program, + context, import.meta.env.VM_LOCAL_DEV ? { debugBefore: (opcode: RuntimeOpImpl): DebugState => { @@ -222,62 +253,91 @@ export class VM implements PublicVM { this.#destructor = {}; this.#destroyableStack.push(this.#destructor); + associateDestroyableChild(this.#stacks.drop, this.#destructor); if (LOCAL_DEBUG) { + const templates = new DebugTemplatesImpl!(); + this.debug = { + context: this.context, + + trace: { + templates, + }, + stacks: this.#stacks, destroyableStack: this.#destroyableStack, - constants: this.constants, lowlevel: this.lowlevel, registers: this.#registers, - }; + } satisfies DebugVmState; } - } - static initial( - runtime: RuntimeContext, - context: CompileTimeCompilationContext, - { handle, self, dynamicScope, treeBuilder, numSymbols, owner }: InitOptions - ) { - let scope = ScopeImpl.root(self, numSymbols, owner); - let state = vmState(runtime.program.heap.getaddr(handle), scope, dynamicScope); - let vm = initVM(context)(runtime, state, treeBuilder); - vm.pushUpdating(); - return vm; + this.pushUpdating(); } - static empty( - runtime: RuntimeContext, - { handle, treeBuilder, dynamicScope, owner }: MinimalInitOptions, - context: CompileTimeCompilationContext - ) { - let vm = initVM(context)( - runtime, - vmState( - runtime.program.heap.getaddr(handle), - ScopeImpl.root(UNDEFINED_REFERENCE, 0, owner), - dynamicScope - ), - treeBuilder + static initial(context: EvaluationContext, options: InitialVmState) { + let scope = ScopeImpl.root( + options.owner, + options.scope ?? { self: UNDEFINED_REFERENCE, size: 0 } ); - vm.pushUpdating(); + return VM.create({ + ...options, + scope, + context, + }); + } + + static create({ + scope, + dynamicScope, + handle, + tree, + context, + }: { + scope: Scope; + dynamicScope: DynamicScope; + handle: number; + tree: TreeBuilder; + context: EvaluationContext; + }) { + let state = closureState(context.program.heap.getaddr(handle), scope, dynamicScope); + let vm = new VM(state, tree, context); return vm; } - private resume: VmInitCallback; + static empty(context: EvaluationContext, options: InitialVmState) { + let scope = ScopeImpl.root( + options.owner, + options.scope ?? { self: UNDEFINED_REFERENCE, size: 0 } + ); + + return VM.create({ + ...options, + scope, + context, + }); + } compile(block: CompilableTemplate): number { let handle = unwrapHandle(block.compile(this.context)); + if (LOCAL_DEBUG) { + this.debug?.trace.templates.register(handle, block.meta); + } + return handle; } - get program(): RuntimeProgram { - return this.runtime.program; + get constants(): ProgramConstants { + return this.context.program.constants; + } + + get program(): Program { + return this.context.program; } get env(): Environment { - return this.runtime.env; + return this.context.env; } captureState(args: number, pc = this.lowlevel.fetchRegister($pc)): VMState { @@ -289,8 +349,17 @@ export class VM implements PublicVM { }; } - capture(args: number, pc = this.lowlevel.fetchRegister($pc)): ResumableVMState { - return new ResumableVMStateImpl(this.captureState(args, pc), this.resume); + private captureClosure(args: number, pc = this.lowlevel.fetchRegister($pc)): ClosureState { + return { + pc, + scope: this.scope(), + dynamicScope: this.dynamicScope(), + stack: this.stack.capture(args), + }; + } + + capture(args: number, pc = this.lowlevel.fetchRegister($pc)): Closure { + return new Closure(this.captureClosure(args, pc), this.context); } beginCacheGroup(name?: string) { @@ -318,9 +387,9 @@ export class VM implements PublicVM { let updating: UpdatingOpcode[] = []; let state = this.capture(args); - let block = this.elements().pushUpdatableBlock(); + let block = this.tree().pushResettableBlock(); - let tryOpcode = new TryOpcode(state, this.runtime, block, updating); + let tryOpcode = new TryOpcode(state, this.context, block, updating); this.didEnter(tryOpcode); } @@ -335,9 +404,9 @@ export class VM implements PublicVM { stack.push(memoRef); let state = this.capture(2); - let block = this.elements().pushUpdatableBlock(); + let block = this.tree().pushResettableBlock(); - let opcode = new ListItemOpcode(state, this.runtime, block, key, memoRef, valueRef); + let opcode = new ListItemOpcode(state, this.context, block, key, memoRef, valueRef); this.didEnter(opcode); return opcode; @@ -352,9 +421,9 @@ export class VM implements PublicVM { let addr = this.lowlevel.target(offset); let state = this.capture(0, addr); - let list = this.elements().pushBlockList(updating) as LiveBlockList; + let list = this.tree().pushBlockList(updating) as LiveBlockList; - let opcode = new ListBlockOpcode(state, this.runtime, list, updating, iterableRef); + let opcode = new ListBlockOpcode(state, this.context, list, updating, iterableRef); this.#stacks.list.push(opcode); @@ -370,7 +439,7 @@ export class VM implements PublicVM { exit() { this.#destroyableStack.pop(); - this.elements().popBlock(); + this.tree().popBlock(); this.popUpdating(); } @@ -411,8 +480,8 @@ export class VM implements PublicVM { ); } - elements(): ElementBuilder { - return this.elementStack; + tree(): TreeBuilder { + return this.#tree; } scope(): Scope { @@ -437,7 +506,7 @@ export class VM implements PublicVM { } pushRootScope(size: number, owner: Owner): Scope { - let scope = ScopeImpl.sized(size, owner); + let scope = ScopeImpl.sized(owner, size); this.#stacks.scope.push(scope); return scope; } @@ -486,7 +555,7 @@ export class VM implements PublicVM { if (hasErrored) { // If any existing blocks are open, due to an error or something like // that, we need to close them all and clean things up properly. - let elements = this.elements(); + let elements = this.tree(); while (elements.hasBlocks) { elements.popBlock(); @@ -517,7 +586,8 @@ export class VM implements PublicVM { } next(): RichIteratorResult { - let { env, elementStack } = this; + let { env } = this; + let tree = this.#tree; let opcode = this.lowlevel.nextStatement(); let result: RichIteratorResult; if (opcode !== null) { @@ -529,12 +599,7 @@ export class VM implements PublicVM { result = { done: true, - value: new RenderResultImpl( - env, - this.popUpdating(), - elementStack.popBlock(), - this.#destructor - ), + value: new RenderResultImpl(env, this.popUpdating(), tree.popBlock(), this.#stacks.drop), }; } return result; @@ -549,7 +614,15 @@ export class VM implements PublicVM { } } -function vmState(pc: number, scope: Scope, dynamicScope: DynamicScope) { +export interface InitialVmState { + handle: number; + tree: TreeBuilder; + dynamicScope: DynamicScope; + owner: Owner; + scope?: ScopeOptions; +} + +function closureState(pc: number, scope: Scope, dynamicScope: DynamicScope): ClosureState { return { pc, scope, @@ -560,7 +633,7 @@ function vmState(pc: number, scope: Scope, dynamicScope: DynamicScope) { export interface MinimalInitOptions { handle: number; - treeBuilder: ElementBuilder; + treeBuilder: TreeBuilder; dynamicScope: DynamicScope; owner: Owner; } @@ -570,13 +643,47 @@ export interface InitOptions extends MinimalInitOptions { numSymbols: number; } -export type VmInitCallback = ( - this: void, - runtime: RuntimeContext, - state: VMState, - builder: ElementBuilder -) => VM; +export interface ClosureState { + /** + * The program counter that subsequent evaluations should start from. + */ + readonly pc: number; -function initVM(context: CompileTimeCompilationContext): VmInitCallback { - return (runtime, state, builder) => new VM(runtime, state, builder, context); + /** + * The current value of the VM's scope (which changes whenever a component is invoked or a block + * with block params is entered). + */ + readonly scope: Scope; + + /** + * The current value of the VM's dynamic scope + */ + readonly dynamicScope: DynamicScope; + + /** + * A number of stack elements captured during the initial evaluation, and which should be restored + * to the stack when the block is re-evaluated. + */ + readonly stack: unknown[]; +} + +/** + * A closure captures the state of the VM for a particular block of code that is necessary to + * re-invoke the block in the future. + * + * In practice, this allows us to clear the previous render and "replay" the block's execution, + * rendering content in the same position as the first render. + */ +export class Closure { + private state: ClosureState; + private context: EvaluationContext; + + constructor(state: ClosureState, context: EvaluationContext) { + this.state = state; + this.context = context; + } + + evaluate(tree: TreeBuilder): VM { + return new VM(this.state, tree, this.context); + } } diff --git a/packages/@glimmer/runtime/lib/vm/attributes/dynamic.ts b/packages/@glimmer/runtime/lib/vm/attributes/dynamic.ts index 1cd35565e7..6ecba2bc0d 100644 --- a/packages/@glimmer/runtime/lib/vm/attributes/dynamic.ts +++ b/packages/@glimmer/runtime/lib/vm/attributes/dynamic.ts @@ -3,10 +3,10 @@ import type { AttributeOperation, AttrNamespace, Dict, - ElementBuilder, Environment, Nullable, SimpleElement, + TreeBuilder, } from '@glimmer/interfaces'; import { NS_SVG } from '@glimmer/constants'; import { castToBrowser } from '@glimmer/debug-util'; @@ -77,12 +77,12 @@ function buildDynamicProperty( export abstract class DynamicAttribute implements AttributeOperation { constructor(public attribute: AttributeCursor) {} - abstract set(dom: ElementBuilder, value: unknown, env: Environment): void; + abstract set(dom: TreeBuilder, value: unknown, env: Environment): void; abstract update(value: unknown, env: Environment): void; } export class SimpleDynamicAttribute extends DynamicAttribute { - set(dom: ElementBuilder, value: unknown, _env: Environment): void { + set(dom: TreeBuilder, value: unknown, _env: Environment): void { const normalizedValue = normalizeValue(value); if (normalizedValue !== null) { @@ -112,7 +112,7 @@ export class DefaultDynamicProperty extends DynamicAttribute { } value: unknown; - set(dom: ElementBuilder, value: unknown, _env: Environment): void { + set(dom: TreeBuilder, value: unknown, _env: Environment): void { if (value !== null && value !== undefined) { this.value = value; dom.__setProperty(this.normalizedName, value); @@ -146,7 +146,7 @@ export class DefaultDynamicProperty extends DynamicAttribute { } export class SafeDynamicProperty extends DefaultDynamicProperty { - override set(dom: ElementBuilder, value: unknown, env: Environment): void { + override set(dom: TreeBuilder, value: unknown, env: Environment): void { const { element, name } = this.attribute; const sanitized = sanitizeAttributeValue(element, name, value); super.set(dom, sanitized, env); @@ -160,7 +160,7 @@ export class SafeDynamicProperty extends DefaultDynamicProperty { } export class SafeDynamicAttribute extends SimpleDynamicAttribute { - override set(dom: ElementBuilder, value: unknown, env: Environment): void { + override set(dom: TreeBuilder, value: unknown, env: Environment): void { const { element, name } = this.attribute; const sanitized = sanitizeAttributeValue(element, name, value); super.set(dom, sanitized, env); @@ -174,7 +174,7 @@ export class SafeDynamicAttribute extends SimpleDynamicAttribute { } export class InputValueDynamicAttribute extends DefaultDynamicProperty { - override set(dom: ElementBuilder, value: unknown) { + override set(dom: TreeBuilder, value: unknown) { dom.__setProperty('value', normalizeStringValue(value)); } @@ -189,7 +189,7 @@ export class InputValueDynamicAttribute extends DefaultDynamicProperty { } export class OptionSelectedDynamicAttribute extends DefaultDynamicProperty { - override set(dom: ElementBuilder, value: unknown): void { + override set(dom: TreeBuilder, value: unknown): void { if (value !== null && value !== undefined && value !== false) { dom.__setProperty('selected', true); } @@ -240,7 +240,7 @@ let DebugStyleAttributeManager: { if (import.meta.env.DEV) { DebugStyleAttributeManager = class extends SimpleDynamicAttribute { - override set(dom: ElementBuilder, value: unknown, env: Environment): void { + override set(dom: TreeBuilder, value: unknown, env: Environment): void { warnIfStyleNotTrusted(value); super.set(dom, value, env); diff --git a/packages/@glimmer/runtime/lib/vm/element-builder.ts b/packages/@glimmer/runtime/lib/vm/element-builder.ts index ce6d2c5c1a..7d37a07d6b 100644 --- a/packages/@glimmer/runtime/lib/vm/element-builder.ts +++ b/packages/@glimmer/runtime/lib/vm/element-builder.ts @@ -1,23 +1,23 @@ import type { + AppendingBlock, AttrNamespace, Bounds, Cursor, CursorStackSymbol, - ElementBuilder, ElementOperations, Environment, GlimmerTreeChanges, GlimmerTreeConstruction, - LiveBlock, Maybe, ModifierInstance, Nullable, + ResettableBlock, SimpleComment, SimpleDocumentFragment, SimpleElement, SimpleNode, SimpleText, - UpdatableBlock, + TreeBuilder, } from '@glimmer/interfaces'; import { assert, expect } from '@glimmer/debug-util'; import { destroy, registerDestructor } from '@glimmer/destroyable'; @@ -74,7 +74,7 @@ export class Fragment implements Bounds { export const CURSOR_STACK: CursorStackSymbol = Symbol('CURSOR_STACK') as CursorStackSymbol; -export class NewElementBuilder implements ElementBuilder { +export class NewTreeBuilder implements TreeBuilder { public dom: GlimmerTreeConstruction; public updateOperations: GlimmerTreeChanges; public constructing: Nullable = null; @@ -83,13 +83,13 @@ export class NewElementBuilder implements ElementBuilder { [CURSOR_STACK] = new Stack(); private modifierStack = new Stack>(); - private blockStack = new Stack(); + private blockStack = new Stack(); static forInitialRender(env: Environment, cursor: CursorImpl) { return new this(env, cursor.element, cursor.nextSibling).initialize(); } - static resume(env: Environment, block: UpdatableBlock): NewElementBuilder { + static resume(env: Environment, block: ResettableBlock): NewTreeBuilder { let parentNode = block.parentElement(); let nextSibling = block.reset(env); @@ -107,11 +107,11 @@ export class NewElementBuilder implements ElementBuilder { } protected initialize(): this { - this.pushSimpleBlock(); + this.pushAppendingBlock(); return this; } - debugBlocks(): LiveBlock[] { + debugBlocks(): AppendingBlock[] { return this.blockStack.toArray(); } @@ -127,7 +127,7 @@ export class NewElementBuilder implements ElementBuilder { return this.blockStack.size > 0; } - protected block(): LiveBlock { + protected block(): AppendingBlock { return expect(this.blockStack.current, 'Expected a current live block'); } @@ -136,19 +136,19 @@ export class NewElementBuilder implements ElementBuilder { expect(this[CURSOR_STACK].current, "can't pop past the last element"); } - pushSimpleBlock(): LiveBlock { + pushAppendingBlock(): AppendingBlock { return this.pushLiveBlock(new SimpleLiveBlock(this.element)); } - pushUpdatableBlock(): UpdatableBlockImpl { + pushResettableBlock(): UpdatableBlockImpl { return this.pushLiveBlock(new UpdatableBlockImpl(this.element)); } - pushBlockList(list: LiveBlock[]): LiveBlockList { + pushBlockList(list: AppendingBlock[]): LiveBlockList { return this.pushLiveBlock(new LiveBlockList(this.element, list)); } - protected pushLiveBlock(block: T, isRemote = false): T { + protected pushLiveBlock(block: T, isRemote = false): T { let current = this.blockStack.current; if (current !== null) { @@ -162,7 +162,7 @@ export class NewElementBuilder implements ElementBuilder { return block; } - popBlock(): LiveBlock { + popBlock(): AppendingBlock { this.block().finalize(this); this.__closeBlock(); return expect(this.blockStack.pop(), 'Expected popBlock to return a block'); @@ -374,7 +374,7 @@ export class NewElementBuilder implements ElementBuilder { } } -export class SimpleLiveBlock implements LiveBlock { +export class SimpleLiveBlock implements AppendingBlock { protected first: Nullable = null; protected last: Nullable = null; protected nesting = 0; @@ -432,7 +432,7 @@ export class SimpleLiveBlock implements LiveBlock { this.last = bounds; } - finalize(stack: ElementBuilder) { + finalize(stack: TreeBuilder) { if (this.first === null) { stack.appendComment(''); } @@ -475,7 +475,7 @@ export class RemoteLiveBlock extends SimpleLiveBlock { } } -export class UpdatableBlockImpl extends SimpleLiveBlock implements UpdatableBlock { +export class UpdatableBlockImpl extends SimpleLiveBlock implements ResettableBlock { reset(): Nullable { destroy(this); let nextSibling = clear(this); @@ -489,10 +489,10 @@ export class UpdatableBlockImpl extends SimpleLiveBlock implements UpdatableBloc } // FIXME: All the noops in here indicate a modelling problem -export class LiveBlockList implements LiveBlock { +export class LiveBlockList implements AppendingBlock { constructor( private readonly parent: SimpleElement, - public boundList: LiveBlock[] + public boundList: AppendingBlock[] ) { this.parent = parent; this.boundList = boundList; @@ -536,11 +536,11 @@ export class LiveBlockList implements LiveBlock { didAppendBounds(_bounds: Bounds) {} - finalize(_stack: ElementBuilder) { + finalize(_stack: TreeBuilder) { assert(this.boundList.length > 0, 'boundsList cannot be empty'); } } -export function clientBuilder(env: Environment, cursor: CursorImpl): ElementBuilder { - return NewElementBuilder.forInitialRender(env, cursor); +export function clientBuilder(env: Environment, cursor: CursorImpl): TreeBuilder { + return NewTreeBuilder.forInitialRender(env, cursor); } diff --git a/packages/@glimmer/runtime/lib/vm/low-level.ts b/packages/@glimmer/runtime/lib/vm/low-level.ts index 919b7a051b..d4997cccee 100644 --- a/packages/@glimmer/runtime/lib/vm/low-level.ts +++ b/packages/@glimmer/runtime/lib/vm/low-level.ts @@ -1,4 +1,4 @@ -import type { Nullable, RuntimeHeap, RuntimeOp, RuntimeProgram } from '@glimmer/interfaces'; +import type { EvaluationContext, Nullable, RuntimeOp } from '@glimmer/interfaces'; import type { MachineRegister } from '@glimmer/vm'; import { VM_INVOKE_STATIC_OP, @@ -17,12 +17,7 @@ import type { VM } from './append'; import { APPEND_OPCODES } from '../opcodes'; -export interface LowLevelRegisters { - [$pc]: number; - [$ra]: number; - [$sp]: number; - [$fp]: number; -} +export type LowLevelRegisters = [$pc: number, $ra: number, $sp: number, $fp: number]; export function initializeRegisters(): LowLevelRegisters { return [0, -1, 0, 0]; @@ -41,9 +36,13 @@ export function initializeRegistersWithPC(pc: number): LowLevelRegisters { } export interface VmStack { + readonly registers: LowLevelRegisters; + push(value: unknown): void; get(position: number): number; pop(): T; + + snapshot?(): unknown[]; } export interface Externs { @@ -53,14 +52,18 @@ export interface Externs { export class LowLevelVM { public currentOpSize = 0; + readonly registers: LowLevelRegisters; + readonly context: EvaluationContext; constructor( public stack: VmStack, - public heap: RuntimeHeap, - public program: RuntimeProgram, + context: EvaluationContext, public externs: Externs | undefined, - readonly registers: LowLevelRegisters - ) {} + registers: LowLevelRegisters + ) { + this.context = context; + this.registers = registers; + } fetchRegister(register: MachineRegister): number { return this.registers[register]; @@ -111,7 +114,7 @@ export class LowLevelVM { assert(handle < 0xffffffff, `Jumping to placeholder address`); this.registers[$ra] = this.registers[$pc]; - this.setPc(this.heap.getaddr(handle)); + this.setPc(this.context.program.heap.getaddr(handle)); } // Put a specific `program` address in $ra @@ -125,7 +128,10 @@ export class LowLevelVM { } nextStatement(): Nullable { - let { registers, program } = this; + let { + registers, + context: { program }, + } = this; let pc = registers[$pc]; @@ -162,13 +168,13 @@ export class LowLevelVM { evaluateInner(opcode: RuntimeOp, vm: VM) { if (opcode.isMachine) { - this.evaluateMachine(opcode); + this.evaluateMachine(opcode, vm); } else { this.evaluateSyscall(opcode, vm); } } - evaluateMachine(opcode: RuntimeOp) { + evaluateMachine(opcode: RuntimeOp, vm: VM) { switch (opcode.type) { case VM_PUSH_FRAME_OP: return this.pushFrame(); @@ -177,11 +183,11 @@ export class LowLevelVM { case VM_INVOKE_STATIC_OP: return this.call(opcode.op1); case VM_INVOKE_VIRTUAL_OP: - return this.call(this.stack.pop()); + return vm.call(this.stack.pop()); case VM_JUMP_OP: return this.goto(opcode.op1); case VM_RETURN_OP: - return this.return(); + return vm.return(); case VM_RETURN_TO_OP: return this.returnTo(opcode.op1); } diff --git a/packages/@glimmer/runtime/lib/vm/rehydrate-builder.ts b/packages/@glimmer/runtime/lib/vm/rehydrate-builder.ts index 00e7218c21..f4d224a2bb 100644 --- a/packages/@glimmer/runtime/lib/vm/rehydrate-builder.ts +++ b/packages/@glimmer/runtime/lib/vm/rehydrate-builder.ts @@ -1,7 +1,6 @@ import type { AttrNamespace, Bounds, - ElementBuilder, Environment, Maybe, Nullable, @@ -10,13 +9,14 @@ import type { SimpleElement, SimpleNode, SimpleText, + TreeBuilder, } from '@glimmer/interfaces'; import type { Stack } from '@glimmer/util'; import { COMMENT_NODE, ELEMENT_NODE, NS_SVG, TEXT_NODE } from '@glimmer/constants'; import { assert, castToBrowser, castToSimple, expect } from '@glimmer/debug-util'; import { ConcreteBounds, CursorImpl } from '../bounds'; -import { CURSOR_STACK, NewElementBuilder, RemoteLiveBlock } from './element-builder'; +import { CURSOR_STACK, NewTreeBuilder, RemoteLiveBlock } from './element-builder'; export const SERIALIZATION_FIRST_NODE_STRING = '%+b:0%'; @@ -38,7 +38,7 @@ export class RehydratingCursor extends CursorImpl { } } -export class RehydrateBuilder extends NewElementBuilder implements ElementBuilder { +export class RehydrateBuilder extends NewTreeBuilder implements TreeBuilder { private unmatchedAttributes: Nullable = null; declare [CURSOR_STACK]: Stack; // Hides property on base class blockDepth = 0; @@ -126,7 +126,7 @@ export class RehydrateBuilder extends NewElementBuilder implements ElementBuilde /** called from parent constructor before we initialize this */ this: | RehydrateBuilder - | (NewElementBuilder & Partial>), + | (NewTreeBuilder & Partial>), element: SimpleElement, nextSibling: Maybe = null ) { @@ -550,6 +550,6 @@ function findByName(array: SimpleAttr[], name: string): SimpleAttr | undefined { return undefined; } -export function rehydrationBuilder(env: Environment, cursor: CursorImpl): ElementBuilder { +export function rehydrationBuilder(env: Environment, cursor: CursorImpl): TreeBuilder { return RehydrateBuilder.forInitialRender(env, cursor); } diff --git a/packages/@glimmer/runtime/lib/vm/render-result.ts b/packages/@glimmer/runtime/lib/vm/render-result.ts index 0faf4b3314..7b3563446e 100644 --- a/packages/@glimmer/runtime/lib/vm/render-result.ts +++ b/packages/@glimmer/runtime/lib/vm/render-result.ts @@ -1,6 +1,6 @@ import type { + AppendingBlock, Environment, - LiveBlock, RenderResult, SimpleElement, SimpleNode, @@ -15,7 +15,7 @@ export default class RenderResultImpl implements RenderResult { constructor( public env: Environment, private updating: UpdatingOpcode[], - private bounds: LiveBlock, + private bounds: AppendingBlock, readonly drop: object ) { associateDestroyableChild(this, drop); diff --git a/packages/@glimmer/runtime/lib/vm/update.ts b/packages/@glimmer/runtime/lib/vm/update.ts index b9c344fcb8..af35ee50da 100644 --- a/packages/@glimmer/runtime/lib/vm/update.ts +++ b/packages/@glimmer/runtime/lib/vm/update.ts @@ -1,16 +1,15 @@ import type { + AppendingBlock, Bounds, DynamicScope, - ElementBuilder, Environment, + EvaluationContext, ExceptionHandler, GlimmerTreeChanges, - LiveBlock, Nullable, - RuntimeContext, + ResettableBlock, Scope, SimpleComment, - UpdatableBlock, UpdatingOpcode, UpdatingVM as IUpdatingVM, } from '@glimmer/interfaces'; @@ -22,11 +21,11 @@ import { updateRef, valueForRef } from '@glimmer/reference'; import { logStep, Stack } from '@glimmer/util'; import { debug, resetTracking } from '@glimmer/validator'; -import type { VM, VmInitCallback } from './append'; +import type { Closure } from './append'; import type { LiveBlockList } from './element-builder'; import { clear, move as moveBounds } from '../bounds'; -import { NewElementBuilder } from './element-builder'; +import { NewTreeBuilder } from './element-builder'; export class UpdatingVM implements IUpdatingVM { public env: Environment; @@ -106,30 +105,15 @@ export interface VMState { readonly stack: unknown[]; } -export interface ResumableVMState { - resume(runtime: RuntimeContext, builder: ElementBuilder): VM; -} - -export class ResumableVMStateImpl implements ResumableVMState { - constructor( - readonly state: VMState, - private resumeCallback: VmInitCallback - ) {} - - resume(runtime: RuntimeContext, builder: ElementBuilder): VM { - return this.resumeCallback(runtime, this.state, builder); - } -} - export abstract class BlockOpcode implements UpdatingOpcode, Bounds { public children: UpdatingOpcode[]; - protected readonly bounds: LiveBlock; + protected readonly bounds: AppendingBlock; constructor( - protected state: ResumableVMState, - protected runtime: RuntimeContext, - bounds: LiveBlock, + protected state: Closure, + protected context: EvaluationContext, + bounds: AppendingBlock, children: UpdatingOpcode[] ) { this.children = children; @@ -156,19 +140,23 @@ export abstract class BlockOpcode implements UpdatingOpcode, Bounds { export class TryOpcode extends BlockOpcode implements ExceptionHandler { public type = 'try'; - protected declare bounds: UpdatableBlock; // Hides property on base class + protected declare bounds: ResettableBlock; // Shadows property on base class override evaluate(vm: UpdatingVM) { vm.try(this.children, this); } handleException() { - let { state, bounds, runtime } = this; + let { + state, + bounds, + context: { env }, + } = this; destroyChildren(this); - let elementStack = NewElementBuilder.resume(runtime.env, bounds); - let vm = state.resume(runtime, elementStack); + let tree = NewTreeBuilder.resume(env, bounds); + let vm = state.evaluate(tree); let updating: UpdatingOpcode[] = []; let children = (this.children = []); @@ -188,14 +176,14 @@ export class ListItemOpcode extends TryOpcode { public index = -1; constructor( - state: ResumableVMState, - runtime: RuntimeContext, - bounds: UpdatableBlock, + state: Closure, + context: EvaluationContext, + bounds: ResettableBlock, public key: unknown, public memo: Reference, public value: Reference ) { - super(state, runtime, bounds, []); + super(state, context, bounds, []); } updateReferences(item: OpaqueIterationItem) { @@ -224,13 +212,13 @@ export class ListBlockOpcode extends BlockOpcode { protected declare readonly bounds: LiveBlockList; constructor( - state: ResumableVMState, - runtime: RuntimeContext, + state: Closure, + context: EvaluationContext, bounds: LiveBlockList, children: ListItemOpcode[], private iterableRef: Reference ) { - super(state, runtime, bounds, children); + super(state, context, bounds, children); this.lastIterator = valueForRef(iterableRef); } @@ -359,16 +347,22 @@ export class ListBlockOpcode extends BlockOpcode { logStep!('list-updates', ['insert', item.key]); } - let { opcodeMap, bounds, state, runtime, children } = this; + let { + opcodeMap, + bounds, + state, + children, + context: { env }, + } = this; let { key } = item; let nextSibling = before === undefined ? this.marker : before.firstNode(); - let elementStack = NewElementBuilder.forInitialRender(runtime.env, { + let elementStack = NewTreeBuilder.forInitialRender(env, { element: bounds.parentElement(), nextSibling, }); - let vm = state.resume(runtime, elementStack); + let vm = state.evaluate(elementStack); vm.execute((vm) => { vm.pushUpdating();