Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ Pattern:
2. Add build step in `scripts/build-vendor.sh` (compile to `.o`)
3. Declare extern functions in LLVM IR and call them from codegen
4. Add conditional linking in `src/compiler.ts` and `src/native-compiler-lib.ts`
5. Add to `scripts/build-target-sdk.sh` bridge list (cross-compile SDK packaging)
6. Add to `ci.yml` in all 4 places: Linux verify loop, Linux release copy, macOS verify loop, macOS release copy

Existing bridges: `regex-bridge.c`, `yyjson-bridge.c`, `os-bridge.c`, `child-process-bridge.c`,
`child-process-spawn.c`, `lws-bridge.c`, `treesitter-bridge.c`.
Expand Down
28 changes: 28 additions & 0 deletions src/codegen/infrastructure/generator-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,12 @@ export interface IGeneratorContext {
lastTypeAssertionSourceVar: string | null;
getLastTypeAssertionSourceVar(): string | null;
setLastTypeAssertionSourceVar(name: string | null): void;

setStackEligibleVars(vars: string[]): void;
isStackEligibleKey(key: string): boolean;
currentVarDeclKey: string | null;
setCurrentVarDeclKey(key: string | null): void;
getCurrentVarDeclKey(): string | null;
}

/**
Expand Down Expand Up @@ -1002,6 +1008,9 @@ export class MockGeneratorContext implements IGeneratorContext {
public lastInlineLambdaEnvPtr: string | null = null;
public lastTypeAssertionSourceVar: string | null = null;

private stackEligibleVars: string[] = [];
public currentVarDeclKey: string | null = null;

constructor() {
this.typeContext = new TypeContext();
this.symbolTable = new SymbolTable(this.typeContext);
Expand Down Expand Up @@ -2190,4 +2199,23 @@ export class MockGeneratorContext implements IGeneratorContext {
if (!ta || !ta.unionMembers) return null;
return ta.unionMembers;
}

setStackEligibleVars(vars: string[]): void {
this.stackEligibleVars = vars;
}

isStackEligibleKey(key: string): boolean {
for (let i = 0; i < this.stackEligibleVars.length; i++) {
if (this.stackEligibleVars[i] === key) return true;
}
return false;
}

setCurrentVarDeclKey(key: string | null): void {
this.currentVarDeclKey = key;
}

getCurrentVarDeclKey(): string | null {
return this.currentVarDeclKey;
}
}
8 changes: 8 additions & 0 deletions src/codegen/infrastructure/variable-allocator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@ export interface VariableAllocatorContext {
getWantsBinaryReturn(): boolean;
getLastTypeAssertionSourceVar(): string | null;
setLastTypeAssertionSourceVar(name: string | null): void;
setCurrentVarDeclKey(key: string | null): void;
isStackEligibleKey(key: string): boolean;
}

export class VariableAllocator {
Expand Down Expand Up @@ -877,6 +879,10 @@ export class VariableAllocator {
arrayMethodReturnType,
);

const declLine = stmt.loc ? stmt.loc.line : (stmt.line ?? -1);
const declCol = stmt.loc ? stmt.loc.column : -1;
const declKey = stmt.name + ":" + declLine + ":" + declCol;
this.ctx.setCurrentVarDeclKey(declKey);
switch (classification.kind) {
case VarKind.DeclaredInterface:
this.allocateDeclaredInterface(stmt, params, classification.declaredInterfaceType!);
Expand Down Expand Up @@ -976,6 +982,8 @@ export class VariableAllocator {
break;
}

this.ctx.setCurrentVarDeclKey(null);

if (resolved && !stmt.declaredType && !isNull) {
this.ctx.symbolTable.setResolvedType(stmt.name, resolved);
}
Expand Down
27 changes: 27 additions & 0 deletions src/codegen/llvm-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ import type { TargetInfo } from "../target-types.js";
import { checkClosureMutations } from "../semantic/closure-mutation-checker.js";
import { checkUnionTypes } from "../semantic/union-type-checker.js";
import { checkTypeAssertions } from "../semantic/type-assertion-checker.js";
import { analyzeEscapes } from "../semantic/escape-analysis.js";

export interface SemaSymbolData {
names: string[];
Expand Down Expand Up @@ -270,6 +271,10 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext {
private semaSymbolSchemaTypes: (string[] | undefined)[];
private semaSymbolCount: number;

// Escape analysis result — string keys "name:line:col" for stack-eligible var decls
private stackEligibleVars: string[] = [];
public currentVarDeclKey: string | null = null;

// Debug info emitter (null when debug info is disabled)
private debugInfoEmitter: number = 0;
private currentSubprogramId: number = -1;
Expand Down Expand Up @@ -1109,6 +1114,21 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext {
public setLastTypeAssertionSourceVar(name: string | null): void {
this.lastTypeAssertionSourceVar = name;
}
public setStackEligibleVars(vars: string[]): void {
this.stackEligibleVars = vars;
}
public isStackEligibleKey(key: string): boolean {
for (let i = 0; i < this.stackEligibleVars.length; i++) {
if (this.stackEligibleVars[i] === key) return true;
}
return false;
}
public setCurrentVarDeclKey(key: string | null): void {
this.currentVarDeclKey = key;
}
public getCurrentVarDeclKey(): string | null {
return this.currentVarDeclKey;
}
public setIsAsyncFunction(value: boolean): void {
this.isAsyncFunction = value;
}
Expand Down Expand Up @@ -1898,6 +1918,7 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext {
let isClassInstance = false;
let isUint8Array = false;
let isBoolean = false;
let isNumber = false;
if (resolved) {
const base = resolved.base;
const depth = resolved.arrayDepth;
Expand All @@ -1910,6 +1931,7 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext {
isRegex = base === "RegExp";
isObject = base === "object" && depth === 0;
isBoolean = base === "boolean" && depth === 0;
isNumber = base === "number" && depth === 0;
isUint8Array = base === "Uint8Array" && depth === 0;
isClassInstance =
!isRegex &&
Expand Down Expand Up @@ -2146,6 +2168,10 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext {
llvmType = "double";
kind = SymbolKind.Boolean;
defaultValue = "0.0";
} else if (isNumber) {
llvmType = "double";
kind = SymbolKind.Number;
defaultValue = "0.0";
} else if (isJSONParse) {
const interfaceName = this.typeInference.getJSONParseInterface(
stmt.value as MethodCallNode,
Expand Down Expand Up @@ -2446,6 +2472,7 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext {
checkClosureMutations(this.ast);
checkUnionTypes(this.ast);
checkTypeAssertions(this.ast);
this.stackEligibleVars = analyzeEscapes(this.ast);

const irParts: string[] = [];

Expand Down
38 changes: 20 additions & 18 deletions src/codegen/types/objects/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ export class ObjectGenerator {
this.ctx.emit(instruction);
}

private allocateStruct(structType: string, structSizeBytes: number): string {
const key = this.ctx.getCurrentVarDeclKey();
if (key && this.ctx.isStackEligibleKey(key)) {
const allocaReg = this.ctx.nextAllocaReg("_obj");
this.emit(`${allocaReg} = alloca ${structType}`);
return allocaReg;
}
const objMem = this.nextTemp();
this.emit(`${objMem} = call i8* @GC_malloc(i64 ${structSizeBytes})`);
const objPtr = this.nextTemp();
this.emit(`${objPtr} = bitcast i8* ${objMem} to ${structType}*`);
return objPtr;
}

generateObjectLiteral(expr: Expression, params: string[]): string {
if (expr.type !== "object") {
throw new Error("Expected object literal");
Expand Down Expand Up @@ -134,13 +148,9 @@ export class ObjectGenerator {
}
const structFields = llvmTypes.join(", ");
const structSizeBytes = orderedFields.length * 8;

const objMem = this.nextTemp();
this.emit(`${objMem} = call i8* @GC_malloc(i64 ${structSizeBytes})`);

const structType = `{ ${structFields} }`;
const objPtr = this.nextTemp();
this.emit(`${objPtr} = bitcast i8* ${objMem} to ${structType}*`);

const objPtr = this.allocateStruct(structType, structSizeBytes);

for (let i = 0; i < orderedFields.length; i++) {
const field = orderedFields[i] as { key: string; llvmType: string; value: string };
Expand Down Expand Up @@ -258,13 +268,9 @@ export class ObjectGenerator {
}

const structType = `%${interfaceName}`;
const structSize = this.ctx.interfaceStructGen?.getStructSize(interfaceName);

const objMem = this.nextTemp();
this.emit(`${objMem} = call i8* @GC_malloc(i64 ${structSize})`);
const structSize = this.ctx.interfaceStructGen?.getStructSize(interfaceName) ?? 0;

const objPtr = this.nextTemp();
this.emit(`${objPtr} = bitcast i8* ${objMem} to ${structType}*`);
const objPtr = this.allocateStruct(structType, structSize);

for (let i = 0; i < orderedFields.length; i++) {
const field = orderedFields[i] as { key: string; llvmType: string; value: string };
Expand Down Expand Up @@ -356,13 +362,9 @@ export class ObjectGenerator {
}
const structFields = llvmTypes.join(", ");
const structSizeBytes = fieldTypes.length * 8;

const objMem = this.nextTemp();
this.emit(`${objMem} = call i8* @GC_malloc(i64 ${structSizeBytes})`);

const structType = `{ ${structFields} }`;
const objPtr = this.nextTemp();
this.emit(`${objPtr} = bitcast i8* ${objMem} to ${structType}*`);

const objPtr = this.allocateStruct(structType, structSizeBytes);

for (let i = 0; i < fieldTypes.length; i++) {
const field = fieldTypes[i] as { key: string; llvmType: string; value: string };
Expand Down
Loading
Loading