Implement shl, shr, or (#82)

This commit is contained in:
Patrick Stevens
2025-07-02 17:42:36 +01:00
committed by GitHub
parent 3d5667ebba
commit af3e4f20f2
13 changed files with 326 additions and 37 deletions

View File

@@ -78,6 +78,9 @@ type NullaryIlOp =
| Not
| Shr
| Shr_un
/// Shifts an integer value to the left (in zeroes) by a specified number of bits, pushing the result onto the evaluation stack.
/// Top of stack is number of bits to be shifted.
/// Inserts a zero bit in the lowest positions.
| Shl
| Conv_ovf_i
| Conv_ovf_u

View File

@@ -16,6 +16,18 @@ module TestPureCases =
let unimplemented =
[
{
FileName = "TestShl.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "TestShr.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "Threads.cs"
ExpectedReturnCode = 3
@@ -100,18 +112,9 @@ module TestPureCases =
}
{
FileName = "ArgumentOrdering.cs"
ExpectedReturnCode = 42
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
LocalVariablesOfMain =
[
// localVar
CliType.Numeric (CliNumericType.Int32 42)
// t
CliType.ValueType [ CliType.Numeric (CliNumericType.Int32 42) ]
// return value
CliType.Numeric (CliNumericType.Int32 42)
]
|> Some
LocalVariablesOfMain = None
}
{
FileName = "BasicLock.cs"
@@ -197,6 +200,12 @@ module TestPureCases =
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "TestOr.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
]
[<TestCaseSource(nameof cases)>]

View File

@@ -31,6 +31,9 @@
<EmbeddedResource Include="sourcesPure\Threads.cs" />
<EmbeddedResource Include="sourcesPure\ResizeArray.cs" />
<EmbeddedResource Include="sourcesPure\ArgumentOrdering.cs" />
<EmbeddedResource Include="sourcesPure\TestShl.cs" />
<EmbeddedResource Include="sourcesPure\TestShr.cs" />
<EmbeddedResource Include="sourcesPure\TestOr.cs" />
<EmbeddedResource Include="sourcesPure\CustomDelegate.cs" />
<EmbeddedResource Include="sourcesPure\Ldind.cs" />
</ItemGroup>

View File

@@ -10,10 +10,40 @@ public class Program
}
}
public struct Calculator
{
private int baseValue;
public Calculator(int initial)
{
baseValue = initial;
}
public int Add(int a, int b, int c)
{
return baseValue + a + b + c;
}
public int SubtractIsh(int a, int b)
{
return baseValue - a + b;
}
}
public static int Main(string[] args)
{
int localVar = 42;
TestStruct t = new TestStruct(ref localVar);
return t.Value;
if (t.Value != 42) return 1;
Calculator calc = new Calculator(10);
int addResult = calc.Add(1, 2, 3); // Should be 10 + 1 + 2 + 3 = 16
if (addResult != 16) return 2;
// Test 2: Verify order matters
int subResult = calc.SubtractIsh(3, 2); // Should be 10 - 3 + 2 = 9
if (subResult != 9) return 3;
return 0;
}
}

View File

@@ -0,0 +1,41 @@
public class TestOr
{
public static int Main(string[] args)
{
// Test 1: Bitwise OR with Int32
int a32 = 12; // Binary: 1100
int b32 = 10; // Binary: 1010
int result32 = a32 | b32; // Should be 14 (Binary: 1110)
if (result32 != 14) return 1;
// Test 2: Bitwise OR with Int64
long a64 = 0x00FF00FFL;
long b64 = 0xFF00FF00L;
long result64 = a64 | b64;
if (result64 != 0xFFFFFFFFL) return 2;
// Test 3: Mixed bitwise OR (Int32 and native int)
int aMixed = 15; // Binary: 1111
nint bMixed = 240; // Binary: 11110000
nint resultMixed = aMixed | bMixed; // Should be 255 (Binary: 11111111)
if (resultMixed != 255) return 3;
// Test 4: OR with itself
int self = 42;
int selfResult = self | self;
if (selfResult != 42) return 4;
// Test 5: OR with 0
int withZero = 123;
int zeroResult = withZero | 0;
if (zeroResult != 123) return 5;
// Test 6: Native int OR native int
nint nativeA = 0x0F;
nint nativeB = 0xF0;
nint nativeResult = nativeA | nativeB; // Should be 0xFF
if (nativeResult != 0xFF) return 6;
return 0;
}
}

