diff --git a/include/cfg.h b/include/cfg.h index 3a1dada..ce65f0a 100644 --- a/include/cfg.h +++ b/include/cfg.h @@ -5,6 +5,7 @@ #include #include "ast.h" +#include "constexpr.h" namespace yl { struct BasicBlock { @@ -42,6 +43,7 @@ struct CFG : public Dumpable { }; class CFGBuilder { + ConstantExpressionEvaluator cee; CFG cfg; int insertBlock(const ResolvedBlock &block, int successor); diff --git a/include/constexpr.h b/include/constexpr.h index c069b6e..c775185 100644 --- a/include/constexpr.h +++ b/include/constexpr.h @@ -8,12 +8,14 @@ namespace yl { class ConstantExpressionEvaluator { std::optional - evaluateBinaryOperator(const ResolvedBinaryOperator &binop); + evaluateBinaryOperator(const ResolvedBinaryOperator &binop, + bool allowSideEffects = false); std::optional evaluateUnaryOperator(const ResolvedUnaryOperator &op); std::optional evaluateDeclRefExpr(const ResolvedDeclRefExpr &dre); public: - std::optional evaluate(const ResolvedExpr &expr); + std::optional evaluate(const ResolvedExpr &expr, + bool allowSideEffects = false); }; } // namespace yl diff --git a/src/cfg.cpp b/src/cfg.cpp index 88ae220..fe95024 100644 --- a/src/cfg.cpp +++ b/src/cfg.cpp @@ -46,7 +46,7 @@ int CFGBuilder::insertIfStmt(const ResolvedIfStmt &stmt, int exit) { int trueBlock = insertBlock(*stmt.trueBlock, exit); int entry = cfg.insertNewBlock(); - std::optional val = stmt.condition->getConstantValue(); + std::optional val = cee.evaluate(*stmt.condition, true); cfg.insertEdge(entry, trueBlock, val != 0); cfg.insertEdge(entry, falseBlock, val.value_or(0) == 0); @@ -61,7 +61,7 @@ int CFGBuilder::insertWhileStmt(const ResolvedWhileStmt &stmt, int exit) { int header = cfg.insertNewBlock(); cfg.insertEdge(latch, header, true); - std::optional val = stmt.condition->getConstantValue(); + std::optional val = cee.evaluate(*stmt.condition, true); cfg.insertEdge(header, body, val != 0); cfg.insertEdge(header, exit, val.value_or(0) == 0); diff --git a/src/constexpr.cpp b/src/constexpr.cpp index 4fb0f38..704ae90 100644 --- a/src/constexpr.cpp +++ b/src/constexpr.cpp @@ -14,33 +14,44 @@ std::optional toBool(std::optional d) { namespace yl { std::optional ConstantExpressionEvaluator::evaluateBinaryOperator( - const ResolvedBinaryOperator &binop) { + const ResolvedBinaryOperator &binop, bool allowSideEffects) { std::optional lhs = evaluate(*binop.lhs); + if (!lhs && !allowSideEffects) + return std::nullopt; + if (binop.op == TokenKind::PipePipe) { // If the LHS of || is true, we don't need to evaluate the RHS. - if (toBool(lhs).value_or(false)) + if (toBool(lhs) == true) + return 1.0; + + // If the LHS is false, or side effects are allowed and the RHS is true, the + // result is true. + std::optional rhs = evaluate(*binop.rhs); + if (toBool(rhs) == true) return 1.0; - return toBool(evaluate(*binop.rhs)); + // If both sides are known but none of them is true, the result is false. + if (lhs && rhs) + return 0.0; + + // Otherwise one of the sides is unknown, so the result is unknown too. + return std::nullopt; } if (binop.op == TokenKind::AmpAmp) { // If the LHS of && is false, we don't need to evaluate the RHS. - if (binop.op == TokenKind::AmpAmp && !toBool(lhs).value_or(true)) + if (toBool(lhs) == false) return 0.0; - // If the LHS is unknown, but the RHS is false, the expression is false. std::optional rhs = evaluate(*binop.rhs); - if (!lhs) { - if (rhs == 0.0) - return rhs; + if (toBool(rhs) == false) + return 0.0; - return std::nullopt; - } + if (lhs && rhs) + return 1.0; - // Otherwise LHS is known to be true, so the result depends on the RHS. - return toBool(rhs); + return std::nullopt; } if (!lhs) @@ -95,7 +106,8 @@ std::optional ConstantExpressionEvaluator::evaluateDeclRefExpr( } std::optional -ConstantExpressionEvaluator::evaluate(const ResolvedExpr &expr) { +ConstantExpressionEvaluator::evaluate(const ResolvedExpr &expr, + bool allowSideEffects) { // Don't evaluate the same expression multiple times. if (std::optional val = expr.getConstantValue()) return val; @@ -106,11 +118,11 @@ ConstantExpressionEvaluator::evaluate(const ResolvedExpr &expr) { if (const auto *groupingExpr = dynamic_cast(&expr)) - return evaluate(*groupingExpr->expr); + return evaluate(*groupingExpr->expr, allowSideEffects); if (const auto *binaryOperator = dynamic_cast(&expr)) - return evaluateBinaryOperator(*binaryOperator); + return evaluateBinaryOperator(*binaryOperator, allowSideEffects); if (const auto *unaryOperator = dynamic_cast(&expr)) diff --git a/test/constexpr/special_cases.yl b/test/constexpr/special_cases.yl index ec711a0..6131460 100644 --- a/test/constexpr/special_cases.yl +++ b/test/constexpr/special_cases.yl @@ -52,19 +52,15 @@ fn lhsUnknownRhsFalse(x: number): number { } // CHECK: ResolvedReturnStmt // CHECK-NEXT: ResolvedBinaryOperator: '&&' -// CHECK-NEXT: | value: 0 // CHECK-NEXT: ResolvedDeclRefExpr: @({{.*}}) x // CHECK-NEXT: ResolvedNumberLiteral: '0' fn lhsUnknownRhsTrue(x: number): number { - return x && (0.0 - 1.0); + return x || 1.0; } -// CHECK: ResolvedReturnStmt -// CHECK-NEXT: ResolvedBinaryOperator: '&&' -// CHECK-NEXT: ResolvedDeclRefExpr: @({{.*}}) x -// CHECK-NEXT: ResolvedGroupingExpr: -// CHECK-NEXT: ResolvedBinaryOperator: '-' -// CHECK-NEXT: ResolvedNumberLiteral: '0' -// CHECK-NEXT: ResolvedNumberLiteral: '1' +// CHECK: ResolvedReturnStmt +// CHECK-NEXT: ResolvedBinaryOperator: '||' +// CHECK-NEXT: ResolvedDeclRefExpr: @({{.*}}) x +// CHECK-NEXT: ResolvedNumberLiteral: '1' fn main(): void {} diff --git a/test/sema/return_on_every_branch.yl b/test/sema/return_on_every_branch.yl index 58f1e91..0b1dc1f 100644 --- a/test/sema/return_on_every_branch.yl +++ b/test/sema/return_on_every_branch.yl @@ -144,5 +144,20 @@ fn noReturnNeverRunningLoop(): number { while 0.0 {} } +fn returnAllPath(x: number): number { + if (x || 1) { + return 1; + } else { + + } +} + +fn returnAllPathElse(x: number): number { + if (x && 0) { + } else { + return 1; + } +} + fn main(): void {} // CHECK-NOT: {{.*}}