Files
swift-mirror/validation-test/SILOptimizer/constant_folded_fp_operations.swift
2024-07-12 02:34:00 +03:00

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)
""")
}
}