From fc62651d55d955e0865d4d530f02e9b701f8dc56 Mon Sep 17 00:00:00 2001 From: Patrick Stevens <3138005+Smaug123@users.noreply.github.com> Date: Sun, 31 Aug 2025 08:32:47 +0100 Subject: [PATCH] Add some more tests (#128) --- .../sourcesPure/ComparisonOperations.cs | 255 ++++++++++++++++++ .../sourcesPure/StackOperations.cs | 184 +++++++++++++ WoofWare.PawPrint/BinaryArithmetic.fs | 21 ++ WoofWare.PawPrint/Intrinsics.fs | 47 ++-- WoofWare.PawPrint/NullaryIlOp.fs | 69 ++++- 5 files changed, 553 insertions(+), 23 deletions(-) create mode 100644 WoofWare.PawPrint.Test/sourcesPure/ComparisonOperations.cs create mode 100644 WoofWare.PawPrint.Test/sourcesPure/StackOperations.cs diff --git a/WoofWare.PawPrint.Test/sourcesPure/ComparisonOperations.cs b/WoofWare.PawPrint.Test/sourcesPure/ComparisonOperations.cs new file mode 100644 index 0000000..f2b8a1d --- /dev/null +++ b/WoofWare.PawPrint.Test/sourcesPure/ComparisonOperations.cs @@ -0,0 +1,255 @@ +public class TestComparisonOperations +{ + // Test Ceq: Compare equal + public static int TestCompareEqual() + { + // Integer equality + if ((5 == 5) != true) return 1; + if ((5 == 6) != false) return 2; + if ((int.MaxValue == int.MaxValue) != true) return 3; + if ((int.MinValue == int.MaxValue) != false) return 4; + + // Negative numbers + if ((-1 == -1) != true) return 5; + if ((-5 == 5) != false) return 6; + + // Long equality + if ((100L == 100L) != true) return 7; + if ((100L == 101L) != false) return 8; + + // Mixed sizes (after promotion) + int i = 42; + long l = 42L; + if ((l == (long)i) != true) return 9; + + // Zero comparisons + if ((0 == 0) != true) return 10; + if ((0 == 1) != false) return 11; + + return 0; + } + + // Test Cgt: Compare greater than (signed) + public static int TestCompareGreaterThan() + { + // Positive integers + if ((10 > 5) != true) return 20; + if ((5 > 10) != false) return 21; + if ((5 > 5) != false) return 22; + + // Negative integers + if ((-5 > -10) != true) return 23; + if ((-10 > -5) != false) return 24; + if ((5 > -5) != true) return 25; + if ((-5 > 5) != false) return 26; + + // Boundary values + if ((int.MaxValue > int.MinValue) != true) return 27; + if ((int.MinValue > int.MaxValue) != false) return 28; + if ((int.MaxValue > (int.MaxValue - 1)) != true) return 29; + + // Zero comparisons + if ((1 > 0) != true) return 30; + if ((0 > 1) != false) return 31; + if ((-1 > 0) != false) return 32; + if ((0 > -1) != true) return 33; + + return 0; + } + + // Test Cgt_un: Compare greater than (unsigned) + public static int TestCompareGreaterThanUnsigned() + { + uint a = 10; + uint b = 5; + + // Basic unsigned comparison + if ((a > b) != true) return 40; + if ((b > a) != false) return 41; + if ((a > a) != false) return 42; + + // High bit set (would be negative if signed) + uint high = 0x80000000; + uint low = 0x7FFFFFFF; + if ((high > low) != true) return 43; // Unsigned: high > low + + // Maximum values + uint max = uint.MaxValue; + uint min = uint.MinValue; + if ((max > min) != true) return 44; + if ((min > max) != false) return 45; + + // Interpret negative as unsigned + uint negAsUint = unchecked((uint)-1); + uint one = 1; + if ((negAsUint > one) != true) return 46; // 0xFFFFFFFF > 1 + + return 0; + } + + // Test Clt: Compare less than (signed) + public static int TestCompareLessThan() + { + // Positive integers + if ((5 < 10) != true) return 50; + if ((10 < 5) != false) return 51; + if ((5 < 5) != false) return 52; + + // Negative integers + if ((-10 < -5) != true) return 53; + if ((-5 < -10) != false) return 54; + if ((-5 < 5) != true) return 55; + if ((5 < -5) != false) return 56; + + // Boundary values + if ((int.MinValue < int.MaxValue) != true) return 57; + if ((int.MaxValue < int.MinValue) != false) return 58; + + // Zero comparisons + if ((0 < 1) != true) return 59; + if ((1 < 0) != false) return 60; + if ((0 < -1) != false) return 61; + if ((-1 < 0) != true) return 62; + + return 0; + } + + // Test Clt_un: Compare less than (unsigned) + public static int TestCompareLessThanUnsigned() + { + uint a = 5; + uint b = 10; + + // Basic unsigned comparison + if ((a < b) != true) return 70; + if ((b < a) != false) return 71; + if ((a < a) != false) return 72; + + // High bit set + uint high = 0x80000000; + uint low = 0x7FFFFFFF; + if ((low < high) != true) return 73; // Unsigned: low < high + + // Boundary values + uint max = uint.MaxValue; + uint min = uint.MinValue; + if ((min < max) != true) return 74; + if ((max < min) != false) return 75; + + // Negative as unsigned + uint one = 1; + uint negAsUint = unchecked((uint)-1); + if ((one < negAsUint) != true) return 76; // 1 < 0xFFFFFFFF + + return 0; + } + + // Test comparison combinations + public static int TestComparisonCombinations() + { + int x = 10; + int y = 20; + int z = 10; + + // Equality chains + if ((x == z) != true) return 80; + if ((x == y) != false) return 81; + + // Inequality combinations + if ((x < y && y > x) != true) return 82; + if ((x < y && x == y) != false) return 83; + + // Transitive comparisons + if (x < y && y < 30) + { + if ((x < 30) != true) return 84; + } + else + { + return 85; + } + + return 0; + } + + // Test comparisons with different types + public static int TestMixedTypeComparisons() + { + // byte comparisons (unsigned by default) + byte b1 = 200; + byte b2 = 100; + if ((b1 > b2) != true) return 90; + + // sbyte comparisons (signed) + sbyte sb1 = -50; + sbyte sb2 = 50; + if ((sb1 < sb2) != true) return 91; + + // short comparisons + short s1 = -1000; + short s2 = 1000; + if ((s1 < s2) != true) return 92; + if ((s1 == s2) != false) return 93; + + // long comparisons + long l1 = long.MaxValue; + long l2 = long.MinValue; + if ((l1 > l2) != true) return 94; + + return 0; + } + + // Test null comparisons + public static int TestNullComparisons() + { + object obj1 = null; + object obj2 = null; + object obj3 = new object(); + + // Null equality + if ((obj1 == obj2) != true) return 100; + if ((obj1 == obj3) != false) return 101; + if ((obj3 == obj1) != false) return 102; + + // String null comparisons + string s1 = null; + string s2 = null; + string s3 = ""; + + if ((s1 == s2) != true) return 103; + if ((s1 == s3) != false) return 104; + + return 0; + } + + public static int Main(string[] argv) + { + int result; + + result = TestCompareEqual(); + if (result != 0) return 100 + result; + + result = TestCompareGreaterThan(); + if (result != 0) return 200 + result; + + result = TestCompareGreaterThanUnsigned(); + if (result != 0) return 300 + result; + + result = TestCompareLessThan(); + if (result != 0) return 400 + result; + + result = TestCompareLessThanUnsigned(); + if (result != 0) return 500 + result; + + result = TestComparisonCombinations(); + if (result != 0) return 600 + result; + + result = TestMixedTypeComparisons(); + if (result != 0) return 700 + result; + + result = TestNullComparisons(); + if (result != 0) return 800 + result; + + return 0; + } +} \ No newline at end of file diff --git a/WoofWare.PawPrint.Test/sourcesPure/StackOperations.cs b/WoofWare.PawPrint.Test/sourcesPure/StackOperations.cs new file mode 100644 index 0000000..3ad2f0e --- /dev/null +++ b/WoofWare.PawPrint.Test/sourcesPure/StackOperations.cs @@ -0,0 +1,184 @@ +public class TestStackOperations +{ + // Test LdArg0-3: Load method arguments + public static int TestLoadArguments(int arg0, int arg1, int arg2, int arg3) + { + // LdArg0 loads 'this' for instance methods or first arg for static + if (arg0 != 10) return 1; + + // LdArg1 loads second argument + if (arg1 != 20) return 2; + + // LdArg2 loads third argument + if (arg2 != 30) return 3; + + // LdArg3 loads fourth argument + if (arg3 != 40) return 4; + + return 0; + } + + // Test Ldloc_0-3 and Stloc_0-3: Load/store local variables + public static int TestLocalVariables() + { + int local0 = 100; + int local1 = 200; + int local2 = 300; + int local3 = 400; + + // Test loading locals + if (local0 != 100) return 10; + if (local1 != 200) return 11; + if (local2 != 300) return 12; + if (local3 != 400) return 13; + + // Test storing to locals + local0 = local1 + local2; // Stloc_0 + if (local0 != 500) return 14; + + local1 = local2 * 2; // Stloc_1 + if (local1 != 600) return 15; + + local2 = local3 - 100; // Stloc_2 + if (local2 != 300) return 16; + + local3 = local0 / 5; // Stloc_3 + if (local3 != 100) return 17; + + return 0; + } + + // Test Pop: Remove top stack value + public static int TestPop() + { + int value = 42; + + // Push value on stack then pop it + PushAndPop(value); + + // If we get here, pop worked + return 0; + } + + private static void PushAndPop(int value) + { + // The compiler will generate pop instructions + // for unused return values + GetValue(); + GetValue(); + } + + private static int GetValue() + { + return 123; + } + + // Test Dup: Duplicate top stack value + public static int TestDup() + { + int value = 50; + + // Dup is used when same value is needed twice + int result1 = value * value; // Compiler may use dup here + if (result1 != 2500) return 20; + + // More complex dup scenario + int x = 10; + int result2 = AddTwice(x); + if (result2 != 20) return 21; + + return 0; + } + + private static int AddTwice(int val) + { + // Compiler may generate dup to use val twice + return val + val; + } + + // Test Ret: Return from method + public static int TestReturn() + { + // Test void return + VoidReturn(); + + // Test value return + int result = ValueReturn(5); + if (result != 5) return 30; + + // Test early return + result = EarlyReturn(true); + if (result != 1) return 31; + + result = EarlyReturn(false); + if (result != 2) return 32; + + return 0; + } + + private static void VoidReturn() + { + // Ret with no value + return; + } + + private static int ValueReturn(int x) + { + // Ret with value + return x; + } + + private static int EarlyReturn(bool condition) + { + if (condition) + return 1; // Early ret + + return 2; // Normal ret + } + + // Test combinations of stack operations + public static int TestStackCombinations() + { + int a = 10, b = 20, c = 30; + + // Complex expression using multiple locals + int result = (a + b) * c - (b - a); + if (result != 890) return 40; + + // Nested method calls + result = Compute(a, Compute(b, c)); + if (result != 60) return 41; + + return 0; + } + + private static int Compute(int x, int y) + { + return x + y; + } + + public static int Main(string[] argv) + { + int result; + + result = TestLoadArguments(10, 20, 30, 40); + if (result != 0) return 100 + result; + + result = TestLocalVariables(); + if (result != 0) return 200 + result; + + result = TestPop(); + if (result != 0) return 300 + result; + + result = TestDup(); + if (result != 0) return 400 + result; + + result = TestReturn(); + if (result != 0) return 500 + result; + + result = TestStackCombinations(); + if (result != 0) return 600 + result; + + return 0; + } +} diff --git a/WoofWare.PawPrint/BinaryArithmetic.fs b/WoofWare.PawPrint/BinaryArithmetic.fs index d460c7b..5c183a5 100644 --- a/WoofWare.PawPrint/BinaryArithmetic.fs +++ b/WoofWare.PawPrint/BinaryArithmetic.fs @@ -69,6 +69,27 @@ module ArithmeticOperation = member _.Name = "add" } + let addOvf = + { new IArithmeticOperation with + member _.Int32Int32 a b = (# "add.ovf" a b : int32 #) + member _.Int64Int64 a b = (# "add.ovf" a b : int64 #) + member _.FloatFloat a b = (# "add.ovf" a b : float #) + member _.NativeIntNativeInt a b = (# "add.ovf" a b : nativeint #) + member _.Int32NativeInt a b = (# "add.ovf" a b : nativeint #) + member _.NativeIntInt32 a b = (# "add.ovf" a b : nativeint #) + + member _.ManagedPtrManagedPtr _ ptr1 ptr2 = + match ptr1, ptr2 with + | ManagedPointerSource.Null, _ -> Choice1Of2 ptr2 + | _, ManagedPointerSource.Null -> Choice1Of2 ptr1 + | _, _ -> failwith "refusing to add two managed pointers" + + member _.Int32ManagedPtr state val1 ptr2 = addInt32ManagedPtr state val1 ptr2 + member _.ManagedPtrInt32 state ptr1 val2 = addInt32ManagedPtr state val2 ptr1 + + member _.Name = "add.ovf" + } + let sub = { new IArithmeticOperation with member _.Int32Int32 a b = (# "sub" a b : int32 #) diff --git a/WoofWare.PawPrint/Intrinsics.fs b/WoofWare.PawPrint/Intrinsics.fs index 52b803d..10f7e4d 100644 --- a/WoofWare.PawPrint/Intrinsics.fs +++ b/WoofWare.PawPrint/Intrinsics.fs @@ -400,7 +400,8 @@ module Intrinsics = let arg1 = match arg1 with | EvalStackValue.ObjectRef h - | EvalStackValue.ManagedPointer (ManagedPointerSource.Heap h) -> h + | EvalStackValue.ManagedPointer (ManagedPointerSource.Heap h) -> Some h + | EvalStackValue.ManagedPointer ManagedPointerSource.Null -> None | EvalStackValue.Int32 _ | EvalStackValue.Int64 _ | EvalStackValue.Float _ -> failwith $"this isn't a string! {arg1}" @@ -411,32 +412,38 @@ module Intrinsics = let arg2 = match arg2 with | EvalStackValue.ObjectRef h - | EvalStackValue.ManagedPointer (ManagedPointerSource.Heap h) -> h + | EvalStackValue.ManagedPointer (ManagedPointerSource.Heap h) -> Some h + | EvalStackValue.ManagedPointer ManagedPointerSource.Null -> None | EvalStackValue.Int32 _ | EvalStackValue.Int64 _ | EvalStackValue.Float _ -> failwith $"this isn't a string! {arg2}" | _ -> failwith $"TODO: %O{arg2}" - if arg1 = arg2 then - state - |> IlMachineState.pushToEvalStack (CliType.ofBool true) currentThread - |> IlMachineState.advanceProgramCounter currentThread - |> Some - else + let areEqual = + match arg1, arg2 with + | None, None -> true + | Some _, None + | None, Some _ -> false + | Some arg1, Some arg2 -> + if arg1 = arg2 then + true + else - let arg1 = ManagedHeap.get arg1 state.ManagedHeap - let arg2 = ManagedHeap.get arg2 state.ManagedHeap + let arg1 = ManagedHeap.get arg1 state.ManagedHeap + let arg2 = ManagedHeap.get arg2 state.ManagedHeap - if - AllocatedNonArrayObject.DereferenceField "_firstChar" arg1 - <> AllocatedNonArrayObject.DereferenceField "_firstChar" arg2 - then - state - |> IlMachineState.pushToEvalStack (CliType.ofBool false) currentThread - |> IlMachineState.advanceProgramCounter currentThread - |> Some - else - failwith "TODO" + if + AllocatedNonArrayObject.DereferenceField "_firstChar" arg1 + <> AllocatedNonArrayObject.DereferenceField "_firstChar" arg2 + then + false + else + failwith "TODO" + + state + |> IlMachineState.pushToEvalStack (CliType.ofBool areEqual) currentThread + |> IlMachineState.advanceProgramCounter currentThread + |> Some | _ -> None | "System.Private.CoreLib", "Unsafe", "ReadUnaligned" -> let ptr, state = IlMachineState.popEvalStack currentThread state diff --git a/WoofWare.PawPrint/NullaryIlOp.fs b/WoofWare.PawPrint/NullaryIlOp.fs index 6361385..d00a83b 100644 --- a/WoofWare.PawPrint/NullaryIlOp.fs +++ b/WoofWare.PawPrint/NullaryIlOp.fs @@ -452,7 +452,25 @@ module NullaryIlOp = |> IlMachineState.advanceProgramCounter currentThread |> Tuple.withRight WhatWeDid.Executed |> ExecutionResult.Stepped - | Add_ovf -> failwith "TODO: Add_ovf unimplemented" + | Add_ovf -> + let val2, state = IlMachineState.popEvalStack currentThread state + let val1, state = IlMachineState.popEvalStack currentThread state + + let result = + try + BinaryArithmetic.execute ArithmeticOperation.addOvf state val1 val2 |> Ok + with :? OverflowException as e -> + Error e + + let state = + match result with + | Ok result -> state |> IlMachineState.pushToEvalStack' result currentThread + | Error excToThrow -> failwith "TODO: throw OverflowException" + + state + |> IlMachineState.advanceProgramCounter currentThread + |> Tuple.withRight WhatWeDid.Executed + |> ExecutionResult.Stepped | Add_ovf_un -> failwith "TODO: Add_ovf_un unimplemented" | Mul -> let val2, state = IlMachineState.popEvalStack currentThread state @@ -617,7 +635,37 @@ module NullaryIlOp = |> IlMachineState.advanceProgramCounter currentThread (state, WhatWeDid.Executed) |> ExecutionResult.Stepped - | Xor -> failwith "TODO: Xor unimplemented" + | Xor -> + let v2, state = IlMachineState.popEvalStack currentThread state + let v1, state = IlMachineState.popEvalStack currentThread state + + let result = + match v1, v2 with + | EvalStackValue.Int32 v1, EvalStackValue.Int32 v2 -> v1 ^^^ v2 |> EvalStackValue.Int32 + | EvalStackValue.Int32 v1, EvalStackValue.NativeInt (NativeIntSource.Verbatim v2) -> + int64 v1 ^^^ v2 |> NativeIntSource.Verbatim |> EvalStackValue.NativeInt + | EvalStackValue.Int32 _, EvalStackValue.NativeInt _ -> + failwith $"can't do binary operation on non-verbatim native int {v2}" + | EvalStackValue.Int64 v1, EvalStackValue.Int64 v2 -> v1 ^^^ v2 |> EvalStackValue.Int64 + | EvalStackValue.NativeInt (NativeIntSource.Verbatim v1), EvalStackValue.Int32 v2 -> + v1 ^^^ int64 v2 |> NativeIntSource.Verbatim |> EvalStackValue.NativeInt + | EvalStackValue.NativeInt _, EvalStackValue.Int32 _ -> + failwith $"can't do binary operation on non-verbatim native int {v1}" + | EvalStackValue.NativeInt (NativeIntSource.Verbatim v1), + EvalStackValue.NativeInt (NativeIntSource.Verbatim v2) -> + v1 ^^^ v2 |> NativeIntSource.Verbatim |> EvalStackValue.NativeInt + | EvalStackValue.NativeInt (NativeIntSource.Verbatim _), EvalStackValue.NativeInt _ -> + failwith $"can't do binary operation on non-verbatim native int {v2}" + | EvalStackValue.NativeInt _, EvalStackValue.NativeInt (NativeIntSource.Verbatim _) -> + failwith $"can't do binary operation on non-verbatim native int {v1}" + | _, _ -> failwith $"refusing to do binary operation on {v1} and {v2}" + + let state = + state + |> IlMachineState.pushToEvalStack' result currentThread + |> IlMachineState.advanceProgramCounter currentThread + + (state, WhatWeDid.Executed) |> ExecutionResult.Stepped | Conv_I -> let popped, state = IlMachineState.popEvalStack currentThread state let converted = EvalStackValue.toNativeInt popped @@ -935,7 +983,22 @@ module NullaryIlOp = | Conv_ovf_i -> failwith "TODO: Conv_ovf_i unimplemented" | Conv_ovf_u -> failwith "TODO: Conv_ovf_u unimplemented" | Neg -> failwith "TODO: Neg unimplemented" - | Not -> failwith "TODO: Not unimplemented" + | Not -> + let val1, state = IlMachineState.popEvalStack currentThread state + + let result = + match val1 with + | EvalStackValue.Int32 i -> ~~~i |> EvalStackValue.Int32 + | EvalStackValue.Int64 i -> ~~~i |> EvalStackValue.Int64 + | EvalStackValue.ManagedPointer _ + | EvalStackValue.ObjectRef _ -> failwith "refusing to negate a pointer" + | _ -> failwith "TODO" + + state + |> IlMachineState.pushToEvalStack' result currentThread + |> IlMachineState.advanceProgramCounter currentThread + |> Tuple.withRight WhatWeDid.Executed + |> ExecutionResult.Stepped | Ldind_ref -> let addr, state = IlMachineState.popEvalStack currentThread state