Skip to content

Commit

Permalink
Merge pull request #1547 from hylo-lang/Mtalymph-1410
Browse files Browse the repository at this point in the history
Add && and || operators to compile-time compilation conditions
  • Loading branch information
kyouko-taiga authored Aug 3, 2024
2 parents 4362ffc + 68dff8f commit 5fb6cfe
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 32 deletions.
4 changes: 3 additions & 1 deletion Sources/CodeGen/LLVM/LLVMProgram.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
24 changes: 16 additions & 8 deletions Sources/CodeGen/LLVM/Transpilation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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])
Expand Down
4 changes: 3 additions & 1 deletion Sources/CodeGen/LLVM/TypeLowering.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Sources/Driver/Driver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import CodeGenLLVM
import Foundation
import FrontEnd
import IR
import SwiftyLLVM
import StandardLibrary
import SwiftyLLVM
import Utils

public struct Driver: ParsableCommand {
Expand Down
14 changes: 14 additions & 0 deletions Sources/FrontEnd/AST/Stmt/ConditionalCompilationStmt.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
Expand All @@ -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)
}
}

Expand Down
4 changes: 3 additions & 1 deletion Sources/FrontEnd/BuiltinFunction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,9 @@ private func mathFlags(_ stream: inout ArraySlice<Substring>) -> NativeInstructi
}

/// Returns an overflow behavior parsed from `stream` or `.ignore` if none can be parsed.
private func overflowBehavior(_ stream: inout ArraySlice<Substring>) -> SwiftyLLVM.OverflowBehavior {
private func overflowBehavior(
_ stream: inout ArraySlice<Substring>
) -> SwiftyLLVM.OverflowBehavior {
switch stream.first {
case "nuw":
stream.removeFirst()
Expand Down
114 changes: 95 additions & 19 deletions Sources/FrontEnd/Parse/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2986,17 +2986,82 @@ 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).
private static func parseCompilerConditionTail(
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]
Expand All @@ -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))
}

Expand Down Expand Up @@ -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 {

Expand Down
2 changes: 1 addition & 1 deletion Sources/GenerateHyloFileTests/GenerateHyloFileTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions Tests/EndToEndTests/TestCases/ConditionalCompilation.hylo
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,30 @@ fun testNot() {
use(b)
}

fun testMultipleOperandConditions() {
var n = 0

#if compiler_version(>= 0.0)
&n += 1
#else if true
<something that would not be parsed>
#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()
Expand All @@ -180,4 +204,5 @@ public fun main() {
testHyloVersion()
testSkipParsingNested()
testNot()
testMultipleOperandConditions()
}
Loading

0 comments on commit 5fb6cfe

Please sign in to comment.