aboutsummaryrefslogtreecommitdiff
path: root/lib/CodeGen/OutputGen.cc
diff options
context:
space:
mode:
author_ov4 <ov4@ov4.org>2026-10-22 15:20:26 +0000
committer_ov4 <ov4@ov4.org>2026-10-22 15:20:26 +0000
commit9ef1cc2424e5afa21d3980b56046f5fb71bff89f (patch)
tree7b1fff9f3114601e3fa7a29ecc9e31481a4bccf6 /lib/CodeGen/OutputGen.cc
parent5c491c54a22044ef1570c3fb709e14264e367fb1 (diff)
feat(output)!: print REAL values in shortest round-trip form
OUTPUT and NUM_TO_STR now share Python repr formatting instead of %f. BREAKING CHANGE: REAL output changes from six fixed decimals (2.500000) to the shortest round-trip text (2.5). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Diffstat (limited to 'lib/CodeGen/OutputGen.cc')
-rw-r--r--lib/CodeGen/OutputGen.cc101
1 files changed, 95 insertions, 6 deletions
diff --git a/lib/CodeGen/OutputGen.cc b/lib/CodeGen/OutputGen.cc
index c1ea4b6..faf21df 100644
--- a/lib/CodeGen/OutputGen.cc
+++ b/lib/CodeGen/OutputGen.cc
@@ -3,6 +3,98 @@
using namespace llvm;
using namespace cps;
+namespace {
+// Defines (once per module) `void __cps_output_real(double, i1 Newline)`, which prints a REAL in
+// the shortest round-trip form of Python's repr: fixed notation (at least one decimal) for
+// 1e-4 <= |x| < 1e16, otherwise scientific with a two-digit exponent. The text is
+// NUM_TO_STR-identical to _string_impl_from_real in pseudocode-lib/string.cc; keep both in sync.
+FunctionCallee getOutputRealHelper(Module &M) {
+ LLVMContext &Ctx = M.getContext();
+ Type *VoidTy = Type::getVoidTy(Ctx), *I32 = Type::getInt32Ty(Ctx), *I64 = Type::getInt64Ty(Ctx);
+ Type *DoubleTy = Type::getDoubleTy(Ctx), *I1 = Type::getInt1Ty(Ctx);
+ PointerType *Ptr = PointerType::getUnqual(Ctx);
+ FunctionType *HelperTy = FunctionType::get(VoidTy, {DoubleTy, I1}, false);
+ if (Function *Existing = M.getFunction("__cps_output_real")) return {HelperTy, Existing};
+
+ FunctionCallee Printf = M.getOrInsertFunction("printf", FunctionType::get(I32, {Ptr}, true));
+ FunctionCallee Snprintf = M.getOrInsertFunction("snprintf", FunctionType::get(I32, {Ptr, I64, Ptr}, true));
+ FunctionCallee Strtod = M.getOrInsertFunction("strtod", FunctionType::get(DoubleTy, {Ptr, Ptr}, false));
+
+ Function *F = Function::Create(HelperTy, Function::InternalLinkage, "__cps_output_real", &M);
+ Value *X = F->getArg(0), *Newline = F->getArg(1);
+ BasicBlock *Entry = BasicBlock::Create(Ctx, "entry", F);
+ BasicBlock *NanBB = BasicBlock::Create(Ctx, "nan", F), *NotNan = BasicBlock::Create(Ctx, "notnan", F);
+ BasicBlock *InfBB = BasicBlock::Create(Ctx, "inf", F), *Finite = BasicBlock::Create(Ctx, "finite", F);
+ BasicBlock *FixedHead = BasicBlock::Create(Ctx, "fixed.head", F), *FixedBody = BasicBlock::Create(Ctx, "fixed.body", F);
+ BasicBlock *SciHead = BasicBlock::Create(Ctx, "sci.head", F), *SciBody = BasicBlock::Create(Ctx, "sci.body", F);
+ BasicBlock *Print = BasicBlock::Create(Ctx, "print", F);
+
+ IRBuilder<> B(Entry);
+ constexpr uint64_t BufSize = 64;
+ Value *Buf = B.CreateAlloca(ArrayType::get(Type::getInt8Ty(Ctx), BufSize), nullptr, "buf");
+ auto Str = [&](const char *Text, const char *Name) { return B.CreateGlobalString(Text, Name, 0, &M); };
+ Value *SizeV = ConstantInt::get(I64, BufSize);
+ Value *NanCheck = B.CreateFCmpUNO(X, X);
+ B.CreateCondBr(NanCheck, NanBB, NotNan);
+
+ B.SetInsertPoint(NanBB);
+ B.CreateCall(Snprintf, {Buf, SizeV, Str("%s", "fmt_real_s"), Str("nan", "real_nan")});
+ B.CreateBr(Print);
+
+ B.SetInsertPoint(NotNan);
+ Value *IsNeg = B.CreateFCmpOLT(X, ConstantFP::get(DoubleTy, 0.0));
+ Value *Abs = B.CreateSelect(IsNeg, B.CreateFNeg(X), X, "abs");
+ B.CreateCondBr(B.CreateFCmpOEQ(Abs, ConstantFP::getInfinity(DoubleTy)), InfBB, Finite);
+
+ B.SetInsertPoint(InfBB);
+ B.CreateCall(Snprintf, {Buf, SizeV, Str("%s", "fmt_real_s"),
+ B.CreateSelect(IsNeg, Str("-inf", "real_ninf"), Str("inf", "real_inf"))});
+ B.CreateBr(Print);
+
+ B.SetInsertPoint(Finite);
+ Value *IsFixed = B.CreateOr(
+ B.CreateAnd(B.CreateFCmpOGE(Abs, ConstantFP::get(DoubleTy, 1e-4)),
+ B.CreateFCmpOLT(Abs, ConstantFP::get(DoubleTy, 1e16))),
+ B.CreateFCmpOEQ(Abs, ConstantFP::get(DoubleTy, 0.0)));
+ B.CreateCondBr(IsFixed, FixedHead, SciHead);
+
+ // Fixed notation: the fewest decimals (at least one) that round-trip.
+ B.SetInsertPoint(FixedHead);
+ PHINode *Decimals = B.CreatePHI(I32, 2, "decimals");
+ Value *FixedFmt = Str("%.*f", "fmt_real_f");
+ B.CreateCall(Snprintf, {Buf, SizeV, FixedFmt, Decimals, X});
+ Value *FixedBack = B.CreateCall(Strtod, {Buf, ConstantPointerNull::get(Ptr)});
+ Value *FixedDone = B.CreateOr(B.CreateFCmpOEQ(FixedBack, X),
+ B.CreateICmpUGE(Decimals, ConstantInt::get(I32, 40)));
+ B.CreateCondBr(FixedDone, Print, FixedBody);
+ B.SetInsertPoint(FixedBody);
+ Value *NextDecimals = B.CreateAdd(Decimals, ConstantInt::get(I32, 1));
+ B.CreateBr(FixedHead);
+ Decimals->addIncoming(ConstantInt::get(I32, 1), Finite);
+ Decimals->addIncoming(NextDecimals, FixedBody);
+
+ // Scientific notation: the fewest significant digits (1..17) that round-trip.
+ B.SetInsertPoint(SciHead);
+ PHINode *Precision = B.CreatePHI(I32, 2, "precision");
+ Value *SciFmt = Str("%.*e", "fmt_real_e");
+ B.CreateCall(Snprintf, {Buf, SizeV, SciFmt, Precision, X});
+ Value *SciBack = B.CreateCall(Strtod, {Buf, ConstantPointerNull::get(Ptr)});
+ Value *SciDone = B.CreateOr(B.CreateFCmpOEQ(SciBack, X),
+ B.CreateICmpUGE(Precision, ConstantInt::get(I32, 16)));
+ B.CreateCondBr(SciDone, Print, SciBody);
+ B.SetInsertPoint(SciBody);
+ Value *NextPrecision = B.CreateAdd(Precision, ConstantInt::get(I32, 1));
+ B.CreateBr(SciHead);
+ Precision->addIncoming(ConstantInt::get(I32, 0), Finite);
+ Precision->addIncoming(NextPrecision, SciBody);
+
+ B.SetInsertPoint(Print);
+ B.CreateCall(Printf, {B.CreateSelect(Newline, Str("%s\n", "fmt_real_out_nl"), Str("%s", "fmt_real_out")), Buf});
+ B.CreateRetVoid();
+ return {HelperTy, F};
+}
+}
+
Value *CodeGen::emitStringNullGuard(Value *StrVal) {
if (!StrVal || !StrVal->getType()->isPointerTy()) return StrVal;
Value *IsNull = Builder->CreateICmpEQ(StrVal,
@@ -28,12 +120,9 @@ void CodeGen::emitOutputValue(Value *Val, const TypeInfo *Info, bool AppendNewli
// kind is added without deciding its OUTPUT story.
switch (Info->Kind) {
case TypeKind::Real:
- Fmt = AppendNewline
- ? PrintfFloatFormatStr
- : Builder->CreateGlobalString("%f", "fmt_flt_inline", 0, TheModule.get());
- Args.push_back(Fmt);
- Args.push_back(Val);
- break;
+ Builder->CreateCall(getOutputRealHelper(*TheModule),
+ {coerceValueToType(Val, resolveType("REAL")), Builder->getInt1(AppendNewline)});
+ return;
case TypeKind::Boolean: {
Value *BoolVal = coerceValueToType(Val, resolveType("BOOLEAN"));