View File

@@ -0,0 +1,34 @@
public class TestShl
{
public static int Main(string[] args)
{
// Test 1: Shift Left with Int32
int value32 = 5; // Binary: 0101
int shift32 = 2;
int result32 = value32 << shift32; // Should be 20 (Binary: 10100)
if (result32 != 20) return 1;
// Test 2: Shift Left with Int64
long value64 = 7L; // Binary: 0111
int shift64 = 3;
long result64 = value64 << shift64; // Should be 56 (Binary: 111000)
if (result64 != 56L) return 2;
// Test 3: Shift by 0
int noShiftValue = 42;
int noShiftResult = noShiftValue << 0;
if (noShiftResult != 42) return 3;
// Test 4: Shift by 1
int singleShiftResult = noShiftValue << 1;
if (singleShiftResult != 84) return 4;
// Test 5: Shift with native int
nint nativeValue = 3;
int nativeShift = 4;
nint nativeResult = nativeValue << nativeShift; // Should be 48
if (nativeResult != 48) return 5;
return 0;
}
}

View File

@@ -0,0 +1,35 @@
public class TestShr
{
public static int Main(string[] args)
{
// Test 1: Shift Right with Int32
int value32 = 20; // Binary: 10100
int shift32 = 2;
int result32 = value32 >> shift32; // Should be 5 (Binary: 0101)
if (result32 != 5) return 1;
// Test 2: Shift Right with Int64
long value64 = 56L; // Binary: 111000
int shift64 = 3;
long result64 = value64 >> shift64; // Should be 7 (Binary: 0111)
if (result64 != 7L) return 2;
// Test 3: Right shift preserving sign (negative number)
int negative = -16;
int negativeResult = negative >> 2; // Should be -4
if (negativeResult != -4) return 3;
// Test 4: Shift by 0
int noShiftValue = 99;
int noShiftResult = noShiftValue >> 0;
if (noShiftResult != 99) return 4;
// Test 5: Shift with native int
nint nativeValue = 48;
int nativeShift = 4;
nint nativeResult = nativeValue >> nativeShift; // Should be 3
if (nativeResult != 3) return 5;
return 0;
}
}

View File

@@ -152,7 +152,7 @@ type CliType =
| RuntimePointer of CliRuntimePointer
/// This is *not* a CLI type as such. I don't actually know its status. A value type is represented simply
/// as a concatenated list of its fields.
| ValueType of CliType list
| ValueType of (string * CliType) list
/// In fact any non-zero value will do for True, but we'll use 1
static member OfBool (b : bool) = CliType.Bool (if b then 1uy else 0uy)
@@ -223,7 +223,7 @@ module CliType =
|> List.filter (fun field -> not (field.Attributes.HasFlag FieldAttributes.Static))
|> List.map (fun fi ->
match zeroOf assemblies corelib sourceAssy typeGenerics methodGenerics fi.Signature with
| CliTypeResolutionResult.Resolved ty -> Ok ty
| CliTypeResolutionResult.Resolved ty -> Ok (fi.Name, ty)
| CliTypeResolutionResult.FirstLoad a -> Error a
)
|> Result.allOkOrError
@@ -257,7 +257,7 @@ module CliType =
|> List.filter (fun field -> not (field.Attributes.HasFlag FieldAttributes.Static))
|> List.map (fun fi ->
match zeroOf assemblies corelib assy typeGenerics methodGenerics fi.Signature with
| CliTypeResolutionResult.Resolved ty -> Ok ty
| CliTypeResolutionResult.Resolved ty -> Ok (fi.Name, ty)
| CliTypeResolutionResult.FirstLoad a -> Error a
)
|> Result.allOkOrError

