blob: b0ff67b91670102b3a17910d4a807ddacbcfc2eb [file] [edit]
//===- FenvOpTest.cpp - Unit tests for CIR fenv operations ----------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/MLIRContext.h"
#include "mlir/IR/OwningOpRef.h"
#include "mlir/Interfaces/SideEffectInterfaces.h"
#include "mlir/Parser/Parser.h"
#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include <gtest/gtest.h>
using namespace mlir;
namespace {
class CIRFenvOpTest : public ::testing::Test {
protected:
CIRFenvOpTest() { context.loadDialect<cir::CIRDialect>(); }
OwningOpRef<ModuleOp> parse(StringRef ir) {
OwningOpRef<ModuleOp> module = parseSourceString<ModuleOp>(ir, &context);
EXPECT_TRUE(module) << "failed to parse IR";
return module;
}
template <typename OpTy> SmallVector<OpTy> findOps(ModuleOp module) {
SmallVector<OpTy> ops;
module.walk([&](OpTy op) { ops.push_back(op); });
return ops;
}
static SmallVector<MemoryEffects::EffectInstance> getEffects(Operation *op) {
MemoryEffectOpInterface effectsOp = cast<MemoryEffectOpInterface>(op);
SmallVector<MemoryEffects::EffectInstance> effects;
effectsOp.getEffects(effects);
return effects;
}
static void expectFenvReadAndWrite(Operation *op) {
SmallVector<MemoryEffects::EffectInstance> effects = getEffects(op);
ASSERT_EQ(effects.size(), 2u);
unsigned reads = 0;
unsigned writes = 0;
for (const MemoryEffects::EffectInstance &effect : effects) {
EXPECT_EQ(effect.getResource(),
cir::FloatingPointEnvironmentResource::get());
reads += isa<MemoryEffects::Read>(effect.getEffect());
writes += isa<MemoryEffects::Write>(effect.getEffect());
}
EXPECT_EQ(reads, 1u);
EXPECT_EQ(writes, 1u);
}
MLIRContext context;
};
TEST_F(CIRFenvOpTest, MemoryEffects) {
OwningOpRef<ModuleOp> module = parse(R"CIR(
!s32i = !cir.int<s, 32>
cir.func @f(%a: !cir.float, %b: !cir.float, %c: !cir.float,
%va: !cir.vector<4 x !cir.float>,
%vb: !cir.vector<4 x !cir.float>) {
%0 = cir.fadd %a, %b : !cir.float
%1 = cir.fadd %a, %b : !cir.float fenv<>
%2 = cir.sqrt %a : !cir.float fenv<>
%3 = cir.pow %a, %b : !cir.float fenv<>
%4 = cir.fma %a, %b, %c : !cir.float
%5 = cir.fma %a, %b, %c : !cir.float fenv<>
%6 = cir.lround %a : !cir.float -> !s32i
%7 = cir.lround %a : !cir.float -> !s32i fenv<>
%8 = cir.cast floating %a : !cir.float -> !cir.double
%9 = cir.cast floating %a : !cir.float -> !cir.double fenv<>
%10 = cir.cmp lt %a, %b : !cir.float
%11 = cir.cmp lt %a, %b : !cir.float fenv<>
%12 = cir.vec.cmp(eq, %va, %vb) : !cir.vector<4 x !cir.float>, !cir.vector<4 x !s32i>
%13 = cir.vec.cmp(eq, %va, %vb) : !cir.vector<4 x !cir.float>, !cir.vector<4 x !s32i> fenv<>
cir.return
}
)CIR");
ASSERT_TRUE(module);
SmallVector<cir::FAddOp> faddOps = findOps<cir::FAddOp>(*module);
ASSERT_EQ(faddOps.size(), 2u);
EXPECT_TRUE(getEffects(faddOps[0]).empty());
EXPECT_TRUE(isMemoryEffectFree(faddOps[0]));
expectFenvReadAndWrite(faddOps[1]);
EXPECT_FALSE(isMemoryEffectFree(faddOps[1]));
SmallVector<cir::SqrtOp> sqrtOps = findOps<cir::SqrtOp>(*module);
ASSERT_EQ(sqrtOps.size(), 1u);
expectFenvReadAndWrite(sqrtOps[0]);
SmallVector<cir::PowOp> powOps = findOps<cir::PowOp>(*module);
ASSERT_EQ(powOps.size(), 1u);
expectFenvReadAndWrite(powOps[0]);
SmallVector<cir::FMAOp> fmaOps = findOps<cir::FMAOp>(*module);
ASSERT_EQ(fmaOps.size(), 2u);
EXPECT_TRUE(getEffects(fmaOps[0]).empty());
EXPECT_TRUE(isMemoryEffectFree(fmaOps[0]));
expectFenvReadAndWrite(fmaOps[1]);
EXPECT_FALSE(isMemoryEffectFree(fmaOps[1]));
SmallVector<cir::LroundOp> lroundOps = findOps<cir::LroundOp>(*module);
ASSERT_EQ(lroundOps.size(), 2u);
EXPECT_TRUE(getEffects(lroundOps[0]).empty());
EXPECT_TRUE(isMemoryEffectFree(lroundOps[0]));
expectFenvReadAndWrite(lroundOps[1]);
EXPECT_FALSE(isMemoryEffectFree(lroundOps[1]));
SmallVector<cir::CastOp> castOps = findOps<cir::CastOp>(*module);
ASSERT_EQ(castOps.size(), 2u);
EXPECT_TRUE(getEffects(castOps[0]).empty());
EXPECT_TRUE(isMemoryEffectFree(castOps[0]));
expectFenvReadAndWrite(castOps[1]);
EXPECT_FALSE(isMemoryEffectFree(castOps[1]));
SmallVector<cir::CmpOp> cmpOps = findOps<cir::CmpOp>(*module);
ASSERT_EQ(cmpOps.size(), 2u);
EXPECT_TRUE(getEffects(cmpOps[0]).empty());
EXPECT_TRUE(isMemoryEffectFree(cmpOps[0]));
expectFenvReadAndWrite(cmpOps[1]);
EXPECT_FALSE(isMemoryEffectFree(cmpOps[1]));
SmallVector<cir::VecCmpOp> vecCmpOps = findOps<cir::VecCmpOp>(*module);
ASSERT_EQ(vecCmpOps.size(), 2u);
EXPECT_TRUE(getEffects(vecCmpOps[0]).empty());
EXPECT_TRUE(isMemoryEffectFree(vecCmpOps[0]));
expectFenvReadAndWrite(vecCmpOps[1]);
EXPECT_FALSE(isMemoryEffectFree(vecCmpOps[1]));
}
TEST_F(CIRFenvOpTest, Speculatability) {
OwningOpRef<ModuleOp> module = parse(R"CIR(
cir.func @f(%a: !cir.float, %b: !cir.float) {
%0 = cir.fadd %a, %b : !cir.float
%1 = cir.fadd %a, %b : !cir.float fenv<>
%2 = cir.fadd %a, %b : !cir.float fenv<except_mode = masked>
%3 = cir.fadd %a, %b : !cir.float fenv<strict_except = false>
%4 = cir.fadd %a, %b : !cir.float fenv<except_mode = masked, strict_except = true>
%5 = cir.fadd %a, %b : !cir.float fenv<except_mode = unmasked, strict_except = false>
%6 = cir.fadd %a, %b : !cir.float fenv<except_mode = unknown, strict_except = false>
cir.return
}
)CIR");
ASSERT_TRUE(module);
SmallVector<cir::FAddOp> ops = findOps<cir::FAddOp>(*module);
ASSERT_EQ(ops.size(), 7u);
// Missing fenv fields use the defaults: masked exceptions and non-strict
// exception behavior.
for (unsigned i = 0; i != 4; ++i)
EXPECT_TRUE(isSpeculatable(ops[i])) << "operation " << i;
for (unsigned i = 4; i != ops.size(); ++i)
EXPECT_FALSE(isSpeculatable(ops[i])) << "operation " << i;
cir::FPEnvConstrainedOpInterface fenvOp =
cast<cir::FPEnvConstrainedOpInterface>(ops[1].getOperation());
EXPECT_EQ(fenvOp.getFenvDynamicRoundingMode(),
cir::FPDynamicRoundingMode::Unknown);
EXPECT_EQ(fenvOp.getFenvExceptionMode(), cir::FPExceptionMode::Masked);
EXPECT_FALSE(fenvOp.getFenvStrictExcept());
}
} // namespace