mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
650 lines
22 KiB
Swift
650 lines
22 KiB
Swift
// RUN: %empty-directory(%t)
|
|
// RUN: %swift_driver %s >%t/constant_folded_fp_operation_validation.swift
|
|
|
|
// RUN: %target-swift-frontend -emit-sil -O -suppress-warnings -verify %t/constant_folded_fp_operation_validation.swift 2>&1 | %FileCheck --check-prefix=CHECK-SIL %t/constant_folded_fp_operation_validation.swift
|
|
// RUN: %target-build-swift -O -suppress-warnings %t/constant_folded_fp_operation_validation.swift -o %t/a.out
|
|
// RUN: %target-codesign %t/a.out
|
|
// RUN: %target-run %t/a.out
|
|
|
|
// REQUIRES: executable_test,optimized_stdlib
|
|
// REQUIRES: swift_in_compiler
|
|
|
|
// Note: This code is not the testfile itself but generates the testfile in the %t directory.
|
|
|
|
createTestFile()
|
|
|
|
func createTestFile() {
|
|
generateOperandAccessors()
|
|
|
|
let cmpOpValidator = FPConstantFoldedComparisonOpsValidator()
|
|
cmpOpValidator.generateOptimizedFuncDecls()
|
|
cmpOpValidator.generateUnoptimizedFuncDecls()
|
|
cmpOpValidator.generateComparisonFuncDecls()
|
|
cmpOpValidator.generateComparisonFuncCalls()
|
|
|
|
let arithOpValidator = FPConstantFoldedArithmeticOpsValidator()
|
|
arithOpValidator.generateOptimizedFuncDecls()
|
|
arithOpValidator.generateUnoptimizedFuncDecls()
|
|
arithOpValidator.generateComparisonFuncDecls()
|
|
arithOpValidator.generateComparisonFuncCalls()
|
|
}
|
|
|
|
/////////////////// Protocols ///////////////////
|
|
|
|
/// Implemented by types that have distinct ways of
|
|
/// being represented in an expression context vs all
|
|
/// other contexts.
|
|
protocol Representable {
|
|
func math_name() -> String
|
|
func printable_name() -> String
|
|
}
|
|
|
|
/// An rough estimation of a type that needs to validate
|
|
/// optimized floating point operations in Swift.
|
|
///
|
|
/// Any new validator should conform to this protocol
|
|
/// and then call the generate* functions to generate
|
|
/// corresponding test file code.
|
|
protocol FPOptimizedOpsValidator {
|
|
associatedtype FPType: Representable
|
|
associatedtype FPOperation: Representable
|
|
associatedtype FPOperand: Representable
|
|
|
|
func generateOptimizedFuncDecls()
|
|
func generateUnoptimizedFuncDecls()
|
|
func generateComparisonFuncDecls()
|
|
func generateComparisonFuncCalls()
|
|
|
|
func optimizedFunDeclCheck(fpType: FPType, op: FPOperation, op1: FPOperand, op2: FPOperand) -> String
|
|
func unoptimizedFunDeclCheck(fpType: FPType, op: FPOperation, op1: FPOperand, op2: FPOperand) -> String
|
|
func resultComparisonCheck(fpType: FPType, op: FPOperation, op1: FPOperand, op2: FPOperand) -> String
|
|
}
|
|
|
|
////////////////// Common ///////////////////
|
|
|
|
// Generates accessors for floating point operands
|
|
// commonly used in the tests.
|
|
//
|
|
// These accessors prevent the unoptimized function versions
|
|
// from getting optimized by the mandatory optimization passes
|
|
// and returning a constant value.
|
|
func generateOperandAccessors() {
|
|
print("""
|
|
@inline(never) @_silgen_name("zero_float") @_optimize(none)
|
|
func zero_float() -> Float {
|
|
return 0.0
|
|
}
|
|
|
|
@inline(never) @_silgen_name("one_float") @_optimize(none)
|
|
func one_float() -> Float {
|
|
return 1.0
|
|
}
|
|
|
|
@inline(never) @_silgen_name("zero_double") @_optimize(none)
|
|
func zero_double() -> Double {
|
|
return 0.0
|
|
}
|
|
|
|
@inline(never) @_silgen_name("one_double") @_optimize(none)
|
|
func one_double() -> Double {
|
|
return 1.0
|
|
}
|
|
""")
|
|
}
|
|
|
|
////////////////// Comparison Operations Validator ///////////////////
|
|
|
|
struct FPConstantFoldedComparisonOpsValidator: FPOptimizedOpsValidator {
|
|
/////////////////////// TYPES ///////////////////////
|
|
|
|
/// Type of floating points this validator deals with.
|
|
enum _FpType : CaseIterable, Representable, Equatable {
|
|
case Float
|
|
case Double
|
|
|
|
func math_name() -> String {
|
|
switch self {
|
|
case .Float:
|
|
return "Float"
|
|
case .Double:
|
|
return "Double"
|
|
}
|
|
}
|
|
|
|
func printable_name() -> String {
|
|
switch self {
|
|
case .Float:
|
|
return "float"
|
|
case .Double:
|
|
return "double"
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Type of floating point operations this validator deals with.
|
|
enum _FPOperation : CaseIterable, Representable, Equatable {
|
|
case LessThan
|
|
case GreaterThan
|
|
case LessThanOrEqual
|
|
case GreaterThanOrEqual
|
|
case Equal
|
|
case NotEqual
|
|
|
|
func math_name() -> String {
|
|
switch self {
|
|
case .LessThan:
|
|
return "<"
|
|
case .GreaterThan:
|
|
return ">"
|
|
case .LessThanOrEqual:
|
|
return "<="
|
|
case .GreaterThanOrEqual:
|
|
return ">="
|
|
case .Equal:
|
|
return "=="
|
|
case .NotEqual:
|
|
return "!="
|
|
}
|
|
}
|
|
|
|
func printable_name() -> String {
|
|
switch self {
|
|
case .LessThan:
|
|
return "lessThan"
|
|
case .GreaterThan:
|
|
return "greaterThan"
|
|
case .LessThanOrEqual:
|
|
return "lessThanOrEqual"
|
|
case .GreaterThanOrEqual:
|
|
return "greaterThanOrEqual"
|
|
case .Equal:
|
|
return "equal"
|
|
case .NotEqual:
|
|
return "notEqual"
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Type of floating point operands this validator deals with.
|
|
enum _FPOperand : CaseIterable, Representable, Equatable {
|
|
case Zero
|
|
case One
|
|
case Infinity
|
|
case Nan
|
|
|
|
func isSpecial() -> Bool {
|
|
switch self {
|
|
case .Zero:
|
|
return false
|
|
case .One:
|
|
return false
|
|
case .Infinity:
|
|
return true
|
|
case .Nan:
|
|
return true
|
|
}
|
|
}
|
|
|
|
func math_name() -> String {
|
|
switch self {
|
|
case .Zero:
|
|
return "0.0"
|
|
case .One:
|
|
return "1.0"
|
|
case .Infinity:
|
|
return "infinity"
|
|
case .Nan:
|
|
return "nan"
|
|
}
|
|
}
|
|
|
|
func printable_name() -> String {
|
|
switch self {
|
|
case .Zero:
|
|
return "zero"
|
|
case .One:
|
|
return "one"
|
|
case .Infinity:
|
|
return "infinity"
|
|
case .Nan:
|
|
return "nan"
|
|
}
|
|
}
|
|
}
|
|
|
|
private let optPrefix = "opt"
|
|
private let unoptPrefix = "unopt"
|
|
|
|
/////////////////////// FPOptimizedOpsValidator Conformances ///////////////////////
|
|
typealias FPType = _FpType
|
|
typealias FPOperation = _FPOperation
|
|
typealias FPOperand = _FPOperand
|
|
|
|
func optimizedFunDeclCheck(
|
|
fpType: FPType,
|
|
op: FPOperation,
|
|
op1: FPOperand,
|
|
op2: FPOperand
|
|
) -> String {
|
|
let funcName = [optPrefix, fpType.printable_name(), op.printable_name(), op1.printable_name(), op2.printable_name()].joined(separator: "_")
|
|
|
|
return """
|
|
// CHECK-SIL-LABEL: sil hidden [noinline] @\(funcName)
|
|
// CHECK-SIL-NEXT: [global: ]
|
|
// CHECK-SIL-NEXT: bb0:
|
|
// CHECK-SIL-NOT: {{.*}}fcmp{{.*}}
|
|
// CHECK-SIL: } // end sil function '\(funcName)'
|
|
"""
|
|
}
|
|
|
|
func unoptimizedFunDeclCheck(
|
|
fpType: FPType,
|
|
op: FPOperation,
|
|
op1: FPOperand,
|
|
op2: FPOperand
|
|
) -> String {
|
|
let funcName = [unoptPrefix, fpType.printable_name(), op.printable_name(), op1.printable_name(), op2.printable_name()].joined(separator: "_")
|
|
|
|
return """
|
|
// CHECK-SIL-LABEL: sil hidden [noinline] [Onone] @\(funcName)
|
|
// CHECK-SIL-NEXT: bb0:
|
|
// CHECK-SIL: {{.*}}fcmp{{.*}}
|
|
// CHECK-SIL: } // end sil function '\(funcName)'
|
|
"""
|
|
}
|
|
|
|
func resultComparisonCheck(
|
|
fpType: FPType,
|
|
op: FPOperation,
|
|
op1: FPOperand,
|
|
op2: FPOperand
|
|
) -> String {
|
|
let comparisonFuncName = ["comparison", fpType.printable_name(), op.printable_name(), op1.printable_name(), op2.printable_name()].joined(separator: "_")
|
|
|
|
return """
|
|
precondition(\(comparisonFuncName)(), "\(comparisonFuncName) returned false!")
|
|
"""
|
|
}
|
|
|
|
func generateOptimizedFuncDecls() {
|
|
for fpType in FPType.allCases {
|
|
for op in FPOperation.allCases {
|
|
for op1 in FPOperand.allCases {
|
|
for op2 in FPOperand.allCases {
|
|
if checkIfEqCmpBetweenInfAndNonInf(op: op, op1: op1, op2: op2) {
|
|
continue
|
|
}
|
|
generateFuncDeclWithCheckDirectives(fpType: fpType, op: op, op1: op1, op2: op2, isopt: true)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func generateUnoptimizedFuncDecls() {
|
|
for fpType in FPType.allCases {
|
|
for op in FPOperation.allCases {
|
|
for op1 in FPOperand.allCases {
|
|
for op2 in FPOperand.allCases {
|
|
if checkIfEqCmpBetweenInfAndNonInf(op: op, op1: op1, op2: op2) {
|
|
continue
|
|
}
|
|
generateFuncDeclWithCheckDirectives(fpType: fpType, op: op, op1: op1, op2: op2, isopt: false)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func generateComparisonFuncDecls() {
|
|
for fpType in FPType.allCases {
|
|
for op in FPOperation.allCases {
|
|
for op1 in FPOperand.allCases {
|
|
for op2 in FPOperand.allCases {
|
|
if checkIfEqCmpBetweenInfAndNonInf(op: op, op1: op1, op2: op2) {
|
|
continue
|
|
}
|
|
let comparisonFuncName = ["comparison", fpType.printable_name(), op.printable_name(), op1.printable_name(), op2.printable_name()].joined(separator: "_")
|
|
let optFuncName = [optPrefix, fpType.printable_name(), op.printable_name(), op1.printable_name(), op2.printable_name()].joined(separator: "_")
|
|
let unoptFuncName = [unoptPrefix, fpType.printable_name(), op.printable_name(), op1.printable_name(), op2.printable_name()].joined(separator: "_")
|
|
|
|
print("""
|
|
@inline(never) @_silgen_name("\(comparisonFuncName)") @_optimize(none)
|
|
func \(comparisonFuncName)() -> Bool {
|
|
return \(optFuncName)() == \(unoptFuncName)()
|
|
}
|
|
|
|
""")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func generateComparisonFuncCalls() {
|
|
for fpType in FPType.allCases {
|
|
for op in FPOperation.allCases {
|
|
for op1 in FPOperand.allCases {
|
|
for op2 in FPOperand.allCases {
|
|
if checkIfEqCmpBetweenInfAndNonInf(op: op, op1: op1, op2: op2) {
|
|
continue
|
|
}
|
|
let comparison = resultComparisonCheck(fpType: fpType, op: op, op1: op1, op2: op2)
|
|
|
|
print("""
|
|
\(comparison)
|
|
|
|
""")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////// Utilities ///////////////////////
|
|
private func generateFuncDeclWithCheckDirectives(
|
|
fpType: FPType,
|
|
op: FPOperation,
|
|
op1: FPOperand,
|
|
op2: FPOperand,
|
|
isopt: Bool = false
|
|
) {
|
|
var operand1 = op1.isSpecial() ? fpType.math_name() + "." + op1.math_name() : op1.math_name()
|
|
var operand2 = op2.isSpecial() ? fpType.math_name() + "." + op2.math_name() : op2.math_name()
|
|
|
|
if !isopt {
|
|
if !op1.isSpecial() {
|
|
if fpType == .Double {
|
|
operand1 = op1 == .Zero ? "zero_double()": "one_double()"
|
|
} else {
|
|
operand1 = op1 == .Zero ? "zero_float()": "one_float()"
|
|
}
|
|
}
|
|
if !op2.isSpecial() {
|
|
if fpType == .Double {
|
|
operand2 = op2 == .Zero ? "zero_double()": "one_double()"
|
|
} else {
|
|
operand2 = op2 == .Zero ? "zero_float()": "one_float()"
|
|
}
|
|
}
|
|
}
|
|
|
|
let optPrefix = isopt ? optPrefix : unoptPrefix
|
|
let optAttr = isopt ? "" : "@_optimize(none)"
|
|
let funcName = [optPrefix, fpType.printable_name(), op.printable_name(), op1.printable_name(), op2.printable_name()].joined(separator: "_")
|
|
let checkDirectives = isopt ?
|
|
optimizedFunDeclCheck(fpType: fpType, op: op, op1: op1, op2: op2) :
|
|
unoptimizedFunDeclCheck(fpType: fpType, op: op, op1: op1, op2: op2)
|
|
|
|
print("""
|
|
@inline(never) @_silgen_name("\(funcName)") \(optAttr)
|
|
func \(funcName)() -> Bool {
|
|
return \(operand1) \(op.math_name()) \(operand2)
|
|
}
|
|
\(checkDirectives)
|
|
|
|
""")
|
|
}
|
|
|
|
// Equality comparisons b/w infinity and non-infinity are not constant folded.
|
|
// In such comparisons, special floating point types - Float80 and Float16, may
|
|
// come into play and pattern matching against them complicates the constant folding
|
|
// logic more than we'd like.
|
|
private func checkIfEqCmpBetweenInfAndNonInf(op: FPOperation, op1: FPOperand, op2: FPOperand) -> Bool {
|
|
if op == .Equal || op == .NotEqual {
|
|
// If only one of the operands is infinity
|
|
if (op1 == .Infinity || op2 == .Infinity) && !(op1 == .Infinity && op2 == .Infinity) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
////////////////// Arithmetic Operations Validator ///////////////////
|
|
|
|
struct FPConstantFoldedArithmeticOpsValidator: FPOptimizedOpsValidator {
|
|
/// Type of floating points this validator deals with.
|
|
enum _FpType : CaseIterable, Representable, Equatable {
|
|
case Float
|
|
case Double
|
|
|
|
func math_name() -> String {
|
|
switch self {
|
|
case .Float:
|
|
return "Float"
|
|
case .Double:
|
|
return "Double"
|
|
}
|
|
}
|
|
|
|
func printable_name() -> String {
|
|
switch self {
|
|
case .Float:
|
|
return "float"
|
|
case .Double:
|
|
return "double"
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Type of floating point operations this validator deals with.
|
|
enum _FPOperation : CaseIterable, Representable, Equatable {
|
|
case Add
|
|
case Subtract
|
|
case Multiply
|
|
case Divide
|
|
|
|
func math_name() -> String {
|
|
switch self {
|
|
case .Add:
|
|
return "+"
|
|
case .Subtract:
|
|
return "-"
|
|
case .Multiply:
|
|
return "*"
|
|
case .Divide:
|
|
return "/"
|
|
}
|
|
}
|
|
|
|
func printable_name() -> String {
|
|
switch self {
|
|
case .Add:
|
|
return "add"
|
|
case .Subtract:
|
|
return "sub"
|
|
case .Multiply:
|
|
return "mul"
|
|
case .Divide:
|
|
return "div"
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Type of floating point operands this validator deals with.
|
|
enum _FPOperand : CaseIterable, Representable, Equatable {
|
|
case Zero
|
|
case One
|
|
|
|
func math_name() -> String {
|
|
switch self {
|
|
case .Zero:
|
|
return "0.0"
|
|
case .One:
|
|
return "1.0"
|
|
}
|
|
}
|
|
|
|
func printable_name() -> String {
|
|
switch self {
|
|
case .Zero:
|
|
return "zero"
|
|
case .One:
|
|
return "one"
|
|
}
|
|
}
|
|
}
|
|
|
|
private let optPrefix = "opt"
|
|
private let unoptPrefix = "unopt"
|
|
|
|
/////////////////////// FPOptimizedOpsValidator Conformances ///////////////////////
|
|
typealias FPType = _FpType
|
|
typealias FPOperation = _FPOperation
|
|
typealias FPOperand = _FPOperand
|
|
|
|
func optimizedFunDeclCheck(
|
|
fpType: FPType,
|
|
op: FPOperation,
|
|
op1: FPOperand,
|
|
op2: FPOperand
|
|
) -> String {
|
|
let funcName = [optPrefix, fpType.printable_name(), op.printable_name(), op1.printable_name(), op2.printable_name()].joined(separator: "_")
|
|
|
|
return """
|
|
// CHECK-SIL-LABEL: sil hidden [noinline] @\(funcName)
|
|
// CHECK-SIL-NEXT: [global: ]
|
|
// CHECK-SIL-NEXT: bb0:
|
|
// CHECK-SIL-NOT: {{.*}}f\(op.printable_name()){{.*}}
|
|
// CHECK-SIL: } // end sil function '\(funcName)'
|
|
"""
|
|
}
|
|
|
|
func unoptimizedFunDeclCheck(
|
|
fpType: FPType,
|
|
op: FPOperation,
|
|
op1: FPOperand,
|
|
op2: FPOperand
|
|
) -> String {
|
|
let funcName = [unoptPrefix, fpType.printable_name(), op.printable_name(), op1.printable_name(), op2.printable_name()].joined(separator: "_")
|
|
|
|
return """
|
|
// CHECK-SIL-LABEL: sil hidden [noinline] [Onone] @\(funcName)
|
|
// CHECK-SIL-NEXT: bb0:
|
|
// CHECK-SIL: {{.*}}f\(op.printable_name()){{.*}}
|
|
// CHECK-SIL: } // end sil function '\(funcName)'
|
|
"""
|
|
}
|
|
|
|
func resultComparisonCheck(
|
|
fpType: FPType,
|
|
op: FPOperation,
|
|
op1: FPOperand,
|
|
op2: FPOperand
|
|
) -> String {
|
|
let comparisonFuncName = ["comparison", fpType.printable_name(), op.printable_name(), op1.printable_name(), op2.printable_name()].joined(separator: "_")
|
|
|
|
// 0.0 / 0.0 is NaN and Nan != Nan
|
|
if op == .Divide && op1 == .Zero && op2 == .Zero {
|
|
return """
|
|
precondition(!\(comparisonFuncName)(), "\(comparisonFuncName) returned true. Expected false.")
|
|
"""
|
|
}
|
|
|
|
return """
|
|
precondition(\(comparisonFuncName)(), "\(comparisonFuncName) returned false. Expected true.")
|
|
"""
|
|
}
|
|
|
|
func generateOptimizedFuncDecls() {
|
|
for fpType in FPType.allCases {
|
|
for op in FPOperation.allCases {
|
|
for op1 in FPOperand.allCases {
|
|
for op2 in FPOperand.allCases {
|
|
generateFuncDeclWithCheckDirectives(fpType: fpType, op: op, op1: op1, op2: op2, isopt: true)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func generateUnoptimizedFuncDecls() {
|
|
for fpType in FPType.allCases {
|
|
for op in FPOperation.allCases {
|
|
for op1 in FPOperand.allCases {
|
|
for op2 in FPOperand.allCases {
|
|
generateFuncDeclWithCheckDirectives(fpType: fpType, op: op, op1: op1, op2: op2, isopt: false)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func generateComparisonFuncDecls() {
|
|
for fpType in FPType.allCases {
|
|
for op in FPOperation.allCases {
|
|
for op1 in FPOperand.allCases {
|
|
for op2 in FPOperand.allCases {
|
|
let comparisonFuncName = ["comparison", fpType.printable_name(), op.printable_name(), op1.printable_name(), op2.printable_name()].joined(separator: "_")
|
|
let optFuncName = [optPrefix, fpType.printable_name(), op.printable_name(), op1.printable_name(), op2.printable_name()].joined(separator: "_")
|
|
let unoptFuncName = [unoptPrefix, fpType.printable_name(), op.printable_name(), op1.printable_name(), op2.printable_name()].joined(separator: "_")
|
|
|
|
print("""
|
|
@inline(never) @_silgen_name("\(comparisonFuncName)") @_optimize(none)
|
|
func \(comparisonFuncName)() -> Bool {
|
|
return \(optFuncName)() == \(unoptFuncName)()
|
|
}
|
|
|
|
""")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func generateComparisonFuncCalls() {
|
|
for fpType in FPType.allCases {
|
|
for op in FPOperation.allCases {
|
|
for op1 in FPOperand.allCases {
|
|
for op2 in FPOperand.allCases {
|
|
let comparison = resultComparisonCheck(fpType: fpType, op: op, op1: op1, op2: op2)
|
|
print("""
|
|
\(comparison)
|
|
|
|
""")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////// Utilities ///////////////////////
|
|
private func generateFuncDeclWithCheckDirectives(
|
|
fpType: FPType,
|
|
op: FPOperation,
|
|
op1: FPOperand,
|
|
op2: FPOperand,
|
|
isopt: Bool = false
|
|
) {
|
|
var operand1 = op1.math_name()
|
|
var operand2 = op2.math_name()
|
|
|
|
if !isopt {
|
|
if fpType == .Double {
|
|
operand1 = op1 == .Zero ? "zero_double()": "one_double()"
|
|
operand2 = op2 == .Zero ? "zero_double()": "one_double()"
|
|
} else {
|
|
operand1 = op1 == .Zero ? "zero_float()": "one_float()"
|
|
operand2 = op2 == .Zero ? "zero_float()": "one_float()"
|
|
}
|
|
}
|
|
|
|
let optPrefix = isopt ? optPrefix : unoptPrefix
|
|
let optAttr = isopt ? "" : "@_optimize(none)"
|
|
let funcName = [optPrefix, fpType.printable_name(), op.printable_name(), op1.printable_name(), op2.printable_name()].joined(separator: "_")
|
|
let checkDirectives = isopt ?
|
|
optimizedFunDeclCheck(fpType: fpType, op: op, op1: op1, op2: op2) :
|
|
unoptimizedFunDeclCheck(fpType: fpType, op: op, op1: op1, op2: op2)
|
|
|
|
print("""
|
|
@inline(never) @_silgen_name("\(funcName)") \(optAttr)
|
|
func \(funcName)() -> \(fpType.math_name()) {
|
|
return \(operand1) \(op.math_name()) \(operand2)
|
|
}
|
|
\(checkDirectives)
|
|
|
|
""")
|
|
}
|
|
}
|