From 5a5a14be5e2683a42906da4b2ad19690e230c871 Mon Sep 17 00:00:00 2001 From: _ov4 Date: Thu, 22 Oct 2026 15:15:20 +0000 Subject: feat(codegen): short-circuit AND and OR Co-Authored-By: Claude Sonnet 5.5 --- AGENTS/CONVENTIONS/TYPES.md | 1 + doc/language/keyword/cpsc-keyword-and.7.md | 2 +- doc/language/keyword/cpsc-keyword-or.7.md | 2 +- include/cps/CodeGen.h | 2 ++ lib/CodeGen/ExpressionGen.cc | 43 ++++++++++++++++++++++++++++++ 5 files changed, 48 insertions(+), 2 deletions(-) diff --git a/AGENTS/CONVENTIONS/TYPES.md b/AGENTS/CONVENTIONS/TYPES.md index f5764d4..7349ab1 100644 --- a/AGENTS/CONVENTIONS/TYPES.md +++ b/AGENTS/CONVENTIONS/TYPES.md @@ -30,6 +30,7 @@ rejects arithmetic on BOOLEAN/CHAR, AND/OR on non-BOOLEAN, and comparisons betwe different kinds (INTEGER with REAL is the only mixed pair); `emitCoercedExpr` allows only INTEGER->REAL between the four builtin scalars. REAL->INTEGER therefore errors (with an `INT(...)` hint); `INT` is a compiler builtin (`Builtins.h`, `CodeGen::emitIntBuiltin`) truncating toward zero with a range check. BOOLEAN (i1) and CHAR (i8) compare unsigned. C scalars keep C semantics. +AND/OR short-circuit (`CodeGen::emitShortCircuit`) unless a user overload applies. **User-kind typing is nominal, and checked upstream only.** Enum/Record/Pointer identity is case-insensitive name equality (`P1 = ^INTEGER` and `P2 = ^INTEGER` are diff --git a/doc/language/keyword/cpsc-keyword-and.7.md b/doc/language/keyword/cpsc-keyword-and.7.md index 7da90b7..52b0a9a 100644 --- a/doc/language/keyword/cpsc-keyword-and.7.md +++ b/doc/language/keyword/cpsc-keyword-and.7.md @@ -12,7 +12,7 @@ left AND right # DESCRIPTION -Return BOOLEAN: true when both operands are true. Both operands must be BOOLEAN; numbers and characters are rejected. Both operands are evaluated; AND does not short-circuit. AND binds more tightly than OR and less tightly than comparisons. Use parentheses for clarity. +Return BOOLEAN: true when both operands are true. Both operands must be BOOLEAN; numbers and characters are rejected. AND short-circuits: the right operand is evaluated only when the left one is true. AND binds more tightly than OR and less tightly than comparisons. Use parentheses for clarity. # EXAMPLE diff --git a/doc/language/keyword/cpsc-keyword-or.7.md b/doc/language/keyword/cpsc-keyword-or.7.md index 678ff19..986044e 100644 --- a/doc/language/keyword/cpsc-keyword-or.7.md +++ b/doc/language/keyword/cpsc-keyword-or.7.md @@ -12,7 +12,7 @@ left OR right # DESCRIPTION -Return BOOLEAN: true when either operand is true. Both operands must be BOOLEAN; numbers and characters are rejected. Both operands are evaluated; OR does not short-circuit. OR binds less tightly than AND and comparisons. +Return BOOLEAN: true when either operand is true. Both operands must be BOOLEAN; numbers and characters are rejected. OR short-circuits: the right operand is evaluated only when the left one is false. OR binds less tightly than AND and comparisons. # EXAMPLE diff --git a/include/cps/CodeGen.h b/include/cps/CodeGen.h index 3b0296f..539a6e6 100644 --- a/include/cps/CodeGen.h +++ b/include/cps/CodeGen.h @@ -201,6 +201,8 @@ class CodeGen { // Read a value from resolved storage. llvm::Value *loadFromLValue(const LValueInfo &LV); llvm::Value *emitAllocationSize(ExprAST *Expr); + static bool isLogicalOperand(const TypeInfo *T); + llvm::Value *emitShortCircuit(BinaryExprAST *Bin, const TypeInfo *LT, const TypeInfo *RT); llvm::Value *emitIntBuiltin(ExprAST *Arg, int Line); llvm::Value *emitCall(const std::string &Callee, const std::vector> &ArgExprs, int Line, diff --git a/lib/CodeGen/ExpressionGen.cc b/lib/CodeGen/ExpressionGen.cc index 51d7087..45583d3 100644 --- a/lib/CodeGen/ExpressionGen.cc +++ b/lib/CodeGen/ExpressionGen.cc @@ -145,6 +145,9 @@ Value *CodeGen::emitExpr(ExprAST *Expr) { if (Overload.Signature) return emitOperatorCall(*Overload.Signature, Bin->getLHS(), Bin->getRHS()); } + if ((Bin->getOp() == tok_and || Bin->getOp() == tok_or) && LT && RT && + Builder->GetInsertBlock() && isLogicalOperand(LT) && isLogicalOperand(RT)) + return emitShortCircuit(Bin, LT, RT); Value *L = emitExpr(Bin->getLHS()); Value *R = emitExpr(Bin->getRHS()); return emitBinaryOp(Bin->getOp(), L, LT, R, RT, Bin->getLine()); @@ -173,6 +176,46 @@ Value *CodeGen::emitExpr(ExprAST *Expr) { return nullptr; } +// BOOLEAN, or a C scalar tested against zero; everything else keeps the +// ordinary operator path (and its diagnostics). +bool CodeGen::isLogicalOperand(const TypeInfo *T) { + return T->Kind == TypeKind::Boolean || (T->Kind == TypeKind::CScalar && !T->isVoid()); +} + +// AND / OR evaluate the right operand only when the left one does not +// already decide the result. +Value *CodeGen::emitShortCircuit(BinaryExprAST *Bin, const TypeInfo *LT, const TypeInfo *RT) { + SaveAndRestore Location(DiagnosticLine, Bin->getLine() ? Bin->getLine() : DiagnosticLine); + const TypeInfo *Bool = resolveType("BOOLEAN"); + bool IsAnd = Bin->getOp() == tok_and; + auto EmitOperand = [&](ExprAST *E, const TypeInfo *T) -> Value * { + Value *V = emitExpr(E); + if (!V) return nullptr; + return T->Kind == TypeKind::Boolean ? V : convertNumericValue(V, T, Bool); + }; + + Value *L = EmitOperand(Bin->getLHS(), LT); + if (!L) return nullptr; + Function *TheFunction = Builder->GetInsertBlock()->getParent(); + BasicBlock *LeftBB = Builder->GetInsertBlock(); + BasicBlock *RightBB = BasicBlock::Create(*TheContext, IsAnd ? "and_rhs" : "or_rhs", TheFunction); + BasicBlock *DoneBB = BasicBlock::Create(*TheContext, IsAnd ? "and_done" : "or_done", TheFunction); + if (IsAnd) Builder->CreateCondBr(L, RightBB, DoneBB); + else Builder->CreateCondBr(L, DoneBB, RightBB); + + Builder->SetInsertPoint(RightBB); + Value *R = EmitOperand(Bin->getRHS(), RT); + if (!R) return nullptr; + BasicBlock *RightEndBB = Builder->GetInsertBlock(); + Builder->CreateBr(DoneBB); + + Builder->SetInsertPoint(DoneBB); + PHINode *Result = Builder->CreatePHI(Type::getInt1Ty(*TheContext), 2, IsAnd ? "andtmp" : "ortmp"); + Result->addIncoming(ConstantInt::get(Type::getInt1Ty(*TheContext), IsAnd ? 0 : 1), LeftBB); + Result->addIncoming(R, RightEndBB); + return Result; +} + // Ordinary binary expressions and CASE use the same typed operations. Value *CodeGen::emitBinaryOp(int Op, Value *L, const TypeInfo *LT, Value *R, const TypeInfo *RT, int Line) { -- cgit v1.3.1-4-g156e