diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc index 26cacbe994..fc6cbb5a4c 100644 --- a/src/writer/spirv/builder.cc +++ b/src/writer/spirv/builder.cc @@ -32,6 +32,7 @@ #include "src/sem/sampled_texture_type.h" #include "src/sem/struct.h" #include "src/sem/variable.h" +#include "src/sem/vector_type.h" #include "src/utils/get_or_create.h" #include "src/writer/append_vector.h" @@ -2251,6 +2252,83 @@ uint32_t Builder::GenerateIntrinsic(ast::CallExpression* call, } return 0; } + case IntrinsicType::kIsNormal: { + // A normal number is finite, non-zero, and not subnormal. + // Its exponent is neither of the extreme possible values. + // Implemented as: + // exponent_bits = bitcast(f); + // clamped = uclamp(1,254,exponent_bits); + // result = (clamped == exponent_bits); + // + auto val_id = get_param_as_value_id(0); + if (!val_id) { + return 0; + } + + // These parameters are valid for IEEE 754 binary32 + const uint32_t kExponentMask = 0x7f80000; + const uint32_t kMinNormalExponent = 0x0080000; + const uint32_t kMaxNormalExponent = 0x7f00000; + + auto set_id = GetGLSLstd450Import(); + sem::U32 u32; + auto unsigned_id = GenerateTypeIfNeeded(&u32); + auto exponent_mask_id = + GenerateConstantIfNeeded(ScalarConstant::U32(kExponentMask)); + auto min_exponent_id = + GenerateConstantIfNeeded(ScalarConstant::U32(kMinNormalExponent)); + auto max_exponent_id = + GenerateConstantIfNeeded(ScalarConstant::U32(kMaxNormalExponent)); + if (auto* fvec_ty = intrinsic->ReturnType()->As()) { + // In the vector case, update the unsigned type to a vector type of the + // same size, and create vector constants by replicating the scalars. + // I expect backend compilers to fold these into unique constants, so + // there is no loss of efficiency. + sem::Vector uvec_ty(&u32, fvec_ty->size()); + unsigned_id = GenerateTypeIfNeeded(&uvec_ty); + auto splat = [&](uint32_t scalar_id) -> uint32_t { + auto splat_result = result_op(); + OperandList splat_params{Operand::Int(unsigned_id), splat_result}; + for (size_t i = 0; i < fvec_ty->size(); i++) { + splat_params.emplace_back(Operand::Int(scalar_id)); + } + if (!push_function_inst(spv::Op::OpCompositeConstruct, + std::move(splat_params))) { + return 0; + } + return splat_result.to_i(); + }; + exponent_mask_id = splat(exponent_mask_id); + min_exponent_id = splat(min_exponent_id); + max_exponent_id = splat(max_exponent_id); + } + auto cast_result = result_op(); + auto exponent_bits_result = result_op(); + auto clamp_result = result_op(); + + if (set_id && unsigned_id && exponent_mask_id && min_exponent_id && + max_exponent_id && + push_function_inst( + spv::Op::OpBitcast, + {Operand::Int(unsigned_id), cast_result, Operand::Int(val_id)}) && + push_function_inst(spv::Op::OpBitwiseAnd, + {Operand::Int(unsigned_id), exponent_bits_result, + Operand::Int(cast_result.to_i()), + Operand::Int(exponent_mask_id)}) && + push_function_inst( + spv::Op::OpExtInst, + {Operand::Int(unsigned_id), clamp_result, Operand::Int(set_id), + Operand::Int(GLSLstd450UClamp), + Operand::Int(exponent_bits_result.to_i()), + Operand::Int(min_exponent_id), Operand::Int(max_exponent_id)}) && + push_function_inst(spv::Op::OpIEqual, + {Operand::Int(result_type_id), result, + Operand::Int(exponent_bits_result.to_i()), + Operand::Int(clamp_result.to_i())})) { + return result_id; + } + return 0; + } case IntrinsicType::kReverseBits: op = spv::Op::OpBitReverse; break; diff --git a/src/writer/spirv/builder_intrinsic_test.cc b/src/writer/spirv/builder_intrinsic_test.cc index 002ee0713d..04351283cb 100644 --- a/src/writer/spirv/builder_intrinsic_test.cc +++ b/src/writer/spirv/builder_intrinsic_test.cc @@ -184,6 +184,97 @@ TEST_F(IntrinsicBuilderTest, IsFinite_Vector) { )"); } +TEST_F(IntrinsicBuilderTest, IsNormal_Scalar) { + auto* var = Global("v", ty.f32(), ast::StorageClass::kPrivate); + + auto* expr = Call("isNormal", "v"); + WrapInFunction(expr); + + auto* func = Func("a_func", ast::VariableList{}, ty.void_(), + ast::StatementList{}, ast::DecorationList{}); + + spirv::Builder& b = Build(); + + ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error(); + ASSERT_TRUE(b.GenerateFunction(func)) << b.error(); + + EXPECT_EQ(b.GenerateCallExpression(expr), 9u) << b.error(); + auto got = DumpBuilder(b); + EXPECT_EQ(got, R"(%12 = OpExtInstImport "GLSL.std.450" +OpName %1 "v" +OpName %7 "a_func" +%3 = OpTypeFloat 32 +%2 = OpTypePointer Private %3 +%4 = OpConstantNull %3 +%1 = OpVariable %2 Private %4 +%6 = OpTypeVoid +%5 = OpTypeFunction %6 +%10 = OpTypeBool +%13 = OpTypeInt 32 0 +%14 = OpConstant %13 133693440 +%15 = OpConstant %13 524288 +%16 = OpConstant %13 133169152 +%7 = OpFunction %6 None %5 +%8 = OpLabel +%11 = OpLoad %3 %1 +%17 = OpBitcast %13 %11 +%18 = OpBitwiseAnd %13 %17 %14 +%19 = OpExtInst %13 %12 UClamp %18 %15 %16 +%9 = OpIEqual %10 %18 %19 +OpReturn +OpFunctionEnd +)"); +} + +TEST_F(IntrinsicBuilderTest, IsNormal_Vector) { + auto* var = Global("v", ty.vec2(), ast::StorageClass::kPrivate); + + auto* expr = Call("isNormal", "v"); + WrapInFunction(expr); + + auto* func = Func("a_func", ast::VariableList{}, ty.void_(), + ast::StatementList{}, ast::DecorationList{}); + + spirv::Builder& b = Build(); + + ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error(); + ASSERT_TRUE(b.GenerateFunction(func)) << b.error(); + + EXPECT_EQ(b.GenerateCallExpression(expr), 10u) << b.error(); + auto got = DumpBuilder(b); + std::cout << got << std::endl; + EXPECT_EQ(got, R"(%14 = OpExtInstImport "GLSL.std.450" +OpName %1 "v" +OpName %8 "a_func" +%4 = OpTypeFloat 32 +%3 = OpTypeVector %4 2 +%2 = OpTypePointer Private %3 +%5 = OpConstantNull %3 +%1 = OpVariable %2 Private %5 +%7 = OpTypeVoid +%6 = OpTypeFunction %7 +%12 = OpTypeBool +%11 = OpTypeVector %12 2 +%15 = OpTypeInt 32 0 +%16 = OpConstant %15 133693440 +%17 = OpConstant %15 524288 +%18 = OpConstant %15 133169152 +%19 = OpTypeVector %15 2 +%8 = OpFunction %7 None %6 +%9 = OpLabel +%13 = OpLoad %3 %1 +%20 = OpCompositeConstruct %19 %16 %16 +%21 = OpCompositeConstruct %19 %17 %17 +%22 = OpCompositeConstruct %19 %18 %18 +%23 = OpBitcast %19 %13 +%24 = OpBitwiseAnd %19 %23 %20 +%25 = OpExtInst %19 %14 UClamp %24 %21 %22 +%10 = OpIEqual %11 %24 %25 +OpReturn +OpFunctionEnd +)"); +} + using IntrinsicIntTest = IntrinsicBuilderTestWithParam; TEST_P(IntrinsicIntTest, Call_SInt_Scalar) { auto param = GetParam();