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

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