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
@@ -7,14 +7,17 @@ import {
7
7
  SyntaxKind,
8
8
  type ArrayLiteralExpression,
9
9
  type ArrowFunction,
10
+ type AwaitExpression,
10
11
  type BinaryExpression,
11
12
  type CallExpression,
13
+ type ClassExpression,
12
14
  type ConditionalExpression,
13
15
  type DeleteExpression,
14
16
  type ElementAccessExpression,
15
17
  type Expression,
16
18
  type FunctionExpression,
17
19
  type Identifier,
20
+ type NewExpression,
18
21
  type ObjectLiteralExpression,
19
22
  type PostfixUnaryExpression,
20
23
  type PrefixUnaryExpression,
@@ -30,7 +33,7 @@ import {
30
33
  import { SymbolKind, type SymbolInfo } from "../../binder/binder.js";
31
34
  import { DiagnosticCode } from "../../diagnostics/diagnostic.js";
32
35
  import { i64, numberLiteral, XT_FALSE, XT_NULL, XT_TRUE, XT_UNDEFINED } from "../values.js";
33
- import { BINARY_RUNTIME, compoundToBinary, isAssignmentOperator } from "./tables.js";
36
+ import { BINARY_RUNTIME, compoundToBinary, isAssignmentOperator, CTOR_FUNCTIONS } from "./tables.js";
34
37
  import type { Generator } from "./generator.js";
35
38
 
36
39
  export interface ExpressionMethods {
@@ -46,6 +49,9 @@ export interface ExpressionMethods {
46
49
  emitAssignment(this: Generator, node: BinaryExpression): string;
47
50
  emitLogicalAssignment(this: Generator, node: BinaryExpression): string;
48
51
  emitAssignmentTarget(this: Generator, target: Expression, value: string): void;
52
+ emitThis(this: Generator): string;
53
+ emitNew(this: Generator, node: NewExpression): string;
54
+ emitAwait(this: Generator, node: AwaitExpression): string;
49
55
  }
50
56
 
51
57
  export const expressionMethods: ExpressionMethods = {
@@ -81,6 +87,21 @@ export const expressionMethods: ExpressionMethods = {
81
87
  return this.emitExpression((node as { expression: Expression }).expression);
82
88
  case SyntaxKind.DeleteExpression:
83
89
  return this.emitDelete(node as DeleteExpression);
90
+ case SyntaxKind.ThisKeyword:
91
+ return this.emitThis();
92
+ case SyntaxKind.NewExpression:
93
+ return this.emitNew(node as NewExpression);
94
+ case SyntaxKind.AwaitExpression:
95
+ return this.emitAwait(node as AwaitExpression);
96
+ case SyntaxKind.ClassExpression: {
97
+ const info = this.binding.classOfNode.get(node);
98
+ if (!info) {
99
+ this.unsupported(node, "class expression");
100
+ return i64(XT_UNDEFINED);
101
+ }
102
+ this.emitClassSetup(info);
103
+ return this.emitClassReference(info);
104
+ }
84
105
  case SyntaxKind.BinaryExpression:
85
106
  return this.emitBinaryOrAssignment(node as BinaryExpression);
86
107
  case SyntaxKind.PrefixUnaryExpression:
@@ -111,6 +132,11 @@ export const expressionMethods: ExpressionMethods = {
111
132
  emitIdentifier(identifier: Identifier): string {
112
133
  const symbol = this.binding.symbolOfIdentifier.get(identifier);
113
134
  if (symbol) {
135
+ if (symbol.kind === SymbolKind.Class) {
136
+ const declaration = symbol.declarations[0];
137
+ const info = declaration ? this.binding.classOfNode.get(declaration) : undefined;
138
+ if (info) return this.emitClassReference(info);
139
+ }
114
140
  if (symbol.kind === SymbolKind.Function && !this.current.slots.has(symbol.id)) {
115
141
  return this.emitFunctionValue(symbol);
116
142
  }
@@ -150,12 +176,63 @@ export const expressionMethods: ExpressionMethods = {
150
176
  const fn = declaration ? this.binding.functionOfNode.get(declaration) : undefined;
151
177
  if (!fn) return i64(XT_UNDEFINED);
152
178
  const cast = this.reg();
153
- this.emit(` ${cast} = bitcast i64 (i64, i32, i64*)* @${this.functionName(fn)} to i8*`);
179
+ this.emit(` ${cast} = bitcast i64 (i64, i64, i32, i64*)* @${this.functionName(fn)} to i8*`);
154
180
  const closure = this.reg();
155
181
  this.emit(` ${closure} = call i64 @xt_closure_new(i8* ${cast}, i32 0, i64* null)`);
156
182
  return closure;
157
183
  },
158
184
 
185
+ emitThis(): string {
186
+ if (!this.current.thisPtr) return i64(XT_UNDEFINED);
187
+ const value = this.reg();
188
+ this.emit(` ${value} = load i64, i64* ${this.current.thisPtr}`);
189
+ return value;
190
+ },
191
+
192
+ emitAwait(node: AwaitExpression): string {
193
+ const value = this.emitExpression(node.expression);
194
+ const result = this.reg();
195
+ this.emit(` ${result} = call i64 @xt_await(i64 ${value})`);
196
+ return result;
197
+ },
198
+
199
+ emitNew(node: NewExpression): string {
200
+ const callee = node.expression;
201
+ if (callee.kind === SyntaxKind.Identifier) {
202
+ const identifier = callee as Identifier;
203
+ const symbol = this.binding.symbolOfIdentifier.get(identifier);
204
+ if (symbol && symbol.kind === SymbolKind.Class) {
205
+ const declaration = symbol.declarations[0];
206
+ const info = declaration ? this.binding.classOfNode.get(declaration) : undefined;
207
+ if (info) {
208
+ const ctor = this.emitClassReference(info);
209
+ const args = this.emitArguments(node.arguments);
210
+ const result = this.reg();
211
+ this.emit(` ${result} = call i64 @xt_new(i64 ${ctor}, i32 ${args.argc}, i64* ${args.ptr})`);
212
+ return result;
213
+ }
214
+ }
215
+ if (!symbol) {
216
+ const ctor = CTOR_FUNCTIONS[identifier.text];
217
+ if (ctor) {
218
+ const args = this.emitArguments(node.arguments);
219
+ const result = this.reg();
220
+ this.emit(` ${result} = call i64 @${ctor}(i32 ${args.argc}, i64* ${args.ptr})`);
221
+ return result;
222
+ }
223
+ if (identifier.text === "Object") {
224
+ if (node.arguments.length > 0) return this.emitExpression(node.arguments[0]!);
225
+ return this.runtimeCall("xt_object_new", []);
226
+ }
227
+ }
228
+ }
229
+ const ctorValue = this.emitExpression(callee);
230
+ const args = this.emitArguments(node.arguments);
231
+ const result = this.reg();
232
+ this.emit(` ${result} = call i64 @xt_new(i64 ${ctorValue}, i32 ${args.argc}, i64* ${args.ptr})`);
233
+ return result;
234
+ },
235
+
159
236
  emitTemplate(node: TemplateLiteral): string {
160
237
  let accumulator = this.stringValue(node.head);
161
238
  for (const span of node.spans) {
@@ -10,7 +10,8 @@ import {
10
10
  type Expression,
11
11
  type Parameter,
12
12
  } from "../../ast/nodes.js";
13
- import type { FunctionInfo, SymbolInfo } from "../../binder/binder.js";
13
+ import type { ClassInfo, FunctionInfo, SymbolInfo } from "../../binder/binder.js";
14
+ import { SymbolKind } from "../../binder/binder.js";
14
15
  import { i64, XT_UNDEFINED } from "../values.js";
15
16
  import { RUNTIME_DECLARATIONS, type FunctionState } from "./state.js";
16
17
  import type { Generator } from "./generator.js";
@@ -20,11 +21,32 @@ export interface ModuleMethods {
20
21
  emitFunction(this: Generator, fn: FunctionInfo): void;
21
22
  emitMain(this: Generator): void;
22
23
  functionName(this: Generator, fn: FunctionInfo): string;
24
+ wrapAsync(this: Generator, value: string): string;
25
+ classGlobal(this: Generator, classInfo: ClassInfo): string;
26
+ emitClassReference(this: Generator, classInfo: ClassInfo): string;
27
+ emitClassSetup(this: Generator, classInfo: ClassInfo): void;
23
28
  emitDefaultParameter(this: Generator, symbol: SymbolInfo, value: string, initializer: Expression): void;
24
29
  }
25
30
 
26
31
  export const moduleMethods: ModuleMethods = {
27
32
  run(): string {
33
+ for (const classInfo of this.binding.classes) {
34
+ const globalName = `@class.${classInfo.id}`;
35
+ this.classGlobals.set(classInfo.id, globalName);
36
+ this.globals.push(`${globalName} = internal global i64 0`);
37
+ }
38
+ // Module-level bindings captured by functions live in globals so that a
39
+ // top-level function can see them without a closure environment.
40
+ const moduleScope = this.binding.scopes.get(this.sourceFile);
41
+ if (moduleScope) {
42
+ for (const symbol of moduleScope.symbols.values()) {
43
+ const isVariable = symbol.kind === SymbolKind.Var || symbol.kind === SymbolKind.Let || symbol.kind === SymbolKind.Const;
44
+ if (!isVariable || !symbol.boxed || !symbol.fn.isModule) continue;
45
+ const globalName = `@g.${symbol.id}`;
46
+ this.moduleGlobals.set(symbol.id, globalName);
47
+ this.globals.push(`${globalName} = internal global i64 ${i64(XT_UNDEFINED)}`);
48
+ }
49
+ }
28
50
  for (const fn of this.binding.functions) this.emitFunction(fn);
29
51
  this.emitMain();
30
52
  const header = ["; ModuleID = 'xbintsc'", "source_filename = \"" + this.sourceFile.fileName + "\"", ""];
@@ -50,9 +72,12 @@ export const moduleMethods: ModuleMethods = {
50
72
  };
51
73
  this.current = state;
52
74
 
53
- const header = `define i64 @${name}(i64 %env, i32 %argc, i64* %argv) {`;
75
+ const header = `define i64 @${name}(i64 %this, i64 %env, i32 %argc, i64* %argv) {`;
54
76
  this.emit(`%saved.env = alloca i64`);
55
77
  this.emit(`store i64 %env, i64* %saved.env`);
78
+ this.emit(`%saved.this = alloca i64`);
79
+ this.emit(`store i64 %this, i64* %saved.this`);
80
+ state.thisPtr = "%saved.this";
56
81
  this.emit(`%saved.argc = alloca i32`);
57
82
  this.emit(`store i32 %argc, i32* %saved.argc`);
58
83
  this.emit(`%saved.argv = alloca i64*`);
@@ -77,24 +102,46 @@ export const moduleMethods: ModuleMethods = {
77
102
 
78
103
  // Captures threaded through the environment.
79
104
  for (const symbol of fn.captures) {
105
+ if (this.moduleGlobals.has(symbol.id)) continue;
80
106
  const index = fn.captureIndex.get(symbol.id)!;
81
107
  const value = this.reg();
82
108
  this.emit(` ${value} = call i64 @xt_closure_env(i64 %env, i32 ${index})`);
83
109
  this.defineCaptureSlot(symbol, value);
84
110
  }
85
111
 
112
+ // Arrow functions inherit `this` lexically; it travels as an extra capture.
113
+ if (fn.isArrow && fn.capturesThis) {
114
+ const inherited = this.reg();
115
+ this.emit(` ${inherited} = call i64 @xt_closure_env(i64 %env, i32 ${fn.captures.length})`);
116
+ this.emit(` store i64 ${inherited}, i64* %saved.this`);
117
+ }
118
+
119
+ // Instance field initializers run before the constructor body.
120
+ if (fn.isConstructor && fn.classInfo) {
121
+ const thisValue = this.emitThis();
122
+ for (const field of fn.classInfo.fields) {
123
+ if (field.isStatic) continue;
124
+ const key = this.stringValue(field.name);
125
+ const value = field.initializer ? this.emitExpression(field.initializer) : i64(XT_UNDEFINED);
126
+ this.emit(` call i64 @xt_set(i64 ${thisValue}, i64 ${key}, i64 ${value})`);
127
+ }
128
+ }
129
+
86
130
  // Body.
87
131
  if (fn.node.kind === SyntaxKind.SourceFile) {
88
132
  this.emitStatements(this.sourceFile.statements);
89
133
  } else if (fn.node.kind === SyntaxKind.ArrowFunction && (fn.node as ArrowFunction).body.kind !== SyntaxKind.Block) {
90
134
  const value = this.emitExpression((fn.node as ArrowFunction).body as Expression);
91
- this.terminate(`ret i64 ${value}`);
135
+ this.terminate(`ret i64 ${fn.isAsync ? this.wrapAsync(value) : value}`);
92
136
  } else {
93
137
  const body = (fn.node as { body: Block }).body;
94
- this.emitStatements(body.statements);
138
+ if (body) this.emitStatements(body.statements);
95
139
  }
96
140
 
97
- if (!this.current.terminated) this.terminate(`ret i64 ${i64(XT_UNDEFINED)}`);
141
+ if (!this.current.terminated) {
142
+ const fallback = fn.isAsync ? this.wrapAsync(i64(XT_UNDEFINED)) : i64(XT_UNDEFINED);
143
+ this.terminate(`ret i64 ${fallback}`);
144
+ }
98
145
 
99
146
  const escapes = state.usesTry
100
147
  ? state.escapePointers.map((ptr) => ` call void asm sideeffect "", "r"(i64* ${ptr})`)
@@ -108,7 +155,8 @@ export const moduleMethods: ModuleMethods = {
108
155
  this.functions.push(
109
156
  [
110
157
  "define i32 @main(i32 %argc, i8** %argv) {",
111
- ` %result = call i64 @${moduleName}(i64 ${i64(XT_UNDEFINED)}, i32 0, i64* null)`,
158
+ ` %result = call i64 @${moduleName}(i64 ${i64(XT_UNDEFINED)}, i64 ${i64(XT_UNDEFINED)}, i32 0, i64* null)`,
159
+ " call void @xt_drain_microtasks()",
112
160
  " ret i32 0",
113
161
  "}",
114
162
  "",
@@ -120,6 +168,63 @@ export const moduleMethods: ModuleMethods = {
120
168
  return fn.isModule ? "xt_module" : `xt_fn_${fn.id}`;
121
169
  },
122
170
 
171
+ /** Wrap a returned value in a resolved promise (for `async` functions). */
172
+ wrapAsync(value: string): string {
173
+ const result = this.reg();
174
+ this.emit(` ${result} = call i64 @xt_promise_resolve(i64 ${value})`);
175
+ return result;
176
+ },
177
+
178
+ classGlobal(classInfo: ClassInfo): string {
179
+ const existing = this.classGlobals.get(classInfo.id);
180
+ if (existing) return existing;
181
+ const name = `@class.${classInfo.id}`;
182
+ this.classGlobals.set(classInfo.id, name);
183
+ this.globals.push(`${name} = internal global i64 0`);
184
+ return name;
185
+ },
186
+
187
+ emitClassReference(classInfo: ClassInfo): string {
188
+ const globalName = this.classGlobal(classInfo);
189
+ const value = this.reg();
190
+ this.emit(` ${value} = load i64, i64* ${globalName}`);
191
+ return value;
192
+ },
193
+
194
+ /** Build a class value (constructor closure + prototype) and store it in its global. */
195
+ emitClassSetup(classInfo: ClassInfo): void {
196
+ const globalName = this.classGlobal(classInfo);
197
+ const proto = this.reg();
198
+ this.emit(` ${proto} = call i64 @xt_object_new()`);
199
+ if (classInfo.parentExpression) {
200
+ const parent = this.emitExpression(classInfo.parentExpression);
201
+ const parentProto = this.reg();
202
+ this.emit(` ${parentProto} = call i64 @xt_function_get_prototype(i64 ${parent})`);
203
+ this.emit(` call i64 @xt_object_set_prototype(i64 ${proto}, i64 ${parentProto})`);
204
+ }
205
+ for (const method of classInfo.methods) {
206
+ const fnValue = this.emitClosureValue(method);
207
+ const key = this.stringValue(method.name);
208
+ this.emit(` call i64 @xt_set(i64 ${proto}, i64 ${key}, i64 ${fnValue})`);
209
+ }
210
+ const ctor = this.emitClosureValue(classInfo.ctor!);
211
+ this.emit(` call i64 @xt_function_set_prototype(i64 ${ctor}, i64 ${proto})`);
212
+ const ctorKey = this.stringValue("__ctor");
213
+ this.emit(` call i64 @xt_set(i64 ${proto}, i64 ${ctorKey}, i64 ${ctor})`);
214
+ for (const method of classInfo.statics) {
215
+ const fnValue = this.emitClosureValue(method);
216
+ const key = this.stringValue(method.name);
217
+ this.emit(` call i64 @xt_set(i64 ${ctor}, i64 ${key}, i64 ${fnValue})`);
218
+ }
219
+ for (const field of classInfo.fields) {
220
+ if (!field.isStatic) continue;
221
+ const key = this.stringValue(field.name);
222
+ const value = field.initializer ? this.emitExpression(field.initializer) : i64(XT_UNDEFINED);
223
+ this.emit(` call i64 @xt_set(i64 ${ctor}, i64 ${key}, i64 ${value})`);
224
+ }
225
+ this.emit(` store i64 ${ctor}, i64* ${globalName}`);
226
+ },
227
+
123
228
  emitDefaultParameter(symbol: SymbolInfo, value: string, initializer: Expression): void {
124
229
  const isUndefined = this.reg();
125
230
  this.emit(` ${isUndefined} = call i64 @xt_seq(i64 ${value}, i64 ${i64(XT_UNDEFINED)})`);
@@ -30,6 +30,8 @@ export interface FunctionState {
30
30
  readonly loops: LoopLabels[];
31
31
  readonly tryFrames: string[];
32
32
  readonly escapePointers: string[];
33
+ /** Alloca holding the `this` binding for the current function. */
34
+ thisPtr?: string;
33
35
  /** Set when the function contains a `try`, forcing locals to live in memory. */
34
36
  usesTry: boolean;
35
37
  }
@@ -55,6 +57,47 @@ export const RUNTIME_DECLARATIONS: readonly string[] = [
55
57
  "declare i64 @xt_closure_new(i8*, i32, i64*)",
56
58
  "declare i64 @xt_closure_call(i64, i32, i64*)",
57
59
  "declare i64 @xt_closure_env(i64, i32)",
60
+ "declare i64 @xt_call_with_this(i64, i64, i32, i64*)",
61
+ "declare i64 @xt_this()",
62
+ "declare i64 @xt_new(i64, i32, i64*)",
63
+ "declare i64 @xt_instance_of(i64, i64)",
64
+ "declare i64 @xt_function_set_prototype(i64, i64)",
65
+ "declare i64 @xt_function_get_prototype(i64)",
66
+ "declare i64 @xt_object_new_with_proto(i64)",
67
+ "declare i64 @xt_object_get_prototype(i64)",
68
+ "declare i64 @xt_object_set_prototype(i64, i64)",
69
+ "declare i64 @xt_await(i64)",
70
+ "declare void @xt_drain_microtasks()",
71
+ "declare i64 @xt_promise_resolve(i64)",
72
+ "declare i64 @xt_promise_reject(i64)",
73
+ "declare i64 @xt_promise_ctor(i32, i64*)",
74
+ "declare i64 @xt_promise_static(i64, i32, i64*)",
75
+ "declare i64 @xt_array_static(i64, i32, i64*)",
76
+ "declare i64 @xt_object_static(i64, i32, i64*)",
77
+ "declare i64 @xt_number_static(i64, i32, i64*)",
78
+ "declare i64 @xt_string_static(i64, i32, i64*)",
79
+ "declare i64 @xt_date_static(i64, i32, i64*)",
80
+ "declare i64 @xt_map_ctor(i32, i64*)",
81
+ "declare i64 @xt_set_ctor(i32, i64*)",
82
+ "declare i64 @xt_date_ctor(i32, i64*)",
83
+ "declare i64 @xt_regexp_ctor(i32, i64*)",
84
+ "declare i64 @xt_json_parse(i32, i64*)",
85
+ "declare i64 @xt_json_stringify(i32, i64*)",
86
+ "declare i64 @xt_object_from_entries(i64)",
87
+ "declare i64 @xt_object_freeze(i64)",
88
+ "declare i32 @xt_object_is_frozen(i64)",
89
+ "declare i64 @xt_object_has_own(i64, i64)",
90
+ "declare i64 @xt_console_dir(i32, i64*)",
91
+ "declare i64 @xt_console_trace(i32, i64*)",
92
+ "declare i64 @xt_console_assert(i32, i64*)",
93
+ "declare i64 @xt_console_count(i32, i64*)",
94
+ "declare i64 @xt_console_count_reset(i32, i64*)",
95
+ "declare i64 @xt_console_group(i32, i64*)",
96
+ "declare i64 @xt_console_group_end(i32, i64*)",
97
+ "declare i64 @xt_console_table(i32, i64*)",
98
+ "declare i64 @xt_console_time(i32, i64*)",
99
+ "declare i64 @xt_console_time_end(i32, i64*)",
100
+ "declare i64 @xt_console_time_log(i32, i64*)",
58
101
  "declare i32 @xt_truthy(i64)",
59
102
  "declare double @xt_to_number(i64)",
60
103
  "declare i64 @xt_to_string(i64)",
@@ -67,6 +67,11 @@ export const statementMethods: StatementMethods = {
67
67
  case SyntaxKind.InterfaceDeclaration:
68
68
  case SyntaxKind.TypeAliasDeclaration:
69
69
  return; // functions are emitted at module scope
70
+ case SyntaxKind.ClassDeclaration: {
71
+ const info = this.binding.classOfNode.get(statement);
72
+ if (info) this.emitClassSetup(info);
73
+ return;
74
+ }
70
75
  case SyntaxKind.IfStatement:
71
76
  this.emitIf(statement as IfStatement);
72
77
  return;
@@ -457,7 +462,8 @@ export const statementMethods: StatementMethods = {
457
462
  },
458
463
 
459
464
  emitReturn(statement: ReturnStatement): void {
460
- const value = statement.expression ? this.emitExpression(statement.expression) : i64(XT_UNDEFINED);
465
+ let value = statement.expression ? this.emitExpression(statement.expression) : i64(XT_UNDEFINED);
466
+ if (this.current.fn.isAsync) value = this.wrapAsync(value);
461
467
  this.popTryFramesTo(0);
462
468
  this.terminate(`ret i64 ${value}`);
463
469
  },
@@ -31,6 +31,7 @@ export const BINARY_RUNTIME: Record<string, string | undefined> = {
31
31
  [BinaryOperator.GreaterThanGreaterThan]: "xt_shr",
32
32
  [BinaryOperator.GreaterThanGreaterThanGreaterThan]: "xt_ushr",
33
33
  [BinaryOperator.In]: "xt_in",
34
+ [BinaryOperator.InstanceOf]: "xt_instance_of",
34
35
  };
35
36
 
36
37
  export const CONSOLE_METHODS: Record<string, string> = {
@@ -38,11 +39,23 @@ export const CONSOLE_METHODS: Record<string, string> = {
38
39
  info: "xt_console_info",
39
40
  warn: "xt_console_warn",
40
41
  error: "xt_console_error",
42
+ dir: "xt_console_dir",
43
+ trace: "xt_console_trace",
44
+ assert: "xt_console_assert",
45
+ count: "xt_console_count",
46
+ countReset: "xt_console_count_reset",
47
+ group: "xt_console_group",
48
+ groupEnd: "xt_console_group_end",
49
+ table: "xt_console_table",
50
+ time: "xt_console_time",
51
+ timeEnd: "xt_console_time_end",
52
+ timeLog: "xt_console_time_log",
41
53
  };
42
54
 
43
55
  export const MATH_FUNCTIONS = new Set<string>([
44
56
  "abs", "floor", "ceil", "round", "trunc", "sqrt", "cbrt", "pow", "exp", "log", "log2", "log10",
45
57
  "sin", "cos", "tan", "asin", "acos", "atan", "atan2", "hypot", "sign", "random", "min", "max",
58
+ "log1p", "expm1", "sinh", "cosh", "tanh", "asinh", "acosh", "atanh", "fround", "imul", "clz32",
46
59
  ]);
47
60
 
48
61
  export const MATH_CONSTANTS: Record<string, number> = {
@@ -66,6 +79,28 @@ export const GLOBAL_FUNCTIONS: Record<string, string> = {
66
79
  Boolean: "xt_boolean_ctor",
67
80
  };
68
81
 
82
+ /** Global namespaces whose static methods map to runtime dispatchers. */
83
+ export const NAMESPACE_STATICS: Record<string, string> = {
84
+ Math: "xt_math_call",
85
+ JSON: "xt_json",
86
+ Array: "xt_array_static",
87
+ Object: "xt_object_static",
88
+ Number: "xt_number_static",
89
+ String: "xt_string_static",
90
+ Date: "xt_date_static",
91
+ Promise: "xt_promise_static",
92
+ };
93
+
94
+ /** Global constructors called as `new X(...)` (all have signature `(i32, i64*)`). */
95
+ export const CTOR_FUNCTIONS: Record<string, string> = {
96
+ Array: "xt_array_new",
97
+ Map: "xt_map_ctor",
98
+ Set: "xt_set_ctor",
99
+ Date: "xt_date_ctor",
100
+ RegExp: "xt_regexp_ctor",
101
+ Promise: "xt_promise_ctor",
102
+ };
103
+
69
104
  export const BUILTIN_METHODS = new Set<string>([
70
105
  "push", "pop", "shift", "unshift", "join", "slice", "indexOf", "includes", "map", "forEach", "filter",
71
106
  "reduce", "concat", "reverse", "charAt", "charCodeAt", "substring", "substr", "split", "toUpperCase",
@@ -13,6 +13,8 @@ import { DiagnosticBag, type Diagnostic } from "../diagnostics/diagnostic.js";
13
13
  import { SourceFile } from "../diagnostics/source.js";
14
14
  import { Parser } from "../parser/parser.js";
15
15
  import { generate } from "../codegen/llvm.js";
16
+ import { SyntaxKind } from "../ast/nodes.js";
17
+ import { bundleModules } from "./modules.js";
16
18
  import { createDefaultRegistry, type ExtensionRegistry } from "../extensions/registry.js";
17
19
  import { BuildCache, hashParts } from "./cache.js";
18
20
  import { findRuntimeDir } from "./paths.js";
@@ -94,9 +96,31 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
94
96
  ? resolve(options.output ?? join(outDir, baseName + ".o"))
95
97
  : executableName(absoluteEntry, outDir, options.output);
96
98
 
99
+ // -- front end -----------------------------------------------------------
100
+ const file = new SourceFile(absoluteEntry, sourceText);
101
+ const diagnostics = new DiagnosticBag();
102
+ const parser = new Parser(file, diagnostics);
103
+ let sourceFile = parser.parseSourceFile();
104
+
105
+ // Lower `import`/`export` by merging every reachable module into one file.
106
+ const isModule = sourceFile.statements.some(
107
+ (statement) =>
108
+ statement.kind === SyntaxKind.ImportDeclaration ||
109
+ statement.kind === SyntaxKind.ExportDeclaration ||
110
+ statement.kind === SyntaxKind.ExportAssignment,
111
+ );
112
+ let cacheText = sourceText;
113
+ if (isModule) {
114
+ const bundled = bundleModules(absoluteEntry, diagnostics);
115
+ if (bundled) {
116
+ sourceFile = bundled.sourceFile;
117
+ cacheText = bundled.text;
118
+ }
119
+ }
120
+
97
121
  const cacheKey = hashParts([
98
122
  COMPILER_VERSION,
99
- sourceText,
123
+ cacheText,
100
124
  emit,
101
125
  optimize,
102
126
  process.platform,
@@ -105,15 +129,10 @@ export function build(entryPath: string, options: BuildOptions = {}): BuildResul
105
129
 
106
130
  const cache = new BuildCache(cacheDir);
107
131
  const outputs = [outputPath];
108
- if (!options.force && cache.isFresh(cacheKey, outputs)) {
132
+ if (!options.force && !diagnostics.hasErrors && cache.isFresh(cacheKey, outputs)) {
109
133
  return { outputPath, cached: true, diagnostics: [] };
110
134
  }
111
135
 
112
- // -- front end -----------------------------------------------------------
113
- const file = new SourceFile(absoluteEntry, sourceText);
114
- const diagnostics = new DiagnosticBag();
115
- const parser = new Parser(file, diagnostics);
116
- const sourceFile = parser.parseSourceFile();
117
136
  const { ir } = generate(sourceFile, diagnostics, { builtins: registry.builtins() });
118
137
 
119
138
  if (options.verbose) {
@@ -174,6 +193,8 @@ const RUNTIME_SOURCES = [
174
193
  "xt_values.c",
175
194
  "xt_containers.c",
176
195
  "xt_stdlib.c",
196
+ "xt_stdlib2.c",
197
+ "xt_promise.c",
177
198
  "xt_builtins.c",
178
199
  "xt_io.c",
179
200
  ] as const;