Plumb method generics through zeroOf too (#37)

This commit is contained in:
Patrick Stevens
2025-06-02 19:47:03 +01:00
committed by GitHub
parent 2b350986d3
commit 3ac37776df
7 changed files with 144 additions and 80 deletions

View File

@@ -26,6 +26,7 @@
<EmbeddedResource Include="sources\WriteLine.cs" />
<EmbeddedResource Include="sources\InstaQuit.cs" />
<EmbeddedResource Include="sources\Threads.cs" />
<EmbeddedResource Include="sources\ResizeArray.cs" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,18 @@
using System.Collections.Generic;
using System.Linq;
namespace HelloWorldApp
{
class Program
{
static int Main(string[] args)
{
var l = new List<int>();
l.Add(3);
l.Add(100);
var m = l.Select(x => x.ToString()).ToList();
// 2 + 103 + (1 + 3) = 109
return m.Count + l.Sum() + m.Select(x => x.Length).Sum();
}
}
}

View File

@@ -428,6 +428,7 @@ module Assembly =
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
(referencedInAssembly : DumpedAssembly)
(target : TypeRef)
(genericArgs : ImmutableArray<TypeDefn> option)
: TypeResolutionResult
=
match target.ResolutionScope with
@@ -457,16 +458,20 @@ module Assembly =
match targetType with
| [ t ] ->
// If resolved from TypeDef (above), it won't have generic parameters, I hope?
let t =
t |> TypeInfo.mapGeneric (fun _ -> failwith<TypeDefn> "no generic parameters")
t
|> TypeInfo.mapGeneric (fun param ->
match genericArgs with
| None -> failwith "got a generic TypeRef but no generic args in context"
| Some genericArgs -> genericArgs.[param.SequenceNumber]
)
TypeResolutionResult.Resolved (assy, t)
| _ :: _ :: _ -> failwith $"Multiple matching type definitions! {nsPath} {target.Name}"
| [] ->
match assy.ExportedType (Some target.Namespace) target.Name with
| None -> failwith $"Failed to find type {nsPath} {target.Name} in {assy.Name.FullName}!"
| Some ty -> resolveTypeFromExport assy assemblies ty
| Some ty -> resolveTypeFromExport assy assemblies ty genericArgs
| k -> failwith $"Unexpected: {k}"
and internal resolveTypeFromName
@@ -474,6 +479,7 @@ module Assembly =
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
(ns : string option)
(name : string)
(genericArgs : ImmutableArray<TypeDefn> option)
: TypeResolutionResult
=
match ns with
@@ -482,26 +488,30 @@ module Assembly =
match assy.TypeDef ns name with
| Some typeDef ->
// If resolved from TypeDef, it won't have generic parameters, I hope?
let typeDef =
typeDef
|> TypeInfo.mapGeneric (fun _ -> failwith<TypeDefn> "no generic parameters")
|> TypeInfo.mapGeneric (fun param ->
match genericArgs with
| None -> failwith<TypeDefn> $"tried to resolve generic type {ns}.{name} but no generics in scope"
| Some genericArgs -> genericArgs.[param.SequenceNumber]
)
TypeResolutionResult.Resolved (assy, typeDef)
| None ->
match assy.TypeRef ns name with
| Some typeRef -> resolveTypeRef assemblies assy typeRef
| Some typeRef -> resolveTypeRef assemblies assy typeRef genericArgs
| None ->
match assy.ExportedType (Some ns) name with
| Some export -> resolveTypeFromExport assy assemblies export
| Some export -> resolveTypeFromExport assy assemblies export genericArgs
| None -> failwith $"TODO: type resolution unimplemented for {ns} {name}"
and resolveTypeFromExport
(fromAssembly : DumpedAssembly)
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
(ty : WoofWare.PawPrint.ExportedType)
(genericArgs : ImmutableArray<TypeDefn> option)
: TypeResolutionResult
=
match ty.Data with
@@ -511,4 +521,4 @@ module Assembly =
match assemblies.TryGetValue assy.Name.FullName with
| false, _ -> TypeResolutionResult.FirstLoadAssy assy
| true, toAssy -> resolveTypeFromName toAssy assemblies ty.Namespace ty.Name
| true, toAssy -> resolveTypeFromName toAssy assemblies ty.Namespace ty.Name genericArgs

View File

@@ -100,7 +100,8 @@ module CliType =
let rec zeroOf
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
(assy : DumpedAssembly)
(generics : TypeDefn ImmutableArray)
(typeGenerics : TypeDefn ImmutableArray option)
(methodGenerics : TypeDefn ImmutableArray option)
(ty : TypeDefn)
: CliTypeResolutionResult
=
@@ -135,7 +136,7 @@ module CliType =
match signatureTypeKind with
| SignatureTypeKind.Unknown -> failwith "todo"
| SignatureTypeKind.ValueType ->
match Assembly.resolveTypeRef assemblies assy typeRef with
match Assembly.resolveTypeRef assemblies assy typeRef typeGenerics with
| TypeResolutionResult.Resolved (_, ty) -> failwith $"TODO: {ty}"
| TypeResolutionResult.FirstLoadAssy assy -> CliTypeResolutionResult.FirstLoad assy
| SignatureTypeKind.Class -> CliType.ObjectRef None |> CliTypeResolutionResult.Resolved
@@ -148,17 +149,20 @@ module CliType =
let fields =
typeDef.Fields
|> List.map (fun fi -> zeroOf assemblies assy generics fi.Signature)
|> List.map (fun fi -> zeroOf assemblies assy typeGenerics methodGenerics fi.Signature)
CliType.ObjectRef None |> CliTypeResolutionResult.Resolved
| SignatureTypeKind.Class -> CliType.ObjectRef None |> CliTypeResolutionResult.Resolved
| _ -> raise (ArgumentOutOfRangeException ())
| TypeDefn.GenericInstantiation (generic, args) ->
// TODO: this is rather concerning and probably incorrect
zeroOf assemblies assy args generic
| TypeDefn.GenericInstantiation (generic, args) -> zeroOf assemblies assy (Some args) methodGenerics generic
| TypeDefn.FunctionPointer typeMethodSignature -> failwith "todo"
| TypeDefn.GenericTypeParameter index ->
// TODO: can generics depend on other generics? presumably, so we pass the array down again
zeroOf assemblies assy generics generics.[index]
| TypeDefn.GenericMethodParameter index -> zeroOf assemblies assy generics generics.[index]
match typeGenerics with
| None -> failwith "asked for a type parameter of generic type, but no generics in scope"
| Some generics -> zeroOf assemblies assy (Some generics) methodGenerics generics.[index]
| TypeDefn.GenericMethodParameter index ->
match methodGenerics with
| None -> failwith "asked for a method parameter of generic type, but no generics in scope"
| Some generics -> zeroOf assemblies assy typeGenerics (Some generics) generics.[index]
| TypeDefn.Void -> failwith "should never construct an element of type Void"

View File

@@ -205,11 +205,12 @@ module IlMachineState =
(loggerFactory : ILoggerFactory)
(ns : string option)
(name : string)
(genericArgs : ImmutableArray<TypeDefn> option)
(assy : DumpedAssembly)
(state : IlMachineState)
: IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo<TypeDefn>
=
match Assembly.resolveTypeFromName assy state._LoadedAssemblies ns name with
match Assembly.resolveTypeFromName assy state._LoadedAssemblies ns name genericArgs with
| TypeResolutionResult.Resolved (assy, typeDef) -> state, assy, typeDef
| TypeResolutionResult.FirstLoadAssy loadFirst ->
let state, _, _ =
@@ -219,16 +220,17 @@ module IlMachineState =
(fst loadFirst.Handle)
state
resolveTypeFromName loggerFactory ns name assy state
resolveTypeFromName loggerFactory ns name genericArgs assy state
and resolveTypeFromExport
(loggerFactory : ILoggerFactory)
(fromAssembly : DumpedAssembly)
(ty : WoofWare.PawPrint.ExportedType)
(genericArgs : ImmutableArray<TypeDefn> option)
(state : IlMachineState)
: IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo<TypeDefn>
=
match Assembly.resolveTypeFromExport fromAssembly state._LoadedAssemblies ty with
match Assembly.resolveTypeFromExport fromAssembly state._LoadedAssemblies ty genericArgs with
| TypeResolutionResult.Resolved (assy, typeDef) -> state, assy, typeDef
| TypeResolutionResult.FirstLoadAssy loadFirst ->
let state, targetAssy, _ =
@@ -238,59 +240,64 @@ module IlMachineState =
(fst loadFirst.Handle)
state
resolveTypeFromName loggerFactory ty.Namespace ty.Name targetAssy state
resolveTypeFromName loggerFactory ty.Namespace ty.Name genericArgs targetAssy state
and resolveTypeFromRef
(loggerFactory : ILoggerFactory)
(referencedInAssembly : DumpedAssembly)
(target : TypeRef)
(genericArgs : ImmutableArray<TypeDefn> option)
(state : IlMachineState)
: IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo<TypeDefn>
=
match Assembly.resolveTypeRef state._LoadedAssemblies referencedInAssembly target with
match Assembly.resolveTypeRef state._LoadedAssemblies referencedInAssembly target genericArgs with
| TypeResolutionResult.Resolved (assy, typeDef) -> state, assy, typeDef
| TypeResolutionResult.FirstLoadAssy loadFirst ->
let state, _, _ =
loadAssembly
loggerFactory
(state._LoadedAssemblies.[snd(loadFirst.Handle).FullName])
state._LoadedAssemblies.[snd(loadFirst.Handle).FullName]
(fst loadFirst.Handle)
state
resolveTypeFromRef loggerFactory referencedInAssembly target state
resolveTypeFromRef loggerFactory referencedInAssembly target genericArgs state
and resolveType
(loggerFactory : ILoggerFactory)
(ty : TypeReferenceHandle)
(genericArgs : ImmutableArray<TypeDefn> option)
(assy : DumpedAssembly)
(state : IlMachineState)
: IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo<TypeDefn>
=
let target = assy.TypeRefs.[ty]
resolveTypeFromRef loggerFactory assy target state
resolveTypeFromRef loggerFactory assy target genericArgs state
let rec resolveTypeFromDefn
(loggerFactory : ILoggerFactory)
(ty : TypeDefn)
(genericArgs : ImmutableArray<TypeDefn> option)
(assy : DumpedAssembly)
(state : IlMachineState)
: IlMachineState *
DumpedAssembly *
WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter> *
TypeDefn ImmutableArray option
: IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo<TypeDefn>
=
match ty with
| TypeDefn.GenericInstantiation (generic, args) ->
let state, _, generic, subArgs =
resolveTypeFromDefn loggerFactory generic assy state
resolveTypeFromDefn loggerFactory generic (Some args) assy state
| TypeDefn.FromDefinition (defn, _typeKind) ->
let defn =
assy.TypeDefs.[defn.Get]
|> TypeInfo.mapGeneric (fun param ->
match genericArgs with
| None -> failwith "somehow got a generic TypeDefn.FromDefinition without any generic args"
| Some genericArgs -> genericArgs.[param.SequenceNumber]
)
match subArgs with
| Some _ -> failwith "unexpectedly had multiple generic instantiations for the same type"
| None ->
state, assy, generic, Some args
| TypeDefn.FromDefinition (defn, _typeKind) -> state, assy, assy.TypeDefs.[defn.Get], None
state, assy, defn
| TypeDefn.FromReference (ref, _typeKind) ->
let state, assy, ty = resolveTypeFromRef loggerFactory assy ref genericArgs state
state, assy, ty
| s -> failwith $"TODO: resolveTypeFromDefn unimplemented for {s}"
let resolveTypeFromSpec
@@ -300,35 +307,25 @@ module IlMachineState =
(state : IlMachineState)
: IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo<TypeDefn>
=
let state, assy, generic, args =
resolveTypeFromDefn loggerFactory assy.TypeSpecs.[ty].Signature assy state
match args with
| None ->
let generic =
generic
|> TypeInfo.mapGeneric (fun _ -> failwith<TypeDefn> "no generic parameters")
state, assy, generic
| Some args ->
let generic = TypeInfo.withGenerics args generic
state, assy, generic
// Any necessary generics will be baked into the TypeDefn e.g. as a `GenericInstantiation`.
resolveTypeFromDefn loggerFactory assy.TypeSpecs.[ty].Signature None assy state
let rec cliTypeZeroOf
(loggerFactory : ILoggerFactory)
(assy : DumpedAssembly)
(ty : TypeDefn)
(generics : TypeDefn ImmutableArray)
(typeGenerics : TypeDefn ImmutableArray option)
(methodGenerics : TypeDefn ImmutableArray option)
(state : IlMachineState)
: IlMachineState * CliType
=
match CliType.zeroOf state._LoadedAssemblies assy generics ty with
match CliType.zeroOf state._LoadedAssemblies assy typeGenerics methodGenerics ty with
| CliTypeResolutionResult.Resolved result -> state, result
| CliTypeResolutionResult.FirstLoad ref ->
let state, _, _ =
loadAssembly loggerFactory state._LoadedAssemblies.[snd(ref.Handle).FullName] (fst ref.Handle) state
cliTypeZeroOf loggerFactory assy ty generics state
cliTypeZeroOf loggerFactory assy ty typeGenerics methodGenerics state
let callMethod
(loggerFactory : ILoggerFactory)
@@ -342,16 +339,16 @@ module IlMachineState =
(state : IlMachineState)
: IlMachineState
=
let typeGenerics =
match methodToCall.DeclaringType.Generics with
| [] -> None
| x -> Some (ImmutableArray.CreateRange x)
let state, argZeroObjects =
((state, []), methodToCall.Signature.ParameterTypes)
||> List.fold (fun (state, zeros) ty ->
let state, zero =
cliTypeZeroOf
loggerFactory
(state.ActiveAssembly thread)
ty
(methodGenerics |> Option.defaultValue ImmutableArray.Empty)
state
cliTypeZeroOf loggerFactory (state.ActiveAssembly thread) ty typeGenerics methodGenerics state
state, zero :: zeros
)
@@ -553,7 +550,13 @@ module IlMachineState =
| NothingToDo state -> Ok state
| TypeRef typeReferenceHandle ->
let state, assy, targetType =
resolveType loggerFactory typeReferenceHandle (state.ActiveAssembly currentThread) state
// TypeRef won't have any generics; it would be a TypeSpec if it did
resolveType
loggerFactory
typeReferenceHandle
None
(state.ActiveAssembly currentThread)
state
logger.LogDebug (
"Resolved base type of {TypeDefNamespace}.{TypeDefName} to a typeref in assembly {ResolvedAssemblyName}, {BaseTypeNamespace}.{BaseTypeName}",
@@ -564,7 +567,6 @@ module IlMachineState =
targetType.Name
)
// TypeRef won't have any generics; it would be a TypeSpec if it did
let ty = ConcreteType.make assy.Name targetType.TypeDefHandle []
match loadClass loggerFactory ty currentThread state with
@@ -900,7 +902,7 @@ module IlMachineState =
let state, assy, targetType =
match mem.Parent with
| MetadataToken.TypeReference parent -> resolveType loggerFactory parent assy state
| MetadataToken.TypeReference parent -> resolveType loggerFactory parent None assy state
| MetadataToken.TypeSpecification parent -> resolveTypeFromSpec loggerFactory parent assy state
| parent -> failwith $"Unexpected: {parent}"
@@ -992,12 +994,7 @@ module IlMachineState =
| retType ->
// TODO: generics
let state, zero =
cliTypeZeroOf
loggerFactory
(state.ActiveAssembly currentThread)
retType
ImmutableArray.Empty
state
cliTypeZeroOf loggerFactory (state.ActiveAssembly currentThread) retType None None state
let toPush = EvalStackValue.toCliTypeCoerced zero retVal

View File

@@ -164,12 +164,16 @@ and MethodState =
let requiredAssemblies = ResizeArray<WoofWare.PawPrint.AssemblyReference> ()
let typeGenerics =
match method.DeclaringType.Generics with
| [] -> None
| x -> ImmutableArray.CreateRange x |> Some
let localVars =
// TODO: generics?
let result = ImmutableArray.CreateBuilder ()
for var in localVariableSig do
match CliType.zeroOf loadedAssemblies containingAssembly ImmutableArray.Empty var with
match CliType.zeroOf loadedAssemblies containingAssembly typeGenerics methodGenerics var with
| CliTypeResolutionResult.Resolved t -> result.Add t
| CliTypeResolutionResult.FirstLoad (assy : WoofWare.PawPrint.AssemblyReference) ->
requiredAssemblies.Add assy

View File

@@ -165,17 +165,17 @@ module internal UnaryMetadataIlOp =
ctorType.Name
)
let typeGenerics =
match ctor.DeclaringType.Generics with
| [] -> None
| l -> Some (ImmutableArray.CreateRange l)
let state, fieldZeros =
((state, []), ctorType.Fields)
||> List.fold (fun (state, zeros) field ->
// TODO: generics
let state, zero =
IlMachineState.cliTypeZeroOf
loggerFactory
ctorAssembly
field.Signature
ImmutableArray.Empty
state
IlMachineState.cliTypeZeroOf loggerFactory ctorAssembly field.Signature typeGenerics None state
state, (field.Name, zero) :: zeros
)
@@ -307,12 +307,18 @@ module internal UnaryMetadataIlOp =
let valueToStore, state = IlMachineState.popEvalStack thread state
let typeGenerics =
match declaringType.Generics with
| [] -> None
| l -> Some (ImmutableArray.CreateRange l)
let state, zero =
IlMachineState.cliTypeZeroOf
loggerFactory
(state.ActiveAssembly thread)
field.Signature
(ImmutableArray.CreateRange declaringType.Generics)
typeGenerics
None // field can't have its own generics
state
let valueToStore = EvalStackValue.toCliTypeCoerced zero valueToStore
@@ -403,12 +409,18 @@ module internal UnaryMetadataIlOp =
let popped, state = IlMachineState.popEvalStack thread state
let typeGenerics =
match field.DeclaringType.Generics with
| [] -> None
| l -> Some (ImmutableArray.CreateRange l)
let state, zero =
IlMachineState.cliTypeZeroOf
loggerFactory
activeAssy
field.Signature
(ImmutableArray.CreateRange field.DeclaringType.Generics)
typeGenerics
None // field can't have its own generics
state
let toStore = EvalStackValue.toCliTypeCoerced zero popped
@@ -453,6 +465,11 @@ module internal UnaryMetadataIlOp =
let currentObj, state = IlMachineState.popEvalStack thread state
let typeGenerics =
match field.DeclaringType.Generics with
| [] -> None
| l -> Some (ImmutableArray.CreateRange l)
if field.Attributes.HasFlag FieldAttributes.Static then
let state, staticField =
match state.GetStatic field.DeclaringType field.Name with
@@ -463,7 +480,8 @@ module internal UnaryMetadataIlOp =
loggerFactory
(state.LoadedAssembly(field.DeclaringType.Assembly).Value)
field.Signature
(ImmutableArray.CreateRange field.DeclaringType.Generics)
typeGenerics
None // field can't have its own generics
state
let state = state.SetStatic field.DeclaringType field.Name zero
@@ -533,6 +551,11 @@ module internal UnaryMetadataIlOp =
| FirstLoadThis state -> state, WhatWeDid.SuspendedForClassInit
| NothingToDo state ->
let typeGenerics =
match field.DeclaringType.Generics with
| [] -> None
| l -> Some (ImmutableArray.CreateRange l)
let fieldValue, state =
match state.GetStatic field.DeclaringType field.Name with
| None ->
@@ -541,7 +564,8 @@ module internal UnaryMetadataIlOp =
loggerFactory
activeAssy
field.Signature
(field.DeclaringType.Generics |> ImmutableArray.CreateRange)
typeGenerics
None // field can't have its own generics
state
newVal, state.SetStatic field.DeclaringType field.Name newVal
@@ -600,13 +624,19 @@ module internal UnaryMetadataIlOp =
|> IlMachineState.advanceProgramCounter thread
|> Tuple.withRight WhatWeDid.Executed
| None ->
let typeGenerics =
match field.DeclaringType.Generics with
| [] -> None
| l -> Some (ImmutableArray.CreateRange l)
// Field is not yet initialised
let state, zero =
IlMachineState.cliTypeZeroOf
loggerFactory
activeAssy
field.Signature
(field.DeclaringType.Generics |> ImmutableArray.CreateRange)
typeGenerics
None // field can't have its own generics
state
state.SetStatic field.DeclaringType field.Name zero