diff --git a/Cesium.CodeGen.Tests/CodeGenDeclarationsTests.cs b/Cesium.CodeGen.Tests/CodeGenDeclarationsTests.cs index 9479c0c6..c0b9d790 100644 --- a/Cesium.CodeGen.Tests/CodeGenDeclarationsTests.cs +++ b/Cesium.CodeGen.Tests/CodeGenDeclarationsTests.cs @@ -26,6 +26,7 @@ public Task StaticDeclarationsInDifferentFunctions() => DoTest( int test() { static int i = 2; + return 0; }"); [Fact] diff --git a/Cesium.CodeGen.Tests/CodeGenMethodTests.cs b/Cesium.CodeGen.Tests/CodeGenMethodTests.cs index ce5bd069..39f45f7a 100644 --- a/Cesium.CodeGen.Tests/CodeGenMethodTests.cs +++ b/Cesium.CodeGen.Tests/CodeGenMethodTests.cs @@ -346,11 +346,24 @@ public Task ImplicitReturnAllowedForMain() => DoTest(@"int main() int unused = 0; }"); - [Fact] - public void ImplicitReturnAllowedNonMain() => DoTest(@"int foo() + [Fact, NoVerify] + public void ImplicitReturnNotAllowedNonMain() => DoesNotCompile(@"int foo() { int unused; -}"); +}", "Not all control flow paths in function foo return a value."); + + [Fact, NoVerify] + public void ReturnIsNecessaryOnEntirePathsForNonMain() => DoesNotCompile(@"int foo() +{ + if(1) + { + // missed return + } + else + { + return 1; + } +}", "Not all control flow paths in function foo return a value."); [Fact, NoVerify] public void ExpressionReturnDisallowedInVoidFunction() => DoesNotCompile(@"void foo() diff --git a/Cesium.CodeGen.Tests/CodeGenSwitchTests.cs b/Cesium.CodeGen.Tests/CodeGenSwitchTests.cs index f7a460aa..7a27cf2a 100644 --- a/Cesium.CodeGen.Tests/CodeGenSwitchTests.cs +++ b/Cesium.CodeGen.Tests/CodeGenSwitchTests.cs @@ -141,7 +141,7 @@ public Task LowerExpression() => DoTest(@" int my_condition() { return 0; } int main() { - switch(my_condition()) { + switch(my_condition()) { case 0: break; case 1: default: break; diff --git a/Cesium.CodeGen.Tests/CodeGenWhileTests.cs b/Cesium.CodeGen.Tests/CodeGenWhileTests.cs index 73363994..ed50b6fe 100644 --- a/Cesium.CodeGen.Tests/CodeGenWhileTests.cs +++ b/Cesium.CodeGen.Tests/CodeGenWhileTests.cs @@ -32,4 +32,36 @@ public Task DoWhile() => DoTest( int i = 0; do ++i; while (i < 10); }"); + + [Fact] + public Task InfinityWhile() => DoTest( + @"int main() +{ + int i = 0; + while (1) i++; +}"); + + [Fact] + public Task InfinityDoWhile() => DoTest( + @"int main() +{ + int i = 0; + do i++; while (1); +}"); + + [Fact] + public Task NegativeWhile() => DoTest( + @"int main() +{ + int i = 0; + while (!1) i++; +}"); + + [Fact] + public Task NegativeDoWhile() => DoTest( + @"int main() +{ + int i = 0; + do i++; while (!1); +}"); } diff --git a/Cesium.CodeGen.Tests/ReturnCheckerForEntirePathsTests.cs b/Cesium.CodeGen.Tests/ReturnCheckerForEntirePathsTests.cs new file mode 100644 index 00000000..71aed777 --- /dev/null +++ b/Cesium.CodeGen.Tests/ReturnCheckerForEntirePathsTests.cs @@ -0,0 +1,472 @@ +// SPDX-FileCopyrightText: 2026 Cesium contributors +// +// SPDX-License-Identifier: MIT + +using System.Diagnostics.CodeAnalysis; +using Cesium.TestFramework; + +namespace Cesium.CodeGen.Tests; + +public class ReturnCheckerForEntirePathsTests : CodeGenTestBase +{ + private static void ShouldCompile([StringSyntax("cpp")] string source) + { + GenerateAssembly(default, source); + } + + [Fact, NoVerify] + public void MissedReturnAfterPositiveIfValid() => ShouldCompile( + @"int foo() +{ + int i = 0; + if (1) return i; + i++; +}"); + + [Fact, NoVerify] + public void MissedReturnInNegationIfValid() => ShouldCompile( + @"int foo() +{ + int i = 0; + if (!1) i++; + return i; + i++; +}"); + + [Fact, NoVerify] + public void MissedReturnAfterNegationIfInvalid() => DoesNotCompile( + @"int foo() +{ + int i = 0; + if (!1) return i; + i++; +}", "Not all control flow paths in function foo return a value."); + + [Fact, NoVerify] + public void MissedReturnInPositiveIfInvalid() => DoesNotCompile( + @"int foo() +{ + int i = 0; + if (1) i++; + i++; +}", "Not all control flow paths in function foo return a value."); + + [Fact, NoVerify] + public void MissedReturnAfterPositiveWhileValid() => ShouldCompile( + @"int foo() +{ + int i = 0; + while (1) return i; + i++; +}"); + + [Fact, NoVerify] + public void MissedReturnInNegationWhileValid() => ShouldCompile( + @"int foo() +{ + int i = 0; + while (!1) i++; + return i; + i++; +}"); + + [Fact, NoVerify] + public void MissedReturnInPositiveWhileValid() => ShouldCompile( + @"int foo() +{ + int i = 0; + while (1) i++; + i++; +}"); + + [Fact, NoVerify] + public void MissedReturnAfterNegationWhileInvalid() => DoesNotCompile( + @"int foo() +{ + int i = 0; + while (!1) return i; + i++; +}", "Not all control flow paths in function foo return a value."); + + [Fact, NoVerify] + public void MissedReturnAfterPositiveDoWhileValid() => ShouldCompile( + @"int foo() +{ + int i = 0; + do return i; while (1); + i++; +}"); + + [Fact, NoVerify] + public void MissedReturnInNegationDoWhileValid() => ShouldCompile( + @"int foo() +{ + int i = 0; + do i++; while (!1); + return i; + i++; +}"); + + [Fact, NoVerify] + public void MissedReturnInPositiveDoWhileValid() => ShouldCompile( + @"int foo() +{ + int i = 0; + do i++; while (1); + return i; + i++; +}"); + + [Fact, NoVerify] + public void MissedReturnAfterNegationDoWhileValid() => ShouldCompile( + @"int foo() +{ + int i = 0; + do return i; while (-1); + i++; +}"); + + [Fact, NoVerify] + public void MissedReturnAfterNegationDoWhileInvalid() => DoesNotCompile( + @"int foo() +{ + int i = 0; + do i++; while (!1); + i++; +}", "Not all control flow paths in function foo return a value."); + + [Fact, NoVerify] +public void ElseIfChainAllReturns() => ShouldCompile( + @"int foo(int x) +{ + if (x > 0) return 1; + else if (x < 0) return -1; + else return 0; +}"); + +[Fact, NoVerify] +public void ElseIfChainMissingReturnInElse() => DoesNotCompile( + @"int foo(int x) +{ + if (x > 0) return 1; + else if (x < 0) return -1; + // else missing +}", "Not all control flow paths in function foo return a value."); + +[Fact, NoVerify] +public void ElseIfChainMissingReturnInOneIf() => DoesNotCompile( + @"int foo(int x) +{ + if (x > 0) return 1; + else if (x < 0) { int y = x; } + else return 0; +}", "Not all control flow paths in function foo return a value."); + +[Fact, NoVerify] +public void NestedIfAllReturns() => ShouldCompile( + @"int foo(int x) +{ + if (x > 0) + { + if (x < 10) return 1; + else return 2; + } + else return 3; +}"); + +[Fact, NoVerify] +public void NestedIfMissingReturnInInnerElse() => DoesNotCompile( + @"int foo(int x) +{ + if (x > 0) + { + if (x < 10) return 1; + // else missing + } + else return 3; +}", "Not all control flow paths in function foo return a value."); + +[Fact, NoVerify] +public void NestedIfMissingReturnInOuterElse() => DoesNotCompile( + @"int foo(int x) +{ + if (x > 0) + { + if (x < 10) return 1; + else return 2; + } + // outer else missing +}", "Not all control flow paths in function foo return a value."); + +[Fact, NoVerify] +public void WhileWithBreakAndReturnAfter() => ShouldCompile( + @"int foo(int x) +{ + int i = 0; + while (i < x) + { + if (i == 5) break; + i++; + } + return i; +}"); + +[Fact, NoVerify] +public void WhileWithBreakInsideIfWithoutReturnAfter() => DoesNotCompile( + @"int foo(int x) +{ + int i = 0; + while (i < x) + { + if (i == 5) break; + i++; + } + // no return after loop +}", "Not all control flow paths in function foo return a value."); + +[Fact, NoVerify] +public void WhileWithContinue() => ShouldCompile( + @"int foo(int x) +{ + int i = 0; + while (i < x) + { + i++; + if (i == 5) continue; + return i; + } + return 0; +}"); + +[Fact, NoVerify] +public void InfiniteLoopWithBreakAndReturnAfter() => ShouldCompile( + @"int foo(int x) +{ + while (1) + { + if (x) break; + } + return 0; +}"); + +[Fact, NoVerify] +public void InfiniteLoopWithBreakAndNoReturnAfter() => DoesNotCompile( + @"int foo(int x) +{ + while (1) + { + if (x) break; + } + // no return +}", "Not all control flow paths in function foo return a value."); + +[Fact, NoVerify] +public void DoWhileWithBreakAndReturnAfter() => ShouldCompile( + @"int foo(int x) +{ + int i = 0; + do + { + if (i == 5) break; + i++; + } while (i < x); + return i; +}"); + +[Fact, NoVerify] +public void DoWhileWithBreakInsideIfWithoutReturnAfter() => DoesNotCompile( + @"int foo(int x) +{ + int i = 0; + do + { + if (i == 5) break; + i++; + } while (i < x); + // no return +}", "Not all control flow paths in function foo return a value."); + +[Fact, NoVerify] +public void DoWhileWithReturnInBodyOnly() => ShouldCompile( + @"int foo(int x) +{ + do { return 1; } while (x); +}"); + +[Fact, NoVerify] +public void IfInsideWhileWithReturnOnlyInIfBranch() => ShouldCompile( + @"int foo(int x) +{ + while (x) + { + if (x > 0) return 1; + x--; + } + return 0; +}"); + +[Fact, NoVerify] +public void IfInsideWhileWithMissingReturnInElseAndNoReturnAfterLoop() => DoesNotCompile( + @"int foo(int x) +{ + while (x) + { + if (x > 0) return 1; + // else does nothing, infinite loop if condition false + } +}", "Not all control flow paths in function foo return a value."); + +[Fact, NoVerify] +public void NestedLoopsWithReturnInInnerLoop() => ShouldCompile( + @"int foo() +{ + int i = 0; + while (i < 10) + { + int j = 0; + while (j < 10) + { + if (i + j > 5) return i + j; + j++; + } + i++; + } + return -1; +}"); + +[Fact, NoVerify] +public void NestedLoopsMissingReturnAfterOuterLoop() => DoesNotCompile( + @"int foo() +{ + int i = 0; + while (i < 10) + { + int j = 0; + while (j < 10) + { + if (i + j > 5) return i + j; + j++; + } + i++; + } + // no return +}", "Not all control flow paths in function foo return a value."); + +[Fact, NoVerify] +public void WhileNeverExecutesWithReturnAfter() => ShouldCompile( + @"int foo(int x) +{ + while (x) { return 1; } + return 0; +}"); + +[Fact, NoVerify] +public void WhileNeverExecutesWithoutReturnAfter() => DoesNotCompile( + @"int foo(int x) +{ + while (x) { return 1; } + // no return after +}", "Not all control flow paths in function foo return a value."); + +[Fact, NoVerify] +public void FunctionWithGotoAndReturn() => ShouldCompile( + @"int foo(int x) +{ + if (x) goto a; + return 0; +a: + return 1; +}"); + +[Fact, NoVerify] +public void FunctionWithGotoMissingReturn() => DoesNotCompile( + @"int foo(int x) +{ + if (x) goto a; + goto b; // a way without return +a: + return 1; +b: + ; // empty +}", "Not all control flow paths in function foo return a value."); + +[Fact, NoVerify] +public void FunctionWithGotoCycleAndReturn() => ShouldCompile( + @"int foo(int x) +{ +a: + if (x) goto a; + return 0; +}"); + +[Fact, NoVerify] +public void FunctionWithGotoCycleNoReturn() => DoesNotCompile( + @"int foo(int x) +{ +a: + if (x) goto a; + // no return +}", "Not all control flow paths in function foo return a value."); + +[Fact, NoVerify] +public void FunctionWithNestedConstConditions() => ShouldCompile( + @"int foo() +{ + if (1) + { + if (0) return 1; + else return 2; + } + else { }; // unreachable +}"); + +[Fact, NoVerify] +public void FunctionWithSwitchLikeGoto() => ShouldCompile( + @"int foo(int x) +{ + if (x) goto case1; + else goto case2; +case1: + return 1; +case2: + return 2; +}"); + +[Fact, NoVerify] +public void FunctionWithSwitchLikeGotoMissingReturn() => DoesNotCompile( + @"int foo(int x) +{ + if (x) goto case1; + else goto case2; +case1: + return 1; +case2: + ; // empty +}", "Not all control flow paths in function foo return a value."); + + +[Fact, NoVerify] +public void FunctionWithComplexLoopAndContinueNoReturnAfterLoop() => DoesNotCompile( + @"int foo(int x) +{ + int i = 0; + while (i < x) + { + i++; + if (i == 5) continue; + return i; + } +}", "Not all control flow paths in function foo return a value."); + +[Fact, NoVerify] +public void FunctionWithReturnOnlyInLoopButLoopMayNotExecute() => DoesNotCompile( + @"int foo(int x) +{ + while (x) + { + return 1; + } + // return is missing +}", "Not all control flow paths in function foo return a value."); + +} diff --git a/Cesium.CodeGen.Tests/verified/CodeGenContinueStatementTests.ContinueInDoWhile.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenContinueStatementTests.ContinueInDoWhile.verified.txt index 6928b7ee..a54e38b6 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenContinueStatementTests.ContinueInDoWhile.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenContinueStatementTests.ContinueInDoWhile.verified.txt @@ -3,27 +3,24 @@ System.Int32 ::main() System.Int32 V_0 IL_0000: ldc.i4.0 IL_0001: stloc.0 - IL_0002: br IL_0012 - IL_0007: nop - IL_0008: ldloc.0 - IL_0009: ldc.i4.s 10 - IL_000b: clt - IL_000d: brfalse IL_002b - IL_0012: nop - IL_0013: ldloc.0 - IL_0014: ldc.i4.1 - IL_0015: add - IL_0016: stloc.0 - IL_0017: ldloc.0 - IL_0018: ldc.i4.0 - IL_0019: ceq - IL_001b: brfalse IL_0025 - IL_0020: br IL_0012 - IL_0025: nop - IL_0026: br IL_0007 - IL_002b: nop - IL_002c: ldc.i4.0 - IL_002d: ret + IL_0002: nop + IL_0003: ldloc.0 + IL_0004: ldc.i4.1 + IL_0005: add + IL_0006: stloc.0 + IL_0007: ldloc.0 + IL_0008: ldc.i4.0 + IL_0009: ceq + IL_000b: brfalse IL_0015 + IL_0010: br IL_0002 + IL_0015: nop + IL_0016: ldloc.0 + IL_0017: ldc.i4.s 10 + IL_0019: clt + IL_001b: brtrue IL_0002 + IL_0020: nop + IL_0021: ldc.i4.0 + IL_0022: ret System.Int32 ::() Locals: diff --git a/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfElseReturnTest.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfElseReturnTest.verified.txt index 9344c8a6..98e343a6 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfElseReturnTest.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfElseReturnTest.verified.txt @@ -1,11 +1,6 @@ System.Int32 ::main() - IL_0000: ldc.i4.1 - IL_0001: brfalse IL_0008 - IL_0006: ldc.i4.0 - IL_0007: ret - IL_0008: nop - IL_0009: ldc.i4.1 - IL_000a: ret + IL_0000: ldc.i4.0 + IL_0001: ret System.Int32 ::() Locals: diff --git a/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfElseTest.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfElseTest.verified.txt index fa827e36..de8a4d2d 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfElseTest.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfElseTest.verified.txt @@ -4,16 +4,11 @@ System.Int32 ::main() IL_0000: ldc.i4.0 IL_0001: stloc.0 IL_0002: ldc.i4.1 - IL_0003: brfalse IL_000f - IL_0008: ldc.i4.1 - IL_0009: stloc.0 - IL_000a: br IL_0012 - IL_000f: nop - IL_0010: ldc.i4.2 - IL_0011: stloc.0 - IL_0012: nop - IL_0013: ldc.i4.0 - IL_0014: ret + IL_0003: stloc.0 + IL_0004: br IL_0009 + IL_0009: nop + IL_000a: ldc.i4.0 + IL_000b: ret System.Int32 ::() Locals: diff --git a/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfElseWithNestedDeclarationAndVoidReturnTest.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfElseWithNestedDeclarationAndVoidReturnTest.verified.txt index 63fa9f00..1670b940 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfElseWithNestedDeclarationAndVoidReturnTest.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfElseWithNestedDeclarationAndVoidReturnTest.verified.txt @@ -1,14 +1,8 @@ System.Void ::test() Locals: System.Int32 V_0 - System.Int32 V_1 IL_0000: ldc.i4.1 - IL_0001: brfalse IL_000d - IL_0006: ldc.i4.1 - IL_0007: stloc.0 - IL_0008: br IL_0010 - IL_000d: nop - IL_000e: ldc.i4.2 - IL_000f: stloc.1 - IL_0010: nop - IL_0011: ret + IL_0001: stloc.0 + IL_0002: br IL_0007 + IL_0007: nop + IL_0008: ret diff --git a/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfElseWithNestedDeclarationTest.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfElseWithNestedDeclarationTest.verified.txt index 8cec1ffa..3b77aa77 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfElseWithNestedDeclarationTest.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfElseWithNestedDeclarationTest.verified.txt @@ -1,18 +1,12 @@ System.Int32 ::main() Locals: System.Int32 V_0 - System.Int32 V_1 IL_0000: ldc.i4.1 - IL_0001: brfalse IL_000d - IL_0006: ldc.i4.1 - IL_0007: stloc.0 - IL_0008: br IL_0010 - IL_000d: nop - IL_000e: ldc.i4.2 - IL_000f: stloc.1 - IL_0010: nop - IL_0011: ldc.i4.0 - IL_0012: ret + IL_0001: stloc.0 + IL_0002: br IL_0007 + IL_0007: nop + IL_0008: ldc.i4.0 + IL_0009: ret System.Int32 ::() Locals: diff --git a/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfImplicitElseTest.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfImplicitElseTest.verified.txt index 9344c8a6..98e343a6 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfImplicitElseTest.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfImplicitElseTest.verified.txt @@ -1,11 +1,6 @@ System.Int32 ::main() - IL_0000: ldc.i4.1 - IL_0001: brfalse IL_0008 - IL_0006: ldc.i4.0 - IL_0007: ret - IL_0008: nop - IL_0009: ldc.i4.1 - IL_000a: ret + IL_0000: ldc.i4.0 + IL_0001: ret System.Int32 ::() Locals: diff --git a/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfNegationTest.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfNegationTest.verified.txt index ed785883..7ee3c9c3 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfNegationTest.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfNegationTest.verified.txt @@ -3,15 +3,10 @@ System.Int32 ::main() System.Int32 V_0 IL_0000: ldc.i4.0 IL_0001: stloc.0 - IL_0002: ldc.i4.1 - IL_0003: ldc.i4.0 - IL_0004: ceq - IL_0006: brfalse IL_000d - IL_000b: ldc.i4.1 - IL_000c: stloc.0 - IL_000d: nop - IL_000e: ldc.i4.0 - IL_000f: ret + IL_0002: br IL_0007 + IL_0007: nop + IL_0008: ldc.i4.0 + IL_0009: ret System.Int32 ::() Locals: diff --git a/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfWithVoidReturnTest.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfWithVoidReturnTest.verified.txt index 6da2d9cb..79c77434 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfWithVoidReturnTest.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenIfTests.IfWithVoidReturnTest.verified.txt @@ -2,8 +2,6 @@ System.Void ::test() Locals: System.Int32 V_0 IL_0000: ldc.i4.1 - IL_0001: brfalse IL_0008 - IL_0006: ldc.i4.1 - IL_0007: stloc.0 - IL_0008: nop - IL_0009: ret + IL_0001: stloc.0 + IL_0002: nop + IL_0003: ret diff --git a/Cesium.CodeGen.Tests/verified/CodeGenIfTests.SimpleIfTest.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenIfTests.SimpleIfTest.verified.txt index ff7f024b..98e343a6 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenIfTests.SimpleIfTest.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenIfTests.SimpleIfTest.verified.txt @@ -1,11 +1,6 @@ System.Int32 ::main() - IL_0000: ldc.i4.1 - IL_0001: brfalse IL_0008 - IL_0006: ldc.i4.0 - IL_0007: ret - IL_0008: nop - IL_0009: ldc.i4.0 - IL_000a: ret + IL_0000: ldc.i4.0 + IL_0001: ret System.Int32 ::() Locals: diff --git a/Cesium.CodeGen.Tests/verified/CodeGenMethodTests.ImplicitReturnAllowedNonMain.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenMethodTests.ImplicitReturnAllowedNonMain.verified.txt deleted file mode 100644 index 2fa41cb2..00000000 --- a/Cesium.CodeGen.Tests/verified/CodeGenMethodTests.ImplicitReturnAllowedNonMain.verified.txt +++ /dev/null @@ -1,3 +0,0 @@ -System.Int32 ::foo() - IL_0000: ldc.i4.0 - IL_0001: ret diff --git a/Cesium.CodeGen.Tests/verified/CodeGenWhileTests.DoWhile.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenWhileTests.DoWhile.verified.txt index 06876c75..26c616be 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenWhileTests.DoWhile.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenWhileTests.DoWhile.verified.txt @@ -3,21 +3,18 @@ System.Int32 ::main() System.Int32 V_0 IL_0000: ldc.i4.0 IL_0001: stloc.0 - IL_0002: br IL_0012 - IL_0007: nop - IL_0008: ldloc.0 - IL_0009: ldc.i4.s 10 - IL_000b: clt - IL_000d: brfalse IL_001c - IL_0012: nop - IL_0013: ldloc.0 - IL_0014: ldc.i4.1 - IL_0015: add - IL_0016: stloc.0 - IL_0017: br IL_0007 - IL_001c: nop - IL_001d: ldc.i4.0 - IL_001e: ret + IL_0002: nop + IL_0003: ldloc.0 + IL_0004: ldc.i4.1 + IL_0005: add + IL_0006: stloc.0 + IL_0007: ldloc.0 + IL_0008: ldc.i4.s 10 + IL_000a: clt + IL_000c: brtrue IL_0002 + IL_0011: nop + IL_0012: ldc.i4.0 + IL_0013: ret System.Int32 ::() Locals: diff --git a/Cesium.CodeGen.Tests/verified/CodeGenWhileTests.InfinityDoWhile.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenWhileTests.InfinityDoWhile.verified.txt new file mode 100644 index 00000000..239c2283 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/CodeGenWhileTests.InfinityDoWhile.verified.txt @@ -0,0 +1,25 @@ +System.Int32 ::main() + Locals: + System.Int32 V_0 + IL_0000: ldc.i4.0 + IL_0001: stloc.0 + IL_0002: nop + IL_0003: ldloc.0 + IL_0004: dup + IL_0005: ldc.i4.1 + IL_0006: add + IL_0007: stloc.0 + IL_0008: pop + IL_0009: br IL_0002 + IL_000e: ldc.i4.0 + IL_000f: ret + +System.Int32 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::main() + IL_0005: stloc.s V_0 + IL_0007: ldloc.s V_0 + IL_0009: call System.Void Cesium.Runtime.RuntimeHelpers::Exit(System.Int32) + IL_000e: ldloc.s V_0 + IL_0010: ret diff --git a/Cesium.CodeGen.Tests/verified/CodeGenWhileTests.InfinityWhile.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenWhileTests.InfinityWhile.verified.txt new file mode 100644 index 00000000..239c2283 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/CodeGenWhileTests.InfinityWhile.verified.txt @@ -0,0 +1,25 @@ +System.Int32 ::main() + Locals: + System.Int32 V_0 + IL_0000: ldc.i4.0 + IL_0001: stloc.0 + IL_0002: nop + IL_0003: ldloc.0 + IL_0004: dup + IL_0005: ldc.i4.1 + IL_0006: add + IL_0007: stloc.0 + IL_0008: pop + IL_0009: br IL_0002 + IL_000e: ldc.i4.0 + IL_000f: ret + +System.Int32 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::main() + IL_0005: stloc.s V_0 + IL_0007: ldloc.s V_0 + IL_0009: call System.Void Cesium.Runtime.RuntimeHelpers::Exit(System.Int32) + IL_000e: ldloc.s V_0 + IL_0010: ret diff --git a/Cesium.CodeGen.Tests/verified/CodeGenWhileTests.NegativeDoWhile.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenWhileTests.NegativeDoWhile.verified.txt new file mode 100644 index 00000000..473d282b --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/CodeGenWhileTests.NegativeDoWhile.verified.txt @@ -0,0 +1,25 @@ +System.Int32 ::main() + Locals: + System.Int32 V_0 + IL_0000: ldc.i4.0 + IL_0001: stloc.0 + IL_0002: nop + IL_0003: ldloc.0 + IL_0004: dup + IL_0005: ldc.i4.1 + IL_0006: add + IL_0007: stloc.0 + IL_0008: pop + IL_0009: nop + IL_000a: ldc.i4.0 + IL_000b: ret + +System.Int32 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::main() + IL_0005: stloc.s V_0 + IL_0007: ldloc.s V_0 + IL_0009: call System.Void Cesium.Runtime.RuntimeHelpers::Exit(System.Int32) + IL_000e: ldloc.s V_0 + IL_0010: ret diff --git a/Cesium.CodeGen.Tests/verified/CodeGenWhileTests.NegativeWhile.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenWhileTests.NegativeWhile.verified.txt new file mode 100644 index 00000000..b0ce9a2b --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/CodeGenWhileTests.NegativeWhile.verified.txt @@ -0,0 +1,20 @@ +System.Int32 ::main() + Locals: + System.Int32 V_0 + IL_0000: ldc.i4.0 + IL_0001: stloc.0 + IL_0002: nop + IL_0003: br IL_0008 + IL_0008: nop + IL_0009: ldc.i4.0 + IL_000a: ret + +System.Int32 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::main() + IL_0005: stloc.s V_0 + IL_0007: ldloc.s V_0 + IL_0009: call System.Void Cesium.Runtime.RuntimeHelpers::Exit(System.Int32) + IL_000e: ldloc.s V_0 + IL_0010: ret diff --git a/Cesium.CodeGen/ConstantEvaluator.cs b/Cesium.CodeGen/ConstantEvaluator.cs index cd592972..3814fdea 100644 --- a/Cesium.CodeGen/ConstantEvaluator.cs +++ b/Cesium.CodeGen/ConstantEvaluator.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using Cesium.CodeGen.Contexts; +using Cesium.CodeGen.Ir.BlockItems; using Cesium.CodeGen.Ir.Expressions; using Cesium.CodeGen.Ir.Expressions.BinaryOperators; using Cesium.CodeGen.Ir.Expressions.Constants; @@ -27,12 +28,14 @@ public static IConstant GetConstantValue(IExpression expression, IDeclarationSco public static (string? ErrorMessage, IConstant? Constant) TryGetConstantValue(IExpression expression, IDeclarationScope? scope) { - switch (expression) + try { - case ConstantLiteralExpression literal: - return (null, literal.Constant); + switch (expression) + { + case ConstantLiteralExpression literal: + return (null, literal.Constant); - case UnaryOperatorExpression unOp: + case UnaryOperatorExpression unOp: { var constant = GetConstantValue(unOp.Target, scope); @@ -44,12 +47,13 @@ public static (string? ErrorMessage, IConstant? Constant) TryGetConstantValue(IE UnaryOperator.Negation => (null, new IntegerConstant(-constInt.Value)), UnaryOperator.BitwiseNot => (null, new IntegerConstant(~constInt.Value)), UnaryOperator.LogicalNot => (null, new IntegerConstant(constInt.Value != 0 ? 0 : 1)), - UnaryOperator.AddressOf or UnaryOperator.Indirection => ($"Operator {unOp.Operator} is not compile-time evaluable", null), + UnaryOperator.AddressOf or UnaryOperator.Indirection => ( + $"Operator {unOp.Operator} is not compile-time evaluable", null), _ => throw new ArgumentOutOfRangeException($"Invalid unary operator {unOp.Operator}."), }; } - case BinaryOperatorExpression binOp: + case BinaryOperatorExpression binOp: { var leftConstant = GetConstantValue(binOp.Left, scope); var rightConstant = GetConstantValue(binOp.Right, scope); @@ -65,25 +69,33 @@ public static (string? ErrorMessage, IConstant? Constant) TryGetConstantValue(IE BinaryOperator.Multiply => (null, new IntegerConstant(leftInt.Value * rightInt.Value)), BinaryOperator.Divide => (null, new IntegerConstant(leftInt.Value / rightInt.Value)), BinaryOperator.Remainder => (null, new IntegerConstant(leftInt.Value % rightInt.Value)), - BinaryOperator.BitwiseLeftShift => (null, new IntegerConstant(leftInt.Value << (int)rightInt.Value)), - BinaryOperator.BitwiseRightShift => (null, new IntegerConstant(leftInt.Value >> (int)rightInt.Value)), + BinaryOperator.BitwiseLeftShift => (null, + new IntegerConstant(leftInt.Value << (int)rightInt.Value)), + BinaryOperator.BitwiseRightShift => (null, + new IntegerConstant(leftInt.Value >> (int)rightInt.Value)), BinaryOperator.BitwiseOr => (null, new IntegerConstant(leftInt.Value | rightInt.Value)), BinaryOperator.BitwiseAnd => (null, new IntegerConstant(leftInt.Value & rightInt.Value)), BinaryOperator.BitwiseXor => (null, new IntegerConstant(leftInt.Value ^ rightInt.Value)), // boolean constants are needed here - BinaryOperator.GreaterThan => (null, new IntegerConstant(leftInt.Value > rightInt.Value ? 1 : 0)), + BinaryOperator.GreaterThan => (null, + new IntegerConstant(leftInt.Value > rightInt.Value ? 1 : 0)), BinaryOperator.LessThan => (null, new IntegerConstant(leftInt.Value < rightInt.Value ? 1 : 0)), - BinaryOperator.GreaterThanOrEqualTo => (null, new IntegerConstant(leftInt.Value >= rightInt.Value ? 1 : 0)), - BinaryOperator.LessThanOrEqualTo => (null, new IntegerConstant(leftInt.Value <= rightInt.Value ? 1 : 0)), + BinaryOperator.GreaterThanOrEqualTo => (null, + new IntegerConstant(leftInt.Value >= rightInt.Value ? 1 : 0)), + BinaryOperator.LessThanOrEqualTo => (null, + new IntegerConstant(leftInt.Value <= rightInt.Value ? 1 : 0)), BinaryOperator.EqualTo => (null, new IntegerConstant(leftInt.Value == rightInt.Value ? 1 : 0)), - BinaryOperator.NotEqualTo => (null, new IntegerConstant(leftInt.Value != rightInt.Value ? 1 : 0)), - BinaryOperator.LogicalAnd => (null, new IntegerConstant((leftInt.Value != 0) && (rightInt.Value != 0) ? 1 : 0)), - BinaryOperator.LogicalOr => (null, new IntegerConstant((leftInt.Value != 0) || (rightInt.Value != 0) ? 1 : 0)), + BinaryOperator.NotEqualTo => (null, + new IntegerConstant(leftInt.Value != rightInt.Value ? 1 : 0)), + BinaryOperator.LogicalAnd => (null, + new IntegerConstant((leftInt.Value != 0) && (rightInt.Value != 0) ? 1 : 0)), + BinaryOperator.LogicalOr => (null, + new IntegerConstant((leftInt.Value != 0) || (rightInt.Value != 0) ? 1 : 0)), _ => throw new ArgumentOutOfRangeException($"Invalid binary operator {binOp.Operator}"), }; } - case IdentifierExpression identifierExpression: + case IdentifierExpression identifierExpression: { if (scope != null) { @@ -98,8 +110,34 @@ public static (string? ErrorMessage, IConstant? Constant) TryGetConstantValue(IE return ($"Expression {expression} cannot be evaluated as constant expression.", null); } - default: - return ($"Expression {expression} cannot be evaluated as constant expression.", null); + default: + return ($"Expression {expression} cannot be evaluated as constant expression.", null); + } + } + catch (Exception e) + { + return (e.Message, null); } } + + public static ConditionalValue EvaluateCondition(IExpression condition) + { + var (results, val) = TryGetConstantValue(condition, null); + + if (results != null || val == null) + return ConditionalValue.Unknown; + + var isEvaluated = true; + + return val switch + { + IntegerConstant integer => integer.Value == 0, + FloatingPointConstant floatingPoint => floatingPoint.Value == 0, + CharConstant charVal => charVal.Value == 0, + StringConstant => false, + // TODO: Handle NULL literals + _ => isEvaluated = false + } ? ConditionalValue.ConstantlyFalse + : isEvaluated ? ConditionalValue.ConstantlyTrue : ConditionalValue.Unknown; + } } diff --git a/Cesium.CodeGen/Ir/BlockItems/ConditionalGotoStatement.cs b/Cesium.CodeGen/Ir/BlockItems/ConditionalGotoStatement.cs index 7555816a..e164a50c 100644 --- a/Cesium.CodeGen/Ir/BlockItems/ConditionalGotoStatement.cs +++ b/Cesium.CodeGen/Ir/BlockItems/ConditionalGotoStatement.cs @@ -8,6 +8,15 @@ namespace Cesium.CodeGen.Ir.BlockItems; internal record ConditionalGotoStatement(IExpression Condition, ConditionalJumpType JumpType, string Identifier) : IBlockItem { + public ConditionalValue Value { get; init; } = ConstantEvaluator.EvaluateCondition(Condition); + + /// Returns JumpType if it's necessary otherwise returns null + public ConditionalJumpType? EffectiveJumpType => Value switch + { + ConditionalValue.ConstantlyTrue or + ConditionalValue.ConstantlyFalse => null, + _ => JumpType + }; } internal enum ConditionalJumpType @@ -16,4 +25,11 @@ internal enum ConditionalJumpType False, } +internal enum ConditionalValue +{ + ConstantlyFalse, + ConstantlyTrue, + Unknown +} + internal record LabeledNopStatement(string Label) : IBlockItem; diff --git a/Cesium.CodeGen/Ir/ControlFlow/ControlFlowChecker.cs b/Cesium.CodeGen/Ir/ControlFlow/ControlFlowChecker.cs index 0c02f6d2..ca2221aa 100644 --- a/Cesium.CodeGen/Ir/ControlFlow/ControlFlowChecker.cs +++ b/Cesium.CodeGen/Ir/ControlFlow/ControlFlowChecker.cs @@ -5,6 +5,7 @@ using Cesium.CodeGen.Contexts; using Cesium.CodeGen.Ir.BlockItems; using Cesium.CodeGen.Ir.Expressions; +using Cesium.CodeGen.Ir.Expressions.BinaryOperators; using Cesium.CodeGen.Ir.Expressions.Constants; using Cesium.CodeGen.Ir.Types; using Cesium.Core; @@ -37,11 +38,15 @@ bool isMain var lastBlock = flowGraph.BasicBlocks.Last(); if (lastBlock.Statements.Count == 0 || lastBlock.Statements.Last() is not ReturnStatement and not GoToStatement) { - // [TODO #928]: More advanced control flow analysis to determine if all paths return a value. - //if (isReturnRequired) - //{ - // throw new CompilationException($"Not all control flow paths in function {scope.Method.Name} return a value."); - //} + if (isReturnRequired && + flowGraph.BasicBlocks.Exists(b => + !b.Statements.Exists(t => t is ReturnStatement) + && b.Targets.Count == 0 + ) + ) + { + throw new CompilationException($"Not all control flow paths in function {scope.Method.Name} return a value."); + } var retn = new ReturnStatement(!isVoidFn ? new ConstantLiteralExpression(new IntegerConstant(0)) : null); lastBlock.Statements.Add(retn); @@ -95,13 +100,36 @@ public FlowGraph(CompoundStatement compoundStatement) } else if (blockItem is ConditionalGotoStatement conditional) { - currentBlock.Statements.Add(blockItem); - BasicBlocks.Add(currentBlock); - var conditionalBlock = Lookup(conditional.Identifier); - currentBlock.Targets.Add(conditionalBlock); - var newBlock = new BasicBlock(); - currentBlock.Targets.Add(newBlock); - currentBlock = newBlock; + bool alwaysTaken = + conditional is { Value: ConditionalValue.ConstantlyTrue, JumpType: ConditionalJumpType.True } + or { Value: ConditionalValue.ConstantlyFalse, JumpType: ConditionalJumpType.False }; + bool neverTaken = + conditional is { Value: ConditionalValue.ConstantlyTrue, JumpType: ConditionalJumpType.False } + or { Value: ConditionalValue.ConstantlyFalse, JumpType: ConditionalJumpType.True }; + + if (alwaysTaken) + { + BasicBlocks.Add(currentBlock); + var gotoStmnt = new GoToStatement(conditional.Identifier); + currentBlock.Statements.Add(gotoStmnt); + var gotoBlock = Lookup(gotoStmnt.Identifier); + currentBlock.Targets.Add(gotoBlock); + currentBlock = new BasicBlock(); + } + else if (neverTaken) + { + // Just ignore and pursue the current block + } + else // ConditionalValue.Unknown + { + BasicBlocks.Add(currentBlock); + currentBlock.Statements.Add(conditional); + var conditionalBlock = Lookup(conditional.Identifier); + currentBlock.Targets.Add(conditionalBlock); + var nextBlock = new BasicBlock(); + currentBlock.Targets.Add(nextBlock); + currentBlock = nextBlock; + } } else if (blockItem is LabeledNopStatement labeled) { @@ -111,11 +139,11 @@ public FlowGraph(CompoundStatement compoundStatement) { BasicBlocks.Add(currentBlock); } - BasicBlock nextBlock = new(); + BasicBlock newBlock = new(); //if (currentBlock.Statements.LastOrDefault() // is not GoToStatement and not ReturnStatement) //{ - // currentBlock.Targets.Add(nextBlock); + // currentBlock.Targets.Add(newBlock); //} if (currentBlock.Statements.Count == 0) @@ -130,10 +158,10 @@ public FlowGraph(CompoundStatement compoundStatement) if (currentBlock.Statements.LastOrDefault() is not GoToStatement and not ReturnStatement) { - currentBlock.Targets.Add(nextBlock); + currentBlock.Targets.Add(newBlock); } - currentBlock = nextBlock; + currentBlock = newBlock; } currentBlock.Statements.Add(labeled); @@ -142,17 +170,17 @@ public FlowGraph(CompoundStatement compoundStatement) { if (labeledBlocks.TryGetValue(labeled.Label, out var existingBlock)) { - nextBlock = existingBlock; - currentBlock.Targets.Add(nextBlock); + newBlock = existingBlock; + currentBlock.Targets.Add(newBlock); } else { - nextBlock = new(); - currentBlock.Targets.Add(nextBlock); - labeledBlocks.Add(labeled.Label, nextBlock); + newBlock = new(); + currentBlock.Targets.Add(newBlock); + labeledBlocks.Add(labeled.Label, newBlock); } - nextBlock.Statements.Add(labeled); - currentBlock = nextBlock; + newBlock.Statements.Add(labeled); + currentBlock = newBlock; } } else diff --git a/Cesium.CodeGen/Ir/Emitting/BlockItemEmitting.cs b/Cesium.CodeGen/Ir/Emitting/BlockItemEmitting.cs index b4579226..cb7ddf24 100644 --- a/Cesium.CodeGen/Ir/Emitting/BlockItemEmitting.cs +++ b/Cesium.CodeGen/Ir/Emitting/BlockItemEmitting.cs @@ -102,9 +102,12 @@ public static void EmitCode(IEmitScope scope, IBlockItem blockItem) } case ConditionalGotoStatement s: { + var jumpType = s.EffectiveJumpType; + if (jumpType is null) return; + s.Condition.EmitTo(scope); var instruction = scope.ResolveLabel(s.Identifier); - var opcode = s.JumpType == ConditionalJumpType.True ? OpCodes.Brtrue : OpCodes.Brfalse; + var opcode = jumpType == ConditionalJumpType.True ? OpCodes.Brtrue : OpCodes.Brfalse; scope.Method.Body.Instructions.Add(Instruction.Create(opcode, instruction)); return; } diff --git a/Cesium.CodeGen/Ir/Lowering/BlockItemLowering.cs b/Cesium.CodeGen/Ir/Lowering/BlockItemLowering.cs index 5813adc5..cf727c20 100644 --- a/Cesium.CodeGen/Ir/Lowering/BlockItemLowering.cs +++ b/Cesium.CodeGen/Ir/Lowering/BlockItemLowering.cs @@ -63,6 +63,24 @@ private static IBlockItem MakeLoop( return Lower(scope, new CompoundStatement(stmts, scope)); } + private static IBlockItem MakeDoWhileLoop( + BlockScope scope, + IExpression testExpression, + IBlockItem body, + string breakLabel, + string loopBodyLabel + ) + { + var stmts = new List + { + new LabelStatement(loopBodyLabel, body), + new ConditionalGotoStatement(testExpression, ConditionalJumpType.True, loopBodyLabel), + new LabelStatement(breakLabel, new ExpressionStatement(null)) + }; + + return Lower(scope, new CompoundStatement(stmts, scope)); + } + public static CompoundStatement LowerBody(FunctionScope scope, IBlockItem blockItem) { CompoundStatement compoundStatement = (CompoundStatement)Lower(scope, blockItem); @@ -231,7 +249,7 @@ void ProcessCaseStatement(CaseStatement caseStatement, BlockScope switchBlock, s } } else - { + { if (cliImportMemberName != null) throw new CompilationException( $"Local declaration with a CLI import member name {cliImportMemberName} isn't supported."); @@ -308,16 +326,12 @@ void ProcessCaseStatement(CaseStatement caseStatement, BlockScope switchBlock, s var loopScope = new BlockScope((IEmitScope)scope, breakLabel, continueLabel); - return MakeLoop( + return MakeDoWhileLoop( loopScope, - new GoToStatement(continueLabel), s.TestExpression, - null, s.Body, breakLabel, - null, - continueLabel, - null + continueLabel ); } case ExpressionStatement s: diff --git a/Cesium.IntegrationTests/functions.c b/Cesium.IntegrationTests/functions.c index 5cf3d40a..ca4eeca7 100644 --- a/Cesium.IntegrationTests/functions.c +++ b/Cesium.IntegrationTests/functions.c @@ -47,11 +47,6 @@ void forward_declaration_void_2() // Do nothing here. } -int missing_return() -{ - // Do nothing here. -} - int main(void) { return foo();