diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 65efdbf479e06f..c1124a336a1aee 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -10422,15 +10422,11 @@ void Lowering::LowerBlockStoreAsGcBulkCopyCall(GenTreeBlk* blk) LowerRange(rangeStart, rangeEnd); - // Finally move all GT_PUTARG_* nodes - // Re-use the existing logic for CFG call args here - MovePutArgNodesUpToCall(call); - BlockRange().Remove(destPlaceholder); BlockRange().Remove(sizePlaceholder); BlockRange().Remove(dataPlaceholder); - // Add implicit nullchecks for dest and data if needed: + // Add implicit nullchecks after both addresses have been evaluated. // auto wrapWithNullcheck = [&](GenTree* node) { if (m_compiler->fgAddrCouldBeNull(node)) @@ -10439,7 +10435,7 @@ void Lowering::LowerBlockStoreAsGcBulkCopyCall(GenTreeBlk* blk) BlockRange().TryGetUse(node, &nodeUse); GenTree* nodeClone = m_compiler->gtNewLclvNode(nodeUse.ReplaceWithLclVar(m_compiler), genActualType(node)); GenTree* nullcheck = m_compiler->gtNewNullCheck(nodeClone); - BlockRange().InsertAfter(nodeUse.Def(), nodeClone, nullcheck); + BlockRange().InsertBefore(call, nodeClone, nullcheck); LowerNode(nullcheck); } }; @@ -10453,6 +10449,10 @@ void Lowering::LowerBlockStoreAsGcBulkCopyCall(GenTreeBlk* blk) { wrapWithNullcheck(data); } + + // Finally move all GT_PUTARG_* nodes + // Re-use the existing logic for CFG call args here + MovePutArgNodesUpToCall(call); } //------------------------------------------------------------------------ diff --git a/src/tests/JIT/Regression_ro_2/Runtime_133587.cs b/src/tests/JIT/Regression_ro_2/Runtime_133587.cs new file mode 100644 index 00000000000000..0f084ffb3eb978 --- /dev/null +++ b/src/tests/JIT/Regression_ro_2/Runtime_133587.cs @@ -0,0 +1,90 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using Xunit; + +public class Runtime_133587 +{ + private static Big s_src = new Big + { + A = new object(), + B = new object(), + C = new object(), + D = new object(), + E = new object() + }; + private static string s_log = ""; + + private struct Big + { + public object A, B, C, D, E; + } + + public enum SourceKind + { + Valid, + Null, + Throwing + } + + [Theory] + [InlineData(false, SourceKind.Valid)] + [InlineData(true, SourceKind.Valid)] + [InlineData(false, SourceKind.Null)] + [InlineData(true, SourceKind.Null)] + [InlineData(false, SourceKind.Throwing)] + [InlineData(true, SourceKind.Throwing)] + public static void TestEntryPoint(bool nullDestination, SourceKind sourceKind) + { + s_log = ""; + Big destination = default; + Action copy = () => Copy(ref (nullDestination ? ref Unsafe.NullRef() : ref destination), sourceKind); + + if (sourceKind is SourceKind.Throwing) + { + Assert.Throws(copy); + } + else if (nullDestination || sourceKind is SourceKind.Null) + { + Assert.Throws(copy); + } + else + { + copy(); + Assert.Same(s_src.A, destination.A); + Assert.Same(s_src.B, destination.B); + Assert.Same(s_src.C, destination.C); + Assert.Same(s_src.D, destination.D); + Assert.Same(s_src.E, destination.E); + } + + Assert.Equal("Index;GetSrc;", s_log); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static ref Big GetSrc(SourceKind sourceKind) + { + s_log += nameof(GetSrc) + ";"; + if (sourceKind is SourceKind.Throwing) + { + throw new InvalidOperationException("Source evaluation failed."); + } + + return ref (sourceKind is SourceKind.Null ? ref Unsafe.NullRef() : ref s_src); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int Index() + { + s_log += nameof(Index) + ";"; + return 0; + } + + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] + private static void Copy(ref Big destination, SourceKind sourceKind) + { + Unsafe.Add(ref destination, Index()) = GetSrc(sourceKind); + } +}