View File

@@ -10,7 +10,8 @@ type EvalStackValue =
| ObjectRef of ManagedHeapAddress
// Fraser thinks this isn't really a thing in CoreCLR
// | TransientPointer of TransientPointerSource
| UserDefinedValueType of EvalStackValue list
/// Mapping of field name to value
| UserDefinedValueType of (string * EvalStackValue) list
override this.ToString () =
match this with
@@ -21,7 +22,11 @@ type EvalStackValue =
| EvalStackValue.ManagedPointer managedPointerSource -> $"Pointer(%O{managedPointerSource})"
| EvalStackValue.ObjectRef managedHeapAddress -> $"ObjectRef(%O{managedHeapAddress})"
| EvalStackValue.UserDefinedValueType evalStackValues ->
let desc = evalStackValues |> List.map string<EvalStackValue> |> String.concat " | "
let desc =
evalStackValues
|> List.map (snd >> string<EvalStackValue>)
|> String.concat " | "
$"Struct(%s{desc})"
[<RequireQualifiedAccess>]
@@ -87,8 +92,8 @@ module EvalStackValue =
/// Then truncates to int64.
let convToUInt64 (value : EvalStackValue) : int64 option =
match value with
| EvalStackValue.Int32 i -> if i >= 0 then Some (int64 i) else failwith "TODO"
| EvalStackValue.Int64 int64 -> failwith "todo"
| EvalStackValue.Int32 i -> Some (int64 (uint32 i))
| EvalStackValue.Int64 int64 -> Some int64
| EvalStackValue.NativeInt nativeIntSource -> failwith "todo"
| EvalStackValue.Float f -> failwith "todo"
| EvalStackValue.ManagedPointer managedPointerSource -> failwith "todo"
@@ -102,7 +107,7 @@ module EvalStackValue =
| CliNumericType.Int32 _ ->
match popped with
| EvalStackValue.Int32 i -> CliType.Numeric (CliNumericType.Int32 i)
| EvalStackValue.UserDefinedValueType [ popped ] -> toCliTypeCoerced target popped
| EvalStackValue.UserDefinedValueType [ popped ] -> toCliTypeCoerced target (snd popped)
| i -> failwith $"TODO: %O{i}"
| CliNumericType.Int64 _ ->
match popped with
@@ -177,7 +182,7 @@ module EvalStackValue =
| _ -> failwith "TODO"
| EvalStackValue.UserDefinedValueType fields ->
match fields with
| [ esv ] -> toCliTypeCoerced target esv
| [ esv ] -> toCliTypeCoerced target (snd esv)
| fields -> failwith $"TODO: don't know how to coerce struct of {fields} to a pointer"
| _ -> failwith $"TODO: {popped}"
| CliType.Bool _ ->
@@ -237,12 +242,22 @@ module EvalStackValue =
match popped with
| EvalStackValue.UserDefinedValueType popped ->
if fields.Length <> popped.Length then
failwith "mismatch"
failwith
$"mismatch: popped value type {popped} (length %i{popped.Length}) into {fields} (length %i{fields.Length})"
List.map2 toCliTypeCoerced fields popped |> CliType.ValueType
List.map2
(fun (name1, v1) (name2, v2) ->
if name1 <> name2 then
failwith $"TODO: name mismatch, {name1} vs {name2}"
name1, toCliTypeCoerced v1 v2
)
fields
popped
|> CliType.ValueType
| popped ->
match fields with
| [ target ] -> toCliTypeCoerced target popped
| [ _, target ] -> toCliTypeCoerced target popped
| _ -> failwith $"TODO: {popped} into value type {target}"
let rec ofCliType (v : CliType) : EvalStackValue =
@@ -283,7 +298,10 @@ module EvalStackValue =
|> EvalStackValue.ManagedPointer
| CliRuntimePointerSource.Heap addr -> EvalStackValue.ObjectRef addr
| CliRuntimePointerSource.Null -> EvalStackValue.ManagedPointer ManagedPointerSource.Null
| CliType.ValueType fields -> fields |> List.map ofCliType |> EvalStackValue.UserDefinedValueType
| CliType.ValueType fields ->
fields
|> List.map (fun (name, f) -> name, ofCliType f)
|> EvalStackValue.UserDefinedValueType
type EvalStack =
{

View File

@@ -151,4 +151,4 @@ module EvalStackValueComparisons =
| EvalStackValue.ManagedPointer var1, EvalStackValue.NativeInt var2 ->
failwith $"TODO (CEQ): managed pointer vs nativeint"
| EvalStackValue.ManagedPointer _, _ -> failwith $"bad ceq: ManagedPointer vs {var2}"
| EvalStackValue.UserDefinedValueType _, _ -> failwith $"bad ceq: UserDefinedValueType vs {var2}"
| EvalStackValue.UserDefinedValueType _, _ -> failwith $"bad ceq: {var1} vs {var2}"

View File

@@ -496,7 +496,8 @@ module IlMachineState =
| ResolvedBaseType.Object -> state |> pushToEvalStack (CliType.OfManagedObject constructing) currentThread
| ResolvedBaseType.ValueType ->
state
|> pushToEvalStack (CliType.ValueType (Seq.toList constructed.Fields.Values)) currentThread
// TODO: ordering of fields probably important
|> pushToEvalStack (CliType.ValueType (Map.toList constructed.Fields)) currentThread
| ResolvedBaseType.Enum -> failwith "TODO"
| None ->
match threadStateAtEndOfMethod.MethodState.EvaluationStack.Values with
@@ -572,7 +573,7 @@ module IlMachineState =
let arg =
let rec go (arg : EvalStackValue) =
match arg with
| EvalStackValue.UserDefinedValueType [ s ] -> go s
| EvalStackValue.UserDefinedValueType [ _, s ] -> go s
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> failwith "TODO: throw NRE"
| EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) -> Some addr
| s -> failwith $"TODO: called with unrecognised arg %O{s}"
@@ -580,7 +581,7 @@ module IlMachineState =
go arg
let state =
pushToEvalStack (CliType.ValueType [ CliType.ObjectRef arg ]) currentThread state
pushToEvalStack (CliType.ValueType [ "m_type", CliType.ObjectRef arg ]) currentThread state
|> advanceProgramCounter currentThread
Some state
@@ -706,7 +707,7 @@ module IlMachineState =
| ManagedPointerSource.Null -> failwith "TODO: throw NRE"
| EvalStackValue.NativeInt src -> failwith "TODO"
| EvalStackValue.ObjectRef ptr -> failwith "TODO"
| EvalStackValue.UserDefinedValueType [ field ] -> go field
| EvalStackValue.UserDefinedValueType [ _, field ] -> go field
| EvalStackValue.UserDefinedValueType []
| EvalStackValue.UserDefinedValueType (_ :: _ :: _)
| EvalStackValue.Int32 _

