From 2aa500454092e9ec2ef9f1842019c440e3b0270a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Wed, 7 Oct 2015 18:25:39 -0700 Subject: [PATCH 1/2] Add support for GC static fields * Expose GC static size and alignment from MetadataType * Add __assignReference JIT helper * Emit GC static data section into the assembly file --- .../src/TypeSystem/Common/FieldLayout.cs | 24 +++++++++ .../src/Compiler/AsmWriter.cs | 54 ++++++++++++++++++- .../src/Compiler/JitHelper.cs | 4 ++ .../src/Compiler/ReadyToRunHelper.cs | 3 ++ src/JitInterface/src/CorInfoImpl.cs | 8 ++- 5 files changed, 90 insertions(+), 3 deletions(-) diff --git a/src/Common/src/TypeSystem/Common/FieldLayout.cs b/src/Common/src/TypeSystem/Common/FieldLayout.cs index 36c33926531..90ced54f0e5 100644 --- a/src/Common/src/TypeSystem/Common/FieldLayout.cs +++ b/src/Common/src/TypeSystem/Common/FieldLayout.cs @@ -538,6 +538,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..5f3d0b72f38 100644 --- a/src/ILToNative.Compiler/src/Compiler/AsmWriter.cs +++ b/src/ILToNative.Compiler/src/Compiler/AsmWriter.cs @@ -30,13 +30,20 @@ void OutputCode() Out.WriteLine(); OutputReadyToHelpers(); + OutputJitHelpers(); OutputEETypes(); Out.WriteLine(); Out.WriteLine(".data"); - OutputStatics(); + Out.WriteLine(); + Out.WriteLine("// Non-GC statics"); + OutputNonGCStatics(); + + Out.WriteLine(); + Out.WriteLine("// GC statics"); + OutputGCStatics(); Out.Dispose(); } @@ -162,6 +169,15 @@ void OutputMethodCode(RegisteredMethod m) Out.WriteLine(); } + void OutputJitHelpers() + { + // assignReference is always the same. This will be a call into the runtime. + Out.WriteLine("__assignReference:"); + Out.WriteLine("movq %rdx, (%rcx)"); + Out.WriteLine("ret"); + Out.WriteLine(); + } + void OutputReadyToHelpers() { foreach (var helper in _readyToRunHelpers.Values) @@ -248,6 +264,13 @@ 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("ret"); + break; + default: throw new NotImplementedException(); } @@ -296,7 +319,7 @@ void OutputEETypes() } } - void OutputStatics() + void OutputNonGCStatics() { foreach (var t in _registeredTypes.Values) { @@ -323,6 +346,33 @@ void OutputStatics() } } + void OutputGCStatics() + { + 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(".align "); + Out.WriteLine(type.GCStaticFieldAlignment); + Out.Write("__GCStaticBase_"); + Out.Write(NameMangler.GetMangledTypeName(type)); + Out.WriteLine(":"); + Out.Write(".rept "); + Out.WriteLine(type.GCStaticFieldSize); + Out.WriteLine(".byte 0"); + Out.WriteLine(".endr"); + Out.WriteLine(); + } + } + } + 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..7961130f2e2 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 "__assignReference"; + 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/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(); } From 377847927a0187b7a0ee80195fcafb6fe686fb0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Mon, 12 Oct 2015 13:28:18 -0700 Subject: [PATCH 2/2] Allocate GC statics on the GC heap We were previously allocating the GC static region statically in the image, but for Ready to Run, we will require GC static regions to be allocated on the GC heap. --- .../src/TypeSystem/Common/FieldLayout.cs | 24 +++++++ .../src/Compiler/AsmWriter.cs | 65 ++++++++++++++----- .../src/Compiler/JitHelper.cs | 2 +- .../tests/StaticFieldLayoutTests.cs | 12 ++-- src/ILToNative/reproNative/main.cpp | 45 +++++++++++++ 5 files changed, 124 insertions(+), 24 deletions(-) diff --git a/src/Common/src/TypeSystem/Common/FieldLayout.cs b/src/Common/src/TypeSystem/Common/FieldLayout.cs index 90ced54f0e5..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; } diff --git a/src/ILToNative.Compiler/src/Compiler/AsmWriter.cs b/src/ILToNative.Compiler/src/Compiler/AsmWriter.cs index 5f3d0b72f38..28bfb3cdee7 100644 --- a/src/ILToNative.Compiler/src/Compiler/AsmWriter.cs +++ b/src/ILToNative.Compiler/src/Compiler/AsmWriter.cs @@ -30,8 +30,6 @@ void OutputCode() Out.WriteLine(); OutputReadyToHelpers(); - OutputJitHelpers(); - OutputEETypes(); Out.WriteLine(); @@ -169,15 +167,6 @@ void OutputMethodCode(RegisteredMethod m) Out.WriteLine(); } - void OutputJitHelpers() - { - // assignReference is always the same. This will be a call into the runtime. - Out.WriteLine("__assignReference:"); - Out.WriteLine("movq %rdx, (%rcx)"); - Out.WriteLine("ret"); - Out.WriteLine(); - } - void OutputReadyToHelpers() { foreach (var helper in _readyToRunHelpers.Values) @@ -268,6 +257,8 @@ void OutputReadyToHelpers() 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; @@ -348,6 +339,13 @@ void OutputNonGCStatics() 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) @@ -359,17 +357,50 @@ void OutputGCStatics() if (type.GCStaticFieldSize > 0) { - Out.Write(".align "); - Out.WriteLine(type.GCStaticFieldAlignment); Out.Write("__GCStaticBase_"); Out.Write(NameMangler.GetMangledTypeName(type)); Out.WriteLine(":"); - Out.Write(".rept "); - Out.WriteLine(type.GCStaticFieldSize); - Out.WriteLine(".byte 0"); - Out.WriteLine(".endr"); + 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); + } } } diff --git a/src/ILToNative.Compiler/src/Compiler/JitHelper.cs b/src/ILToNative.Compiler/src/Compiler/JitHelper.cs index 7961130f2e2..a9a6631385c 100644 --- a/src/ILToNative.Compiler/src/Compiler/JitHelper.cs +++ b/src/ILToNative.Compiler/src/Compiler/JitHelper.cs @@ -37,7 +37,7 @@ public string MangledName return "__range_check_fail"; case JitHelperId.AssignRef: - return "__assignReference"; + return "WriteBarrier"; 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