Skip to content

Commit 18b02a7

Browse files
authored
Merge pull request #83172 from eeckstein/span-bounds-checking
Improve Span bounds check elimination by adding `_assumeNonNegative` to `count`
2 parents 02eb71c + 27dc031 commit 18b02a7

File tree

5 files changed

+23
-32
lines changed

5 files changed

+23
-32
lines changed

lib/IRGen/GenBuiltin.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -600,8 +600,8 @@ void irgen::emitBuiltinCall(IRGenFunction &IGF, const BuiltinInfo &Builtin,
600600
}
601601
if (Builtin.ID == BuiltinValueKind::AssumeNonNegative) {
602602
llvm::Value *v = args.claimNext();
603-
// Set a value range on the load instruction, which must be the argument of
604-
// the builtin.
603+
// If the argument is a `load` or `call` we can use a range metadata to
604+
// specify the >= 0 constraint.
605605
if (isa<llvm::LoadInst>(v) || isa<llvm::CallInst>(v)) {
606606
// The load must be post-dominated by the builtin. Otherwise we would get
607607
// a wrong assumption in the else-branch in this example:
@@ -626,6 +626,10 @@ void irgen::emitBuiltinCall(IRGenFunction &IGF, const BuiltinInfo &Builtin,
626626
llvm::MDNode *range = llvm::MDNode::get(ctx, rangeElems);
627627
I->setMetadata(llvm::LLVMContext::MD_range, range);
628628
}
629+
} else {
630+
// Otherwise, we specify the constraint with an `llvm.assume` intrinsic.
631+
auto *cmp = IGF.Builder.CreateICmpSGE(v, llvm::ConstantInt::get(v->getType(), 0));
632+
IGF.Builder.CreateIntrinsicCall(llvm::Intrinsic::assume, cmp);
629633
}
630634
// Don't generate any code for the builtin.
631635
return out.add(v);

stdlib/public/core/Span/MutableSpan.swift

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ extension MutableSpan where Element: ~Copyable {
237237
extension MutableSpan where Element: ~Copyable {
238238

239239
@_alwaysEmitIntoClient
240-
public var count: Int { _count }
240+
public var count: Int { _assumeNonNegative(_count) }
241241

242242
@_alwaysEmitIntoClient
243243
public var isEmpty: Bool { _count == 0 }
@@ -246,7 +246,7 @@ extension MutableSpan where Element: ~Copyable {
246246

247247
@_alwaysEmitIntoClient
248248
public var indices: Range<Index> {
249-
unsafe Range(_uncheckedBounds: (0, _count))
249+
unsafe Range(_uncheckedBounds: (0, count))
250250
}
251251
}
252252

@@ -290,18 +290,12 @@ extension MutableSpan where Element: ~Copyable {
290290
@_alwaysEmitIntoClient
291291
public subscript(_ position: Index) -> Element {
292292
unsafeAddress {
293-
_precondition(
294-
UInt(bitPattern: position) < UInt(bitPattern: _count),
295-
"Index out of bounds"
296-
)
293+
_precondition(indices.contains(position), "index out of bounds")
297294
return unsafe UnsafePointer(_unsafeAddressOfElement(unchecked: position))
298295
}
299296
@lifetime(self: copy self)
300297
unsafeMutableAddress {
301-
_precondition(
302-
UInt(bitPattern: position) < UInt(bitPattern: _count),
303-
"Index out of bounds"
304-
)
298+
_precondition(indices.contains(position), "index out of bounds")
305299
return unsafe _unsafeAddressOfElement(unchecked: position)
306300
}
307301
}

stdlib/public/core/Span/Span.swift

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ extension Span where Element: ~Copyable {
391391
/// - Complexity: O(1)
392392
@_alwaysEmitIntoClient
393393
@_semantics("fixed_storage.get_count")
394-
public var count: Int { _count }
394+
public var count: Int { _assumeNonNegative(_count) }
395395

396396
/// A Boolean value indicating whether the span is empty.
397397
///
@@ -408,7 +408,7 @@ extension Span where Element: ~Copyable {
408408
/// - Complexity: O(1)
409409
@_alwaysEmitIntoClient
410410
public var indices: Range<Index> {
411-
unsafe Range(_uncheckedBounds: (0, _count))
411+
unsafe Range(_uncheckedBounds: (0, count))
412412
}
413413
}
414414

@@ -419,10 +419,7 @@ extension Span where Element: ~Copyable {
419419
@inline(__always)
420420
@_alwaysEmitIntoClient
421421
internal func _checkIndex(_ position: Index) {
422-
_precondition(
423-
UInt(bitPattern: position) < UInt(bitPattern: _count),
424-
"Index out of bounds"
425-
)
422+
_precondition(indices.contains(position), "Index out of bounds")
426423
}
427424

428425
/// Accesses the element at the specified position in the `Span`.

test/IRGen/builtins.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,13 @@ func assumeNonNegative_test2(_ x: Builtin.Word) -> Builtin.Word {
646646
return Builtin.assumeNonNegative_Word(return_word(x))
647647
}
648648

649+
// CHECK-LABEL: define hidden {{.*}}@"$s8builtins23assumeNonNegative_test3yBwBwF"
650+
func assumeNonNegative_test3(_ x: Builtin.Word) -> Builtin.Word {
651+
// CHECK: [[C:%.*]] = icmp sge i{{[0-9]+}} %0, 0
652+
// CHECK: call void @llvm.assume(i1 [[C]])
653+
return Builtin.assumeNonNegative_Word(x)
654+
}
655+
649656
struct Empty {}
650657
struct Pair { var i: Int, b: Bool }
651658

test/SILOptimizer/span_bounds_check_tests.swift

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public func span_sum_iterate_to_count_wo_trap(_ v: Span<Int>) -> Int {
2828
for i in 0..<v.count {
2929
sum &+= v[i]
3030
}
31-
return sum
31+
return sum &+ 1 // adding 1 to avoid LLVM function merging
3232
}
3333

3434
// Bounds check should be eliminated
@@ -175,15 +175,10 @@ public func span_sum_iterate_to_deducible_count2_with_trap(_ v: Span<Int>, _ n:
175175
}
176176

177177
// Bounds check should be eliminated
178-
// SIL optimizer hoists bounds checks from the loop
178+
// TODO: there should not be any cond_fail in this function
179179

180180
// CHECK-SIL-LABEL: sil @$s23span_bounds_check_tests0A29_iterate_over_indices_wo_trapySis4SpanVySiGF :
181-
// CHECK-SIL: bb1
182-
// CHECK-SIL: cond_fail {{.*}}, "Index out of bounds"
183-
// CHECK-SIL: cond_fail {{.*}}, "Index out of bounds"
184-
// CHECK-SIL: bb3
185181
// CHECK-SIL-NOT: cond_fail {{.*}}, "Index out of bounds"
186-
// CHECK-SIL: cond_br
187182
// CHECK-SIL-LABEL: } // end sil function '$s23span_bounds_check_tests0A29_iterate_over_indices_wo_trapySis4SpanVySiGF'
188183

189184
// CHECK-IR: define {{.*}} @"$s23span_bounds_check_tests0A29_iterate_over_indices_wo_trapySis4SpanVySiGF"
@@ -200,12 +195,7 @@ public func span_iterate_over_indices_wo_trap(_ v: Span<Int>) -> Int {
200195
// SIL optimizer hoists bounds checks from the loop
201196

202197
// CHECK-SIL-LABEL: sil @$s23span_bounds_check_tests0A31_iterate_over_indices_with_trapySis4SpanVySiGF :
203-
// CHECK-SIL: bb1
204-
// CHECK-SIL: cond_fail {{.*}}, "Index out of bounds"
205-
// CHECK-SIL: cond_fail {{.*}}, "Index out of bounds"
206-
// CHECK-SIL: bb3
207198
// CHECK-SIL-NOT: cond_fail {{.*}}, "Index out of bounds"
208-
// CHECK-SIL: cond_br
209199
// CHECK-SIL-LABEL: } // end sil function '$s23span_bounds_check_tests0A31_iterate_over_indices_with_trapySis4SpanVySiGF'
210200
public func span_iterate_over_indices_with_trap(_ v: Span<Int>) -> Int {
211201
var sum = 0
@@ -254,12 +244,11 @@ public func span_search<T : Equatable>(_ v: Span<T>, _ elem: T) -> Int? {
254244
return nil
255245
}
256246

257-
// Bounds check should be eliminated
247+
// TODO: Bounds check should be eliminated
258248

259249
// CHECK-SIL-LABEL: sil @$s23span_bounds_check_tests0A11_search_splySiSgs4SpanVySiG_SitF :
260250
// CHECK-SIL: bb3:
261251
// CHECK-SIL: cond_fail {{.*}}, "Index out of bounds"
262-
// CHECK-SIL: cond_fail {{.*}}, "Index out of bounds"
263252
// CHECK-SIL: cond_br
264253
// CHECK-SIL-LABEL: } // end sil function '$s23span_bounds_check_tests0A11_search_splySiSgs4SpanVySiG_SitF'
265254
public func span_search_spl(_ v: Span<Int>, _ elem: Int) -> Int? {

0 commit comments

Comments
 (0)