Added real support for Unbox stubs to WebAssembly - #5052
Conversation
If you make a valuetype the parent of the thunk, the thunk would be an instance method on a valuetype. |
|
Do we have enough things working that you could write a test that boxes a valuetype and then e.g. calls |
| // Get a reference type that has the same layout as the boxed valuetype. | ||
| var typeKey = new BoxedValuetypeHashtableKey(owningTypeDefinition, ownerModuleOfThunk); | ||
| BoxedValueType boxedTypeDefinition = _boxedValuetypeHashtable.GetOrCreateValue(typeKey); | ||
| return new UnboxingThunk(boxedTypeDefinition, targetMethod); |
There was a problem hiding this comment.
This needs to do the hashtable dance for allocation. We use object equality within the type system everywhere to compare two type system objects. This API would make it possible to have two UnboxingThunk instances that are the same thing.
| return thunk; | ||
| } | ||
|
|
||
| public MethodDesc GetUnspecialUnboxingThunk(MethodDesc targetMethod, ModuleDesc ownerModuleOfThunk) |
There was a problem hiding this comment.
Just call it GetUnboxingThunk?
| TypeDesc owningType = targetMethod.OwningType; | ||
| Debug.Assert(owningType.IsValueType); | ||
|
|
||
| var owningTypeDefinition = (MetadataType)owningType.GetTypeDefinition(); |
There was a problem hiding this comment.
This is correctly going to the type definition, but we also need to do the inverse to specialize back before returning the result.
Consider we need an unboxing thunk for method Foo<SomeType>.Bar - this should end up creating an unboxing thunk for Boxed_Foo<T>.Bar that we then specialize to Boxed_Foo<SomeType>.Bar.
See how GetSpecialUnboxingThunk achieves that with GetTypicalMethodDefinition() and GetMethodForInstantiatedType. The special unboxing thunk only deals with generics, so it just asserts owningType != owningTypeDefinition, but in your case you'll have an if to check if we need to do that.
|
|
||
| // unbox to get a pointer to the value type | ||
| codeStream.EmitLdArg(0); | ||
| codeStream.Emit(ILOpcode.unbox, emit.NewToken(_owningType)); |
There was a problem hiding this comment.
This will throw an InvalidCastException. Owning type is not the valuetype, it's a "type with the same layout as a boxed valuetype". I would prefer we use the ldflda approach the special unbox thunk uses because it avoids the typecheck completely and codegens are much better at optimizing it.
|
Do you have any recommendations for a specific test I can write for this? other than ToString I'm not really sure what uses unboxing stubs, and I don't think I can run ToString because of other limitations in WebAssembly right now. |
struct Foo
{
public int X;
public override GetHashCode() => X;
}
Foo x = new Foo { X = 123456 };
object boxed = x;
if (boxed.GetHashCode() != x.X) Fail();Would this work? |
|
I think I've followed all of your advice but I cant seem to get past a field of type SignatureTypeVariable getting thrown into the mix of things, causing an assert when we try to get the offset for it. If I commit what I have can you take a look? |
Sure! |
| case TypeFlags.Interface: | ||
| case TypeFlags.Array: | ||
| case TypeFlags.SzArray: | ||
| case TypeFlags.SignatureTypeVariable: |
There was a problem hiding this comment.
If we hit this, things are already going bad.
| private void ImportLoadField(int token, bool isStatic) | ||
| { | ||
| FieldDesc field = (FieldDesc)_methodIL.GetObject(token); | ||
| FieldDesc field = ((FieldDesc)_methodIL.GetObject(token)).GetTypicalFieldDefinition(); |
There was a problem hiding this comment.
Codegen should always look at fully instantiated things. It should never see the signature variables. Trying to load the value of field SomeGenericType<T>.SomeField should produce InvalidProgramException. Only SomeGenericType<SomeArgument>.SomeField exists. Could this be the source of trouble?
There was a problem hiding this comment.
(The GetTypicalFieldDefinition doesn't look right.)
|
Throwing seems like a good idea here, but I don't really understand how a non typical field is making it to codegen time. Is there some other issue that needs to be hunted down? |
|
|
||
| private LLVMValueRef GetInstanceFieldAddress(StackEntry objectEntry, FieldDesc field) | ||
| { | ||
| if (!field.IsTypicalFieldDefinition) |
There was a problem hiding this comment.
No, what I meant is that the call to GetTypicalFieldDefinition didn't look right.
Codegen should never operate on uninstantiated things if generics are involved. Everything should be fully instantiated. We can't compute field offsets, can't determine field sizes, etc. Uninstantiated things are useless.
Typical field definition is a field definition on the type definition. I.e. if Foo is nongeneric, Foo.Field is a typical field definition. If Foo is generic, Foo.Field is a typical field definition, Foo<Something>.Field is not a typical field definition.
The new throw doesn't look right.
There was a problem hiding this comment.
I switched it to if (field.FieldType.IsSignatureVariable) as that seems to be a more direct representation of my problem, but I die later when a Internal.TypeSystem.MethodForInstantiatedType fails for obvious reasons to get a mangled name. My problem here is I don't understand why all of these open generics are suddenly flying around in codegen. How are my UnboxingStub changes causing this?
There was a problem hiding this comment.
The problem is definately inside GetUnboxingStub
var owningTypeDefinition = (MetadataType)owningType.GetTypeDefinition();
if (owningType != owningTypeDefinition)
{
InstantiatedType boxedType = boxedTypeDefinition.MakeInstantiatedType(owningType.Instantiation);
MethodDesc thunk = GetMethodForInstantiatedType(thunkDefinition, boxedType);
Debug.Assert(!thunk.HasInstantiation);
return thunk;
}
this is where the Internal.TypeSystem.MethodForInstantiatedType is coming from. Why am i not just using the owningType directly, I think I may have just misunderstood your initial comment about this area.
There was a problem hiding this comment.
You missed the InstantiateAsOpen() call in the method body generator.
The generated IL for methods on generic types is:
// [S.P.CompilerGenerated]Boxed_Enumerator<string>.get_Current_Unbox()
.method instance string get_Current_Unbox() cil managed
{
// Code size: 12
.maxstack 12
IL_0000: ldarg.0
IL_0001: ldflda valuetype [System.Private.CoreLib]System.Collections.Generic.List`1/Enumerator<string> class Boxed_Enumerator<string>::BoxedValue
IL_0006: call instance !0 valuetype [System.Private.CoreLib]System.Collections.Generic.List`1/Enumerator::get_Current()
IL_000B: ret
}
But it should be:
// [S.P.CompilerGenerated]Boxed_Enumerator<string>.get_Current_Unbox()
.method instance string get_Current_Unbox() cil managed
{
// Code size: 12
.maxstack 12
IL_0000: ldarg.0
IL_0001: ldflda valuetype [System.Private.CoreLib]System.Collections.Generic.List`1/Enumerator<string> class Boxed_Enumerator<string>::BoxedValue
IL_0006: call instance string valuetype [System.Private.CoreLib]System.Collections.Generic.List`1/Enumerator<string>::get_Current()
IL_000B: ret
}
| return thunk; | ||
| } | ||
| else | ||
| return thunkDefinition; |
There was a problem hiding this comment.
Maybe add assert for !thunkDefinition.HasInstantiation. That part is a TODO (maybe add that TODO note to the same assert 4 lines above too), but I don't think we can trigger it without having a struct that implements an interface with a generic method, so it doesn't make sense to write code for it for now.
|
this failure appears to be infrastructure related can you rerun the test on osx? |
|
@dotnet-bot test OSX10.12 Debug and CoreCLR tests |
MichalStrehovsky
left a comment
There was a problem hiding this comment.
I'll let @morganbr review the ILToWebAssemblyImporter change (I'm not paying much attention to that), but the rest looks good. Thanks!
morganbr
left a comment
There was a problem hiding this comment.
Looks good. Thanks, @hippiehunter!
This fixes #5005
@MichalStrehovsky I think this is in line with your first suggested path, but I'm a little hazy on the real meaning of things like the ClassCode when adding a new node. I'm also not totally sure that I really needed to use BoxedValueType at all, I think maybe I could have just used the original value type.