From 293a57ca7b5991eed23cf31627265abaf5d8cc30 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Fri, 18 Aug 2023 18:03:22 -0400 Subject: [PATCH 01/21] Refactor binding introducer to access conversion --- Sources/Core/AccessEffect.swift | 12 ++++++++++++ Sources/IR/Emitter.swift | 12 ++---------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Sources/Core/AccessEffect.swift b/Sources/Core/AccessEffect.swift index e5c3dbbb1..40292b20c 100644 --- a/Sources/Core/AccessEffect.swift +++ b/Sources/Core/AccessEffect.swift @@ -16,6 +16,18 @@ public enum AccessEffect: UInt8, Codable { /// Value may be accessed with any of the other effects, depending on the context. case yielded = 16 + /// Creates an instance denoting the access granted to a binding introduced with `i`. + public init(_ i: BindingPattern.Introducer) { + switch i { + case .let: + self = .let + case .inout: + self = .inout + case .sinklet, .var: + self = .sink + } + } + } extension AccessEffect: Comparable { diff --git a/Sources/IR/Emitter.swift b/Sources/IR/Emitter.swift index 4f1109fcc..2e3dd9432 100644 --- a/Sources/IR/Emitter.swift +++ b/Sources/IR/Emitter.swift @@ -546,18 +546,10 @@ struct Emitter { /// /// - Requires: `d` is a local `let` or `inout` binding. private mutating func lower(projectedLocalBinding d: BindingDecl.ID) { + let access = AccessEffect(program[d].pattern.introducer.value) + precondition(access == .let || access == .inout) precondition(program.isLocal(d)) - let access: AccessEffect - switch program[d].pattern.introducer.value { - case .let: - access = .let - case .inout: - access = .inout - default: - preconditionFailure() - } - // Borrowed binding requires an initializer. guard let initializer = ast[d].initializer else { report(.error(binding: access, requiresInitializerAt: program[d].pattern.introducer.site)) From dae04ef2f68d0ccb2061ab1f5ec36c32a1f48ad6 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Fri, 18 Aug 2023 18:23:58 -0400 Subject: [PATCH 02/21] Move missing initializer checks from IR lowering to type checking --- .../TypeChecking/TypeChecker+Diagnostics.swift | 6 ++++++ Sources/FrontEnd/TypeChecking/TypeChecker.swift | 9 ++++++++- Sources/IR/Emitter.swift | 17 +---------------- .../TestCases/TypeChecking/Import.hylo | 3 ++- .../LocalBindings.hylo | 2 +- .../TestCases/TypeChecking/UnionType.hylo | 12 ++++++------ 6 files changed, 24 insertions(+), 25 deletions(-) rename Tests/HyloTests/TestCases/{Lowering => TypeChecking}/LocalBindings.hylo (84%) diff --git a/Sources/FrontEnd/TypeChecking/TypeChecker+Diagnostics.swift b/Sources/FrontEnd/TypeChecking/TypeChecker+Diagnostics.swift index 3ffbdaa57..f0225dbbf 100644 --- a/Sources/FrontEnd/TypeChecking/TypeChecker+Diagnostics.swift +++ b/Sources/FrontEnd/TypeChecking/TypeChecker+Diagnostics.swift @@ -6,6 +6,12 @@ extension Diagnostic { .error("ambiguous disjunction", at: site) } + static func error( + binding a: BindingPattern.Introducer, requiresInitializerAt site: SourceRange + ) -> Diagnostic { + .error("declaration of \(a) binding requires an initializer", at: site) + } + static func error(circularRefinementAt site: SourceRange) -> Diagnostic { .error("circular trait refinement", at: site) } diff --git a/Sources/FrontEnd/TypeChecking/TypeChecker.swift b/Sources/FrontEnd/TypeChecking/TypeChecker.swift index 3e1fd05b2..55e80a09b 100644 --- a/Sources/FrontEnd/TypeChecking/TypeChecker.swift +++ b/Sources/FrontEnd/TypeChecking/TypeChecker.swift @@ -3432,7 +3432,14 @@ struct TypeChecker { guard let i = program[d].initializer else { assert(program[d].pattern.annotation != nil, "expected type annotation") - return pattern + + let a = program[d].pattern.introducer.value + if ((a == .let) || (a == .inout)) && program.isLocal(d) { + report(.error(binding: a, requiresInitializerAt: program[d].pattern.introducer.site)) + return .error + } else { + return pattern + } } // Note: `i` should not have been assigned a type if before `d` is checked. diff --git a/Sources/IR/Emitter.swift b/Sources/IR/Emitter.swift index 2e3dd9432..8f4c0ab02 100644 --- a/Sources/IR/Emitter.swift +++ b/Sources/IR/Emitter.swift @@ -550,16 +550,7 @@ struct Emitter { precondition(access == .let || access == .inout) precondition(program.isLocal(d)) - // Borrowed binding requires an initializer. - guard let initializer = ast[d].initializer else { - report(.error(binding: access, requiresInitializerAt: program[d].pattern.introducer.site)) - for (_, name) in ast.names(in: program[d].pattern.subpattern) { - let t = canonical(program[program[name].decl].type) - frames[ast[name].decl] = .constant(Poison(type: .address(t))) - } - return - } - + let initializer = ast[d].initializer! let source = emitLValue(initializer) let isSink = module.isSink(source) @@ -2595,12 +2586,6 @@ extension Diagnostic { .error("left-hand side of assignment must be marked for mutation", at: site) } - fileprivate static func error( - binding a: AccessEffect, requiresInitializerAt site: SourceRange - ) -> Diagnostic { - .error("declaration of \(a) binding requires an initializer", at: site) - } - fileprivate static func error(cannotCaptureAccessAt site: SourceRange) -> Diagnostic { .error("cannot capture access", at: site) } diff --git a/Tests/HyloTests/TestCases/TypeChecking/Import.hylo b/Tests/HyloTests/TestCases/TypeChecking/Import.hylo index 91c807689..c65ee39a0 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/Import.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/Import.hylo @@ -6,7 +6,8 @@ import NotFound //! diagnostic no such module 'NotFound' fun check(_ x: T) {} +let x: Hylo.Int + public fun main() { - let x: Hylo.Int check(x) } diff --git a/Tests/HyloTests/TestCases/Lowering/LocalBindings.hylo b/Tests/HyloTests/TestCases/TypeChecking/LocalBindings.hylo similarity index 84% rename from Tests/HyloTests/TestCases/Lowering/LocalBindings.hylo rename to Tests/HyloTests/TestCases/TypeChecking/LocalBindings.hylo index 93d9bdc68..bf6fd099d 100644 --- a/Tests/HyloTests/TestCases/Lowering/LocalBindings.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/LocalBindings.hylo @@ -1,4 +1,4 @@ -//- lowerToFinishedIR expecting: failure +//- typeCheck expecting: failure public fun main() { sink let x: Int diff --git a/Tests/HyloTests/TestCases/TypeChecking/UnionType.hylo b/Tests/HyloTests/TestCases/TypeChecking/UnionType.hylo index feca12c6a..b0ad25a71 100644 --- a/Tests/HyloTests/TestCases/TypeChecking/UnionType.hylo +++ b/Tests/HyloTests/TestCases/TypeChecking/UnionType.hylo @@ -2,14 +2,14 @@ public fun main() { // Standard cases. - let _ : Union - let _ : Union + var _ : Union + var _ : Union // Special cases. - let _ : Union //! diagnostic empty union type is better expressed as 'Never' - let _ : Union //! diagnostic union types should contain at least 2 elements + var _ : Union //! diagnostic empty union type is better expressed as 'Never' + var _ : Union //! diagnostic union types should contain at least 2 elements // Initialization with subtype. - let _ : Union = true - let _ : Union = 0 as Int + var _ : Union = true + var _ : Union = 0 as Int } From 342a3365b2ad84fa92911b03c04affea2c5cbc06 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Fri, 18 Aug 2023 18:34:56 -0400 Subject: [PATCH 03/21] Removed dead code --- Sources/CodeGen/LLVM/Transpilation.swift | 4 ---- Sources/IR/Operands/Constant/Poison.swift | 13 ------------- 2 files changed, 17 deletions(-) delete mode 100644 Sources/IR/Operands/Constant/Poison.swift diff --git a/Sources/CodeGen/LLVM/Transpilation.swift b/Sources/CodeGen/LLVM/Transpilation.swift index 0e6b02fd2..92569a879 100644 --- a/Sources/CodeGen/LLVM/Transpilation.swift +++ b/Sources/CodeGen/LLVM/Transpilation.swift @@ -192,10 +192,6 @@ extension LLVM.Module { case let v as TraitType: return transpiledTrait(v, usedIn: m, from: ir) - case is IR.Poison: - let t = ir.llvm(c.type.ast, in: &self) - return LLVM.Poison(of: t) - case is IR.VoidConstant: return LLVM.StructConstant(aggregating: [], in: &self) diff --git a/Sources/IR/Operands/Constant/Poison.swift b/Sources/IR/Operands/Constant/Poison.swift deleted file mode 100644 index 92682be9a..000000000 --- a/Sources/IR/Operands/Constant/Poison.swift +++ /dev/null @@ -1,13 +0,0 @@ -/// A poison value in Hylo IR. -public struct Poison: Constant, Hashable { - - /// The type of the poison. - public let type: IR.`Type` - -} - -extension Poison: CustomStringConvertible { - - public var description: String { "poison" } - -} From 81c7462c5d14c6dda246c3480967edb17c4a6901 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Sat, 19 Aug 2023 00:17:49 -0400 Subject: [PATCH 04/21] Refactor 'Emitter.emitCoerce(_:to:at:)' --- Sources/Core/Types/RemoteType.swift | 5 +++ Sources/IR/Emitter.swift | 49 ++++++++++++++++++----------- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/Sources/Core/Types/RemoteType.swift b/Sources/Core/Types/RemoteType.swift index bbf22a7cb..e33e14f4b 100644 --- a/Sources/Core/Types/RemoteType.swift +++ b/Sources/Core/Types/RemoteType.swift @@ -16,6 +16,11 @@ public struct RemoteType: TypeProtocol { self.flags = bareType.flags.inserting(.hasRemoteType) } + /// Creates an instance converting `t`. + public init(_ t: ParameterType) { + self.init(t.access, t.bareType) + } + public func transformParts( mutating m: inout M, _ transformer: (inout M, AnyType) -> TypeTransformAction ) -> Self { diff --git a/Sources/IR/Emitter.swift b/Sources/IR/Emitter.swift index 8f4c0ab02..f9f5bf050 100644 --- a/Sources/IR/Emitter.swift +++ b/Sources/IR/Emitter.swift @@ -1857,52 +1857,65 @@ struct Emitter { /// Inserts the IR for coercing `source` to an address of type `target`. /// - /// `source` is returned unchanged if it stores an instance of `target`. Otherwise, the IR for + /// `source` is returned unchanged if it stores an instance of `target`. Otherwise, the IR /// producing an address of type `target` is inserted, consuming `source` if necessary. private mutating func emitCoerce( _ source: Operand, to target: AnyType, at site: SourceRange ) -> Operand { - let target = program.canonical(target, in: insertionScope!) + let lhs = module.type(of: source).ast + let rhs = program.canonical(target, in: insertionScope!) - let sourceType = module.type(of: source).ast - if program.areEquivalent(sourceType, target, in: insertionScope!) { + if program.areEquivalent(lhs, rhs, in: insertionScope!) { return source } - if sourceType.base is RemoteType { - let v = insert(module.makeOpenCapture(source, at: site))! - return emitCoerce(v, to: target, at: site) + if lhs.base is RemoteType { + let s = insert(module.makeOpenCapture(source, at: site))! + return emitCoerce(s, to: rhs, at: site) } - if let t = LambdaType(target), let o = _emitCoerce(source, to: t, at: site) { - return o + switch rhs.base { + case let t as LambdaType: + return _emitCoerce(source, to: t, at: site) + default: + unexpectedCoercion(from: lhs, to: rhs) } - - unreachable("unexpected coercion from '\(sourceType)' to \(target)") } - /// Inserts the IR for coercing `source` to an address of type `target`, returning `nil` if such - /// a coercion is not possible. + /// Inserts the IR for coercing `source` to an address of type `target`. /// /// - Requires: `target` is canonical. private mutating func _emitCoerce( _ source: Operand, to target: LambdaType, at site: SourceRange - ) -> Operand? { + ) -> Operand { precondition(target[.isCanonical]) - guard let s = LambdaType(module.type(of: source).ast) else { return nil } + let t = module.type(of: source).ast + guard let lhs = LambdaType(t) else { + unexpectedCoercion(from: t, to: ^target) + } // TODO: Handle variance - let t = LambdaType( - receiverEffect: s.receiverEffect, + let rhs = LambdaType( + receiverEffect: lhs.receiverEffect, environment: target.environment, inputs: target.inputs, output: target.output) - if !program.areEquivalent(^s, ^t, in: insertionScope!) { return nil } + + if !program.areEquivalent(^lhs, ^rhs, in: insertionScope!) { + unexpectedCoercion(from: t, to: ^target) + } // If we're here, then `t` and `u` only differ on their effects. return source } + /// Traps on this execution path becauses of un unexpected coercion from `lhs` to `rhs`. + private func unexpectedCoercion( + from lhs: AnyType, to rhs: AnyType, file: StaticString = #file, line: UInt = #line + ) -> Never { + fatalError("unexpected coercion from '\(lhs)' to \(rhs)", file: file, line: line) + } + /// Inserts the IR for converting `foreign` to a value of type `ir`. private mutating func emitConvert( foreign: Operand, to ir: AnyType, at site: SourceRange From 6a6706684a331110f77fa9567d87335b3183f99c Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Sat, 19 Aug 2023 01:52:28 -0400 Subject: [PATCH 05/21] Create abstractions to represent IR region delimiters --- Sources/IR/Analysis/Module+CloseBorrows.swift | 6 +- Sources/IR/Operands/Instruction/Access.swift | 4 +- .../Operands/Instruction/CloseCapture.swift | 30 +--------- .../IR/Operands/Instruction/CloseUnion.swift | 30 +--------- .../IR/Operands/Instruction/EndAccess.swift | 35 ++---------- .../IR/Operands/Instruction/EndProject.swift | 30 +--------- .../IR/Operands/Instruction/OpenCapture.swift | 4 +- .../IR/Operands/Instruction/OpenUnion.swift | 4 +- Sources/IR/Operands/Instruction/Project.swift | 4 +- .../IR/Operands/Instruction/RegionEntry.swift | 7 +++ .../IR/Operands/Instruction/RegionExit.swift | 56 +++++++++++++++++++ 11 files changed, 89 insertions(+), 121 deletions(-) create mode 100644 Sources/IR/Operands/Instruction/RegionEntry.swift create mode 100644 Sources/IR/Operands/Instruction/RegionExit.swift diff --git a/Sources/IR/Analysis/Module+CloseBorrows.swift b/Sources/IR/Analysis/Module+CloseBorrows.swift index e6b18b119..24a86e857 100644 --- a/Sources/IR/Analysis/Module+CloseBorrows.swift +++ b/Sources/IR/Analysis/Module+CloseBorrows.swift @@ -157,8 +157,4 @@ private protocol LifetimeCloser: Instruction { } -extension EndAccess: LifetimeCloser {} - -extension EndProject: LifetimeCloser {} - -extension CloseCapture: LifetimeCloser {} +extension RegionExit: LifetimeCloser {} diff --git a/Sources/IR/Operands/Instruction/Access.swift b/Sources/IR/Operands/Instruction/Access.swift index 7c4ed1bae..a40d4efb6 100644 --- a/Sources/IR/Operands/Instruction/Access.swift +++ b/Sources/IR/Operands/Instruction/Access.swift @@ -8,7 +8,9 @@ import Core /// cases, IR generation will emit `access` instructions with the set capabilities that may be /// inferred from the syntax. These instructions are expected to be "reifed" during IR analysis /// so that only a single capability is requested. -public struct Access: Instruction { +public struct Access: RegionEntry { + + public typealias Exit = EndAccess /// The capabilities of the access. /// diff --git a/Sources/IR/Operands/Instruction/CloseCapture.swift b/Sources/IR/Operands/Instruction/CloseCapture.swift index b4baf7d98..b2fde8647 100644 --- a/Sources/IR/Operands/Instruction/CloseCapture.swift +++ b/Sources/IR/Operands/Instruction/CloseCapture.swift @@ -1,37 +1,13 @@ import Core -/// Ends the exposition of a captured access. -public struct CloseCapture: Instruction { - - /// The operands of the instruction. - public private(set) var operands: [Operand] - - /// The site of the code corresponding to that instruction. - public let site: SourceRange - - /// Creates an instance with the given properties. - fileprivate init(start: Operand, site: SourceRange) { - self.operands = [start] - self.site = site - } - - /// The access being closed; must be the result of an `open_capture` instruction. - public var start: Operand { - operands[0] - } - - public mutating func replaceOperand(at i: Int, with new: Operand) { - operands[0] = new - } - -} +/// Ends the lifetime of a projection. +public typealias CloseCapture = RegionExit extension Module { /// Creates a `close_capture` anchored at `site` that ends the exposition of a captured access. func makeCloseCapture(_ start: Operand, at site: SourceRange) -> CloseCapture { - precondition(start.instruction.map({ self[$0] is OpenCapture }) ?? false) - return .init(start: start, site: site) + makeRegionExit(start, at: site) } } diff --git a/Sources/IR/Operands/Instruction/CloseUnion.swift b/Sources/IR/Operands/Instruction/CloseUnion.swift index a1aa865b4..3de540371 100644 --- a/Sources/IR/Operands/Instruction/CloseUnion.swift +++ b/Sources/IR/Operands/Instruction/CloseUnion.swift @@ -1,38 +1,14 @@ import Core -/// Close a previously created access to the payload of a union. -public struct CloseUnion: Instruction { - - /// The access being closed; must be the result of an `open_union` instruction. - public private(set) var start: Operand - - /// The site of the code corresponding to that instruction. - public let site: SourceRange - - /// Creates an instance with the given properties. - fileprivate init(start: Operand, site: SourceRange) { - self.start = start - self.site = site - } - - public var operands: [Operand] { - [start] - } - - public mutating func replaceOperand(at i: Int, with new: Operand) { - precondition(i == 0) - start = new - } - -} +/// Ends the lifetime of a projection. +public typealias CloseUnion = RegionExit extension Module { /// Creates an `close_union` anchored at `site` that ends an access to the payload of a union /// opened previously by `start`. func makeCloseUnion(_ start: Operand, at site: SourceRange) -> CloseUnion { - precondition(start.instruction.map({ self[$0] is OpenUnion }) ?? false) - return .init(start: start, site: site) + makeRegionExit(start, at: site) } } diff --git a/Sources/IR/Operands/Instruction/EndAccess.swift b/Sources/IR/Operands/Instruction/EndAccess.swift index b6f3316d5..cecb5cc71 100644 --- a/Sources/IR/Operands/Instruction/EndAccess.swift +++ b/Sources/IR/Operands/Instruction/EndAccess.swift @@ -1,40 +1,13 @@ import Core -/// Ends the lifetime of an access. -public struct EndAccess: Instruction { - - /// The access whose lifetime is ended. - public private(set) var start: Operand - - /// The site of the code corresponding to that instruction. - public let site: SourceRange - - /// Creates an instance with the given properties. - fileprivate init(start: Operand, site: SourceRange) { - self.start = start - self.site = site - } - - public var operands: [Operand] { - [start] - } - - public mutating func replaceOperand(at i: Int, with new: Operand) { - precondition(i == 0) - start = new - } - -} +/// Ends the lifetime of a projection. +public typealias EndAccess = RegionExit extension Module { - /// Creates an `end_access` anchored at `site` that ends an access previously created by `start`. - /// - /// - Parameters: - /// - access: The borrow to end. Must be the result of `borrow`. + /// Creates an `end_access` anchored at `site` that ends the projection created by `start`. func makeEndAccess(_ start: Operand, at site: SourceRange) -> EndAccess { - precondition(start.instruction.map({ self[$0] is Access }) ?? false) - return .init(start: start, site: site) + makeRegionExit(start, at: site) } } diff --git a/Sources/IR/Operands/Instruction/EndProject.swift b/Sources/IR/Operands/Instruction/EndProject.swift index 5dd4a23c8..9ce213598 100644 --- a/Sources/IR/Operands/Instruction/EndProject.swift +++ b/Sources/IR/Operands/Instruction/EndProject.swift @@ -1,37 +1,13 @@ import Core /// Ends the lifetime of a projection. -public struct EndProject: Instruction { - - /// The projection whose lifetime is ended. - public private(set) var start: Operand - - /// The site of the code corresponding to that instruction. - public let site: SourceRange - - /// Creates an instance with the given properties. - fileprivate init(start: Operand, site: SourceRange) { - self.start = start - self.site = site - } - - public var operands: [Operand] { - [start] - } - - public mutating func replaceOperand(at i: Int, with new: Operand) { - precondition(i == 0) - start = new - } - -} +public typealias EndProject = RegionExit extension Module { /// Creates an `end_project` anchored at `site` that ends the projection created by `start`. - func makeEndProject(_ start: Operand, at anchor: SourceRange) -> EndProject { - precondition(start.instruction.map({ self[$0] is Project }) ?? false) - return .init(start: start, site: anchor) + func makeEndProject(_ start: Operand, at site: SourceRange) -> EndProject { + makeRegionExit(start, at: site) } } diff --git a/Sources/IR/Operands/Instruction/OpenCapture.swift b/Sources/IR/Operands/Instruction/OpenCapture.swift index 9769514b0..46acfa850 100644 --- a/Sources/IR/Operands/Instruction/OpenCapture.swift +++ b/Sources/IR/Operands/Instruction/OpenCapture.swift @@ -2,7 +2,9 @@ import Core import Utils /// Exposes a captured access. -public struct OpenCapture: Instruction { +public struct OpenCapture: RegionEntry { + + public typealias Exit = CloseCapture /// The type of the address being loaded. public let result: IR.`Type`? diff --git a/Sources/IR/Operands/Instruction/OpenUnion.swift b/Sources/IR/Operands/Instruction/OpenUnion.swift index 6dc5a44ec..a40704637 100644 --- a/Sources/IR/Operands/Instruction/OpenUnion.swift +++ b/Sources/IR/Operands/Instruction/OpenUnion.swift @@ -1,7 +1,9 @@ import Core /// Projects the address of a union payload, viewed as an instance of a given type. -public struct OpenUnion: Instruction { +public struct OpenUnion: RegionEntry { + + public typealias Exit = CloseUnion /// The union whose payload should be projected. /// diff --git a/Sources/IR/Operands/Instruction/Project.swift b/Sources/IR/Operands/Instruction/Project.swift index d10630339..61510d52e 100644 --- a/Sources/IR/Operands/Instruction/Project.swift +++ b/Sources/IR/Operands/Instruction/Project.swift @@ -1,7 +1,9 @@ import Core /// Projects a value. -public struct Project: Instruction { +public struct Project: RegionEntry { + + public typealias Exit = EndProject /// The type of the projected value. public let projection: RemoteType diff --git a/Sources/IR/Operands/Instruction/RegionEntry.swift b/Sources/IR/Operands/Instruction/RegionEntry.swift new file mode 100644 index 000000000..8eab3ca9d --- /dev/null +++ b/Sources/IR/Operands/Instruction/RegionEntry.swift @@ -0,0 +1,7 @@ +/// An instruction marking the entry into a region within an IR function. +public protocol RegionEntry: Instruction { + + /// The type of the instruction marking exits of this region. + associatedtype Exit: Instruction + +} diff --git a/Sources/IR/Operands/Instruction/RegionExit.swift b/Sources/IR/Operands/Instruction/RegionExit.swift new file mode 100644 index 000000000..3f47856fc --- /dev/null +++ b/Sources/IR/Operands/Instruction/RegionExit.swift @@ -0,0 +1,56 @@ +import Core + +/// An instruction marking an exit from a region. +public struct RegionExit { + + /// The operands of the instruction. + public private(set) var operands: [Operand] + + /// The site of the code corresponding to that instruction. + public let site: SourceRange + + /// Creates an instance with the given properties. + fileprivate init(start: Operand, site: SourceRange) { + self.operands = [start] + self.site = site + } + + /// The exited region. + public var start: Operand { operands[0] } + + public mutating func replaceOperand(at i: Int, with new: Operand) { + operands[i] = new + } + +} + +extension RegionExit: CustomStringConvertible { + + public var description: String { + switch Entry.self { + case let s where s == Access.self: + return "end_access \(start)" + case let s where s == OpenCapture.self: + return "close_capture \(start)" + case let s where s == OpenUnion.self: + return "close_union \(start)" + case let s where s == Project.self: + return "end_project \(start)" + default: + return "end_region \(start)" + } + } + +} + +extension Module { + + /// Creates a region exit anchored at `site` marking an exit of the regions started by `start`. + func makeRegionExit( + _ start: Operand, at anchor: SourceRange + ) -> RegionExit { + precondition(start.instruction.map({ self[$0] is Entry }) ?? false) + return .init(start: start, site: anchor) + } + +} From 3b91b3eaa072855149571ba7e4b8a1833145caf7 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Sat, 19 Aug 2023 02:02:11 -0400 Subject: [PATCH 06/21] Add instructions to project values in existential containers --- .../Instruction/EndProjectWitness.swift | 13 +++++ .../Operands/Instruction/ProjectWitness.swift | 55 +++++++++++++++++++ .../IR/Operands/Instruction/RegionExit.swift | 2 + 3 files changed, 70 insertions(+) create mode 100644 Sources/IR/Operands/Instruction/EndProjectWitness.swift create mode 100644 Sources/IR/Operands/Instruction/ProjectWitness.swift diff --git a/Sources/IR/Operands/Instruction/EndProjectWitness.swift b/Sources/IR/Operands/Instruction/EndProjectWitness.swift new file mode 100644 index 000000000..6cbfb6f0a --- /dev/null +++ b/Sources/IR/Operands/Instruction/EndProjectWitness.swift @@ -0,0 +1,13 @@ +import Core + +/// Ends the lifetime of a projection. +public typealias EndProjectWitness = RegionExit + +extension Module { + + /// Creates an `end_project` anchored at `site` that ends the projection created by `start`. + func makeEndProjectWitness(_ start: Operand, at site: SourceRange) -> EndProjectWitness { + makeRegionExit(start, at: site) + } + +} diff --git a/Sources/IR/Operands/Instruction/ProjectWitness.swift b/Sources/IR/Operands/Instruction/ProjectWitness.swift new file mode 100644 index 000000000..6d835767e --- /dev/null +++ b/Sources/IR/Operands/Instruction/ProjectWitness.swift @@ -0,0 +1,55 @@ +import Core + +/// Projects a the witness of an existential container. +public struct ProjectWitness: RegionEntry { + + public typealias Exit = EndProjectWitness + + /// The type of the projected value. + public let projection: RemoteType + + /// The operands of the instruction. + public private(set) var operands: [Operand] + + /// The site of the code corresponding to that instruction. + public let site: SourceRange + + /// Creates an instance with the given properties. + fileprivate init(projection: RemoteType, container: Operand, site: SourceRange) { + self.projection = projection + self.operands = [container] + self.site = site + } + + /// The container whose witness is projected. + public var container: Operand { operands[0] } + + /// The types of the instruction's results. + public var result: IR.`Type`? { .address(projection.bareType) } + + public mutating func replaceOperand(at i: Int, with new: Operand) { + operands[i] = new + } + +} + +extension ProjectWitness: CustomStringConvertible { + + public var description: String { + "project_witness [\(projection.access)] \(container)" + } + +} + +extension Module { + + /// Creates a `project_witness` anchored at `site` that projects the witness of `container`, + /// which is an existential container, as an address of type `projection`. + func makeProjectWitness( + of container: Operand, as projection: RemoteType, at site: SourceRange + ) -> ProjectWitness { + precondition(projection[.isCanonical]) + return .init(projection: projection, container: container, site: site) + } + +} diff --git a/Sources/IR/Operands/Instruction/RegionExit.swift b/Sources/IR/Operands/Instruction/RegionExit.swift index 3f47856fc..3da46bf44 100644 --- a/Sources/IR/Operands/Instruction/RegionExit.swift +++ b/Sources/IR/Operands/Instruction/RegionExit.swift @@ -36,6 +36,8 @@ extension RegionExit: CustomStringConvertible { return "close_union \(start)" case let s where s == Project.self: return "end_project \(start)" + case let s where s == ProjectWitness.self: + return "end_project_witness \(start)" default: return "end_region \(start)" } From 4960e5c302f1ecbd9302d3a0373925de02eb9fd9 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Sat, 19 Aug 2023 09:42:14 -0400 Subject: [PATCH 07/21] Remove code duplication --- .../Module+NormalizeObjectStates.swift | 69 +++++++++++++++---- 1 file changed, 56 insertions(+), 13 deletions(-) diff --git a/Sources/IR/Analysis/Module+NormalizeObjectStates.swift b/Sources/IR/Analysis/Module+NormalizeObjectStates.swift index cfcc42006..60a098915 100644 --- a/Sources/IR/Analysis/Module+NormalizeObjectStates.swift +++ b/Sources/IR/Analysis/Module+NormalizeObjectStates.swift @@ -389,27 +389,18 @@ extension Module { func interpret(pointerToAddress i: InstructionID, in context: inout Context) -> PC? { let s = self[i] as! PointerToAddress consume(s.source, with: i, at: s.site, in: &context) - - let l = AbstractLocation.root(.register(i)) - context.memory[l] = .init( - layout: AbstractTypeLayout(of: s.target.bareType, definedIn: program), - value: .full(s.target.access == .set ? .uninitialized : .initialized)) - context.locals[.register(i)] = .locations([l]) + initializeRegister(createdBy: i, projecting: s.target, in: &context) return successor(of: i) } /// Interprets `i` in `context`, reporting violations into `diagnostics`. func interpret(project i: InstructionID, in context: inout Context) -> PC? { + let s = self[i] as! Project + // TODO: Process arguments - let s = self[i] as! Project - let l = AbstractLocation.root(.register(i)) - let t = s.projection + initializeRegister(createdBy: i, projecting: s.projection, in: &context) - context.memory[l] = .init( - layout: AbstractTypeLayout(of: t.bareType, definedIn: program), - value: .full(t.access == .set ? .uninitialized : .initialized)) - context.locals[.register(i)] = .locations([l]) return successor(of: i) } @@ -547,6 +538,58 @@ extension Module { .illegalParameterEscape(consumedBy: o.value.consumers, in: self, at: site)) } } + + /// Checks that the state of the projection of `sources` in the region defined at `start` and + /// exited with `exit` is consistent with `access`, updating `context` accordingly. + /// + /// `start` is the definition of projection (e.g., the result of `project`) dominating `i`, + /// which is a corresponding exit. `context.locals[start]` is the unique address of the object + /// `o` being projected in that region. `sources` are the addresses of the objects notionally + /// containing `o`. + /// + /// If `access` is `.let`, `.inout`, or `.set`, `o` must be fully initialized. If `access` is + /// `sink`, `o` must be fully deinitialized. implicit deinitialization is inserted to maintain + /// the latter requirement. + func finalize( + region start: Operand, projecting access: AccessEffect, from sources: Set, + exitedWith exit: InstructionID, + in context: inout Context + ) { + // Skip the instruction if an error occured upstream. + guard let v = context.locals[start] else { + assert(diagnostics.containsError) + return + } + + let l = v.unwrapLocations()!.uniqueElement! + let projection = context.withObject(at: l, { $0 }) + + switch access { + case .let, .inout: + for c in projection.value.consumers { + diagnostics.insert(.error(cannotConsume: access, at: self[c].site)) + } + + case .set: + for c in projection.value.consumers { + diagnostics.insert(.error(cannotConsume: access, at: self[c].site)) + } + for s in sources { + context.withObject(at: s, { $0.value = .full(.initialized) }) + } + + case .sink: + insertDeinit( + start, at: projection.value.initializedSubfields, anchoredTo: self[exit].site, + before: exit, reportingDiagnosticsTo: &diagnostics) + context.withObject(at: l, { $0.value = .full(.uninitialized) }) + + case .yielded: + unreachable() + } + + context.memory[l] = nil + } } /// Returns the initial context in which `f` should be interpreted. From 95505010cfdad48241a345343a59b23e89a9aee4 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Sat, 19 Aug 2023 09:43:23 -0400 Subject: [PATCH 08/21] Handle 'project_witness' during escape analysis --- .../Module+NormalizeObjectStates.swift | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/Sources/IR/Analysis/Module+NormalizeObjectStates.swift b/Sources/IR/Analysis/Module+NormalizeObjectStates.swift index 60a098915..b520e54f6 100644 --- a/Sources/IR/Analysis/Module+NormalizeObjectStates.swift +++ b/Sources/IR/Analysis/Module+NormalizeObjectStates.swift @@ -50,6 +50,8 @@ extension Module { pc = successor(of: user) case is EndProject: pc = interpret(endProject: user, in: &context) + case is EndProjectWitness: + pc = interpret(endProjectWitness: user, in: &context) case is GlobalAddr: pc = interpret(globalAddr: user, in: &context) case is LLVMInstruction: @@ -68,6 +70,8 @@ extension Module { pc = interpret(pointerToAddress: user, in: &context) case is Project: pc = interpret(project: user, in: &context) + case is ProjectWitness: + pc = interpret(projectWitness: user, in: &context) case is ReleaseCaptures: pc = successor(of: user) case is Return: @@ -285,27 +289,24 @@ extension Module { /// Interprets `i` in `context`, reporting violations into `diagnostics`. func interpret(endProject i: InstructionID, in context: inout Context) -> PC? { let s = self[i] as! EndProject - let l = context.locals[s.start]!.unwrapLocations()!.uniqueElement! - let projection = context.withObject(at: l, { $0 }) - - let source = self[s.start.instruction!] as! Project - - switch source.projection.access { - case .let, .inout, .set: - for c in projection.value.consumers { - diagnostics.insert(.error(cannotConsume: source.projection.access, at: self[c].site)) - } + let r = self[s.start.instruction!] as! Project - case .sink: - insertDeinit( - s.start, at: projection.value.initializedSubfields, anchoredTo: s.site, before: i, - reportingDiagnosticsTo: &diagnostics) - context.withObject(at: l, { $0.value = .full(.uninitialized) }) - - case .yielded: - unreachable() - } + // TODO: Process projection arguments + finalize( + region: s.start, projecting: r.projection.access, from: [], + exitedWith: i, in: &context) + return successor(of: i) + } + /// Interprets `i` in `context`, reporting violations into `diagnostics`. + func interpret(endProjectWitness i: InstructionID, in context: inout Context) -> PC? { + let s = self[i] as! EndProjectWitness + let r = self[s.start.instruction!] as! ProjectWitness + + let sources = context.locals[r.container]!.unwrapLocations()! + finalize( + region: s.start, projecting: r.projection.access, from: sources, + exitedWith: i, in: &context) return successor(of: i) } @@ -400,7 +401,13 @@ extension Module { // TODO: Process arguments initializeRegister(createdBy: i, projecting: s.projection, in: &context) + return successor(of: i) + } + /// Interprets `i` in `context`, reporting violations into `diagnostics`. + func interpret(projectWitness i: InstructionID, in context: inout Context) -> PC? { + let s = self[i] as! ProjectWitness + initializeRegister(createdBy: i, projecting: s.projection, in: &context) return successor(of: i) } From 63bcef36bc137450383099ac3f6a63821e279649 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Sat, 19 Aug 2023 09:43:54 -0400 Subject: [PATCH 09/21] Handle 'project_witness' during ownership analysis --- Sources/IR/Analysis/Module+Ownership.swift | 56 +++++++++++++++++----- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/Sources/IR/Analysis/Module+Ownership.swift b/Sources/IR/Analysis/Module+Ownership.swift index e235b9942..62147adb7 100644 --- a/Sources/IR/Analysis/Module+Ownership.swift +++ b/Sources/IR/Analysis/Module+Ownership.swift @@ -31,6 +31,8 @@ extension Module { interpret(endBorrow: user, in: &context) case is EndProject: interpret(endProject: user, in: &context) + case is EndProject: + interpret(endProjectWitness: user, in: &context) case is GlobalAddr: interpret(globalAddr: user, in: &context) case is OpenCapture: @@ -41,6 +43,8 @@ extension Module { interpret(pointerToAddress: user, in: &context) case is Project: interpret(project: user, in: &context) + case is ProjectWitness: + interpret(projectWitness: user, in: &context) case is SubfieldView: interpret(subfieldView: user, in: &context) case is WrapExistentialAddr: @@ -182,12 +186,15 @@ extension Module { /// Interprets `i` in `context`, reporting violations into `diagnostics`. func interpret(endProject i: InstructionID, in context: inout Context) { let s = self[i] as! EndProject + let r = self[s.start.instruction!] as! Project + finalize(region: s.start, projecting: r.projection.access, exitedWith: i, in: &context) + } - // Skip the instruction if an error occured upstream. - guard context.locals[s.start] != nil else { - assert(diagnostics.containsError) - return - } + /// Interprets `i` in `context`, reporting violations into `diagnostics`. + func interpret(endProjectWitness i: InstructionID, in context: inout Context) { + let s = self[i] as! EndProjectWitness + let r = self[s.start.instruction!] as! Project + finalize(region: s.start, projecting: r.projection.access, exitedWith: i, in: &context) } /// Interprets `i` in `context`, reporting violations into `diagnostics`. @@ -241,13 +248,13 @@ extension Module { /// Interprets `i` in `context`, reporting violations into `diagnostics`. func interpret(project i: InstructionID, in context: inout Context) { let s = self[i] as! Project - let l = AbstractLocation.root(.register(i)) - precondition(context.memory[l] == nil, "projection leak") + initializeRegister(createdBy: i, projecting: s.projection, in: &context) + } - context.memory[l] = .init( - layout: AbstractTypeLayout(of: s.projection.bareType, definedIn: program), - value: .full(.unique)) - context.locals[.register(i)] = .locations([l]) + /// Interprets `i` in `context`, reporting violations into `diagnostics`. + func interpret(projectWitness i: InstructionID, in context: inout Context) { + let s = self[i] as! Project + initializeRegister(createdBy: i, projecting: s.projection, in: &context) } /// Interprets `i` in `context`, reporting violations into `diagnostics`. @@ -279,6 +286,19 @@ extension Module { context.locals[.register(i)] = context.locals[s.witness] } + /// Checks that the state of the object projected in the region defined at `start` and exited + /// with `exit` is consistent with `access`, updating `context` accordingly. + func finalize( + region start: Operand, projecting access: AccessEffect, exitedWith exit: InstructionID, + in context: inout Context + ) { + // Skip the instruction if an error occured upstream. + guard let v = context.locals[start] else { + assert(diagnostics.containsError) + return + } + context.memory[v.unwrapLocations()!.uniqueElement!] = nil + } } /// Returns the initial context in which `f` should be interpreted. @@ -324,6 +344,20 @@ extension Module { } } + /// Assigns the virtual register defined by `i` to the location of the storage projected by `i`, + /// using `t` to set the initial state of that storage. + private func initializeRegister( + createdBy i: InstructionID, projecting t: RemoteType, in context: inout Context + ) { + let l = AbstractLocation.root(.register(i)) + precondition(context.memory[l] == nil, "projection leak") + + context.memory[l] = .init( + layout: AbstractTypeLayout(of: t.bareType, definedIn: program), + value: .full(.unique)) + context.locals[.register(i)] = .locations([l]) + } + /// Returns the borrowed instruction from which `b` reborrows, if any. private func reborrowedSource(_ b: Access) -> InstructionID? { if let s = accessSource(b.source).instruction, self[s] is Access { From a35b82efb4bc4b7a9fd2adc65f146cb156db4e11 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Sat, 19 Aug 2023 09:44:26 -0400 Subject: [PATCH 10/21] Lower coercions into existential containers --- Sources/IR/Emitter.swift | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Sources/IR/Emitter.swift b/Sources/IR/Emitter.swift index f9f5bf050..dcd0634cd 100644 --- a/Sources/IR/Emitter.swift +++ b/Sources/IR/Emitter.swift @@ -1875,6 +1875,8 @@ struct Emitter { } switch rhs.base { + case let t as ExistentialType: + return _emitCoerce(source, to: t, at: site) case let t as LambdaType: return _emitCoerce(source, to: t, at: site) default: @@ -1882,13 +1884,30 @@ struct Emitter { } } + /// Inserts the IR for coercing `source` to an address of type `target`. + /// + /// - Requires: `target` is canonical. + private mutating func _emitCoerce( + _ source: Operand, to target: ExistentialType, at site: SourceRange + ) -> Operand { + let t = module.type(of: source).ast + if t.base is ExistentialType { + return source + } + + let x0 = emitAllocStack(for: ^target, at: site) + let x1 = insert(module.makeProjectWitness(of: x0, as: .init(.set, t), at: site))! + emitMove([.set], source, to: x1, at: site) + insert(module.makeEndProjectWitness(x1, at: site)) + return x0 + } + /// Inserts the IR for coercing `source` to an address of type `target`. /// /// - Requires: `target` is canonical. private mutating func _emitCoerce( _ source: Operand, to target: LambdaType, at site: SourceRange ) -> Operand { - precondition(target[.isCanonical]) let t = module.type(of: source).ast guard let lhs = LambdaType(t) else { unexpectedCoercion(from: t, to: ^target) From dcaca697e9c9deeac1fb2378b88f7be549dc7ffb Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Sat, 19 Aug 2023 09:44:40 -0400 Subject: [PATCH 11/21] Fix comment --- Sources/IR/Operands/Instruction/UnionDiscriminator.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/IR/Operands/Instruction/UnionDiscriminator.swift b/Sources/IR/Operands/Instruction/UnionDiscriminator.swift index d73993a5a..613fd6a73 100644 --- a/Sources/IR/Operands/Instruction/UnionDiscriminator.swift +++ b/Sources/IR/Operands/Instruction/UnionDiscriminator.swift @@ -32,7 +32,8 @@ public struct UnionDiscriminator: Instruction { extension Module { - /// Creates an `unreachable` anchored at `site` that marks the execution path unreachable. + /// Creates a `union_discriminator` anchored at `site` that returns the discriminator of the + /// element stored in `container`. func makeUnionDiscriminator( _ container: Operand, at site: SourceRange ) -> UnionDiscriminator { From 677567aab6704b8f4807eafbd36f7b1f7a95b1ae Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Sat, 19 Aug 2023 12:17:15 -0400 Subject: [PATCH 12/21] Interpret 'emit_existential_addr' an offset computation during IR analysis --- .../Analysis/Module+AccessReification.swift | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Sources/IR/Analysis/Module+AccessReification.swift b/Sources/IR/Analysis/Module+AccessReification.swift index 44ec002d3..b4802673a 100644 --- a/Sources/IR/Analysis/Module+AccessReification.swift +++ b/Sources/IR/Analysis/Module+AccessReification.swift @@ -101,10 +101,9 @@ extension Module { private func forEachClient(of i: InstructionID, _ action: (Use) -> Void) { guard let uses = self.uses[.register(i)] else { return } for u in uses { - switch self[u.user] { - case is AdvancedByBytes, is OpenCapture, is OpenUnion, is SubfieldView: + if self[u.user].isTransparentOffset { forEachClient(of: u.user, action) - default: + } else { action(u) } } @@ -165,3 +164,18 @@ private protocol ReifiableAccess { extension Access: ReifiableAccess {} extension ProjectBundle: ReifiableAccess {} + +extension Instruction { + + /// `true` iff `self` is an instruction computing an address derived from its operand without + /// accessing them. + fileprivate var isTransparentOffset: Bool { + switch self { + case is AdvancedByBytes, is OpenCapture, is OpenUnion, is SubfieldView, is WrapExistentialAddr: + return true + default: + return false + } + } + +} From e33854f8d7472e57a8898bcc45da4fa79ae4e54e Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Sat, 19 Aug 2023 12:18:19 -0400 Subject: [PATCH 13/21] Refactor the creation of borrowed existentials --- Sources/IR/Emitter.swift | 76 ++++------------------------------------ Sources/IR/Module.swift | 32 +++++++++++++++++ 2 files changed, 39 insertions(+), 69 deletions(-) diff --git a/Sources/IR/Emitter.swift b/Sources/IR/Emitter.swift index dcd0634cd..a2d2d207c 100644 --- a/Sources/IR/Emitter.swift +++ b/Sources/IR/Emitter.swift @@ -562,7 +562,7 @@ struct Emitter { let t = canonical(program[partDecl].type) if !program.areEquivalent(t, partType, in: program[d].scope) { if let u = ExistentialType(t) { - let box = emitExistential(u, borrowing: access, from: part, at: ast[partDecl].site) + let box = emitExistential(u, wrapping: part, at: ast[partDecl].site) part = box } } @@ -579,55 +579,6 @@ struct Emitter { } } - /// Returns the lowered conformances of `model` that are exposed to `useScope`. - private mutating func loweredConformances( - of model: AnyType, exposedTo useScope: AnyScopeID - ) -> Set { - guard let conformances = program.conformances[model] else { return [] } - - var result: Set = [] - for concept in conformances.keys { - let c = program.conformance(of: model, to: concept, exposedTo: useScope)! - result.insert(loweredConformance(c)) - } - return result - } - - /// Returns the lowered form of `c`, generating function references in `useScope`. - private mutating func loweredConformance(_ c: Core.Conformance) -> IR.Conformance { - var implementations = IR.Conformance.ImplementationMap() - for (r, i) in c.implementations { - switch i { - case .concrete(let d): - implementations[r] = loweredRequirementImplementation(d) - - case .synthetic(let d): - lower(synthetic: d) - implementations[r] = .function(.init(to: .init(d), in: module)) - } - } - - return .init(concept: c.concept, implementations: implementations) - } - - /// Returns the lowered form of the requirement implementation `d` in `useScope`. - private mutating func loweredRequirementImplementation( - _ d: AnyDeclID - ) -> IR.Conformance.Implementation { - switch d.kind { - case FunctionDecl.self: - let r = FunctionReference(to: FunctionDecl.ID(d)!, in: &module) - return .function(r) - - case InitializerDecl.self: - let r = FunctionReference(to: InitializerDecl.ID(d)!, in: &module) - return .function(r) - - default: - fatalError("not implemented") - } - } - // MARK: Synthetic declarations /// Synthesizes the implementation of `d`. @@ -1895,11 +1846,7 @@ struct Emitter { return source } - let x0 = emitAllocStack(for: ^target, at: site) - let x1 = insert(module.makeProjectWitness(of: x0, as: .init(.set, t), at: site))! - emitMove([.set], source, to: x1, at: site) - insert(module.makeEndProjectWitness(x1, at: site)) - return x0 + return emitExistential(target, wrapping: source, at: site) } /// Inserts the IR for coercing `source` to an address of type `target`. @@ -2013,22 +1960,13 @@ struct Emitter { } } - /// Returns an existential container of type `t` borrowing `access` on `witness`. + /// Returns an existential container of type `t` wrappring `witness`. private mutating func emitExistential( - _ t: ExistentialType, borrowing access: AccessEffect, from witness: Operand, - at site: SourceRange + _ t: ExistentialType, wrapping witness: Operand, at site: SourceRange ) -> Operand { - let witnessTable = emitWitnessTable(of: module.type(of: witness).ast, usedIn: insertionScope!) - let g = PointerConstant(module.id, module.addGlobal(witnessTable)) - - let x0 = insert(module.makeAccess(access, from: witness, at: site))! - let x1 = insert(module.makeWrapExistentialAddr(x0, .constant(g), as: t, at: site))! - return x1 - } - - /// Returns the witness table of `t` in `s`. - private mutating func emitWitnessTable(of t: AnyType, usedIn s: AnyScopeID) -> WitnessTable { - .init(for: t, conformingTo: loweredConformances(of: t, exposedTo: s), in: s) + let w = module.type(of: witness).ast + let table = Operand.constant(module.demandWitnessTable(w, in: insertionScope!)) + return insert(module.makeWrapExistentialAddr(witness, table, as: t, at: site))! } // MARK: l-values diff --git a/Sources/IR/Module.swift b/Sources/IR/Module.swift index 5fe08e3ef..e71d3e115 100644 --- a/Sources/IR/Module.swift +++ b/Sources/IR/Module.swift @@ -470,6 +470,38 @@ public struct Module { } } + /// Returns a pointer to the witness table of `t` used in `scopeOfUse`. + mutating func demandWitnessTable(_ t: AnyType, in scopeOfUse: AnyScopeID) -> PointerConstant { + let w = WitnessTable( + for: t, conformingTo: loweredConformances(of: t, exposedTo: scopeOfUse), + in: scopeOfUse) + return PointerConstant(id, addGlobal(w)) + } + + /// Returns the lowered conformances of `model` that are exposed to `useScope`. + private mutating func loweredConformances( + of model: AnyType, exposedTo useScope: AnyScopeID + ) -> Set { + guard let conformances = program.conformances[model] else { return [] } + + var result: Set = [] + for concept in conformances.keys { + let c = program.conformance(of: model, to: concept, exposedTo: useScope)! + result.insert(loweredConformance(c)) + } + return result + } + + /// Returns the lowered form of `c`. + private mutating func loweredConformance(_ c: Core.Conformance) -> IR.Conformance { + var implementations = IR.Conformance.ImplementationMap() + for (r, i) in c.implementations { + let f = demandDeclaration(lowering: i)! + implementations[r] = .function(FunctionReference(to: f, in: self)) + } + return .init(concept: c.concept, implementations: implementations) + } + /// Returns a map from `f`'s generic arguments to their skolemized form. /// /// - Requires: `f` is declared in `self`. From 216b299750e43abba6788070f965ba165431d28a Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Sat, 19 Aug 2023 16:12:12 -0400 Subject: [PATCH 14/21] Support implicit coercion to union types --- Sources/IR/Emitter.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Sources/IR/Emitter.swift b/Sources/IR/Emitter.swift index a2d2d207c..3f3834334 100644 --- a/Sources/IR/Emitter.swift +++ b/Sources/IR/Emitter.swift @@ -1830,6 +1830,8 @@ struct Emitter { return _emitCoerce(source, to: t, at: site) case let t as LambdaType: return _emitCoerce(source, to: t, at: site) + case let t as UnionType: + return _emitCoerce(source, to: t, at: site) default: unexpectedCoercion(from: lhs, to: rhs) } @@ -1875,6 +1877,17 @@ struct Emitter { return source } + /// Inserts the IR for coercing `source` to an address of type `target`. + /// + /// - Requires: `target` is canonical. + private mutating func _emitCoerce( + _ source: Operand, to target: UnionType, at site: SourceRange + ) -> Operand { + let x0 = emitAllocStack(for: ^target, at: site) + emitMove([.set], source, to: x0, at: site) + return x0 + } + /// Traps on this execution path becauses of un unexpected coercion from `lhs` to `rhs`. private func unexpectedCoercion( from lhs: AnyType, to rhs: AnyType, file: StaticString = #file, line: UInt = #line From 0228bea749c1fefd42f69b8f9a22229efc7c25ec Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Sat, 19 Aug 2023 20:11:03 -0400 Subject: [PATCH 15/21] Add missing lifetime extenders --- Sources/IR/Analysis/Module+CloseBorrows.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/IR/Analysis/Module+CloseBorrows.swift b/Sources/IR/Analysis/Module+CloseBorrows.swift index 24a86e857..fc7f3c41d 100644 --- a/Sources/IR/Analysis/Module+CloseBorrows.swift +++ b/Sources/IR/Analysis/Module+CloseBorrows.swift @@ -139,6 +139,10 @@ private protocol LifetimeExtender {} extension Access: LifetimeExtender {} +extension AdvancedByBytes: LifetimeExtender {} + +extension OpenCapture: LifetimeExtender {} + extension OpenUnion: LifetimeExtender {} extension Project: LifetimeExtender {} From 5577520aa8d3304236eea071bea3245b3e5c6997 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Sat, 19 Aug 2023 20:11:32 -0400 Subject: [PATCH 16/21] Fix access reification --- Sources/IR/Analysis/Module+AccessReification.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/IR/Analysis/Module+AccessReification.swift b/Sources/IR/Analysis/Module+AccessReification.swift index b4802673a..2408cb41b 100644 --- a/Sources/IR/Analysis/Module+AccessReification.swift +++ b/Sources/IR/Analysis/Module+AccessReification.swift @@ -87,7 +87,7 @@ extension Module { case is ProjectBundle: return requests(projectBundle: u) default: - unreachable() + return [] } } From 43f94b02e78d7e500bb20393365b86a365823b7f Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Sat, 19 Aug 2023 20:12:52 -0400 Subject: [PATCH 17/21] Consider all address offset instructions when looking for reborrowers --- Sources/IR/Analysis/Module+Ownership.swift | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Sources/IR/Analysis/Module+Ownership.swift b/Sources/IR/Analysis/Module+Ownership.swift index 62147adb7..f877e2bd5 100644 --- a/Sources/IR/Analysis/Module+Ownership.swift +++ b/Sources/IR/Analysis/Module+Ownership.swift @@ -371,9 +371,18 @@ extension Module { /// /// - Requires: `o` denotes a location. private func accessSource(_ o: Operand) -> Operand { - if let a = self[o] as? SubfieldView { + switch self[o] { + case let a as AdvancedByBytes: + return accessSource(a.base) + case let a as OpenCapture: + return accessSource(a.source) + case let a as OpenUnion: + return accessSource(a.container) + case let a as SubfieldView: return accessSource(a.recordAddress) - } else { + case let a as WrapExistentialAddr: + return accessSource(a.witness) + default: return o } } From 61b51ae53df21c71616b3f01985727d33b6982f2 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Sat, 19 Aug 2023 20:13:43 -0400 Subject: [PATCH 18/21] Handle coercions initializing let and inout bindings --- Sources/IR/Emitter.swift | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Sources/IR/Emitter.swift b/Sources/IR/Emitter.swift index 3f3834334..7816f698a 100644 --- a/Sources/IR/Emitter.swift +++ b/Sources/IR/Emitter.swift @@ -556,16 +556,10 @@ struct Emitter { for (path, name) in ast.names(in: program[d].pattern.subpattern) { var part = emitSubfieldView(source, at: path, at: program[name].decl.site) - let partType = module.type(of: part).ast let partDecl = ast[name].decl let t = canonical(program[partDecl].type) - if !program.areEquivalent(t, partType, in: program[d].scope) { - if let u = ExistentialType(t) { - let box = emitExistential(u, wrapping: part, at: ast[partDecl].site) - part = box - } - } + part = emitCoerce(part, to: t, at: ast[partDecl].site) if isSink { let b = module.makeAccess( From 1ea7fb802e669cb87653ed4d9e11962aeabd9d4e Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Sat, 19 Aug 2023 20:14:25 -0400 Subject: [PATCH 19/21] Add a helper to insert IR extracting union discriminators --- Sources/IR/Emitter.swift | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Sources/IR/Emitter.swift b/Sources/IR/Emitter.swift index 7816f698a..3ff41a239 100644 --- a/Sources/IR/Emitter.swift +++ b/Sources/IR/Emitter.swift @@ -702,7 +702,7 @@ struct Emitter { successors.append(appendBlock()) } - let n = insert(module.makeUnionDiscriminator(argument, at: site))! + let n = emitUnionDiscriminator(argument, at: site) insert(module.makeSwitch(on: n, toOneOf: successors, at: site)) let tail = appendBlock() @@ -1766,7 +1766,7 @@ struct Emitter { let i = program.discriminatorToElement(in: containerType).firstIndex(of: patternType)! let expected = IntegerConstant(i, bitWidth: 64) // FIXME: should be width of 'word' - let actual = insert(module.makeUnionDiscriminator(container, at: site))! + let actual = emitUnionDiscriminator(container, at: site) let test = insert( module.makeLLVM(applying: .icmp(.eq, .word), to: [.constant(expected), actual], at: site))! @@ -2367,7 +2367,7 @@ struct Emitter { successors.append(appendBlock()) } - let n = insert(module.makeUnionDiscriminator(storage, at: site))! + let n = emitUnionDiscriminator(storage, at: site) insert(module.makeSwitch(on: n, toOneOf: successors, at: site)) let tail = appendBlock() @@ -2453,6 +2453,17 @@ struct Emitter { insertionPoint = .end(of: success) } + /// Emits the IR for copying the union discriminator of `container`, which is the address of + /// a union container, anchoring new instructions at `site`. + private mutating func emitUnionDiscriminator( + _ container: Operand, at site: SourceRange + ) -> Operand { + let x0 = insert(module.makeAccess(.let, from: container, at: site))! + let x1 = insert(module.makeUnionDiscriminator(x0, at: site))! + insert(module.makeEndAccess(x0, at: site)) + return x1 + } + /// Returns the result of calling `action` on a copy of `self` in which a `newFrame` is the top /// frame. /// From 9e27c3b7f1e4dd812468dedfbb3250c58ebfa726 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Sat, 19 Aug 2023 20:14:42 -0400 Subject: [PATCH 20/21] Fix coercion to union containers --- Sources/IR/Emitter.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/IR/Emitter.swift b/Sources/IR/Emitter.swift index 3ff41a239..a3fedc322 100644 --- a/Sources/IR/Emitter.swift +++ b/Sources/IR/Emitter.swift @@ -1877,8 +1877,12 @@ struct Emitter { private mutating func _emitCoerce( _ source: Operand, to target: UnionType, at site: SourceRange ) -> Operand { + let lhs = module.type(of: source).ast + let x0 = emitAllocStack(for: ^target, at: site) - emitMove([.set], source, to: x0, at: site) + let x1 = insert(module.makeOpenUnion(x0, as: lhs, forInitialization: true, at: site))! + emitMove([.set], source, to: x1, at: site) + insert(module.makeCloseUnion(x1, at: site)) return x0 } From acef096e0b26442149cb3f5fee5d09a0f290ce25 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Sat, 19 Aug 2023 20:15:18 -0400 Subject: [PATCH 21/21] Fix codegen of 'open_union' --- Sources/CodeGen/LLVM/Transpilation.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Sources/CodeGen/LLVM/Transpilation.swift b/Sources/CodeGen/LLVM/Transpilation.swift index 92569a879..7d7fe8e3b 100644 --- a/Sources/CodeGen/LLVM/Transpilation.swift +++ b/Sources/CodeGen/LLVM/Transpilation.swift @@ -916,7 +916,13 @@ extension LLVM.Module { /// Inserts the transpilation of `i` at `insertionPoint`. func insert(openUnion i: IR.InstructionID) { let s = m[i] as! OpenUnion - register[.register(i)] = llvm(s.container) + let t = UnionType(m.type(of: s.container).ast)! + + let baseType = ir.llvm(unionType: t, in: &self) + let container = llvm(s.container) + let indices = [i32.constant(0), i32.constant(0)] + register[.register(i)] = insertGetElementPointerInBounds( + of: container, typed: baseType, indices: indices, at: insertionPoint) } /// Inserts the transpilation of `i` at `insertionPoint`.