Files
swift-mirror/test/SILOptimizer/performance-annotations.swift
Doug Gregor c09bbf4c10 [Performance diagnostics] Enable checking of throw instructions
When performance diagnostics were introduced, typed throws didn't exist
so it was not generally possible to have throws anywhere without
triggering performance diagnostics. As a short term hack, we disabled
checking of `throw` instructions and the basic blocks that terminate
in a `throw`.

Now that typed throws is available and can be used to eliminate
allocations with error handling, remove all of the hacks. We'll now
diagnose attempts to throw or catch existential values (e.g., the `any
Error` used for untyped throws), but typed throws are fine.
2024-11-21 16:06:44 -08:00

608 lines
12 KiB
Swift

// RUN: %target-swift-frontend -parse-as-library -disable-availability-checking -enable-experimental-feature RawLayout -import-objc-header %S/Inputs/perf-annotations.h -emit-sil %s -o /dev/null -verify
// REQUIRES: swift_in_compiler
// REQUIRES: optimized_stdlib
// REQUIRES: swift_feature_RawLayout
protocol P {
func protoMethod(_ a: Int) -> Int
}
open class Cl {
open func classMethod() {}
final func finalMethod() {}
}
func initFunc() -> Int { return Int.random(in: 0..<10) }
struct Str : P {
let x: Int
func protoMethod(_ a: Int) -> Int {
return a + x
}
static let s = 27
static var s2 = 10 + s
static var s3 = initFunc() // expected-error {{global/static variable initialization can cause locking}}
}
struct AllocatingStr : P {
func protoMethod(_ a: Int) -> Int {
_ = Cl() // expected-error {{Using type 'Cl' can cause metadata allocation or locks}}
return 0
}
}
func noRTCallsForArrayGet(_ a: [Str], _ i: Int) -> Int {
return a[i].x
}
@_noLocks
func callArrayGet(_ a: [Str]) -> Int {
return noRTCallsForArrayGet(a, 0)
}
@_noLocks
func arcOperations(_ x: Cl) -> Cl {
return x // expected-error {{this code performs reference counting operations which can cause locking}}
}
func genFunc<T: P>(_ t: T, _ a: Int) -> Int {
let s = t
return t.protoMethod(a) + s.protoMethod(a) // expected-note {{called from here}}
}
@_noAllocation
func callMethodGood(_ a: Int) -> Int {
return genFunc(Str(x: 1), a)
}
@_noAllocation
func callMethodBad(_ a: Int) -> Int {
return genFunc(AllocatingStr(), a) // expected-note {{called from here}}
}
@_noAllocation
func callClassMethod(_ c: Cl) {
return c.classMethod() // expected-error {{called function is not known at compile time and can have unpredictable performance}}
}
@_noAllocation
func callFinalMethod(_ c: Cl) {
return c.finalMethod()
}
@_noAllocation
func callProtocolMethod(_ p: P) -> Int {
return p.protoMethod(0) // expected-error {{this code pattern can cause metadata allocation or locks}}
}
@_noAllocation
func dynamicCast(_ a: AnyObject) -> Cl? {
return a as? Cl // expected-error {{dynamic casting can lock or allocate}}
}
@_noAllocation
func testUnsafePerformance(_ idx: Int) -> [Int] {
return _unsafePerformance { [10, 20, 30, 40] }
}
@_noAllocation
func testMemoryLayout() -> Int {
return MemoryLayout<Int>.size + MemoryLayout<Int>.stride + MemoryLayout<Int>.alignment
}
class MyError : Error {}
class MyError2 : Error {}
@_noLocks
func errorExistential(_ b: Bool) throws -> Int {
if b {
return 28
}
throw MyError() // expected-error{{Using type 'MyError' can cause metadata allocation or locks}}
}
@_noLocks
func concreteThrowsExistential(_ b: Bool) throws -> Int {
if b {
return 28
}
throw ErrorEnum.tryAgain // expected-error{{Using type 'any Error' can cause metadata allocation or locks}}
}
@_noLocks
func multipleThrows(_ b1: Bool, _ b2: Bool) throws -> Int {
if b1 {
throw MyError() // expected-error{{Using type 'MyError' can cause metadata allocation or locks}}
}
if b2 {
throw MyError2()
}
return 28
}
@_noLocks
func testCatch(_ b: Bool) throws -> Int? {
do {
return try errorExistential(true)
} catch let e as MyError { // expected-error{{this code performs reference counting operations which can cause locking}}
print(e)
return nil
}
}
enum ErrorEnum: Error {
case failed
case tryAgain
}
@_noLocks
func concreteError(_ b: Bool) throws(ErrorEnum) -> Int {
if b {
return 28
}
throw .tryAgain
}
func concreteErrorOther(_ b: Bool) throws(ErrorEnum) -> Int {
if b {
return 28
}
throw .tryAgain
}
@_noLocks
func testCatchConcrete(_ b: Bool) -> Int {
do {
return try concreteError(b) + concreteErrorOther(b)
} catch {
return 17
}
}
@_noLocks
func testRecursion(_ i: Int) -> Int {
if i > 0 {
return testRecursion(i - 1)
}
return 0
}
@_noLocks
func testGlobal() -> Int {
return Str.s + Str.s2
}
@_noLocks
func testGlobalWithComplexInit() -> Int {
return Str.s3 // expected-note {{called from here}}
}
func metatypeArg<T>(_ t: T.Type, _ b: Bool) {
}
@_noAllocation
func callFuncWithMetatypeArg() {
metatypeArg(Int.self, false)
}
@_noAllocation
func intConversion() {
let x = 42
_ = UInt(x)
}
@_noAllocation
func integerRange() {
for _ in 0 ..< 10 {
}
}
struct GenStruct<A> {
var a: A
}
@_noAllocation
func memoryLayout() -> Int? {
return MemoryLayout<GenStruct<Int>>.size
}
class H {
var hash: Int { 27 }
}
struct MyStruct {
static var v: Int = { // expected-error {{Using type 'H' can cause metadata allocation or locks}}
return H().hash
}()
}
@_noAllocation
func globalWithInitializer(x: MyStruct) {
_ = MyStruct.v // expected-note {{called from here}}
}
@_noAllocation
func callBadClosure(closure: ()->Int) -> Int {
return closure()
}
@_noAllocation
func badClosure() {
_ = callBadClosure(closure: { // expected-note {{called from here}}
_ = Cl() // expected-error {{Using type 'Cl' can cause metadata allocation or locks}}
return 42
})
}
func badClosure2() {
_ = callBadClosure(closure: { // expected-note {{called from here}}
_ = Cl() // expected-error {{Using type 'Cl' can cause metadata allocation or locks}}
return 42
})
}
@_noAllocation
func callGoodClosure(closure: ()->Int) -> Int {
return closure()
}
@_noAllocation
func goodClosure() {
_ = callBadClosure(closure: {
return 42
})
}
func goodClosure2() {
_ = callBadClosure(closure: {
return 42
})
}
@_noAllocation
func closueWhichModifiesLocalVar() -> Int {
var x = 42
let localNonEscapingClosure = {
x += 1
}
localNonEscapingClosure()
return x
}
struct Buffer {
var p: UnsafeMutableRawBufferPointer
func bind<T>(of type: T.Type) -> UnsafeMutableBufferPointer<T> {
self.p.bindMemory(to: T.self)
}
@_noAllocation
func callBind() -> UnsafeMutableBufferPointer<Int> {
return bind(of: Int.self)
}
}
@_noLocks
func testBitShift(_ x: Int) -> Int {
return x << 1
}
@_noLocks
func testUintIntConversion() -> Int {
let u: UInt32 = 5
return Int(u)
}
struct OptSet: OptionSet {
let rawValue: Int
public static var a: OptSet { return OptSet(rawValue: 1) }
public static var b: OptSet { return OptSet(rawValue: 2) }
public static var c: OptSet { return OptSet(rawValue: 4) }
public static var d: OptSet { return OptSet(rawValue: 8) }
}
@_noLocks
func testOptionSet(_ options: OptSet) -> Bool {
return options.contains(.b)
}
let globalA = 0xff
let globalB = UInt32(globalA)
@_noLocks
func testGlobalsWithConversion() -> UInt32 {
return globalB
}
public struct X: Collection {
public func index(after i: Int) -> Int {
return i + 1
}
public subscript(position: Int) -> Int {
get {
return 0
}
}
public var startIndex: Int = 0
public var endIndex: Int = 1
public typealias Index = Int
}
extension Collection where Element: Comparable {
public func testSorted() -> Int {
return testSorted(by: <)
}
public func testSorted(by areInIncreasingOrder: (Element, Element) -> Bool) -> Int {
let x = 0
_ = areInIncreasingOrder(self.first!, self.first!)
return x
}
}
@_noLocks
public func testCollectionSort(a: X) -> Int {
_ = a.testSorted()
return 0
}
public struct Y {
var a, b, c: Int
}
extension Y {
func with2(_ body: () -> ()) {
body()
}
func with1(_ body: (Int) -> (Int)) -> Int {
with2 {
_ = body(48)
}
return 777
}
func Xsort() -> Int {
with1 { i in
i
}
}
}
@_noLocks
public func testClosurePassing(a: inout Y) -> Int {
return a.Xsort()
}
struct LargeGenericStruct<T> {
var a: T
var b: T
var c: T
var d: T
var e: T
var f: T
var g: T
var h: T
}
var largeGeneric = LargeGenericStruct<Int>(a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8)
@_noLocks
func testLargeGenericStruct() -> LargeGenericStruct<Int> {
return largeGeneric
}
struct ContainsLargeGenericStruct {
var s: LargeGenericStruct<Int>
}
var clgs = ContainsLargeGenericStruct(s: LargeGenericStruct<Int>(a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8))
@_noLocks
func testClgs() -> ContainsLargeGenericStruct {
return clgs
}
struct NestedGenericStruct<T> {
var a: T
var b: T
var c: LargeGenericStruct<T>
var d: T
var e: T
var f: T
var g: T
var h: T
}
var nestedGeneric = NestedGenericStruct(a: 1, b: 2, c: LargeGenericStruct<Int>(a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8), d: 4, e: 5, f: 6, g: 7, h: 8)
@_noLocks
func testNestedGenericStruct() -> NestedGenericStruct<Int> {
return nestedGeneric
}
var x = 24
let pointerToX = UnsafePointer(&x)
@_noLocks
func testPointerToX() -> UnsafePointer<Int> {
return pointerToX
}
func foo<T>(_ body: () -> (T)) -> T {
return body()
}
func bar<T>(_ body: () -> (T)) -> T {
return body()
}
func baz<T>(t: T) -> T {
foo {
bar {
return t
}
}
}
@_noLocks
func nestedClosures() -> Int {
return baz(t: 42)
}
@_noAllocation
func testInfiniteLoop(_ c: Cl) {
c.classMethod() // expected-error {{called function is not known at compile time and can have unpredictable performance}}
while true {}
}
@_noAllocation
func testPrecondition(_ count: Int) {
precondition(count == 2, "abc")
}
@_noRuntime
func dynamicCastNoRuntime(_ a: AnyObject) -> Cl? {
return a as? Cl // expected-error {{dynamic casting can lock or allocate}}
}
func useExistential<T: P>(_: T) {}
@_noRuntime
func openExistentialNoRuntime(_ existential: P) {
_openExistential(existential, do: useExistential) // expected-error {{generic function calls can cause metadata allocation or locks}}
}
@_noExistentials
func dynamicCastNoExistential(_ a: AnyObject) -> Cl? {
return a as? Cl
}
@_noExistentials
func useOfExistential() -> P {
Str(x: 1) // expected-error {{cannot use a value of protocol type 'any P' in @_noExistential function}}
}
@_noExistentials
func genericNoExistential() -> some P {
Str(x: 1)
}
@_noRuntime
func genericNoRuntime() -> some P {
Str(x: 1)
}
@_noObjCBridging
func useOfExistentialNoObjc() -> P {
Str(x: 1)
}
@_noRuntime
func useOfExistentialNoRuntime() -> P {
Str(x: 1) // expected-error {{Using type 'any P' can cause metadata allocation or locks}}
}
public struct NonCopyable: ~Copyable {
var value: Int
}
@_noAllocation
public func testNonCopyable(_ foo: consuming NonCopyable) {
let _ = foo.value
}
@_noAllocation
func matchCEnum(_ variant: c_closed_enum_t) -> Int {
switch variant {
case .A:
return 1
case .B:
return 2
case .C:
return 5
}
}
public struct GenericStruct<T> {
private var x = 0
private var y: T?
@inline(never)
init() {}
}
@_noLocks
func testLargeTuple() {
typealias SixInt8s = (Int8, Int8, Int8, Int8, Int8, Int8)
_ = GenericStruct<SixInt8s>()
}
struct Ptr<T> {
public var p: UnsafeMutablePointer<T>
@_noAllocation
init(p: UnsafeMutablePointer<T>) {
self.p = p
}
}
struct NonCopyableStruct: ~Copyable {
func foo() {}
}
@_noLocks
func testNonCopyable() {
let t = NonCopyableStruct()
t.foo()
}
public struct RawLayoutWrapper: ~Copyable {
private let x = RawLayout<Int>()
@_noLocks func testit() {
x.test()
}
}
@_rawLayout(like: T)
public struct RawLayout<T>: ~Copyable {
public func test() {}
}
func takesClosure(_: () -> ()) {}
@_noLocks
func testClosureExpression<T>(_ t: T) {
takesClosure {
// expected-error@-1 {{generic closures or local functions can cause metadata allocation or locks}}
_ = T.self
}
}
@_noLocks
func testLocalFunction<T>(_ t: T) {
func localFunc() {
_ = T.self
}
takesClosure(localFunc)
// expected-error@-1 {{generic closures or local functions can cause metadata allocation or locks}}
}
func takesGInt(_ x: G<Int>) {}
struct G<T> {}
extension G where T == Int {
@_noAllocation func method() {
takesClosure {
takesGInt(self) // OK
}
}
}