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
3 changes: 3 additions & 0 deletions src/ast/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,9 @@ export interface FunctionNode {
// When true, codegen emits LLVM `declare` instead of `define`, no _cs_ prefix
declare?: boolean;
typeParameters?: string[];
// Marked true by int-specialization pass when params and return are
// all integer-valued. Triggers i64 ABI codegen instead of double.
intSpecialized?: boolean;
}

export interface ClassMethod {
Expand Down
9 changes: 6 additions & 3 deletions src/codegen/expressions/arrow-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ export class ArrowFunctionExpressionGenerator extends BaseGenerator {
}

// All FunctionNode fields must be present so the native compiler allocates
// the full struct size — closureInfo is the 11th field (after declare).
// the full struct size — closureInfo is the last field after the
// FunctionNode prefix.
const liftedFunc: LiftedFunction = {
name: funcName,
params: funcParams,
Expand All @@ -174,6 +175,7 @@ export class ArrowFunctionExpressionGenerator extends BaseGenerator {
loc: undefined,
declare: false,
typeParameters: undefined,
intSpecialized: false,
closureInfo,
};

Expand Down Expand Up @@ -231,8 +233,8 @@ export class ArrowFunctionExpressionGenerator extends BaseGenerator {
}
if (funcResult) {
// Type assertion must include ALL fields from FunctionNode + closureInfo
// in exact struct order. LiftedFunction extends FunctionNode (10 fields),
// so closureInfo is at index 10. Omitting middle fields causes GEP to
// in exact struct order. LiftedFunction extends FunctionNode, so
// closureInfo is the final field. Omitting middle fields causes GEP to
// read the wrong offset in native code.
const func = funcResult as {
name: string;
Expand All @@ -245,6 +247,7 @@ export class ArrowFunctionExpressionGenerator extends BaseGenerator {
loc: SourceLocation;
declare: boolean;
typeParameters: string[];
intSpecialized: boolean;
closureInfo: ClosureInfo;
};
return func.closureInfo;
Expand Down
22 changes: 22 additions & 0 deletions src/codegen/expressions/calls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,15 @@ export class CallExpressionGenerator {
),
);
}
// Integer-specialized callee: every double param/return becomes i64.
// The existing FFI coercion paths in this loop already handle paramType
// === "i64" (fptosi from double, or pass-through from i64).
if (func.intSpecialized) {
for (let pi = 0; pi < paramTypes.length; pi++) {
if (paramTypes[pi] === "double") paramTypes[pi] = "i64";
}
if (returnType === "double") returnType = "i64";
}
} else {
const funcNode = this.getFunctionFromAST(expr.name);
if (funcNode) {
Expand Down Expand Up @@ -936,6 +945,12 @@ export class CallExpressionGenerator {
);
}
}
if (funcNode.intSpecialized) {
for (let pi = 0; pi < paramTypes.length; pi++) {
if (paramTypes[pi] === "double") paramTypes[pi] = "i64";
}
if (returnType === "double") returnType = "i64";
}
}
}

Expand Down Expand Up @@ -1022,6 +1037,13 @@ export class CallExpressionGenerator {
return coerced;
}
if (returnType === "i64") {
// Integer-specialized callees keep their result as native i64 so that
// surrounding integer arithmetic stays in the i64 lane (no fadd round-trip).
// Other i64-returning extern calls still get coerced to double.
if (func && func.intSpecialized) {
this.ctx.setVariableType(temp, "i64");
return temp;
}
const coerced = this.ctx.nextTemp();
this.ctx.emit(`${coerced} = sitofp i64 ${temp} to double`);
return coerced;
Expand Down
23 changes: 22 additions & 1 deletion src/codegen/infrastructure/function-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,22 @@ export class FunctionGenerator {
const liftedFunc = func as LiftedFunction;
const closureInfo = liftedFunc.closureInfo;
const hasClosure = closureInfo ? closureInfo.captures.length > 0 : false;

// Integer-specialized functions (detected by markIntSpecializedFunctions)
// use the i64 ABI: every numeric param becomes i64 instead of double, and
// the return type becomes i64 instead of double. Closures are excluded by
// construction (the detector only sees ast.functions, not lifted lambdas)
// but we double-check here.
const intSpecialized = func.intSpecialized && !hasClosure ? true : false;
if (intSpecialized) {
for (let i = 0; i < paramLLVMTypes.length; i++) {
if (paramLLVMTypes[i] === "double") paramLLVMTypes[i] = "i64";
}
if (returnType === "double") {
returnType = "i64";
this.ctx.setCurrentFunctionReturnType("i64");
}
}
const captures = closureInfo ? closureInfo.captures : null;
let hasOptionalParams = false;
if (func.parameters) {
Expand Down Expand Up @@ -310,7 +326,8 @@ export class FunctionGenerator {
const bodyStmts = func.body ? func.body.statements : [];
const numericParamNames: string[] = [];
for (let i = 0; i < funcParams.length; i++) {
if (paramLLVMTypes[i] === "double") {
// Numeric params include both default-double params and intSpec'd i64 params.
if (paramLLVMTypes[i] === "double" || paramLLVMTypes[i] === "i64") {
numericParamNames.push(funcParams[i]);
}
}
Expand Down Expand Up @@ -630,11 +647,15 @@ export class FunctionGenerator {
break;
}
}
// intSpecialized: the function ABI passes i64 directly, so skip fptosi.
const paramAbiIsI64 = llvmType === "i64";
if (paramIsI64) {
this.ctx.defineVariable(paramName, allocaReg, "i64", SymbolKind_Number, "local");
this.ctx.emit(`${allocaReg} = alloca i64`);
if (isOptional && hasOptionalParams) {
this.generateOptionalParamInitI64(i, allocaReg, paramInfo!, funcParams);
} else if (paramAbiIsI64) {
this.ctx.emit(`store i64 %arg${i}, i64* ${allocaReg}`);
} else {
const i64Val = this.ctx.nextTemp();
this.ctx.emit(`${i64Val} = fptosi double %arg${i} to i64`);
Expand Down
Loading
Loading