diff --git a/src/codegen/infrastructure/function-generator.ts b/src/codegen/infrastructure/function-generator.ts index 81a40b07..fa4e53e9 100644 --- a/src/codegen/infrastructure/function-generator.ts +++ b/src/codegen/infrastructure/function-generator.ts @@ -105,6 +105,7 @@ export interface FunctionGeneratorContext { getTargetOS(): string; setRawInterfaceType(name: string, type: string): void; getUsesMathRandom(): boolean; + getUsesGC(): boolean; } export class FunctionGenerator { @@ -1026,7 +1027,9 @@ export class FunctionGenerator { " {\n"; ir += "entry:\n"; - ir += " call void @GC_init()\n"; + if (this.ctx.getUsesGC()) { + ir += " call void @GC_init()\n"; + } if (this.ctx.getUsesMathRandom()) { ir += " %__seed_time = call i64 @time(i8* null)\n"; ir += " call void @srand48(i64 %__seed_time)\n"; diff --git a/src/codegen/infrastructure/llvm-declarations.ts b/src/codegen/infrastructure/llvm-declarations.ts index 3f05b279..7c2e779e 100644 --- a/src/codegen/infrastructure/llvm-declarations.ts +++ b/src/codegen/infrastructure/llvm-declarations.ts @@ -1,4 +1,5 @@ export interface DeclConfig { + gc?: boolean; curl?: boolean; crypto?: boolean; sqlite?: boolean; @@ -25,14 +26,16 @@ export function getLLVMDeclarations(config?: DeclConfig): string { ir += "declare i8* @calloc(i64, i64)\n"; ir += "declare void @free(i8*)\n"; - ir += "; Boehm GC - automatic garbage collection\n"; - ir += "declare void @GC_init()\n"; - ir += "declare noalias i8* @GC_malloc(i64)\n"; - ir += "declare noalias i8* @GC_malloc_atomic(i64)\n"; - ir += "declare noalias i8* @GC_malloc_uncollectable(i64)\n"; - ir += "declare i8* @GC_realloc(i8*, i64)\n"; - ir += "declare void @GC_disable()\n"; - ir += "declare void @GC_enable()\n"; + if (config?.gc) { + ir += "; Boehm GC - automatic garbage collection\n"; + ir += "declare void @GC_init()\n"; + ir += "declare noalias i8* @GC_malloc(i64)\n"; + ir += "declare noalias i8* @GC_malloc_atomic(i64)\n"; + ir += "declare noalias i8* @GC_malloc_uncollectable(i64)\n"; + ir += "declare i8* @GC_realloc(i8*, i64)\n"; + ir += "declare void @GC_disable()\n"; + ir += "declare void @GC_enable()\n"; + } ir += "declare i8* @strcpy(i8*, i8*)\n"; ir += "declare i8* @strncpy(i8*, i8*, i64)\n"; ir += "declare i8* @strcat(i8*, i8*)\n"; diff --git a/src/codegen/llvm-generator.ts b/src/codegen/llvm-generator.ts index 276bfb0e..70a5a9d6 100644 --- a/src/codegen/llvm-generator.ts +++ b/src/codegen/llvm-generator.ts @@ -296,6 +296,13 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { public usesGC: number = 0; public usesMathRandom: number = 0; + public usesOs: number = 0; + public usesDoubleToString: number = 0; + public usesPath: number = 0; + public usesFs: number = 0; + public usesBase64Bridge: number = 0; + public usesUrlBridge: number = 0; + public usesUriBridge: number = 0; private dbgAlloc(): number { const id = this.dbgNextId; @@ -324,18 +331,20 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { } private dbgInit(sourceFilePath: string): void { - let lastSlash = -1; - for (let i = 0; i < sourceFilePath.length; i++) { - if (sourceFilePath.charAt(i) === "/") { - lastSlash = i; + let lastSlash: number = 0; + let foundSlash = false; + let pos: number = 0; + let ch = sourceFilePath.charAt(pos); + while (ch !== "") { + if (ch === "/") { + lastSlash = pos; + foundSlash = true; } + pos = pos + 1; + ch = sourceFilePath.charAt(pos); } - let filename = sourceFilePath; - let directory = "."; - if (lastSlash >= 0) { - filename = sourceFilePath.substring(lastSlash + 1); - directory = sourceFilePath.substring(0, lastSlash); - } + const filename = foundSlash ? sourceFilePath.slice(lastSlash + 1) : sourceFilePath; + const directory = foundSlash ? sourceFilePath.slice(0, lastSlash) : "."; this.dbgFileId = this.dbgAlloc(); this.dbgSetNode( @@ -691,7 +700,8 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { name: string, ): { keys: string[]; types: string[]; tsTypes: string[] } | null { if (!this.ast || !this.ast.interfaces) return null; - const baseName = name.endsWith("?") ? name.slice(0, -1) : name; + const qIdx = name.indexOf("?"); + const baseName = qIdx >= 0 ? name.slice(0, qIdx) : name; const cleanName = baseName.indexOf(" | ") !== -1 ? baseName.split(" | ")[0] : baseName; const keys: string[] = []; const types: string[] = []; @@ -706,7 +716,7 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { if (!field) continue; let fieldName = field.name; if (fieldName.endsWith("?")) { - fieldName = fieldName.slice(0, -1); + fieldName = fieldName.slice(0, fieldName.indexOf("?")); } keys.push(fieldName); // field.type is a TS type (e.g. "string", "number") — convert to LLVM for types[] @@ -811,7 +821,7 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { if (!f) continue; let fName = f.name; if (fName.endsWith("?")) { - fName = fName.slice(0, -1); + fName = fName.slice(0, fName.indexOf("?")); } if (fName === fieldName) { return f.type; @@ -1010,7 +1020,6 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { public setUsesCurl(value: boolean): void { this.usesCurl = value ? 1 : 0; } - public setUsesOs(_value: boolean): void {} public getUsesCurl(): boolean { return this.usesCurl !== 0; } @@ -1101,6 +1110,21 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { public setUsesMathRandom(value: boolean): void { this.usesMathRandom = value ? 1 : 0; } + public setUsesOs(value: boolean): void { + this.usesOs = value ? 1 : 0; + } + public getUsesOs(): boolean { + return this.usesOs !== 0; + } + public getUsesBase64Bridge(): boolean { + return this.usesBase64Bridge !== 0; + } + public getUsesUrlBridge(): boolean { + return this.usesUrlBridge !== 0; + } + public getUsesUriBridge(): boolean { + return this.usesUriBridge !== 0; + } public setCurrentDeclaredInterfaceType(type: string | undefined): void { this.currentDeclaredInterfaceType = type; } @@ -1498,6 +1522,13 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { this.usesAsyncFs = 0; this.usesGC = 0; this.usesMathRandom = 0; + this.usesOs = 0; + this.usesDoubleToString = 0; + this.usesPath = 0; + this.usesFs = 0; + this.usesBase64Bridge = 0; + this.usesUrlBridge = 0; + this.usesUriBridge = 0; this.ast = ast; @@ -1662,7 +1693,7 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { ) continue; - let semaIdx = -1; + let semaIdx: number = -1; for (let si = 0; si < this.semaSymbolCount; si++) { if (this.semaSymbolNames[si] === name) { semaIdx = si; @@ -1769,6 +1800,43 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { this.stringBuilderScap.clear(); } + emit(instruction: string): void { + if (!this.usesGC && instruction.includes("@GC_")) { + this.usesGC = 1; + } + if (!this.usesDoubleToString && instruction.includes("@__double_to_string(")) { + this.usesDoubleToString = 1; + } + if (!this.usesPath && instruction.includes("@__path_")) { + this.usesPath = 1; + } + if (!this.usesFs && instruction.includes("@__fs_")) { + this.usesFs = 1; + } + if ( + !this.usesBase64Bridge && + (instruction.includes("@cs_btoa(") || + instruction.includes("@cs_atob(") || + instruction.includes("@cs_base64_decode(")) + ) { + this.usesBase64Bridge = 1; + } + if ( + !this.usesUrlBridge && + (instruction.includes("@cs_url_") || instruction.includes("@cs_urlsearch_")) + ) { + this.usesUrlBridge = 1; + } + if ( + !this.usesUriBridge && + (instruction.includes("@cs_encode_uri_component(") || + instruction.includes("@cs_decode_uri_component(")) + ) { + this.usesUriBridge = 1; + } + super.emit(instruction); + } + getThisPointer(): string | null { return this.thisPointer; } @@ -2505,21 +2573,11 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { if (safeStr) { irParts.push(safeStr); } - const dblToStr = getDoubleToStringHelper(); - if (dblToStr) { - irParts.push(dblToStr); - } const strHash = getStringHashHelper(); if (strHash) { irParts.push(strHash); } - irParts.push(this.fsGen.generateReaddirSyncHelper()); - irParts.push(this.fsGen.generateStatSyncHelper()); - irParts.push(this.pathGen.generateNormalizeHelper()); - irParts.push(this.pathGen.generateRelativeHelper()); - irParts.push(this.pathGen.generateParseHelper()); - const globalVars = getGlobalVariables(); if (globalVars) { irParts.push(globalVars); @@ -2551,6 +2609,23 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { } } + if (!this.usesGC) { + for (let i = 0; i < irParts.length; i++) { + if (irParts[i].includes("@GC_")) { + this.usesGC = 1; + break; + } + } + } + if (!this.usesGC) { + for (let i = 0; i < this.globalStrings.length; i++) { + if (this.globalStrings[i].includes("@GC_")) { + this.usesGC = 1; + break; + } + } + } + const mainIr = this.generateMain(); const liftedFunctions = this.exprGen.arrowFunctionGen.getLiftedFunctions(); @@ -2714,6 +2789,60 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { irParts.push(this.embedGen.generateLengthLookupFunction()); } + for (let psi = 0; psi < irParts.length; psi++) { + const part = irParts[psi]; + if (!this.usesGC && part.includes("@GC_")) { + this.usesGC = 1; + } + if (!this.usesDoubleToString && part.includes("@__double_to_string(")) { + this.usesDoubleToString = 1; + } + if (!this.usesPath && part.includes("@__path_")) { + this.usesPath = 1; + } + if (!this.usesFs && part.includes("@__fs_")) { + this.usesFs = 1; + } + if ( + !this.usesBase64Bridge && + (part.includes("@cs_btoa(") || + part.includes("@cs_atob(") || + part.includes("@cs_base64_decode(")) + ) { + this.usesBase64Bridge = 1; + } + if (!this.usesUrlBridge && (part.includes("@cs_url_") || part.includes("@cs_urlsearch_"))) { + this.usesUrlBridge = 1; + } + if ( + !this.usesUriBridge && + (part.includes("@cs_encode_uri_component(") || part.includes("@cs_decode_uri_component(")) + ) { + this.usesUriBridge = 1; + } + } + for (let gsi = 0; gsi < this.globalStrings.length; gsi++) { + if (!this.usesGC && this.globalStrings[gsi].includes("@GC_")) { + this.usesGC = 1; + } + } + + if (this.usesDoubleToString) { + this.usesGC = 1; + irParts.push(getDoubleToStringHelper()); + } + if (this.usesFs) { + this.usesGC = 1; + irParts.push(this.fsGen.generateReaddirSyncHelper()); + irParts.push(this.fsGen.generateStatSyncHelper()); + } + if (this.usesPath) { + this.usesGC = 1; + irParts.push(this.pathGen.generateNormalizeHelper()); + irParts.push(this.pathGen.generateRelativeHelper()); + irParts.push(this.pathGen.generateParseHelper()); + } + const needsLibuv = this.usesTimers || this.usesPromises || @@ -2732,6 +2861,8 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { finalParts.push("\n"); } + finalParts.push("%PathParseResult = type { i8*, i8*, i8*, i8*, i8* }\n\n"); + finalParts.push("; Tree-sitter type definitions\n"); finalParts.push("%TSParser = type opaque\n"); finalParts.push("%TSTree = type opaque\n"); @@ -2762,9 +2893,14 @@ export class LLVMGenerator extends BaseGenerator implements IGeneratorContext { finalParts.push("\n"); } + if (this.usesStringBuilder || this.usesCurl) { + this.usesGC = 1; + } + finalParts.push( this.filterDuplicateDeclarations( getLLVMDeclarations({ + gc: this.usesGC !== 0, curl: this.usesCurl !== 0, crypto: this.usesCrypto !== 0, sqlite: this.usesSqlite !== 0, diff --git a/src/codegen/stdlib/json.ts b/src/codegen/stdlib/json.ts index d9ed2b92..1222ce6a 100644 --- a/src/codegen/stdlib/json.ts +++ b/src/codegen/stdlib/json.ts @@ -262,6 +262,7 @@ export class JsonGenerator { return; } + this.ctx.setUsesGC(true); const fieldCount = this.ctx.interfaceStructGenGetFieldCount(typeName); for (let fi = 0; fi < fieldCount; fi++) { diff --git a/src/codegen/stdlib/path.ts b/src/codegen/stdlib/path.ts index 7042fcc3..b28459b5 100644 --- a/src/codegen/stdlib/path.ts +++ b/src/codegen/stdlib/path.ts @@ -671,9 +671,9 @@ export class PathGenerator { } // %PathParseResult = { root, dir, base, name, ext } — all i8* + // Type definition emitted separately in llvm-generator.ts finalParts so it precedes all GEPs generateParseHelper(): string { let ir = ""; - ir += "%PathParseResult = type { i8*, i8*, i8*, i8*, i8* }\n\n"; ir += "define i8* @__path_parse(i8* %path) {\n"; ir += "entry:\n"; ir += " %len = call i64 @strlen(i8* %path)\n"; diff --git a/src/compiler.ts b/src/compiler.ts index a54ea357..016304c5 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -345,7 +345,14 @@ export function compile( const treeSitterPath = sdk ? sdk.vendorPath : TREESITTER_LIB_PATH; const platformLibs = targetIsMac ? "" : " -lm -ldl -lrt -lpthread"; - let linkLibs = `-L${gcPath} -lgc` + platformLibs; + const needsGcLib = + generator.usesGC || + generator.usesRegex || + generator.usesBase64Bridge || + generator.usesUrlBridge || + generator.usesUriBridge || + generator.usesStringBuilder; + let linkLibs = needsGcLib ? `-L${gcPath} -lgc` + platformLibs : platformLibs.trimStart(); if (generator.usesJson) { linkLibs += ` -L${yyjsonPath} -lyyjson`; } @@ -404,9 +411,9 @@ export function compile( const cpBridgeObj = generator.usesChildProcess ? `${bridgePath}/child-process-bridge.o` : ""; const osBridgeObj = `${bridgePath}/os-bridge.o`; const timeBridgeObj = `${bridgePath}/time-bridge.o`; - const base64BridgeObj = `${bridgePath}/base64-bridge.o`; - const urlBridgeObj = `${bridgePath}/url-bridge.o`; - const uriBridgeObj = `${bridgePath}/uri-bridge.o`; + const base64BridgeObj = generator.usesBase64Bridge ? `${bridgePath}/base64-bridge.o` : ""; + const urlBridgeObj = generator.usesUrlBridge ? `${bridgePath}/url-bridge.o` : ""; + const uriBridgeObj = generator.usesUriBridge ? `${bridgePath}/uri-bridge.o` : ""; const dotenvBridgeObj = fs.existsSync(`${bridgePath}/dotenv-bridge.o`) ? `${bridgePath}/dotenv-bridge.o` : ""; diff --git a/src/native-compiler-lib.ts b/src/native-compiler-lib.ts index d2a1ed2d..9996d9d1 100644 --- a/src/native-compiler-lib.ts +++ b/src/native-compiler-lib.ts @@ -463,7 +463,16 @@ export function compileNative(inputFile: string, outputFile: string): void { : "./vendor/tree-sitter/libtree-sitter.a"; } } - let linkLibs = "-L" + effectiveGcPath + " -lgc" + platformLibs; + const needsGcLib = + generator.getUsesGC() || + generator.getUsesRegex() || + generator.getUsesBase64Bridge() || + generator.getUsesUrlBridge() || + generator.getUsesUriBridge() || + generator.usesStringBuilder !== 0; + let linkLibs = needsGcLib + ? "-L" + effectiveGcPath + " -lgc" + platformLibs + : platformLibs.trimStart(); if (tsLibPath) { linkLibs = linkLibs + " " + tsLibPath; } @@ -507,9 +516,11 @@ export function compileNative(inputFile: string, outputFile: string): void { : ""; const osBridgeObj = effectiveBridgePath + "/os-bridge.o"; const timeBridgeObj = effectiveBridgePath + "/time-bridge.o"; - const base64BridgeObj = effectiveBridgePath + "/base64-bridge.o"; - const urlBridgeObj = effectiveBridgePath + "/url-bridge.o"; - const uriBridgeObj = effectiveBridgePath + "/uri-bridge.o"; + const base64BridgeObj = generator.getUsesBase64Bridge() + ? effectiveBridgePath + "/base64-bridge.o" + : ""; + const urlBridgeObj = generator.getUsesUrlBridge() ? effectiveBridgePath + "/url-bridge.o" : ""; + const uriBridgeObj = generator.getUsesUriBridge() ? effectiveBridgePath + "/uri-bridge.o" : ""; const dotenvBridgePath = effectiveBridgePath + "/dotenv-bridge.o"; const dotenvBridgeObj = fs.existsSync(dotenvBridgePath) ? dotenvBridgePath : ""; const watchBridgeObj = effectiveBridgePath + "/watch-bridge.o"; diff --git a/src/parser-native/transformer.ts b/src/parser-native/transformer.ts index 93e72a0e..e661fc15 100644 --- a/src/parser-native/transformer.ts +++ b/src/parser-native/transformer.ts @@ -1392,15 +1392,9 @@ function transformAwaitExpression(node: TreeSitterNode): Expression { function transformRegexNode(node: TreeSitterNode): RegexNode { const text = (node as NodeBase).text; - let lastSlash = -1; - for (let i = text.length - 1; i >= 0; i--) { - if (text.charAt(i) === "/") { - lastSlash = i; - break; - } - } - const pattern = text.slice(1, lastSlash); - const flags = text.slice(lastSlash + 1); + const lastSlash = text.lastIndexOf("/"); + const pattern = text.substring(1, lastSlash); + const flags = text.substring(lastSlash + 1, text.length); return { type: "regex", pattern, flags }; } @@ -2645,7 +2639,7 @@ function transformClassDeclaration(node: TreeSitterNode): ClassNode | null { const params = method.params; for (let pi = 0; pi < method.parameterProperties.length; pi++) { const propName = method.parameterProperties[pi]; - let propIdx = -1; + let propIdx: number = -1; for (let k = 0; k < params.length; k++) { if (params[k] === propName) { propIdx = k;