-
Notifications
You must be signed in to change notification settings - Fork 510
Multi-module framework assembly compilation #2126
Conversation
This sounds like a JIT bug - we should get a bug on it. The JIT should be able to inline p/invoke with struct return just fine. |
@@ -61,7 +61,7 @@ public static MethodIL EmitIL(MethodDesc methodThatShouldThrow, TypeSystemExcept | |||
|
|||
foreach (var arg in exception.Arguments) | |||
{ | |||
codeStream.Emit(ILOpcode.ldstr, emitter.NewToken(arg)); | |||
codeStream.Emit(ILOpcode.ldstr, emitter.NewToken(arg ?? "")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How are we ending up with a null string here? That kind of sounds like a bug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The messageArg
argument to TypeLoadException
defaults to null. We could change the default to empty string in the constructor to TypeLoadException
instead. I don't mind either way; I think this combined with the assert you added to NewToken
is fine
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This means we're passing more formatting arguments than the exception wants. Are you sure this won't throw at runtime while trying to construct the exception? This might be my fault for not testing it, but this solution only moves the problem to somewhere else.
@@ -67,6 +67,9 @@ public EETypeNode(TypeDesc type) | |||
Debug.Assert(!type.IsRuntimeDeterminedSubtype); | |||
_type = type; | |||
_optionalFieldsNode = new EETypeOptionalFieldsNode(this); | |||
|
|||
if (!type.IsGenericDefinition) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CheckCanGenerateEEType
is skipping generic definitions at the right spot - why is this necessary?
(We still need to validate e.g. the base type can get an EEType.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't create an EEType for the base type of a generic definition though, so we shouldn't validate it. I think a better way would be to move the IsGenericDefinition
to the top of CheckCanGenerateEEType
so we don't go down that path for defs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense.
{ | ||
// Ensure we can compute the type layout | ||
defType.ComputeInstanceLayout(InstanceLayoutKind.TypeAndFields); | ||
defType.ComputeStaticFieldLayout(StaticLayoutKind.StaticRegionSizesAndFields); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need static field layout to build the EEType
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correction: we need the static layout if there's a deferred .cctor
. Having an EEType allows people to pass it to the runtime method that runs the .cctor
, which is why EEType has that dependency.
(Adding an explanatory comment in that sense might be useful for future reference.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do - Now I'll have to pipe NodeFactory through EEType's constructors :(
} | ||
|
||
// Don't validate __System.Canon since it is not present in the output image | ||
if (type.IsCanonicalSubtype(CanonicalFormKind.Specific)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is an assert in EETypeNode
constructor that checks this never happens. We should crash and burn if someone asks for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was needed when the IsGenericDefinition
check came after the base type check. Removed.
TypeDesc baseType = type.BaseType; | ||
if (baseType != null) | ||
{ | ||
CheckCanGenerateEEType(baseType); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of recursively calling these, we should probably just grab the EETypeNode
from the NodeFactory
for these so that the answer is essentially cached (this was one of Jan's comments when I initially implemented this.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, will do
{ | ||
MethodSignature signature = method.Signature; | ||
|
||
EETypeNode.CheckCanGenerateEEType(signature.ReturnType); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a pretty expensive check. I think what we actually need is somewhere along the lines of "make sure Get_CORINFO_SIG_INFO
can succeed".
Maybe this method should also be part of CompilationModuleGroup
or something like that. This file is RyuJIT specific. (We should really make the split of these into separate assemblies so that it gets more obvious.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I whittled this down to the bare minimum the JIT needs, which is it calls getClassAttrib for each method argument and currently the only type loading implication is a check of ContainsGCPointers
.
@@ -30,6 +30,22 @@ public MethodCodeNode(MethodDesc method) | |||
_method = method; | |||
} | |||
|
|||
/// <summary> | |||
/// Validates that it will be possible |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you accidentally
// This way we can fail generating code for the method that references the EEType | ||
// and (depending on the policy), we could avoid scraping the entire compilation. | ||
TypeDesc type = (TypeDesc)target; | ||
EETypeNode.CheckCanGenerateEEType(type); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe actually grabbing the EETypeNode here from the factory might be better (same "caching of answers" argument as above).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed
case ReadyToRunHelperId.NewArr1: | ||
case ReadyToRunHelperId.IsInstanceOf: | ||
case ReadyToRunHelperId.CastClass: | ||
case ReadyToRunHelperId.GetNonGCStaticBase: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Getting the bases only requires being able to compute the static layout
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Altered the three base helpers to check we can ComputeStaticLayout
instead
catch (TypeSystemException) | ||
{ | ||
// Swallow type load exceptions while rooting | ||
continue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add the same TODO comments we have around this catch block as we have in RyuJitCompilation
? (We'll probably want (suppressible) warnings, plus a strict mode where missing references are fatal.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
14bcadf
to
5b26373
Compare
Do we want checks in |
3a01b46
to
773496f
Compare
Done. Fixed up the Unix failure, too. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM otherwise (but I didn't spend much time reading the msbuild changes)
@@ -29,7 +29,7 @@ public MethodCodeNode(MethodDesc method) | |||
Debug.Assert(method.GetCanonMethodTarget(CanonicalFormKind.Specific) == method); | |||
_method = method; | |||
} | |||
|
|||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: unnecessary white space getting checked in. There's no reason to touch this file now.
@@ -122,7 +137,7 @@ public override bool ShouldShareAcrossModules(MethodDesc method) | |||
{ | |||
if (method is InstantiatedMethod) | |||
return true; | |||
|
|||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: unnecessary white space getting checked in.
|
||
if (defType != null) | ||
{ | ||
var unused = defType.ContainsGCPointers; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this cause any build warnings?
Maybe exposing ComputeTypeContainsGCPointers
(for parity with the methods that compute layout) wouldn't be that bad.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope - I was sort of expecting to see a warning about that since we compile with level 4. I can expose ComputeTypeContainsGCPointers
if you still wish. Setting a property to a dummy variable does look silly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leave it up to you. If you don't expose it, I'll probably do it myself because I tend to be OCD about these things :).
// System.Console's Interop.mincore.GetLargestConsoleWindowSize, the JIT decides it cannot | ||
// generate an inline p/invoke because the return type is a struct. | ||
// | ||
if (type.IsValueType && !type.IsPrimitive) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you make sure there's an issue for this (and the issue # is linked from here) per Jan's comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#2144 added to the comments
@@ -61,7 +61,7 @@ public static MethodIL EmitIL(MethodDesc methodThatShouldThrow, TypeSystemExcept | |||
|
|||
foreach (var arg in exception.Arguments) | |||
{ | |||
codeStream.Emit(ILOpcode.ldstr, emitter.NewToken(arg)); | |||
codeStream.Emit(ILOpcode.ldstr, emitter.NewToken(arg ?? "")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you're not fixing this, please file an issue on me and add a link to it here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be addressed in #2143
Enable compiling all framework / SDK assemblies in multi-module library mode Add a work-around to compile System.Console as a library in PInvokeILEmitter.cs System.Console has a p/invoke to GetLargestConsoleWindowSize which returns a COORD struct containing two 16 byte ints. The struct return type prevents the JIT from emitting an inline p/invoke in the IL stub we generate and instead it treats the external p/invoke target as a managed call and tries to compile it. Force p/invokes returning structs to use lazy p/invokes. These use Calli, which the JIT allows to always be inlined. For p/invokes, set CorJitFlag.CORJIT_FLG_IL_STUB JIT flag, which is required to allow us to always inline p/invokes. Address library module compilation failures due to unsuccessful type loads by checking on EEType creation whether the type can be loaded sufficiently to write out the EETypeNode. Code for this stolen from one of Michal's previous PRs :) Eagerly create EETypeNodes in ReadyToRunHelperNode so the failure point happens during the referencing method compilation, not later on when emitting the graph Check method signatures on library compilation method rooting Methods whose signatures contain types that cannot be loaded (they are in a separate, non-existent assembly for example) should be skipped since we cannot even replace their IL body with a throw helper. The JIT will fail trying to getClassAttribs on the argument types. This check is done when rooting methods instead of in MethodCodeNode's constructor because MethodCodeNodes are created during graph sweeping for things like virtual method combined dependencies. Add a test that verifies we can compile all framework assemblies to their own separate object files successfully. Don't check base types for generic type defs Only check static field layout for types with deferred cctors Move CheckCanGenerateMethod into MultiFileCompilationModuleGroup and scope its checks to just what the JIT requires (whether each type contains GC Pointers). Scope the type checking for static base ReadyToRunHelpers to just `ComputeStaticFieldLayout
773496f
to
a9ec5fd
Compare
Enable compiling all framework / SDK assemblies in multi-module library
mode
Add a work-around to compile System.Console as a library in PInvokeILEmitter.cs
System.Console has a p/invoke to GetLargestConsoleWindowSize which
returns a COORD struct containing two 16 byte ints. The struct return
type prevents the JIT from emitting an inline p/invoke in the IL stub we
generate and instead it treats the external p/invoke target as a managed
call and tries to compile it. This work-around alters the return type
marshalling to be a 32bit int and allows the JIT to generate a proper
unmanaged call.
Fix bug in TypeSystemThrowingILEmitter where a null message argument
caused the jit to fail creating a string literal
Address library module compilation failures due to unsuccessful type
loads by checking on EEType creation whether the type can be loaded
sufficiently to write out the EETypeNode. Code for this stolen from one of Michal's previous PRs :)
Eagerly create EETypeNodes in ReadyToRunHelperNode so the failure point
happens during the referencing method compilation, not later on when
emitting the graph
Check method signatures on library compilation method rooting
Methods whose signatures contain types that cannot be loaded (they are
in a separate, non-existent assembly for example) should be skipped
since we cannot even replace their IL body with a throw helper. The JIT
will fail trying to getClassAttribs on the argument types.
This check is done when rooting methods instead of in MethodCodeNode's
constructor because MethodCodeNodes are created during graph sweeping
for things like virtual method combined dependencies.
Add a test that verifies we can compile all framework assemblies to their own separate object files successfully.