Skip to content
Open
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
40 changes: 39 additions & 1 deletion packages/compiler/src/backend/llvm/expr-primitives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,29 @@ export function emitOperatorExpr(
const fn = libm[e.op];
if (fn === undefined) throw new LlvmUnsupportedError(`bin:${e.op}`, e.loc);
host.declare(`declare double @${fn}(double, double)`);
B.line(`${t} = call double @${fn}(double ${l.name}, double ${r.name})`);
// libm's pow answers 1 for pow(±1, ±Infinity) and pow(1, NaN), where
// `**` answers NaN (and LLVM folds pow(1.0, x) to 1.0): an inline
// guard selects NaN for a base of ±1 with a non-finite exponent and
// keeps the direct pow call. A finite literal exponent, or a literal
// base other than ±1, cannot meet those cases and needs no guard.
if (fn === "pow" && !libmPowAgrees(e.left, e.right)) {
host.declare(`declare double @llvm.fabs.f64(double)`);
const power = B.tmp();
B.line(`${power} = call double @pow(double ${l.name}, double ${r.name})`);
const base = B.tmp();
const unit = B.tmp();
const magnitude = B.tmp();
const finite = B.tmp();
const reject = B.tmp();
B.line(`${base} = call double @llvm.fabs.f64(double ${l.name})`);
B.line(`${unit} = fcmp oeq double ${base}, 1.0`);
B.line(`${magnitude} = call double @llvm.fabs.f64(double ${r.name})`);
B.line(`${finite} = fcmp olt double ${magnitude}, 0x7FF0000000000000`);
B.line(`${reject} = select i1 ${finite}, i1 false, i1 ${unit}`);
B.line(`${t} = select i1 ${reject}, double 0x7FF8000000000000, double ${power}`);
} else {
B.line(`${t} = call double @${fn}(double ${l.name}, double ${r.name})`);
}
}
return { name: t, type: e.type };
}
Expand Down Expand Up @@ -1103,3 +1125,19 @@ export function emitRecordLitFields(
host.initField(ptr, type, inline, v);
}
}

/** Whether libm pow matches `**` for these operands: every case except a
* base of ±1 with a NaN or infinite exponent agrees. */
function libmPowAgrees(base: IrExpr, exponent: IrExpr): boolean {
const power = numericLiteral(exponent);
if (power !== null && Number.isFinite(power)) return true;
const value = numericLiteral(base);
return value !== null && !Number.isNaN(value) && Math.abs(value) !== 1;
}

function numericLiteral(expr: IrExpr): number | null {
if (expr.kind === "numLit") return expr.value;
if (expr.kind === "unary" && expr.op === "-" && expr.operand.kind === "numLit")
return -expr.operand.value;
return null;
}
52 changes: 52 additions & 0 deletions packages/compiler/src/frontend/lowering/builtin-function-fences.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/** A builtin module function used as a JavaScript value when it has no
* value adapter (lowerNativeCallableValue). Node hands the program a real
* function; the compiled value is one function per builtin that throws the
* named fence when called. Identity flows (`fn === fs.readFileSync`, Set
* membership) still hold, `typeof` answers "function" and inspection
* prints `[Function: name]`, where a string token would answer "string"
* and turn a call into an ordinary "is not a function" TypeError. */
import * as ts from "../ts7/adapter.js";
import { DYN, type IrExpr, type SrcLoc, funcOf } from "../../ir/ir.js";
import { unsupportedDiag } from "../../diagnostics/diagnostic.js";
import type { Lowerer } from "./lowerer.js";

export function builtinFunctionFenceValue(
lowerer: Lowerer,
bi: { module: string; member: string },
node: ts.Identifier,
loc: SrcLoc,
): IrExpr {
const display = `${bi.module}.${bi.member}`;
const name = `%builtin.fence.${display}`;
if (!lowerer.liftedFns.some((fn) => fn.name === name)) {
const diag = unsupportedDiag(
"SC1090",
loc,
`library functions as values ('${display}' has no function-value lowering; call it directly)`,
);
lowerer.runtimeFences.push(diag);
const line = ts.getLineAndCharacterOfPosition(node.getSourceFile(), loc.start).line + 1;
lowerer.liftedFns.push({
name,
params: [],
locals: [],
returnType: DYN,
body: [
{
kind: "runtimeFence",
code: diag.code,
message: `${diag.message} [${diag.code} at ${loc.file}:${line}]`,
loc,
},
],
loc,
});
}
return {
kind: "dynFrom",
value: { kind: "closure", fnName: name, captures: [], type: funcOf([], DYN), loc },
fnName: bi.member,
type: DYN,
loc,
};
}
5 changes: 3 additions & 2 deletions packages/compiler/src/frontend/lowering/class-construction.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { dynUndefinedExpr, varRef } from "../../ir/build.js";
import { constructedValue } from "./constructor-returns.js";
import {
BOOL,
DYN,
Expand Down Expand Up @@ -389,13 +390,13 @@ export class ClassConstructionDispatch {
if (!args) continue;
const type: IrType = { kind: "classval", className: name };
const callee = varRef("p.0", DYN, loc);
const value: IrExpr = {
const value = constructedValue(lowerer, info, {
kind: "newValue",
callee: { kind: "dynCheck", value: callee, type, loc },
args,
type: { kind: "object", className: name },
loc,
};
});
const body: IrStmt[] = [
{ kind: "return", value: lowerer.coerceToExpected(value, DYN), loc },
];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { dynUndefinedExpr, helperLoc, nodeThrowExpr, varRef } from "../../ir/build.js";
import { constructedValue } from "./constructor-returns.js";
import { eventTargetApiMembers, eventTargetInheritedMember } from "./lower-event-target.js";
import {
BOOL,
Expand Down Expand Up @@ -3158,13 +3159,13 @@ export class ClassDynamicDispatch {
}
if (!supported || !info.decl) continue;
const cls = classValueRef(lowerer, info, info.decl);
const result: IrExpr = {
const result = constructedValue(lowerer, info, {
kind: "new",
className: name,
args,
type: { kind: "object", className: name },
loc,
};
});
this.constructDispatch.body.unshift({
kind: "if",
cond: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { constructedValue } from "../constructor-returns.js";
import { dynUndefinedExpr, varRef } from "../../../ir/build.js";
import type { FieldLift } from "./structural-plans.js";
import { buildArrayConversion } from "./builders.js";
Expand Down Expand Up @@ -524,7 +525,13 @@ export function recordClassWidthHelper(
params: [{ localId: "r.0", name: "r", type: fromT }],
returnType: toT,
locals: [{ id: "r.0", name: "r", type: fromT, mutable: true }],
body: [{ kind: "return", value: { kind: "new", className, args, type: toT, loc }, loc }],
body: [
{
kind: "return",
value: constructedValue(lowerer, info, { kind: "new", className, args, type: toT, loc }),
loc,
},
],
loc,
});
return name;
Expand Down
Loading