diff --git a/Library/Hylo/Array.hylo b/Library/Hylo/Array.hylo new file mode 100644 index 000000000..577455ca1 --- /dev/null +++ b/Library/Hylo/Array.hylo @@ -0,0 +1,118 @@ +/// An ordered, random-access collection. +public type Array: Deinitializable { + + /// The out-of-line storage of the array. + /// + /// The header of the buffer indicates the number of elements contained in the array. + var storage: DynamicBuffer + + /// Creates a new, empty array. + public init() { + &storage = .new() + } + + /// Deinitializes `self`. + public fun deinit() sink { + var i = 0 + while i < count() { + &pointer_to_element(at: i).unsafe_pointee().deinit() + &i += 1 + } + } + + /// The number of elements in the array. + public fun count() -> Int { + if storage.capacity() == 0 { 0 } else { storage.header.copy() } + } + + /// The number of elements that can be stored in the array before new storage must be allocated. + public fun capacity() -> Int { + return storage.capacity() + } + + /// Reserves enough space to store `n` elements + public fun reserve_capacity(_ n: Int) inout { + if n < capacity() { return } + + var new_capacity = max[1, capacity()].copy() + while new_capacity < n { + &new_capacity += new_capacity.copy() + } + + // TODO: Call `self.copy()` directly in the lambda. + let c = count() + var new_storage = DynamicBuffer( + capacity: new_capacity, + initializing_header_with: fun (_ h: set Int) -> Void { &h = c.copy() }) + + var i = 0 + var e = storage.first_element_address() + var f = new_storage.first_element_address() + while i < count() { + f.unsafe_initialize_pointee(e.unsafe_pointee()) + &e = e.advance(by: 1) + &f = f.advance(by: 1) + &i += 1 + } + + // Deinitializing the `self.storage` is safe at this point because all its elements must have + // been moved to `new_storage`. + &storage = new_storage + } + + /// Adds a new element at the end of the array. + public fun append(_ source: sink Element) inout { + &reserve_capacity(count() + 1) + pointer_to_element(at: count()).unsafe_initialize_pointee(source) + &storage.header += 1 + } + + /// Accesses the element at `position`. + /// + /// - Requires: `position` is in the range `0 ..< count()`. + public subscript(_ position: Int): Element { + let { + // precondition(position >= 0 && position < count()) + pointer_to_element(at: position).unsafe[] + } + inout { + // precondition(position >= 0 && position < count()) + pointer_to_element(at: position).unsafe[] + } + } + + /// Returns the address of the element at `position`. + /// + /// - Requires: `position` is in the range `0 ..< capacity()`. + fun pointer_to_element(at position: Int) -> PointerToMutable { + storage.first_element_address().advance(by: position) + } + +} + +/* + +// TODO: Make Array conform to Regular instead of Deinitializable once #1002 is fixed. +// Currently that issue prevents the copy() function below from compiling. +// +// Error is "type 'Element' does not conform to trait 'Movable'" + +public conformance Array: Equatable { + + /// Returns `true` iff `other` has an equivalent value. + public fun infix== (_ other: Self) -> Bool { + // TODO + return true + } + +} + +public conformance Array: Copyable { + /// Returns an equivalent instance. + public fun copy() -> Self { + // TODO + .new() + } +} + +*/ diff --git a/Library/Hylo/Core/PointerToMutable.hylo b/Library/Hylo/Core/PointerToMutable.hylo index 32161b46c..17a5bd82f 100644 --- a/Library/Hylo/Core/PointerToMutable.hylo +++ b/Library/Hylo/Core/PointerToMutable.hylo @@ -34,10 +34,10 @@ public type PointerToMutable: Regular { } /// Returns `self` offset forward by `n` array elements of `Pointee` type. - public fun advance( by n: Int ) -> Self { + public fun advance(by n: Int) -> Self { let offset_in_bytes = MemoryLayout.stride() * n return PointerToMutable.new( - base: Builtin.advanced_by_bytes_word( base, offset_in_bytes.value ) ) + base: Builtin.advanced_by_bytes_word(base, offset_in_bytes.value)) } /// Creates an instance that does not address any usable storage. @@ -82,3 +82,30 @@ public conformance PointerToMutable: Equatable { } } + +public extension PointerToMutable where Pointee: Movable { + + /// Returns the value at the address represented by `self`, leaving the storage at this address + /// uninitialized. + /// + /// Note: This method should be deprecated once nonmutating subscripts are implemented. + public fun unsafe_pointee() -> Pointee { + return base as* (remote sink Pointee) + } + + /// Initialize the value at the address represented by `self` to `value`. + /// + /// - Requires: the `MemoryLayout.size()` bytes starting at the address are uninitialized + /// and suitably aligned for `Pointee`. + public fun unsafe_initialize_pointee(_ value: sink Pointee) { + initialize(&(base as* (remote set Pointee)), to: value) + } + +} + +/// Initializes `x` to `y`. +/// +/// - Note: This function is a workaround for the lack of `set` bindings (see #925). +fun initialize(_ x: set T, to y: sink T) { + &x = y +} diff --git a/Library/Hylo/DynamicBuffer.hylo b/Library/Hylo/DynamicBuffer.hylo index bc83f0f56..415cb0cf4 100644 --- a/Library/Hylo/DynamicBuffer.hylo +++ b/Library/Hylo/DynamicBuffer.hylo @@ -4,7 +4,7 @@ /// - Warning: The deinitializer of `DynamicBuffer` does not deinitialize the elements that may /// be stored in its payload. You must ensure that they are properly deinitialized before /// `deinit` is called. -public type DynamicBuffer: Deinitializable { +public type DynamicBuffer: Deinitializable, Movable { /// The description of payloads in `DynamicBuffer`s. typealias BufferHeader = { @@ -16,7 +16,7 @@ public type DynamicBuffer: Deinitializable { static fun payload_offset() -> Int { let a = MemoryLayout.alignment() let s = MemoryLayout.size() - return s + (a - s % a) + return s + (a - s) % a } /// A pointer to the base of the buffer's out-of-line storage. @@ -49,11 +49,12 @@ public type DynamicBuffer: Deinitializable { } /// Returns the address of the initial element. - /// - Precondition: `capacity != 0` + /// + /// - Requires: `capacity() > 0`. public fun first_element_address() -> PointerToMutable { // TODO: Uncomment the following line when #995 is fixed - // precondition(self.capacity() == 0) - return PointerToMutable(type_punning: storage.advance(by_bytes: payload_offset())) + // precondition(self.capacity() > 0) + PointerToMutable(type_punning: storage.advance(by_bytes: payload_offset())) } /// Deinitializes `self`. @@ -73,4 +74,29 @@ public type DynamicBuffer: Deinitializable { return p.unsafe[].0.copy() } + /// Accesses the header of `self`. + /// + /// - Requires: `capacity() > 0`. + public property header: Header { + let { buffer_header().unsafe[].1 } + inout { &buffer_header().unsafe[].1 } + } + + /// Returns the address of the `self`'s header. + fun buffer_header() -> PointerToMutable { + PointerToMutable(type_punning: storage) + } + +} + +// TODO: This trait was added as a workaround for #1002 since DynamicBuffer +// is initialized in the Array constructor +public conformance DynamicBuffer: Movable { + + fun take_value(from source: sink Self) -> {self: Self, Void} { + // TODO: Fix defects preventing the following line from working + // &storage.base = source.storage.base + (self: source, ()) + } + } diff --git a/Package.swift b/Package.swift index a998a1be7..d02d2d675 100644 --- a/Package.swift +++ b/Package.swift @@ -16,6 +16,7 @@ let package = Package( products: [ .executable(name: "hc", targets: ["hc"]), .executable(name: "hylo-demangle", targets: ["hylo-demangle"]), + .library(name: "Hylo", targets: ["Driver"]), ], dependencies: [ diff --git a/Sources/CodeGen/LLVM/Transpilation.swift b/Sources/CodeGen/LLVM/Transpilation.swift index 7b48ba67e..e9b43a468 100644 --- a/Sources/CodeGen/LLVM/Transpilation.swift +++ b/Sources/CodeGen/LLVM/Transpilation.swift @@ -406,7 +406,7 @@ extension LLVM.Module { let transpilation = declareFunction(ir.base.mangled(f), .init(from: parameters, in: &self)) configureAttributes(transpilation, transpiledFrom: f, of: m) - configureInputAttributes(transpilation.parameters.dropFirst(), transpiledFrom: f, in: m) + configureInputAttributes(transpilation.parameters.dropLast(), transpiledFrom: f, in: m) return transpilation } @@ -903,10 +903,7 @@ extension LLVM.Module { let base = llvm(s.operands[0]) let byteOffset = llvm(s.operands[1]) register[.register(i)] = insertGetElementPointerInBounds( - of: base, - typed: ptr, - indices: [byteOffset], - at: insertionPoint) + of: base, typed: i8, indices: [byteOffset], at: insertionPoint) default: unreachable("unexpected LLVM instruction '\(s.instruction)'") diff --git a/Sources/Core/AccessEffectSet.swift b/Sources/Core/AccessEffectSet.swift index b5ac9883f..494e72494 100644 --- a/Sources/Core/AccessEffectSet.swift +++ b/Sources/Core/AccessEffectSet.swift @@ -30,8 +30,7 @@ public struct AccessEffectSet: OptionSet, Hashable { /// The weakest capability in `self`, or `nil` if `self` is empty. public var weakest: AccessEffect? { - var s = elements - return s.next() + elements.first } /// Returns the strongest capability in `self` including `k`. @@ -74,33 +73,39 @@ public struct AccessEffectSet: OptionSet, Hashable { extension AccessEffectSet { - /// A sequence with the elements of an access effect set. - public struct Elements: Sequence, IteratorProtocol { + /// A collection with the elements of an access effect set. + public struct Elements: Collection { + + public typealias Index = UInt8 + + public typealias Element = AccessEffect /// The contents of the set being iterated over. private let base: AccessEffectSet - /// The next effect returned by the iterator. - private var position: UInt8 - /// Creates an iterator over `s`. public init(_ s: AccessEffectSet) { self.base = s - self.position = s.rawValue & (~s.rawValue + 1) - if self.position == 0 { - self.position = 1 << 7 - } } - /// Returns the next element in the sequence. - public mutating func next() -> AccessEffect? { - if position == (1 << 7) { return nil } - defer { - repeat { - position = position << 1 - } while (position != (1 << 7)) && ((position & base.rawValue) != position) - } - return .init(rawValue: position) + public var startIndex: UInt8 { + base.rawValue & (~base.rawValue + 1) + } + + public var endIndex: UInt8 { + 1 << 7 + } + + public func index(after position: UInt8) -> UInt8 { + var next = position + repeat { + next = next << 1 + } while (next != endIndex) && ((next & base.rawValue) != next) + return next + } + + public subscript(position: UInt8) -> AccessEffect { + AccessEffect(rawValue: position)! } } diff --git a/Sources/Core/NativeInstruction.swift b/Sources/Core/NativeInstruction.swift index cf348bf1f..33d5d10c4 100644 --- a/Sources/Core/NativeInstruction.swift +++ b/Sources/Core/NativeInstruction.swift @@ -311,8 +311,8 @@ extension NativeInstruction: CustomStringConvertible { return "fptosi_\(l)_\(r)" case .zeroinitializer(let t): return "zeroinitializer_\(t)" - case .advancedByBytes(let byteOffset): - return "advanced_by_bytes_\(byteOffset)" + case .advancedByBytes(let t): + return "advanced_by_bytes_\(t)" } } diff --git a/Sources/Core/Program.swift b/Sources/Core/Program.swift index 46feb23c0..a4d2e169c 100644 --- a/Sources/Core/Program.swift +++ b/Sources/Core/Program.swift @@ -311,7 +311,7 @@ extension Program { scopes(from: scope).first(TranslationUnit.self)! } - /// Returns the trait defining `d` iff `d` is a requirement. Otherwise, returns nil. + /// Returns the trait of which `d` is a member, or `nil` if `d` isn't member of a trait. public func trait(defining d: T) -> TraitDecl.ID? { switch d.kind { case AssociatedTypeDecl.self, AssociatedValueDecl.self: diff --git a/Sources/Core/SourcePosition.swift b/Sources/Core/SourcePosition.swift index a82fbcbf2..fae17641c 100644 --- a/Sources/Core/SourcePosition.swift +++ b/Sources/Core/SourcePosition.swift @@ -8,7 +8,7 @@ public struct SourcePosition: Hashable { public let index: String.Index /// Creates an instance with the given properties. - init(_ index: String.Index, in file: SourceFile) { + public init(_ index: String.Index, in file: SourceFile) { self.file = file self.index = index } @@ -16,7 +16,7 @@ public struct SourcePosition: Hashable { /// Creates an instance referring to the given 1-based line and column numbers in `source`. /// /// - Precondition: `line` and `column` denote a valid position in `source`. - init(line: Int, column: Int, in file: SourceFile) { + public init(line: Int, column: Int, in file: SourceFile) { self.file = file self.index = file.index(line: line, column: column) } diff --git a/Sources/FrontEnd/TypeChecking/TypeChecker.swift b/Sources/FrontEnd/TypeChecking/TypeChecker.swift index d11758bf7..cf3f5f472 100644 --- a/Sources/FrontEnd/TypeChecking/TypeChecker.swift +++ b/Sources/FrontEnd/TypeChecking/TypeChecker.swift @@ -152,7 +152,7 @@ struct TypeChecker { return [ program.ast.movableTrait, program.ast.deinitializableTrait, - program.ast.foreignConvertibleTrait + program.ast.foreignConvertibleTrait, ] } @@ -165,13 +165,7 @@ struct TypeChecker { return conformedTraits(of: TraitType(d, ast: program.ast), in: scopeOfUse) } - // Conformances of other generic parameters are stored in generic environments. - var result = Set() - for s in program.scopes(from: scopeOfUse) where s.kind.value is GenericScope.Type { - let e = environment(of: s)! - result.formUnion(e.conformedTraits(of: ^t)) - } - + var result = conformedTraits(declaredInEnvironmentIntroducing: ^t, exposedTo: scopeOfUse) result.formUnion(conformedTraits(declaredInExtensionsOf: ^t, exposedTo: scopeOfUse)) return result } @@ -240,6 +234,22 @@ struct TypeChecker { return result } + /// Returns the traits to which `t` is declared conforming in its generic environment. + /// + /// `t` is a generic type parameter or an associated type introduced by a generic environment + /// logically containing `scopeOfUse`. The return value is the set of traits used as bounds of + /// `t` in that environment. + mutating func conformedTraits( + declaredInEnvironmentIntroducing t: AnyType, exposedTo scopeOfUse: AnyScopeID + ) -> Set { + var result = Set() + for s in program.scopes(from: scopeOfUse) where s.kind.value is GenericScope.Type { + let e = environment(of: s)! + result.formUnion(e.conformedTraits(of: ^t)) + } + return result + } + // MARK: Type transformations /// Returns `generic` with occurrences of parameters keying `specialization` replaced by their @@ -2939,7 +2949,7 @@ struct TypeChecker { // The specialization of the match includes that of context in which it was looked up. var specialization = context?.arguments ?? [:] - // If the match is a trait requirement, specialize its receiver as necessary. + // If the match is a trait member, specialize its receiver as necessary. if let t = program.trait(defining: m) { assert(specialization[program[t].receiver] == nil) specialization[program[t].receiver] = context?.type diff --git a/Sources/FrontEnd/TypedProgram.swift b/Sources/FrontEnd/TypedProgram.swift index 5e4c2fc15..fabafcc90 100644 --- a/Sources/FrontEnd/TypedProgram.swift +++ b/Sources/FrontEnd/TypedProgram.swift @@ -329,19 +329,26 @@ public struct TypedProgram { of model: AnyType, to concept: TraitType, exposedTo scopeOfUse: AnyScopeID ) -> Conformance? { let m = canonical(model, in: scopeOfUse) - if let c = declaredConformance(of: m, to: concept, exposedTo: scopeOfUse) { + + if let c = explicitConformance(of: m, to: concept, exposedTo: scopeOfUse) { return c - } else { - return structuralConformance(of: m, to: concept, exposedTo: scopeOfUse) } + + if let c = impliedConformance(of: m, to: concept, exposedTo: scopeOfUse) { + return c + } + + // Last resort; maybe the conformance is structural. + return structuralConformance(of: m, to: concept, exposedTo: scopeOfUse) } /// Returns the explicitly declared conformance of `model` to `concept` that is exposed to /// `scopeOfUse`, or `nil` if such a conformance doesn't exist. /// /// This method returns `nil` if the conformance of `model` to `concept` is structural (e.g., a - /// tuple's synthesized conformance to `Movable`). - private func declaredConformance( + /// tuple's synthesized conformance to `Movable`) or if the conformance is implied by a trait + /// bound (e.g., `T: P` in `fun f() {}`). + private func explicitConformance( of model: AnyType, to concept: TraitType, exposedTo scopeOfUse: AnyScopeID ) -> Conformance? { assert(model[.isCanonical]) @@ -376,6 +383,28 @@ public struct TypedProgram { } } + /// Returns the conformance of `model` to `concept` that is implied by the generic environment + /// introducing `model` in `scopeOfUse`, or `nil` if such a conformance doesn't exist. + private func impliedConformance( + of model: AnyType, to concept: TraitType, exposedTo scopeOfUse: AnyScopeID + ) -> Conformance? { + var checker = TypeChecker(asContextFor: self) + let bounds = checker.conformedTraits( + declaredInEnvironmentIntroducing: model, + exposedTo: scopeOfUse) + guard bounds.contains(concept) else { return nil } + + var implementations = Conformance.ImplementationMap() + for requirement in ast.requirements(of: concept.decl) { + implementations[requirement] = .concrete(requirement) + } + + return .init( + model: model, concept: concept, arguments: [:], conditions: [], scope: scopeOfUse, + implementations: implementations, isStructural: true, + site: .empty(at: ast[scopeOfUse].site.first())) + } + /// Returns the implicit structural conformance of `model` to `concept` that is exposed to /// `scopeOfUse`, or `nil` if such a conformance doesn't exist. private func structuralConformance( @@ -428,7 +457,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 +476,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..3a7caa68d 100644 --- a/Sources/IR/Analysis/Module+AccessReification.swift +++ b/Sources/IR/Analysis/Module+AccessReification.swift @@ -83,7 +83,7 @@ extension Module { case is Load: return [.sink] case is Move: - return [.inout] + return u.index == 0 ? [.sink] : [.inout] case is ProjectBundle: return requests(projectBundle: u) default: @@ -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/Analysis/Module+CallBundleReification.swift b/Sources/IR/Analysis/Module+CallBundleReification.swift new file mode 100644 index 000000000..3dc82a4d1 --- /dev/null +++ b/Sources/IR/Analysis/Module+CallBundleReification.swift @@ -0,0 +1,32 @@ +import Core + +extension Module { + + /// Replace occurrences of `call_bundle` by `call` depending on the uses of their first operands, + /// reporting errors and warnings to `diagnostics`. + /// + /// - Requires: `f` is in `self`. + public mutating func reifyCallsToBundles(in f: Function.ID, diagnostics: inout DiagnosticSet) { + for i in blocks(in: f).map(instructions(in:)).joined() where self[i] is CallBundle { + reify(callBundle: i, for: .let) + } + } + + private mutating func reify(callBundle i: InstructionID, for k: AccessEffect) { + let s = self[i] as! CallBundle + assert(s.capabilities.contains(k)) + + var arguments = Array(s.arguments) + let r = makeAccess(k, from: arguments[0], at: s.site) + arguments[0] = .register(insert(r, before: i)) + + let b = Block.ID(containing: i) + let f = FunctionReference( + to: s.variants[k]!, in: self, specializedBy: s.bundle.arguments, in: self[b].scope) + + let reified = makeCall( + applying: .constant(f), to: arguments, writingResultTo: s.output, at: s.site) + replace(i, with: reified) + } + +} diff --git a/Sources/IR/Analysis/Module+Depolymorphize.swift b/Sources/IR/Analysis/Module+Depolymorphize.swift index 296593ec1..6ebfa5b79 100644 --- a/Sources/IR/Analysis/Module+Depolymorphize.swift +++ b/Sources/IR/Analysis/Module+Depolymorphize.swift @@ -481,8 +481,8 @@ extension Module { let p = program.specialize(c.specialization, for: specialization, in: scopeOfUse) let f: Function.ID - if let r = program.requirement(referredBy: c) { - f = monomorphize(requirement: r.declaration, of: r.trait, in: ir, for: p, in: scopeOfUse) + if let m = program.traitMember(referredBy: c.function) { + f = monomorphize(requirement: m.declaration, of: m.trait, in: ir, for: p, in: scopeOfUse) } else { f = monomorphize(c.function, in: ir, for: p, in: scopeOfUse) } @@ -552,26 +552,3 @@ extension Module { } } - -extension TypedProgram { - - /// If `r` refers to a requirement, returns the declaration of that requirement along with the - /// trait that defines it. Otherwise, returns `nil`. - fileprivate func requirement( - referredBy r: FunctionReference - ) -> (declaration: AnyDeclID, trait: TraitType)? { - switch r.function.value { - case .lowered(let d): - guard let t = trait(defining: d) else { return nil } - return (declaration: d, trait: TraitType(t, ast: ast)) - - case .loweredSubscript(let d): - guard let t = trait(defining: d) else { return nil } - return (declaration: AnyDeclID(d), trait: TraitType(t, ast: ast)) - - default: - return nil - } - } - -} diff --git a/Sources/IR/Analysis/Module+NormalizeObjectStates.swift b/Sources/IR/Analysis/Module+NormalizeObjectStates.swift index 16bbc2986..9858540fe 100644 --- a/Sources/IR/Analysis/Module+NormalizeObjectStates.swift +++ b/Sources/IR/Analysis/Module+NormalizeObjectStates.swift @@ -534,6 +534,16 @@ extension Module { if o.value == .full(.initialized) { return } if k == .set { + // If the parameter is a return value (index == 0) we emit specialized diagnostics. + if case .parameter(_, 0) = p { + let t = self[f].output + if !t.isVoidOrNever { + diagnostics.insert( + .missingFunctionReturn(expectedReturnType: t, at: site) + ) + return + } + } diagnostics.insert( .uninitializedSetParameter(beforeReturningFrom: f, in: self, at: site)) return @@ -991,4 +1001,11 @@ extension Diagnostic { .error("use of uninitialized object", at: site) } + fileprivate static func missingFunctionReturn( + expectedReturnType: AnyType, + at site: SourceRange + ) -> Diagnostic { + .error("missing return in function expected to return '\(expectedReturnType)'", at: site) + } + } 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..045cf12e2 100644 --- a/Sources/IR/Emitter.swift +++ b/Sources/IR/Emitter.swift @@ -20,7 +20,9 @@ import Utils /// /// Other entry points may be used during IR passes (e.g., `emitDeinit`). /// -/// - Note: Unless documented otherwise, methods insert IR in `self.module` at `insertionPoint`. +/// - Note: Unless documented otherwise, the methods of `Emitter` type insert IR in `self.module` +/// at `self.insertionPoint`, anchoring new instructions at the given source range, named `site` +/// in their parameter lists. struct Emitter { /// The diagnostics of lowering errors. @@ -233,9 +235,7 @@ struct Emitter { ) -> SourceRange { switch emit(braceStmt: b) { case .next: - if !canonical(returnType).isVoidOrNever { - report(.error(missingReturn: returnType, at: .empty(atEndOf: ast[b].site))) - } else { + if canonical(returnType).isVoidOrNever { emitStore(value: .void, to: returnValue!, at: .empty(atEndOf: ast[b].site)) } return ast[b].site @@ -316,9 +316,57 @@ struct Emitter { insertionPoint = .end(of: entry) let bodyFrame = Frame(locals: locals) let returnSite = pushing(bodyFrame, { $0.lowerStatements($0.ast[d].body!, expecting: .void) }) + + let receiverLayout = AbstractTypeLayout(of: program[d].receiver.type, definedIn: program) + + // If the object is empty, simply mark it initialized. + if receiverLayout.properties.isEmpty { + let receiver = entry.parameter(0) + insert(module.makeMarkState(receiver, initialized: true, at: ast[d].site)) + } insert(module.makeReturn(at: returnSite)) } + /// Inserts the IR for `d`. + private mutating func lower(method d: MethodDecl.ID) { + for i in ast[d].impls { + lower(methodImpl: i) + } + } + + /// Inserts the IR for `d`. + private mutating func lower(methodImpl d: MethodImpl.ID) { + let f = module.demandDeclaration(lowering: d) + guard let b = ast[d].body else { return } + + // Create the function entry. + let entry = module.appendEntry(in: program.scopeContainingBody(of: d)!, to: f) + + // Configure the locals. + var locals = DeclProperty() + locals[ast[d].receiver] = .parameter(entry, 0) + + let bundle = MethodDecl.ID(program[d].scope)! + for (i, p) in ast[bundle].parameters.enumerated() { + locals[p] = .parameter(entry, i + 1) + } + + let bodyFrame = Frame(locals: locals) + + // Emit the body. + self.insertionPoint = .end(of: entry) + switch b { + case .block(let s): + let returnType = LambdaType(program[d].type)!.output + let returnSite = pushing(bodyFrame, { $0.lowerStatements(s, expecting: returnType) }) + insert(module.makeReturn(at: returnSite)) + + case .expr(let e): + pushing(bodyFrame, { $0.emitStore(value: e, to: $0.returnValue!) }) + insert(module.makeReturn(at: ast[e].site)) + } + } + /// Inserts the IR for `d`. private mutating func lower(subscript d: SubscriptDecl.ID) { for i in ast[d].impls { @@ -418,6 +466,8 @@ struct Emitter { lower(function: .init(m)!) case InitializerDecl.self: lower(initializer: .init(m)!) + case MethodDecl.self: + lower(method: .init(m)!) case SubscriptDecl.self: lower(subscript: .init(m)!) default: @@ -786,7 +836,7 @@ struct Emitter { } - /// Inserts IR for returning from current function, anchoring instructions to `s`. + /// Inserts IR for returning from current function, anchoring instructions at `s`. private mutating func emitControlFlow(return s: ReturnStmt.ID) { for f in frames.elements.reversed() { emitDeallocs(for: f, at: ast[s].site) @@ -1073,7 +1123,7 @@ struct Emitter { case .down: emitStore(downcast: e, to: storage) case .pointerConversion: - unreachable("pointer to address conversion evaluates to a lvalue") + emitStore(pointerConversion: e, to: storage) } } @@ -1111,6 +1161,21 @@ struct Emitter { UNIMPLEMENTED() } + /// Inserts the IR for storing the value of `e` to `storage`. + private mutating func emitStore(pointerConversion e: CastExpr.ID, to storage: Operand) { + let x0 = emitLValue(pointerConversion: e) + + // Consuming a pointee requires a conformance to `Movable`. + let target = RemoteType(canonical(program[e].type))! + let movable = program.ast.movableTrait + if !program.conforms(target.bareType, to: movable, in: insertionScope!) { + report(.error(module.type(of: x0).ast, doesNotConformTo: movable, at: ast[e].site)) + return + } + + emitMove([.inout, .set], x0, to: storage, at: ast[e].site) + } + /// Inserts the IR for storing the value of `e` to `storage`. private mutating func emitStore(_ e: ConditionalExpr.ID, to storage: Operand) { let (success, failure) = emitTest(condition: ast[e].condition, in: AnyScopeID(e)) @@ -1164,8 +1229,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`. @@ -1356,8 +1427,7 @@ struct Emitter { insert(module.makeStore(x2, at: x1, at: syntax.site)) } - /// Writes an instance of `Hylo.Int` with value `v` to `storage`, anchoring new instruction at - /// `site`. + /// Writes an instance of `Hylo.Int` with value `v` to `storage`. /// /// - Requires: `storage` is the address of uninitialized memory of type `Hylo.Int`. private mutating func emitStore(int v: Int, to storage: Operand, at site: SourceRange) { @@ -1366,8 +1436,7 @@ struct Emitter { insert(module.makeStore(.word(v), at: x1, at: site)) } - /// Writes an instance of `Hylo.String` with value `v` to `storage`, anchoring new instruction at - /// `site`. + /// Writes an instance of `Hylo.String` with value `v` to `storage`. /// /// - Requires: `storage` is the address of uninitialized memory of type `Hylo.String`. private mutating func emitStore(string v: String, to storage: Operand, at site: SourceRange) { @@ -1404,6 +1473,28 @@ 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 +1670,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 +1680,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), []) - - 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!) + return (.direct(f), []) - // 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 +1718,7 @@ struct Emitter { default: // Callee is a lambda. let f = emit(lambdaCallee: .init(callee)) - return (f, []) + return (.lambda(f), []) } } @@ -1655,7 +1741,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 +1758,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 +1767,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) @@ -2001,18 +2087,23 @@ struct Emitter { private mutating func emitLValue(_ e: CastExpr.ID) -> Operand { switch ast[e].direction { case .pointerConversion: - let x0 = emitLValue(ast[e].left) - let x1 = insert(module.makeAccess(.sink, from: x0, at: ast[e].site))! - let x2 = insert(module.makeLoad(x1, at: ast[e].site))! - insert(module.makeEndAccess(x1, at: ast[e].site)) - let target = RemoteType(canonical(program[e].type))! - return insert(module.makePointerToAddress(x2, to: target, at: ast[e].site))! - + return emitLValue(pointerConversion: e) default: UNIMPLEMENTED() } } + /// Inserts the IR for lvalue `e`. + private mutating func emitLValue(pointerConversion e: CastExpr.ID) -> Operand { + let x0 = emitLValue(ast[e].left) + let x1 = insert(module.makeAccess(.sink, from: x0, at: ast[e].site))! + let x2 = insert(module.makeLoad(x1, at: ast[e].site))! + insert(module.makeEndAccess(x1, at: ast[e].site)) + + let target = RemoteType(canonical(program[e].type))! + return insert(module.makePointerToAddress(x2, to: target, at: ast[e].site))! + } + /// Inserts the IR for lvalue `e`. private mutating func emitLValue(_ e: InoutExpr.ID) -> Operand { emitLValue(ast[e].subject) @@ -2034,17 +2125,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`. @@ -2111,8 +2194,8 @@ struct Emitter { UNIMPLEMENTED() } - /// Returns the address of the member declared by `d`, specialized with `specialization` and - /// bound to `receiver`, inserting IR anchored at `site`. + /// Inserts IR to return the address of the member declared by `d`, bound to `receiver`, and + /// specialized by `specialization`. private mutating func emitProperty( boundTo receiver: Operand, declaredBy d: AnyDeclID, specializedBy specialization: GenericArguments, @@ -2133,8 +2216,8 @@ struct Emitter { } } - /// Returns the projection the property declared by `d`, specialized with `specialization` and - /// bound to `receiver`, inserting IR anchored at `site`. + /// Returns the projection the property declared by `d`, bound to `receiver`, and specialized by + /// `specialization`. private mutating func emitComputedProperty( boundTo receiver: Operand, declaredByBundle d: SubscriptDecl.ID, specializedBy specialization: GenericArguments, @@ -2145,22 +2228,17 @@ 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)! } - /// Returns the projection of the property declared by `d`, specialized with `specialization` and - /// bound to `receiver`, inserting IR anchored at `site`. + /// Returns the projection of the property declared by `d`, bound to `receiver`, and specialized + /// by `specialization`. private mutating func emitComputedProperty( boundTo receiver: Operand, declaredBy d: SubscriptImpl.ID, specializedBy specialization: GenericArguments, @@ -2189,7 +2267,7 @@ struct Emitter { let predecessor = module.instruction(before: i) insertionPoint = .before(i) - emitMove([semantics], s.object, to: s.target, at: s.site) + emitMove(semantics, s.object, to: s.target, withMovableConformance: s.movable, at: s.site) module.removeInstruction(i) if let p = predecessor { @@ -2200,31 +2278,26 @@ struct Emitter { } } - /// Appends the IR for a call to move-initialize/assign `storage` with `value`, anchoring new - /// instructions at `site`. + /// Inserts IR for move-initializing/assigning `storage` with `value`. /// /// The type of `value` must a built-in or conform to `Movable` in `insertionScope`. /// /// The value of `semantics` defines the type of move to emit: - /// - `[.set]` unconditionally emits move-initialization. - /// - `[.inout]` unconditionally emits move-assignment. - /// - `[.inout, .set]` (default) emits a `move` instruction that will is later replaced during - /// definite initialization analysis by move-assignment if `storage` is found initialized or - /// move-initialization otherwise. after + /// - `[.set]` emits move-initialization. + /// - `[.inout]` emits move-assignment. + /// - `[.inout, .set]` emits a `move` instruction that will is later replaced during definite + /// initialization analysis by either move-assignment if `storage` is found initialized or + /// by move-initialization otherwise. private mutating func emitMove( _ semantics: AccessEffectSet, _ value: Operand, to storage: Operand, at site: SourceRange ) { precondition(!semantics.isEmpty && semantics.isSubset(of: [.set, .inout])) - // Built-in are always stored. let t = module.type(of: storage).ast + + // Built-in types are handled as a special case. if t.isBuiltin { - let x0 = insert(module.makeAccess(.set, from: storage, at: site))! - let x1 = insert(module.makeAccess(.sink, from: value, at: site))! - let x2 = insert(module.makeLoad(x1, at: site))! - insert(module.makeStore(x2, at: x0, at: site)) - insert(module.makeEndAccess(x1, at: site)) - insert(module.makeEndAccess(x0, at: site)) + emitMoveBuiltIn(value, to: storage, at: site) return } @@ -2232,33 +2305,58 @@ struct Emitter { let movable = program.conformance( of: t, to: program.ast.movableTrait, exposedTo: insertionScope!)! - // If the semantics of the move is known, emit a call to the corresponding move method. + // Insert a call to the approriate move implementation if its semantics is unambiguous. + // Otherwise, insert a call to the method bundle. if let k = semantics.uniqueElement { - let d = module.demandMoveOperatorDeclaration(k, from: movable) - let r = FunctionReference( - to: d, in: module, specializedBy: movable.arguments, in: insertionScope!) - let f = Operand.constant(r) - - let x0 = insert(module.makeAllocStack(.void, at: site))! - let x1 = insert(module.makeAccess(.set, from: x0, at: site))! - let x2 = insert(module.makeAccess(k, from: storage, at: site))! - let x3 = insert(module.makeAccess(.sink, from: value, at: site))! - insert(module.makeCall(applying: f, to: [x2, x3], writingResultTo: x1, at: site)) - insert(module.makeEndAccess(x3, at: site)) - insert(module.makeEndAccess(x2, at: site)) - insert(module.makeEndAccess(x1, at: site)) - insert(module.makeDeallocStack(for: x0, at: site)) - return + emitMove(k, value, to: storage, withMovableConformance: movable, at: site) + } else { + insert(module.makeMove(value, to: storage, usingConformance: movable, at: site)) } + } + + /// Implements `emitMove` for built-in types. + private mutating func emitMoveBuiltIn( + _ value: Operand, to storage: Operand, at site: SourceRange + ) { + // Built-in are always stored. + let x0 = insert(module.makeAccess(.set, from: storage, at: site))! + let x1 = insert(module.makeAccess(.sink, from: value, at: site))! + let x2 = insert(module.makeLoad(x1, at: site))! + insert(module.makeStore(x2, at: x0, at: site)) + insert(module.makeEndAccess(x1, at: site)) + insert(module.makeEndAccess(x0, at: site)) + } - // Otherwise, emit a move. - insert(module.makeMove(value, to: storage, usingConformance: movable, at: site)) + /// Inserts IR for move-initializing/assigning `storage` with `value` using `movable` to locate + /// the implementations of these operations. + /// + /// The value of `semantics` defines the type of move to emit: + /// - `.set` emits move-initialization. + /// - `.inout` emits move-assignment. + /// + /// - Requires: `storage` does not have a built-in type. + private mutating func emitMove( + _ semantics: AccessEffect, _ value: Operand, to storage: Operand, + withMovableConformance movable: Core.Conformance, at site: SourceRange + ) { + let d = module.demandMoveOperatorDeclaration(semantics, from: movable) + let f = reference(to: d, implementedFor: movable) + + let x0 = insert(module.makeAllocStack(.void, at: site))! + let x1 = insert(module.makeAccess(.set, from: x0, at: site))! + let x2 = insert(module.makeAccess(semantics, from: storage, at: site))! + let x3 = insert(module.makeAccess(.sink, from: value, at: site))! + insert(module.makeCall(applying: .constant(f), to: [x2, x3], writingResultTo: x1, at: site)) + insert(module.makeEndAccess(x3, at: site)) + insert(module.makeEndAccess(x2, at: site)) + insert(module.makeEndAccess(x1, at: site)) + insert(module.makeDeallocStack(for: x0, at: site)) } // MARK: Deinitialization /// If `storage` is deinitializable in `self.insertionScope`, inserts the IR for deinitializing - /// it, anchoring new instructions at `site`. Otherwise, reports a diagnostic. + /// it. Otherwise, reports a diagnostic. /// /// Let `T` be the type of `storage`, `storage` is deinitializable iff `T` has a deinitializer /// exposed to `self.insertionScope`. @@ -2274,7 +2372,7 @@ struct Emitter { // Use custom conformance to `Deinitializable` if possible. let concept = program.ast.deinitializableTrait if let c = module.program.conformance(of: model, to: concept, exposedTo: insertionScope!) { - emitDeinit(storage, withConformanceToDeinitializable: c, at: site) + emitDeinit(storage, withDeinitializableConformance: c, at: site) return } @@ -2282,20 +2380,19 @@ struct Emitter { report(.error(module.type(of: storage).ast, doesNotConformTo: concept, at: site)) } - /// Inserts the IR for deinitializing `storage`, using `c` to identify the deinitializer to apply - /// and anchoring new instructions at `site`. + /// Inserts the IR for deinitializing `storage`, using `deinitializable` to identify the locate + /// the deinitializer to apply. private mutating func emitDeinit( - _ storage: Operand, withConformanceToDeinitializable c: Core.Conformance, + _ storage: Operand, withDeinitializableConformance deinitializable: Core.Conformance, at site: SourceRange ) { - let d = module.demandDeinitDeclaration(from: c) - let f = Operand.constant( - FunctionReference(to: d, in: module, specializedBy: c.arguments, in: insertionScope!)) + let d = module.demandDeinitDeclaration(from: deinitializable) + let f = reference(to: d, implementedFor: deinitializable) let x0 = insert(module.makeAllocStack(.void, at: site))! let x1 = insert(module.makeAccess(.set, from: x0, at: site))! let x2 = insert(module.makeAccess(.sink, from: storage, at: site))! - insert(module.makeCall(applying: f, to: [x2], writingResultTo: x1, at: site)) + insert(module.makeCall(applying: .constant(f), to: [x2], writingResultTo: x1, at: site)) insert(module.makeEndAccess(x2, at: site)) insert(module.makeEndAccess(x1, at: site)) insert(module.makeMarkState(x0, initialized: false, at: site)) @@ -2303,8 +2400,7 @@ struct Emitter { } /// If `storage` is deinitializable in `self.insertionScope`, inserts the IR for deinitializing - /// it, anchoring new instructions at `site`. Otherwise, reports a diagnostic for each part that - /// isn't deinitializable. + /// it. Otherwise, reports a diagnostic for each part that isn't deinitializable. private mutating func emitDeinitParts(of storage: Operand, at site: SourceRange) { let t = module.type(of: storage).ast @@ -2318,8 +2414,8 @@ struct Emitter { } /// If `storage`, which stores a record, is deinitializable in `self.insertionScope`, inserts - /// the IR for deinitializing it, anchoring new instructions at `site`. Otherwise, reports a - /// diagnostic for each part that isn't deinitializable. + /// the IR for deinitializing it. Otherwise, reports a diagnostic for each part that isn't + /// deinitializable. /// /// - Requires: the type of `storage` has a record layout. private mutating func emitDeinitRecordParts(of storage: Operand, at site: SourceRange) { @@ -2342,8 +2438,8 @@ struct Emitter { } /// If `storage`, which stores a union. is deinitializable in `self.insertionScope`, inserts - /// the IR for deinitializing it, anchoring new instructions at `site`. Otherwise, reports a - /// diagnostic for each part that isn't deinitializable. + /// the IR for deinitializing it. Otherwise, reports a diagnostic for each part that isn't + /// deinitializable. /// /// - Requires: the type of `storage` is a union. private mutating func emitDeinitUnionPayload(of storage: Operand, at site: SourceRange) { @@ -2382,8 +2478,8 @@ struct Emitter { } /// If `storage`, which stores a union container holding a `payload`, is deinitializable in - /// `self.insertionScope`, inserts the IR for deinitializing it, anchoring new instructions at - /// `site`. Otherwise, reports a diagnostic for each part that isn't deinitializable. + /// `self.insertionScope`, inserts the IR for deinitializing it. Otherwise, reports a diagnostic + /// for each part that isn't deinitializable. private mutating func emitDeinitUnionPayload( of storage: Operand, containing payload: AnyType, at site: SourceRange ) { @@ -2394,6 +2490,17 @@ struct Emitter { // MARK: Helpers + /// Returns a function reference to `d`, which is an implementation that's part of `c`. + private func reference( + to d: Function.ID, implementedFor c: Core.Conformance + ) -> FunctionReference { + var a = module.specialization(in: insertionFunction!).merging(c.arguments) + if let m = program.traitMember(referredBy: d) { + a = a.merging([program[m.trait.decl].receiver: c.model]) + } + return FunctionReference(to: d, in: module, specializedBy: a, in: insertionScope!) + } + /// Returns the canonical form of `t` in the current insertion scope. private func canonical(_ t: AnyType) -> AnyType { program.canonical(t, in: insertionScope!) @@ -2407,6 +2514,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 @@ -2416,14 +2536,13 @@ struct Emitter { return s } - /// Inserts the IR for deallocating each allocation in the top frame of `self.frames`, anchoring - /// new instructions at `site`. + /// Inserts the IR for deallocating each allocation in the top frame of `self.frames`. private mutating func emitDeallocTopFrame(at site: SourceRange) { emitDeallocs(for: frames.top, at: site) frames.top.allocs.removeAll() } - /// Inserts the IR for deallocating each allocation in `f`, anchoring new instructions at `site`. + /// Inserts the IR for deallocating each allocation in `f`. private mutating func emitDeallocs(for f: Frame, at site: SourceRange) { for a in f.allocs.reversed() { if a.mayHoldCaptures { @@ -2434,7 +2553,7 @@ struct Emitter { } /// Appends the IR for computing the address of the given `subfield` of the record at - /// `recordAddress` and returns the resulting address, anchoring new instructions at `site`. + /// `recordAddress` and returns the resulting address. mutating func emitSubfieldView( _ recordAddress: Operand, at subfield: RecordPath, at site: SourceRange ) -> Operand { @@ -2443,8 +2562,7 @@ struct Emitter { return insert(s)! } - /// Emits the IR trapping iff `predicate`, which is an object of type `i1`, anchoring new - /// instructions at `site`. + /// Emits the IR trapping iff `predicate`, which is an object of type `i1`. private mutating func emitGuard(_ predicate: Operand, at site: SourceRange) { let failure = appendBlock() let success = appendBlock() @@ -2456,7 +2574,7 @@ struct Emitter { } /// Emits the IR for copying the union discriminator of `container`, which is the address of - /// a union container, anchoring new instructions at `site`. + /// a union container. private mutating func emitUnionDiscriminator( _ container: Operand, at site: SourceRange ) -> Operand { @@ -2587,12 +2705,6 @@ extension Diagnostic { .error("integer literal '\(s)' overflows when stored into '\(t)'", at: site) } - fileprivate static func error( - missingReturn returnType: AnyType, at site: SourceRange - ) -> Diagnostic { - .error("missing return in function expected to return '\(returnType)'", at: site) - } - fileprivate static func warning(unreachableStatement s: AnyStmtID, in ast: AST) -> Diagnostic { .error("statement will never be executed", at: .empty(at: ast[s].site.first())) } diff --git a/Sources/IR/FunctionID.swift b/Sources/IR/FunctionID.swift index 41e121451..e2f5676cb 100644 --- a/Sources/IR/FunctionID.swift +++ b/Sources/IR/FunctionID.swift @@ -1,4 +1,5 @@ import Core +import Utils extension Function { @@ -8,12 +9,9 @@ extension Function { /// The value of a function IR identity. public enum Value: Hashable { - /// The identity of a lowered Hylo function, initializer, or method variant. + /// The identity of a lowered function, initializer, method variant, or subscript variant. case lowered(AnyDeclID) - /// The identity of a lowered subscript variant. - case loweredSubscript(SubscriptImpl.ID) - /// The identity of a synthesized declaration. case synthesized(SynthesizedFunctionDecl) @@ -28,23 +26,19 @@ 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, SubscriptImpl.self: + self.value = .lowered(AnyDeclID(d)) + default: + unreachable() + } } /// Creates the identity of the lowered form of `s`. - public init(_ s: SynthesizedFunctionDecl) { + init(_ s: SynthesizedFunctionDecl) { self.value = .synthesized(s) } @@ -81,8 +75,6 @@ extension Function.ID: CustomStringConvertible { switch value { case .lowered(let d): return d.description - case .loweredSubscript(let d): - return d.description case .synthesized(let d): return d.description case .existentialized(let b): diff --git a/Sources/IR/Mangling/Demangler.swift b/Sources/IR/Mangling/Demangler.swift index 142967e2c..a563d6654 100644 --- a/Sources/IR/Mangling/Demangler.swift +++ b/Sources/IR/Mangling/Demangler.swift @@ -113,6 +113,12 @@ struct Demangler { return nil case .monomorphizedFunctionDecl: return nil + case .methodDecl: + return nil + case .methodImpl: + return nil + case .methodType: + return nil case .synthesizedFunctionDecl: return nil case .conformanceConstraint, .equalityConstraint, .valueConstraint, .whereClause: diff --git a/Sources/IR/Mangling/Mangler.swift b/Sources/IR/Mangling/Mangler.swift index 874cb0964..dfadc6881 100644 --- a/Sources/IR/Mangling/Mangler.swift +++ b/Sources/IR/Mangling/Mangler.swift @@ -145,6 +145,10 @@ struct Mangler { write(initializer: InitializerDecl.ID(symbol)!, to: &output) case MatchCase.self: write(anonymousScope: symbol, to: &output) + case MethodDecl.self: + write(methodDecl: MethodDecl.ID(symbol)!, to: &output) + case MethodImpl.self: + write(methodImpl: MethodImpl.ID(symbol)!, to: &output) case ModuleDecl.self: write(entity: ModuleDecl.ID(symbol)!, to: &output) case NamespaceDecl.self: @@ -238,6 +242,19 @@ struct Mangler { mangle(type: program[d].type, to: &output) } + /// Writes the mangled representation of `d` to `output`. + private mutating func write(methodDecl d: MethodDecl.ID, to output: inout Output) { + write(operator: .methodDecl, to: &output) + write(string: program.ast[d].identifier.value, to: &output) + mangle(type: program[d].type, to: &output) + } + + /// Writes the mangled representation of `d` to `output`. + private mutating func write(methodImpl d: MethodImpl.ID, to output: inout Output) { + write(operator: .methodImpl, to: &output) + write(base64Digit: program.ast[d].introducer.value, to: &output) + } + /// Writes the mangled representation of `d` to `output`. private mutating func write(subscriptDecl d: SubscriptDecl.ID, to output: inout Output) { if program.ast[d].isProperty { @@ -278,9 +295,17 @@ struct Mangler { /// Writes the mangled representation of `c` to `output`. private mutating func write(constraint c: WhereClause.ConstraintExpr, to output: inout Output) { switch c { - case .conformance, .value: + case .value: UNIMPLEMENTED() + case .conformance(let lhs, let rhs): + write(operator: .conformanceConstraint, to: &output) + mangle(type: program[lhs].type, to: &output) + write(integer: rhs.count, to: &output) + for t in rhs { + mangle(type: program[t].type, to: &output) + } + case .equality(let lhs, let rhs): write(operator: .equalityConstraint, to: &output) mangle(type: program[lhs].type, to: &output) @@ -293,8 +318,6 @@ struct Mangler { switch symbol.value { case .lowered(let d): mangle(decl: d, to: &output) - case .loweredSubscript(let d): - mangle(decl: d, to: &output) case .monomorphized(let f, let a): write(monomorphized: f, for: a, to: &output) case .synthesized(let d): @@ -383,6 +406,9 @@ struct Mangler { case let t as LambdaType: write(lambda: t, to: &output) + case let t as MethodType: + write(method: t, to: &output) + case let t as MetatypeType: write(operator: .metatypeType, to: &output) mangle(type: t.instance, to: &output) @@ -499,6 +525,21 @@ struct Mangler { mangle(type: t.output, to: &output) } + /// Writes the mangled representation of `symbol` to `output`. + private mutating func write(method t: MethodType, to output: inout Output) { + write(operator: .methodType, to: &output) + write(base64Digit: t.capabilities, to: &output) + mangle(type: t.receiver, to: &output) + + write(integer: t.inputs.count, to: &output) + for i in t.inputs { + write(string: i.label ?? "", to: &output) + mangle(type: i.type, to: &output) + } + + mangle(type: t.output, to: &output) + } + /// Writes the mangled representation of `symbol` to `output`. private mutating func write(subscriptType t: SubscriptType, to output: inout Output) { write(operator: .subscriptType, to: &output) diff --git a/Sources/IR/Mangling/ManglingOperator.swift b/Sources/IR/Mangling/ManglingOperator.swift index cd6bb5206..835805489 100644 --- a/Sources/IR/Mangling/ManglingOperator.swift +++ b/Sources/IR/Mangling/ManglingOperator.swift @@ -22,6 +22,10 @@ public enum ManglingOperator: String { case existentializedFunctionDecl = "eF" + case methodDecl = "hF" + + case methodImpl = "iF" + case monomorphizedFunctionDecl = "mF" case staticFunctionDecl = "sF" @@ -76,6 +80,8 @@ public enum ManglingOperator: String { case genericTypeParameterType = "gT" + case methodType = "hT" + case lambdaType = "lT" case metatypeType = "mT" diff --git a/Sources/IR/Module+Description.swift b/Sources/IR/Module+Description.swift index 078ad3b77..bfa9e9fda 100644 --- a/Sources/IR/Module+Description.swift +++ b/Sources/IR/Module+Description.swift @@ -82,8 +82,6 @@ extension Module: TextOutputStreamable { return "Existentialized form of '\(debugDescription(base))'" case .lowered(let d): return program.debugDescription(d) - case .loweredSubscript(let d): - return program.debugDescription(d) case .monomorphized(let base, let arguments): return "Monomorphized form of '\(debugDescription(base))' for <\(list: arguments.values)>" case .synthesized(let d): diff --git a/Sources/IR/Module.swift b/Sources/IR/Module.swift index f99bfe9f3..5d7515dd3 100644 --- a/Sources/IR/Module.swift +++ b/Sources/IR/Module.swift @@ -202,6 +202,7 @@ public struct Module { } try run({ removeDeadCode(in: $0, diagnostics: &log) }) + try run({ reifyCallsToBundles(in: $0, diagnostics: &log) }) try run({ reifyAccesses(in: $0, diagnostics: &log) }) try run({ closeBorrows(in: $0, diagnostics: &log) }) try run({ normalizeObjectStates(in: $0, diagnostics: &log) }) @@ -236,12 +237,24 @@ public struct Module { functions[identity] = function } + /// Returns the identity of the IR function corresponding to `i`. + mutating func demandDeclaration(lowering i: Core.Conformance.Implementation) -> Function.ID { + switch i { + case .concrete(let d): + return demandDeclaration(lowering: d)! + case .synthetic(let d): + return demandDeclaration(lowering: d) + } + } + /// Returns the identity of the IR function corresponding to `d`, or `nil` if `d` can't be /// lowered to an IR function. mutating func demandDeclaration(lowering d: AnyDeclID) -> Function.ID? { switch d.kind { case FunctionDecl.self: return demandDeclaration(lowering: FunctionDecl.ID(d)!) + case MethodImpl.self: + return demandDeclaration(lowering: MethodImpl.ID(d)!) case InitializerDecl.self: return demandDeclaration(lowering: InitializerDecl.ID(d)!) case SubscriptImpl.self: @@ -280,6 +293,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 +344,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) @@ -368,31 +405,22 @@ public struct Module { /// Returns the identity of the IR function implementing the deinitializer defined in /// conformance `c`. - mutating func demandDeinitDeclaration(from c: Core.Conformance) -> Function.ID { + mutating func demandDeinitDeclaration( + from c: Core.Conformance + ) -> Function.ID { let d = program.ast.deinitRequirement() - switch c.implementations[d]! { - case .concrete(let s): - return demandDeclaration(lowering: FunctionDecl.ID(s)!) - case .synthetic(let s): - return demandDeclaration(lowering: s) - } + return demandDeclaration(lowering: c.implementations[d]!) } - /// Returns the identity of the IR function implementing the `k` variant move-operator defined in - /// conformance `c`. + /// Returns the identity of the IR function implementing the `k` variant move-operation defined + /// in conformance `c`. /// /// - Requires: `k` is either `.set` or `.inout` mutating func demandMoveOperatorDeclaration( _ k: AccessEffect, from c: Core.Conformance ) -> Function.ID { let d = program.ast.moveRequirement(k) - switch c.implementations[d]! { - case .concrete: - UNIMPLEMENTED() - - case .synthetic(let s): - return demandDeclaration(lowering: s) - } + return demandDeclaration(lowering: c.implementations[d]!) } /// Returns the lowered declarations of `d`'s parameters. @@ -407,6 +435,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 +827,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/PointerToAddress.swift b/Sources/IR/Operands/Instruction/PointerToAddress.swift index 34f0d0dbd..f7e1d684c 100644 --- a/Sources/IR/Operands/Instruction/PointerToAddress.swift +++ b/Sources/IR/Operands/Instruction/PointerToAddress.swift @@ -49,14 +49,10 @@ extension Module { /// Creates a `pointer_to_address` anchored at `site` that converts `source`, which is a /// built-in pointer value, to an address of type `target`. - /// - /// - Requires: `target.access` is `.let`, `.inout`, or `.set` func makePointerToAddress( - _ source: Operand, - to target: RemoteType, - at site: SourceRange + _ source: Operand, to target: RemoteType, at site: SourceRange ) -> PointerToAddress { - precondition(AccessEffectSet([.let, .inout, .set]).contains(target.access)) + precondition(target.access != .yielded) return .init(source: source, target: target, 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) } } diff --git a/Sources/IR/Program.swift b/Sources/IR/Program.swift index d85bdb2d0..86a920424 100644 --- a/Sources/IR/Program.swift +++ b/Sources/IR/Program.swift @@ -42,8 +42,6 @@ public struct Program: Core.Program { switch f.value { case .lowered(let d): return base.module(containing: base[d].scope) - case .loweredSubscript(let d): - return base.module(containing: base[d].scope) case .monomorphized: UNIMPLEMENTED() case .existentialized(let i): diff --git a/Sources/IR/TypedProgram+Extensions.swift b/Sources/IR/TypedProgram+Extensions.swift new file mode 100644 index 000000000..6afcb5d18 --- /dev/null +++ b/Sources/IR/TypedProgram+Extensions.swift @@ -0,0 +1,21 @@ +import Core +import FrontEnd + +extension TypedProgram { + + /// If `f` refers to a trait member, returns the declaration of that member along with the trait + /// in which it is defined. Otherwise, returns `nil`. + func traitMember( + referredBy f: Function.ID + ) -> (declaration: AnyDeclID, trait: TraitType)? { + switch f.value { + case .lowered(let d): + guard let t = trait(defining: d) else { return nil } + return (declaration: d, trait: TraitType(t, ast: ast)) + + default: + return nil + } + } + +} diff --git a/Tests/EndToEndTests/TestCases/CustomMove.hylo b/Tests/EndToEndTests/TestCases/CustomMove.hylo new file mode 100644 index 000000000..0ce246712 --- /dev/null +++ b/Tests/EndToEndTests/TestCases/CustomMove.hylo @@ -0,0 +1,32 @@ +//- compileAndRun expecting: success + +type A: Deinitializable { + var witness: Int + public var x: Int + public init(x: sink Int) { + &self.x = x + &self.witness = 0 + } +} + +conformance A: Movable { + public fun take_value(from source: sink Self) -> {self: Self, Void} { + set { + &self.x = source.x + &self.witness = 0 + } + inout { + &self.x = source.x + &self.witness += 1 + } + } +} + +public fun main() { + var s = A(x: 1) + &s = A(x: 2) + &s = A(x: 2) + + precondition(s.x == 2) + precondition(s.witness == 2) +} diff --git a/Tests/EndToEndTests/TestCases/ExhaustiveBranchReturns.hylo b/Tests/EndToEndTests/TestCases/ExhaustiveBranchReturns.hylo new file mode 100644 index 000000000..4775ef85d --- /dev/null +++ b/Tests/EndToEndTests/TestCases/ExhaustiveBranchReturns.hylo @@ -0,0 +1,9 @@ +//- compileAndRun expecting: success + +public fun main() { + let _ = f() +} + +public fun f() -> Int { + if true { return 0 } else { return 1 } +} diff --git a/Tests/EndToEndTests/TestCases/MethodBundle.hylo b/Tests/EndToEndTests/TestCases/MethodBundle.hylo new file mode 100644 index 000000000..96959d646 --- /dev/null +++ b/Tests/EndToEndTests/TestCases/MethodBundle.hylo @@ -0,0 +1,20 @@ +//- compileAndRun expecting: success + +type A: Deinitializable { + + public var x: Int + + public memberwise init + + public fun foo() -> Int { + let { x.copy() } + sink { x } + } + +} + +public fun main() { + var a = A(x: 42) + precondition(a.foo() == 42) + precondition(a.foo() == 42) +} diff --git a/Tests/HyloTests/TestCases/Lowering/EmptyStruct.hylo b/Tests/HyloTests/TestCases/Lowering/EmptyStruct.hylo new file mode 100644 index 000000000..73d82fb9c --- /dev/null +++ b/Tests/HyloTests/TestCases/Lowering/EmptyStruct.hylo @@ -0,0 +1,5 @@ +//- lowerToFinishedIR expecting: success + +public type Foo { + public init() { } +} diff --git a/Tests/HyloTests/TestCases/Lowering/Identity.hylo b/Tests/HyloTests/TestCases/Lowering/Identity.hylo new file mode 100644 index 000000000..54c0871bf --- /dev/null +++ b/Tests/HyloTests/TestCases/Lowering/Identity.hylo @@ -0,0 +1,9 @@ +//- lowerToFinishedIR expecting: success + +fun identity(_ x: sink T) -> T { + return x +} + +public fun main() { + _ = identity(42) +} diff --git a/Tests/HyloTests/TestCases/Lowering/Sink.hylo b/Tests/HyloTests/TestCases/Lowering/Sink.hylo new file mode 100644 index 000000000..3cb497a11 --- /dev/null +++ b/Tests/HyloTests/TestCases/Lowering/Sink.hylo @@ -0,0 +1,9 @@ +//- lowerToFinishedIR expecting: success + +fun eat(_ x: sink T) { + _ = x +} + +public fun main() { + eat(42) +} diff --git a/Tests/LibraryTests/TestCases/ArrayTests.hylo b/Tests/LibraryTests/TestCases/ArrayTests.hylo new file mode 100644 index 000000000..a975a83b8 --- /dev/null +++ b/Tests/LibraryTests/TestCases/ArrayTests.hylo @@ -0,0 +1,22 @@ +//- compileAndRun expecting: success + +fun test_init_empty() { + var d = Array() + precondition(d.count() == 0) +} + +fun test_append() { + var d = Array() + &d.append(21) + &d.append(42) + &d.append(84) + + precondition(d[0] == 21) + precondition(d[1] == 42) + precondition(d[2] == 84) +} + +public fun main() { + test_init_empty() + test_append() +} diff --git a/Tests/LibraryTests/TestCases/PointerToMutableTests.hylo b/Tests/LibraryTests/TestCases/PointerToMutableTests.hylo index a3f7613a8..844afa613 100644 --- a/Tests/LibraryTests/TestCases/PointerToMutableTests.hylo +++ b/Tests/LibraryTests/TestCases/PointerToMutableTests.hylo @@ -24,7 +24,25 @@ fun test_advance() { precondition(!(f == e)) } +fun test_initialize_pointee_lambda() { + let p = PointerToMutable.allocate(count: 1) + p.unsafe_initialize_pointee(fun (_ i: set Int) -> Void { &i = 42 }) + let y = p.unsafe_pointee() + precondition(y == 42) + p.deallocate() +} + +fun test_initialize_pointee_direct() { + let p = PointerToMutable.allocate(count: 1) + p.unsafe_initialize_pointee(42) + let y = p.unsafe_pointee() + precondition(y == 42) + p.deallocate() +} + public fun main() { test_advance_by_bytes() test_advance() + test_initialize_pointee_lambda() + test_initialize_pointee_direct() }