Skip to content
This repository was archived by the owner on Nov 1, 2020. It is now read-only.

enabled thread static fields in WebAssembly - #4999

Merged
morganbr merged 5 commits into
dotnet:masterfrom
hippiehunter:threadstatic
Nov 30, 2017
Merged

enabled thread static fields in WebAssembly#4999
morganbr merged 5 commits into
dotnet:masterfrom
hippiehunter:threadstatic

Conversation

@hippiehunter

Copy link
Copy Markdown
Contributor

@morganbr
I've enabled thread static fields, it's generating quite a bit more code as a result and it ended up pulling in some weirdness around virtual calls to System.Type. The EEType wasnt getting output, so I've put in a dependency for the owning type when calling a virtual method. If you have any ideas for a different place to trigger the reference for the EEType that works too.

@MichalStrehovsky

Copy link
Copy Markdown
Member

The EEType wasnt getting output, so I've put in a dependency for the owning type when calling a virtual method

That sounds like a bug somewhere.

To put it into a sample: the code below is not supposed to have an EEType for MyProgram generated and for RyuJIT codegen, it won't. For CppCodegen it will, but only because CppCodegen predates dependency analysis infrastructure and it was never fully hooked up into it. Wasm codegen should be more like what we do with RyuJIT than what we do in CppCodegen.

internal class MyProgram
{
    protected virtual void DoNothing() { }

    private static void Main(string[] args)
    {
        MyProgram p = null;
        if (args == null)
            p.DoNothing();
    }
}

@hippiehunter

Copy link
Copy Markdown
Contributor Author

How can I call a method via virtual slot without an eetype? Is this case supposed to be devirtualized?

@MichalStrehovsky

Copy link
Copy Markdown
Member

How can I call a method via virtual slot without an eetype? Is this case supposed to be devirtualized?

You don't need to directly reference the EEType at the callsite. The EEType is implicitly looked up by dereferencing the this pointer at runtime. The compiler only needs to emit the offset into the EEType to find the slot. The fact that there's no EEType into which the slot number could index doesn't matter; the code is not going to execute (and if it does execute, it will throw a NullReferenceException - because no EEType means we clearly never allocated such type).

The EEType might be required because of how virtual calls got hooked up for WASM, but this can be implemented without the EEType. E.g. a scheme that would likely work is:

  • Take InterfaceDispatchCellNode.cs as inspiration, with a couple differences:
    • The constructor only takes one MethodDesc (the slot defining method)
    • In GetData, only do a single Int32 write - the offset into the EEType to find the slot for the slot defining method
    • The rest of methods in InterfaceDispatchCellNode.cs can be pretty much taken verbatim (with a couple obvious tweaks
  • Hook up the new node into WebAssemblyCodegenNodeFactory (take inspiration from how the interface dispatch cell is hooked up in NodeFactory.cs)

Once you have that, to generate a virtual call, you would:

  • dereference this
  • dereference the "virtual method slot offset node" that corresponds to the slot defining method that is to be called
  • add those two numbers
  • dereference the result and call it

{
slot = LLVM.AddGlobal(Module, LLVM.Int32Type(), globalRefName);
}
var vtableSlotSymbol = ((WebAssemblyCodegenNodeFactory)_compilation.NodeFactory).VTableSlot(method);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be a general improvement to add public new WebAssemblyCodegenNodeFactory NodeFactory property to WebAssemblyCodegenCompilation so that this cast is not necessary. WebAssemblyCodegenCompilationBuilder can just pass it as a strongly typed argument to the constructor and Compilation can save it.

if (!relocsOnly)
{
var tableOffset = EETypeNode.GetVTableOffset(factory.Target.PointerSize) / factory.Target.PointerSize;
objData.EmitInt(tableOffset + VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, _targetMethod));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this multiplied by pointer size at runtime? It might be better to just save it premultiplied by pointer size so that this is a simple addition at runtime.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

storing it as a multiple of pointers allows the LLVM/WASM instructions to be shorter because we treat the EEType as a blob of function pointers and this lets us directly index without having to cast anything.

{
public sealed class WebAssemblyCodegenNodeFactory : NodeFactory
{
private NodeCache<MethodKey, WebAssemblyVTableSlotNode> _vTableSlotNodes;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can just use MethodDesc as the key. MethodKey is used when the method can be an unboxing stub. It can't in this case.


public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: unnecessary blank line


if (!relocsOnly)
{
var tableOffset = EETypeNode.GetVTableOffset(factory.Target.PointerSize) / factory.Target.PointerSize;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I don't think we would ever want vtable slots to be unaligned, maybe assert that the offset is divisible by pointer size?

internal WebAssemblyCodegenConfigProvider Options { get; }
internal LLVMModuleRef Module { get; }

public new WebAssemblyCodegenNodeFactory NodeFactory { get; private set; }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: remove the private set; part to make it a read only property.

LLVM.SetInitializer(llvmValue, GetConstZeroArray(field.FieldType.GetElementSize().AsInt));
if (field.IsThreadStatic)
{
LLVM.SetThreadLocal(llvmValue, LLVMMisc.True);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's cool that LLVM can do this for us 😄. What happens with initialization for this? Can you please also add a test using a thread local? I know we can't test it multithreaded, but it would be good to just ensure reading/writing works right.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hippiehunter what I meant about initialization is that .NET thread static fields are supposed to be zero/null initialized. Does LLVM do that for us or do you also need to do some sort of initialization?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a test to validate the initial value of zero and that works. My understanding is that this should all be managed by the call to SetInitializer directly above.

@morganbr morganbr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thanks, @hippiehunter!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants