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:
Erik Eckstein
2024-05-14 20:37:18 +02:00
parent 4f73008177
commit af68435d90
8 changed files with 164 additions and 129 deletions

View File

@@ -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 ||

View File

@@ -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
}

View File

@@ -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

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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 {

View File

@@ -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)) {

View File

@@ -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))