From ac2f81f59ce2b9d65ea9674c0065df489ddf82cb Mon Sep 17 00:00:00 2001 From: Stephen Canon Date: Thu, 17 Jul 2025 16:47:26 -0400 Subject: [PATCH 1/4] Use an explicit unsigned comparison for span index checks LLVM ought to be able to do this transformation for us, but it currently fails to do so. We can code around it easily enough. --- stdlib/public/core/Span/MutableSpan.swift | 10 ++++++++-- stdlib/public/core/Span/Span.swift | 5 ++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/stdlib/public/core/Span/MutableSpan.swift b/stdlib/public/core/Span/MutableSpan.swift index c4c88282621a9..3ffe7b054e68c 100644 --- a/stdlib/public/core/Span/MutableSpan.swift +++ b/stdlib/public/core/Span/MutableSpan.swift @@ -290,12 +290,18 @@ extension MutableSpan where Element: ~Copyable { @_alwaysEmitIntoClient public subscript(_ position: Index) -> Element { unsafeAddress { - _precondition(indices.contains(position), "index out of bounds") + _precondition( + UInt(bitPattern: position) < UInt(bitPattern: _count), + "Index out of bounds" + ) return unsafe UnsafePointer(_unsafeAddressOfElement(unchecked: position)) } @lifetime(self: copy self) unsafeMutableAddress { - _precondition(indices.contains(position), "index out of bounds") + _precondition( + UInt(bitPattern: position) < UInt(bitPattern: _count), + "Index out of bounds" + ) return unsafe _unsafeAddressOfElement(unchecked: position) } } diff --git a/stdlib/public/core/Span/Span.swift b/stdlib/public/core/Span/Span.swift index fafa9b8e92818..92daf0383996e 100644 --- a/stdlib/public/core/Span/Span.swift +++ b/stdlib/public/core/Span/Span.swift @@ -419,7 +419,10 @@ extension Span where Element: ~Copyable { @inline(__always) @_alwaysEmitIntoClient internal func _checkIndex(_ position: Index) { - _precondition(indices.contains(position), "Index out of bounds") + _precondition( + UInt(bitPattern: position) < UInt(bitPattern: _count), + "Index out of bounds" + ) } /// Accesses the element at the specified position in the `Span`. From 68a0dccff3accd10b0c97a830d07b22ca9ef0c24 Mon Sep 17 00:00:00 2001 From: Stephen Canon Date: Thu, 17 Jul 2025 20:19:48 -0400 Subject: [PATCH 2/4] Add file-check tests for bounds-check optimizations on Span And apply the unsigned-compare optimization one other place that I missed the first time, as revealed by the new tests. --- .../stdlib/Span/BoundsCheckOptimization.swift | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 test/stdlib/Span/BoundsCheckOptimization.swift diff --git a/test/stdlib/Span/BoundsCheckOptimization.swift b/test/stdlib/Span/BoundsCheckOptimization.swift new file mode 100644 index 0000000000000..ef4af09f81dc5 --- /dev/null +++ b/test/stdlib/Span/BoundsCheckOptimization.swift @@ -0,0 +1,61 @@ +//===--- BoundsCheckOptimization.swift ------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +// RUN: %target-swift-frontend -primary-file %s -O -emit-assembly | %FileCheck %s --check-prefix CHECK --check-prefix CHECK-%target-cpu + +import Swift + +func read(index: Int, span: Span) -> UInt8 { + span[index] +} +// CHECK: s22BoundsCheckElimination4read5index4spans5UInt8VSi_s4SpanVyAFGtF: + +// CHECK-arm64-NOT: tbnz +// CHECK-arm64: cmp +// all unsigned comparison spellings? +// CHECK-arm64-NEXT: b.{{hs|hi|ls|lo|cc|cs}} +// CHECK-arm64-NEXT: ldrb +// CHECK-arm64-NEXT: ret + +// CHECK-x86_64-NOT: test +// CHECK-x86_64: cmp +// all unsigned comparison spellings? +// CHECK-x86_64-NEXT: j{{a|ae|b|be|c|na|nae|nb|nbe|nc}} +// CHECK-x86_64-NEXT: movzb +// x86_64 might have a frame pointer operation before ret + +func write(value: UInt8, index: Int, span: inout MutableSpan) { + span[index] = value +} +// CHECK: s22BoundsCheckElimination5write5value5index4spanys5UInt8V_Sis11MutableSpanVyAGGztF: + +// CHECK-arm64-NOT: tbnz +// CHECK-arm64: cmp +// all unsigned comparison spellings? +// CHECK-arm64-NEXT: b.{{hs|hi|ls|lo|cc|cs}} +// no second compare +// CHECK-arm64-NOT: cmp +// no test after compare +// CHECK-arm64-NOT: tbnz +// CHECK-arm64: strb +// CHECK-arm64-NEXT: ret + +// CHECK-x86_64-NOT: test +// CHECK-x86_64: cmp +// all unsigned comparison spellings? +// CHECK-x86_64-NEXT: j{{a|ae|b|be|c|na|nae|nb|nbe|nc}} +// no second compare +// CHECK-x86_64-NOT: cmp +// no test after compare +// CHECK-x86_64-NOT: test +// CHECK-x86_64: movb +// x86_64 might have a frame pointer operation before ret From 1bd4562c4d0cf76b094f717a62bdc06fd389e5ed Mon Sep 17 00:00:00 2001 From: Stephen Canon Date: Thu, 17 Jul 2025 22:14:28 -0400 Subject: [PATCH 3/4] Adjust file check to reflect file renaming --- test/stdlib/Span/BoundsCheckOptimization.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/stdlib/Span/BoundsCheckOptimization.swift b/test/stdlib/Span/BoundsCheckOptimization.swift index ef4af09f81dc5..b43e56eeffe2e 100644 --- a/test/stdlib/Span/BoundsCheckOptimization.swift +++ b/test/stdlib/Span/BoundsCheckOptimization.swift @@ -17,7 +17,7 @@ import Swift func read(index: Int, span: Span) -> UInt8 { span[index] } -// CHECK: s22BoundsCheckElimination4read5index4spans5UInt8VSi_s4SpanVyAFGtF: +// CHECK: s23BoundsCheckOptimization4read5index4spans5UInt8VSi_s4SpanVyAFGtF: // CHECK-arm64-NOT: tbnz // CHECK-arm64: cmp @@ -36,7 +36,7 @@ func read(index: Int, span: Span) -> UInt8 { func write(value: UInt8, index: Int, span: inout MutableSpan) { span[index] = value } -// CHECK: s22BoundsCheckElimination5write5value5index4spanys5UInt8V_Sis11MutableSpanVyAGGztF: +// CHECK: s23BoundsCheckOptimization5write5value5index4spanys5UInt8V_Sis11MutableSpanVyAGGztF: // CHECK-arm64-NOT: tbnz // CHECK-arm64: cmp From d0506e49ac38bc6e67ac6a1855392217f057249f Mon Sep 17 00:00:00 2001 From: Stephen Canon Date: Fri, 18 Jul 2025 10:59:57 -0400 Subject: [PATCH 4/4] Disable Span bounds check elimination tests for asserts builds The asserts introduce null pointer checks that make the test too fragile. --- test/stdlib/Span/BoundsCheckOptimization.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/test/stdlib/Span/BoundsCheckOptimization.swift b/test/stdlib/Span/BoundsCheckOptimization.swift index b43e56eeffe2e..59544891d86ea 100644 --- a/test/stdlib/Span/BoundsCheckOptimization.swift +++ b/test/stdlib/Span/BoundsCheckOptimization.swift @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// // RUN: %target-swift-frontend -primary-file %s -O -emit-assembly | %FileCheck %s --check-prefix CHECK --check-prefix CHECK-%target-cpu +// REQUIRES: swift_stdlib_no_asserts import Swift