Files
swift-mirror/test/1_stdlib/NewArray.swift.gyb
Dave Abrahams d9c750eefd [stdlib] Remove ArrayBuffer indirection
The case where Array stores class instances no longer requires an
intermediate indirect buffer object.

Also fixes <rdar://problem/17348939>

These are the speed changes < 0.95x and > 1.05x, as I measured them.
Although I don't have great confidence in these numbers, some are
consistent with Arnold's measurements, FWIW.

-O:
0.93 ArrayLiteral
1.56 Ary
1.39 Ary2
1.06 CaptureProp
1.93 ClassArrayGetter
1.08 DeltaBlue
1.08 DollarChain
1.13 InsertionSort
1.08 PrimeNum
1.11 RC4
0.93 Rectangles
1.08 SwiftStructuresBubbleSort

-Onone:
1.06 ArrayLiteral
1.10 ArraySubscript
1.12 Ary
1.12 Ary2
1.10 Ary3
1.20 ClassArrayGetter
1.19 DeltaBlue
1.08 DollarChain
1.09 Hash
1.07 Havlak
1.10 HeapSort
1.11 ImageProc
1.17 InsertionSort
1.18 Memset
1.11 NBody
1.07 PrimeNum
1.11 QuickSort
1.12 RC4
1.08 Rectangles
1.07 SelectionSort
1.06 StringBuilder
1.12 SwiftStructuresBubbleSort
1.10 Walsh
1.12 XorLoop

-Ounchecked:
0.91 ArrayLiteral
1.74 Ary
1.53 Ary2
2.08 ClassArrayGetter
1.19 DeltaBlue
1.06 DollarChain
1.07 Havlak
1.09 ImageProc
1.22 InsertionSort
0.89 PopFrontArrayGeneric
1.11 PrimeNum
0.85 QuickSort
1.10 RC4
1.12 Rectangles
1.06 StrToInt
1.11 SwiftStructuresBubbleSort

Swift SVN r23551
2014-11-22 08:19:57 +00:00

514 lines
14 KiB
Swift

