Skip to content

Commit 66ec50c

Browse files
committed
[SYCL] Pass foffload-fp32-prec-[div/sqrt] options to device's BE
This patch also adds a pass the removes llvm.fpbuiltin.[sqrt/fdiv] intrinsic functions from the module to ensure compatibility with the old drivers (that don't support SPV_INTEL_fp_max_error extension) in case if they are used with standart for OpenCL max-error (e.g [3.0/2.5] ULP) and there are no other llvm.fpbuiltin.* intrinsic functions, fdiv instructions or @sqrt builtins/intrinsics in the module. Signed-off-by: Sidorov, Dmitry <[email protected]>
1 parent ac24092 commit 66ec50c

17 files changed

+540
-1
lines changed

clang/lib/Driver/ToolChains/SYCL.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1935,6 +1935,7 @@ void SYCLToolChain::AddImpliedTargetArgs(const llvm::Triple &Triple,
19351935
Triple.isSPIROrSPIRV() && Triple.getSubArch() == llvm::Triple::NoSubArch;
19361936
if (IsGen && Args.hasArg(options::OPT_fsycl_fp64_conv_emu))
19371937
BeArgs.push_back("-ze-fp64-gen-conv-emu");
1938+
19381939
if (Arg *A = Args.getLastArg(options::OPT_g_Group, options::OPT__SLASH_Z7))
19391940
if (!A->getOption().matches(options::OPT_g0))
19401941
BeArgs.push_back("-g");
@@ -2061,9 +2062,18 @@ void SYCLToolChain::AddImpliedTargetArgs(const llvm::Triple &Triple,
20612062
if (Args.hasFlag(options::OPT_ftarget_export_symbols,
20622063
options::OPT_fno_target_export_symbols, false))
20632064
BeArgs.push_back("-library-compilation");
2064-
} else if (IsJIT)
2065+
// -foffload-fp32-prec-[sqrt/div]
2066+
if (Args.hasArg(options::OPT_foffload_fp32_prec_div) ||
2067+
Args.hasArg(options::OPT_foffload_fp32_prec_sqrt))
2068+
BeArgs.push_back("-ze-fp32-correctly-rounded-divide-sqrt");
2069+
} else if (IsJIT) {
20652070
// -ftarget-compile-fast JIT
20662071
Args.AddLastArg(BeArgs, options::OPT_ftarget_compile_fast);
2072+
// -foffload-fp32-prec-div JIT
2073+
Args.AddLastArg(BeArgs, options::OPT_foffload_fp32_prec_div);
2074+
// -foffload-fp32-prec-sqrt JIT
2075+
Args.AddLastArg(BeArgs, options::OPT_OPT_foffload_fp32_prec_sqrt);
2076+
}
20672077
if (IsGen) {
20682078
for (auto [DeviceName, BackendArgStr] : PerDeviceArgs) {
20692079
CmdArgs.push_back("-device_options");
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//===-- SYCLSqrtFDivMaxErrorCleanUp.h - SYCLSqrtFDivMaxErrorCleanUp Pass --===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
// Remove llvm.fpbuiltin.[sqrt/fdiv] intrinsics to ensure backward compatibility
9+
// with the old drivers (that don't support SPV_INTEL_fp_max_error extension)
10+
// in case if they are used with standart for OpenCL max-error (e.g [3.0/2.5]
11+
// ULP and there are no other llvm.fpbuiltin.* intrinsic functions, fdiv
12+
// instructions or @sqrt builtins/intrinsics in the module.
13+
//===----------------------------------------------------------------------===//
14+
//
15+
#ifndef LLVM_SYCL_SQRT_FDIV_MAX_ERROR_CLEAN_UP_H
16+
#define LLVM_SYCL_SQRT_FDIV_MAX_ERROR_CLEAN_UP_H
17+
18+
#include "llvm/IR/PassManager.h"
19+
20+
namespace llvm {
21+
22+
// FIXME: remove this pass, it's not really needed.
23+
class SYCLSqrtFDivMaxErrorCleanUpPass
24+
: public PassInfoMixin<SYCLSqrtFDivMaxErrorCleanUpPass> {
25+
public:
26+
PreservedAnalyses run(Module &M, ModuleAnalysisManager &);
27+
28+
static bool isRequired() { return true; }
29+
};
30+
31+
} // namespace llvm
32+
33+
#endif // LLVM_SYCL_SQRT_FDIV_MAX_ERROR_CLEAN_UP_H

llvm/lib/Passes/PassBuilder.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@
166166
#include "llvm/SYCLLowerIR/SYCLOptimizeBackToBackBarrier.h"
167167
#include "llvm/SYCLLowerIR/SYCLPropagateAspectsUsage.h"
168168
#include "llvm/SYCLLowerIR/SYCLPropagateJointMatrixUsage.h"
169+
#include "llvm/SYCLLowerIR/SYCLSqrtFDivMaxErrorCleanUp.h"
169170
#include "llvm/SYCLLowerIR/SYCLVirtualFunctionsAnalysis.h"
170171
#include "llvm/SYCLLowerIR/SpecConstants.h"
171172
#include "llvm/Support/CommandLine.h"

llvm/lib/Passes/PassRegistry.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ MODULE_PASS("esimd-remove-optnone-noinline", ESIMDRemoveOptnoneNoinlinePass());
174174
MODULE_PASS("sycl-conditional-call-on-device", SYCLConditionalCallOnDevicePass())
175175
MODULE_PASS("sycl-joint-matrix-transform", SYCLJointMatrixTransformPass())
176176
MODULE_PASS("sycl-optimize-back-to-back-barrier", SYCLOptimizeBackToBackBarrierPass())
177+
MODULE_PASS("sycl-sqrt-fdiv-max-error-clean-up", SYCLSqrtFDivMaxErrorCleanUpPass())
177178
MODULE_PASS("sycl-propagate-aspects-usage", SYCLPropagateAspectsUsagePass())
178179
MODULE_PASS("sycl-propagate-joint-matrix-usage", SYCLPropagateJointMatrixUsagePass())
179180
MODULE_PASS("sycl-add-opt-level-attribute", SYCLAddOptLevelAttributePass())

llvm/lib/SYCLLowerIR/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ add_llvm_component_library(LLVMSYCLLowerIR
6868
SYCLOptimizeBackToBackBarrier.cpp
6969
SYCLPropagateAspectsUsage.cpp
7070
SYCLPropagateJointMatrixUsage.cpp
71+
SYCLSqrtFDivMaxErrorCleanUp.cpp
7172
SYCLVirtualFunctionsAnalysis.cpp
7273
SYCLUtils.cpp
7374

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
//===- SYCLSqrtFDivMaxErrorCleanUp.cpp - SYCLSqrtFDivMaxErrorCleanUp Pass -===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
// Remove llvm.fpbuiltin.[sqrt/fdiv] intrinsics to ensure backward compatibility
9+
// with the old drivers (that don't support SPV_INTEL_fp_max_error extension)
10+
// in case if they are used with standart for OpenCL max-error (e.g [3.0/2.5]
11+
// ULP and there are no other llvm.fpbuiltin.* intrinsic functions, fdiv
12+
// instructions or @sqrt builtins/intrinsics in the module.
13+
//===----------------------------------------------------------------------===//
14+
15+
#include "llvm/SYCLLowerIR/SYCLSqrtFDivMaxErrorCleanUp.h"
16+
17+
#include "llvm/ADT/SmallSet.h"
18+
#include "llvm/IR/Module.h"
19+
#include "llvm/IR/IntrinsicInst.h"
20+
#include "llvm/IR/IRBuilder.h"
21+
22+
using namespace llvm;
23+
24+
namespace {
25+
static constexpr char SQRT_ERROR[] = "3.0";
26+
static constexpr char FDIV_ERROR[] = "2.5";
27+
} // namespace
28+
29+
PreservedAnalyses
30+
SYCLSqrtFDivMaxErrorCleanUpPass::run(Module &M,
31+
ModuleAnalysisManager &MAM) {
32+
SmallVector<IntrinsicInst *, 16> WorkListSqrt;
33+
SmallVector<IntrinsicInst *, 16> WorkListFDiv;
34+
35+
// Add all llvm.fpbuiltin.sqrt with 3.0 error and llvm.fpbuiltin.fdiv with
36+
// 2.5 error to the work list to remove them later. If attributes with other
37+
// values or other llvm.fpbuiltin.* intrinsic functions found - abort the
38+
// pass.
39+
for (auto &F : M) {
40+
if (!F.isDeclaration())
41+
continue;
42+
const auto ID = F.getIntrinsicID();
43+
if (ID != llvm::Intrinsic::fpbuiltin_sqrt &&
44+
ID != llvm::Intrinsic::fpbuiltin_fdiv)
45+
continue;
46+
47+
for (auto *Use : F.users()) {
48+
auto *II = cast<IntrinsicInst>(Use);
49+
if (II && II->getCalledFunction()->getName().
50+
starts_with("llvm.fpbuiltin")) {
51+
// llvm.fpbuiltin.* intrinsics should always have fpbuiltin-max-error
52+
// attribute, but it's not a concern of the pass, so just do an early
53+
// exit here if the attribute is not attached.
54+
if (!II->getAttributes().hasFnAttr("fpbuiltin-max-error"))
55+
return PreservedAnalyses::none();
56+
StringRef MaxError = II->getAttributes().getFnAttr(
57+
"fpbuiltin-max-error").getValueAsString();
58+
59+
if (ID == llvm::Intrinsic::fpbuiltin_sqrt) {
60+
if (MaxError != SQRT_ERROR)
61+
return PreservedAnalyses::none();
62+
WorkListSqrt.push_back(II);
63+
}
64+
else if (ID == llvm::Intrinsic::fpbuiltin_fdiv) {
65+
if (MaxError != FDIV_ERROR)
66+
return PreservedAnalyses::none();
67+
WorkListFDiv.push_back(II);
68+
} else {
69+
// Another llvm.fpbuiltin.* intrinsic was found - the module is
70+
// already not backward compatible.
71+
return PreservedAnalyses::none();
72+
}
73+
}
74+
}
75+
}
76+
77+
// No intrinsics at all - do an early exist
78+
if (WorkListSqrt.empty() && WorkListFDiv.empty())
79+
return PreservedAnalyses::none();
80+
81+
// If @sqrt, @_Z4sqrt*, @llvm.sqrt. or fdiv present in the module - do
82+
// nothing.
83+
for (auto &F : M) {
84+
if (F.isDeclaration())
85+
continue;
86+
for (auto &BB : F) {
87+
for (auto &II : BB) {
88+
if (auto *CI = dyn_cast<CallInst>(&II)) {
89+
auto *SqrtF = CI->getCalledFunction();
90+
if (SqrtF->getName() == "sqrt" ||
91+
SqrtF->getName().starts_with("_Z4sqrt") ||
92+
SqrtF->getIntrinsicID() == llvm::Intrinsic::sqrt)
93+
return PreservedAnalyses::none();
94+
}
95+
if (auto *FPI = dyn_cast<FPMathOperator>(&II)) {
96+
auto Opcode = FPI->getOpcode();
97+
if (Opcode == Instruction::FDiv)
98+
return PreservedAnalyses::none();
99+
}
100+
}
101+
}
102+
}
103+
104+
// Replace @llvm.fpbuiltin.sqrt call with @llvm.sqrt. llvm-spirv will handle
105+
// it later.
106+
SmallSet<Function *, 2> DeclToRemove;
107+
for (auto *Sqrt : WorkListSqrt) {
108+
DeclToRemove.insert(Sqrt->getCalledFunction());
109+
IRBuilder Builder(Sqrt);
110+
Builder.SetInsertPoint(Sqrt);
111+
Type *Ty = Sqrt->getType();
112+
AttributeList Attrs = Sqrt->getAttributes();
113+
Function *NewSqrtF =
114+
Intrinsic::getDeclaration(&M, llvm::Intrinsic::sqrt, Ty);
115+
auto *NewSqrt = Builder.CreateCall(NewSqrtF, { Sqrt->getOperand(0) },
116+
Sqrt->getName());
117+
118+
// Copy FP flags, metadata and attributes. Replace old call with a new call.
119+
Attrs = Attrs.removeFnAttribute(Sqrt->getContext(), "fpbuiltin-max-error");
120+
NewSqrt->setAttributes(Attrs);
121+
NewSqrt->copyMetadata(*Sqrt);
122+
FPMathOperator *FPOp = cast<FPMathOperator>(Sqrt);
123+
FastMathFlags FMF = FPOp->getFastMathFlags();
124+
NewSqrt->setFastMathFlags(FMF);
125+
Sqrt->replaceAllUsesWith(NewSqrt);
126+
Sqrt->dropAllReferences();
127+
Sqrt->eraseFromParent();
128+
}
129+
130+
// Replace @llvm.fpbuiltin.fdiv call with fdiv.
131+
for (auto *FDiv : WorkListFDiv) {
132+
DeclToRemove.insert(FDiv->getCalledFunction());
133+
IRBuilder Builder(FDiv);
134+
Builder.SetInsertPoint(FDiv);
135+
Instruction *NewFDiv =
136+
cast<Instruction>(Builder.CreateFDiv(
137+
FDiv->getOperand(0), FDiv->getOperand(1), FDiv->getName()));
138+
139+
// Copy FP flags and metadata. Replace old call with a new instruction.
140+
cast<Instruction>(NewFDiv)->copyMetadata(*FDiv);
141+
FPMathOperator *FPOp = cast<FPMathOperator>(FDiv);
142+
FastMathFlags FMF = FPOp->getFastMathFlags();
143+
NewFDiv->setFastMathFlags(FMF);
144+
FDiv->replaceAllUsesWith(NewFDiv);
145+
FDiv->dropAllReferences();
146+
FDiv->eraseFromParent();
147+
}
148+
149+
// Clear old declarations.
150+
for (auto *Decl : DeclToRemove) {
151+
assert(Decl->isDeclaration() &&
152+
"attempting to remove a function definition");
153+
Decl->dropAllReferences();
154+
Decl->eraseFromParent();
155+
}
156+
157+
return PreservedAnalyses::all();
158+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
; Test checks if @llvm.fpbuiltin.fdiv and @llvm.fpbuiltin.sqrt are removed from
2+
; the module.
3+
4+
; RUN: opt -passes=sycl-sqrt-fdiv-max-error-clean-up < %s -S | FileCheck %s
5+
6+
; CHECK-NOT: llvm.fpbuiltin.fdiv.f32
7+
; CHECK-NOT: llvm.fpbuiltin.sqrt.f32
8+
; CHECK-NOT: fpbuiltin-max-error
9+
10+
; CHECK: test_fp_max_error_decoration(float [[F1:[%0-9a-z.]+]], float [[F2:[%0-9a-z.]+]])
11+
; CHECK: [[V1:[%0-9a-z.]+]] = fdiv float [[F1]], [[F2]]
12+
; CHECK: call float @llvm.sqrt.f32(float [[V1]])
13+
14+
; CHECK: test_fp_max_error_decoration_fast(float [[F1:[%0-9a-z.]+]], float [[F2:[%0-9a-z.]+]])
15+
; CHECK: [[V1:[%0-9a-z.]+]] = fdiv fast float [[F1]], [[F2]]
16+
; CHECK: call fast float @llvm.sqrt.f32(float [[V1]])
17+
18+
; CHECK: test_fp_max_error_decoration_debug(float [[F1:[%0-9a-z.]+]], float [[F2:[%0-9a-z.]+]])
19+
; CHECK: [[V1:[%0-9a-z.]+]] = fdiv float [[F1]], [[F2]], !dbg ![[#Loc1:]]
20+
; CHECK: call float @llvm.sqrt.f32(float [[V1]]), !dbg ![[#Loc2:]]
21+
22+
; CHECK: [[#Loc1]] = !DILocation(line: 1, column: 1, scope: ![[#]])
23+
; CHECK: [[#Loc2]] = !DILocation(line: 2, column: 1, scope: ![[#]])
24+
25+
target datalayout = "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64"
26+
target triple = "spir64-unknown-unknown"
27+
28+
define void @test_fp_max_error_decoration(float %f1, float %f2) {
29+
entry:
30+
%v1 = call float @llvm.fpbuiltin.fdiv.f32(float %f1, float %f2) #0
31+
%v2 = call float @llvm.fpbuiltin.sqrt.f32(float %v1) #1
32+
ret void
33+
}
34+
35+
define void @test_fp_max_error_decoration_fast(float %f1, float %f2) {
36+
entry:
37+
%v1 = call fast float @llvm.fpbuiltin.fdiv.f32(float %f1, float %f2) #0
38+
%v2 = call fast float @llvm.fpbuiltin.sqrt.f32(float %v1) #1
39+
ret void
40+
}
41+
42+
define void @test_fp_max_error_decoration_debug(float %f1, float %f2) {
43+
entry:
44+
%v1 = call float @llvm.fpbuiltin.fdiv.f32(float %f1, float %f2) #0, !dbg !7
45+
%v2 = call float @llvm.fpbuiltin.sqrt.f32(float %v1) #1, !dbg !8
46+
ret void
47+
}
48+
49+
declare float @llvm.fpbuiltin.fdiv.f32(float, float)
50+
51+
declare float @llvm.fpbuiltin.sqrt.f32(float)
52+
53+
attributes #0 = { "fpbuiltin-max-error"="2.5" }
54+
attributes #1 = { "fpbuiltin-max-error"="3.0" }
55+
56+
!llvm.dbg.cu = !{!0}
57+
!llvm.module.flags = !{!9}
58+
59+
!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, nameTableKind: None)
60+
!1 = !DIFile(filename: "test.c", directory: "/tmp", checksumkind: CSK_MD5, checksum: "2a034da6937f5b9cf6dd2d89127f57fd")
61+
!2 = distinct !DISubprogram(name: "test_fp_max_error_decoration_debug", scope: !1, file: !1, line: 1, type: !3, scopeLine: 2, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
62+
!3 = !DISubroutineType(types: !4)
63+
!4 = !{!5, !6, !6}
64+
!5 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
65+
!6 = !DIBasicType(name: "float", size: 32, encoding: DW_ATE_float)
66+
!7 = !DILocation(line: 1, column: 1, scope: !2)
67+
!8 = !DILocation(line: 2, column: 1, scope: !2)
68+
!9 = !{i32 2, !"Debug Info Version", i32 3}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
; Test checks if @llvm.fpbuiltin.fdiv and @llvm.fpbuiltin.sqrt remain if
2+
; non-standart for OpenCL max-error is used.
3+
4+
; RUN: opt -passes=sycl-sqrt-fdiv-max-error-clean-up < %s -S | FileCheck %s
5+
6+
; CHECK: llvm.fpbuiltin.fdiv.f32
7+
; CHECK: llvm.fpbuiltin.sqrt.f32
8+
; CHECK: fpbuiltin-max-error
9+
10+
target datalayout = "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64"
11+
target triple = "spir64-unknown-unknown"
12+
13+
define void @test_fp_max_error_decoration(float %f1, float %f2) {
14+
entry:
15+
%v1 = call float @llvm.fpbuiltin.fdiv.f32(float %f1, float %f2) #0
16+
%v2 = call float @llvm.fpbuiltin.sqrt.f32(float %v1) #1
17+
ret void
18+
}
19+
20+
declare float @llvm.fpbuiltin.fdiv.f32(float, float)
21+
22+
declare float @llvm.fpbuiltin.sqrt.f32(float)
23+
24+
attributes #0 = { "fpbuiltin-max-error"="2.0" }
25+
attributes #1 = { "fpbuiltin-max-error"="3.0" }
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
; Test checks if @llvm.fpbuiltin.fdiv and @llvm.fpbuiltin.sqrt remain if
2+
; fdiv instruction was in the module.
3+
4+
; RUN: opt -passes=sycl-sqrt-fdiv-max-error-clean-up < %s -S | FileCheck %s
5+
6+
; CHECK: llvm.fpbuiltin.fdiv.f32
7+
; CHECK: llvm.fpbuiltin.sqrt.f32
8+
; CHECK: fpbuiltin-max-error
9+
10+
target datalayout = "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64"
11+
target triple = "spir64-unknown-unknown"
12+
13+
define void @test_fp_max_error_decoration(float %f1, float %f2) {
14+
entry:
15+
%v1 = call float @llvm.fpbuiltin.fdiv.f32(float %f1, float %f2) #0
16+
%v2 = call float @llvm.fpbuiltin.sqrt.f32(float %v1) #1
17+
%v3 = fdiv float %v2, %f2
18+
ret void
19+
}
20+
21+
declare float @llvm.fpbuiltin.fdiv.f32(float, float)
22+
23+
declare float @llvm.fpbuiltin.sqrt.f32(float)
24+
25+
attributes #0 = { "fpbuiltin-max-error"="2.0" }
26+
attributes #1 = { "fpbuiltin-max-error"="3.0" }
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
; Test checks if @llvm.fpbuiltin.fdiv and @llvm.fpbuiltin.sqrt remain if
2+
; other fpbuiltin intrinsic is used in the module.
3+
4+
; RUN: opt -passes=sycl-sqrt-fdiv-max-error-clean-up < %s -S | FileCheck %s
5+
6+
; CHECK: llvm.fpbuiltin.fdiv.f32
7+
; CHECK: llvm.fpbuiltin.sqrt.f32
8+
; CHECK: fpbuiltin-max-error
9+
10+
target datalayout = "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64"
11+
target triple = "spir64-unknown-unknown"
12+
13+
define void @test_fp_max_error_decoration(float %f1, float %f2) {
14+
entry:
15+
%v1 = call float @llvm.fpbuiltin.fdiv.f32(float %f1, float %f2) #0
16+
%v2 = call float @llvm.fpbuiltin.sqrt.f32(float %v1) #1
17+
%v3 = call float @llvm.fpbuiltin.exp.f32(float %v2)
18+
ret void
19+
}
20+
21+
declare float @llvm.fpbuiltin.fdiv.f32(float, float)
22+
23+
declare float @llvm.fpbuiltin.sqrt.f32(float)
24+
25+
declare float @llvm.fpbuiltin.exp.f32(float)
26+
27+
attributes #0 = { "fpbuiltin-max-error"="2.0" }
28+
attributes #1 = { "fpbuiltin-max-error"="3.0" }

0 commit comments

Comments
 (0)