View File

@@ -438,8 +438,8 @@ module NullaryIlOp =
|> Tuple.withRight WhatWeDid.Executed
|> ExecutionResult.Stepped
| Sub ->
let val1, state = IlMachineState.popEvalStack currentThread state
let val2, state = IlMachineState.popEvalStack currentThread state
let val1, state = IlMachineState.popEvalStack currentThread state
let result = BinaryArithmetic.execute ArithmeticOperation.sub val1 val2
state
@@ -475,11 +475,119 @@ module NullaryIlOp =
| Mul_ovf_un -> failwith "TODO: Mul_ovf_un unimplemented"
| Div -> failwith "TODO: Div unimplemented"
| Div_un -> failwith "TODO: Div_un unimplemented"
| Shr -> failwith "TODO: Shr unimplemented"
| Shr ->
let shift, state = IlMachineState.popEvalStack currentThread state
let number, state = IlMachineState.popEvalStack currentThread state
let shift =
match shift with
| EvalStackValue.Int32 i -> i
| EvalStackValue.NativeInt (NativeIntSource.Verbatim i) -> int<int64> i
| _ -> failwith $"Not allowed shift of {shift}"
let result =
// See table III.6
match number with
| EvalStackValue.Int32 i -> i >>> shift |> EvalStackValue.Int32
| EvalStackValue.Int64 i -> i >>> shift |> EvalStackValue.Int64
| EvalStackValue.NativeInt (NativeIntSource.Verbatim i) ->
(i >>> shift) |> NativeIntSource.Verbatim |> EvalStackValue.NativeInt
| _ -> failwith $"Not allowed to shift {number}"
let state =
state
|> IlMachineState.pushToEvalStack' result currentThread
|> IlMachineState.advanceProgramCounter currentThread
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
| Shr_un -> failwith "TODO: Shr_un unimplemented"
| Shl -> failwith "TODO: Shl unimplemented"
| And -> failwith "TODO: And unimplemented"
| Or -> failwith "TODO: Or unimplemented"
| Shl ->
let shift, state = IlMachineState.popEvalStack currentThread state
let number, state = IlMachineState.popEvalStack currentThread state
let shift =
match shift with
| EvalStackValue.Int32 i -> i
| EvalStackValue.NativeInt (NativeIntSource.Verbatim i) -> int<int64> i
| _ -> failwith $"Not allowed shift of {shift}"
let result =
// See table III.6
match number with
| EvalStackValue.Int32 i -> i <<< shift |> EvalStackValue.Int32
| EvalStackValue.Int64 i -> i <<< shift |> EvalStackValue.Int64
| EvalStackValue.NativeInt (NativeIntSource.Verbatim i) ->
(i <<< shift) |> NativeIntSource.Verbatim |> EvalStackValue.NativeInt
| _ -> failwith $"Not allowed to shift {number}"
let state =
state
|> IlMachineState.pushToEvalStack' result currentThread
|> IlMachineState.advanceProgramCounter currentThread
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
| And ->
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<int32> 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<int32> 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
| Or ->
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<int32> 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<int32> 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
| Xor -> failwith "TODO: Xor unimplemented"
| Conv_I ->
let popped, state = IlMachineState.popEvalStack currentThread state

View File

@@ -679,7 +679,11 @@ module internal UnaryMetadataIlOp =
IlMachineState.pushToEvalStack currentValue thread state
| ManagedPointerSource.Null -> failwith "TODO: raise NullReferenceException"
| EvalStackValue.ObjectRef managedHeapAddress -> failwith $"todo: {managedHeapAddress}"
| EvalStackValue.UserDefinedValueType _ as udvt -> IlMachineState.pushToEvalStack' udvt thread state
| EvalStackValue.UserDefinedValueType fields ->
let result =
fields |> List.pick (fun (k, v) -> if k = field.Name then Some v else None)
IlMachineState.pushToEvalStack' result thread state
state
|> IlMachineState.advanceProgramCounter thread
@@ -1016,7 +1020,10 @@ module internal UnaryMetadataIlOp =
let (_, alloc), state = IlMachineState.getOrAllocateType baseClassTypes handle state
IlMachineState.pushToEvalStack (CliType.ValueType [ CliType.ObjectRef (Some alloc) ]) thread state
IlMachineState.pushToEvalStack
(CliType.ValueType [ "m_type", CliType.ObjectRef (Some alloc) ])
thread
state
| _ -> failwith $"Unexpected metadata token %O{metadataToken} in LdToken"
state