mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
Optimizer: support static initialization of global arrays
The buffer of global arrays could already be statically initialized. The missing piece was the array itself, which is basically a reference to the array buffer. For example: ``` var a = [1, 2, 3] ``` ends up in two statically initialized globals: 1. the array buffer, which contains the elements 2. the variable `a` which is a single reference (= pointer) of the array buffer This optimization removes the need for lazy initialization of such variables. rdar://127757554
This commit is contained in:
@@ -283,7 +283,7 @@ private func getInitStores(to allocVectorBuiltin: BuiltinInst, count: Int,
|
||||
|
||||
for use in allocVectorBuiltin.uses {
|
||||
if let ptrToAddr = use.instruction as? PointerToAddressInst {
|
||||
if !findInitStores(of: ptrToAddr, atIndex: 0, &stores) {
|
||||
if !findInitStores(of: ptrToAddr, atIndex: 0, &stores, context) {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
@@ -307,18 +307,20 @@ private func getInitStores(to allocVectorBuiltin: BuiltinInst, count: Int,
|
||||
return stores.map { $0! }
|
||||
}
|
||||
|
||||
private func findInitStores(of address: Value, atIndex: Int, _ initStores: inout [StoreInst?]) -> Bool {
|
||||
private func findInitStores(of address: Value, atIndex: Int, _ initStores: inout [StoreInst?],
|
||||
_ context: FunctionPassContext) -> Bool
|
||||
{
|
||||
for use in address.uses {
|
||||
switch use.instruction {
|
||||
case let indexAddr as IndexAddrInst:
|
||||
guard let indexLiteral = indexAddr.index as? IntegerLiteralInst,
|
||||
let index = indexLiteral.value,
|
||||
findInitStores(of: indexAddr, atIndex: atIndex + index, &initStores) else
|
||||
findInitStores(of: indexAddr, atIndex: atIndex + index, &initStores, context) else
|
||||
{
|
||||
return false
|
||||
}
|
||||
case let store as StoreInst where store.destinationOperand == use:
|
||||
if !store.source.isValidGlobalInitValue {
|
||||
if !store.source.isValidGlobalInitValue(context) {
|
||||
return false
|
||||
}
|
||||
if atIndex >= initStores.count ||
|
||||
|
||||
@@ -59,7 +59,10 @@ let initializeStaticGlobalsPass = FunctionPass(name: "initialize-static-globals"
|
||||
|
||||
// The initializer must not contain a `global_value` because `global_value` needs to
|
||||
// initialize the class metadata at runtime.
|
||||
guard let (allocInst, storeToGlobal) = getGlobalInitialization(of: function, allowGlobalValue: false) else {
|
||||
guard let (allocInst, storeToGlobal) = getGlobalInitialization(of: function,
|
||||
forStaticInitializer: true,
|
||||
context) else
|
||||
{
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,10 @@ private func optimizeObjectAllocation(allocRef: AllocRefInstBase, _ context: Fun
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let (storesToClassFields, storesToTailElements) = getInitialization(of: allocRef, ignore: endOfInitInst) else {
|
||||
guard let (storesToClassFields, storesToTailElements) = getInitialization(of: allocRef,
|
||||
ignore: endOfInitInst,
|
||||
context) else
|
||||
{
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -136,7 +139,8 @@ private func findEndOfInitialization(of object: Value, canStoreToGlobal: Bool) -
|
||||
return nil
|
||||
}
|
||||
|
||||
private func getInitialization(of allocRef: AllocRefInstBase, ignore ignoreInst: Instruction)
|
||||
private func getInitialization(of allocRef: AllocRefInstBase, ignore ignoreInst: Instruction,
|
||||
_ context: FunctionPassContext)
|
||||
-> (storesToClassFields: [StoreInst], storesToTailElements: [StoreInst])?
|
||||
{
|
||||
guard let numTailElements = allocRef.numTailElements else {
|
||||
@@ -154,7 +158,7 @@ private func getInitialization(of allocRef: AllocRefInstBase, ignore ignoreInst:
|
||||
let tailCount = numTailElements != 0 ? numTailElements * allocRef.numStoresPerTailElement : 0
|
||||
var tailStores = Array<StoreInst?>(repeating: nil, count: tailCount)
|
||||
|
||||
if !findInitStores(of: allocRef, &fieldStores, &tailStores, ignore: ignoreInst) {
|
||||
if !findInitStores(of: allocRef, &fieldStores, &tailStores, ignore: ignoreInst, context) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -168,7 +172,9 @@ private func getInitialization(of allocRef: AllocRefInstBase, ignore ignoreInst:
|
||||
private func findInitStores(of object: Value,
|
||||
_ fieldStores: inout [StoreInst?],
|
||||
_ tailStores: inout [StoreInst?],
|
||||
ignore ignoreInst: Instruction) -> Bool {
|
||||
ignore ignoreInst: Instruction,
|
||||
_ context: FunctionPassContext) -> Bool
|
||||
{
|
||||
for use in object.uses {
|
||||
let user = use.instruction
|
||||
switch user {
|
||||
@@ -177,15 +183,15 @@ private func findInitStores(of object: Value,
|
||||
is MoveValueInst,
|
||||
is EndInitLetRefInst,
|
||||
is BeginBorrowInst:
|
||||
if !findInitStores(of: user as! SingleValueInstruction, &fieldStores, &tailStores, ignore: ignoreInst) {
|
||||
if !findInitStores(of: user as! SingleValueInstruction, &fieldStores, &tailStores, ignore: ignoreInst, context) {
|
||||
return false
|
||||
}
|
||||
case let rea as RefElementAddrInst:
|
||||
if !findStores(inUsesOf: rea, index: rea.fieldIndex, stores: &fieldStores) {
|
||||
if !findStores(inUsesOf: rea, index: rea.fieldIndex, stores: &fieldStores, context) {
|
||||
return false
|
||||
}
|
||||
case let rta as RefTailAddrInst:
|
||||
if !findStores(toTailAddress: rta, tailElementIndex: 0, stores: &tailStores) {
|
||||
if !findStores(toTailAddress: rta, tailElementIndex: 0, stores: &tailStores, context) {
|
||||
return false
|
||||
}
|
||||
case ignoreInst,
|
||||
@@ -200,7 +206,8 @@ private func findInitStores(of object: Value,
|
||||
return true
|
||||
}
|
||||
|
||||
private func findStores(toTailAddress tailAddr: Value, tailElementIndex: Int, stores: inout [StoreInst?]) -> Bool {
|
||||
private func findStores(toTailAddress tailAddr: Value, tailElementIndex: Int, stores: inout [StoreInst?],
|
||||
_ context: FunctionPassContext) -> Bool {
|
||||
for use in tailAddr.uses {
|
||||
switch use.instruction {
|
||||
case let indexAddr as IndexAddrInst:
|
||||
@@ -209,26 +216,26 @@ private func findStores(toTailAddress tailAddr: Value, tailElementIndex: Int, st
|
||||
{
|
||||
return false
|
||||
}
|
||||
if !findStores(toTailAddress: indexAddr, tailElementIndex: tailElementIndex + tailIdx, stores: &stores) {
|
||||
if !findStores(toTailAddress: indexAddr, tailElementIndex: tailElementIndex + tailIdx, stores: &stores, context) {
|
||||
return false
|
||||
}
|
||||
case let tea as TupleElementAddrInst:
|
||||
// The tail elements are tuples. There is a separate store for each tuple element.
|
||||
let numTupleElements = tea.tuple.type.tupleElements.count
|
||||
let tupleIdx = tea.fieldIndex
|
||||
if !findStores(inUsesOf: tea, index: tailElementIndex * numTupleElements + tupleIdx, stores: &stores) {
|
||||
if !findStores(inUsesOf: tea, index: tailElementIndex * numTupleElements + tupleIdx, stores: &stores, context) {
|
||||
return false
|
||||
}
|
||||
case let atp as AddressToPointerInst:
|
||||
if !findStores(toTailAddress: atp, tailElementIndex: tailElementIndex, stores: &stores) {
|
||||
if !findStores(toTailAddress: atp, tailElementIndex: tailElementIndex, stores: &stores, context) {
|
||||
return false
|
||||
}
|
||||
case let mdi as MarkDependenceInst:
|
||||
if !findStores(toTailAddress: mdi, tailElementIndex: tailElementIndex, stores: &stores) {
|
||||
if !findStores(toTailAddress: mdi, tailElementIndex: tailElementIndex, stores: &stores, context) {
|
||||
return false
|
||||
}
|
||||
case let pta as PointerToAddressInst:
|
||||
if !findStores(toTailAddress: pta, tailElementIndex: tailElementIndex, stores: &stores) {
|
||||
if !findStores(toTailAddress: pta, tailElementIndex: tailElementIndex, stores: &stores, context) {
|
||||
return false
|
||||
}
|
||||
case let store as StoreInst:
|
||||
@@ -237,7 +244,7 @@ private func findStores(toTailAddress tailAddr: Value, tailElementIndex: Int, st
|
||||
// Just to be on the safe side..
|
||||
return false
|
||||
}
|
||||
if !handleStore(store, index: tailElementIndex, stores: &stores) {
|
||||
if !handleStore(store, index: tailElementIndex, stores: &stores, context) {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
@@ -249,10 +256,12 @@ private func findStores(toTailAddress tailAddr: Value, tailElementIndex: Int, st
|
||||
return true
|
||||
}
|
||||
|
||||
private func findStores(inUsesOf address: Value, index: Int, stores: inout [StoreInst?]) -> Bool {
|
||||
private func findStores(inUsesOf address: Value, index: Int, stores: inout [StoreInst?],
|
||||
_ context: FunctionPassContext) -> Bool
|
||||
{
|
||||
for use in address.uses {
|
||||
if let store = use.instruction as? StoreInst {
|
||||
if !handleStore(store, index: index, stores: &stores) {
|
||||
if !handleStore(store, index: index, stores: &stores, context) {
|
||||
return false
|
||||
}
|
||||
} else if !isValidUseOfObject(use) {
|
||||
@@ -262,9 +271,11 @@ private func findStores(inUsesOf address: Value, index: Int, stores: inout [Stor
|
||||
return true
|
||||
}
|
||||
|
||||
private func handleStore(_ store: StoreInst, index: Int, stores: inout [StoreInst?]) -> Bool {
|
||||
private func handleStore(_ store: StoreInst, index: Int, stores: inout [StoreInst?],
|
||||
_ context: FunctionPassContext) -> Bool
|
||||
{
|
||||
if index >= 0 && index < stores.count,
|
||||
store.source.isValidGlobalInitValue,
|
||||
store.source.isValidGlobalInitValue(context),
|
||||
stores[index] == nil {
|
||||
stores[index] = store
|
||||
return true
|
||||
|
||||
@@ -250,7 +250,7 @@ private func getInitializerFromInitFunction(of globalAddr: GlobalAddrInst, _ con
|
||||
}
|
||||
let initFn = initFnRef.referencedFunction
|
||||
context.notifyDependency(onBodyOf: initFn)
|
||||
guard let (_, storeToGlobal) = getGlobalInitialization(of: initFn, allowGlobalValue: true) else {
|
||||
guard let (_, storeToGlobal) = getGlobalInitialization(of: initFn, forStaticInitializer: false, context) else {
|
||||
return nil
|
||||
}
|
||||
return storeToGlobal.source
|
||||
|
||||
@@ -131,18 +131,18 @@ extension Value {
|
||||
}
|
||||
|
||||
/// True if this value is a valid in a static initializer, including all its operands.
|
||||
var isValidGlobalInitValue: Bool {
|
||||
func isValidGlobalInitValue(_ context: some Context) -> Bool {
|
||||
guard let svi = self as? SingleValueInstruction else {
|
||||
return false
|
||||
}
|
||||
if let beginAccess = svi as? BeginAccessInst {
|
||||
return beginAccess.address.isValidGlobalInitValue
|
||||
return beginAccess.address.isValidGlobalInitValue(context)
|
||||
}
|
||||
if !svi.isValidInStaticInitializerOfGlobal {
|
||||
if !svi.isValidInStaticInitializerOfGlobal(context) {
|
||||
return false
|
||||
}
|
||||
for op in svi.operands {
|
||||
if !op.value.isValidGlobalInitValue {
|
||||
if !op.value.isValidGlobalInitValue(context) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -303,6 +303,102 @@ extension Instruction {
|
||||
}
|
||||
return !mayReadOrWriteMemory && !hasUnspecifiedSideEffects
|
||||
}
|
||||
|
||||
func isValidInStaticInitializerOfGlobal(_ context: some Context) -> Bool {
|
||||
// Rule out SILUndef and SILArgument.
|
||||
if operands.contains(where: { $0.value.definingInstruction == nil }) {
|
||||
return false
|
||||
}
|
||||
switch self {
|
||||
case let bi as BuiltinInst:
|
||||
switch bi.id {
|
||||
case .ZeroInitializer:
|
||||
let type = bi.type.isBuiltinVector ? bi.type.builtinVectorElementType : bi.type
|
||||
return type.isBuiltinInteger || type.isBuiltinFloat
|
||||
case .PtrToInt:
|
||||
return bi.operands[0].value is StringLiteralInst
|
||||
case .IntToPtr:
|
||||
return bi.operands[0].value is IntegerLiteralInst
|
||||
case .StringObjectOr:
|
||||
// The first operand can be a string literal (i.e. a pointer), but the
|
||||
// second operand must be a constant. This enables creating a
|
||||
// a pointer+offset relocation.
|
||||
// Note that StringObjectOr requires the or'd bits in the first
|
||||
// operand to be 0, so the operation is equivalent to an addition.
|
||||
return bi.operands[1].value is IntegerLiteralInst
|
||||
case .ZExtOrBitCast:
|
||||
return true;
|
||||
case .USubOver:
|
||||
// Handle StringObjectOr(tuple_extract(usub_with_overflow(x, offset)), bits)
|
||||
// This pattern appears in UTF8 String literal construction.
|
||||
if let tei = bi.uses.getSingleUser(ofType: TupleExtractInst.self),
|
||||
tei.isResultOfOffsetSubtract {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
case .OnFastPath:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
case let tei as TupleExtractInst:
|
||||
// Handle StringObjectOr(tuple_extract(usub_with_overflow(x, offset)), bits)
|
||||
// This pattern appears in UTF8 String literal construction.
|
||||
if tei.isResultOfOffsetSubtract,
|
||||
let bi = tei.uses.getSingleUser(ofType: BuiltinInst.self),
|
||||
bi.id == .StringObjectOr {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
case let sli as StringLiteralInst:
|
||||
switch sli.encoding {
|
||||
case .Bytes, .UTF8, .UTF8_OSLOG:
|
||||
return true
|
||||
case .ObjCSelector:
|
||||
// Objective-C selector string literals cannot be used in static
|
||||
// initializers.
|
||||
return false
|
||||
}
|
||||
case let gvi as GlobalValueInst:
|
||||
return context.canMakeStaticObjectReadOnly(objectType: gvi.type)
|
||||
case is StructInst,
|
||||
is TupleInst,
|
||||
is EnumInst,
|
||||
is IntegerLiteralInst,
|
||||
is FloatLiteralInst,
|
||||
is ObjectInst,
|
||||
is VectorInst,
|
||||
is AllocVectorInst,
|
||||
is UncheckedRefCastInst,
|
||||
is ValueToBridgeObjectInst,
|
||||
is ConvertFunctionInst,
|
||||
is ThinToThickFunctionInst,
|
||||
is AddressToPointerInst,
|
||||
is GlobalAddrInst,
|
||||
is FunctionRefInst:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Match the pattern:
|
||||
// tuple_extract(usub_with_overflow(x, integer_literal, integer_literal 0), 0)
|
||||
private extension TupleExtractInst {
|
||||
var isResultOfOffsetSubtract: Bool {
|
||||
if fieldIndex == 0,
|
||||
let bi = tuple as? BuiltinInst,
|
||||
bi.id == .USubOver,
|
||||
bi.operands[1].value is IntegerLiteralInst,
|
||||
let overflowLiteral = bi.operands[2].value as? IntegerLiteralInst,
|
||||
let overflowValue = overflowLiteral.value,
|
||||
overflowValue == 0
|
||||
{
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
extension StoreInst {
|
||||
@@ -657,7 +753,8 @@ extension InstructionRange {
|
||||
/// ```
|
||||
func getGlobalInitialization(
|
||||
of function: Function,
|
||||
allowGlobalValue: Bool
|
||||
forStaticInitializer: Bool,
|
||||
_ context: some Context
|
||||
) -> (allocInst: AllocGlobalInst, storeToGlobal: StoreInst)? {
|
||||
guard let block = function.blocks.singleElement else {
|
||||
return nil
|
||||
@@ -695,10 +792,10 @@ func getGlobalInitialization(
|
||||
return nil
|
||||
}
|
||||
store = si
|
||||
case is GlobalValueInst where allowGlobalValue:
|
||||
case is GlobalValueInst where !forStaticInitializer:
|
||||
break
|
||||
default:
|
||||
if !inst.isValidInStaticInitializerOfGlobal {
|
||||
if !inst.isValidInStaticInitializerOfGlobal(context) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,101 +81,6 @@ final public class GlobalVariable : CustomStringConvertible, HasShortDescription
|
||||
public var bridged: BridgedGlobalVar { BridgedGlobalVar(SwiftObject(self)) }
|
||||
}
|
||||
|
||||
extension Instruction {
|
||||
public var isValidInStaticInitializerOfGlobal: Bool {
|
||||
// Rule out SILUndef and SILArgument.
|
||||
if operands.contains(where: { $0.value.definingInstruction == nil }) {
|
||||
return false
|
||||
}
|
||||
switch self {
|
||||
case let bi as BuiltinInst:
|
||||
switch bi.id {
|
||||
case .ZeroInitializer:
|
||||
let type = bi.type.isBuiltinVector ? bi.type.builtinVectorElementType : bi.type
|
||||
return type.isBuiltinInteger || type.isBuiltinFloat
|
||||
case .PtrToInt:
|
||||
return bi.operands[0].value is StringLiteralInst
|
||||
case .IntToPtr:
|
||||
return bi.operands[0].value is IntegerLiteralInst
|
||||
case .StringObjectOr:
|
||||
// The first operand can be a string literal (i.e. a pointer), but the
|
||||
// second operand must be a constant. This enables creating a
|
||||
// a pointer+offset relocation.
|
||||
// Note that StringObjectOr requires the or'd bits in the first
|
||||
// operand to be 0, so the operation is equivalent to an addition.
|
||||
return bi.operands[1].value is IntegerLiteralInst
|
||||
case .ZExtOrBitCast:
|
||||
return true;
|
||||
case .USubOver:
|
||||
// Handle StringObjectOr(tuple_extract(usub_with_overflow(x, offset)), bits)
|
||||
// This pattern appears in UTF8 String literal construction.
|
||||
if let tei = bi.uses.getSingleUser(ofType: TupleExtractInst.self),
|
||||
tei.isResultOfOffsetSubtract {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
case .OnFastPath:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
case let tei as TupleExtractInst:
|
||||
// Handle StringObjectOr(tuple_extract(usub_with_overflow(x, offset)), bits)
|
||||
// This pattern appears in UTF8 String literal construction.
|
||||
if tei.isResultOfOffsetSubtract,
|
||||
let bi = tei.uses.getSingleUser(ofType: BuiltinInst.self),
|
||||
bi.id == .StringObjectOr {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
case let sli as StringLiteralInst:
|
||||
switch sli.encoding {
|
||||
case .Bytes, .UTF8, .UTF8_OSLOG:
|
||||
return true
|
||||
case .ObjCSelector:
|
||||
// Objective-C selector string literals cannot be used in static
|
||||
// initializers.
|
||||
return false
|
||||
}
|
||||
case is StructInst,
|
||||
is TupleInst,
|
||||
is EnumInst,
|
||||
is IntegerLiteralInst,
|
||||
is FloatLiteralInst,
|
||||
is ObjectInst,
|
||||
is VectorInst,
|
||||
is AllocVectorInst,
|
||||
is ValueToBridgeObjectInst,
|
||||
is ConvertFunctionInst,
|
||||
is ThinToThickFunctionInst,
|
||||
is AddressToPointerInst,
|
||||
is GlobalAddrInst,
|
||||
is FunctionRefInst:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Match the pattern:
|
||||
// tuple_extract(usub_with_overflow(x, integer_literal, integer_literal 0), 0)
|
||||
private extension TupleExtractInst {
|
||||
var isResultOfOffsetSubtract: Bool {
|
||||
if fieldIndex == 0,
|
||||
let bi = tuple as? BuiltinInst,
|
||||
bi.id == .USubOver,
|
||||
bi.operands[1].value is IntegerLiteralInst,
|
||||
let overflowLiteral = bi.operands[2].value as? IntegerLiteralInst,
|
||||
let overflowValue = overflowLiteral.value,
|
||||
overflowValue == 0
|
||||
{
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Bridging utilities
|
||||
|
||||
extension BridgedGlobalVar {
|
||||
|
||||
@@ -355,6 +355,9 @@ Explosion irgen::emitConstantValue(IRGenModule &IGM, SILValue operand,
|
||||
} else if (auto *CFI = dyn_cast<ConvertFunctionInst>(operand)) {
|
||||
return emitConstantValue(IGM, CFI->getOperand(), flatten);
|
||||
|
||||
} else if (auto *URCI = dyn_cast<UncheckedRefCastInst>(operand)) {
|
||||
return emitConstantValue(IGM, URCI->getOperand(), flatten);
|
||||
|
||||
} else if (auto *T2TFI = dyn_cast<ThinToThickFunctionInst>(operand)) {
|
||||
SILType type = operand->getType();
|
||||
auto *sTy = cast<llvm::StructType>(IGM.getTypeInfo(type).getStorageType());
|
||||
@@ -405,6 +408,14 @@ Explosion irgen::emitConstantValue(IRGenModule &IGM, SILValue operand,
|
||||
return llvm::ConstantPointerNull::get(IGM.OpaquePtrTy);
|
||||
}
|
||||
|
||||
Address addr = IGM.getAddrOfSILGlobalVariable(var, ti, NotForDefinition);
|
||||
return addr.getAddress();
|
||||
} else if (auto *gVal = dyn_cast<GlobalValueInst>(operand)) {
|
||||
assert(IGM.canMakeStaticObjectReadOnly(gVal->getType()));
|
||||
SILGlobalVariable *var = gVal->getReferencedGlobal();
|
||||
auto &ti = IGM.getTypeInfo(var->getLoweredType());
|
||||
auto expansion = IGM.getResilienceExpansionForLayout(var);
|
||||
assert(ti.isFixedSize(expansion));
|
||||
Address addr = IGM.getAddrOfSILGlobalVariable(var, ti, NotForDefinition);
|
||||
return addr.getAddress();
|
||||
} else if (auto *atp = dyn_cast<AddressToPointerInst>(operand)) {
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
// CHECK-DAG: @"$s4test9passArrayyyFTv_r" = {{.*}} constant {{.*}} @"$ss20__StaticArrayStorageCN", {{.*}} @_swiftImmortalRefCount
|
||||
// CHECK-DAG: @"$s4test9passArrayyyFTv0_r" = {{.*}} constant {{.*}} @"$ss20__StaticArrayStorageCN", {{.*}} @_swiftImmortalRefCount
|
||||
// CHECK-DAG: @"$s4test10storeArrayyyFTv_r" = {{.*}} constant {{.*}} @"$ss20__StaticArrayStorageCN", {{.*}} @_swiftImmortalRefCount
|
||||
// CHECK-DAG: @"$s4test3StrV14staticVariable_WZTv_r" = {{.*}} constant {{.*}} @"$ss20__StaticArrayStorageCN", {{.*}} @_swiftImmortalRefCount
|
||||
// CHECK-DAG: @"$s4test3StrV9staticLet_WZTv_r" = {{.*}} constant {{.*}} @"$ss20__StaticArrayStorageCN", {{.*}} @_swiftImmortalRefCount
|
||||
// CHECK-DAG: @"$s4test3StrV9staticVar_WZTv_r" = {{.*}} constant {{.*}} @"$ss20__StaticArrayStorageCN", {{.*}} @_swiftImmortalRefCount
|
||||
// CHECK-DAG: @"$s4test3StrV9staticVarSaySiGvpZ" = global %TSa <{ %Ts12_ArrayBufferV <{ %Ts14_BridgeStorageV <{ ptr @"$s4test3StrV9staticVar_WZTv_r" }> }> }>
|
||||
// CHECK-NOT: swift_initStaticObject
|
||||
|
||||
// UNSUPPORTED: use_os_stdlib
|
||||
@@ -24,7 +26,8 @@
|
||||
|
||||
|
||||
public struct Str {
|
||||
public static let staticVariable = [ 200, 201, 202 ]
|
||||
public static let staticLet = [ 200, 201, 202 ]
|
||||
public static var staticVar = [ 300, 301, 302 ]
|
||||
}
|
||||
|
||||
@inline(never)
|
||||
@@ -64,7 +67,10 @@ public func storeArray() {
|
||||
}
|
||||
|
||||
// CHECK-OUTPUT: [200, 201, 202]
|
||||
print(Str.staticVariable)
|
||||
print(Str.staticLet)
|
||||
|
||||
// CHECK-OUTPUT: [300, 301, 302]
|
||||
print(Str.staticVar)
|
||||
|
||||
// CHECK-OUTPUT-NEXT: 11
|
||||
print(arrayLookup(1))
|
||||
|
||||
Reference in New Issue
Block a user