This restriction was originally added in https://reviews.llvm.org/D143256, with the given justification: > Currently, in TargetLowering, if the target does not support fminnum, we lower to fminimum if neither operand could be a NaN. But this isn't quite correct because fminnum and fminimum treat +/-0 differently; so, we need to prove that one of the operands isn't a zero. As far as I can tell, this was never correct. Before https://github.com/llvm/llvm-project/pull/172012, `minnum` and `maxnum` were nondeterministic with regards to signed zero, so it's always been perfectly legal to lower them to operations that order signed zeroes.
338 lines
10 KiB
LLVM
338 lines
10 KiB
LLVM
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
|
|
; RUN: llc < %s -disable-wasm-fallthrough-return-opt -wasm-keep-registers | FileCheck %s
|
|
|
|
; Test that basic 32-bit floating-point operations assemble as expected.
|
|
|
|
target triple = "wasm32-unknown-unknown"
|
|
|
|
declare float @llvm.fabs.f32(float)
|
|
declare float @llvm.copysign.f32(float, float)
|
|
declare float @llvm.sqrt.f32(float)
|
|
declare float @llvm.ceil.f32(float)
|
|
declare float @llvm.floor.f32(float)
|
|
declare float @llvm.trunc.f32(float)
|
|
declare float @llvm.nearbyint.f32(float)
|
|
declare float @llvm.rint.f32(float)
|
|
declare float @llvm.roundeven.f32(float)
|
|
declare float @llvm.fma.f32(float, float, float)
|
|
|
|
define float @fadd32(float %x, float %y) {
|
|
; CHECK-LABEL: fadd32:
|
|
; CHECK: .functype fadd32 (f32, f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get $push2=, 0
|
|
; CHECK-NEXT: local.get $push1=, 1
|
|
; CHECK-NEXT: f32.add $push0=, $pop2, $pop1
|
|
; CHECK-NEXT: return $pop0
|
|
%a = fadd float %x, %y
|
|
ret float %a
|
|
}
|
|
|
|
define float @fsub32(float %x, float %y) {
|
|
; CHECK-LABEL: fsub32:
|
|
; CHECK: .functype fsub32 (f32, f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get $push2=, 0
|
|
; CHECK-NEXT: local.get $push1=, 1
|
|
; CHECK-NEXT: f32.sub $push0=, $pop2, $pop1
|
|
; CHECK-NEXT: return $pop0
|
|
%a = fsub float %x, %y
|
|
ret float %a
|
|
}
|
|
|
|
define float @fmul32(float %x, float %y) {
|
|
; CHECK-LABEL: fmul32:
|
|
; CHECK: .functype fmul32 (f32, f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get $push2=, 0
|
|
; CHECK-NEXT: local.get $push1=, 1
|
|
; CHECK-NEXT: f32.mul $push0=, $pop2, $pop1
|
|
; CHECK-NEXT: return $pop0
|
|
%a = fmul float %x, %y
|
|
ret float %a
|
|
}
|
|
|
|
define float @fdiv32(float %x, float %y) {
|
|
; CHECK-LABEL: fdiv32:
|
|
; CHECK: .functype fdiv32 (f32, f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get $push2=, 0
|
|
; CHECK-NEXT: local.get $push1=, 1
|
|
; CHECK-NEXT: f32.div $push0=, $pop2, $pop1
|
|
; CHECK-NEXT: return $pop0
|
|
%a = fdiv float %x, %y
|
|
ret float %a
|
|
}
|
|
|
|
define float @fabs32(float %x) {
|
|
; CHECK-LABEL: fabs32:
|
|
; CHECK: .functype fabs32 (f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get $push1=, 0
|
|
; CHECK-NEXT: f32.abs $push0=, $pop1
|
|
; CHECK-NEXT: return $pop0
|
|
%a = call float @llvm.fabs.f32(float %x)
|
|
ret float %a
|
|
}
|
|
|
|
define float @fneg32(float %x) {
|
|
; CHECK-LABEL: fneg32:
|
|
; CHECK: .functype fneg32 (f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get $push1=, 0
|
|
; CHECK-NEXT: f32.neg $push0=, $pop1
|
|
; CHECK-NEXT: return $pop0
|
|
%a = fsub float -0., %x
|
|
ret float %a
|
|
}
|
|
|
|
define float @copysign32(float %x, float %y) {
|
|
; CHECK-LABEL: copysign32:
|
|
; CHECK: .functype copysign32 (f32, f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get $push2=, 0
|
|
; CHECK-NEXT: local.get $push1=, 1
|
|
; CHECK-NEXT: f32.copysign $push0=, $pop2, $pop1
|
|
; CHECK-NEXT: return $pop0
|
|
%a = call float @llvm.copysign.f32(float %x, float %y)
|
|
ret float %a
|
|
}
|
|
|
|
define float @sqrt32(float %x) {
|
|
; CHECK-LABEL: sqrt32:
|
|
; CHECK: .functype sqrt32 (f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get $push1=, 0
|
|
; CHECK-NEXT: f32.sqrt $push0=, $pop1
|
|
; CHECK-NEXT: return $pop0
|
|
%a = call float @llvm.sqrt.f32(float %x)
|
|
ret float %a
|
|
}
|
|
|
|
define float @ceil32(float %x) {
|
|
; CHECK-LABEL: ceil32:
|
|
; CHECK: .functype ceil32 (f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get $push1=, 0
|
|
; CHECK-NEXT: f32.ceil $push0=, $pop1
|
|
; CHECK-NEXT: return $pop0
|
|
%a = call float @llvm.ceil.f32(float %x)
|
|
ret float %a
|
|
}
|
|
|
|
define float @floor32(float %x) {
|
|
; CHECK-LABEL: floor32:
|
|
; CHECK: .functype floor32 (f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get $push1=, 0
|
|
; CHECK-NEXT: f32.floor $push0=, $pop1
|
|
; CHECK-NEXT: return $pop0
|
|
%a = call float @llvm.floor.f32(float %x)
|
|
ret float %a
|
|
}
|
|
|
|
define float @trunc32(float %x) {
|
|
; CHECK-LABEL: trunc32:
|
|
; CHECK: .functype trunc32 (f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get $push1=, 0
|
|
; CHECK-NEXT: f32.trunc $push0=, $pop1
|
|
; CHECK-NEXT: return $pop0
|
|
%a = call float @llvm.trunc.f32(float %x)
|
|
ret float %a
|
|
}
|
|
|
|
define float @nearest32(float %x) {
|
|
; CHECK-LABEL: nearest32:
|
|
; CHECK: .functype nearest32 (f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get $push1=, 0
|
|
; CHECK-NEXT: f32.nearest $push0=, $pop1
|
|
; CHECK-NEXT: return $pop0
|
|
%a = call float @llvm.nearbyint.f32(float %x)
|
|
ret float %a
|
|
}
|
|
|
|
define float @nearest32_via_rint(float %x) {
|
|
; CHECK-LABEL: nearest32_via_rint:
|
|
; CHECK: .functype nearest32_via_rint (f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get $push1=, 0
|
|
; CHECK-NEXT: f32.nearest $push0=, $pop1
|
|
; CHECK-NEXT: return $pop0
|
|
%a = call float @llvm.rint.f32(float %x)
|
|
ret float %a
|
|
}
|
|
|
|
define float @nearest32_via_roundeven(float %x) {
|
|
; CHECK-LABEL: nearest32_via_roundeven:
|
|
; CHECK: .functype nearest32_via_roundeven (f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get $push1=, 0
|
|
; CHECK-NEXT: f32.nearest $push0=, $pop1
|
|
; CHECK-NEXT: return $pop0
|
|
%a = call float @llvm.roundeven.f32(float %x)
|
|
ret float %a
|
|
}
|
|
|
|
; This is not "minimum" because a -0.0 input returns +0.0.
|
|
|
|
define float @fmin32(float %x) {
|
|
; CHECK-LABEL: fmin32:
|
|
; CHECK: .functype fmin32 (f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: f32.const $push0=, 0x0p0
|
|
; CHECK-NEXT: local.get $push5=, 0
|
|
; CHECK-NEXT: local.get $push4=, 0
|
|
; CHECK-NEXT: f32.const $push3=, 0x0p0
|
|
; CHECK-NEXT: f32.ge $push1=, $pop4, $pop3
|
|
; CHECK-NEXT: f32.select $push2=, $pop0, $pop5, $pop1
|
|
; CHECK-NEXT: return $pop2
|
|
%a = fcmp ult float %x, 0.0
|
|
%b = select i1 %a, float %x, float 0.0
|
|
ret float %b
|
|
}
|
|
|
|
; This is not "maximum" because a -0.0 input returns +0.0.
|
|
|
|
define float @fmax32(float %x) {
|
|
; CHECK-LABEL: fmax32:
|
|
; CHECK: .functype fmax32 (f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: f32.const $push0=, 0x0p0
|
|
; CHECK-NEXT: local.get $push5=, 0
|
|
; CHECK-NEXT: local.get $push4=, 0
|
|
; CHECK-NEXT: f32.const $push3=, 0x0p0
|
|
; CHECK-NEXT: f32.le $push1=, $pop4, $pop3
|
|
; CHECK-NEXT: f32.select $push2=, $pop0, $pop5, $pop1
|
|
; CHECK-NEXT: return $pop2
|
|
%a = fcmp ugt float %x, 0.0
|
|
%b = select i1 %a, float %x, float 0.0
|
|
ret float %b
|
|
}
|
|
|
|
declare float @llvm.minimum.f32(float, float)
|
|
define float @fmin32_intrinsic(float %x, float %y) {
|
|
; CHECK-LABEL: fmin32_intrinsic:
|
|
; CHECK: .functype fmin32_intrinsic (f32, f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get $push2=, 0
|
|
; CHECK-NEXT: local.get $push1=, 1
|
|
; CHECK-NEXT: f32.min $push0=, $pop2, $pop1
|
|
; CHECK-NEXT: return $pop0
|
|
%a = call float @llvm.minimum.f32(float %x, float %y)
|
|
ret float %a
|
|
}
|
|
|
|
declare float @llvm.minnum.f32(float, float)
|
|
define float @fminnum32_intrinsic(float %x, float %y) {
|
|
; CHECK-LABEL: fminnum32_intrinsic:
|
|
; CHECK: .functype fminnum32_intrinsic (f32, f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get $push2=, 0
|
|
; CHECK-NEXT: local.get $push1=, 1
|
|
; CHECK-NEXT: f32.min $push0=, $pop2, $pop1
|
|
; CHECK-NEXT: return $pop0
|
|
%a = call nnan float @llvm.minnum.f32(float %x, float %y)
|
|
ret float %a
|
|
}
|
|
|
|
define float @fminnum32_non_zero_intrinsic(float %x) {
|
|
; CHECK-LABEL: fminnum32_non_zero_intrinsic:
|
|
; CHECK: .functype fminnum32_non_zero_intrinsic (f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get $push2=, 0
|
|
; CHECK-NEXT: f32.const $push0=, -0x1p0
|
|
; CHECK-NEXT: f32.min $push1=, $pop2, $pop0
|
|
; CHECK-NEXT: return $pop1
|
|
%a = call nnan float @llvm.minnum.f32(float %x, float -1.0)
|
|
ret float %a
|
|
}
|
|
|
|
define float @fminnum32_nsz_intrinsic(float %x, float %y) {
|
|
; CHECK-LABEL: fminnum32_nsz_intrinsic:
|
|
; CHECK: .functype fminnum32_nsz_intrinsic (f32, f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get $push2=, 0
|
|
; CHECK-NEXT: local.get $push1=, 1
|
|
; CHECK-NEXT: f32.min $push0=, $pop2, $pop1
|
|
; CHECK-NEXT: return $pop0
|
|
%a = call nnan nsz float @llvm.minnum.f32(float %x, float %y)
|
|
ret float %a
|
|
}
|
|
|
|
declare float @llvm.maximum.f32(float, float)
|
|
define float @fmax32_intrinsic(float %x, float %y) {
|
|
; CHECK-LABEL: fmax32_intrinsic:
|
|
; CHECK: .functype fmax32_intrinsic (f32, f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get $push2=, 0
|
|
; CHECK-NEXT: local.get $push1=, 1
|
|
; CHECK-NEXT: f32.max $push0=, $pop2, $pop1
|
|
; CHECK-NEXT: return $pop0
|
|
%a = call float @llvm.maximum.f32(float %x, float %y)
|
|
ret float %a
|
|
}
|
|
|
|
declare float @llvm.maxnum.f32(float, float)
|
|
define float @fmaxnum32_intrinsic(float %x, float %y) {
|
|
; CHECK-LABEL: fmaxnum32_intrinsic:
|
|
; CHECK: .functype fmaxnum32_intrinsic (f32, f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get $push2=, 0
|
|
; CHECK-NEXT: local.get $push1=, 1
|
|
; CHECK-NEXT: f32.max $push0=, $pop2, $pop1
|
|
; CHECK-NEXT: return $pop0
|
|
%a = call nnan float @llvm.maxnum.f32(float %x, float %y)
|
|
ret float %a
|
|
}
|
|
|
|
define float @fmaxnum32_nsz_intrinsic(float %x, float %y) {
|
|
; CHECK-LABEL: fmaxnum32_nsz_intrinsic:
|
|
; CHECK: .functype fmaxnum32_nsz_intrinsic (f32, f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get $push2=, 0
|
|
; CHECK-NEXT: local.get $push1=, 1
|
|
; CHECK-NEXT: f32.max $push0=, $pop2, $pop1
|
|
; CHECK-NEXT: return $pop0
|
|
%a = call nnan nsz float @llvm.maxnum.f32(float %x, float %y)
|
|
ret float %a
|
|
}
|
|
|
|
define float @fmaxnum32_zero_intrinsic(float %x) {
|
|
; CHECK-LABEL: fmaxnum32_zero_intrinsic:
|
|
; CHECK: .functype fmaxnum32_zero_intrinsic (f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get $push2=, 0
|
|
; CHECK-NEXT: f32.const $push0=, 0x0p0
|
|
; CHECK-NEXT: f32.max $push1=, $pop2, $pop0
|
|
; CHECK-NEXT: return $pop1
|
|
%a = call nnan float @llvm.maxnum.f32(float %x, float 0.0)
|
|
ret float %a
|
|
}
|
|
|
|
define float @fmaxnum32_non_zero_intrinsic(float %x) {
|
|
; CHECK-LABEL: fmaxnum32_non_zero_intrinsic:
|
|
; CHECK: .functype fmaxnum32_non_zero_intrinsic (f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get $push2=, 0
|
|
; CHECK-NEXT: f32.const $push0=, 0x1p0
|
|
; CHECK-NEXT: f32.max $push1=, $pop2, $pop0
|
|
; CHECK-NEXT: return $pop1
|
|
%a = call nnan float @llvm.maxnum.f32(float %x, float 1.0)
|
|
ret float %a
|
|
}
|
|
|
|
define float @fma32(float %a, float %b, float %c) {
|
|
; CHECK-LABEL: fma32:
|
|
; CHECK: .functype fma32 (f32, f32, f32) -> (f32)
|
|
; CHECK-NEXT: # %bb.0:
|
|
; CHECK-NEXT: local.get $push3=, 0
|
|
; CHECK-NEXT: local.get $push2=, 1
|
|
; CHECK-NEXT: local.get $push1=, 2
|
|
; CHECK-NEXT: call $push0=, fmaf, $pop3, $pop2, $pop1
|
|
; CHECK-NEXT: return $pop0
|
|
%d = call float @llvm.fma.f32(float %a, float %b, float %c)
|
|
ret float %d
|
|
}
|