diff --git a/src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter.cs b/src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter.cs
index 48d6974320c..59daf5f9e30 100644
--- a/src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter.cs
+++ b/src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter.cs
@@ -1072,7 +1072,7 @@ private void ImportStoreVar(int index, bool argument)
TypeDesc varType;
StackEntry toStore = _stack.Pop();
LLVMValueRef varAddress = LoadVarAddress(index, argument ? LocalVarKind.Argument : LocalVarKind.Local, out varType);
- CastingStore(varAddress, toStore, varType, $"Variable{index}_");
+ CastingStore(varAddress, toStore, varType, false, $"Variable{index}_");
}
private void ImportStoreHelper(LLVMValueRef toStore, LLVMTypeRef valueType, LLVMValueRef basePtr, uint offset, string name = null, LLVMBuilderRef builder = default(LLVMBuilderRef))
@@ -1104,10 +1104,75 @@ private LLVMValueRef CastToPointerToTypeDesc(LLVMValueRef source, TypeDesc type,
return CastIfNecessary(source, LLVMTypeRef.CreatePointer(GetLLVMTypeForTypeDesc(type), 0), (name ?? "") + type.ToString());
}
- private void CastingStore(LLVMValueRef address, StackEntry value, TypeDesc targetType, string targetName = null)
+ private void CastingStore(LLVMValueRef address, StackEntry value, TypeDesc targetType, bool withBarrier, string targetName = null)
{
- var typedStoreLocation = CastToPointerToTypeDesc(address, targetType, targetName);
- _builder.BuildStore(value.ValueAsType(targetType, _builder), typedStoreLocation);
+ if (withBarrier && targetType.IsGCPointer)
+ {
+ CallRuntime(_method.Context, "InternalCalls", "RhpAssignRef", new StackEntry[]
+ {
+ new ExpressionEntry(StackValueKind.Int32, "address", address), value
+ });
+ }
+ else
+ {
+ var typedStoreLocation = CastToPointerToTypeDesc(address, targetType, targetName);
+ var llvmValue = value.ValueAsType(targetType, _builder);
+ if (withBarrier && IsStruct(targetType))
+ {
+ StoreStruct(address, llvmValue, targetType, typedStoreLocation);
+ }
+ else
+ {
+ _builder.BuildStore(llvmValue, typedStoreLocation);
+ }
+ }
+ }
+
+ private static bool IsStruct(TypeDesc typeDesc)
+ {
+ return typeDesc.IsValueType && !typeDesc.IsPrimitive && !typeDesc.IsEnum;
+ }
+
+ private void StoreStruct(LLVMValueRef address, LLVMValueRef llvmValue, TypeDesc targetType, LLVMValueRef typedStoreLocation, bool childStruct = false)
+ {
+ foreach (FieldDesc f in targetType.GetFields())
+ {
+ if (f.IsStatic) continue;
+ if (IsStruct(f.FieldType) && llvmValue.TypeOf.IsPackedStruct)
+ {
+ LLVMValueRef targetAddress = _builder.BuildGEP(address, new[] { BuildConstInt32(f.Offset.AsInt) });
+ uint index = LLVMSharpInterop.ElementAtOffset(_compilation.TargetData, llvmValue.TypeOf, (ulong)f.Offset.AsInt);
+ LLVMValueRef fieldValue = _builder.BuildExtractValue(llvmValue, index);
+ //recurse into struct
+ StoreStruct(targetAddress, fieldValue, f.FieldType, CastToPointerToTypeDesc(targetAddress, f.FieldType), true);
+ }
+ else if (f.FieldType.IsGCPointer)
+ {
+ LLVMValueRef targetAddress = _builder.BuildGEP(address, new[] {BuildConstInt32(f.Offset.AsInt)});
+ LLVMValueRef fieldValue;
+ if (llvmValue.TypeOf.IsPackedStruct)
+ {
+ uint index = LLVMSharpInterop.ElementAtOffset(_compilation.TargetData, llvmValue.TypeOf, (ulong) f.Offset.AsInt);
+ fieldValue = _builder.BuildExtractValue(llvmValue, index);
+ Debug.Assert(fieldValue.TypeOf.Kind == LLVMTypeKind.LLVMPointerTypeKind, "expected an LLVM pointer type");
+ }
+ else
+ {
+ // single field IL structs are not LLVM structs
+ fieldValue = llvmValue;
+ }
+ CallRuntime(_method.Context, "InternalCalls", "RhpAssignRef",
+ new StackEntry[]
+ {
+ new ExpressionEntry(StackValueKind.Int32, "targetAddress", targetAddress),
+ new ExpressionEntry(StackValueKind.ObjRef, "sourceAddress", fieldValue)
+ });
+ }
+ }
+ if (!childStruct)
+ {
+ _builder.BuildStore(llvmValue, typedStoreLocation); // just copy all the fields again for simplicity, if all the fields where set using RhpAssignRef then a possible optimisation would be to skip this line
+ }
}
private LLVMValueRef CastIfNecessary(LLVMValueRef source, LLVMTypeRef valueType, string name = null, bool unsigned = false)
@@ -3578,7 +3643,6 @@ private void ImportLoadIndirect(TypeDesc type)
{
var pointer = _stack.Pop();
Debug.Assert(pointer is ExpressionEntry || pointer is ConstantEntry);
- var expressionPointer = pointer as ExpressionEntry;
if (type == null)
{
type = GetWellKnownType(WellKnownType.Object);
@@ -3600,19 +3664,36 @@ private void ImportStoreIndirect(TypeDesc type)
StackEntry destinationPointer = _stack.Pop();
LLVMValueRef typedValue;
LLVMValueRef typedPointer;
+ bool requireWriteBarrier;
if (type != null)
{
- typedValue = value.ValueAsType(type, _builder);
typedPointer = destinationPointer.ValueAsType(type.MakePointerType(), _builder);
+ typedValue = value.ValueAsType(type, _builder);
+ if (IsStruct(type))
+ {
+ StoreStruct(typedPointer, typedValue, type, typedPointer);
+ return;
+ }
+ requireWriteBarrier = type.IsGCPointer;
}
else
{
typedPointer = destinationPointer.ValueAsType(LLVMTypeRef.CreatePointer(LLVMTypeRef.Int32, 0), _builder);
typedValue = value.ValueAsInt32(_builder, false);
+ requireWriteBarrier = (value is ExpressionEntry) && !((ExpressionEntry)value).RawLLVMValue.IsNull && value.Type.IsGCPointer;
+ }
+ if (requireWriteBarrier)
+ {
+ CallRuntime(_method.Context, "InternalCalls", "RhpAssignRef", new StackEntry[]
+ {
+ new ExpressionEntry(StackValueKind.Int32, "typedPointer", typedPointer), value
+ });
+ }
+ else
+ {
+ _builder.BuildStore(typedValue, typedPointer);
}
-
- _builder.BuildStore(typedValue, typedPointer);
}
private void ImportBinaryOperation(ILOpcode opcode)
@@ -4720,7 +4801,7 @@ private void ImportStoreField(int token, bool isStatic)
StackEntry valueEntry = _stack.Pop();
LLVMValueRef fieldAddress = GetFieldAddress(runtimeDeterminedField, field, isStatic);
- CastingStore(fieldAddress, valueEntry, field.FieldType);
+ CastingStore(fieldAddress, valueEntry, field.FieldType, true);
}
// Loads symbol address. Address is represented as a i32*
@@ -4936,7 +5017,7 @@ private void ImportStoreElement(TypeDesc elementType)
StackEntry arrayReference = _stack.Pop();
var nullSafeElementType = elementType ?? GetWellKnownType(WellKnownType.Object);
LLVMValueRef elementAddress = GetElementAddress(index.ValueAsInt32(_builder, true), arrayReference.ValueAsType(LLVMTypeRef.CreatePointer(LLVMTypeRef.Int8, 0), _builder), nullSafeElementType);
- CastingStore(elementAddress, value, nullSafeElementType);
+ CastingStore(elementAddress, value, nullSafeElementType, true);
}
private void ImportLoadLength()
diff --git a/src/ILCompiler.WebAssembly/src/CodeGen/LLVMSharpInterop.cs b/src/ILCompiler.WebAssembly/src/CodeGen/LLVMSharpInterop.cs
new file mode 100644
index 00000000000..e4a2fb9c1b0
--- /dev/null
+++ b/src/ILCompiler.WebAssembly/src/CodeGen/LLVMSharpInterop.cs
@@ -0,0 +1,15 @@
+using LLVMSharp.Interop;
+
+namespace Internal.IL
+{
+ ///
+ /// Workaround while waiting for https://github.com/microsoft/LLVMSharp/pull/141
+ ///
+ internal class LLVMSharpInterop
+ {
+ internal static unsafe uint ElementAtOffset(LLVMTargetDataRef targetDataRef, LLVMTypeRef structTypeRef, ulong offset)
+ {
+ return LLVM.ElementAtOffset(targetDataRef, structTypeRef, offset);
+ }
+ }
+}
diff --git a/src/ILCompiler.WebAssembly/src/Compiler/WebAssemblyCodegenCompilation.cs b/src/ILCompiler.WebAssembly/src/Compiler/WebAssemblyCodegenCompilation.cs
index 0fcb2982101..ae2075f7e8e 100644
--- a/src/ILCompiler.WebAssembly/src/Compiler/WebAssemblyCodegenCompilation.cs
+++ b/src/ILCompiler.WebAssembly/src/Compiler/WebAssemblyCodegenCompilation.cs
@@ -17,6 +17,7 @@ public sealed class WebAssemblyCodegenCompilation : Compilation
{
internal WebAssemblyCodegenConfigProvider Options { get; }
internal LLVMModuleRef Module { get; }
+ internal LLVMTargetDataRef TargetData { get; }
public new WebAssemblyCodegenNodeFactory NodeFactory { get; }
internal LLVMDIBuilderRef DIBuilder { get; }
internal Dictionary DebugMetadataMap { get; }
@@ -32,7 +33,7 @@ internal WebAssemblyCodegenCompilation(
{
NodeFactory = nodeFactory;
LLVMModuleRef m = LLVMModuleRef.CreateWithName("netscripten");
- m.Target = "wasm32-unknown-unknown-wasm";
+ m.Target = "wasm32-unknown-emscripten";
// https://llvm.org/docs/LangRef.html#langref-datalayout
// e litte endian, mangled names
// m:e ELF mangling
@@ -41,8 +42,8 @@ internal WebAssemblyCodegenCompilation(
// n:32:64 native widths
// S128 natural alignment of stack
m.DataLayout = "e-m:e-p:32:32-i64:64-n32:64-S128";
- Module = m;
-
+ Module = m;
+ TargetData = m.CreateExecutionEngine().TargetData;
Options = options;
DIBuilder = Module.CreateDIBuilder();
DebugMetadataMap = new Dictionary();
diff --git a/src/ILCompiler.WebAssembly/src/ILCompiler.WebAssembly.csproj b/src/ILCompiler.WebAssembly/src/ILCompiler.WebAssembly.csproj
index 63257e819bd..401bf46abb2 100644
--- a/src/ILCompiler.WebAssembly/src/ILCompiler.WebAssembly.csproj
+++ b/src/ILCompiler.WebAssembly/src/ILCompiler.WebAssembly.csproj
@@ -40,6 +40,7 @@
+
diff --git a/src/Native/gc/gc.cpp b/src/Native/gc/gc.cpp
index 3aae8dd4f3f..35eabd18244 100644
--- a/src/Native/gc/gc.cpp
+++ b/src/Native/gc/gc.cpp
@@ -17835,7 +17835,7 @@ uint8_t* gc_heap::find_object (uint8_t* interior)
heap_segment* seg = find_segment (interior, FALSE);
if (seg
#ifdef FEATURE_CONSERVATIVE_GC
- && (GCConfig::GetConservativeGC() || interior <= heap_segment_allocated(seg))
+ && (!GCConfig::GetConservativeGC() || interior <= heap_segment_allocated(seg))
#endif
)
{
diff --git a/tests/src/Simple/HelloWasm/Program.cs b/tests/src/Simple/HelloWasm/Program.cs
index 5c428ce1e60..9c2bc5f96cf 100644
--- a/tests/src/Simple/HelloWasm/Program.cs
+++ b/tests/src/Simple/HelloWasm/Program.cs
@@ -21,6 +21,11 @@ internal static class Program
internal static bool Success;
private static unsafe int Main(string[] args)
{
+ var x = new StructWithObjRefs
+ {
+ C1 = null,
+ C2 = null,
+ };
Success = true;
PrintLine("Starting " + 1);
@@ -393,15 +398,235 @@ private static void TestGC()
PrintString("GC Collection Count " + i.ToString() + " ");
PrintLine(GC.CollectionCount(i).ToString());
}
- if(!TestObjectRefInUncoveredShadowStackSlot())
+
+ if (!TestObjectRefInUncoveredShadowStackSlot())
{
FailTest("struct Child1 alive unexpectedly");
-
}
- EndTest(true);
+
+ if (!TestRhpAssignRefWithClassInStructGC())
+ {
+ FailTest();
+ return;
+ }
+
+ EndTest(TestGeneration2Rooting());
}
+ private static Parent aParent;
+ private static ParentOfStructWithObjRefs aParentOfStructWithObjRefs;
private static WeakReference childRef;
+
+ private static unsafe bool TestRhpAssignRefWithClassInStructGC()
+ {
+ bool result = true;
+
+ var parentRef = CreateParentWithStruct();
+ result &= BumpToGen(parentRef, 1);
+ result &= BumpToGen(parentRef, 2);
+
+ StoreChildInC1();
+ GC.Collect(1);
+ PrintLine("GC finished");
+
+ if (!childRef.IsAlive)
+ {
+ PrintLine("Child died unexpectedly");
+ result = false;
+ }
+
+ KillParentWithStruct();
+ GC.Collect();
+ if (childRef.IsAlive)
+ {
+ PrintLine("Child alive unexpectedly");
+ result = false;
+ }
+ if (parentRef.IsAlive)
+ {
+ PrintLine("Parent of struct Child1 alive unexpectedly");
+ result = false;
+ }
+ return result;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static bool BumpToGen(WeakReference reference, int expectedGeneration)
+ {
+ GC.Collect();
+ var target = reference.Target;
+ if (target == null)
+ {
+ PrintLine("WeakReference died unexpectedly");
+ return false;
+ }
+ if (GC.GetGeneration(target) is { } actualGeneration && actualGeneration != expectedGeneration)
+ {
+ PrintLine("WeakReference is in gen " + actualGeneration + " instead of " + expectedGeneration);
+ return false;
+ }
+ return true;
+ }
+
+ private static bool TestGeneration2Rooting()
+ {
+ var parent = CreateParent();
+ GC.Collect(); // parent moves to gen1
+ GC.Collect(); // parent moves to gen2
+ if (!CheckParentGeneration()) return false;
+
+ // store our children in the gen2 object
+ var child1 = StoreProperty();
+ var child2 = StoreField();
+
+ KillParent(); // even though we kill the parent, it should survive as we do not collect gen2
+ GC.Collect(1);
+
+ // the parent should have kept the children alive
+ bool parentAlive = parent.IsAlive;
+ bool child1Alive = child1.IsAlive;
+ bool child2Alive = child2.IsAlive;
+ if (!parentAlive)
+ {
+ PrintLine("Parent died unexpectedly");
+ return false;
+ }
+
+ if (!child1Alive)
+ {
+ PrintLine("Child1 died unexpectedly");
+ return false;
+ }
+
+ if (!child2Alive)
+ {
+ PrintLine("Child2 died unexpectedly");
+ return false;
+ }
+
+ // Test struct assignment keeps fields alive
+ var parentRef = CreateParentWithStruct();
+ GC.Collect(); // move parent to gen1
+ GC.Collect(); // move parent to gen2
+ StoreChildInC1(); // store ephemeral object in gen 2 object via struct assignment
+ KillParentWithStruct();
+ GC.Collect(1);
+
+ if (childRef.IsAlive)
+ {
+ PrintLine("Child1 gen:" + GC.GetGeneration(childRef.Target));
+ }
+
+ if (!childRef.IsAlive)
+ {
+ PrintLine("struct Child1 died unexpectedly");
+ return false;
+ }
+ if (!parentRef.IsAlive)
+ {
+ PrintLine("parent of struct Child1 died unexpectedly");
+ return false;
+ }
+
+ return true;
+ }
+
+ class ParentOfStructWithObjRefs
+ {
+ internal StructWithObjRefs StructWithObjRefs;
+ }
+
+ struct StructWithObjRefs
+ {
+ internal Child C1;
+ internal Child C2;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static WeakReference CreateParent()
+ {
+ var parent = new Parent();
+ aParent = parent;
+ return new WeakReference(parent);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static WeakReference CreateStruct()
+ {
+ var parent = new Parent();
+ aParent = parent;
+ return new WeakReference(parent);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static void KillParent()
+ {
+ aParent = null;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static bool CheckParentGeneration()
+ {
+ int actualGen = GC.GetGeneration(aParent);
+ if (actualGen != 2)
+ {
+ PrintLine("Parent Object is not in expected generation 2 but in " + actualGen);
+ return false;
+ }
+ return true;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static WeakReference StoreProperty()
+ {
+ var child = new Child();
+ aParent.Child1 = child;
+ return new WeakReference(child);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static WeakReference StoreField()
+ {
+ var child = new Child();
+ aParent.Child2 = child;
+ return new WeakReference(child);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ unsafe static WeakReference CreateParentWithStruct()
+ {
+ var parent = new ParentOfStructWithObjRefs();
+ aParentOfStructWithObjRefs = parent;
+ return new WeakReference(parent);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static void KillParentWithStruct()
+ {
+ aParentOfStructWithObjRefs = null;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static unsafe void StoreChildInC1()
+ {
+ var child = new Child();
+ aParentOfStructWithObjRefs.StructWithObjRefs = new StructWithObjRefs
+ {
+ C1 = child,
+ };
+ childRef = new WeakReference(child);
+ }
+
+ public class Parent
+ {
+ public Child Child1 { get; set; }
+ public Child Child2;
+ }
+
+ public class Child
+ {
+ }
+
// This test is to catch where slots are allocated on the shadow stack uncovering object references that were there previously.
// If this happens in the call to GC.Collect, which at the time of writing allocate 12 bytes in the call, 3 slots, then any objects that were in those
// 3 slots will not be collected as they will now be (back) in the range of bottom of stack -> top of stack.
@@ -421,10 +646,6 @@ static unsafe void CreateObjectRefsInShadowStack()
childRef = new WeakReference(child);
}
- public class Child
- {
- }
-
private static unsafe void TestBoxUnboxDifferentSizes()
{
StartTest("Box/Unbox different sizes");