From 2b00468133fcf879c4ef89050e1a663a8d01155f Mon Sep 17 00:00:00 2001 From: Ahmed Mustafa Date: Tue, 9 Dec 2025 19:52:19 +0200 Subject: [PATCH 1/7] Enable ARM (32-bit) deb/rpm package generation --- src/installer/pkg/sfx/Directory.Build.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/installer/pkg/sfx/Directory.Build.props b/src/installer/pkg/sfx/Directory.Build.props index 04c7177930cd61..ef62cf7c25d50f 100644 --- a/src/installer/pkg/sfx/Directory.Build.props +++ b/src/installer/pkg/sfx/Directory.Build.props @@ -14,8 +14,8 @@ true - true - true + true + true true From e648f546c9ad6a3cdd084f1bdc112e436c6ed5f4 Mon Sep 17 00:00:00 2001 From: Ahmed Mustafa Date: Sun, 14 Dec 2025 02:59:54 +0200 Subject: [PATCH 2/7] Fix #74020: Optimize consecutive shifts in JIT Lowering This change implements a peephole optimization in Lowering::LowerShift to combine consecutive right shifts (RSH/RSZ) with constant amounts. It specifically addresses cases where division optimization introduces redundant shifts, such as (x / c1) / c2. --- src/coreclr/jit/lower.cpp | 59 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index efbc46213660a3..80bde1b5ddb10b 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -8578,6 +8578,65 @@ void Lowering::LowerShift(GenTreeOp* shift) shift->gtOp2->ClearContained(); } + if (comp->opts.OptimizationEnabled() && shift->OperIs(GT_RSH, GT_RSZ) && shift->gtGetOp2()->IsCnsIntOrI()) + { + GenTree* op1 = shift->gtGetOp1(); + ssize_t c2 = shift->gtGetOp2()->AsIntCon()->IconValue(); + unsigned bitWidth = genTypeSize(shift->TypeGet()) * 8; + + // Case 1: (shift (shift x c1) c2) + if (op1->OperIs(shift->OperGet()) && op1->gtGetOp2()->IsCnsIntOrI() && !op1->IsMultiReg()) + { + ssize_t c1 = op1->gtGetOp2()->AsIntCon()->IconValue(); + unsigned innerBitWidth = genTypeSize(op1->TypeGet()) * 8; + + // Check if combined shift is valid and types match + if ((c1 > 0) && (c2 > 0) && ((c1 + c2) < innerBitWidth) && ((c1 + c2) < bitWidth) && + (op1->TypeGet() == shift->TypeGet())) + { + JITDUMP("Optimizing consecutive shifts: (x >> %d) >> %d -> x >> %d\n", (int)c1, (int)c2, + (int)(c1 + c2)); + + shift->gtGetOp2()->AsIntCon()->SetIconValue(c1 + c2); + shift->gtOp1 = op1->gtGetOp1(); + op1->gtGetOp1()->ClearContained(); + BlockRange().Remove(op1->gtGetOp2()); + BlockRange().Remove(op1); + } + } + // Case 2: (shift (cast (shift x c1)) c2) + // Optimization for: RSZ(CAST(RSZ(x, c1)), c2) -> CAST(RSZ(x, c1 + c2)) + else if (shift->OperIs(GT_RSZ) && op1->OperIs(GT_CAST) && !op1->gtOverflow() && !op1->IsMultiReg()) + { + GenTree* cast = op1; + GenTree* innerShift = cast->gtGetOp1(); + if (innerShift->OperIs(GT_RSZ) && innerShift->gtGetOp2()->IsCnsIntOrI() && !innerShift->IsMultiReg()) + { + ssize_t c1 = innerShift->gtGetOp2()->AsIntCon()->IconValue(); + unsigned innerBitWidth = genTypeSize(innerShift->TypeGet()) * 8; + + if ((c1 > 0) && (c2 > 0) && ((c1 + c2) < innerBitWidth)) + { + JITDUMP("Optimizing distinct type shifts: (cast (x >> %d)) >> %d -> cast (x >> %d)\n", (int)c1, + (int)c2, (int)(c1 + c2)); + + innerShift->gtGetOp2()->AsIntCon()->SetIconValue(c1 + c2); + + // Replace uses of 'shift' with 'cast', bypassing 'shift' + LIR::Use use; + if (BlockRange().TryGetUse(shift, &use)) + { + use.ReplaceWith(cast); + } + + // Remove 'c2' and turn 'shift' into NOP + BlockRange().Remove(shift->gtGetOp2()); + shift->gtBashToNOP(); + } + } + } + } + ContainCheckShiftRotate(shift); #ifdef TARGET_ARM64 From 4aa8c2622451ac908863cf12e907602754f5dbc8 Mon Sep 17 00:00:00 2001 From: Ahmed Mustafa Date: Mon, 15 Dec 2025 13:49:38 +0200 Subject: [PATCH 3/7] Enhance shift optimization: Handle LSH, Overshift, and Mixed shifts --- src/coreclr/jit/lower.cpp | 101 ++++++++++++++++++++++++++++++++------ 1 file changed, 87 insertions(+), 14 deletions(-) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 80bde1b5ddb10b..347b93e41f7754 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -8578,30 +8578,103 @@ void Lowering::LowerShift(GenTreeOp* shift) shift->gtOp2->ClearContained(); } - if (comp->opts.OptimizationEnabled() && shift->OperIs(GT_RSH, GT_RSZ) && shift->gtGetOp2()->IsCnsIntOrI()) + if (comp->opts.OptimizationEnabled() && shift->OperIs(GT_LSH, GT_RSH, GT_RSZ) && shift->gtGetOp2()->IsCnsIntOrI()) { - GenTree* op1 = shift->gtGetOp1(); - ssize_t c2 = shift->gtGetOp2()->AsIntCon()->IconValue(); + GenTree* op1 = shift->gtGetOp1(); + ssize_t c2 = shift->gtGetOp2()->AsIntCon()->IconValue(); unsigned bitWidth = genTypeSize(shift->TypeGet()) * 8; // Case 1: (shift (shift x c1) c2) - if (op1->OperIs(shift->OperGet()) && op1->gtGetOp2()->IsCnsIntOrI() && !op1->IsMultiReg()) + // We can combine if: + // 1. Same operation (LSH/LSH, RSH/RSH, RSZ/RSZ) + // 2. Mixed: Outer RSH, Inner RSZ (Effective result is RSZ because sign bit is 0) + bool sameOp = op1->OperIs(shift->OperGet()); + bool mixedOp = shift->OperIs(GT_RSH) && op1->OperIs(GT_RSZ); + + if ((sameOp || mixedOp) && op1->gtGetOp2()->IsCnsIntOrI() && !op1->IsMultiReg()) { ssize_t c1 = op1->gtGetOp2()->AsIntCon()->IconValue(); unsigned innerBitWidth = genTypeSize(op1->TypeGet()) * 8; - // Check if combined shift is valid and types match - if ((c1 > 0) && (c2 > 0) && ((c1 + c2) < innerBitWidth) && ((c1 + c2) < bitWidth) && - (op1->TypeGet() == shift->TypeGet())) + // Only optimize if types match (simplifies width checks) + if (op1->TypeGet() == shift->TypeGet()) { - JITDUMP("Optimizing consecutive shifts: (x >> %d) >> %d -> x >> %d\n", (int)c1, (int)c2, - (int)(c1 + c2)); + // We use a larger type to check for overflow (though shift counts likely small) + // But conceptually c1+c2 can be large. + ssize_t combined = c1 + c2; - shift->gtGetOp2()->AsIntCon()->SetIconValue(c1 + c2); - shift->gtOp1 = op1->gtGetOp1(); - op1->gtGetOp1()->ClearContained(); - BlockRange().Remove(op1->gtGetOp2()); - BlockRange().Remove(op1); + if ((c1 > 0) && (c2 > 0)) + { + if (combined < bitWidth) + { + JITDUMP("Optimizing consecutive shifts: (x %s %d) %s %d -> x %s %d\n", + GenTree::OpName(op1->OperGet()), (int)c1, GenTree::OpName(shift->OperGet()), (int)c2, + GenTree::OpName(mixedOp ? GT_RSZ : shift->OperGet()), (int)combined); + + // If we had RSH(RSZ), result is RSZ. + if (mixedOp) + { + shift->SetOper(GT_RSZ); + } + + shift->gtGetOp2()->AsIntCon()->SetIconValue(combined); + shift->gtOp1 = op1->gtGetOp1(); + op1->gtGetOp1()->ClearContained(); + BlockRange().Remove(op1->gtGetOp2()); + BlockRange().Remove(op1); + } + else + { + // Overshift Case + JITDUMP("Optimizing overshift: (x %s %d) %s %d\n", GenTree::OpName(op1->OperGet()), (int)c1, + GenTree::OpName(shift->OperGet()), (int)c2); + + if (shift->OperIs(GT_RSH) /* Implies sameOp since mixedOp is effectively RSZ */) + { + // RSH saturates to sign bit (shift by bitWidth - 1) + // (x >> 30) >> 30 -> x >> 31 (for 32-bit) + JITDUMP(" -> x >> %d\n", bitWidth - 1); + + shift->gtGetOp2()->AsIntCon()->SetIconValue(bitWidth - 1); + shift->gtOp1 = op1->gtGetOp1(); + op1->gtGetOp1()->ClearContained(); + BlockRange().Remove(op1->gtGetOp2()); + BlockRange().Remove(op1); + } + else + { + // LSH, RSZ, or Mixed(RSH after RSZ) -> 0 + // (x << 30) << 2 -> 0 + // (x >>> 30) >>> 2 -> 0 + JITDUMP(" -> 0\n"); + + GenTree* zero = comp->gtNewZeroConNode(shift->TypeGet()); + BlockRange().InsertAfter(shift, zero); + + LIR::Use use; + if (BlockRange().TryGetUse(shift, &use)) + { + use.ReplaceWith(zero); + } + else + { + zero->SetUnusedValue(); + } + + // Remove the entire chain if possible, or at least the outer shift + // Note: op1 might still be used elsewhere if ref counts > 1? + // But peephole assumes single use usually or we just disconnect. + // The LIR::Use check handles the result use. + // We remove 'shift' and its op2. + BlockRange().Remove(shift->gtGetOp2()); + BlockRange().Remove(shift); + + // We don't remove op1 here as it might be used elsewhere (unlikely in this peephole context but safer) + // Actually if we disconnect it from shift, and it has no other uses... + // But let's leave DCE to handle op1 if it becomes dead. + } + } + } } } // Case 2: (shift (cast (shift x c1)) c2) From cca811597eb9682d52d0b7f051a5b563a3a5a344 Mon Sep 17 00:00:00 2001 From: Ahmed Mustafa Date: Mon, 15 Dec 2025 14:39:39 +0200 Subject: [PATCH 4/7] Fix signed/unsigned comparison warning in LowerShift --- src/coreclr/jit/lower.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 347b93e41f7754..f1ca5df00dfc5d 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -8605,7 +8605,7 @@ void Lowering::LowerShift(GenTreeOp* shift) if ((c1 > 0) && (c2 > 0)) { - if (combined < bitWidth) + if (combined < (ssize_t)bitWidth) { JITDUMP("Optimizing consecutive shifts: (x %s %d) %s %d -> x %s %d\n", GenTree::OpName(op1->OperGet()), (int)c1, GenTree::OpName(shift->OperGet()), (int)c2, From dd9d052d76af4bb02110fc92a891a3dd05a1db7a Mon Sep 17 00:00:00 2001 From: Ahmed Mustafa Date: Mon, 15 Dec 2025 16:32:39 +0200 Subject: [PATCH 5/7] Make UDP SendTo tests deterministic on mobile/restricted environments --- .../tests/FunctionalTests/SendTo.cs | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendTo.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendTo.cs index 2208d905efb675..ca922dd6d1de48 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendTo.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendTo.cs @@ -19,6 +19,14 @@ namespace System.Net.Sockets.Tests protected static IPEndPoint GetGetDummyTestEndpoint(AddressFamily addressFamily = AddressFamily.InterNetwork) => addressFamily == AddressFamily.InterNetwork ? new IPEndPoint(IPAddress.Parse("1.2.3.4"), 1234) : new IPEndPoint(IPAddress.Parse("1:2:3::4"), 1234); + private static IPEndPoint CreateLoopbackUdpEndpoint(AddressFamily family, out Socket receiver) + { + IPAddress loopback = family == AddressFamily.InterNetwork ? IPAddress.Loopback : IPAddress.IPv6Loopback; + receiver = new Socket(family, SocketType.Dgram, ProtocolType.Udp); + receiver.Bind(new IPEndPoint(loopback, 0)); // ephemeral port on loopback + return (IPEndPoint)receiver.LocalEndPoint!; + } + protected SendTo(ITestOutputHelper output) : base(output) { } @@ -78,9 +86,11 @@ public async Task NullSocketAddress_Throws_ArgumentException() public async Task Datagram_UDP_ShouldImplicitlyBindLocalEndpoint() { using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + using Socket receiver; + IPEndPoint remote = CreateLoopbackUdpEndpoint(socket.AddressFamily, out receiver); byte[] buffer = new byte[32]; - Task sendTask = SendToAsync(socket, new ArraySegment(buffer), GetGetDummyTestEndpoint()); + Task sendTask = SendToAsync(socket, new ArraySegment(buffer), remote); // Asynchronous calls shall alter the property immediately: if (!UsesSync) @@ -91,7 +101,11 @@ public async Task Datagram_UDP_ShouldImplicitlyBindLocalEndpoint() await sendTask; // In synchronous calls, we should wait for the completion of the helper task: - Assert.NotNull(socket.LocalEndPoint); + EndPoint? local = socket.LocalEndPoint; + Assert.NotNull(local); + var localIp = (IPEndPoint)local!; + Assert.NotEqual(0, localIp.Port); + Assert.True(IPAddress.IsLoopback(localIp.Address), "Implicit bind should select loopback when sending to loopback."); } [ConditionalFact] @@ -109,7 +123,19 @@ public async Task Datagram_UDP_AccessDenied_Throws_DoesNotBind() throw new SkipTestException("HostUnreachable indicates missing local network permission; this test might pass or fail depending on the environment. Please verify manually."); } - Assert.Equal(SocketError.AccessDenied, e.SocketErrorCode); + // On some mobile/restricted queues the send can fail earlier with unreachable + // rather than AccessDenied (see #120526, #114450). + if (OperatingSystem.IsAndroid() || OperatingSystem.IsMacCatalyst() || OperatingSystem.IsIOS() || OperatingSystem.IsTvOS()) + { + Assert.True( + e.SocketErrorCode is SocketError.AccessDenied or SocketError.NetworkUnreachable or SocketError.HostUnreachable, + $"Unexpected error: {e.SocketErrorCode}"); + } + else + { + Assert.Equal(SocketError.AccessDenied, e.SocketErrorCode); + } + Assert.Null(socket.LocalEndPoint); } From 59dadb63172a505dcee0dff8b137a453682b5529 Mon Sep 17 00:00:00 2001 From: Ahmed Mustafa Date: Mon, 15 Dec 2025 18:49:12 +0200 Subject: [PATCH 6/7] Revert unrelated JIT changes in lower.cpp --- src/coreclr/jit/lower.cpp | 132 -------------------------------------- 1 file changed, 132 deletions(-) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 1ef6a80d1e7f0a..118d7b75b320e1 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -8581,138 +8581,6 @@ void Lowering::LowerShift(GenTreeOp* shift) shift->gtOp2->ClearContained(); } - if (comp->opts.OptimizationEnabled() && shift->OperIs(GT_LSH, GT_RSH, GT_RSZ) && shift->gtGetOp2()->IsCnsIntOrI()) - { - GenTree* op1 = shift->gtGetOp1(); - ssize_t c2 = shift->gtGetOp2()->AsIntCon()->IconValue(); - unsigned bitWidth = genTypeSize(shift->TypeGet()) * 8; - - // Case 1: (shift (shift x c1) c2) - // We can combine if: - // 1. Same operation (LSH/LSH, RSH/RSH, RSZ/RSZ) - // 2. Mixed: Outer RSH, Inner RSZ (Effective result is RSZ because sign bit is 0) - bool sameOp = op1->OperIs(shift->OperGet()); - bool mixedOp = shift->OperIs(GT_RSH) && op1->OperIs(GT_RSZ); - - if ((sameOp || mixedOp) && op1->gtGetOp2()->IsCnsIntOrI() && !op1->IsMultiReg()) - { - ssize_t c1 = op1->gtGetOp2()->AsIntCon()->IconValue(); - unsigned innerBitWidth = genTypeSize(op1->TypeGet()) * 8; - - // Only optimize if types match (simplifies width checks) - if (op1->TypeGet() == shift->TypeGet()) - { - // We use a larger type to check for overflow (though shift counts likely small) - // But conceptually c1+c2 can be large. - ssize_t combined = c1 + c2; - - if ((c1 > 0) && (c2 > 0)) - { - if (combined < (ssize_t)bitWidth) - { - JITDUMP("Optimizing consecutive shifts: (x %s %d) %s %d -> x %s %d\n", - GenTree::OpName(op1->OperGet()), (int)c1, GenTree::OpName(shift->OperGet()), (int)c2, - GenTree::OpName(mixedOp ? GT_RSZ : shift->OperGet()), (int)combined); - - // If we had RSH(RSZ), result is RSZ. - if (mixedOp) - { - shift->SetOper(GT_RSZ); - } - - shift->gtGetOp2()->AsIntCon()->SetIconValue(combined); - shift->gtOp1 = op1->gtGetOp1(); - op1->gtGetOp1()->ClearContained(); - BlockRange().Remove(op1->gtGetOp2()); - BlockRange().Remove(op1); - } - else - { - // Overshift Case - JITDUMP("Optimizing overshift: (x %s %d) %s %d\n", GenTree::OpName(op1->OperGet()), (int)c1, - GenTree::OpName(shift->OperGet()), (int)c2); - - if (shift->OperIs(GT_RSH) /* Implies sameOp since mixedOp is effectively RSZ */) - { - // RSH saturates to sign bit (shift by bitWidth - 1) - // (x >> 30) >> 30 -> x >> 31 (for 32-bit) - JITDUMP(" -> x >> %d\n", bitWidth - 1); - - shift->gtGetOp2()->AsIntCon()->SetIconValue(bitWidth - 1); - shift->gtOp1 = op1->gtGetOp1(); - op1->gtGetOp1()->ClearContained(); - BlockRange().Remove(op1->gtGetOp2()); - BlockRange().Remove(op1); - } - else - { - // LSH, RSZ, or Mixed(RSH after RSZ) -> 0 - // (x << 30) << 2 -> 0 - // (x >>> 30) >>> 2 -> 0 - JITDUMP(" -> 0\n"); - - GenTree* zero = comp->gtNewZeroConNode(shift->TypeGet()); - BlockRange().InsertAfter(shift, zero); - - LIR::Use use; - if (BlockRange().TryGetUse(shift, &use)) - { - use.ReplaceWith(zero); - } - else - { - zero->SetUnusedValue(); - } - - // Remove the entire chain if possible, or at least the outer shift - // Note: op1 might still be used elsewhere if ref counts > 1? - // But peephole assumes single use usually or we just disconnect. - // The LIR::Use check handles the result use. - // We remove 'shift' and its op2. - BlockRange().Remove(shift->gtGetOp2()); - BlockRange().Remove(shift); - - // We don't remove op1 here as it might be used elsewhere (unlikely in this peephole context but safer) - // Actually if we disconnect it from shift, and it has no other uses... - // But let's leave DCE to handle op1 if it becomes dead. - } - } - } - } - } - // Case 2: (shift (cast (shift x c1)) c2) - // Optimization for: RSZ(CAST(RSZ(x, c1)), c2) -> CAST(RSZ(x, c1 + c2)) - else if (shift->OperIs(GT_RSZ) && op1->OperIs(GT_CAST) && !op1->gtOverflow() && !op1->IsMultiReg()) - { - GenTree* cast = op1; - GenTree* innerShift = cast->gtGetOp1(); - if (innerShift->OperIs(GT_RSZ) && innerShift->gtGetOp2()->IsCnsIntOrI() && !innerShift->IsMultiReg()) - { - ssize_t c1 = innerShift->gtGetOp2()->AsIntCon()->IconValue(); - unsigned innerBitWidth = genTypeSize(innerShift->TypeGet()) * 8; - - if ((c1 > 0) && (c2 > 0) && ((c1 + c2) < innerBitWidth)) - { - JITDUMP("Optimizing distinct type shifts: (cast (x >> %d)) >> %d -> cast (x >> %d)\n", (int)c1, - (int)c2, (int)(c1 + c2)); - - innerShift->gtGetOp2()->AsIntCon()->SetIconValue(c1 + c2); - - // Replace uses of 'shift' with 'cast', bypassing 'shift' - LIR::Use use; - if (BlockRange().TryGetUse(shift, &use)) - { - use.ReplaceWith(cast); - } - - // Remove 'c2' and turn 'shift' into NOP - BlockRange().Remove(shift->gtGetOp2()); - shift->gtBashToNOP(); - } - } - } - } - ContainCheckShiftRotate(shift); #ifdef TARGET_ARM64 From eff70054f8f840837c52486bcb29095e67c6de86 Mon Sep 17 00:00:00 2001 From: Ahmed Mustafa Date: Mon, 15 Dec 2025 18:57:16 +0200 Subject: [PATCH 7/7] Revert unrelated changes in Directory.Build.props --- src/installer/pkg/sfx/Directory.Build.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/installer/pkg/sfx/Directory.Build.props b/src/installer/pkg/sfx/Directory.Build.props index ef62cf7c25d50f..04c7177930cd61 100644 --- a/src/installer/pkg/sfx/Directory.Build.props +++ b/src/installer/pkg/sfx/Directory.Build.props @@ -14,8 +14,8 @@ true - true - true + true + true true