ReviewOS

zig-utils/zig-js

VM: initialize base class instance elements in constructor activations

#747
Closed chris opened this 9 days ago ·
9 days ago

Parent: #746 Parents of parent: #465, #498 Discovered by: #745

Problem

Base class constructors without instance fields already own ordinary VM chunks, but buildClass marks every constructor with instance initializers as plain_policy_class_constructor_semantics. The field AST is currently retained separately for tree-walk execution before parameter binding, so required bytecode cannot construct even a base class C { value = 1 }.

Scope

  • Compile base-class instance initialization as a constructor-chunk prefix before arguments-object creation, parameter defaults/destructuring, and the constructor body.
  • Resolve field initializer identifiers through the class-definition closure, never constructor parameter/body slots.
  • Implement canonical public CreateDataPropertyOrThrow and private PrivateFieldAdd operations, including anonymous-function naming, computed names already evaluated once at class definition, private method/accessor brands before fields, and source-order initialization.
  • Preserve field-initializer lexical context for this, super properties, new.target === undefined, direct-eval restrictions, and arrows across nested calls, errors, moving GC, OOM, and shared no-GIL execution.
  • Remove the class-constructor policy barrier only for base constructors whose complete initializer prefix lowers; derived constructors remain under #746 until super() activation is represented.

No-workaround rules

  • No eager field evaluation at class definition, AST callback from bytecode, source rewriting, constructor-shape whitelist, parameter-slot aliasing, duplicate computed-name evaluation, setter-based public field assignment, skipped private brand checks, or hidden fallback in required mode.
  • No stacks, queues, jobs, Promise, microtask, Worker lifecycle, or Map/Set iterator changes.

Acceptance

  • Forced tree-walker and required bytecode agree on public/private fields, auto-accessor storage, computed names, anonymous naming, brand/field order, prototype-setter shadowing, non-extensible errors, and field-before-parameter ordering.
  • Field initializers resolve the class closure rather than constructor parameters/body locals and preserve this, super, new.target, direct-eval restrictions, and arrow lexical state.
  • Admission inventory compiles complete base-field constructors while derived constructors stay causally fail-closed under #746.
  • Moving-GC, OOM restoration, shared no-GIL, TSan, focused frontend/compiler/VM, full units, threadfuzz, and exact nonzero relevant Test262 accounting pass with zero regressions.
  • A conventional commit lands directly on main and cross-links #465, #498, #745, and #746.
9 days ago

Landed on main in d9d480d0 feat(compiler): lower base class field initializers (#747).

The constructor VM chunk now owns a pre-parameter instance-element prefix with dedicated public DefineField/private brand operations and activation-local field context. Computed keys are evaluated once at class definition and retained in arena-owned storage before deferred compilation; implicit-undefined fields retain the same marker, including Symbol keys. Identifier resolution bypasses constructor slots and uses the class closure. Derived constructors remain causally blocked under #746.

Validation:

  • forced tree-walker vs required bytecode: exact public/private/auto-accessor/computed Symbol+string/naming/brand/order/closure/this/super/new.target/direct-eval/arrow behavior
  • moving GC + compaction, deterministic OOM unwind, native four-lane shared-chunk no-GIL, and focused normal/TSan gates: pass
  • Test262 test/language/expressions/class: 4,059 → 4,059, 0 failures, 0 flips
  • Test262 test/language/statements/class: 4,367 → 4,367, 0 failures, 0 flips
  • full units: 1,891 passed, 1 skipped, 0 failed, 0 leaked
  • frontend: 25/25; threadfuzz: 400/400 with 0 failures in normal and TSan builds

This closes the base-constructor slice discovered by #745 and advances #746, #465, and #498 without touching jobs/Promise/microtask/Worker lifecycle or Map/Set iterator surfaces.

Sign in to comment on this issue.