%# -*- mode: swift -*-
//===--- Array.swift ------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
// RUN-DISABLED: %target-run-simple-swift | FileCheck %s
// RUN: rm -rf %t && mkdir -p %t && %S/../../utils/gyb %s -o %t/NewArray.swift
// RUN: %S/../../utils/line-directive %t/NewArray.swift -- %target-build-swift -module-cache-path %t/clang-module-cache %t/NewArray.swift -o %t/a.out -Xfrontend -disable-access-control
// RUN: %target-run %t/a.out 2>&1 | %S/../../utils/line-directive %t/NewArray.swift -- FileCheck %t/NewArray.swift
var xCount = 0
var xSerial = 0
import StdlibUnittest
// Instead of testing with Int elements, we use this wrapper class
// that can help us track allocations and find issues with object
// lifetime inside Array implementations.
final class X : ForwardIndexType, Comparable, Printable,
IntegerLiteralConvertible
{
required init(_ value: Int) {
++xCount
serial = ++xSerial
self.value = value
}
deinit {
assert(serial > 0, "double destruction!")
--xCount
serial = -serial
}
var description: String {
assert(serial > 0, "dead X!")
return value.description
}
func successor() -> Self {
return self.dynamicType(self.value.successor())
}
convenience init(integerLiteral value: Int) {
self.init(value)
}
var value: Int
var serial: Int
}
func == (x: X, y: X) -> Bool {
return x.value == y.value
}
func < (x: X, y: X) -> Bool {
return x.value < y.value
}
//===----------------------------------------------------------------------===//
func printSequence<T: SequenceType>(x: T) {
print("[")
var prefix = ""
for a in x {
print(prefix)
print(a)
prefix = ", "
}
println("]")
}
typealias BufferID = UnsafePointer<Void>
func bufferID<T : _ArrayType>(x: T) -> BufferID {
return x._buffer.identity
}
func checkReallocation<T : _ArrayType>(
x: T, lastBuffer: BufferID, reallocationExpected: Bool
) -> BufferID {
let currentBuffer = bufferID(x)
if (currentBuffer != lastBuffer) != reallocationExpected {
let message = reallocationExpected ? "lack of" : ""
println("unexpected \(message) reallocation")
}
return currentBuffer
}
func checkEqual<
S1 : SequenceType, S2 : SequenceType
where
S1.Generator.Element == S2.Generator.Element,
S1.Generator.Element : Equatable
>(a1: S1, a2: S2, expected: Bool) {
if equal(a1, a2) != expected {
let un = expected ? "un" : ""
println("unexpectedly \(un)equal sequences!")
}
}
func test<
T: _ArrayType
where T.Generator.Element == T._Buffer.Element,
T._Buffer.Element == T.Element,
T.Element == X,
T.Index == Int
>(_: T.Type, label: String) {
print("test: \(label)...")
var x: T = [1, 2, 3, 4, 5]
checkEqual(x, 1...5, true)
x.reserveCapacity(x.count + 2)
checkEqual(x, 1...5, true)
let bufferId0 = bufferID(x)
// Append a range of integers
x += 0..<2
let bufferId1 = checkReallocation(x, bufferId0, false)
for i in x.count..<(x.capacity + 1) {
let bufferId1a = checkReallocation(x, bufferId1, false)
x.append(13)
}
let bufferId2 = checkReallocation(x, bufferId1, true)
let y = x
x[x.endIndex.predecessor()] = 17
let bufferId3 = checkReallocation(x, bufferId2, true)
checkEqual(x, y, false)
func checkReallocations(
var a: T, growthDescription: String, growBy1: (inout _: T)->()
) -> () {
var reallocations = 0
// Note: right now this test is dependent on a growth factor of 2.
// It's possible that factor will change, but (cursory) testing
// has shown that using 1.5, the other popular growth factor,
// slows things down.
for _ in a.count..<(a.capacity * 4) {
let oldId = bufferID(a)
growBy1(&a)
if oldId != bufferID(a) {
++reallocations
}
}
if reallocations > 3 {
println(
"Unexpectedly found \(reallocations) reallocations "
+ "of \(label) when growing via \(growthDescription)")
}
}
checkReallocations(x, "append") { (inout x: T)->() in x.append(42) }
checkReallocations(x, "+=") { (inout x: T)->() in x.append(42) }
println("done.")
}
println("testing...")
// CHECK: testing...
test(ContiguousArray<X>.self, "ContiguousArray")
// CHECK-NEXT: test: ContiguousArray...done
test(_UnitTestArray<X>.self, "_UnitTestArray")
// CHECK-NEXT: test: _UnitTestArray...done
test(Array<X>.self, "Array")
// CHECK-NEXT: test: Array...done
test(Slice<X>.self, "Slice")
// CHECK-NEXT: test: Slice...done
func testAsArray() {
println("== AsArray ==")
var w: ContiguousArray<X> = [4, 2, 1]
// CHECK: == AsArray ==
let x = ContiguousArray(w)
println(bufferID(w) == bufferID(x))
// CHECK-NEXT: true
let y = Array(x)
println(bufferID(x) == bufferID(y))
// CHECK-NEXT: true
// Because of their indirection, arrays of classes can share
// buffers.
let y1 = Array(y)
println(bufferID(y1) == bufferID(y))
// CHECK-NEXT: true
let z = Slice(y)
println(bufferID(y) == bufferID(z))
// CHECK-NEXT: true
w = ContiguousArray(z)
println(bufferID(w) == bufferID(z))
// CHECK-NEXT: true
let zz = y[0..<2]
println(bufferID(zz))
// CHECK-NEXT: 0x
}
testAsArray()
import Foundation
func nsArrayOfStrings() -> Array<NSString> {
let src: ContiguousArray<NSString> = ["foo", "bar", "baz"]
return src.withUnsafeBufferPointer {
let ns = NSArray(objects: UnsafePointer($0.baseAddress), count: $0.count)
return _convertNSArrayToArray(ns)
}
}
func testCocoa() {
println("== Cocoa ==")
// CHECK: == Cocoa ==
var a = nsArrayOfStrings()
printSequence(a)
// CHECK-NEXT: [foo, bar, baz]
a.append("qux")
printSequence(a)
// CHECK-NEXT: [foo, bar, baz, qux]
a = nsArrayOfStrings()
printSequence(a)
// CHECK-NEXT: [foo, bar, baz]
var b = a
a[1] = "garply"
printSequence(a)
// CHECK-NEXT: [foo, garply, baz]
// Mutating an element in a has no effect on b
printSequence(b)
// CHECK-NEXT: [foo, bar, baz]
a = nsArrayOfStrings()
a.insert("bag", atIndex:2)
printSequence(a)
// CHECK-NEXT: [foo, bar, bag, baz]
a = nsArrayOfStrings()
a.reserveCapacity(30)
printSequence(a)
// CHECK-NEXT: [foo, bar, baz]
println(a.capacity >= 30)
// CHECK-NEXT: true
// Prove that we create contiguous storage for an opaque NSArray
a.withUnsafeBufferPointer {
(p)->() in
println(p[0])
// CHECK-NEXT: foo
}
}
testCocoa()
extension Slice {
mutating func qsort(compare: (T,T)->Bool) {
_introSort(&self, indices(self), compare)
}
}
func testSlice() {
println("== Slice ==")
// CHECK: == Slice ==
// do some tests on the shared semantics
var b = ContiguousArray(X(0)..<X(7))
// Slice it
var bSlice = b[3..<5]
println("<\(bSlice.count)>")
// CHECK-NEXT: <2>
println("bSlice0: \(bSlice)") // CHECK-NEXT: bSlice0: [3, 4]
// bSlice += X(11)..<X(13)
// Writing into b does not change bSlice
b[4] = 41
println("bSlice1: \(bSlice)") // CHECK-NEXT: bSlice1: [3, 4]
// Writing into bSlice does not change b
bSlice[1] = 42
println("bSlice2: \(bSlice)") // CHECK-NEXT: bSlice2: [3, 42]
printSequence(b) // CHECK-NEXT: [0, 1, 2, 3, 41, 5, 6]
var c = b
b[4..<b.count].qsort(<)
printSequence(b) // CHECK-NEXT: [0, 1, 2, 3, 5, 6, 41]
printSequence(c) // CHECK-NEXT: [0, 1, 2, 3, 41, 5, 6]
// Now a bridged slice
var a = Array<NSString>(
_ArrayBuffer(nsArray: nsArrayOfStrings()._buffer._asCocoaArray()))
printSequence(a) // CHECK-NEXT: [foo, bar, baz]
var aSlice = a[1..<3] // CHECK-NEXT: [bar, baz]
printSequence(aSlice)
// Writing into aSlice works
aSlice[0] = "buzz" // CHECK-NEXT: [buzz, baz]
printSequence(aSlice)
// ...and doesn't affect a
printSequence(a) // CHECK-NEXT: [foo, bar, baz]
// Appending to aSlice works...
aSlice.append("fodder")
println("<\(aSlice.count)>") // CHECK-NEXT: <3>
printSequence(aSlice) // CHECK-NEXT: [buzz, baz, fodder]
// And doesn't change a
printSequence(a) // CHECK-NEXT: [foo, bar, baz]
}
testSlice()
//===--- sub-range replacement --------------------------------------------===//
// Size of the array on which we're going to test "replace."
// testing time grows roughly as the cube of this constant
let testWidth = 11
%arrayTypes = ['ContiguousArray', '_UnitTestArray', 'Array', 'Slice']
%for A in arrayTypes:
func testReplace(make: ()->${A}<X>) {
checkRangeReplaceable(make, { X(100)..<X(100 + $0) })
}
func testReplace${A}(
makeOne: ()->${A}<X> = {
var x = ${A}<X>()
// make sure some - but not all - replacements will have to grow the buffer
x.reserveCapacity(testWidth * 3 / 2)
x += X(0)..<X(testWidth)
return x
}
) {
testReplace(makeOne)
// Create one that will not be uniquely-referenced so we can test
// the out-of-place code paths.
let r = makeOne()
testReplace({ r })
// This test should ensure r's retain isn't dropped before we start testing.
if (r.count != testWidth) {
println("something bad happened!")
}
}
println("testing subrange replacement in ${A}")
testReplace${A}()
%end
// Also test with a sub-slice of some larger buffer. The "trailing"
// case is interesting because when the buffer is uniquely referenced
// we can expect to expand the slice in-place
for (maxValue, label) in [(testWidth, "trailing"), (testWidth*2, "interior")] {
println("testing subrange replacement in \(label) Sub-Slice")
testReplaceSlice {
var a = ContiguousArray(X(-testWidth)..<X(maxValue))
a.reserveCapacity(a.count * 3 / 2)
return a[testWidth..<(2 * testWidth)]
}
}
// CHECK-NEXT: testing subrange replacement in ContiguousArray
// CHECK-NEXT: testing subrange replacement in _UnitTestArray
// CHECK-NEXT: testing subrange replacement in Array
// CHECK-NEXT: testing subrange replacement in Slice
// CHECK-NEXT: testing subrange replacement in trailing Sub-Slice
// CHECK-NEXT: testing subrange replacement in interior Sub-Slice
//===--- inout violations -------------------------------------------------===//
// The user has to obey certain rules when things are passed via
// inout, but in those cases we only guarantee memory-safety, not
// coherent semantics. Let's try to force a memory-safety problem
// here. This crashes when withUnsafeMutableBufferPointer is not
// sufficiently careful.
func testInoutViolation() {
var a: [X] = [
X(10), X(8), X(6), X(4), X(2), X(0), X(9), X(7), X(5), X(3), X(1)
]
%for A in arrayTypes:
if true {
var b = ${A}(a)
b.sort { x, y in
b.removeAll()
return x < y
}
}
%end
// An overload of sort for Arrays uses withUnsafeMutableBufferPointer,
// which disables bounds checks.
sort(&a) { x, y in
a = [] // Invalidate the whole array during sorting
return x < y
}
}
testInoutViolation()
//===--- single-element modifiers -----------------------------------------===//
%for A in arrayTypes:
func testSingleElementModifiers${A}() {
println("testing ${A} single-argument modifiers")
// CHECK-NEXT: testing ${A} single-argument modifiers
var a = ${A}(X(0)..<10)
println(a.removeLast().value) // CHECK-NEXT: 9
printSequence(a) // CHECK-NEXT: [0, 1, 2, 3, 4, 5, 6, 7, 8]
a.insert(42, atIndex: 4)
printSequence(a) // CHECK-NEXT: [0, 1, 2, 3, 42, 4, 5, 6, 7, 8]
println(a.removeAtIndex(2).value) // CHECK-NEXT: 2
printSequence(a) // CHECK-NEXT: [0, 1, 3, 42, 4, 5, 6, 7, 8]
}
testSingleElementModifiers${A}()
%end
//===--- isEmpty, first, last ---------------------------------------------===//
%for A in arrayTypes:
func testIsEmptyFirstLast${A}() {
println("testing ${A} isEmpty, first, and last")
// CHECK-NEXT: testing ${A} isEmpty, first, and last
println(${A}<Int>().isEmpty) // CHECK-NEXT: true
println(${A}(42...42).isEmpty) // CHECK-NEXT: false
println("<\(${A}(3...42).first!)>") // CHECK-NEXT: <3>
println("<\(${A}(3...42).last!)>") // CHECK-NEXT: <42>
println("<\(${A}<Int>().first)>") // CHECK-NEXT: nil
println("<\(${A}<Int>().last)>") // CHECK-NEXT: nil
var a = ${A}(X(0)..<10)
println(a.removeLast().value) // CHECK-NEXT: 9
printSequence(a) // CHECK-NEXT: [0, 1, 2, 3, 4, 5, 6, 7, 8]
a.insert(42, atIndex: 4)
printSequence(a) // CHECK-NEXT: [0, 1, 2, 3, 42, 4, 5, 6, 7, 8]
println(a.removeAtIndex(2).value) // CHECK-NEXT: 2
printSequence(a) // CHECK-NEXT: [0, 1, 3, 42, 4, 5, 6, 7, 8]
}
testIsEmptyFirstLast${A}()
%end
//===--- Regression Tests -------------------------------------------------===//
func rdar16958865() {
var a: [Int] = []
a += SequenceOf([ 42, 4242 ])
// CHECK-NEXT: [42, 4242]
println(a)
}
rdar16958865()
import SpriteKit
class Rdar16914909 : NSObject {
var basicColorSet = [SKColor]()
func doColorStuff() {
basicColorSet.append(SKColor.lightGrayColor())
println("appended")
}
}
Rdar16914909().doColorStuff()
// CHECK-NEXT: appended
// FIXME: should be "leaks = 0", if not for <rdar://problem/17892507>
println("leaks = \(xCount)")
// CHECK-NEXT: leaks =
// CHECK-NEXT: all done.
println("all done.")
// ${'Local Variables'}:
// eval: (read-only-mode 1)
// End: