diff --git a/Sources/CodeGen/LLVM/LLVMProgram.swift b/Sources/CodeGen/LLVM/LLVMProgram.swift index 54b90ee93..b2d57f2a4 100644 --- a/Sources/CodeGen/LLVM/LLVMProgram.swift +++ b/Sources/CodeGen/LLVM/LLVMProgram.swift @@ -55,7 +55,9 @@ public struct LLVMProgram { /// to `directory`, returning the URL of each written file. /// /// - Returns: The URL of each written product, one for each module in `self`. - public func write(_ type: SwiftyLLVM.CodeGenerationResultType, to directory: URL) throws -> [URL] { + public func write( + _ type: SwiftyLLVM.CodeGenerationResultType, to directory: URL + ) throws -> [URL] { precondition(directory.hasDirectoryPath) var result: [URL] = [] for m in llvmModules.values { diff --git a/Sources/CodeGen/LLVM/Transpilation.swift b/Sources/CodeGen/LLVM/Transpilation.swift index ba3ea58c8..b31383170 100644 --- a/Sources/CodeGen/LLVM/Transpilation.swift +++ b/Sources/CodeGen/LLVM/Transpilation.swift @@ -322,7 +322,8 @@ extension SwiftyLLVM.Module { if !implementations.isEmpty { tableContents.append( SwiftyLLVM.ArrayConstant( - of: SwiftyLLVM.StructType([word(), ptr], in: &self), containing: implementations, in: &self)) + of: SwiftyLLVM.StructType([word(), ptr], in: &self), containing: implementations, + in: &self)) } let table = SwiftyLLVM.StructConstant(aggregating: tableContents, in: &self) @@ -927,42 +928,48 @@ extension SwiftyLLVM.Module { let r = llvm(s.operands[1]) let f = intrinsic( named: Intrinsic.llvm.sadd.with.overflow, for: [ir.llvm(builtinType: t, in: &self)])! - register[.register(i)] = insertCall(SwiftyLLVM.Function(f)!, on: [l, r], at: insertionPoint) + register[.register(i)] = insertCall( + SwiftyLLVM.Function(f)!, on: [l, r], at: insertionPoint) case .unsignedAdditionWithOverflow(let t): let l = llvm(s.operands[0]) let r = llvm(s.operands[1]) let f = intrinsic( named: Intrinsic.llvm.uadd.with.overflow, for: [ir.llvm(builtinType: t, in: &self)])! - register[.register(i)] = insertCall(SwiftyLLVM.Function(f)!, on: [l, r], at: insertionPoint) + register[.register(i)] = insertCall( + SwiftyLLVM.Function(f)!, on: [l, r], at: insertionPoint) case .signedSubtractionWithOverflow(let t): let l = llvm(s.operands[0]) let r = llvm(s.operands[1]) let f = intrinsic( named: Intrinsic.llvm.ssub.with.overflow, for: [ir.llvm(builtinType: t, in: &self)])! - register[.register(i)] = insertCall(SwiftyLLVM.Function(f)!, on: [l, r], at: insertionPoint) + register[.register(i)] = insertCall( + SwiftyLLVM.Function(f)!, on: [l, r], at: insertionPoint) case .unsignedSubtractionWithOverflow(let t): let l = llvm(s.operands[0]) let r = llvm(s.operands[1]) let f = intrinsic( named: Intrinsic.llvm.usub.with.overflow, for: [ir.llvm(builtinType: t, in: &self)])! - register[.register(i)] = insertCall(SwiftyLLVM.Function(f)!, on: [l, r], at: insertionPoint) + register[.register(i)] = insertCall( + SwiftyLLVM.Function(f)!, on: [l, r], at: insertionPoint) case .signedMultiplicationWithOverflow(let t): let l = llvm(s.operands[0]) let r = llvm(s.operands[1]) let f = intrinsic( named: Intrinsic.llvm.smul.with.overflow, for: [ir.llvm(builtinType: t, in: &self)])! - register[.register(i)] = insertCall(SwiftyLLVM.Function(f)!, on: [l, r], at: insertionPoint) + register[.register(i)] = insertCall( + SwiftyLLVM.Function(f)!, on: [l, r], at: insertionPoint) case .unsignedMultiplicationWithOverflow(let t): let l = llvm(s.operands[0]) let r = llvm(s.operands[1]) let f = intrinsic( named: Intrinsic.llvm.umul.with.overflow, for: [ir.llvm(builtinType: t, in: &self)])! - register[.register(i)] = insertCall(SwiftyLLVM.Function(f)!, on: [l, r], at: insertionPoint) + register[.register(i)] = insertCall( + SwiftyLLVM.Function(f)!, on: [l, r], at: insertionPoint) case .icmp(let p, _): let l = llvm(s.operands[0]) @@ -1046,7 +1053,8 @@ extension SwiftyLLVM.Module { case .ctpop(let t): let source = llvm(s.operands[0]) let f = intrinsic(named: Intrinsic.llvm.ctpop, for: [ir.llvm(builtinType: t, in: &self)])! - register[.register(i)] = insertCall(SwiftyLLVM.Function(f)!, on: [source], at: insertionPoint) + register[.register(i)] = insertCall( + SwiftyLLVM.Function(f)!, on: [source], at: insertionPoint) case .ctlz(let t): let source = llvm(s.operands[0]) diff --git a/Sources/CodeGen/LLVM/TypeLowering.swift b/Sources/CodeGen/LLVM/TypeLowering.swift index 4c640c389..83c1602a4 100644 --- a/Sources/CodeGen/LLVM/TypeLowering.swift +++ b/Sources/CodeGen/LLVM/TypeLowering.swift @@ -82,7 +82,9 @@ extension IR.Program { /// Returns the LLVM form of `val` in `module`. /// /// - Requires: `val` is representable in LLVM. - func llvm(boundGenericType val: BoundGenericType, in module: inout SwiftyLLVM.Module) -> SwiftyLLVM.IRType { + func llvm( + boundGenericType val: BoundGenericType, in module: inout SwiftyLLVM.Module + ) -> SwiftyLLVM.IRType { precondition(val[.isCanonical]) let fields = base.storage(of: val.base).map { (part) in diff --git a/Sources/Driver/Driver.swift b/Sources/Driver/Driver.swift index 713db97c6..5e123f79a 100644 --- a/Sources/Driver/Driver.swift +++ b/Sources/Driver/Driver.swift @@ -3,8 +3,8 @@ import CodeGenLLVM import Foundation import FrontEnd import IR -import SwiftyLLVM import StandardLibrary +import SwiftyLLVM import Utils public struct Driver: ParsableCommand { diff --git a/Sources/FrontEnd/AST/Stmt/ConditionalCompilationStmt.swift b/Sources/FrontEnd/AST/Stmt/ConditionalCompilationStmt.swift index b186ed1ae..e74ec3620 100644 --- a/Sources/FrontEnd/AST/Stmt/ConditionalCompilationStmt.swift +++ b/Sources/FrontEnd/AST/Stmt/ConditionalCompilationStmt.swift @@ -52,6 +52,12 @@ public struct ConditionalCompilationStmt: Stmt { /// Holds iff the payload doesn't. case not(Condition) + /// Holds iff both conditions in the payload do. + case and(Condition, Condition) + + /// Holds iff either condition in the payload does. + case or(Condition, Condition) + /// `true` iff the body of the conditional-compilation shouldn't be parsed. public var mayNotNeedParsing: Bool { switch self { @@ -63,6 +69,10 @@ public struct ConditionalCompilationStmt: Stmt { return true case .not(let c): return c.mayNotNeedParsing + case .and(let l, let r): + return l.mayNotNeedParsing && r.mayNotNeedParsing + case .or(let l, let r): + return l.mayNotNeedParsing || r.mayNotNeedParsing default: return false } @@ -89,6 +99,10 @@ public struct ConditionalCompilationStmt: Stmt { return comparison.evaluate(for: factors.hyloVersion) case .not(let c): return !c.holds(for: factors) + case .and(let l, let r): + return l.holds(for: factors) && r.holds(for: factors) + case .or(let l, let r): + return l.holds(for: factors) || r.holds(for: factors) } } diff --git a/Sources/FrontEnd/BuiltinFunction.swift b/Sources/FrontEnd/BuiltinFunction.swift index 7ca0db7f9..976d3e4f6 100644 --- a/Sources/FrontEnd/BuiltinFunction.swift +++ b/Sources/FrontEnd/BuiltinFunction.swift @@ -352,7 +352,9 @@ private func mathFlags(_ stream: inout ArraySlice) -> NativeInstructi } /// Returns an overflow behavior parsed from `stream` or `.ignore` if none can be parsed. -private func overflowBehavior(_ stream: inout ArraySlice) -> SwiftyLLVM.OverflowBehavior { +private func overflowBehavior( + _ stream: inout ArraySlice +) -> SwiftyLLVM.OverflowBehavior { switch stream.first { case "nuw": stream.removeFirst() diff --git a/Sources/FrontEnd/Parse/Parser.swift b/Sources/FrontEnd/Parse/Parser.swift index 94a833b85..9c975999d 100644 --- a/Sources/FrontEnd/Parse/Parser.swift +++ b/Sources/FrontEnd/Parse/Parser.swift @@ -2986,9 +2986,74 @@ public enum Parser { static let compilerConditionStmt = Apply(parseCompilerConditionStmt(in:)) - private static func parseCompilerConditionStmt(in state: inout ParserState) throws -> AnyStmtID? { - let head = state.take(.poundIf) - return head != nil ? try parseCompilerConditionTail(head: head!, in: &state) : nil + private static func parseCompilerConditionStmt( + in state: inout ParserState + ) throws -> AnyStmtID? { + try state.take(.poundIf).map { (head) in + try parseCompilerConditionTail(head: head, in: &state) + } + } + + /// Parses a logical connective from `state`. + private static func parseConnective(in state: inout ParserState) -> Connective? { + // Next token must be an operator. + guard let t = state.peek(), t.kind == .oper else { return nil } + + // The value of the token must be either `||` or `&&`. + var r: Connective + switch t.site.text { + case "||": + r = .disjunction + case "&&": + r = .conjunction + default: + return nil + } + + // Consume the token and "succeed". + _ = state.take() + return r + } + + /// Parses a `Condition` for a `ConditionalCompilationStmt`. + private static func parseCondition( + in state: inout ParserState + ) throws -> ConditionalCompilationStmt.Condition { + return try condition(withInfixConnectiveStrongerOrEqualTo: .disjunction, in: &state) + + /// Parses a condition as a proposition, a negation, or an infix sentence whose operator has + /// a precedence at least as strong as `p`. + func condition( + withInfixConnectiveStrongerOrEqualTo p: Connective, + in state: inout ParserState + ) throws -> ConditionalCompilationStmt.Condition { + var lhs = try parseCompilerCondition(in: &state) + + while true { + // Tentatively parse a connective. + let backup = state.backup() + guard let c = parseConnective(in: &state) else { return lhs } + + // Backtrack if the connective we got hasn't strong enough precedence. + if (c.rawValue < p.rawValue) { + state.restore(from: backup) + return lhs + } + + // If we parsed `||` the RHS must be a conjunction. Otherwise it must be a proposition or + // negation. In either case we'll come back here to parse the remainder of the expression. + switch c { + case .disjunction: + let rhs = try condition(withInfixConnectiveStrongerOrEqualTo: .conjunction, in: &state) + lhs = .or(lhs, rhs) + case .conjunction: + let rhs = try parseCompilerCondition(in: &state) + lhs = .and(lhs, rhs) + } + } + + return lhs + } } /// Parses a compiler condition structure, after the initial token (#if or #elseif). @@ -2996,7 +3061,7 @@ public enum Parser { head: Token, in state: inout ParserState ) throws -> AnyStmtID { // Parse the condition. - let condition = try parseCompilerCondition(in: &state) + let condition = try parseCondition(in: &state) // Parse the body of the compiler condition. let stmts: [AnyStmtID] @@ -3007,28 +3072,28 @@ public enum Parser { stmts = try parseConditionalCompilationBranch(in: &state) } - // The next token may be #endif, #else or #elseif. + // Parse the other branch(es) of the condition. let fallback: [AnyStmtID] - if state.take(.poundEndif) != nil { + + switch state.peek()?.kind { + case .poundEndif: fallback = [] - } else if state.take(.poundElse) != nil { - if condition.mayNotNeedParsing && condition.holds(for: state.ast.compilationConditions) { - try skipConditionalCompilationBranch(in: &state, stoppingAtElse: false) - fallback = [] - } else { - fallback = try parseConditionalCompilationBranch(in: &state) - } - // Expect #endif. _ = try state.expect("'#endif'", using: { $0.take(.poundEndif) }) - } else if let head2 = state.take(.poundElseif) { + + case .poundElse, .poundElseif: + let h = state.take()! if condition.mayNotNeedParsing && condition.holds(for: state.ast.compilationConditions) { try skipConditionalCompilationBranch(in: &state, stoppingAtElse: false) fallback = [] - } else { - // We continue with another conditional compilation statement. - fallback = [try parseCompilerConditionTail(head: head2, in: &state)] + _ = try state.expect("'#endif'", using: { $0.take(.poundEndif) }) + } else if h.kind == .poundElse { + fallback = try parseConditionalCompilationBranch(in: &state) + _ = try state.expect("'#endif'", using: { $0.take(.poundEndif) }) + } else{ + fallback = [try parseCompilerConditionTail(head: h, in: &state)] } - } else { + + default: try fail(.error(expected: "statement, #endif, #else or #elseif", at: state.currentLocation)) } @@ -3488,6 +3553,17 @@ struct ParameterInterface { } +/// A conjunction (`&&`) or disjunction (`||`) operator. +enum Connective: Int { + + /// The logical disjunction. + case disjunction + + /// The logical conjunction. + case conjunction + +} + /// A combinator that parses tokens with a specific kind. struct TakeKind: Combinator { diff --git a/Sources/GenerateHyloFileTests/GenerateHyloFileTests.swift b/Sources/GenerateHyloFileTests/GenerateHyloFileTests.swift index b7149faa1..3f2ec4f61 100644 --- a/Sources/GenerateHyloFileTests/GenerateHyloFileTests.swift +++ b/Sources/GenerateHyloFileTests/GenerateHyloFileTests.swift @@ -192,7 +192,7 @@ fileprivate struct TestArgument: CustomStringConvertible { } /// Information necessary to generate a test case. -fileprivate struct TestDescription { +private struct TestDescription { /// The name of the method implementing the logic of the test runner. let methodName: TestMethod diff --git a/Tests/EndToEndTests/TestCases/ConditionalCompilation.hylo b/Tests/EndToEndTests/TestCases/ConditionalCompilation.hylo index e3d6060a0..aa9f7839a 100644 --- a/Tests/EndToEndTests/TestCases/ConditionalCompilation.hylo +++ b/Tests/EndToEndTests/TestCases/ConditionalCompilation.hylo @@ -169,6 +169,30 @@ fun testNot() { use(b) } +fun testMultipleOperandConditions() { + var n = 0 + + #if compiler_version(>= 0.0) + &n += 1 + #else if true + + #endif + + #if os(macOS) + &n += 1 + #elseif os(macOS) && !true + &n += 1 + #elseif os(macOS) && false || !true || false + &n += 1 + #elseif os(macOS) || compiler_version(< 1.0) + &n += 1 + #else + &n += 1 + #endif + + use(n) +} + public fun main() { testTrue() testFalse() @@ -180,4 +204,5 @@ public fun main() { testHyloVersion() testSkipParsingNested() testNot() + testMultipleOperandConditions() } diff --git a/Tests/HyloTests/ParserTests.swift b/Tests/HyloTests/ParserTests.swift index 907520e38..cf9418a47 100644 --- a/Tests/HyloTests/ParserTests.swift +++ b/Tests/HyloTests/ParserTests.swift @@ -1710,10 +1710,12 @@ final class ParserTests: XCTestCase { XCTAssertEqual(stmt.condition, .operatingSystem("macOs")) XCTAssertEqual(stmt.stmts.count, 1) XCTAssertEqual(stmt.fallback.count, 1) + let stmt2 = try XCTUnwrap(ast[stmt.fallback[0]] as? ConditionalCompilationStmt) XCTAssertEqual(stmt2.condition, .operatingSystem("Linux")) XCTAssertEqual(stmt2.stmts.count, 1) XCTAssertEqual(stmt2.fallback.count, 1) + let stmt3 = try XCTUnwrap(ast[stmt2.fallback[0]] as? ConditionalCompilationStmt) XCTAssertEqual(stmt3.condition, .operatingSystem("Windows")) XCTAssertEqual(stmt3.stmts.count, 1) @@ -1728,14 +1730,17 @@ final class ParserTests: XCTestCase { XCTAssertEqual(stmt.condition, .architecture("x86_64")) XCTAssertEqual(stmt.stmts.count, 1) XCTAssertEqual(stmt.fallback.count, 1) + let stmt2 = try XCTUnwrap(ast[stmt.fallback[0]] as? ConditionalCompilationStmt) XCTAssertEqual(stmt2.condition, .architecture("i386")) XCTAssertEqual(stmt2.stmts.count, 1) XCTAssertEqual(stmt2.fallback.count, 1) + let stmt3 = try XCTUnwrap(ast[stmt2.fallback[0]] as? ConditionalCompilationStmt) XCTAssertEqual(stmt3.condition, .architecture("arm64")) XCTAssertEqual(stmt3.stmts.count, 1) XCTAssertEqual(stmt3.fallback.count, 1) + let stmt4 = try XCTUnwrap(ast[stmt3.fallback[0]] as? ConditionalCompilationStmt) XCTAssertEqual(stmt4.condition, .architecture("arm")) XCTAssertEqual(stmt4.stmts.count, 1) @@ -1851,6 +1856,7 @@ final class ParserTests: XCTestCase { .hyloVersion(comparison: .less(SemanticVersion(major: 0, minor: 1, patch: 0)))) XCTAssertEqual(stmt.stmts.count, 0) // Body not parsed XCTAssertEqual(stmt.fallback.count, 1) + let stmt2 = try XCTUnwrap(ast[stmt.fallback[0]] as? ConditionalCompilationStmt) XCTAssertEqual(stmt2.condition, .operatingSystem("bla")) XCTAssertEqual(stmt2.stmts.count, 0) @@ -1890,6 +1896,7 @@ final class ParserTests: XCTestCase { // all good } } + func testConditionalControlNotOperatorOnFalse() throws { let input: SourceFile = "#if !os(abracadabra) foo() #endif" let (stmtID, ast) = try apply(Parser.stmt, on: input) @@ -1905,6 +1912,7 @@ final class ParserTests: XCTestCase { // We should expand to nothing. XCTAssertEqual(stmt.expansion(for: ConditionalCompilationFactors()).count, 0) } + func testConditionalControlNotNot() throws { let input: SourceFile = "#if ! !true foo() #endif" let (stmtID, ast) = try apply(Parser.stmt, on: input) @@ -1912,6 +1920,7 @@ final class ParserTests: XCTestCase { // We should expand to the body. XCTAssertEqual(stmt.expansion(for: ConditionalCompilationFactors()).count, 1) } + func testConditionalControlSkipParsingAfterNot() throws { let input: SourceFile = "#if !compiler_version(< 0.1) foo() #else #endif" let (stmtID, ast) = try apply(Parser.stmt, on: input) @@ -1920,6 +1929,26 @@ final class ParserTests: XCTestCase { XCTAssertEqual(stmt.fallback.count, 0) // don't parse the #else part } + func testConditionalControlInfix() throws { + let input = SourceFile.diagnosableLiteral( + """ + #if os(macOS) || os(Linux) && hylo_version(< 1.0.0) || os(Windows) + do_something() + #endif + """) + let (stmtID, ast) = try apply(Parser.stmt, on: input) + let stmt = try XCTUnwrap(ast[stmtID] as? ConditionalCompilationStmt) + XCTAssertEqual( + stmt.condition, + .or( + .or( + .operatingSystem("macOS"), + .and( + .operatingSystem("Linux"), + .hyloVersion(comparison: .less(SemanticVersion(major: 1, minor: 0, patch: 0))))), + .operatingSystem("Windows"))) + } + // MARK: Operators func testTakeOperator() throws {