From cbeeedbecce159b7fab1f36ceea20475ebd32db7 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Mon, 19 Aug 2024 14:08:14 +0200 Subject: [PATCH 01/11] Factor out common parts of the construction of synthesized functions --- Sources/IR/Emitter.swift | 191 +++++++++++++++++---------------------- 1 file changed, 82 insertions(+), 109 deletions(-) diff --git a/Sources/IR/Emitter.swift b/Sources/IR/Emitter.swift index d4978bd2b..85d0b47cc 100644 --- a/Sources/IR/Emitter.swift +++ b/Sources/IR/Emitter.swift @@ -645,15 +645,15 @@ struct Emitter { mutating func lower(synthetic d: SynthesizedFunctionDecl) { switch d.kind { case .deinitialize: - return withClearContext({ $0.lower(syntheticDeinit: d) }) + lower(syntheticDeinit: d) case .moveInitialization: - return withClearContext({ $0.lower(syntheticMoveInit: d) }) + lower(syntheticMoveInit: d) case .moveAssignment: - return withClearContext({ $0.lower(syntheticMoveAssign: d) }) + lower(syntheticMoveAssign: d) case .copy: - return withClearContext({ $0.lower(syntheticCopy: d) }) + lower(syntheticCopy: d) case .globalInitialization: - return withClearContext({ $0.lower(globalBindingInitializer: d) }) + lower(globalBindingInitializer: d) case .autoclosure: // nothing do to here; expansion is done at the caller side. break @@ -662,54 +662,34 @@ struct Emitter { /// Inserts the IR for `d`, which is a synthetic deinitializer. private mutating func lower(syntheticDeinit d: SynthesizedFunctionDecl) { - let f = module.demandDeclaration(lowering: d) - if !shouldEmitBody(of: d, loweredTo: f) { return } + withPrologue(of: d) { (me, site, entry) in + // The receiver is a sink parameter representing the object to deinitialize. + let receiver = Operand.parameter(entry, 0) + me.emitDeinitParts(of: receiver, at: site) - let site = ast[module.id].site - let entry = module.appendEntry(in: d.scope, to: f) - insertionPoint = .end(of: entry) - self.frames.push() - defer { - self.frames.pop() - assert(self.frames.isEmpty) + me.insert(me.module.makeMarkState(me.returnValue!, initialized: true, at: site)) + me.emitDeallocTopFrame(at: site) + me.insert(me.module.makeReturn(at: site)) } - - // The receiver is a sink parameter representing the object to deinitialize. - let receiver = Operand.parameter(entry, 0) - emitDeinitParts(of: receiver, at: site) - - insert(module.makeMarkState(returnValue!, initialized: true, at: site)) - emitDeallocTopFrame(at: site) - insert(module.makeReturn(at: site)) } /// Inserts the IR for `d`, which is a synthetic move initialization method. private mutating func lower(syntheticMoveInit d: SynthesizedFunctionDecl) { - let f = module.demandDeclaration(lowering: d) - if !shouldEmitBody(of: d, loweredTo: f) { return } - - let site = ast[module.id].site - let entry = module.appendEntry(in: d.scope, to: f) - insertionPoint = .end(of: entry) - self.frames.push() - defer { - self.frames.pop() - assert(self.frames.isEmpty) - } - - let receiver = Operand.parameter(entry, 0) - let argument = Operand.parameter(entry, 1) - let object = module.type(of: receiver).ast + withPrologue(of: d) { (me, site, entry) in + let receiver = Operand.parameter(entry, 0) + let argument = Operand.parameter(entry, 1) + let object = me.module.type(of: receiver).ast + + if object.hasRecordLayout { + me.emitMoveInitRecordParts(of: receiver, consuming: argument, at: site) + } else if object.base is UnionType { + me.emitMoveInitUnionPayload(of: receiver, consuming: argument, at: site) + } - if object.hasRecordLayout { - emitMoveInitRecordParts(of: receiver, consuming: argument, at: site) - } else if object.base is UnionType { - emitMoveInitUnionPayload(of: receiver, consuming: argument, at: site) + me.insert(me.module.makeMarkState(me.returnValue!, initialized: true, at: site)) + me.emitDeallocTopFrame(at: site) + me.insert(me.module.makeReturn(at: site)) } - - insert(module.makeMarkState(returnValue!, initialized: true, at: site)) - emitDeallocTopFrame(at: site) - insert(module.makeReturn(at: site)) } /// Inserts the IR for initializing the stored parts of `receiver`, which stores a record, @@ -799,57 +779,61 @@ struct Emitter { /// Inserts the IR for `d`, which is a synthetic move initialization method. private mutating func lower(syntheticMoveAssign d: SynthesizedFunctionDecl) { - let f = module.demandDeclaration(lowering: d) - if !shouldEmitBody(of: d, loweredTo: f) { return } + withPrologue(of: d) { (me, site, entry) in + let receiver = Operand.parameter(entry, 0) + let argument = Operand.parameter(entry, 1) - let site = ast[module.id].site - let entry = module.appendEntry(in: d.scope, to: f) - insertionPoint = .end(of: entry) - self.frames.push() - defer { - self.frames.pop() - assert(self.frames.isEmpty) - } - - let receiver = Operand.parameter(entry, 0) - let argument = Operand.parameter(entry, 1) + // Deinitialize the receiver. + me.emitDeinit(receiver, at: site) - // Deinitialize the receiver. - emitDeinit(receiver, at: site) - - // Apply the move-initializer. - emitMove([.set], argument, to: receiver, at: site) - insert(module.makeMarkState(returnValue!, initialized: true, at: site)) - emitDeallocTopFrame(at: site) - insert(module.makeReturn(at: site)) + // Apply the move-initializer. + me.emitMove([.set], argument, to: receiver, at: site) + me.insert(me.module.makeMarkState(me.returnValue!, initialized: true, at: site)) + me.emitDeallocTopFrame(at: site) + me.insert(me.module.makeReturn(at: site)) + } } /// Inserts the IR for `d`, which is a synthetic copy method. private mutating func lower(syntheticCopy d: SynthesizedFunctionDecl) { - let f = module.demandDeclaration(lowering: d) - if !shouldEmitBody(of: d, loweredTo: f) { return } + withPrologue(of: d) { (me, site, entry) in + let source = Operand.parameter(entry, 0) + let target = Operand.parameter(entry, 1) + let object = me.module.type(of: source).ast + + if object.hasRecordLayout { + me.emitCopyRecordParts(from: source, to: target, at: site) + } else if object.base is UnionType { + me.emitCopyUnionPayload(from: source, to: target, at: site) + } - let site = ast[module.id].site - let entry = module.appendEntry(in: d.scope, to: f) - insertionPoint = .end(of: entry) - self.frames.push() - defer { - self.frames.pop() - assert(self.frames.isEmpty) + me.emitDeallocTopFrame(at: site) + me.insert(me.module.makeReturn(at: site)) } + } - let source = Operand.parameter(entry, 0) - let target = Operand.parameter(entry, 1) - let object = module.type(of: source).ast - - if object.hasRecordLayout { - emitCopyRecordParts(from: source, to: target, at: site) - } else if object.base is UnionType { - emitCopyUnionPayload(from: source, to: target, at: site) + /// Declares `d` in the current module and returns its corresponding identifier, calls `action` + /// to generate its implementation if it should be emitted the current module. + @discardableResult + private mutating func withPrologue( + of d: SynthesizedFunctionDecl, + _ action: (inout Self, _ site: SourceRange, _ entry: Block.ID) -> Void + ) -> Function.ID { + withClearContext { (me) in + let f = me.module.demandDeclaration(lowering: d) + if me.shouldEmitBody(of: d, loweredTo: f) { + let site = me.ast[me.module.id].site + let entry = me.module.appendEntry(in: d.scope, to: f) + me.insertionPoint = .end(of: entry) + me.frames.push() + + action(&me, site, entry) + + me.frames.pop() + assert(me.frames.isEmpty) + } + return f } - - emitDeallocTopFrame(at: site) - insert(module.makeReturn(at: site)) } /// Inserts the IR for copying the stored parts of `source`, which stores a record, to `target` @@ -927,31 +911,20 @@ struct Emitter { /// the lowered function. @discardableResult private mutating func lower(globalBindingInitializer d: SynthesizedFunctionDecl) -> Function.ID { - let f = module.demandDeclaration(lowering: d) - if !shouldEmitBody(of: d, loweredTo: f) { - return f - } + withPrologue(of: d) { (me, _, entry) in + let storage = Operand.parameter(entry, 0) + guard case .globalInitialization(let binding) = d.kind else { unreachable() } - let entry = module.appendEntry(in: d.scope, to: f) - insertionPoint = .end(of: entry) - self.frames.push() - defer { - self.frames.pop() - assert(self.frames.isEmpty) - } - - let storage = Operand.parameter(entry, 0) - guard case .globalInitialization(let binding) = d.kind else { unreachable() } - let initializer = program[binding].initializer! + let initializer = me.program[binding].initializer! + let site = me.program[initializer].site - emitInitStoredLocalBindings( - in: program[binding].pattern.subpattern, referringTo: [], relativeTo: storage, - consuming: initializer) - insert(module.makeMarkState(returnValue!, initialized: true, at: program[initializer].site)) - emitDeallocTopFrame(at: program[initializer].site) - insert(module.makeReturn(at: program[initializer].site)) - - return f + me.emitInitStoredLocalBindings( + in: me.program[binding].pattern.subpattern, referringTo: [], relativeTo: storage, + consuming: initializer) + me.insert(me.module.makeMarkState(me.returnValue!, initialized: true, at: site)) + me.emitDeallocTopFrame(at: site) + me.insert(me.module.makeReturn(at: site)) + } } private mutating func lower(syntheticAutoclosure d: SynthesizedFunctionDecl) -> Function.ID { From 1cb5df907c957bfcae1f02776404052dec42573d Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Tue, 20 Aug 2024 00:02:12 +0200 Subject: [PATCH 02/11] Add a helper to store Boolean values in the IR --- Sources/IR/Emitter.swift | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Sources/IR/Emitter.swift b/Sources/IR/Emitter.swift index 85d0b47cc..d13551b18 100644 --- a/Sources/IR/Emitter.swift +++ b/Sources/IR/Emitter.swift @@ -1405,9 +1405,7 @@ struct Emitter { /// Inserts the IR for storing the value of `e` to `storage`. private mutating func emitStore(_ e: BooleanLiteralExpr.ID, to storage: Operand) { - let x0 = emitSubfieldView(storage, at: [0], at: ast[e].site) - let x1 = insert(module.makeAccess(.set, from: x0, at: ast[e].site))! - insert(module.makeStore(.i1(ast[e].value), at: x1, at: ast[e].site)) + emitStore(boolean: ast[e].value, to: storage, at: ast[e].site) } /// Inserts the IR for storing the value of `e` to `storage`. @@ -1806,6 +1804,16 @@ struct Emitter { insert(module.makeStore(x2, at: x1, at: syntax.site)) } + /// Writes an instance of `Hylo.Bool` with value `v` to `storage`. + /// + /// - Requires: `storage` is the address of uninitialized memory of type `Hylo.Int`. + private mutating func emitStore(boolean v: Bool, to storage: Operand, at site: SourceRange) { + let x0 = emitSubfieldView(storage, at: [0], at: site) + let x1 = insert(module.makeAccess(.set, from: x0, at: site))! + insert(module.makeStore(.i1(v), at: x1, at: site)) + insert(module.makeEndAccess(x1, 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`. From 5e4b6895de31da59728c1d80a3120e3ac1febbd7 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Tue, 20 Aug 2024 00:03:11 +0200 Subject: [PATCH 03/11] Factor our the lowering of a member function call --- Sources/IR/Emitter.swift | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/Sources/IR/Emitter.swift b/Sources/IR/Emitter.swift index d13551b18..c99c1b791 100644 --- a/Sources/IR/Emitter.swift +++ b/Sources/IR/Emitter.swift @@ -1552,17 +1552,9 @@ struct Emitter { usingExplicit: ast[e].arguments, synthesizingDefaultAt: .empty(at: ast[e].site.end)) let m = ast.isMarkedForMutation(ast[e].callee) let (callee, captures) = emitFunctionCallee(ast[e].callee, markedForMutation: m) - let inputs = captures + arguments // Call is evaluated last. - switch callee { - case .direct(let r): - emitApply(.constant(r), to: inputs, writingResultTo: storage, at: ast[e].site) - case .lambda(let r): - emitApply(r, to: inputs, writingResultTo: storage, at: ast[e].site) - case .bundle(let r): - emitApply(r, to: inputs, writingResultTo: storage, at: ast[e].site) - } + emitApply(callee, to: captures + arguments, writingResultTo: storage, at: ast[e].site) } /// Inserts the IR for storing the value of `e` to `storage`. @@ -1853,6 +1845,21 @@ struct Emitter { frames.top.setMayHoldCaptures(s) } + /// Inserts the IR for calling `callee` on `arguments`, storing the result to `storage`. + private mutating func emitApply( + _ callee: Callee, to arguments: [Operand], + writingResultTo storage: Operand, at site: SourceRange + ) { + switch callee { + case .direct(let r): + emitApply(.constant(r), to: arguments, writingResultTo: storage, at: site) + case .lambda(let r): + emitApply(r, to: arguments, writingResultTo: storage, at: site) + case .bundle(let r): + emitApply(r, to: arguments, writingResultTo: storage, at: site) + } + } + /// Inserts the IR for calling `callee` on `arguments`, storing the result to `storage`. private mutating func emitApply( _ callee: Operand, to arguments: [Operand], From 4de368a4c7deb8fa39075af5f71801820de9d583 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Tue, 20 Aug 2024 00:04:27 +0200 Subject: [PATCH 04/11] Refactor the lowering of infix function calls --- Sources/IR/Emitter.swift | 46 ++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/Sources/IR/Emitter.swift b/Sources/IR/Emitter.swift index c99c1b791..6c2b82b10 100644 --- a/Sources/IR/Emitter.swift +++ b/Sources/IR/Emitter.swift @@ -1644,8 +1644,8 @@ struct Emitter { private mutating func emitStore(_ e: FoldedSequenceExpr, to storage: Operand) { switch e { case .infix(let callee, let lhs, let rhs): - let t = program[callee.expr].type - let calleeType = ArrowType(canonical(t))!.lifted + let t = canonical(program[callee.expr].type) + let calleeType = ArrowType(t)!.lifted // Emit the operands, starting with RHS. let r = emit(infixOperand: rhs, passed: ParameterType(calleeType.inputs[1].type)!.access) @@ -1656,13 +1656,13 @@ struct Emitter { unreachable() } - let o = FunctionReference(to: d, in: &module, specializedBy: a, in: insertionScope!) - let f = Operand.constant(o) - - // Emit the call. - let site = ast.site(of: e) - let result = insert(module.makeAccess(.set, from: storage, at: site))! - insert(module.makeCall(applying: f, to: [l, r], writingResultTo: result, at: site)) + let site = program[callee.expr].site + let lhsIsMarkedForMutation = program.ast.isMarkedForMutation(lhs) + let (callee, captures) = emitMemberFunctionCallee( + referringTo: d, memberOf: l, markedForMutation: lhsIsMarkedForMutation, + specializedBy: a, in: program[callee.expr].scope, + at: site) + emitApply(callee, to: captures + [r], writingResultTo: storage, at: site) case .leaf(let v): emitStore(value: v, to: storage) @@ -2165,23 +2165,37 @@ struct Emitter { ) -> (callee: Callee, captures: [Operand]) { guard case .member(let d, let a, let s) = program[callee].referredDecl else { unreachable() } - let receiver = emitLValue(receiver: s, at: ast[callee].site) - let receiverType = module.type(of: receiver).ast + let r = emitLValue(receiver: s, at: ast[callee].site) + return emitMemberFunctionCallee( + referringTo: d, memberOf: r, markedForMutation: isMutating, + specializedBy: a, in:program[callee].scope, + at: program[callee].site) + } - let available = receiverCapabilities(program[callee].type) + /// Inserts the IR constructing the callee of a call referring to `d`, which is a member function + /// of `r`, returning the callee's value along with the call receiver. + /// + /// The callee is marked for mutation iff `isMutating` is `true`, in which case the receiver is + /// accessed with a `set` or `inout` capability. + private mutating func emitMemberFunctionCallee( + referringTo d: AnyDeclID, memberOf r: Operand, markedForMutation isMutating: Bool, + specializedBy a: GenericArguments, in scopeOfUse: AnyScopeID, + at site: SourceRange + ) -> (callee: Callee, captures: [Operand]) { + let available = receiverCapabilities(program[d].type) var requested = available.intersection(.forUseOfBundle(performingInPlaceMutation: isMutating)) // TODO: Should report an error when available is `let|sink` and requested is `inout/set` requested = requested.isEmpty ? available : requested let entityToCall = module.memberCallee( - referringTo: d, memberOf: receiverType, accessedWith: requested, - specializedBy: a, usedIn: program[callee].scope) + referringTo: d, memberOf: module.type(of: r).ast, accessedWith: requested, + specializedBy: a, usedIn: scopeOfUse) if case .bundle(let b) = entityToCall { - return emitMethodBundleCallee(referringTo: b, on: receiver, at: program[callee].site) + return emitMethodBundleCallee(referringTo: b, on: r, at: site) } else { - let c = insert(module.makeAccess(requested, from: receiver, at: program[callee].site))! + let c = insert(module.makeAccess(requested, from: r, at: site))! return (callee: entityToCall, captures: [c]) } } From a828d5624172d0924af9e487a8e4e8cddb44d517 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Tue, 20 Aug 2024 00:04:49 +0200 Subject: [PATCH 05/11] Improve documentation --- Sources/IR/Emitter.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Sources/IR/Emitter.swift b/Sources/IR/Emitter.swift index 6c2b82b10..b5c982bc0 100644 --- a/Sources/IR/Emitter.swift +++ b/Sources/IR/Emitter.swift @@ -2107,7 +2107,7 @@ struct Emitter { } } - /// Inserts the IR for given `callee`, which is marked for mutation if `isMutating` is `true`, + /// Inserts the IR for given `callee`, which is marked for mutation iff `isMutating` is `true`, /// and returns the callee's value along with its lifted arguments. /// /// Lifted arguments correspond to the captures of the `callee`, which are additional parameters @@ -2158,8 +2158,11 @@ struct Emitter { } } - /// Inserts the IR evaluating `callee`, which refers to a member function marked for mutation - /// iff `isMutating` is `true`, returning the callee's value along with the call receiver. + /// Inserts the IR evaluating `callee`, which refers to a member function, returning the callee's + /// value along with the call receiver. + /// + /// The callee is marked for mutation iff `isMutating` is `true`, in which case the receiver is + /// accessed with a `set` or `inout` capability. private mutating func emitMemberFunctionCallee( _ callee: NameExpr.ID, markedForMutation isMutating: Bool ) -> (callee: Callee, captures: [Operand]) { From 8d60550d5b446415615bee205e9334495e3ad542 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Tue, 20 Aug 2024 14:40:44 +0200 Subject: [PATCH 06/11] Handle Boolean literals in cast expressions --- Sources/IR/Emitter.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/IR/Emitter.swift b/Sources/IR/Emitter.swift index b5c982bc0..9fff3d611 100644 --- a/Sources/IR/Emitter.swift +++ b/Sources/IR/Emitter.swift @@ -2644,6 +2644,8 @@ struct Emitter { /// Inserts the IR for lvalue `e`. private mutating func emitLValue(upcast e: CastExpr.ID) -> Operand { switch ast[e].left.kind { + case BooleanLiteralExpr.self: + return emitStore(value: ast[e].left) case FloatLiteralExpr.self: return emitStore(value: ast[e].left) case IntegerLiteralExpr.self: From 6245b94998910bd7d94231de9adedb01481380a3 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Tue, 20 Aug 2024 14:41:23 +0200 Subject: [PATCH 07/11] Add an alias for the type of union discriminators --- Sources/FrontEnd/Types/BuiltinType.swift | 3 +++ Sources/IR/Operands/Instruction/UnionDiscriminator.swift | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/FrontEnd/Types/BuiltinType.swift b/Sources/FrontEnd/Types/BuiltinType.swift index b229da345..ac4404407 100644 --- a/Sources/FrontEnd/Types/BuiltinType.swift +++ b/Sources/FrontEnd/Types/BuiltinType.swift @@ -28,6 +28,9 @@ public enum BuiltinType: TypeProtocol { /// The type of the built-in module. case module + /// The type of a union discriminator. + public static let discriminator = word + /// `true` iff `self` is `.i` or `.word`. public var isInteger: Bool { switch self { diff --git a/Sources/IR/Operands/Instruction/UnionDiscriminator.swift b/Sources/IR/Operands/Instruction/UnionDiscriminator.swift index 90b35e998..a2b30b66b 100644 --- a/Sources/IR/Operands/Instruction/UnionDiscriminator.swift +++ b/Sources/IR/Operands/Instruction/UnionDiscriminator.swift @@ -16,7 +16,7 @@ public struct UnionDiscriminator: Instruction { } public var result: IR.`Type`? { - .object(BuiltinType.word) + .object(BuiltinType.discriminator) } public var operands: [Operand] { From 554f167d438267374618e9932c40d4b08e91362a Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Tue, 20 Aug 2024 14:42:41 +0200 Subject: [PATCH 08/11] Implement equality operator synthesization --- Sources/FrontEnd/AST/AST.swift | 2 + .../AST/Decl/SynthesizedFunctionDecl.swift | 3 + Sources/IR/Emitter.swift | 132 ++++++++++++++++++ Sources/IR/Mangling/Mangler.swift | 6 +- Sources/IR/Module.swift | 10 ++ 5 files changed, 151 insertions(+), 2 deletions(-) diff --git a/Sources/FrontEnd/AST/AST.swift b/Sources/FrontEnd/AST/AST.swift index 1f9952440..a7a18f0ca 100644 --- a/Sources/FrontEnd/AST/AST.swift +++ b/Sources/FrontEnd/AST/AST.swift @@ -313,6 +313,8 @@ public struct AST { return .moveAssignment case core.copyable.copy.rawValue: return .copy + case core.equatable.equal.rawValue: + return .equal default: return nil } diff --git a/Sources/FrontEnd/AST/Decl/SynthesizedFunctionDecl.swift b/Sources/FrontEnd/AST/Decl/SynthesizedFunctionDecl.swift index d0c785930..495aa313d 100644 --- a/Sources/FrontEnd/AST/Decl/SynthesizedFunctionDecl.swift +++ b/Sources/FrontEnd/AST/Decl/SynthesizedFunctionDecl.swift @@ -18,6 +18,9 @@ public struct SynthesizedFunctionDecl: Hashable { /// A copy method. case copy + /// An equality method. + case equal + /// A global initializer for a binding declaration. case globalInitialization(BindingDecl.ID) diff --git a/Sources/IR/Emitter.swift b/Sources/IR/Emitter.swift index 9fff3d611..dfb2692e0 100644 --- a/Sources/IR/Emitter.swift +++ b/Sources/IR/Emitter.swift @@ -652,6 +652,8 @@ struct Emitter { lower(syntheticMoveAssign: d) case .copy: lower(syntheticCopy: d) + case .equal: + lower(syntheticEqual: d) case .globalInitialization: lower(globalBindingInitializer: d) case .autoclosure: @@ -812,6 +814,26 @@ struct Emitter { } } + /// Inserts the ID for `d`, which is an equality operator. + private mutating func lower(syntheticEqual d: SynthesizedFunctionDecl) { + withPrologue(of: d) { (me, site, entry) in + let lhs = Operand.parameter(entry, 0) + let rhs = Operand.parameter(entry, 1) + let t = me.module.type(of: lhs).ast + + if t.hasRecordLayout { + me.emitStorePartsEquality(lhs, rhs, to: me.returnValue!, at: site) + } else if t.base is UnionType { + me.emitStoreUnionPayloadEquality(lhs, rhs, to: me.returnValue!, at: site) + } else { + UNIMPLEMENTED("synthetic equality for type '\(t)'") + } + + me.emitDeallocTopFrame(at: site) + me.insert(me.module.makeReturn(at: site)) + } + } + /// Declares `d` in the current module and returns its corresponding identifier, calls `action` /// to generate its implementation if it should be emitted the current module. @discardableResult @@ -3093,6 +3115,116 @@ struct Emitter { insert(module.makeCloseUnion(x0, at: site)) } + // MARK: Equality + + private mutating func emitStoreEquality( + _ lhs: Operand, _ rhs: Operand, to target: Operand, at site: SourceRange + ) { + let m = module.type(of: lhs).ast + let d = program.ast.core.equatable.type + + if let equatable = program.conformance(of: m, to: d, exposedTo: insertionScope!) { + let d = module.demandEqualDeclaration(definedBy: equatable) + let f = module.reference(to: d, implementedFor: equatable) + + let x0 = insert(module.makeAccess(.set, from: target, at: site))! + let x1 = insert(module.makeAccess(.let, from: lhs, at: site))! + let x2 = insert(module.makeAccess(.let, from: rhs, at: site))! + insert(module.makeCall(applying: .constant(f), to: [x1, x2], writingResultTo: x0, at: site)) + insert(module.makeEndAccess(x2, at: site)) + insert(module.makeEndAccess(x1, at: site)) + insert(module.makeEndAccess(x0, at: site)) + } else { + report(.error(m, doesNotConformTo: d, at: site)) + } + } + + /// Inserts the IR writing in `target` whether the parts of `lhs` and `rhs` are pairwise equal. + private mutating func emitStorePartsEquality( + _ lhs: Operand, _ rhs: Operand, + to target: Operand, at site: SourceRange + ) { + let layout = AbstractTypeLayout( + of: module.type(of: lhs).ast, definedIn: module.program) + + // If the object is empty, return true. + var parts = layout.properties[...] + if parts.isEmpty { + emitStore(boolean: true, to: target, at: site) + return + } + + // Otherwise, compare all parts pairwise. + let tail = appendBlock() + while !parts.isEmpty { + let x0 = emitSubfieldView(lhs, at: [parts.startIndex], at: site) + let x1 = emitSubfieldView(rhs, at: [parts.startIndex], at: site) + emitStoreEquality(x0, x1, to: target, at: site) + + parts = parts.dropFirst() + if parts.isEmpty { + insert(module.makeBranch(to: tail, at: site)) + insertionPoint = .end(of: tail) + } else { + let x2 = emitLoadBuiltinBool(target, at: site) + let next = appendBlock() + insert(module.makeCondBranch(if: x2, then: next, else: tail, at: site)) + insertionPoint = .end(of: next) + } + } + } + + /// Inserts the IR writing in `target` whether the payloads of `lhs` and `rhs` are equal. + private mutating func emitStoreUnionPayloadEquality( + _ lhs: Operand, _ rhs: Operand, + to target: Operand, at site: SourceRange + ) { + let union = UnionType(module.type(of: lhs).ast)! + + // If the union is empty, return true. + if union.elements.isEmpty { + emitStore(boolean: true, to: target, at: site) + return + } + + // Otherwise, compare their payloads. + let elements = program.discriminatorToElement(in: union) + + let same = appendBlock() + var successors: [Block.ID] = [] + for _ in elements { + successors.append(appendBlock()) + } + let fail = appendBlock() + let tail = appendBlock() + + // The success blocks compare discriminators and then payloads. + let dl = emitUnionDiscriminator(lhs, at: site) + let dr = emitUnionDiscriminator(rhs, at: site) + let x0 = insert(module.makeLLVM(applying: .icmp(.eq, .discriminator), to: [dl, dr], at: site))! + insert(module.makeCondBranch(if: x0, then: same, else: fail, at: site)) + + insertionPoint = .end(of: same) + insert(module.makeSwitch(on: dl, toOneOf: successors, at: site)) + for i in 0 ..< elements.count { + insertionPoint = .end(of: successors[i]) + let y0 = insert(module.makeOpenUnion(lhs, as: elements[i], at: site))! + let y1 = insert(module.makeOpenUnion(rhs, as: elements[i], at: site))! + emitStoreEquality(y0, y1, to: target, at: site) + insert(module.makeCloseUnion(y1, at: site)) + insert(module.makeCloseUnion(y0, at: site)) + insert(module.makeBranch(to: tail, at: site)) + } + + // The failure block writes `false` to the return storage. + insertionPoint = .end(of: fail) + emitStore(boolean: false, to: target, at: site) + insert(module.makeBranch(to: tail, at: site)) + + // The tail block represents the continuation. + insertionPoint = .end(of: tail) + } + // MARK: Helpers /// Returns the canonical form of `t` in the current insertion scope. diff --git a/Sources/IR/Mangling/Mangler.swift b/Sources/IR/Mangling/Mangler.swift index c9a895e6b..7e7e3ae2d 100644 --- a/Sources/IR/Mangling/Mangler.swift +++ b/Sources/IR/Mangling/Mangler.swift @@ -379,11 +379,13 @@ struct Mangler { write(base64Didit: 2, to: &output) case .copy: write(base64Didit: 3, to: &output) - case .globalInitialization(let d): + case .equal: write(base64Didit: 4, to: &output) + case .globalInitialization(let d): + write(base64Didit: 5, to: &output) write(entity: d, to: &output) case .autoclosure(let e): - write(base64Didit: 5, to: &output) + write(base64Didit: 6, to: &output) // To allow using multiple autoclosures in the same scope, also write the expression ID. write(integer: Int(e.rawValue.bits), to: &output) } diff --git a/Sources/IR/Module.swift b/Sources/IR/Module.swift index de45a4335..cfc98e648 100644 --- a/Sources/IR/Module.swift +++ b/Sources/IR/Module.swift @@ -518,6 +518,16 @@ public struct Module { return demandDeclaration(lowering: conformanceToCopyable.implementations[d]!) } + /// Returns the IR function implementing the operator defined in `conformanceToEquatable`. + /// + /// - Parameter conformanceToEquatable: A conformance to `Equatable`. + mutating func demandEqualDeclaration( + definedBy conformanceToEquatable: FrontEnd.Conformance + ) -> Function.ID { + let d = program.ast.core.equatable.equal + return demandDeclaration(lowering: conformanceToEquatable.implementations[d]!) + } + /// Returns a function reference to the implementation of the requirement `r` in `witness`. /// /// - Requires: `r` identifies a function or subscript requirement in the trait for which From 6881b72ff0fa041057d1dc4f78e92d9b48d6147d Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Tue, 20 Aug 2024 14:43:28 +0200 Subject: [PATCH 09/11] Simplify function call --- Sources/IR/Emitter.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/IR/Emitter.swift b/Sources/IR/Emitter.swift index dfb2692e0..ff17742a3 100644 --- a/Sources/IR/Emitter.swift +++ b/Sources/IR/Emitter.swift @@ -3001,7 +3001,7 @@ struct Emitter { } else if m.isBuiltinOrRawTuple { insert(module.makeMarkState(storage, initialized: false, at: site)) } else { - report(.error(module.type(of: storage).ast, doesNotConformTo: d, at: site)) + report(.error(m, doesNotConformTo: d, at: site)) } } From 7318b8d31476fc1ddf0cef423dcd268205df6ff0 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Tue, 20 Aug 2024 14:43:44 +0200 Subject: [PATCH 10/11] Test synthetic equality --- .../TestCases/SyntheticEquality.hylo | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 Tests/EndToEndTests/TestCases/SyntheticEquality.hylo diff --git a/Tests/EndToEndTests/TestCases/SyntheticEquality.hylo b/Tests/EndToEndTests/TestCases/SyntheticEquality.hylo new file mode 100644 index 000000000..78408699a --- /dev/null +++ b/Tests/EndToEndTests/TestCases/SyntheticEquality.hylo @@ -0,0 +1,28 @@ +//- compileAndRun expecting: .success + +type A: Regular { + public let x: Int + public let y: Int + public memberwise init +} + +type B: Regular { + public let a: A + public memberwise init +} + +typealias IntOrBool = Union + +conformance IntOrBool: Equatable {} + +public fun main() { + precondition(B(a: A(x: 1, y: 2)) == B(a: A(x: 1, y: 2))) + precondition(B(a: A(x: 1, y: 2)) != B(a: A(x: 2, y: 1))) + + let n: IntOrBool = 42 as _ + let m: IntOrBool = 1337 as _ + let b: IntOrBool = true as _ + precondition(n == n) + precondition(n != m) // inequal payloads + precondition(n != b) // inequal discriminators +} From 37425939709dd5d33c251952821feed90435ee94 Mon Sep 17 00:00:00 2001 From: Dimi Racordon Date: Tue, 20 Aug 2024 14:43:54 +0200 Subject: [PATCH 11/11] Remove needless comment --- Sources/FrontEnd/AST/AST.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/FrontEnd/AST/AST.swift b/Sources/FrontEnd/AST/AST.swift index a7a18f0ca..57ee86d38 100644 --- a/Sources/FrontEnd/AST/AST.swift +++ b/Sources/FrontEnd/AST/AST.swift @@ -303,7 +303,6 @@ public struct AST { /// Returns the kind identifying synthesized declarations of `requirement`, or `nil` if /// `requirement` is not synthesizable. public func synthesizedKind(of requirement: T) -> SynthesizedFunctionDecl.Kind? { - // If the requirement is defined in `Deinitializable`, it must be the deinitialization method. switch requirement.rawValue { case core.deinitializable.deinitialize.rawValue: return .deinitialize