diff --git a/src/Common/src/TypeSystem/Common/FieldLayout.cs b/src/Common/src/TypeSystem/Common/FieldLayout.cs index 36c33926531..2ab7993115c 100644 --- a/src/Common/src/TypeSystem/Common/FieldLayout.cs +++ b/src/Common/src/TypeSystem/Common/FieldLayout.cs @@ -153,6 +153,26 @@ out byteCount return algorithm._computedLayout; } + #region Runtime specific adjustements to the static field layout + // TODO: these should be factored out to make the static field layout algorithm more general purpose + + private static void PrepareRuntimeSpecificStaticFieldLayout(TypeSystemContext context, ref ComputedStaticFieldLayout layout) + { + // GC statics start with a pointer to the "EEType" that signals the size and GCDesc to the GC + layout.GcStatics.Size = context.Target.PointerSize; + } + + private static void FinalizeRuntimeSpecificStaticFieldLayout(TypeSystemContext context, ref ComputedStaticFieldLayout layout) + { + // If the size of GCStatics is equal to the size set in PrepareRuntimeSpecificStaticFieldLayout, we + // don't have any GC statics + if (layout.GcStatics.Size == context.Target.PointerSize) + { + layout.GcStatics.Size = 0; + } + } + #endregion + public static unsafe ComputedStaticFieldLayout ComputeStaticFieldLayout(MetadataType type) { int numStaticFields = 0; @@ -178,6 +198,8 @@ public static unsafe ComputedStaticFieldLayout ComputeStaticFieldLayout(Metadata result.Offsets = new FieldAndOffset[numStaticFields]; + PrepareRuntimeSpecificStaticFieldLayout(type.Context, ref result); + int index = 0; foreach (var field in type.GetFields()) @@ -206,6 +228,8 @@ public static unsafe ComputedStaticFieldLayout ComputeStaticFieldLayout(Metadata index++; } + FinalizeRuntimeSpecificStaticFieldLayout(type.Context, ref result); + return result; } @@ -538,6 +562,30 @@ public int NonGCStaticFieldAlignment } } + public int GCStaticFieldSize + { + get + { + if (!_fieldLayoutFlags.HasFlags(FieldLayoutFlags.HasStaticFieldLayout)) + { + ComputeStaticFieldLayout(); + } + return _staticBlockInfo == null ? 0 : _staticBlockInfo.GcStatics.Size; + } + } + + public int GCStaticFieldAlignment + { + get + { + if (!_fieldLayoutFlags.HasFlags(FieldLayoutFlags.HasStaticFieldLayout)) + { + ComputeStaticFieldLayout(); + } + return _staticBlockInfo == null ? 0 : _staticBlockInfo.GcStatics.LargestAlignment; + } + } + internal void ComputeInstanceFieldLayout() { var computedLayout = FieldLayoutAlgorithm.ComputeInstanceFieldLayout(this); diff --git a/src/ILToNative.Compiler/src/Compiler/AsmWriter.cs b/src/ILToNative.Compiler/src/Compiler/AsmWriter.cs index 7c64a7ec2a5..28bfb3cdee7 100644 --- a/src/ILToNative.Compiler/src/Compiler/AsmWriter.cs +++ b/src/ILToNative.Compiler/src/Compiler/AsmWriter.cs @@ -30,13 +30,18 @@ void OutputCode() Out.WriteLine(); OutputReadyToHelpers(); - OutputEETypes(); Out.WriteLine(); Out.WriteLine(".data"); - OutputStatics(); + Out.WriteLine(); + Out.WriteLine("// Non-GC statics"); + OutputNonGCStatics(); + + Out.WriteLine(); + Out.WriteLine("// GC statics"); + OutputGCStatics(); Out.Dispose(); } @@ -248,6 +253,15 @@ void OutputReadyToHelpers() Out.WriteLine("ret"); break; + case ReadyToRunHelperId.GetGCStaticBase: + Out.Write("leaq __GCStaticBase_"); + Out.Write(NameMangler.GetMangledTypeName((TypeDesc)helper.Target)); + Out.WriteLine("(%rip), %rax"); + Out.WriteLine("mov (%rax), %rax"); + Out.WriteLine("mov (%rax), %rax"); // RAX is now a GC pointer + Out.WriteLine("ret"); + break; + default: throw new NotImplementedException(); } @@ -296,7 +310,7 @@ void OutputEETypes() } } - void OutputStatics() + void OutputNonGCStatics() { foreach (var t in _registeredTypes.Values) { @@ -323,6 +337,73 @@ void OutputStatics() } } + void OutputGCStatics() + { + // Emit an array of GCHandle-sized elements for each type with GC statics + // Each element will be initially pointing at the pseudo EEType for the static. + // At runtime, it will be replaced by a GC handle to the GC-heap allocated object. + + Out.WriteLine(".global __GCStaticRegionStart"); + Out.WriteLine("__GCStaticRegionStart:"); + + foreach (var t in _registeredTypes.Values) + { + if (!t.IncludedInCompilation) + continue; + + var type = t.Type as MetadataType; + if (type == null) + continue; + + if (type.GCStaticFieldSize > 0) + { + Out.Write("__GCStaticBase_"); + Out.Write(NameMangler.GetMangledTypeName(type)); + Out.WriteLine(":"); + Out.Write(".quad "); + Out.Write("__GCStaticEEType_"); + Out.Write(NameMangler.GetMangledTypeName(type)); + Out.WriteLine(); + } + } + + Out.WriteLine(".global __GCStaticRegionEnd"); + Out.WriteLine("__GCStaticRegionEnd:"); + + // Next emit a GCDesc followed by the size of the region described by the GCDesc + // for each type with GC statics. + + // It should be possible to intern these at some point. + + foreach (var t in _registeredTypes.Values) + { + if (!t.IncludedInCompilation) + continue; + + var type = t.Type as MetadataType; + if (type == null) + continue; + + if (type.GCStaticFieldSize > 0) + { + // numSeries + Out.WriteLine(".quad 0"); + + Out.Write("__GCStaticEEType_"); + Out.Write(NameMangler.GetMangledTypeName(type)); + Out.WriteLine(":"); + Out.WriteLine(".int 0"); + Out.Write(".int "); + + // GC requires a minimum object size + int minimumObjectSize = type.Context.Target.PointerSize * 3; + int gcStaticSize = Math.Max(type.GCStaticFieldSize, minimumObjectSize); + + Out.WriteLine(gcStaticSize); + } + } + } + void OutputVirtualSlots(TypeDesc implType, TypeDesc declType) { var baseType = declType.BaseType; diff --git a/src/ILToNative.Compiler/src/Compiler/JitHelper.cs b/src/ILToNative.Compiler/src/Compiler/JitHelper.cs index 7c86f0815b8..a9a6631385c 100644 --- a/src/ILToNative.Compiler/src/Compiler/JitHelper.cs +++ b/src/ILToNative.Compiler/src/Compiler/JitHelper.cs @@ -11,6 +11,7 @@ namespace ILToNative public enum JitHelperId { RngChkFail, + AssignRef, } class JitHelper @@ -35,6 +36,9 @@ public string MangledName case JitHelperId.RngChkFail: return "__range_check_fail"; + case JitHelperId.AssignRef: + return "WriteBarrier"; + default: throw new NotImplementedException(); } diff --git a/src/ILToNative.Compiler/src/Compiler/ReadyToRunHelper.cs b/src/ILToNative.Compiler/src/Compiler/ReadyToRunHelper.cs index 55ace677b0b..e4b5b49b484 100644 --- a/src/ILToNative.Compiler/src/Compiler/ReadyToRunHelper.cs +++ b/src/ILToNative.Compiler/src/Compiler/ReadyToRunHelper.cs @@ -19,6 +19,7 @@ public enum ReadyToRunHelperId IsInstanceOf, CastClass, GetNonGCStaticBase, + GetGCStaticBase, } class ReadyToRunHelper @@ -54,6 +55,8 @@ public string MangledName return "__CastClass_" + _compilation.NameMangler.GetMangledTypeName((TypeDesc)this.Target); case ReadyToRunHelperId.GetNonGCStaticBase: return "__GetNonGCStaticBase_" + _compilation.NameMangler.GetMangledTypeName((TypeDesc)this.Target); + case ReadyToRunHelperId.GetGCStaticBase: + return "__GetGCStaticBase_" + _compilation.NameMangler.GetMangledTypeName((TypeDesc)this.Target); default: throw new NotImplementedException(); } diff --git a/src/ILToNative.TypeSystem/tests/StaticFieldLayoutTests.cs b/src/ILToNative.TypeSystem/tests/StaticFieldLayoutTests.cs index d203803c8b8..b2cc1cbe932 100644 --- a/src/ILToNative.TypeSystem/tests/StaticFieldLayoutTests.cs +++ b/src/ILToNative.TypeSystem/tests/StaticFieldLayoutTests.cs @@ -124,10 +124,10 @@ public void TestHasPointers() switch (field.Name) { case "string1": - Assert.Equal(0, field.Offset); + Assert.Equal(8, field.Offset); break; case "class1": - Assert.Equal(8, field.Offset); + Assert.Equal(16, field.Offset); break; default: throw new Exception(field.Name); @@ -152,19 +152,19 @@ public void TestMixPointersAndNonPointers() switch (field.Name) { case "string1": - Assert.Equal(0, field.Offset); + Assert.Equal(8, field.Offset); break; case "int1": Assert.Equal(0, field.Offset); break; case "class1": - Assert.Equal(8, field.Offset); + Assert.Equal(16, field.Offset); break; case "int2": Assert.Equal(4, field.Offset); break; case "string2": - Assert.Equal(16, field.Offset); + Assert.Equal(24, field.Offset); break; default: throw new Exception(field.Name); @@ -193,7 +193,7 @@ public void TestEnsureInheritanceResetsStaticOffsets() Assert.Equal(0, field.Offset); break; case "string3": - Assert.Equal(0, field.Offset); + Assert.Equal(8, field.Offset); break; default: throw new Exception(field.Name); diff --git a/src/ILToNative/reproNative/main.cpp b/src/ILToNative/reproNative/main.cpp index e643b29c723..7a6e8da7736 100644 --- a/src/ILToNative/reproNative/main.cpp +++ b/src/ILToNative/reproNative/main.cpp @@ -81,11 +81,24 @@ void __reverse_pinvoke_return(ReversePInvokeFrame* pRevFrame) #endif // USE_MRT } +extern "C" void* __GCStaticRegionStart; +extern "C" void* __GCStaticRegionEnd; + void __register_module(SimpleModuleHeader* pModule) { #if USE_MRT RhpRegisterSimpleModule(pModule); #endif // USE_MRT + + + // Initialize GC statics in the module + // TODO: emit a ModuleHeader and use it here + + for (void** currentBlock = &__GCStaticRegionStart; currentBlock < &__GCStaticRegionEnd; currentBlock++) + { + Object* gcBlock = __allocate_object((MethodTable*)*currentBlock); + *currentBlock = CreateGlobalHandle(ObjectToOBJECTREF(gcBlock)); + } } namespace mscorlib { namespace System { @@ -231,6 +244,38 @@ extern "C" Object * __allocate_array(size_t elements, MethodTable * pMT) #endif } +#if defined(_WIN64) +// Card byte shift is different on 64bit. +#define card_byte_shift 11 +#else +#define card_byte_shift 10 +#endif + +#define card_byte(addr) (((size_t)(addr)) >> card_byte_shift) + +inline void ErectWriteBarrier(Object ** dst, Object * ref) +{ + // if the dst is outside of the heap (unboxed value classes) then we + // simply exit + if (((BYTE*)dst < g_lowest_address) || ((BYTE*)dst >= g_highest_address)) + return; + + if ((BYTE*)ref >= g_ephemeral_low && (BYTE*)ref < g_ephemeral_high) + { + // volatile is used here to prevent fetch of g_card_table from being reordered + // with g_lowest/highest_address check above. See comment in code:gc_heap::grow_brick_card_tables. + BYTE* pCardByte = (BYTE *)*(volatile BYTE **)(&g_card_table) + card_byte((BYTE *)dst); + if (*pCardByte != 0xFF) + *pCardByte = 0xFF; + } +} + +extern "C" void WriteBarrier(Object ** dst, Object * ref) +{ + *dst = ref; + ErectWriteBarrier(dst, ref); +} + void __throw_exception(void * pEx) { // TODO: Exception throwing diff --git a/src/JitInterface/src/CorInfoImpl.cs b/src/JitInterface/src/CorInfoImpl.cs index 50cff30ac72..3187e4ce504 100644 --- a/src/JitInterface/src/CorInfoImpl.cs +++ b/src/JitInterface/src/CorInfoImpl.cs @@ -861,10 +861,14 @@ void getFieldInfo(IntPtr _this, ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORIN pResult.helper = CorInfoHelpFunc.CORINFO_HELP_READYTORUN_STATIC_BASE; ReadyToRunHelperId helperId; - if (field.IsThreadStatic || field.HasGCStaticBase) + if (field.IsThreadStatic) { throw new NotImplementedException(); } + else if (field.HasGCStaticBase) + { + helperId = ReadyToRunHelperId.GetGCStaticBase; + } else { helperId = ReadyToRunHelperId.GetNonGCStaticBase; @@ -1066,6 +1070,8 @@ uint getThreadTLSIndex(IntPtr _this, ref void* ppIndirection) { case CorInfoHelpFunc.CORINFO_HELP_RNGCHKFAIL: return (void*)ObjectToHandle(_compilation.GetJitHelper(JitHelperId.RngChkFail)); + case CorInfoHelpFunc.CORINFO_HELP_ASSIGN_REF: + return (void*)ObjectToHandle(_compilation.GetJitHelper(JitHelperId.AssignRef)); default: throw new NotImplementedException(); }