diff options
author | Morgan Brown <morganbr@users.noreply.github.com> | 2018-03-06 15:15:09 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-03-06 15:15:09 +0300 |
commit | 19116562388ed45b336321456079fc7c398556fc (patch) | |
tree | bb027294e397853d1db5852090881de0b1034088 | |
parent | 40b2afa6ba669c28aeffdd5a8d544220b2312232 (diff) |
Static constructors for WebAssembly (#5425)
* Implement static constructor triggering. Also includes several bug fixes found in the process:
* Don't make the ClassConstructorRunner depend on module intialization
* Moves non-GC statics and thread statics from globals to the type's data regions. GC statics can't be moved until we can call InitializeModules on startup.
* Devirtualizing interface calls to structs in order to be able to compile the class constructor runner.
* Add a prolog block before Block0 to allow branching to Block0, which happens in retail builds
* Correct use of unreachable in traps and at the end of finally blocks to fix more retail build problems
* Stop reusing spill slots when a spilled value is spilled again. This avoids cases where the spills feeding into a block don't use the same slot
* Enable thunks for NativeCallable methods and call RhpReversePInvoke for them
* Fix alignment for cases where a small type is followed by a larger one
* Workaround for Emscripten atomics bug
6 files changed, 322 insertions, 65 deletions
diff --git a/src/Common/src/TypeSystem/IL/ILImporter.cs b/src/Common/src/TypeSystem/IL/ILImporter.cs index fbfdac072..9a28605a7 100644 --- a/src/Common/src/TypeSystem/IL/ILImporter.cs +++ b/src/Common/src/TypeSystem/IL/ILImporter.cs @@ -317,6 +317,7 @@ namespace Internal.IL private void ImportBasicBlocks() { _pendingBasicBlocks = _basicBlocks[0]; + _basicBlocks[0].State = BasicBlock.ImportState.IsPending; while (_pendingBasicBlocks != null) { BasicBlock basicBlock = _pendingBasicBlocks; diff --git a/src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter.cs b/src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter.cs index 8b0936d1e..b4859b849 100644 --- a/src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter.cs +++ b/src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter.cs @@ -110,6 +110,8 @@ namespace Internal.IL { FindBasicBlocks(); + GenerateProlog(); + try { ImportBasicBlocks(); @@ -130,39 +132,53 @@ namespace Internal.IL LLVM.PositionBuilderAtEnd(_builder, trapBlock); EmitTrapCall(); - LLVM.BuildRetVoid(_builder); throw; } finally { // Generate thunk for runtime exports - if (_method.IsRuntimeExport) + if (_method.IsRuntimeExport || _method.IsNativeCallable) { - EmitNativeToManagedThunk(_compilation, _method, ((EcmaMethod)_method).GetRuntimeExportName(), _llvmFunction); + EcmaMethod ecmaMethod = ((EcmaMethod)_method); + string exportName = ecmaMethod.IsRuntimeExport ? ecmaMethod.GetRuntimeExportName() : ecmaMethod.GetNativeCallableExportName(); + EmitNativeToManagedThunk(_compilation, _method, exportName, _llvmFunction); } } } private void GenerateProlog() { - if (!_methodIL.IsInitLocals) - { - return; - } + LLVMBasicBlockRef prologBlock = LLVM.AppendBasicBlock(_llvmFunction, "Prolog"); + LLVM.PositionBuilderAtEnd(_builder, prologBlock); - int totalLocalSize = 0; - foreach(LocalVariableDefinition local in _locals) + if (_methodIL.IsInitLocals) { - totalLocalSize = PadNextOffset(local.Type, totalLocalSize); + int totalLocalSize = 0; + foreach (LocalVariableDefinition local in _locals) + { + totalLocalSize = PadNextOffset(local.Type, totalLocalSize); + } + + var sp = LLVM.GetFirstParam(_llvmFunction); + int paramOffset = GetTotalParameterOffset(); + for (int i = 0; i < totalLocalSize; i++) + { + var stackOffset = LLVM.BuildGEP(_builder, sp, new LLVMValueRef[] { LLVM.ConstInt(LLVM.Int32Type(), (ulong)(paramOffset + i), LLVMMisc.False) }, String.Empty); + LLVM.BuildStore(_builder, LLVM.ConstInt(LLVM.Int8Type(), 0, LLVMMisc.False), stackOffset); + } } - var sp = LLVM.GetFirstParam(_llvmFunction); - int paramOffset = GetTotalParameterOffset(); - for (int i = 0; i < totalLocalSize; i++) + MetadataType metadataType = (MetadataType)_thisType; + if (!metadataType.IsBeforeFieldInit) { - var stackOffset = LLVM.BuildGEP(_builder, sp, new LLVMValueRef[] { LLVM.ConstInt(LLVM.Int32Type(), (ulong)(paramOffset + i), LLVMMisc.False) }, String.Empty); - LLVM.BuildStore(_builder, LLVM.ConstInt(LLVM.Int8Type(), 0, LLVMMisc.False), stackOffset); + if (!_method.IsStaticConstructor && _method.Signature.IsStatic || _method.IsConstructor || (_thisType.IsValueType && !_method.Signature.IsStatic)) + { + TriggerCctor(metadataType); + } } + + LLVMBasicBlockRef block0 = GetLLVMBasicBlockForBlock(_basicBlocks[0]); + LLVM.BuildBr(_builder, block0); } private LLVMValueRef CreateLLVMFunction(string mangledName) @@ -278,19 +294,9 @@ namespace Internal.IL } } - bool isFirstBlock = false; - if(_curBasicBlock.Equals(default(LLVMBasicBlockRef))) - { - isFirstBlock = true; - } _curBasicBlock = GetLLVMBasicBlockForBlock(basicBlock); LLVM.PositionBuilderAtEnd(_builder, _curBasicBlock); - - if(isFirstBlock) - { - GenerateProlog(); - } } private void EndImportingBasicBlock(BasicBlock basicBlock) @@ -305,10 +311,6 @@ namespace Internal.IL LLVM.BuildBr(_builder, GetLLVMBasicBlockForBlock(_basicBlocks[_currentOffset])); } - else - { - LLVM.BuildRet(_builder, default(LLVMValueRef)); - } } } @@ -749,6 +751,7 @@ namespace Internal.IL { offset = PadNextOffset(_signature[i], offset); } + offset = PadOffset(argType, offset); } private void GetLocalSizeAndOffsetAtIndex(int index, out int size, out int offset) @@ -761,6 +764,7 @@ namespace Internal.IL { offset = PadNextOffset(_locals[i].Type, offset); } + offset = PadOffset(local.Type, offset); } private void GetSpillSizeAndOffsetAtIndex(int index, out int size, out int offset) @@ -773,6 +777,7 @@ namespace Internal.IL { offset = PadNextOffset(_spilledExpressions[i].Type, offset); } + offset = PadOffset(spill.Type, offset); } public int PadNextOffset(TypeDesc type, int atOffset) @@ -810,7 +815,7 @@ namespace Internal.IL { TypeDesc type; LLVMValueRef typedLoadLocation = LoadVarAddress(index, argument ? LocalVarKind.Argument : LocalVarKind.Local, out type); - _stack.Push(new AddressExpressionEntry(StackValueKind.ByRef, "ldloca", typedLoadLocation, type.MakePointerType())); + _stack.Push(new AddressExpressionEntry(StackValueKind.ByRef, "ldloca", typedLoadLocation, type.MakeByRefType())); } private void ImportDup() @@ -959,9 +964,46 @@ namespace Internal.IL if (!_compilation.HasFixedSlotVTable(callee.OwningType)) AddVirtualMethodReference(callee); - //TODO: needs runtime support for DispatchByInterface + bool isValueTypeCall = false; + TypeDesc thisType = thisPointer.Type; + TypeFlags category = thisType.Category; + MethodDesc targetMethod = null; + TypeDesc parameterType = null; + + if (category == TypeFlags.ByRef) + { + parameterType = ((ByRefType)thisType).ParameterType; + if (parameterType.IsValueType) + { + isValueTypeCall = true; + } + } if (callee.OwningType.IsInterface) - throw new NotImplementedException("Interface call"); + { + // For value types, devirtualize the call + if (isValueTypeCall) + { + targetMethod = parameterType.ResolveInterfaceMethodTarget(callee); + } + else + { + //TODO: needs runtime support for DispatchByInterface + throw new NotImplementedException("Interface call"); + } + } + else + { + if (isValueTypeCall) + { + targetMethod = parameterType.FindVirtualFunctionTargetMethodOnObjectType(callee); + } + } + + if (targetMethod != null) + { + AddMethodReference(targetMethod); + return GetOrCreateLLVMFunction(_compilation.NameMangler.GetMangledMethodName(targetMethod).ToString()); + } return GetCallableVirtualMethod(thisPointer.ValueAsType(LLVM.PointerType(LLVM.Int8Type(), 0), _builder), callee); } @@ -1204,11 +1246,14 @@ namespace Internal.IL argType = signature[index - instanceAdjustment]; } + // The previous argument might have left this type unaligned, so pad if necessary + argOffset = PadOffset(argType, argOffset); + LLVMTypeRef valueType = GetLLVMTypeForTypeDesc(argType); ImportStoreHelper(toStore.ValueAsType(valueType, _builder), valueType, castShadowStack, (uint)argOffset); - argOffset = PadNextOffset(argType, argOffset); + argOffset += argType.GetElementSize().AsInt; } LLVMValueRef fn; @@ -1382,25 +1427,61 @@ namespace Internal.IL LLVMTypeRef thunkSig = LLVM.FunctionType(GetLLVMTypeForTypeDesc(method.Signature.ReturnType), llvmParams, false); LLVMValueRef thunkFunc = LLVM.AddFunction(compilation.Module, nativeName, thunkSig); - LLVMBasicBlockRef block = LLVM.AppendBasicBlock(thunkFunc, "Block0"); + LLVMBasicBlockRef shadowStackSetupBlock = LLVM.AppendBasicBlock(thunkFunc, "ShadowStackSetupBlock"); + LLVMBasicBlockRef allocateShadowStackBlock = LLVM.AppendBasicBlock(thunkFunc, "allocateShadowStackBlock"); + LLVMBasicBlockRef managedCallBlock = LLVM.AppendBasicBlock(thunkFunc, "ManagedCallBlock"); + LLVMBuilderRef builder = LLVM.CreateBuilder(); - LLVM.PositionBuilderAtEnd(builder, block); + LLVM.PositionBuilderAtEnd(builder, shadowStackSetupBlock); - LLVMValueRef shadowStack = LLVM.BuildLoad(builder, ShadowStackTop, ""); + // Allocate shadow stack if it's null + LLVMValueRef shadowStackPtr = LLVM.BuildAlloca(builder, LLVM.PointerType(LLVM.Int8Type(), 0), "ShadowStackPtr"); + LLVMValueRef savedShadowStack = LLVM.BuildLoad(builder, ShadowStackTop, "SavedShadowStack"); + LLVM.BuildStore(builder, savedShadowStack, shadowStackPtr); + LLVMValueRef shadowStackNull = LLVM.BuildICmp(builder, LLVMIntPredicate.LLVMIntEQ, savedShadowStack, LLVM.ConstPointerNull(LLVM.PointerType(LLVM.Int8Type(), 0)), "ShadowStackNull"); + LLVM.BuildCondBr(builder, shadowStackNull, allocateShadowStackBlock, managedCallBlock); + LLVM.PositionBuilderAtEnd(builder, allocateShadowStackBlock); + + LLVMValueRef newShadowStack = LLVM.BuildArrayMalloc(builder, LLVM.Int8Type(), BuildConstInt32(1000000), "NewShadowStack"); + LLVM.BuildStore(builder, newShadowStack, shadowStackPtr); + LLVM.BuildBr(builder, managedCallBlock); + + LLVM.PositionBuilderAtEnd(builder, managedCallBlock); + LLVMTypeRef reversePInvokeFrameType = LLVM.StructType(new LLVMTypeRef[] { LLVM.PointerType(LLVM.Int8Type(), 0), LLVM.PointerType(LLVM.Int8Type(), 0) }, false); + LLVMValueRef reversePInvokeFrame = default(LLVMValueRef); + LLVMTypeRef reversePInvokeFunctionType = LLVM.FunctionType(LLVM.VoidType(), new LLVMTypeRef[] { LLVM.PointerType(reversePInvokeFrameType, 0) }, false); + if (method.IsNativeCallable) + { + reversePInvokeFrame = LLVM.BuildAlloca(builder, reversePInvokeFrameType, "ReversePInvokeFrame"); + LLVMValueRef RhpReversePInvoke2 = GetOrCreateLLVMFunction("RhpReversePInvoke2", reversePInvokeFunctionType); + LLVM.BuildCall(builder, RhpReversePInvoke2, new LLVMValueRef[] { reversePInvokeFrame }, ""); + } + + LLVMValueRef shadowStack = LLVM.BuildLoad(builder, shadowStackPtr, "ShadowStack"); int curOffset = 0; + curOffset = PadNextOffset(method.Signature.ReturnType, curOffset); + LLVMValueRef calleeFrame = LLVM.BuildGEP(builder, shadowStack, new LLVMValueRef[] { BuildConstInt32(curOffset) }, "calleeFrame"); + for (int i = 0; i < llvmParams.Length; i++) { + curOffset = PadOffset(method.Signature[i], curOffset); LLVMValueRef argAddr = LLVM.BuildGEP(builder, shadowStack, new LLVMValueRef[] { LLVM.ConstInt(LLVM.Int32Type(), (ulong)curOffset, LLVMMisc.False) }, "arg" + i); LLVM.BuildStore(builder, LLVM.GetParam(thunkFunc, (uint)i), CastIfNecessary(builder, argAddr, LLVM.PointerType(llvmParams[i], 0))); curOffset = PadNextOffset(method.Signature[i], curOffset); } - LLVMValueRef retAddr = LLVM.BuildGEP(builder, shadowStack, new LLVMValueRef[] { LLVM.ConstInt(LLVM.Int32Type(), (ulong)curOffset, LLVMMisc.False) }, "retAddr"); - LLVM.BuildCall(builder, managedFunction, new LLVMValueRef[] { shadowStack, retAddr }, ""); + LLVM.BuildCall(builder, managedFunction, new LLVMValueRef[] { calleeFrame, shadowStack }, ""); + + if (method.IsNativeCallable) + { + LLVMValueRef RhpReversePInvokeReturn2 = GetOrCreateLLVMFunction("RhpReversePInvokeReturn2", reversePInvokeFunctionType); + LLVM.BuildCall(builder, RhpReversePInvokeReturn2, new LLVMValueRef[] { reversePInvokeFrame }, ""); + } + if (method.Signature.ReturnType != compilation.TypeSystemContext.GetWellKnownType(WellKnownType.Void)) { - LLVM.BuildRet(builder, LLVM.BuildLoad(builder, CastIfNecessary(builder, retAddr, LLVM.PointerType(GetLLVMTypeForTypeDesc(method.Signature.ReturnType), 0)), "")); + LLVM.BuildRet(builder, LLVM.BuildLoad(builder, CastIfNecessary(builder, shadowStack, LLVM.PointerType(GetLLVMTypeForTypeDesc(method.Signature.ReturnType), 0)), "")); } else { @@ -1470,9 +1551,6 @@ namespace Internal.IL if (opcode == ILOpcode.br) { ImportFallthrough(target); - //TODO: why does this illegal branch happen in System.Reflection.MemberFilter.ctor - if (target.StartOffset == 0) - throw new InvalidProgramException(); LLVM.BuildBr(_builder, GetLLVMBasicBlockForBlock(target)); } else @@ -1593,9 +1671,6 @@ namespace Internal.IL } } } - //TODO: why did this happen only during an optimized build of [System.Private.CoreLib]System.Threading.Lock.ReleaseContended - if (target.StartOffset == 0) - throw new NotImplementedException("cant branch to entry basic block"); ImportFallthrough(target); ImportFallthrough(fallthrough); @@ -1630,7 +1705,7 @@ namespace Internal.IL var expressionPointer = pointer as ExpressionEntry; if(type == null) { - type = GetWellKnownType(WellKnownType.Object).MakeByRefType(); + type = GetWellKnownType(WellKnownType.Object); } LLVMValueRef pointerElementType = pointer.ValueAsType(type.MakePointerType(), _builder); @@ -1827,6 +1902,7 @@ namespace Internal.IL if(enumCleanTargetType != null && targetType.IsPrimitive) { if(enumCleanTargetType.IsWellKnownType(WellKnownType.Byte) || + enumCleanTargetType.IsWellKnownType(WellKnownType.Char) || enumCleanTargetType.IsWellKnownType(WellKnownType.UInt16) || enumCleanTargetType.IsWellKnownType(WellKnownType.UInt32) || enumCleanTargetType.IsWellKnownType(WellKnownType.UInt64) || @@ -2186,7 +2262,49 @@ namespace Internal.IL if (!isStatic) _stack.Pop(); - return WebAssemblyObjectWriter.EmitGlobal(Module, field, _compilation.NameMangler); + ISymbolNode node; + MetadataType owningType = (MetadataType)field.OwningType; + LLVMValueRef staticBase; + int fieldOffset = field.Offset.AsInt; + + // TODO: We need the right thread static per thread + if (field.IsThreadStatic) + { + node = _compilation.NodeFactory.TypeThreadStaticsSymbol(owningType); + staticBase = LoadAddressOfSymbolNode(node); + } + + else if (field.HasGCStaticBase) + { + node = _compilation.NodeFactory.TypeGCStaticsSymbol(owningType); + + // We can't use GCStatics in the data section until we can successfully call + // InitializeModules on startup, so stick with globals for now + //LLVMValueRef basePtrPtr = LoadAddressOfSymbolNode(node); + //staticBase = LLVM.BuildLoad(_builder, LLVM.BuildLoad(_builder, LLVM.BuildPointerCast(_builder, basePtrPtr, LLVM.PointerType(LLVM.PointerType(LLVM.PointerType(LLVM.Int8Type(), 0), 0), 0), "castBasePtrPtr"), "basePtr"), "base"); + staticBase = WebAssemblyObjectWriter.EmitGlobal(Module, field, _compilation.NameMangler); + fieldOffset = 0; + } + else + { + node = _compilation.NodeFactory.TypeNonGCStaticsSymbol(owningType); + staticBase = LoadAddressOfSymbolNode(node); + } + + _dependencies.Add(node); + + // Run static constructor if necessary + // If the type is non-BeforeFieldInit, this is handled before calling any methods on it + if (owningType.IsBeforeFieldInit || (!owningType.IsBeforeFieldInit && owningType != _thisType)) + { + TriggerCctor(owningType); + } + + LLVMValueRef castStaticBase = LLVM.BuildPointerCast(_builder, staticBase, LLVM.PointerType(LLVM.Int8Type(), 0), owningType.Name + "_statics"); + LLVMValueRef fieldAddr = LLVM.BuildGEP(_builder, castStaticBase, new LLVMValueRef[] { BuildConstInt32(fieldOffset) }, field.Name + "_addr"); + + + return fieldAddr; } else { @@ -2194,6 +2312,27 @@ namespace Internal.IL } } + /// <summary> + /// Triggers a static constructor check and call for types that have them + /// </summary> + private void TriggerCctor(MetadataType type) + { + if (_compilation.TypeSystemContext.HasLazyStaticConstructor(type)) + { + ISymbolNode classConstructionContextSymbol = _compilation.NodeFactory.TypeNonGCStaticsSymbol(type); + _dependencies.Add(classConstructionContextSymbol); + LLVMValueRef firstNonGcStatic = LoadAddressOfSymbolNode(classConstructionContextSymbol); + + // TODO: Codegen could check whether it has already run rather than calling into EnsureClassConstructorRun + // but we'd have to figure out how to manage the additional basic blocks + LLVMValueRef classConstructionContextPtr = LLVM.BuildGEP(_builder, firstNonGcStatic, new LLVMValueRef[] { BuildConstInt32(-2) }, "classConstructionContext"); + StackEntry classConstructionContext = new AddressExpressionEntry(StackValueKind.NativeInt, "classConstructionContext", classConstructionContextPtr, GetWellKnownType(WellKnownType.IntPtr)); + MetadataType helperType = _compilation.TypeSystemContext.SystemModule.GetKnownType("System.Runtime.CompilerServices", "ClassConstructorRunner"); + MethodDesc helperMethod = helperType.GetKnownMethod("EnsureClassConstructorRun", null); + HandleCall(helperMethod, helperMethod.Signature, new StackEntry[] { classConstructionContext }); + } + } + private void ImportLoadField(int token, bool isStatic) { FieldDesc field = (FieldDesc)_methodIL.GetObject(token); @@ -2205,7 +2344,7 @@ namespace Internal.IL { FieldDesc field = (FieldDesc)_methodIL.GetObject(token); LLVMValueRef fieldAddress = GetFieldAddress(field, isStatic); - _stack.Push(new AddressExpressionEntry(StackValueKind.ByRef, "ldflda", fieldAddress, field.FieldType.MakePointerType())); + _stack.Push(new AddressExpressionEntry(StackValueKind.ByRef, "ldflda", fieldAddress, field.FieldType.MakeByRefType())); } private void ImportStoreField(int token, bool isStatic) @@ -2372,6 +2511,9 @@ namespace Internal.IL private void ImportEndFinally() { + // These are currently unreachable since we can't get into finally blocks. + // We'll need to change this once we have other finally block handling. + LLVM.BuildUnreachable(_builder); } private void ImportFallthrough(BasicBlock next) @@ -2418,9 +2560,6 @@ namespace Internal.IL if (entry == null) throw new InvalidProgramException(); - if (currentEntry is SpilledExpressionEntry) - continue; //this is already a sharable value - StoreTemp(entry.LocalIndex, currentEntry.ValueAsType(entry.Type, _builder)); } } @@ -2453,16 +2592,11 @@ namespace Internal.IL private StackEntry NewSpillSlot(StackEntry entry) { - if (entry is SpilledExpressionEntry) - return entry; - else - { - var entryType = entry.Type ?? GetWellKnownType(WellKnownType.Object); //type is required here, currently the only time entry.Type is null is if someone has pushed a null literal - var entryIndex = _spilledExpressions.Count; - var newEntry = new SpilledExpressionEntry(entry.Kind, entry is ExpressionEntry ? ((ExpressionEntry)entry).Name : "spilled" + entryIndex, entryType, entryIndex, this); - _spilledExpressions.Add(newEntry); - return newEntry; - } + var entryType = entry.Type ?? GetWellKnownType(WellKnownType.Object); //type is required here, currently the only time entry.Type is null is if someone has pushed a null literal + var entryIndex = _spilledExpressions.Count; + var newEntry = new SpilledExpressionEntry(entry.Kind, entry is ExpressionEntry ? ((ExpressionEntry)entry).Name : "spilled" + entryIndex, entryType, entryIndex, this); + _spilledExpressions.Add(newEntry); + return newEntry; } private StackEntry TakeAddressOf(StackEntry entry) @@ -2535,6 +2669,7 @@ namespace Internal.IL TrapFunction = LLVM.AddFunction(Module, "llvm.trap", LLVM.FunctionType(LLVM.VoidType(), Array.Empty<LLVMTypeRef>(), false)); } LLVM.BuildCall(_builder, TrapFunction, Array.Empty<LLVMValueRef>(), string.Empty); + LLVM.BuildUnreachable(_builder); } private void EmitDoNothingCall() diff --git a/src/ILCompiler.WebAssembly/src/CodeGen/WebAssemblyObjectWriter.cs b/src/ILCompiler.WebAssembly/src/CodeGen/WebAssemblyObjectWriter.cs index 8808a2e02..29a6a26cd 100644 --- a/src/ILCompiler.WebAssembly/src/CodeGen/WebAssemblyObjectWriter.cs +++ b/src/ILCompiler.WebAssembly/src/CodeGen/WebAssemblyObjectWriter.cs @@ -243,7 +243,13 @@ namespace ILCompiler.DependencyAnalysis LLVMTypeRef reversePInvokeFrameType = LLVM.StructType(new LLVMTypeRef[] { LLVM.PointerType(LLVM.Int8Type(), 0), LLVM.PointerType(LLVM.Int8Type(), 0) }, false); LLVMValueRef reversePinvokeFrame = LLVM.BuildAlloca(builder, reversePInvokeFrameType, "ReversePInvokeFrame"); - LLVMValueRef RhpReversePInvoke2 = LLVM.AddFunction(Module, "RhpReversePInvoke2", LLVM.FunctionType(LLVM.VoidType(), new LLVMTypeRef[] { LLVM.PointerType(reversePInvokeFrameType, 0) }, false)); + LLVMValueRef RhpReversePInvoke2 = LLVM.GetNamedFunction(Module, "RhpReversePInvoke2"); + + if (RhpReversePInvoke2.Pointer == IntPtr.Zero) + { + RhpReversePInvoke2 = LLVM.AddFunction(Module, "RhpReversePInvoke2", LLVM.FunctionType(LLVM.VoidType(), new LLVMTypeRef[] { LLVM.PointerType(reversePInvokeFrameType, 0) }, false)); + } + LLVM.BuildCall(builder, RhpReversePInvoke2, new LLVMValueRef[] { reversePinvokeFrame }, ""); var shadowStack = LLVM.BuildMalloc(builder, LLVM.ArrayType(LLVM.Int8Type(), 1000000), String.Empty); diff --git a/src/Native/Runtime/unix/PalRedhawkInline.h b/src/Native/Runtime/unix/PalRedhawkInline.h index e46d9ce87..67e197e72 100644 --- a/src/Native/Runtime/unix/PalRedhawkInline.h +++ b/src/Native/Runtime/unix/PalRedhawkInline.h @@ -43,7 +43,11 @@ FORCEINLINE Int32 PalInterlockedCompareExchange(_Inout_ _Interlocked_operand_ In FORCEINLINE Int64 PalInterlockedCompareExchange64(_Inout_ _Interlocked_operand_ Int64 volatile *pDst, Int64 iValue, Int64 iComparand) { +#if defined(_WASM_) + PORTABILITY_ASSERT("Emscripten does not support 64-bit atomics until version 1.37.33"); +#else // _WASM_ return __sync_val_compare_and_swap(pDst, iComparand, iValue); +#endif // _WASM_ } #if defined(_AMD64_) diff --git a/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/ClassConstructorRunner.cs b/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/ClassConstructorRunner.cs index 51abcf771..39708d993 100644 --- a/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/ClassConstructorRunner.cs +++ b/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/ClassConstructorRunner.cs @@ -279,6 +279,19 @@ namespace System.Runtime.CompilerServices #else const int Grow = 10; #endif + + // WASMTODO: Remove this when the Initialize method gets called by the runtime startup +#if WASM + if (s_cctorGlobalLock == null) + { + Interlocked.CompareExchange(ref s_cctorGlobalLock, new Lock(), null); + } + if (s_cctorArrays == null) + { + Interlocked.CompareExchange(ref s_cctorArrays, new Cctor[10][], null); + } +#endif // WASM + using (LockHolder.Hold(s_cctorGlobalLock)) { Cctor[] resultArray = null; diff --git a/tests/src/Simple/HelloWasm/Program.cs b/tests/src/Simple/HelloWasm/Program.cs index 75864c2e0..2b925142b 100644 --- a/tests/src/Simple/HelloWasm/Program.cs +++ b/tests/src/Simple/HelloWasm/Program.cs @@ -55,6 +55,8 @@ internal static class Program PrintLine("thread static int field test: Ok."); } + StaticCtorTest(); + var boxedInt = (object)tempInt; if(((int)boxedInt) == 9) { @@ -208,16 +210,20 @@ internal static class Program ldindTest(); - System.Diagnostics.Debugger.Break(); + InterfaceDispatchTest(); - var testRuntimeHelpersInitArray = new long[] {1, 2, 3}; - if(testRuntimeHelpersInitArray[0] == 1 && + var testRuntimeHelpersInitArray = new long[] { 1, 2, 3 }; + if (testRuntimeHelpersInitArray[0] == 1 && testRuntimeHelpersInitArray[1] == 2 && testRuntimeHelpersInitArray[2] == 3) { PrintLine("Runtime.Helpers array initialization test: Ok."); } + // This test should remain last to get other results before stopping the debugger + PrintLine("Debugger.Break() test: Ok if debugger is open and breaks."); + System.Diagnostics.Debugger.Break(); + PrintLine("Done"); } @@ -346,6 +352,53 @@ internal static class Program } } + private static void InterfaceDispatchTest() + { + ItfStruct itfStruct = new ItfStruct(); + if (ItfCaller(itfStruct) == 4) + { + PrintLine("Struct interface test: Ok."); + } + } + + // Calls the ITestItf interface via a generic to ensure the concrete type is known and + // an interface call is generated instead of a virtual or direct call + private static int ItfCaller<T>(T obj) where T : ITestItf + { + return obj.GetValue(); + } + + private static void StaticCtorTest() + { + BeforeFieldInitTest.Nop(); + if (StaticsInited.BeforeFieldInitInited) + { + PrintLine("BeforeFieldInitType inited too early"); + } + else + { + int x = BeforeFieldInitTest.TestField; + if (StaticsInited.BeforeFieldInitInited) + { + PrintLine("BeforeFieldInit test: Ok."); + } + else + { + PrintLine("BeforeFieldInit cctor not run"); + } + } + + NonBeforeFieldInitTest.Nop(); + if (StaticsInited.NonBeforeFieldInitInited) + { + PrintLine("NonBeforeFieldInit test: Ok."); + } + else + { + PrintLine("NonBeforeFieldInitType cctor not run"); + } + } + [DllImport("*")] private static unsafe extern int printf(byte* str, byte* unused); } @@ -433,6 +486,38 @@ public class TestDerivedClass : TestClass } } +public class StaticsInited +{ + public static bool BeforeFieldInitInited; + public static bool NonBeforeFieldInitInited; +} + +public class BeforeFieldInitTest +{ + public static int TestField = BeforeFieldInit(); + + public static void Nop() { } + + static int BeforeFieldInit() + { + StaticsInited.BeforeFieldInitInited = true; + return 3; + } +} + +public class NonBeforeFieldInitTest +{ + public static int TestField; + + public static void Nop() { } + + static NonBeforeFieldInitTest() + { + TestField = 4; + StaticsInited.NonBeforeFieldInitInited = true; + } +} + public interface ICastingTest1 { int GetValue(); @@ -457,3 +542,16 @@ public class DerivedCastingTestClass2 : CastingTestClass, ICastingTest2 { public override int GetValue() => 2; } + +public interface ITestItf +{ + int GetValue(); +} + +public struct ItfStruct : ITestItf +{ + public int GetValue() + { + return 4; + } +} |