diff --git a/Sources/FrontEnd/TypedProgram.swift b/Sources/FrontEnd/TypedProgram.swift index 5e4c2fc15..3a10e8d46 100644 --- a/Sources/FrontEnd/TypedProgram.swift +++ b/Sources/FrontEnd/TypedProgram.swift @@ -428,7 +428,15 @@ public struct TypedProgram { } } - /// Returns the declarations of `d`' captures. + /// Returns the type of `d` specialized by `specialization` in `scopeOfUse`. + public func canonicalType( + of d: T.ID, specializedBy specialization: GenericArguments, in scopeOfUse: AnyScopeID + ) -> AnyType { + let t = specialize(self[d].type, for: specialization, in: scopeOfUse) + return canonical(t, in: scopeOfUse) + } + + /// Returns the declarations of `d`'s captures. /// /// If `d` is a member, its receiver is its only capture. Otherwise, this method returns /// `nonMemberCaptures(d)`. @@ -439,7 +447,7 @@ public struct TypedProgram { return nonMemberCaptures(of: d) } - /// Returns the declarations of `d`' captures. + /// Returns the declarations of `d`'s captures. /// /// If `d` is a member, its receiver is its only capture. Otherwise, this method returns /// `nonMemberCaptures(d)`. diff --git a/Sources/IR/Analysis/Module+AccessReification.swift b/Sources/IR/Analysis/Module+AccessReification.swift index 2408cb41b..665468380 100644 --- a/Sources/IR/Analysis/Module+AccessReification.swift +++ b/Sources/IR/Analysis/Module+AccessReification.swift @@ -145,7 +145,7 @@ extension Module { arguments[a] = .register(insert(b, before: i)) } - let o = RemoteType(k, s.projection.bareType) + let o = RemoteType(k, s.projection) let reified = makeProject( o, applying: s.variants[k]!, specializedBy: s.bundle.arguments, to: arguments, at: s.site) replace(i, with: reified) diff --git a/Sources/IR/BundleReference.swift b/Sources/IR/BundleReference.swift new file mode 100644 index 000000000..8a2cdd2d5 --- /dev/null +++ b/Sources/IR/BundleReference.swift @@ -0,0 +1,30 @@ +import Core + +/// A reference to a method or subscript bundle. +public struct BundleReference: Hashable { + + /// The ID of the referred bundle. + public let bundle: T.ID + + /// If `bundle` is generic, the arguments to its generic parameter. + public let arguments: GenericArguments + + /// Creates a reference to `s` parameterized by `a`. + public init(to s: T.ID, specializedBy a: GenericArguments) { + self.bundle = s + self.arguments = a + } + +} + +extension BundleReference: CustomStringConvertible { + + public var description: String { + if arguments.isEmpty { + return "@\(bundle)" + } else { + return "@\(bundle)<\(list: arguments.values)>" + } + } + +} diff --git a/Sources/IR/Callee.swift b/Sources/IR/Callee.swift new file mode 100644 index 000000000..39b2851f4 --- /dev/null +++ b/Sources/IR/Callee.swift @@ -0,0 +1,30 @@ +import Core + +/// The callee of a function call in the IR. +enum Callee { + + /// A direct reference to a function, initializer, or method implementation. + case direct(FunctionReference) + + /// A reference to a variant in a method bundle. + case bundle(BundleReference) + + /// A lambda. + case lambda(Operand) + + /// Creates an instance representing a reference to the lowered form of `d` in `module`, + /// specialized by`a` in `scopeOfUse`. + init( + _ d: AnyDeclID, specializedBy a: GenericArguments, + in module: inout Module, usedIn scopeOfUse: AnyScopeID + ) { + if let m = MethodDecl.ID(d) { + self = .bundle(BundleReference(to: m, specializedBy: a)) + } else { + let r = FunctionReference( + to: FunctionDecl.ID(d)!, in: &module, specializedBy: a, in: scopeOfUse) + self = .direct(r) + } + } + +} diff --git a/Sources/IR/Emitter.swift b/Sources/IR/Emitter.swift index bf746b5eb..6bfca5f9a 100644 --- a/Sources/IR/Emitter.swift +++ b/Sources/IR/Emitter.swift @@ -1164,8 +1164,14 @@ struct Emitter { let arguments = captures + explicitArguments // Call is evaluated last. - let o = insert(module.makeAccess(.set, from: storage, at: ast[e].site))! - insert(module.makeCall(applying: callee, to: arguments, writingResultTo: o, at: ast[e].site)) + switch callee { + case .direct(let r): + emitApply(.constant(r), to: arguments, writingResultTo: storage, at: ast[e].site) + case .lambda(let r): + emitApply(r, to: arguments, writingResultTo: storage, at: ast[e].site) + case .bundle(let r): + emitApply(r, to: arguments, writingResultTo: storage, at: ast[e].site) + } } /// Inserts the IR for storing the value of `e` to `storage`. @@ -1404,6 +1410,27 @@ struct Emitter { frames.top.setMayHoldCaptures(s) } + /// Inserts the IR for calling `callee` on `arguments`, storing the result to `storage`. + private mutating func emitApply( + _ callee: Operand, to arguments: [Operand], + writingResultTo storage: Operand, at site: SourceRange + ) { + let o = insert(module.makeAccess(.set, from: storage, at: site))! + insert(module.makeCall(applying: callee, to: arguments, writingResultTo: o, at: site)) + insert(module.makeEndAccess(o, at: site)) + } + + /// Inserts the IR for calling `callee` on `arguments`, storing the result to `storage`. + private mutating func emitApply( + _ callee: BundleReference, to arguments: [Operand], + writingResultTo storage: Operand, at site: SourceRange + ) { + let o = insert(module.makeAccess(.set, from: storage, at: site))! + insert(module.makeCallBundle( + applying: .init(callee, in: insertionScope!), to: arguments, writingResultTo: o, at: site)) + insert(module.makeEndAccess(o, at: site)) + } + /// Inserts the IR for given constructor `call`, which initializes storage `r` by applying /// initializer `d` parameterized by `a`. /// @@ -1579,7 +1606,7 @@ struct Emitter { /// - Requires: `callee` has a lambda type. private mutating func emit( functionCallee callee: AnyExprID - ) -> (callee: Operand, captures: [Operand]) { + ) -> (callee: Callee, captures: [Operand]) { switch callee.kind { case NameExpr.self: return emit(namedFunctionCallee: .init(callee)!) @@ -1589,41 +1616,36 @@ struct Emitter { return emit(functionCallee: ast[InoutExpr.ID(callee)!].subject) default: - return (emit(lambdaCallee: callee), []) + let f = emit(lambdaCallee: callee) + return (.lambda(f), []) } } /// Inserts the IR for given `callee` and returns `(c, a)`, where `c` is the callee's value and /// `a` are arguments to lifted parameters. - /// - /// - Requires: `callee` has a lambda type. private mutating func emit( namedFunctionCallee callee: NameExpr.ID - ) -> (callee: Operand, captures: [Operand]) { - let calleeType = LambdaType(canonical(program[callee].type))! - + ) -> (callee: Callee, captures: [Operand]) { switch program[callee].referredDecl { case .direct(let d, let a) where d.kind == FunctionDecl.self: // Callee is a direct reference to a function declaration. - guard calleeType.environment == .void else { + guard LambdaType(canonical(program[callee].type))!.environment == .void else { UNIMPLEMENTED() } let specialization = module.specialization(in: insertionFunction!).merging(a) - let r = FunctionReference( + let f = FunctionReference( to: FunctionDecl.ID(d)!, in: &module, specializedBy: specialization, in: insertionScope!) - return (.constant(r), []) + return (.direct(f), []) - case .member(let d, let a, let s) where d.kind == FunctionDecl.self: - // Callee is a member reference to a function or method. - let r = FunctionReference( - to: FunctionDecl.ID(d)!, in: &module, specializedBy: a, in: insertionScope!) - - // The callee's receiver is the sole capture. + case .member(let d, let a, let s): + // Callee is a member reference to a function or method. Its receiver is the only capture. + let specialization = module.specialization(in: insertionFunction!).merging(a) let receiver = emitLValue(receiver: s, at: ast[callee].site) - let k = RemoteType(calleeType.captures[0].type)?.access ?? .sink - let i = insert(module.makeAccess(k, from: receiver, at: ast[callee].site))! - return (Operand.constant(r), [i]) + let k = receiverCapabilities(program[callee].type) + let c = insert(module.makeAccess(k, from: receiver, at: ast[callee].site))! + let f = Callee(d, specializedBy: specialization, in: &module, usedIn: insertionScope!) + return (f, [c]) case .builtinFunction, .builtinType: // Calls to built-ins should have been handled already. @@ -1632,7 +1654,7 @@ struct Emitter { default: // Callee is a lambda. let f = emit(lambdaCallee: .init(callee)) - return (f, []) + return (.lambda(f), []) } } @@ -1655,7 +1677,7 @@ struct Emitter { private mutating func emit( subscriptCallee callee: AnyExprID - ) -> (callee: SubscriptBundleReference, captures: [Operand]) { + ) -> (callee: BundleReference, captures: [Operand]) { // TODO: Handle captures switch callee.kind { case NameExpr.self: @@ -1672,7 +1694,7 @@ struct Emitter { private mutating func emit( namedSubscriptCallee callee: NameExpr.ID - ) -> (callee: SubscriptBundleReference, captures: [Operand]) { + ) -> (callee: BundleReference, captures: [Operand]) { switch program[callee].referredDecl { case .direct(let d, let a) where d.kind == SubscriptDecl.self: // Callee is a direct reference to a subscript declaration. @@ -1681,12 +1703,12 @@ struct Emitter { UNIMPLEMENTED() } - let b = SubscriptBundleReference(to: SubscriptDecl.ID(d)!, parameterizedBy: a) + let b = BundleReference(to: SubscriptDecl.ID(d)!, specializedBy: a) return (b, []) case .member(let d, let a, let s) where d.kind == SubscriptDecl.self: // Callee is a member reference to a subscript declaration. - let b = SubscriptBundleReference(to: SubscriptDecl.ID(d)!, parameterizedBy: a) + let b = BundleReference(to: SubscriptDecl.ID(d)!, specializedBy: a) // The callee's receiver is the sole capture. let receiver = emitLValue(receiver: s, at: ast[callee].site) @@ -2034,17 +2056,9 @@ struct Emitter { let (callee, captures) = emit(subscriptCallee: ast[e].callee) let arguments = captures + explicitArguments - var variants: [AccessEffect: Function.ID] = [:] - for v in ast[callee.bundle].impls { - variants[ast[v].introducer.value] = module.demandDeclaration(lowering: v) - } - - // Projection is evaluated last. - let t = canonicalType(of: callee.bundle, specializedBy: callee.arguments) - return insert( - module.makeProjectBundle( - applying: variants, of: callee, typed: SubscriptType(t)!, - to: arguments, at: ast[e].site))! + let s = module.makeProjectBundle( + applying: .init(callee, in: insertionScope!), to: arguments, at: ast[e].site) + return insert(s)! } /// Inserts the IR for lvalue `e`. @@ -2145,17 +2159,12 @@ struct Emitter { boundTo: receiver, declaredBy: i, specializedBy: specialization, at: site) } + let callee = BundleReference(to: d, specializedBy: specialization) let t = SubscriptType(canonicalType(of: d, specializedBy: specialization))! let r = insert(module.makeAccess(t.capabilities, from: receiver, at: site))! - var variants: [AccessEffect: Function.ID] = [:] - for v in ast[d].impls { - variants[ast[v].introducer.value] = module.demandDeclaration(lowering: v) - } - let s = module.makeProjectBundle( - applying: variants, of: .init(to: d, parameterizedBy: specialization), typed: t, - to: [r], at: site) + applying: .init(callee, in: insertionScope!), to: [r], at: site) return insert(s)! } @@ -2407,6 +2416,19 @@ struct Emitter { return canonical(t) } + /// Returns the capabilities required by the receiver of `callee`, which is the type of a member + /// function or method bundle. + private func receiverCapabilities(_ callee: AnyType) -> AccessEffectSet { + switch canonical(callee).base { + case let t as LambdaType: + return [RemoteType(t.captures[0].type)?.access ?? .sink] + case let t as MethodType: + return t.capabilities + default: + unreachable() + } + } + /// Inserts a stack allocation for an object of type `t`. private mutating func emitAllocStack( for t: AnyType, at site: SourceRange diff --git a/Sources/IR/FunctionID.swift b/Sources/IR/FunctionID.swift index 41e121451..22977970c 100644 --- a/Sources/IR/FunctionID.swift +++ b/Sources/IR/FunctionID.swift @@ -1,4 +1,5 @@ import Core +import Utils extension Function { @@ -28,23 +29,21 @@ extension Function { /// The value of this identity. public let value: Value - /// Creates the identity of the lowered form of `f`. - public init(_ f: FunctionDecl.ID) { - self.value = .lowered(AnyDeclID(f)) - } - - /// Creates the identity of the lowered form of `f` used as an initializer. - public init(initializer f: InitializerDecl.ID) { - self.value = .lowered(AnyDeclID(f)) - } - - /// Creates the identity of the lowered form of `s`. - public init(_ s: SubscriptImpl.ID) { - self.value = .loweredSubscript(s) + /// Creates the identity of the lowered form of `d`, which is the declaration of a function, + /// initializer, method implementation, or subscript implementation.s + init(_ d: T) { + switch d.kind { + case FunctionDecl.self, InitializerDecl.self, MethodImpl.self: + self.value = .lowered(AnyDeclID(d)) + case SubscriptImpl.self: + self.value = .loweredSubscript(SubscriptImpl.ID(d)!) + default: + unreachable() + } } /// Creates the identity of the lowered form of `s`. - public init(_ s: SynthesizedFunctionDecl) { + init(_ s: SynthesizedFunctionDecl) { self.value = .synthesized(s) } diff --git a/Sources/IR/Module.swift b/Sources/IR/Module.swift index f99bfe9f3..2865a9953 100644 --- a/Sources/IR/Module.swift +++ b/Sources/IR/Module.swift @@ -280,6 +280,30 @@ public struct Module { return f } + /// Returns the identity of the IR function corresponding to `d`. + mutating func demandDeclaration(lowering d: MethodImpl.ID) -> Function.ID { + let f = Function.ID(d) + if functions[f] != nil { return f } + + let parameters = program.accumulatedGenericParameters(in: d) + let output = program.canonical( + (program[d].type.base as! CallableType).output, in: program[d].scope) + + let inputs = loweredParameters(of: d) + + let entity = Function( + isSubscript: false, + site: program.ast[d].site, + linkage: program.isExported(d) ? .external : .module, + genericParameters: Array(parameters), + inputs: inputs, + output: output, + blocks: []) + addFunction(entity, for: f) + + return f + } + /// Returns the identity of the IR function corresponding to `d`. mutating func demandDeclaration(lowering d: SubscriptImpl.ID) -> Function.ID { let f = Function.ID(d) @@ -307,7 +331,7 @@ public struct Module { mutating func demandDeclaration(lowering d: InitializerDecl.ID) -> Function.ID { precondition(!program.ast[d].isMemberwise) - let f = Function.ID(initializer: d) + let f = Function.ID(d) if functions[f] != nil { return f } let parameters = program.accumulatedGenericParameters(in: d) @@ -407,6 +431,14 @@ public struct Module { return result } + /// Returns the lowered declarations of `d`'s parameters. + private func loweredParameters(of d: MethodImpl.ID) -> [Parameter] { + let bundle = MethodDecl.ID(program[d].scope)! + let inputs = program.ast[bundle].parameters + let r = Parameter(AnyDeclID(program[d].receiver), capturedAs: program[d].receiver.type) + return [r] + inputs.map(pairedWithLoweredType(parameter:)) + } + /// Returns the lowered declarations of `d`'s parameters. /// /// `d`'s receiver comes first and is followed by `d`'s formal parameters, from left to right. @@ -791,4 +823,13 @@ public struct Module { } } + /// Returns `true` iff `o` is an `access [set]` instruction. + func isBorrowSet(_ o: Operand) -> Bool { + guard + let i = o.instruction, + let s = self[i] as? Access + else { return false } + return s.capabilities == [.set] + } + } diff --git a/Sources/IR/Operands/Constant/SubscriptBundleReference.swift b/Sources/IR/Operands/Constant/SubscriptBundleReference.swift deleted file mode 100644 index edfb15bc2..000000000 --- a/Sources/IR/Operands/Constant/SubscriptBundleReference.swift +++ /dev/null @@ -1,30 +0,0 @@ -import Core - -/// A Hylo IR reference to a user subscript bundle. -public struct SubscriptBundleReference: Hashable { - - /// The ID of the referred IR subscript bundle. - public let bundle: SubscriptDecl.ID - - /// If `function` is generic, the arguments to its generic parameter. - public let arguments: GenericArguments - - /// Creates a reference to `s` parameterized by `a`. - public init(to s: SubscriptDecl.ID, parameterizedBy a: GenericArguments) { - self.bundle = s - self.arguments = a - } - -} - -extension SubscriptBundleReference: CustomStringConvertible { - - public var description: String { - if arguments.isEmpty { - return "@\(bundle)" - } else { - return "@\(bundle)<\(list: arguments.values)>" - } - } - -} diff --git a/Sources/IR/Operands/Instruction/Call.swift b/Sources/IR/Operands/Instruction/Call.swift index 2e572481e..92c7f3f57 100644 --- a/Sources/IR/Operands/Instruction/Call.swift +++ b/Sources/IR/Operands/Instruction/Call.swift @@ -7,7 +7,7 @@ import Utils /// of the callee. `operands` must contain as many operands as the callee's type. public struct Call: Instruction { - /// The callee and arguments of the call. + /// The callee, the return storage, and arguments of the call. public private(set) var operands: [Operand] /// The site of the code corresponding to that instruction. @@ -77,13 +77,4 @@ extension Module { return .init(callee: callee, output: output, arguments: arguments, site: site) } - /// Returns `true` iff `o` is an `access [set]` instruction. - fileprivate func isBorrowSet(_ o: Operand) -> Bool { - guard - let i = o.instruction, - let s = self[i] as? Access - else { return false } - return s.capabilities == [.set] - } - } diff --git a/Sources/IR/Operands/Instruction/CallBundle.swift b/Sources/IR/Operands/Instruction/CallBundle.swift new file mode 100644 index 000000000..8f642dbe6 --- /dev/null +++ b/Sources/IR/Operands/Instruction/CallBundle.swift @@ -0,0 +1,91 @@ +import Core +import Utils + +/// Invokes one variant of `bundle` with `arguments` and writes its result to `output`. +public struct CallBundle: Instruction { + + /// The method bundle implementing the variant to call. + public let bundle: BundleReference + + /// The type of the bundle. + public let bundleType: MethodType + + /// The variants of the bundle. + public let variants: [AccessEffect: Function.ID] + + /// The return storage and arguments of the call. + 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( + bundle: BundleReference, + bundleType: MethodType, + variants: [AccessEffect: Function.ID], + output: Operand, + arguments: [Operand], + site: SourceRange + ) { + self.bundle = bundle + self.bundleType = bundleType + self.variants = variants + self.operands = [output] + arguments + self.site = site + } + + /// The capabilities possibly requested on the receiver. + public var capabilities: AccessEffectSet { + .init(variants.keys) + } + + /// The location at which the result of `callee` is stored. + public var output: Operand { operands[0] } + + /// The arguments of the call. + public var arguments: ArraySlice { operands[1...] } + + public mutating func replaceOperand(at i: Int, with new: Operand) { + operands[i] = new + } + +} + +extension CallBundle: CustomStringConvertible { + + public var description: String { + "call_bundle \(capabilities) \(bundle)(\(list: arguments)) to \(output)" + } + +} + +extension Module { + + /// Creates a `call_bundle` anchored at `site` that calls one of the variants in `bundle` on + /// `arguments`. + mutating func makeCallBundle( + applying bundle: ScopedValue>, to arguments: [Operand], + writingResultTo output: Operand, + at site: SourceRange + ) -> CallBundle { + var variants: [AccessEffect: Function.ID] = [:] + for v in program[bundle.value.bundle].impls { + variants[program[v].introducer.value] = demandDeclaration(lowering: v) + } + + let bundleType = program.canonicalType( + of: bundle.value.bundle, specializedBy: bundle.value.arguments, in: bundle.scope) + let t = MethodType(bundleType)! + + precondition((t.inputs.count + 1) == arguments.count) + precondition(arguments.allSatisfy({ self[$0] is Access })) + precondition(isBorrowSet(output)) + + return .init( + bundle: bundle.value, bundleType: t, variants: variants, + output: output, arguments: arguments, + site: site) + } + +} diff --git a/Sources/IR/Operands/Instruction/ProjectBundle.swift b/Sources/IR/Operands/Instruction/ProjectBundle.swift index e7786e1f2..3cff64b72 100644 --- a/Sources/IR/Operands/Instruction/ProjectBundle.swift +++ b/Sources/IR/Operands/Instruction/ProjectBundle.swift @@ -4,18 +4,21 @@ import Core public struct ProjectBundle: Instruction { /// The subscript bundle implementing the projections. - public let bundle: SubscriptBundleReference + public let bundle: BundleReference - /// The pure functional type of the callee. - public let pureCalleeType: LambdaType + /// The parameters of the subscript. + public let parameters: [ParameterType] + + /// The type of the projected value. + public let projection: AnyType /// The subscripts implementing the projection. public let variants: [AccessEffect: Function.ID] /// The arguments of the call. /// - /// Operands to non-`sink` inputs must be the result of an `access` instruction requesting a - /// capability for each variant in `callee` and having no use before `project`. + /// Operands to must be the result of an `access` instruction requesting a capability for each + /// variant in `callee` and having no use before `project_bundle`. public private(set) var operands: [Operand] /// The site of the code corresponding to that instruction. @@ -23,15 +26,17 @@ public struct ProjectBundle: Instruction { /// Creates an instance with the given properties. fileprivate init( - bundle: SubscriptBundleReference, - pureCalleeType: LambdaType, + bundle: BundleReference, variants: [AccessEffect: Function.ID], + parameters: [ParameterType], + projection: AnyType, operands: [Operand], site: SourceRange ) { self.bundle = bundle - self.pureCalleeType = pureCalleeType self.variants = variants + self.parameters = parameters + self.projection = projection self.operands = operands self.site = site } @@ -41,19 +46,9 @@ public struct ProjectBundle: Instruction { .init(variants.keys) } - /// The type of the projected value. - public var projection: RemoteType { - RemoteType(pureCalleeType.output)! - } - - /// The parameters of the projection. - public var parameters: LazyMapSequence<[CallableTypeParameter], ParameterType> { - pureCalleeType.inputs.lazy.map({ ParameterType($0.type)! }) - } - /// The types of the instruction's results. public var result: IR.`Type`? { - .address(projection.bareType) + .address(projection) } public mutating func replaceOperand(at i: Int, with new: Operand) { @@ -77,24 +72,26 @@ extension ProjectBundle: CustomStringConvertible { extension Module { /// Creates a `project_bundle` anchored at `site` that projects a value by applying one of the - /// given `variants` on `arguments`. The variants are defined in `bundle`, which is has type - /// `bundleType`. - /// - /// - Requires: `bundleType` is canonical and `variants` is not empty. - func makeProjectBundle( - applying variants: [AccessEffect: Function.ID], - of bundle: SubscriptBundleReference, - typed bundleType: SubscriptType, + /// variants in `bundle` on `arguments`. + mutating func makeProjectBundle( + applying bundle: ScopedValue>, to arguments: [Operand], at site: SourceRange ) -> ProjectBundle { - precondition(bundleType[.isCanonical]) + var variants: [AccessEffect: Function.ID] = [:] + for v in program[bundle.value.bundle].impls { + variants[program[v].introducer.value] = demandDeclaration(lowering: v) + } + + let bundleType = program.canonicalType( + of: bundle.value.bundle, specializedBy: bundle.value.arguments, in: bundle.scope) + let t = SubscriptType(bundleType)!.pure + return .init( - bundle: bundle, - pureCalleeType: bundleType.pure, - variants: variants, - operands: arguments, - site: site) + bundle: bundle.value, variants: variants, + parameters: t.inputs.lazy.map({ ParameterType($0.type)! }), + projection: RemoteType(t.output)!.bareType, + operands: arguments, site: site) } }