diff --git a/Sources/CLI/FullPathInFatalErrors.swift b/Sources/CLI/FullPathInFatalErrors.swift index 868b8ee5f..302fef325 100644 --- a/Sources/CLI/FullPathInFatalErrors.swift +++ b/Sources/CLI/FullPathInFatalErrors.swift @@ -10,9 +10,9 @@ func precondition( /// Just like Swift.preconditionFailure, but includes the full file path in the diagnostic. func preconditionFailure( - _ message: @autoclosure () -> String = String(), - file: StaticString = #filePath, - line: UInt = #line + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line ) -> Never { Swift.preconditionFailure(message(), file: (file), line: line) } @@ -28,19 +28,19 @@ func fatalError( /// Just like Swift.assert, but includes the full file path in the diagnostic. func assert( - _ condition: @autoclosure () -> Bool, - _ message: @autoclosure () -> String = String(), - file: StaticString = #filePath, - line: UInt = #line + _ condition: @autoclosure () -> Bool, + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line ) { Swift.assert(condition(), message(), file: (file), line: line) } /// Just like Swift.assertionFailure, but includes the full file path in the diagnostic. func assertionFailure( - _ message: @autoclosure () -> String = String(), - file: StaticString = #filePath, - line: UInt = #line + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line ) { Swift.assertionFailure(message(), file: (file), line: line) } diff --git a/Sources/CodeGen/FullPathInFatalErrors.swift b/Sources/CodeGen/FullPathInFatalErrors.swift deleted file mode 120000 index f128bec88..000000000 --- a/Sources/CodeGen/FullPathInFatalErrors.swift +++ /dev/null @@ -1 +0,0 @@ -../CLI/FullPathInFatalErrors.swift \ No newline at end of file diff --git a/Sources/CodeGen/FullPathInFatalErrors.swift b/Sources/CodeGen/FullPathInFatalErrors.swift new file mode 100644 index 000000000..302fef325 --- /dev/null +++ b/Sources/CodeGen/FullPathInFatalErrors.swift @@ -0,0 +1,46 @@ +/// Just like Swift.precondition, but includes the full file path in the diagnostic. +func precondition( + _ condition: @autoclosure () -> Bool, + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) { + Swift.precondition(condition(), message(), file: (file), line: line) +} + +/// Just like Swift.preconditionFailure, but includes the full file path in the diagnostic. +func preconditionFailure( + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) -> Never { + Swift.preconditionFailure(message(), file: (file), line: line) +} + +/// Just like Swift.fatalError, but includes the full file path in the diagnostic. +func fatalError( + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) -> Never { + Swift.fatalError(message(), file: (file), line: line) +} + +/// Just like Swift.assert, but includes the full file path in the diagnostic. +func assert( + _ condition: @autoclosure () -> Bool, + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) { + Swift.assert(condition(), message(), file: (file), line: line) +} + +/// Just like Swift.assertionFailure, but includes the full file path in the diagnostic. +func assertionFailure( + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) { + Swift.assertionFailure(message(), file: (file), line: line) +} diff --git a/Sources/CodeGen/LLVM/Transpilation.swift b/Sources/CodeGen/LLVM/Transpilation.swift index 31349f515..4c15610ba 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) @@ -920,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`. 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/Core/FullPathInFatalErrors.swift b/Sources/Core/FullPathInFatalErrors.swift deleted file mode 120000 index f128bec88..000000000 --- a/Sources/Core/FullPathInFatalErrors.swift +++ /dev/null @@ -1 +0,0 @@ -../CLI/FullPathInFatalErrors.swift \ No newline at end of file diff --git a/Sources/Core/FullPathInFatalErrors.swift b/Sources/Core/FullPathInFatalErrors.swift new file mode 100644 index 000000000..302fef325 --- /dev/null +++ b/Sources/Core/FullPathInFatalErrors.swift @@ -0,0 +1,46 @@ +/// Just like Swift.precondition, but includes the full file path in the diagnostic. +func precondition( + _ condition: @autoclosure () -> Bool, + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) { + Swift.precondition(condition(), message(), file: (file), line: line) +} + +/// Just like Swift.preconditionFailure, but includes the full file path in the diagnostic. +func preconditionFailure( + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) -> Never { + Swift.preconditionFailure(message(), file: (file), line: line) +} + +/// Just like Swift.fatalError, but includes the full file path in the diagnostic. +func fatalError( + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) -> Never { + Swift.fatalError(message(), file: (file), line: line) +} + +/// Just like Swift.assert, but includes the full file path in the diagnostic. +func assert( + _ condition: @autoclosure () -> Bool, + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) { + Swift.assert(condition(), message(), file: (file), line: line) +} + +/// Just like Swift.assertionFailure, but includes the full file path in the diagnostic. +func assertionFailure( + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) { + Swift.assertionFailure(message(), file: (file), line: line) +} 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/Driver/FullPathInFatalErrors.swift b/Sources/Driver/FullPathInFatalErrors.swift deleted file mode 120000 index f128bec88..000000000 --- a/Sources/Driver/FullPathInFatalErrors.swift +++ /dev/null @@ -1 +0,0 @@ -../CLI/FullPathInFatalErrors.swift \ No newline at end of file diff --git a/Sources/Driver/FullPathInFatalErrors.swift b/Sources/Driver/FullPathInFatalErrors.swift new file mode 100644 index 000000000..302fef325 --- /dev/null +++ b/Sources/Driver/FullPathInFatalErrors.swift @@ -0,0 +1,46 @@ +/// Just like Swift.precondition, but includes the full file path in the diagnostic. +func precondition( + _ condition: @autoclosure () -> Bool, + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) { + Swift.precondition(condition(), message(), file: (file), line: line) +} + +/// Just like Swift.preconditionFailure, but includes the full file path in the diagnostic. +func preconditionFailure( + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) -> Never { + Swift.preconditionFailure(message(), file: (file), line: line) +} + +/// Just like Swift.fatalError, but includes the full file path in the diagnostic. +func fatalError( + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) -> Never { + Swift.fatalError(message(), file: (file), line: line) +} + +/// Just like Swift.assert, but includes the full file path in the diagnostic. +func assert( + _ condition: @autoclosure () -> Bool, + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) { + Swift.assert(condition(), message(), file: (file), line: line) +} + +/// Just like Swift.assertionFailure, but includes the full file path in the diagnostic. +func assertionFailure( + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) { + Swift.assertionFailure(message(), file: (file), line: line) +} diff --git a/Sources/FrontEnd/FullPathInFatalErrors.swift b/Sources/FrontEnd/FullPathInFatalErrors.swift deleted file mode 120000 index f128bec88..000000000 --- a/Sources/FrontEnd/FullPathInFatalErrors.swift +++ /dev/null @@ -1 +0,0 @@ -../CLI/FullPathInFatalErrors.swift \ No newline at end of file diff --git a/Sources/FrontEnd/FullPathInFatalErrors.swift b/Sources/FrontEnd/FullPathInFatalErrors.swift new file mode 100644 index 000000000..302fef325 --- /dev/null +++ b/Sources/FrontEnd/FullPathInFatalErrors.swift @@ -0,0 +1,46 @@ +/// Just like Swift.precondition, but includes the full file path in the diagnostic. +func precondition( + _ condition: @autoclosure () -> Bool, + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) { + Swift.precondition(condition(), message(), file: (file), line: line) +} + +/// Just like Swift.preconditionFailure, but includes the full file path in the diagnostic. +func preconditionFailure( + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) -> Never { + Swift.preconditionFailure(message(), file: (file), line: line) +} + +/// Just like Swift.fatalError, but includes the full file path in the diagnostic. +func fatalError( + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) -> Never { + Swift.fatalError(message(), file: (file), line: line) +} + +/// Just like Swift.assert, but includes the full file path in the diagnostic. +func assert( + _ condition: @autoclosure () -> Bool, + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) { + Swift.assert(condition(), message(), file: (file), line: line) +} + +/// Just like Swift.assertionFailure, but includes the full file path in the diagnostic. +func assertionFailure( + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) { + Swift.assertionFailure(message(), file: (file), line: line) +} 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 04ccd5566..db47fe431 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/GenerateHyloFileTests/FullPathInFatalErrors.swift b/Sources/GenerateHyloFileTests/FullPathInFatalErrors.swift deleted file mode 120000 index f128bec88..000000000 --- a/Sources/GenerateHyloFileTests/FullPathInFatalErrors.swift +++ /dev/null @@ -1 +0,0 @@ -../CLI/FullPathInFatalErrors.swift \ No newline at end of file diff --git a/Sources/GenerateHyloFileTests/FullPathInFatalErrors.swift b/Sources/GenerateHyloFileTests/FullPathInFatalErrors.swift new file mode 100644 index 000000000..302fef325 --- /dev/null +++ b/Sources/GenerateHyloFileTests/FullPathInFatalErrors.swift @@ -0,0 +1,46 @@ +/// Just like Swift.precondition, but includes the full file path in the diagnostic. +func precondition( + _ condition: @autoclosure () -> Bool, + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) { + Swift.precondition(condition(), message(), file: (file), line: line) +} + +/// Just like Swift.preconditionFailure, but includes the full file path in the diagnostic. +func preconditionFailure( + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) -> Never { + Swift.preconditionFailure(message(), file: (file), line: line) +} + +/// Just like Swift.fatalError, but includes the full file path in the diagnostic. +func fatalError( + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) -> Never { + Swift.fatalError(message(), file: (file), line: line) +} + +/// Just like Swift.assert, but includes the full file path in the diagnostic. +func assert( + _ condition: @autoclosure () -> Bool, + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) { + Swift.assert(condition(), message(), file: (file), line: line) +} + +/// Just like Swift.assertionFailure, but includes the full file path in the diagnostic. +func assertionFailure( + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) { + Swift.assertionFailure(message(), file: (file), line: line) +} diff --git a/Sources/IR/Analysis/Module+AccessReification.swift b/Sources/IR/Analysis/Module+AccessReification.swift index 44ec002d3..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 [] } } @@ -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 + } + } + +} diff --git a/Sources/IR/Analysis/Module+CloseBorrows.swift b/Sources/IR/Analysis/Module+CloseBorrows.swift index dcaa1f177..1dee6c29a 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 {} @@ -157,8 +161,4 @@ private protocol LifetimeCloser: Instruction { } -extension EndAccess: LifetimeCloser {} - -extension EndProject: LifetimeCloser {} - -extension CloseCapture: LifetimeCloser {} +extension RegionExit: LifetimeCloser {} diff --git a/Sources/IR/Analysis/Module+NormalizeObjectStates.swift b/Sources/IR/Analysis/Module+NormalizeObjectStates.swift index dda6ad96c..3dd0e15b6 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) } @@ -389,27 +390,24 @@ 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) + return successor(of: i) + } - context.memory[l] = .init( - layout: AbstractTypeLayout(of: t.bareType, definedIn: program), - value: .full(t.access == .set ? .uninitialized : .initialized)) - context.locals[.register(i)] = .locations([l]) + /// 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) } @@ -547,6 +545,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 occurred 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. diff --git a/Sources/IR/Analysis/Module+Ownership.swift b/Sources/IR/Analysis/Module+Ownership.swift index ca9ab13b9..d20192500 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 occurred 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 occurred 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 { @@ -337,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 } } diff --git a/Sources/IR/Emitter.swift b/Sources/IR/Emitter.swift index 433852ff1..d8ed974de 100644 --- a/Sources/IR/Emitter.swift +++ b/Sources/IR/Emitter.swift @@ -546,43 +546,20 @@ 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)) - 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) 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, borrowing: access, from: part, at: ast[partDecl].site) - part = box - } - } + part = emitCoerce(part, to: t, at: ast[partDecl].site) if isSink { let b = module.makeAccess( @@ -596,55 +573,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: - UNIMPLEMENTED() - } - } - // MARK: Synthetic declarations /// Synthesizes the implementation of `d`. @@ -774,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() @@ -1838,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))! @@ -1874,52 +1802,97 @@ 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 ExistentialType: + 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) } + } - unreachable("unexpected coercion from '\(sourceType)' to \(target)") + /// 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 + } + + return emitExistential(target, wrapping: source, at: site) } - /// 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? { - precondition(target[.isCanonical]) - guard let s = LambdaType(module.type(of: source).ast) else { return nil } + ) -> Operand { + 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 } + /// 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 lhs = module.type(of: source).ast + + let x0 = emitAllocStack(for: ^target, 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 + } + + /// 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 @@ -1998,22 +1971,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 @@ -2407,7 +2371,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() @@ -2493,6 +2457,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. /// @@ -2603,12 +2578,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/Sources/IR/FullPathInFatalErrors.swift b/Sources/IR/FullPathInFatalErrors.swift deleted file mode 120000 index f128bec88..000000000 --- a/Sources/IR/FullPathInFatalErrors.swift +++ /dev/null @@ -1 +0,0 @@ -../CLI/FullPathInFatalErrors.swift \ No newline at end of file diff --git a/Sources/IR/FullPathInFatalErrors.swift b/Sources/IR/FullPathInFatalErrors.swift new file mode 100644 index 000000000..302fef325 --- /dev/null +++ b/Sources/IR/FullPathInFatalErrors.swift @@ -0,0 +1,46 @@ +/// Just like Swift.precondition, but includes the full file path in the diagnostic. +func precondition( + _ condition: @autoclosure () -> Bool, + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) { + Swift.precondition(condition(), message(), file: (file), line: line) +} + +/// Just like Swift.preconditionFailure, but includes the full file path in the diagnostic. +func preconditionFailure( + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) -> Never { + Swift.preconditionFailure(message(), file: (file), line: line) +} + +/// Just like Swift.fatalError, but includes the full file path in the diagnostic. +func fatalError( + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) -> Never { + Swift.fatalError(message(), file: (file), line: line) +} + +/// Just like Swift.assert, but includes the full file path in the diagnostic. +func assert( + _ condition: @autoclosure () -> Bool, + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) { + Swift.assert(condition(), message(), file: (file), line: line) +} + +/// Just like Swift.assertionFailure, but includes the full file path in the diagnostic. +func assertionFailure( + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) { + Swift.assertionFailure(message(), file: (file), line: line) +} diff --git a/Sources/IR/Module.swift b/Sources/IR/Module.swift index 37994f1fe..f99bfe9f3 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`. 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" } - -} 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/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/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/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/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..3da46bf44 --- /dev/null +++ b/Sources/IR/Operands/Instruction/RegionExit.swift @@ -0,0 +1,58 @@ +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)" + case let s where s == ProjectWitness.self: + return "end_project_witness \(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) + } + +} 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 { diff --git a/Sources/TestUtils/FullPathInFatalErrors.swift b/Sources/TestUtils/FullPathInFatalErrors.swift deleted file mode 120000 index f128bec88..000000000 --- a/Sources/TestUtils/FullPathInFatalErrors.swift +++ /dev/null @@ -1 +0,0 @@ -../CLI/FullPathInFatalErrors.swift \ No newline at end of file diff --git a/Sources/TestUtils/FullPathInFatalErrors.swift b/Sources/TestUtils/FullPathInFatalErrors.swift new file mode 100644 index 000000000..302fef325 --- /dev/null +++ b/Sources/TestUtils/FullPathInFatalErrors.swift @@ -0,0 +1,46 @@ +/// Just like Swift.precondition, but includes the full file path in the diagnostic. +func precondition( + _ condition: @autoclosure () -> Bool, + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) { + Swift.precondition(condition(), message(), file: (file), line: line) +} + +/// Just like Swift.preconditionFailure, but includes the full file path in the diagnostic. +func preconditionFailure( + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) -> Never { + Swift.preconditionFailure(message(), file: (file), line: line) +} + +/// Just like Swift.fatalError, but includes the full file path in the diagnostic. +func fatalError( + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) -> Never { + Swift.fatalError(message(), file: (file), line: line) +} + +/// Just like Swift.assert, but includes the full file path in the diagnostic. +func assert( + _ condition: @autoclosure () -> Bool, + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) { + Swift.assert(condition(), message(), file: (file), line: line) +} + +/// Just like Swift.assertionFailure, but includes the full file path in the diagnostic. +func assertionFailure( + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) { + Swift.assertionFailure(message(), file: (file), line: line) +} diff --git a/Sources/Utils/FullPathInFatalErrors.swift b/Sources/Utils/FullPathInFatalErrors.swift deleted file mode 120000 index f128bec88..000000000 --- a/Sources/Utils/FullPathInFatalErrors.swift +++ /dev/null @@ -1 +0,0 @@ -../CLI/FullPathInFatalErrors.swift \ No newline at end of file diff --git a/Sources/Utils/FullPathInFatalErrors.swift b/Sources/Utils/FullPathInFatalErrors.swift new file mode 100644 index 000000000..302fef325 --- /dev/null +++ b/Sources/Utils/FullPathInFatalErrors.swift @@ -0,0 +1,46 @@ +/// Just like Swift.precondition, but includes the full file path in the diagnostic. +func precondition( + _ condition: @autoclosure () -> Bool, + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) { + Swift.precondition(condition(), message(), file: (file), line: line) +} + +/// Just like Swift.preconditionFailure, but includes the full file path in the diagnostic. +func preconditionFailure( + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) -> Never { + Swift.preconditionFailure(message(), file: (file), line: line) +} + +/// Just like Swift.fatalError, but includes the full file path in the diagnostic. +func fatalError( + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) -> Never { + Swift.fatalError(message(), file: (file), line: line) +} + +/// Just like Swift.assert, but includes the full file path in the diagnostic. +func assert( + _ condition: @autoclosure () -> Bool, + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) { + Swift.assert(condition(), message(), file: (file), line: line) +} + +/// Just like Swift.assertionFailure, but includes the full file path in the diagnostic. +func assertionFailure( + _ message: @autoclosure () -> String = String(), + file: StaticString = #filePath, + line: UInt = #line +) { + Swift.assertionFailure(message(), file: (file), line: line) +} diff --git a/Tests/DriverTests/DriverTests.swift b/Tests/DriverTests/DriverTests.swift index 186c17a4f..ea82f3a86 100644 --- a/Tests/DriverTests/DriverTests.swift +++ b/Tests/DriverTests/DriverTests.swift @@ -84,11 +84,11 @@ final class DriverTests: XCTestCase { XCTAssertFalse(result.status.isSuccess) result.checkDiagnosticText( is: """ - \(valSource.relativePath):1.6: error: expected function signature - fun x - ^ + \(valSource.relativePath):1.6: error: expected function signature + fun x + ^ - """) + """) } func testTypeCheckSuccess() throws { @@ -104,11 +104,11 @@ final class DriverTests: XCTestCase { XCTAssertFalse(result.status.isSuccess) result.checkDiagnosticText( is: """ - \(valSource.relativePath):1.21-24: error: undefined name 'foo' in this scope - public fun main() { foo() } - ~~~ + \(valSource.relativePath):1.21-24: error: undefined name 'foo' in this scope + public fun main() { foo() } + ~~~ - """) + """) XCTAssertFalse(FileManager.default.fileExists(atPath: result.output.relativePath)) } 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 }