Skip to content

SIL: introduce the vector_base_addr instruction and use it in InlineArray #81441

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
May 13, 2025
5 changes: 5 additions & 0 deletions SwiftCompilerSources/Sources/AST/Type.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ public struct Type: TypeProperties, CustomStringConvertible, NoReflectionChildre

public var builtinVectorElementType: Type { Type(bridged: bridged.getBuiltinVectorElementType()) }

public var builtinFixedArrayElementType: Type { Type(bridged: bridged.getBuiltinFixedArrayElementType()) }

public func subst(with substitutionMap: SubstitutionMap) -> Type {
return Type(bridged: bridged.subst(substitutionMap.bridged))
}
Expand All @@ -81,6 +83,8 @@ public struct CanonicalType: TypeProperties, CustomStringConvertible, NoReflecti

public var builtinVectorElementType: CanonicalType { rawType.builtinVectorElementType.canonical }

public var builtinFixedArrayElementType: CanonicalType { rawType.builtinFixedArrayElementType.canonical }

public func subst(with substitutionMap: SubstitutionMap) -> CanonicalType {
return rawType.subst(with: substitutionMap).canonical
}
Expand All @@ -106,6 +110,7 @@ extension TypeProperties {

public var isBuiltinFloat: Bool { rawType.bridged.isBuiltinFloat() }
public var isBuiltinVector: Bool { rawType.bridged.isBuiltinVector() }
public var isBuiltinFixedArray: Bool { rawType.bridged.isBuiltinFixedArray() }

public var isClass: Bool {
if let nominal = nominal, nominal is ClassDecl {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ private func lowerInlineArray(array: InlineArray, _ context: FunctionPassContext
///
private func getInlineArrayInfo(of allocStack: AllocStackInst) -> InlineArray? {
var arrayLoad: LoadInst? = nil
var elementStorage: UncheckedAddrCastInst? = nil
var elementStorage: VectorBaseAddrInst? = nil

for use in allocStack.uses {
switch use.instruction {
Expand All @@ -188,11 +188,11 @@ private func getInlineArrayInfo(of allocStack: AllocStackInst) -> InlineArray? {
arrayLoad = load
case is DeallocStackInst:
break
case let addrCastToElement as UncheckedAddrCastInst:
case let baseAddr as VectorBaseAddrInst:
if elementStorage != nil {
return nil
}
elementStorage = addrCastToElement
elementStorage = baseAddr
default:
return nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ swift_compiler_sources(Optimizer
SimplifySwitchEnum.swift
SimplifyTuple.swift
SimplifyTupleExtract.swift
SimplifyUncheckedAddrCast.swift
SimplifyUncheckedEnumData.swift
SimplifyValueToBridgeObject.swift
SimplifyWitnessMethod.swift)
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//===--- SimplifyUncheckedAddrCast.swift ----------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import SIL

extension UncheckedAddrCastInst : OnoneSimplifiable, SILCombineSimplifiable {

func simplify(_ context: SimplifyContext) {
// ```
// %1 = unchecked_addr_cast %0 : $*T to $*T
// ```
// ->
// replace %1 with %0
//
if optimizeSameTypeCast(context) {
return
}

// ```
// %1 = unchecked_addr_cast %0 : $*U to $*V
// %2 = unchecked_addr_cast %1 : $*V to $*T
// ```
// ->
// ```
// %2 = unchecked_addr_cast %0: $*U to $*T
// ```
if optimizeDoubleCast(context) {
return
}

// ```
// %1 = unchecked_addr_cast %0 : $*Builtin.FixedArray<N, Element> to $*Element
// ```
// ->
// ```
// %1 = vector_base_addr %0 : $*Builtin.FixedArray<N, Element>
// ```
_ = optimizeVectorBaseCast(context)
}
}

private extension UncheckedAddrCastInst {
func optimizeSameTypeCast(_ context: SimplifyContext) -> Bool {
if fromAddress.type == type {
self.replace(with: fromAddress, context)
return true
}
return false
}

func optimizeDoubleCast(_ context: SimplifyContext) -> Bool {
if let firstCast = fromAddress as? UncheckedAddrCastInst {
let builder = Builder(before: self, context)
let newCast = builder.createUncheckedAddrCast(from: firstCast.fromAddress, to: type)
self.replace(with: newCast, context)
return true
}
return false
}

func optimizeVectorBaseCast(_ context: SimplifyContext) -> Bool {
if fromAddress.type.isBuiltinFixedArray,
fromAddress.type.builtinFixedArrayElementType(in: parentFunction, maximallyAbstracted: true).addressType == type
{
let builder = Builder(before: self, context)
let vectorBase = builder.createVectorBaseAddr(vector: fromAddress)
self.replace(with: vectorBase, context)
return true
}
return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ private func registerSwiftPasses() {
registerForSILCombine(PointerToAddressInst.self, { run(PointerToAddressInst.self, $0) })
registerForSILCombine(UncheckedEnumDataInst.self, { run(UncheckedEnumDataInst.self, $0) })
registerForSILCombine(WitnessMethodInst.self, { run(WitnessMethodInst.self, $0) })
registerForSILCombine(UncheckedAddrCastInst.self, { run(UncheckedAddrCastInst.self, $0) })
registerForSILCombine(UnconditionalCheckedCastInst.self, { run(UnconditionalCheckedCastInst.self, $0) })
registerForSILCombine(AllocStackInst.self, { run(AllocStackInst.self, $0) })
registerForSILCombine(ApplyInst.self, { run(ApplyInst.self, $0) })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ extension Instruction {
case let bi as BuiltinInst:
switch bi.id {
case .ZeroInitializer:
let type = bi.type.isBuiltinVector ? bi.type.builtinVectorElementType : bi.type
let type = bi.type.isBuiltinVector ? bi.type.builtinVectorElementType(in: parentFunction) : bi.type
return type.isBuiltinInteger || type.isBuiltinFloat
case .PtrToInt:
return bi.operands[0].value is StringLiteralInst
Expand Down
10 changes: 10 additions & 0 deletions SwiftCompilerSources/Sources/Optimizer/Utilities/Verifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,16 @@ extension LoadBorrowInst : VerifiableInstruction {
}
}

extension VectorBaseAddrInst : VerifiableInstruction {
func verify(_ context: FunctionPassContext) {
require(vector.type.isBuiltinFixedArray,
"vector operand of vector_element_addr must be a Builtin.FixedArray")
require(type == vector.type.builtinFixedArrayElementType(in: parentFunction,
maximallyAbstracted: true).addressType,
"result of vector_element_addr has wrong type")
}
}

// Used to check if any instruction is mutating the memory location within the liverange of a `load_borrow`.
// Note that it is not checking if an instruction _may_ mutate the memory, but it's checking if any instruction
// _definitely_ will mutate the memory.
Expand Down
4 changes: 4 additions & 0 deletions SwiftCompilerSources/Sources/SIL/Builder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,10 @@ public struct Builder {
return notifyNew(vectorInst.getAs(VectorInst.self))
}

public func createVectorBaseAddr(vector: Value) -> VectorBaseAddrInst {
return notifyNew(bridged.createVectorBaseAddr(vector.bridged).getAs(VectorBaseAddrInst.self))
}

public func createGlobalAddr(global: GlobalVariable, dependencyToken: Value?) -> GlobalAddrInst {
return notifyNew(bridged.createGlobalAddr(global.bridged, dependencyToken.bridged).getAs(GlobalAddrInst.self))
}
Expand Down
4 changes: 4 additions & 0 deletions SwiftCompilerSources/Sources/SIL/Instruction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1350,6 +1350,10 @@ final public class ObjectInst : SingleValueInstruction {
final public class VectorInst : SingleValueInstruction {
}

final public class VectorBaseAddrInst : SingleValueInstruction, UnaryInstruction {
public var vector: Value { operand.value }
}

final public class DifferentiableFunctionInst: SingleValueInstruction {}

final public class LinearFunctionInst: SingleValueInstruction {}
Expand Down
1 change: 1 addition & 0 deletions SwiftCompilerSources/Sources/SIL/Registration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ public func registerSILClasses() {
register(MoveOnlyWrapperToCopyableAddrInst.self)
register(ObjectInst.self)
register(VectorInst.self)
register(VectorBaseAddrInst.self)
register(TuplePackExtractInst.self)
register(TuplePackElementAddrInst.self)
register(PackElementGetInst.self)
Expand Down
8 changes: 7 additions & 1 deletion SwiftCompilerSources/Sources/SIL/Type.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,13 @@ public struct Type : TypeProperties, CustomStringConvertible, NoReflectionChildr
!isNoEscapeFunction && isEscapable(in: function)
}

public var builtinVectorElementType: Type { canonicalType.builtinVectorElementType.silType! }
public func builtinVectorElementType(in function: Function) -> Type {
canonicalType.builtinVectorElementType.loweredType(in: function)
}

public func builtinFixedArrayElementType(in function: Function, maximallyAbstracted: Bool = false) -> Type {
canonicalType.builtinFixedArrayElementType.loweredType(in: function, maximallyAbstracted: maximallyAbstracted)
}

public var superClassType: Type? { canonicalType.superClassType?.silType }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ public struct AccessPath : CustomStringConvertible, Hashable {

private func canBeOperandOfIndexAddr(_ value: Value) -> Bool {
switch value {
case is IndexAddrInst, is RefTailAddrInst, is PointerToAddressInst:
case is IndexAddrInst, is RefTailAddrInst, is PointerToAddressInst, is VectorBaseAddrInst:
return true
default:
return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,17 @@ public struct SmallProjectionPath : Hashable, CustomStringConvertible, NoReflect
// This and all following kinds (we'll add in the future) cannot have a field index.
case tailElements = 0x07 // (0 << 3) | 0x7 A tail allocated element of a class: syntax `ct`
case existential = 0x0f // (1 << 3) | 0x7 A concrete value projected out of an existential: synatx 'x'
case anyClassField = 0x17 // (2 << 3) | 0x7 Any class field, including tail elements: syntax `c*`
case anyIndexedElement = 0x1f // (3 << 3) | 0x7 An unknown offset into an array of elements.
case vectorBase = 0x17 // (2 << 3) | 0x7 The base element of a vector: synatx 'b'
case anyClassField = 0x1f // (3 << 3) | 0x7 Any class field, including tail elements: syntax `c*`
case anyIndexedElement = 0x27 // (4 << 3) | 0x7 An unknown offset into an array of elements.
// There must not be two successive element indices in the path.
case anyValueFields = 0x27 // (4 << 3) | 0x7 Any number of any value fields (struct, tuple, enum): syntax `v**`
case anything = 0x2f // (5 << 3) | 0x7 Any number of any fields: syntax `**`
case anyValueFields = 0x2f // (5 << 3) | 0x7 Any number of any value fields (struct, tuple, enum): syntax `v**`
case anything = 0x37 // (6 << 3) | 0x7 Any number of any fields: syntax `**`

public var isValueField: Bool {
switch self {
case .anyValueFields, .structField, .tupleField, .enumCase, .indexedElement, .anyIndexedElement, .existential:
case .structField, .tupleField, .enumCase, .indexedElement, .existential, .vectorBase,
.anyValueFields, .anyIndexedElement:
return true
case .root, .anything, .anyClassField, .classField, .tailElements:
return false
Expand All @@ -102,7 +104,8 @@ public struct SmallProjectionPath : Hashable, CustomStringConvertible, NoReflect
switch self {
case .anyClassField, .classField, .tailElements:
return true
case .root, .anything, .anyValueFields, .structField, .tupleField, .enumCase, .indexedElement, .anyIndexedElement, .existential:
case .root, .anything, .anyValueFields, .structField, .tupleField, .enumCase, .indexedElement,
.anyIndexedElement, .existential, .vectorBase:
return false
}
}
Expand Down Expand Up @@ -140,6 +143,7 @@ public struct SmallProjectionPath : Hashable, CustomStringConvertible, NoReflect
case .classField: s = "c\(idx)"
case .tailElements: s = "ct"
case .existential: s = "x"
case .vectorBase: s = "b"
case .indexedElement: s = "i\(idx)"
case .anyIndexedElement: s = "i*"
case .anything: s = "**"
Expand Down Expand Up @@ -398,7 +402,7 @@ public struct SmallProjectionPath : Hashable, CustomStringConvertible, NoReflect
return subPath.matches(pattern: subPattern)
case .anyIndexedElement:
return popIndexedElements().matches(pattern: subPattern)
case .structField, .tupleField, .enumCase, .classField, .tailElements, .indexedElement, .existential:
case .structField, .tupleField, .enumCase, .classField, .tailElements, .indexedElement, .existential, .vectorBase:
let (kind, index, subPath) = pop()
if kind != patternKind || index != patternIdx { return false }
return subPath.matches(pattern: subPattern)
Expand Down Expand Up @@ -478,8 +482,18 @@ public struct SmallProjectionPath : Hashable, CustomStringConvertible, NoReflect
}
if (lhsKind == rhsKind && lhsIdx == rhsIdx) ||
(lhsKind == .anyClassField && rhsKind.isClassField) ||
(lhsKind.isClassField && rhsKind == .anyClassField) {
return pop(numBits: lhsBits).mayOverlap(with: rhs.pop(numBits: rhsBits))
(lhsKind.isClassField && rhsKind == .anyClassField)
{
let poppedPath = pop(numBits: lhsBits)
let rhsPoppedPath = rhs.pop(numBits: rhsBits)
// Check for the case of overlapping the first element of a vector with another element.
// Note that the index of `.indexedElement` cannot be 0.
if (poppedPath.isEmpty && rhsPoppedPath.pop().kind == .indexedElement) ||
(rhsPoppedPath.isEmpty && poppedPath.pop().kind == .indexedElement)
{
return false
}
return poppedPath.mayOverlap(with: rhsPoppedPath)
}
return false
}
Expand All @@ -496,7 +510,7 @@ public struct SmallProjectionPath : Hashable, CustomStringConvertible, NoReflect
switch lhsKind {
case .root:
return rhs
case .classField, .tailElements, .structField, .tupleField, .enumCase, .existential, .indexedElement:
case .classField, .tailElements, .structField, .tupleField, .enumCase, .existential, .indexedElement, .vectorBase:
let (rhsKind, rhsIdx, rhsBits) = rhs.top
if lhsKind == rhsKind && lhsIdx == rhsIdx {
return pop(numBits: lhsBits).subtract(from: rhs.pop(numBits: rhsBits))
Expand Down Expand Up @@ -601,6 +615,8 @@ extension StringParser {
entries.append((.tailElements, 0))
} else if consume("x") {
entries.append((.existential, 0))
} else if consume("b") {
entries.append((.vectorBase, 0))
} else if consume("c") {
guard let idx = consumeInt(withWhiteSpace: false) else {
try throwError("expected class field index")
Expand Down Expand Up @@ -701,7 +717,8 @@ extension SmallProjectionPath {
.push(.enumCase, index: 6)
.push(.anyClassField)
.push(.tupleField, index: 2))
testParse("i3.x.i*", expect: SmallProjectionPath(.anyIndexedElement)
testParse("i3.x.b.i*", expect: SmallProjectionPath(.anyIndexedElement)
.push(.vectorBase)
.push(.existential)
.push(.indexedElement, index: 3))

Expand Down Expand Up @@ -739,6 +756,8 @@ extension SmallProjectionPath {
testMerge("i*", "i2", expect: "i*")
testMerge("s0.i*.e3", "s0.e3", expect: "s0.i*.e3")
testMerge("i*", "v**", expect: "v**")
testMerge("s0.b.i1", "s0.b.i0", expect: "s0.b.i*")
testMerge("s0.b", "s0.1", expect: "s0.v**")

testMerge("ct.s0.e0.v**.c0", "ct.s0.e0.v**.c0", expect: "ct.s0.e0.v**.c0")
testMerge("ct.s0.s0.c0", "ct.s0.e0.s0.c0", expect: "ct.s0.v**.c0")
Expand Down Expand Up @@ -813,6 +832,7 @@ extension SmallProjectionPath {
testMatch("s1.v**", "s0.**", expect: false)
testMatch("s0.**", "s0.v**", expect: false)
testMatch("s0.s1", "s0.i*.s1", expect: true)
testMatch("s0.b.s1", "s0.b.i*.s1", expect: true)
}

func testMatch(_ lhsStr: String, _ rhsStr: String, expect: Bool) {
Expand Down Expand Up @@ -847,6 +867,13 @@ extension SmallProjectionPath {
testOverlap("i1", "i*", expect: true)
testOverlap("i1", "v**", expect: true)
testOverlap("s0.i*.s1", "s0.s1", expect: true)
testOverlap("s0.b.s1", "s0.b.i*.s1", expect: true)
testOverlap("s0.b.i0.s1", "s0.b.i1.s1", expect: false)
testOverlap("s0.b.i2.s1", "s0.b.i1.s1", expect: false)
testOverlap("s0.b.s1", "s0.b.i0.s1", expect: true)
testOverlap("s0.b", "s0.b.i1", expect: false)
testOverlap("s0.b.i1", "s0.b", expect: false)
testOverlap("s0.b.i1", "s0", expect: true)
}

func testOverlap(_ lhsStr: String, _ rhsStr: String, expect: Bool) {
Expand Down Expand Up @@ -889,7 +916,7 @@ extension SmallProjectionPath {
}

func path2path() {
testPath2Path("s0.e2.3.c4.s1", { $0.popAllValueFields() }, expect: "c4.s1")
testPath2Path("s0.b.e2.3.c4.s1", { $0.popAllValueFields() }, expect: "c4.s1")
testPath2Path("v**.c4.s1", { $0.popAllValueFields() }, expect: "c4.s1")
testPath2Path("**", { $0.popAllValueFields() }, expect: "**")

Expand Down
8 changes: 8 additions & 0 deletions SwiftCompilerSources/Sources/SIL/Utilities/WalkUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,12 @@ extension AddressDefUseWalker {
} else {
return unmatchedPath(address: operand, path: path)
}
case let vba as VectorBaseAddrInst:
if let path = path.popIfMatches(.vectorBase, index: 0) {
return walkDownUses(ofAddress: vba, path: path)
} else {
return unmatchedPath(address: operand, path: path)
}
case is InitEnumDataAddrInst, is UncheckedTakeEnumDataAddrInst:
let ei = instruction as! SingleValueInstruction
if let path = path.popIfMatches(.enumCase, index: (instruction as! EnumInstruction).caseIndex) {
Expand Down Expand Up @@ -814,6 +820,8 @@ extension AddressUseDefWalker {
return walkUp(address: sea.struct, path: path.push(.structField, index: sea.fieldIndex))
case let tea as TupleElementAddrInst:
return walkUp(address: tea.tuple, path: path.push(.tupleField, index: tea.fieldIndex))
case let vba as VectorBaseAddrInst:
return walkUp(address: vba.vector, path: path.push(.vectorBase, index: 0))
case let ida as InitEnumDataAddrInst:
return walkUp(address: ida.operand.value, path: path.push(.enumCase, index: ida.caseIndex))
case let uteda as UncheckedTakeEnumDataAddrInst:
Expand Down
Loading