Files
WoofWare.PawPrint/WoofWare.PawPrint/EvalStack.fs
2025-08-25 11:27:14 +00:00

363 lines
17 KiB
Forth

namespace WoofWare.PawPrint
/// See I.12.3.2.1 for definition
type EvalStackValue =
| Int32 of int32
| Int64 of int64
| NativeInt of NativeIntSource
| Float of float
| ManagedPointer of ManagedPointerSource
| ObjectRef of ManagedHeapAddress
| UserDefinedValueType of EvalStackValueUserType
override this.ToString () =
match this with
| EvalStackValue.Int32 i -> $"Int32(%i{i})"
| EvalStackValue.Int64 i -> $"Int64(%i{i})"
| EvalStackValue.NativeInt src -> $"NativeInt(%O{src})"
| EvalStackValue.Float f -> $"Float(%f{f})"
| EvalStackValue.ManagedPointer managedPointerSource -> $"Pointer(%O{managedPointerSource})"
| EvalStackValue.ObjectRef managedHeapAddress -> $"ObjectRef(%O{managedHeapAddress})"
| EvalStackValue.UserDefinedValueType evalStackValues ->
let desc =
evalStackValues.Fields
|> List.map (_.ContentsEval >> string<EvalStackValue>)
|> String.concat " | "
$"Struct(%s{desc})"
and EvalStackValueField =
{
Name : string
ContentsEval : EvalStackValue
Offset : int option
}
and EvalStackValueUserType =
{
Fields : EvalStackValueField list
}
static member DereferenceField (name : string) (this : EvalStackValueUserType) =
// TODO: this doesn't account for overlapping fields
this.Fields
|> List.pick (fun stackField ->
if stackField.Name = name then
Some stackField.ContentsEval
else
None
)
static member OfFields (fields : EvalStackValueField list) =
{
Fields = fields
}
[<RequireQualifiedAccess>]
module EvalStackValue =
/// The conversion performed by Conv_u.
let toUnsignedNativeInt (value : EvalStackValue) : UnsignedNativeIntSource option =
// Table III.8
match value with
| EvalStackValue.Int32 i ->
if i >= 0 then
Some (uint64 i |> UnsignedNativeIntSource.Verbatim)
else
// Zero-extend.
failwith "todo"
| EvalStackValue.Int64 i ->
if i >= 0L then
Some (uint64 i |> UnsignedNativeIntSource.Verbatim)
else
failwith "todo"
| EvalStackValue.NativeInt i ->
match i with
| NativeIntSource.Verbatim i ->
if i >= 0L then
uint64 i |> UnsignedNativeIntSource.Verbatim |> Some
else
failwith "todo"
| NativeIntSource.ManagedPointer _ -> failwith "TODO"
| NativeIntSource.FunctionPointer _ -> failwith "TODO"
| NativeIntSource.TypeHandlePtr _ -> failwith "TODO"
| EvalStackValue.Float f -> failwith "todo"
| EvalStackValue.ManagedPointer managedPointerSource ->
UnsignedNativeIntSource.FromManagedPointer managedPointerSource |> Some
| EvalStackValue.ObjectRef managedHeapAddress -> failwith "todo"
| EvalStackValue.UserDefinedValueType _ -> failwith "todo"
/// The conversion performed by Conv_i.
let toNativeInt (value : EvalStackValue) : NativeIntSource option =
match value with
| EvalStackValue.Int64 i -> Some (NativeIntSource.Verbatim i)
| EvalStackValue.Int32 i -> Some (NativeIntSource.Verbatim (int64<int> i))
| value -> failwith $"{value}"
let convToInt32 (value : EvalStackValue) : int32 option =
match value with
| EvalStackValue.Int32 i -> Some i
| EvalStackValue.Int64 int64 -> failwith "todo"
| EvalStackValue.NativeInt nativeIntSource -> failwith "todo"
| EvalStackValue.Float f -> failwith "todo"
| EvalStackValue.ManagedPointer managedPointerSource -> failwith "todo"
| EvalStackValue.ObjectRef managedHeapAddress -> failwith "todo"
| EvalStackValue.UserDefinedValueType evalStackValues -> failwith "todo"
let convToInt64 (value : EvalStackValue) : int64 option =
match value with
| EvalStackValue.Int32 i -> Some (int64<int> i)
| EvalStackValue.Int64 i -> Some i
| EvalStackValue.NativeInt nativeIntSource -> failwith "todo"
| EvalStackValue.Float f -> failwith "todo"
| EvalStackValue.ManagedPointer managedPointerSource -> failwith "todo"
| EvalStackValue.ObjectRef managedHeapAddress -> failwith "todo"
| EvalStackValue.UserDefinedValueType evalStackValues -> failwith "todo"
/// Then truncates to int64.
let convToUInt64 (value : EvalStackValue) : int64 option =
match value with
| 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"
| EvalStackValue.ObjectRef managedHeapAddress -> failwith "todo"
| EvalStackValue.UserDefinedValueType evalStackValues -> failwith "todo"
let rec toCliTypeCoerced (target : CliType) (popped : EvalStackValue) : CliType =
match target with
| CliType.Numeric numeric ->
match numeric with
| CliNumericType.Int32 _ ->
match popped with
| EvalStackValue.Int32 i -> CliType.Numeric (CliNumericType.Int32 i)
| EvalStackValue.UserDefinedValueType popped ->
match popped.Fields with
| [] -> failwith "unexpectedly empty"
| [ popped ] -> toCliTypeCoerced target popped.ContentsEval
| fields ->
match fields.[0].Offset with
| None -> failwith "TODO"
| Some _ ->
let fields =
fields
|> List.map (fun f ->
match f.Offset with
| None -> failwith "unexpectedly got a field which didn't have an offset"
| Some offset -> offset, f
)
|> List.sortBy fst
failwith "TODO"
| i -> failwith $"TODO: %O{i}"
| CliNumericType.Int64 _ ->
match popped with
| EvalStackValue.Int64 i -> CliType.Numeric (CliNumericType.Int64 i)
| EvalStackValue.NativeInt src ->
match src with
| NativeIntSource.Verbatim i -> CliType.Numeric (CliNumericType.Int64 i)
| NativeIntSource.ManagedPointer ptr -> failwith "TODO"
| NativeIntSource.FunctionPointer f -> failwith $"TODO: {f}"
// CliType.Numeric (CliNumericType.ProvenanceTrackedNativeInt64 f)
| NativeIntSource.TypeHandlePtr f -> failwith $"TODO: {f}"
// CliType.Numeric (CliNumericType.TypeHandlePtr f)
| i -> failwith $"TODO: %O{i}"
| CliNumericType.NativeInt _ ->
match popped with
| EvalStackValue.NativeInt s -> CliNumericType.NativeInt s |> CliType.Numeric
| EvalStackValue.ManagedPointer ptrSrc ->
CliNumericType.NativeInt (NativeIntSource.ManagedPointer ptrSrc)
|> CliType.Numeric
| EvalStackValue.UserDefinedValueType vt ->
match vt.Fields with
| [] -> failwith "unexpected"
| [ vt ] -> toCliTypeCoerced target vt.ContentsEval
| _ -> failwith $"TODO: {popped}"
| _ -> failwith $"TODO: {popped}"
| CliNumericType.NativeFloat f -> failwith "todo"
| CliNumericType.Int8 _ ->
match popped with
| EvalStackValue.Int32 i -> CliType.Numeric (CliNumericType.Int8 (i % 256 |> int8))
| i -> failwith $"TODO: %O{i}"
| CliNumericType.Int16 _ ->
match popped with
| EvalStackValue.Int32 popped -> CliType.Numeric (CliNumericType.Int16 (popped % 65536 |> int16<int>))
| _ -> failwith $"TODO: {popped}"
| CliNumericType.UInt8 _ ->
match popped with
| EvalStackValue.Int32 i -> CliType.Numeric (CliNumericType.UInt8 (i % 256 |> uint8))
| i -> failwith $"todo: {i} to uint8"
| CliNumericType.UInt16 _ ->
match popped with
| EvalStackValue.Int32 popped -> CliType.Numeric (CliNumericType.UInt16 (uint16<int32> popped))
| i -> failwith $"todo: {i} to uint16"
| CliNumericType.Float32 _ ->
match popped with
| EvalStackValue.Float f -> CliType.Numeric (CliNumericType.Float32 (float32<float> f))
| i -> failwith $"todo: {i} to float32"
| CliNumericType.Float64 _ ->
match popped with
| EvalStackValue.Float f -> CliType.Numeric (CliNumericType.Float64 f)
| _ -> failwith $"todo: {popped} to float64"
| CliType.ObjectRef _ ->
match popped with
| EvalStackValue.ManagedPointer ptrSource ->
ptrSource |> CliRuntimePointer.Managed |> CliType.RuntimePointer
| EvalStackValue.ObjectRef ptr ->
ManagedPointerSource.Heap ptr
|> CliRuntimePointer.Managed
|> CliType.RuntimePointer
| EvalStackValue.NativeInt nativeIntSource ->
match nativeIntSource with
| NativeIntSource.Verbatim 0L -> CliType.ObjectRef None
| NativeIntSource.Verbatim i -> failwith $"refusing to interpret verbatim native int {i} as a pointer"
| NativeIntSource.FunctionPointer _ -> failwith "TODO"
| NativeIntSource.TypeHandlePtr _ -> failwith "refusing to interpret type handle ID as an object ref"
| NativeIntSource.ManagedPointer ptr ->
match ptr with
| ManagedPointerSource.Null -> CliType.ObjectRef None
| ManagedPointerSource.Heap s -> CliType.ObjectRef (Some s)
| _ -> failwith "TODO"
| EvalStackValue.UserDefinedValueType obj ->
match obj.Fields with
| [ esv ] -> toCliTypeCoerced target esv.ContentsEval
| fields -> failwith $"TODO: don't know how to coerce struct of {fields} to a pointer"
| _ -> failwith $"TODO: {popped}"
| CliType.Bool _ ->
match popped with
| EvalStackValue.Int32 i ->
// Bools are zero-extended
CliType.Bool (i % 256 |> byte)
| EvalStackValue.ManagedPointer src ->
failwith $"unexpectedly tried to convert a managed pointer (%O{src}) into a bool"
| i -> failwith $"TODO: %O{i}"
| CliType.RuntimePointer _ ->
match popped with
| EvalStackValue.ManagedPointer src -> src |> CliRuntimePointer.Managed |> CliType.RuntimePointer
| EvalStackValue.NativeInt intSrc ->
match intSrc with
| NativeIntSource.Verbatim i -> CliType.RuntimePointer (CliRuntimePointer.Unmanaged i)
| NativeIntSource.ManagedPointer src -> src |> CliRuntimePointer.Managed |> CliType.RuntimePointer
| NativeIntSource.FunctionPointer methodInfo ->
CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.FunctionPointer methodInfo))
| NativeIntSource.TypeHandlePtr int64 -> failwith "todo"
| EvalStackValue.ObjectRef addr ->
ManagedPointerSource.Heap addr
|> CliRuntimePointer.Managed
|> CliType.RuntimePointer
| _ -> failwith $"TODO: %O{popped}"
| CliType.Char _ ->
match popped with
| EvalStackValue.Int32 i ->
let high = i / 256
let low = i % 256
CliType.Char (byte<int> high, byte<int> low)
| popped -> failwith $"Unexpectedly wanted a char from {popped}"
| CliType.ValueType vt ->
match popped with
| EvalStackValue.UserDefinedValueType popped ->
if vt.Fields.Length <> popped.Fields.Length then
// TODO: overlapping fields
failwith
$"mismatch: popped value type {popped} (length %i{popped.Fields.Length}) into {vt} (length %i{vt.Fields.Length})"
(vt.Fields, popped.Fields)
||> List.map2 (fun field1 popped ->
if field1.Name <> popped.Name then
failwith $"TODO: name mismatch, {field1.Name} vs {popped.Name}"
if field1.Offset <> popped.Offset then
failwith $"TODO: offset mismatch for {field1.Name}, {field1.Offset} vs {popped.Offset}"
let contents = toCliTypeCoerced field1.Contents popped.ContentsEval
{
CliField.Name = field1.Name
Contents = contents
Offset = field1.Offset
}
)
|> CliValueType.OfFields
|> CliType.ValueType
| popped ->
match vt.Fields with
| [ field ] -> toCliTypeCoerced field.Contents popped
| _ -> failwith $"TODO: {popped} into value type {target}"
let rec ofCliType (v : CliType) : EvalStackValue =
match v with
| CliType.Numeric numeric ->
match numeric with
| CliNumericType.Int32 i -> EvalStackValue.Int32 i
| CliNumericType.Int64 i -> EvalStackValue.Int64 i
| CliNumericType.NativeInt i -> EvalStackValue.NativeInt i
// Sign-extend types int8 and int16
// Zero-extend unsigned int8/unsigned int16
| CliNumericType.Int8 b -> int32<int8> b |> EvalStackValue.Int32
| CliNumericType.UInt8 b -> int32<uint8> b |> EvalStackValue.Int32
| CliNumericType.Int16 s -> int32<int16> s |> EvalStackValue.Int32
| CliNumericType.UInt16 s -> int32<uint16> s |> EvalStackValue.Int32
| CliNumericType.Float32 f -> EvalStackValue.Float (float<float32> f)
| CliNumericType.Float64 f -> EvalStackValue.Float f
| CliNumericType.NativeFloat f -> EvalStackValue.Float f
| CliType.ObjectRef i ->
match i with
| None -> EvalStackValue.ManagedPointer ManagedPointerSource.Null
| Some i -> EvalStackValue.ManagedPointer (ManagedPointerSource.Heap i)
// Zero-extend bool/char
| CliType.Bool b -> int32 b |> EvalStackValue.Int32
| CliType.Char (high, low) -> int32 high * 256 + int32 low |> EvalStackValue.Int32
| CliType.RuntimePointer ptr ->
match ptr with
| CliRuntimePointer.Unmanaged ptrInt -> NativeIntSource.Verbatim ptrInt |> EvalStackValue.NativeInt
| CliRuntimePointer.Managed ptr -> ptr |> EvalStackValue.ManagedPointer
| CliType.ValueType fields ->
// TODO: this is a bit dubious; we're being a bit sloppy with possibly-overlapping fields here
fields.Fields
|> List.map (fun field ->
let contents = ofCliType field.Contents
{
Name = field.Name
Offset = field.Offset
ContentsEval = contents
}
)
|> EvalStackValueUserType.OfFields
|> EvalStackValue.UserDefinedValueType
type EvalStack =
{
Values : EvalStackValue list
}
static member Empty : EvalStack =
{
Values = []
}
static member Pop (stack : EvalStack) : EvalStackValue * EvalStack =
match stack.Values with
| [] -> failwith "eval stack was empty on pop instruction"
| v :: rest ->
let stack =
{
Values = rest
}
v, stack
static member Peek (stack : EvalStack) : EvalStackValue option = stack.Values |> List.tryHead
static member Push' (v : EvalStackValue) (stack : EvalStack) : EvalStack =
{
Values = v :: stack.Values
}
static member Push (v : CliType) (stack : EvalStack) : EvalStack =
let v = EvalStackValue.ofCliType v
EvalStack.Push' v stack
static member PeekNthFromTop (n : int) (stack : EvalStack) : EvalStackValue option = stack.Values |> List.tryItem n