xbintsc 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/src/binder/binder.d.ts +35 -1
  2. package/dist/src/binder/binder.js +138 -0
  3. package/dist/src/binder/binder.js.map +1 -1
  4. package/dist/src/codegen/generator/calls.d.ts +4 -0
  5. package/dist/src/codegen/generator/calls.js +104 -32
  6. package/dist/src/codegen/generator/calls.js.map +1 -1
  7. package/dist/src/codegen/generator/context.d.ts +2 -0
  8. package/dist/src/codegen/generator/context.js +36 -0
  9. package/dist/src/codegen/generator/context.js.map +1 -1
  10. package/dist/src/codegen/generator/expressions.d.ts +4 -1
  11. package/dist/src/codegen/generator/expressions.js +73 -2
  12. package/dist/src/codegen/generator/expressions.js.map +1 -1
  13. package/dist/src/codegen/generator/module.d.ts +5 -1
  14. package/dist/src/codegen/generator/module.js +106 -6
  15. package/dist/src/codegen/generator/module.js.map +1 -1
  16. package/dist/src/codegen/generator/state.d.ts +2 -0
  17. package/dist/src/codegen/generator/state.js +41 -0
  18. package/dist/src/codegen/generator/state.js.map +1 -1
  19. package/dist/src/codegen/generator/statements.js +9 -1
  20. package/dist/src/codegen/generator/statements.js.map +1 -1
  21. package/dist/src/codegen/generator/tables.d.ts +4 -0
  22. package/dist/src/codegen/generator/tables.js +33 -0
  23. package/dist/src/codegen/generator/tables.js.map +1 -1
  24. package/dist/src/driver/compiler.js +23 -7
  25. package/dist/src/driver/compiler.js.map +1 -1
  26. package/dist/src/driver/modules.d.ts +24 -0
  27. package/dist/src/driver/modules.js +347 -0
  28. package/dist/src/driver/modules.js.map +1 -0
  29. package/dist/src/parser/expressions.js +3 -0
  30. package/dist/src/parser/expressions.js.map +1 -1
  31. package/dist/tests/codegen/llvm.test.js +1 -1
  32. package/dist/tests/codegen/llvm.test.js.map +1 -1
  33. package/dist/tests/e2e/compile.test.js +86 -0
  34. package/dist/tests/e2e/compile.test.js.map +1 -1
  35. package/package.json +1 -1
  36. package/runtime/rt.h +55 -4
  37. package/runtime/rt_internal.h +63 -0
  38. package/runtime/xt_builtins.c +47 -1
  39. package/runtime/xt_containers.c +95 -8
  40. package/runtime/xt_io.c +113 -0
  41. package/runtime/xt_promise.c +292 -0
  42. package/runtime/xt_stdlib.c +59 -5
  43. package/runtime/xt_stdlib2.c +1043 -0
  44. package/src/binder/binder.ts +196 -0
  45. package/src/codegen/generator/calls.ts +111 -33
  46. package/src/codegen/generator/context.ts +35 -1
  47. package/src/codegen/generator/expressions.ts +79 -2
  48. package/src/codegen/generator/module.ts +111 -6
  49. package/src/codegen/generator/state.ts +43 -0
  50. package/src/codegen/generator/statements.ts +7 -1
  51. package/src/codegen/generator/tables.ts +35 -0
  52. package/src/driver/compiler.ts +28 -7
  53. package/src/driver/modules.ts +377 -0
  54. package/src/parser/expressions.ts +3 -0
@@ -9,14 +9,21 @@
9
9
  * cached, serialized for the incremental build, and unit tested in isolation.
10
10
  */
11
11
 
12
+ import { NodeFlags, ModifierKind } from "../ast/kinds.js";
12
13
  import {
13
14
  SyntaxKind,
14
15
  type ArrowFunction,
15
16
  type Block,
17
+ type ClassDeclaration,
18
+ type ClassExpression,
19
+ type ConstructorDeclaration,
16
20
  type FunctionDeclaration,
17
21
  type FunctionExpression,
18
22
  type Identifier,
23
+ type MethodDeclaration,
19
24
  type Node,
25
+ type PropertyDeclaration,
26
+ type PropertyName,
20
27
  type SourceFileNode,
21
28
  type Statement,
22
29
  type Expression,
@@ -76,6 +83,40 @@ export interface FunctionInfo {
76
83
  readonly captureIndex: Map<number, number>;
77
84
  readonly isModule: boolean;
78
85
  readonly isArrow: boolean;
86
+ /** True when the function was declared `async`. */
87
+ readonly isAsync: boolean;
88
+ /** True when this function's body mentions `this` directly. */
89
+ usesThis: boolean;
90
+ /** True when an arrow function must receive `this` from its enclosing scope. */
91
+ capturesThis: boolean;
92
+ /** Set for class methods/constructors; carries the owning class. */
93
+ classInfo?: ClassInfo;
94
+ /** True for `static` class members. */
95
+ isStatic?: boolean;
96
+ /** True for an explicit `constructor`. */
97
+ isConstructor?: boolean;
98
+ }
99
+
100
+ export interface ClassInfo {
101
+ readonly id: number;
102
+ readonly name: string;
103
+ readonly node: Node;
104
+ readonly scope: Scope;
105
+ readonly parent?: ClassInfo;
106
+ readonly parentExpression?: Expression;
107
+ /** `this`-bearing fields declared on the class. */
108
+ readonly fields: ClassFieldInfo[];
109
+ readonly methods: FunctionInfo[];
110
+ readonly statics: FunctionInfo[];
111
+ ctor?: FunctionInfo;
112
+ /** The symbol the class name binds to, when there is one. */
113
+ }
114
+
115
+ export interface ClassFieldInfo {
116
+ readonly name: string;
117
+ readonly initializer: Expression | undefined;
118
+ readonly fn: FunctionInfo;
119
+ readonly isStatic: boolean;
79
120
  }
80
121
 
81
122
  export enum ScopeKind {
@@ -98,6 +139,9 @@ export interface Scope {
98
139
  export interface BindResult {
99
140
  readonly moduleFunction: FunctionInfo;
100
141
  readonly functions: FunctionInfo[];
142
+ readonly classes: ClassInfo[];
143
+ /** Maps a class-like AST node to the ClassInfo the binder built. */
144
+ readonly classOfNode: Map<Node, ClassInfo>;
101
145
  /** Maps a function-like AST node to the FunctionInfo the binder built for it. */
102
146
  readonly functionOfNode: Map<Node, FunctionInfo>;
103
147
  readonly symbolOfDeclaration: Map<Node, SymbolInfo>;
@@ -120,12 +164,15 @@ export function bind(sourceFile: SourceFileNode): BindResult {
120
164
 
121
165
  class Binder {
122
166
  private readonly functions: FunctionInfo[] = [];
167
+ private readonly classes: ClassInfo[] = [];
168
+ private readonly classOfNode = new Map<Node, ClassInfo>();
123
169
  private readonly symbolOfDeclaration = new Map<Node, SymbolInfo>();
124
170
  private readonly symbolOfIdentifier = new Map<Identifier, SymbolInfo>();
125
171
  private readonly scopes = new Map<Node, Scope>();
126
172
  private readonly unresolved: Identifier[] = [];
127
173
  private nextSymbolId = 1;
128
174
  private nextFunctionId = 1;
175
+ private nextClassId = 1;
129
176
  private current!: MutableFunction;
130
177
 
131
178
  constructor(private readonly sourceFile: SourceFileNode) {}
@@ -140,9 +187,22 @@ class Binder {
140
187
  this.collectFunctionScoped(this.sourceFile.statements, this.current);
141
188
  this.bindStatements(this.sourceFile.statements, scope);
142
189
 
190
+ // Arrows inherit `this` lexically: propagate the need to capture it up the
191
+ // chain of arrow functions until reaching a regular function.
192
+ for (let index = this.functions.length - 1; index >= 0; index--) {
193
+ const fn = this.functions[index]! as MutableFunction;
194
+ if (fn.usesThis && fn.isArrow) {
195
+ fn.capturesThis = true;
196
+ const parent = fn.parent as MutableFunction | undefined;
197
+ if (parent && parent.isArrow) parent.usesThis = true;
198
+ }
199
+ }
200
+
143
201
  return {
144
202
  moduleFunction: this.current,
145
203
  functions: this.functions,
204
+ classes: this.classes,
205
+ classOfNode: this.classOfNode,
146
206
  functionOfNode: new Map(this.functions.map((fn) => [fn.node as Node, fn])),
147
207
  symbolOfDeclaration: this.symbolOfDeclaration,
148
208
  symbolOfIdentifier: this.symbolOfIdentifier,
@@ -172,6 +232,9 @@ class Binder {
172
232
  captureIndex: new Map(),
173
233
  isModule,
174
234
  isArrow,
235
+ isAsync: (((node as { flags?: number }).flags ?? 0) & NodeFlags.Async) !== 0,
236
+ usesThis: false,
237
+ capturesThis: false,
175
238
  };
176
239
  this.functions.push(fn);
177
240
  return fn;
@@ -369,6 +432,13 @@ class Binder {
369
432
  case SyntaxKind.Identifier:
370
433
  this.reference(node as Identifier, scope);
371
434
  return;
435
+ case SyntaxKind.ThisKeyword:
436
+ this.current.usesThis = true;
437
+ return;
438
+ case SyntaxKind.ClassDeclaration:
439
+ case SyntaxKind.ClassExpression:
440
+ this.bindClass(node as ClassDeclaration | ClassExpression, scope);
441
+ return;
372
442
  case SyntaxKind.InterfaceDeclaration:
373
443
  case SyntaxKind.TypeAliasDeclaration:
374
444
  // Type-only: skip the body entirely.
@@ -490,7 +560,118 @@ class Binder {
490
560
  this.current = parentFn;
491
561
  }
492
562
 
563
+ private bindClass(node: ClassDeclaration | ClassExpression, scope: Scope): void {
564
+ const parentFn = this.current;
565
+ const name = node.name ? node.name.text : "(anonymous)";
566
+
567
+ let parentExpression: Expression | undefined;
568
+ let parentClass: ClassInfo | undefined;
569
+ const heritage = node.heritage.find((clause) => clause.token === "extends");
570
+ if (heritage && heritage.types.length > 0) {
571
+ parentExpression = heritage.types[0]!.expression;
572
+ this.bindNode(parentExpression, scope);
573
+ if (parentExpression.kind === SyntaxKind.Identifier) {
574
+ const symbol = this.symbolOfIdentifier.get(parentExpression as Identifier);
575
+ const declaration = symbol?.declarations[0];
576
+ if (declaration) parentClass = this.classOfNode.get(declaration);
577
+ }
578
+ }
579
+
580
+ const info: ClassInfo = {
581
+ id: this.nextClassId++,
582
+ name,
583
+ node,
584
+ scope,
585
+ parent: parentClass,
586
+ parentExpression,
587
+ fields: [],
588
+ methods: [],
589
+ statics: [],
590
+ };
591
+ this.classes.push(info);
592
+ this.classOfNode.set(node, info);
593
+
594
+ const classScope = this.createScope(ScopeKind.Block, node, scope);
595
+ this.scopes.set(node, classScope);
596
+
597
+ let constructor: FunctionInfo | undefined;
598
+ for (const member of node.members) {
599
+ if (member.kind === SyntaxKind.ConstructorDeclaration) {
600
+ const ctor = member as ConstructorDeclaration;
601
+ constructor = this.createClassFunction(ctor, "constructor", ctor.parameters, ctor.body, classScope, info, false, true);
602
+ } else if (member.kind === SyntaxKind.MethodDeclaration) {
603
+ const method = member as MethodDeclaration;
604
+ const isStatic = method.modifiers.some((modifier) => modifier.modifierKind === ModifierKind.Static);
605
+ const fn = this.createClassFunction(method, classMemberName(method.name), method.parameters, method.body, classScope, info, isStatic, false);
606
+ if (isStatic) (info.statics as FunctionInfo[]).push(fn);
607
+ else (info.methods as FunctionInfo[]).push(fn);
608
+ }
609
+ }
610
+ if (!constructor) {
611
+ constructor = this.createClassFunction(node, "constructor", [], undefined, classScope, info, false, true);
612
+ }
613
+ info.ctor = constructor;
614
+
615
+ // Field initializers run in the constructor's context so they can capture
616
+ // enclosing variables.
617
+ for (const member of node.members) {
618
+ if (member.kind === SyntaxKind.PropertyDeclaration) {
619
+ const property = member as PropertyDeclaration;
620
+ const isStatic = property.modifiers.some((modifier) => modifier.modifierKind === ModifierKind.Static);
621
+ const owner = this.current;
622
+ this.current = constructor as MutableFunction;
623
+ if (property.initializer) this.bindNode(property.initializer, classScope);
624
+ this.current = owner;
625
+ info.fields.push({ name: classMemberName(property.name), initializer: property.initializer, fn: constructor, isStatic });
626
+ }
627
+ }
628
+
629
+ this.current = parentFn;
630
+ }
631
+
632
+ private createClassFunction(
633
+ member: Node,
634
+ name: string,
635
+ parameters: readonly import("../ast/declarations.js").Parameter[],
636
+ body: Block | undefined,
637
+ parentScope: Scope,
638
+ info: ClassInfo,
639
+ isStatic: boolean,
640
+ isConstructor: boolean,
641
+ ): FunctionInfo {
642
+ const parentFn = this.current;
643
+ const fn = this.createFunction(name, member as unknown as FunctionNode, parentFn, false, false);
644
+ (fn as MutableFunction & { classInfo: ClassInfo }).classInfo = info;
645
+ (fn as MutableFunction & { isStatic: boolean }).isStatic = isStatic;
646
+ (fn as MutableFunction & { isConstructor: boolean }).isConstructor = isConstructor;
647
+ this.current = fn as MutableFunction;
648
+ const scope = this.createScope(ScopeKind.Function, member, parentScope);
649
+ (fn as { scope: Scope }).scope = scope;
650
+ this.scopes.set(member, scope);
651
+
652
+ const params: SymbolInfo[] = [];
653
+ for (const parameter of parameters) {
654
+ const paramSymbol = this.declare(parameter.name.text, SymbolKind.Parameter, parameter, scope, true);
655
+ params.push(paramSymbol);
656
+ if (parameter.initializer) this.bindNode(parameter.initializer, scope);
657
+ }
658
+ (fn as { params: SymbolInfo[] }).params = params;
659
+
660
+ if (body) {
661
+ this.collectFunctionScoped([body], fn as MutableFunction);
662
+ this.predeclareStatements(body.statements, scope);
663
+ this.bindStatements(body.statements, scope);
664
+ }
665
+
666
+ this.current = parentFn;
667
+ return fn;
668
+ }
669
+
493
670
  private reference(identifier: Identifier, scope: Scope): void {
671
+ if (identifier.text === "super") {
672
+ this.current.usesThis = true;
673
+ return;
674
+ }
494
675
  let current: Scope | undefined = scope;
495
676
  while (current) {
496
677
  const symbol = current.symbols.get(identifier.text);
@@ -547,3 +728,18 @@ function isCapturable(kind: SymbolKind): boolean {
547
728
  kind === SymbolKind.Parameter
548
729
  );
549
730
  }
731
+
732
+ /** Extract the textual name of a class member (`method`, `"key"`, `0`). */
733
+ function classMemberName(name: PropertyName): string {
734
+ switch (name.kind) {
735
+ case SyntaxKind.Identifier:
736
+ case SyntaxKind.PrivateIdentifier:
737
+ return (name as unknown as { text: string }).text;
738
+ case SyntaxKind.StringLiteral:
739
+ return (name as unknown as { value: string }).value;
740
+ case SyntaxKind.NumericLiteral:
741
+ return String((name as unknown as { value: number }).value);
742
+ default:
743
+ return "";
744
+ }
745
+ }
@@ -15,14 +15,16 @@ import {
15
15
  type ObjectLiteralExpression,
16
16
  type PropertyAccessExpression,
17
17
  } from "../../ast/nodes.js";
18
- import { SymbolKind } from "../../binder/binder.js";
18
+ import { SymbolKind, type FunctionInfo } from "../../binder/binder.js";
19
19
  import { i64, numberLiteral, XT_TRUE, XT_UNDEFINED } from "../values.js";
20
20
  import {
21
21
  BUILTIN_METHODS,
22
22
  CONSOLE_METHODS,
23
+ CTOR_FUNCTIONS,
23
24
  GLOBAL_FUNCTIONS,
24
25
  MATH_CONSTANTS,
25
26
  MATH_FUNCTIONS,
27
+ NAMESPACE_STATICS,
26
28
  propertyNameText,
27
29
  } from "./tables.js";
28
30
  import type { Generator } from "./generator.js";
@@ -39,6 +41,9 @@ export interface CallMethods {
39
41
  emitArrayLiteral(this: Generator, node: ArrayLiteralExpression): string;
40
42
  emitObjectLiteral(this: Generator, node: ObjectLiteralExpression): string;
41
43
  emitClosure(this: Generator, node: ArrowFunction | FunctionExpression): string;
44
+ emitClosureValue(this: Generator, fn: FunctionInfo): string;
45
+ emitSuperConstructor(this: Generator, node: CallExpression): string;
46
+ emitSuperCall(this: Generator, node: CallExpression, access: PropertyAccessExpression): string;
42
47
  }
43
48
 
44
49
  export const callMethods: CallMethods = {
@@ -76,10 +81,30 @@ export const callMethods: CallMethods = {
76
81
  });
77
82
  }
78
83
  if (callee.kind === SyntaxKind.PropertyAccessExpression) {
79
- const special = this.tryEmitBuiltinCall(node, callee as PropertyAccessExpression);
84
+ const access = callee as PropertyAccessExpression;
85
+ const special = this.tryEmitBuiltinCall(node, access);
80
86
  if (special) return special;
87
+ if (access.expression.kind === SyntaxKind.Identifier && (access.expression as Identifier).text === "super") {
88
+ return this.emitSuperCall(node, access);
89
+ }
90
+ const object = this.emitExpression(access.expression);
91
+ const name = this.stringValue(access.name.text);
92
+ const args = this.emitArguments(node.arguments);
93
+ const result = this.reg();
94
+ this.emit(` ${result} = call i64 @xt_call_method(i64 ${object}, i64 ${name}, i32 ${args.argc}, i64* ${args.ptr})`);
95
+ return result;
96
+ }
97
+ if (callee.kind === SyntaxKind.ElementAccessExpression) {
98
+ const access = callee as ElementAccessExpression;
99
+ const object = this.emitExpression(access.expression);
100
+ const name = this.emitExpression(access.argumentExpression);
101
+ const args = this.emitArguments(node.arguments);
102
+ const result = this.reg();
103
+ this.emit(` ${result} = call i64 @xt_call_method(i64 ${object}, i64 ${name}, i32 ${args.argc}, i64* ${args.ptr})`);
104
+ return result;
81
105
  }
82
106
  if (callee.kind === SyntaxKind.Identifier) {
107
+ if ((callee as Identifier).text === "super") return this.emitSuperConstructor(node);
83
108
  const symbol = this.binding.symbolOfIdentifier.get(callee as Identifier);
84
109
  if (symbol && symbol.kind === SymbolKind.Function) {
85
110
  const declaration = symbol.declarations[0];
@@ -88,7 +113,7 @@ export const callMethods: CallMethods = {
88
113
  const args = this.emitArguments(node.arguments);
89
114
  const result = this.reg();
90
115
  this.emit(
91
- ` ${result} = call i64 @${this.functionName(fn)}(i64 ${i64(XT_UNDEFINED)}, i32 ${args.argc}, i64* ${args.ptr})`,
116
+ ` ${result} = call i64 @${this.functionName(fn)}(i64 ${i64(XT_UNDEFINED)}, i64 ${i64(XT_UNDEFINED)}, i32 ${args.argc}, i64* ${args.ptr})`,
92
117
  );
93
118
  return result;
94
119
  }
@@ -114,6 +139,13 @@ export const callMethods: CallMethods = {
114
139
  this.emit(` ${result} = call i64 @${builtin.symbol}(i32 ${args.argc}, i64* ${args.ptr})`);
115
140
  return result;
116
141
  }
142
+ const ctor = !symbol ? CTOR_FUNCTIONS[(callee as Identifier).text] : undefined;
143
+ if (ctor) {
144
+ const args = this.emitArguments(node.arguments);
145
+ const result = this.reg();
146
+ this.emit(` ${result} = call i64 @${ctor}(i32 ${args.argc}, i64* ${args.ptr})`);
147
+ return result;
148
+ }
117
149
  }
118
150
  const calleeValue = this.emitExpression(callee);
119
151
  const args = this.emitArguments(node.arguments);
@@ -147,31 +179,27 @@ export const callMethods: CallMethods = {
147
179
  return result;
148
180
  }
149
181
 
150
- if (targetIdentifier && targetIdentifier.text === "Object" && !targetSymbol) {
151
- if (method === "keys" || method === "values" || method === "entries") {
152
- const argument = node.arguments.length > 0 ? this.emitExpression(node.arguments[0]!) : i64(XT_UNDEFINED);
153
- const fn = method === "keys" ? "xt_object_keys" : method === "values" ? "xt_object_values" : "xt_object_entries";
154
- return this.runtimeCall(fn, [argument]);
155
- }
156
- if (method === "assign") {
182
+ if (targetIdentifier && targetIdentifier.text === "JSON" && !targetSymbol) {
183
+ const fn = method === "parse" ? "xt_json_parse" : method === "stringify" ? "xt_json_stringify" : undefined;
184
+ if (!fn) return undefined;
185
+ const args = this.emitArguments(node.arguments);
186
+ const result = this.reg();
187
+ this.emit(` ${result} = call i64 @${fn}(i32 ${args.argc}, i64* ${args.ptr})`);
188
+ return result;
189
+ }
190
+
191
+ if (targetIdentifier && !targetSymbol) {
192
+ const dispatcher = NAMESPACE_STATICS[targetIdentifier.text];
193
+ if (dispatcher && targetIdentifier.text !== "Math" && targetIdentifier.text !== "JSON") {
194
+ const name = this.stringValue(method);
157
195
  const args = this.emitArguments(node.arguments);
158
196
  const result = this.reg();
159
- this.emit(` ${result} = call i64 @xt_object_assign(i32 ${args.argc}, i64* ${args.ptr})`);
197
+ this.emit(` ${result} = call i64 @${dispatcher}(i64 ${name}, i32 ${args.argc}, i64* ${args.ptr})`);
160
198
  return result;
161
199
  }
162
- return undefined;
163
200
  }
164
201
 
165
- if (BUILTIN_METHODS.has(method)) {
166
- const object = this.emitExpression(target);
167
- const name = this.stringValue(method);
168
- const args = this.emitArguments(node.arguments);
169
- const result = this.reg();
170
- this.emit(
171
- ` ${result} = call i64 @xt_call_method(i64 ${object}, i64 ${name}, i32 ${args.argc}, i64* ${args.ptr})`,
172
- );
173
- return result;
174
- }
202
+ void BUILTIN_METHODS;
175
203
  return undefined;
176
204
  },
177
205
 
@@ -212,6 +240,17 @@ export const callMethods: CallMethods = {
212
240
  },
213
241
 
214
242
  emitPropertyAccess(node: PropertyAccessExpression): string {
243
+ if (node.expression.kind === SyntaxKind.Identifier && (node.expression as Identifier).text === "super") {
244
+ const thisValue = this.emitThis();
245
+ const proto = this.reg();
246
+ this.emit(` ${proto} = call i64 @xt_object_get_prototype(i64 ${thisValue})`);
247
+ const parentProto = this.reg();
248
+ this.emit(` ${parentProto} = call i64 @xt_object_get_prototype(i64 ${proto})`);
249
+ const key = this.stringValue(node.name.text);
250
+ const value = this.reg();
251
+ this.emit(` ${value} = call i64 @xt_get(i64 ${parentProto}, i64 ${key})`);
252
+ return value;
253
+ }
215
254
  if (
216
255
  node.name.text in MATH_CONSTANTS &&
217
256
  node.expression.kind === SyntaxKind.Identifier &&
@@ -323,33 +362,72 @@ export const callMethods: CallMethods = {
323
362
  this.unsupported(node, "closure");
324
363
  return i64(XT_UNDEFINED);
325
364
  }
365
+ return this.emitClosureValue(fn);
366
+ },
367
+
368
+ emitClosureValue(fn: FunctionInfo): string {
369
+ const count = fn.captures.length + (fn.capturesThis ? 1 : 0);
326
370
  let envPtr = "null";
327
- if (fn.captures.length > 0) {
371
+ if (count > 0) {
328
372
  envPtr = `%env${this.current.allocas.length}`;
329
- this.current.allocas.push(`${envPtr} = alloca i64, i32 ${fn.captures.length}`);
373
+ this.current.allocas.push(`${envPtr} = alloca i64, i32 ${count}`);
330
374
  for (let index = 0; index < fn.captures.length; index++) {
331
375
  const symbol = fn.captures[index]!;
332
- // The current function must be able to read the capture: either from
333
- // its own slot (local) or from one of its own capture slots.
334
376
  const slot = this.current.slots.get(symbol.id);
335
- let raw: string;
377
+ let raw = i64(XT_UNDEFINED);
336
378
  if (slot) {
337
379
  raw = this.reg();
338
380
  this.emit(` ${raw} = load i64, i64* ${slot.ptr}`);
339
- } else {
340
- const outer = this.binding.functionOfNode.get(node);
341
- void outer;
342
- raw = i64(XT_UNDEFINED);
343
381
  }
344
382
  const target = this.reg();
345
383
  this.emit(` ${target} = getelementptr i64, i64* ${envPtr}, i32 ${index}`);
346
384
  this.emit(` store i64 ${raw}, i64* ${target}`);
347
385
  }
386
+ if (fn.capturesThis) {
387
+ let thisValue = i64(XT_UNDEFINED);
388
+ if (this.current.thisPtr) {
389
+ thisValue = this.reg();
390
+ this.emit(` ${thisValue} = load i64, i64* ${this.current.thisPtr}`);
391
+ }
392
+ const target = this.reg();
393
+ this.emit(` ${target} = getelementptr i64, i64* ${envPtr}, i32 ${fn.captures.length}`);
394
+ this.emit(` store i64 ${thisValue}, i64* ${target}`);
395
+ }
348
396
  }
349
397
  const cast = this.reg();
350
- this.emit(` ${cast} = bitcast i64 (i64, i32, i64*)* @${this.functionName(fn)} to i8*`);
398
+ this.emit(` ${cast} = bitcast i64 (i64, i64, i32, i64*)* @${this.functionName(fn)} to i8*`);
351
399
  const closure = this.reg();
352
- this.emit(` ${closure} = call i64 @xt_closure_new(i8* ${cast}, i32 ${fn.captures.length}, i64* ${envPtr})`);
400
+ this.emit(` ${closure} = call i64 @xt_closure_new(i8* ${cast}, i32 ${count}, i64* ${envPtr})`);
353
401
  return closure;
354
402
  },
403
+
404
+ emitSuperConstructor(node: CallExpression): string {
405
+ const thisValue = this.emitThis();
406
+ const proto = this.reg();
407
+ this.emit(` ${proto} = call i64 @xt_object_get_prototype(i64 ${thisValue})`);
408
+ const parentProto = this.reg();
409
+ this.emit(` ${parentProto} = call i64 @xt_object_get_prototype(i64 ${proto})`);
410
+ const key = this.stringValue("__ctor");
411
+ const ctor = this.reg();
412
+ this.emit(` ${ctor} = call i64 @xt_get(i64 ${parentProto}, i64 ${key})`);
413
+ const args = this.emitArguments(node.arguments);
414
+ const result = this.reg();
415
+ this.emit(` ${result} = call i64 @xt_call_with_this(i64 ${ctor}, i64 ${thisValue}, i32 ${args.argc}, i64* ${args.ptr})`);
416
+ return result;
417
+ },
418
+
419
+ emitSuperCall(node: CallExpression, access: PropertyAccessExpression): string {
420
+ const thisValue = this.emitThis();
421
+ const proto = this.reg();
422
+ this.emit(` ${proto} = call i64 @xt_object_get_prototype(i64 ${thisValue})`);
423
+ const parentProto = this.reg();
424
+ this.emit(` ${parentProto} = call i64 @xt_object_get_prototype(i64 ${proto})`);
425
+ const key = this.stringValue(access.name.text);
426
+ const method = this.reg();
427
+ this.emit(` ${method} = call i64 @xt_get(i64 ${parentProto}, i64 ${key})`);
428
+ const args = this.emitArguments(node.arguments);
429
+ const result = this.reg();
430
+ this.emit(` ${result} = call i64 @xt_call_with_this(i64 ${method}, i64 ${thisValue}, i32 ${args.argc}, i64* ${args.ptr})`);
431
+ return result;
432
+ },
355
433
  };
@@ -6,7 +6,7 @@
6
6
  * helpers every group relies on.
7
7
  */
8
8
 
9
- import { bind, type BindResult, type SymbolInfo } from "../../binder/binder.js";
9
+ import { bind, type BindResult, type ClassInfo, type SymbolInfo } from "../../binder/binder.js";
10
10
  import type { DiagnosticBag } from "../../diagnostics/diagnostic.js";
11
11
  import { DiagnosticCode } from "../../diagnostics/diagnostic.js";
12
12
  import type { SourceFileNode, Node } from "../../ast/nodes.js";
@@ -23,6 +23,8 @@ export class GeneratorContext {
23
23
  readonly globals: string[] = [];
24
24
  readonly functions: string[] = [];
25
25
  readonly strings = new Map<string, { label: string; length: number }>();
26
+ readonly classGlobals = new Map<number, string>();
27
+ readonly moduleGlobals = new Map<number, string>();
26
28
  readonly extraDeclarations = new Set<string>();
27
29
  stringCounter = 0;
28
30
  current!: FunctionState;
@@ -72,6 +74,18 @@ export class GeneratorContext {
72
74
  // -- variables -----------------------------------------------------------
73
75
 
74
76
  declareSlot(symbol: SymbolInfo, initial: string): void {
77
+ const globalName = this.moduleGlobals.get(symbol.id);
78
+ if (globalName) {
79
+ if (symbol.boxed) {
80
+ const box = this.reg();
81
+ this.emit(` ${box} = call i64 @xt_box_new(i64 ${initial})`);
82
+ this.emit(` store i64 ${box}, i64* ${globalName}`);
83
+ } else {
84
+ this.emit(` store i64 ${initial}, i64* ${globalName}`);
85
+ }
86
+ this.current.slots.set(symbol.id, { ptr: globalName, boxed: symbol.boxed });
87
+ return;
88
+ }
75
89
  const ptr = this.alloca();
76
90
  if (symbol.boxed) {
77
91
  const box = this.reg();
@@ -91,6 +105,15 @@ export class GeneratorContext {
91
105
  }
92
106
 
93
107
  readSlot(symbol: SymbolInfo): string {
108
+ const globalName = this.moduleGlobals.get(symbol.id);
109
+ if (globalName) {
110
+ const value = this.reg();
111
+ this.emit(` ${value} = load i64, i64* ${globalName}`);
112
+ if (!symbol.boxed) return value;
113
+ const unboxed = this.reg();
114
+ this.emit(` ${unboxed} = call i64 @xt_box_get(i64 ${value})`);
115
+ return unboxed;
116
+ }
94
117
  const slot = this.current.slots.get(symbol.id);
95
118
  if (!slot) {
96
119
  // Referenced from an inner function without a capture slot: this should
@@ -110,6 +133,17 @@ export class GeneratorContext {
110
133
  }
111
134
 
112
135
  writeSlot(symbol: SymbolInfo, value: string): void {
136
+ const globalName = this.moduleGlobals.get(symbol.id);
137
+ if (globalName) {
138
+ if (!symbol.boxed) {
139
+ this.emit(` store i64 ${value}, i64* ${globalName}`);
140
+ return;
141
+ }
142
+ const box = this.reg();
143
+ this.emit(` ${box} = load i64, i64* ${globalName}`);
144
+ this.emit(` call i64 @xt_box_set(i64 ${box}, i64 ${value})`);
145
+ return;
146
+ }
113
147
  const slot = this.current.slots.get(symbol.id);
114
148
  if (!slot) {
115
149
  this.diagnostics.error(DiagnosticCode.CodegenError, `Internal: no slot for '${symbol.name}'`);