[ir][spirv-writer] Polyfill first*Bit builtins
Add config options to polyfill firstLeadingBit and firstTrailingBit to
the generic BuiltinPolyfill transform.
Bug: tint:1906
Change-Id: I366eb8900cf79487beef7a701686e2b9f23a93d3
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/144981
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Auto-Submit: James Price <jrprice@google.com>
diff --git a/src/tint/lang/core/ir/transform/builtin_polyfill.cc b/src/tint/lang/core/ir/transform/builtin_polyfill.cc
index db6563f..8d96925 100644
--- a/src/tint/lang/core/ir/transform/builtin_polyfill.cc
+++ b/src/tint/lang/core/ir/transform/builtin_polyfill.cc
@@ -54,6 +54,16 @@
}
if (auto* builtin = inst->As<ir::CoreBuiltinCall>()) {
switch (builtin->Func()) {
+ case core::Function::kFirstLeadingBit:
+ if (config.first_leading_bit) {
+ worklist.Push(builtin);
+ }
+ break;
+ case core::Function::kFirstTrailingBit:
+ if (config.first_trailing_bit) {
+ worklist.Push(builtin);
+ }
+ break;
case core::Function::kSaturate:
if (config.saturate) {
worklist.Push(builtin);
@@ -69,6 +79,12 @@
for (auto* builtin : worklist) {
ir::Value* replacement = nullptr;
switch (builtin->Func()) {
+ case core::Function::kFirstLeadingBit:
+ replacement = FirstLeadingBit(builtin);
+ break;
+ case core::Function::kFirstTrailingBit:
+ replacement = FirstTrailingBit(builtin);
+ break;
case core::Function::kSaturate:
replacement = Saturate(builtin);
break;
@@ -86,6 +102,154 @@
}
}
+ /// Return a type with element type @p type that has the same number of vector components as
+ /// @p match. If @p match is scalar just return @p type.
+ /// @param el_ty the type to extend
+ /// @param match the type to match the component count of
+ /// @returns a type with the same number of vector components as @p match
+ const type::Type* MatchWidth(const type::Type* el_ty, const type::Type* match) {
+ if (auto* vec = match->As<type::Vector>()) {
+ return ty.vec(el_ty, vec->Width());
+ }
+ return el_ty;
+ }
+
+ /// Return a constant that has the same number of vector components as @p match, each with the
+ /// value @p element. If @p match is scalar just return @p element.
+ /// @param element the value to extend
+ /// @param match the type to match the component count of
+ /// @returns a value with the same number of vector components as @p match
+ ir::Constant* MatchWidth(ir::Constant* element, const type::Type* match) {
+ if (auto* vec = match->As<type::Vector>()) {
+ return b.Splat(MatchWidth(element->Type(), match), element, vec->Width());
+ }
+ return element;
+ }
+
+ /// Polyfill a `firstLeadingBit()` builtin call.
+ /// @param call the builtin call instruction
+ /// @returns the replacement value
+ ir::Value* FirstLeadingBit(ir::CoreBuiltinCall* call) {
+ auto* input = call->Args()[0];
+ auto* result_ty = input->Type();
+ auto* uint_ty = MatchWidth(ty.u32(), result_ty);
+ auto* bool_ty = MatchWidth(ty.bool_(), result_ty);
+
+ // Make an u32 constant with the same component count as result_ty.
+ auto V = [&](uint32_t u) { return MatchWidth(b.Constant(u32(u)), result_ty); };
+
+ Value* result = nullptr;
+ b.InsertBefore(call, [&] {
+ // %x = %input;
+ // if (%x is signed) {
+ // %x = select(u32(%x), ~u32(%x), x > 0x80000000);
+ // }
+ // %b16 = select(16, 0, (%x & 0xffff0000) == 0);
+ // %x >>= %b16;
+ // %b8 = select(8, 0, (%x & 0x0000ff00) == 0);
+ // %x >>= %b8;
+ // %b4 = select(4, 0, (%x & 0x000000f0) == 0);
+ // %x >>= %b4;
+ // %b2 = select(2, 0, (%x & 0x0000000c) == 0);
+ // %x >>= %b2;
+ // %b1 = select(1, 0, (%x & 0x00000002) == 0);
+ // %result = %b16 | %b8 | %b4 | %b2 | %b1;
+ // %result = select(%result, 0xffffffff, %x == 0);
+
+ auto* x = input;
+ if (result_ty->is_signed_integer_scalar_or_vector()) {
+ x = b.Bitcast(uint_ty, x)->Result();
+ auto* inverted = b.Complement(uint_ty, x);
+ x = b.Call(uint_ty, core::Function::kSelect, inverted, x,
+ b.LessThan(bool_ty, x, V(0x80000000)))
+ ->Result();
+ }
+ auto* b16 = b.Call(uint_ty, core::Function::kSelect, V(16), V(0),
+ b.Equal(bool_ty, b.And(uint_ty, x, V(0xffff0000)), V(0)));
+ x = b.ShiftRight(uint_ty, x, b16)->Result();
+ auto* b8 = b.Call(uint_ty, core::Function::kSelect, V(8), V(0),
+ b.Equal(bool_ty, b.And(uint_ty, x, V(0x0000ff00)), V(0)));
+ x = b.ShiftRight(uint_ty, x, b8)->Result();
+ auto* b4 = b.Call(uint_ty, core::Function::kSelect, V(4), V(0),
+ b.Equal(bool_ty, b.And(uint_ty, x, V(0x000000f0)), V(0)));
+ x = b.ShiftRight(uint_ty, x, b4)->Result();
+ auto* b2 = b.Call(uint_ty, core::Function::kSelect, V(2), V(0),
+ b.Equal(bool_ty, b.And(uint_ty, x, V(0x0000000c)), V(0)));
+ x = b.ShiftRight(uint_ty, x, b2)->Result();
+ auto* b1 = b.Call(uint_ty, core::Function::kSelect, V(1), V(0),
+ b.Equal(bool_ty, b.And(uint_ty, x, V(0x00000002)), V(0)));
+ result = b.Or(uint_ty, b16, b.Or(uint_ty, b8, b.Or(uint_ty, b4, b.Or(uint_ty, b2, b1))))
+ ->Result();
+ result = b.Call(uint_ty, core::Function::kSelect, result, V(0xffffffff),
+ b.Equal(bool_ty, x, V(0)))
+ ->Result();
+ if (result_ty->is_signed_integer_scalar_or_vector()) {
+ result = b.Bitcast(result_ty, result)->Result();
+ }
+ });
+ return result;
+ }
+
+ /// Polyfill a `firstTrailingBit()` builtin call.
+ /// @param call the builtin call instruction
+ /// @returns the replacement value
+ ir::Value* FirstTrailingBit(ir::CoreBuiltinCall* call) {
+ auto* input = call->Args()[0];
+ auto* result_ty = input->Type();
+ auto* uint_ty = MatchWidth(ty.u32(), result_ty);
+ auto* bool_ty = MatchWidth(ty.bool_(), result_ty);
+
+ // Make an u32 constant with the same component count as result_ty.
+ auto V = [&](uint32_t u) { return MatchWidth(b.Constant(u32(u)), result_ty); };
+
+ Value* result = nullptr;
+ b.InsertBefore(call, [&] {
+ // %x = %input;
+ // if (%x is signed) {
+ // %x = bitcast<u32>(%x)
+ // }
+ // %b16 = select(0, 16, (%x & 0x0000ffff) == 0);
+ // %x >>= %b16;
+ // %b8 = select(0, 8, (%x & 0x000000ff) == 0);
+ // %x >>= %b8;
+ // %b4 = select(0, 4, (%x & 0x0000000f) == 0);
+ // %x >>= %b4;
+ // %b2 = select(0, 2, (%x & 0x00000003) == 0);
+ // %x >>= %b2;
+ // %b1 = select(0, 1, (%x & 0x00000001) == 0);
+ // %result = %b16 | %b8 | %b4 | %b2 | %b1;
+ // %result = select(%result, 0xffffffff, %x == 0);
+
+ auto* x = input;
+ if (result_ty->is_signed_integer_scalar_or_vector()) {
+ x = b.Bitcast(uint_ty, x)->Result();
+ }
+ auto* b16 = b.Call(uint_ty, core::Function::kSelect, V(0), V(16),
+ b.Equal(bool_ty, b.And(uint_ty, x, V(0x0000ffff)), V(0)));
+ x = b.ShiftRight(uint_ty, x, b16)->Result();
+ auto* b8 = b.Call(uint_ty, core::Function::kSelect, V(0), V(8),
+ b.Equal(bool_ty, b.And(uint_ty, x, V(0x000000ff)), V(0)));
+ x = b.ShiftRight(uint_ty, x, b8)->Result();
+ auto* b4 = b.Call(uint_ty, core::Function::kSelect, V(0), V(4),
+ b.Equal(bool_ty, b.And(uint_ty, x, V(0x0000000f)), V(0)));
+ x = b.ShiftRight(uint_ty, x, b4)->Result();
+ auto* b2 = b.Call(uint_ty, core::Function::kSelect, V(0), V(2),
+ b.Equal(bool_ty, b.And(uint_ty, x, V(0x00000003)), V(0)));
+ x = b.ShiftRight(uint_ty, x, b2)->Result();
+ auto* b1 = b.Call(uint_ty, core::Function::kSelect, V(0), V(1),
+ b.Equal(bool_ty, b.And(uint_ty, x, V(0x00000001)), V(0)));
+ result = b.Or(uint_ty, b16, b.Or(uint_ty, b8, b.Or(uint_ty, b4, b.Or(uint_ty, b2, b1))))
+ ->Result();
+ result = b.Call(uint_ty, core::Function::kSelect, result, V(0xffffffff),
+ b.Equal(bool_ty, x, V(0)))
+ ->Result();
+ if (result_ty->is_signed_integer_scalar_or_vector()) {
+ result = b.Bitcast(result_ty, result)->Result();
+ }
+ });
+ return result;
+ }
+
/// Polyfill a `saturate()` builtin call.
/// @param call the builtin call instruction
/// @returns the replacement value
@@ -95,16 +259,11 @@
ir::Constant* zero = nullptr;
ir::Constant* one = nullptr;
if (type->DeepestElement()->Is<type::F32>()) {
- zero = b.Constant(0_f);
- one = b.Constant(1_f);
+ zero = MatchWidth(b.Constant(0_f), type);
+ one = MatchWidth(b.Constant(1_f), type);
} else if (type->DeepestElement()->Is<type::F16>()) {
- zero = b.Constant(0_h);
- one = b.Constant(1_h);
- }
- if (auto* vec = type->As<type::Vector>()) {
- // Splat the zero and one arguments to vectors.
- zero = b.Splat(vec, zero, vec->Width());
- one = b.Splat(vec, one, vec->Width());
+ zero = MatchWidth(b.Constant(0_h), type);
+ one = MatchWidth(b.Constant(1_h), type);
}
auto* clamp = b.Call(type, core::Function::kClamp, Vector{call->Args()[0], zero, one});
clamp->InsertBefore(call);
diff --git a/src/tint/lang/core/ir/transform/builtin_polyfill.h b/src/tint/lang/core/ir/transform/builtin_polyfill.h
index 948b946..1ec91a8 100644
--- a/src/tint/lang/core/ir/transform/builtin_polyfill.h
+++ b/src/tint/lang/core/ir/transform/builtin_polyfill.h
@@ -28,6 +28,10 @@
/// The set of polyfills that should be applied.
struct BuiltinPolyfillConfig {
+ /// Should `firstLeadingBit()` be polyfilled?
+ bool first_leading_bit = false;
+ /// Should `firstTrailingBit()` be polyfilled?
+ bool first_trailing_bit = false;
/// Should `saturate()` be polyfilled?
bool saturate = false;
};
diff --git a/src/tint/lang/core/ir/transform/builtin_polyfill_test.cc b/src/tint/lang/core/ir/transform/builtin_polyfill_test.cc
index 62bdb1f..000ddf2 100644
--- a/src/tint/lang/core/ir/transform/builtin_polyfill_test.cc
+++ b/src/tint/lang/core/ir/transform/builtin_polyfill_test.cc
@@ -174,5 +174,467 @@
EXPECT_EQ(expect, str());
}
+TEST_F(IR_BuiltinPolyfillTest, FirstLeadingBit_NoPolyfill) {
+ Build(core::Function::kFirstLeadingBit, ty.u32(), Vector{ty.u32()});
+ auto* src = R"(
+%foo = func(%arg:u32):u32 -> %b1 {
+ %b1 = block {
+ %result:u32 = firstLeadingBit %arg
+ ret %result
+ }
+}
+)";
+ auto* expect = src;
+
+ EXPECT_EQ(src, str());
+
+ BuiltinPolyfillConfig config;
+ config.first_leading_bit = false;
+ Run(BuiltinPolyfill, config);
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BuiltinPolyfillTest, FirstLeadingBit_U32) {
+ Build(core::Function::kFirstLeadingBit, ty.u32(), Vector{ty.u32()});
+ auto* src = R"(
+%foo = func(%arg:u32):u32 -> %b1 {
+ %b1 = block {
+ %result:u32 = firstLeadingBit %arg
+ ret %result
+ }
+}
+)";
+ auto* expect = R"(
+%foo = func(%arg:u32):u32 -> %b1 {
+ %b1 = block {
+ %3:u32 = and %arg, 4294901760u
+ %4:bool = eq %3, 0u
+ %5:u32 = select 16u, 0u, %4
+ %6:u32 = shiftr %arg, %5
+ %7:u32 = and %6, 65280u
+ %8:bool = eq %7, 0u
+ %9:u32 = select 8u, 0u, %8
+ %10:u32 = shiftr %6, %9
+ %11:u32 = and %10, 240u
+ %12:bool = eq %11, 0u
+ %13:u32 = select 4u, 0u, %12
+ %14:u32 = shiftr %10, %13
+ %15:u32 = and %14, 12u
+ %16:bool = eq %15, 0u
+ %17:u32 = select 2u, 0u, %16
+ %18:u32 = shiftr %14, %17
+ %19:u32 = and %18, 2u
+ %20:bool = eq %19, 0u
+ %21:u32 = select 1u, 0u, %20
+ %22:u32 = or %17, %21
+ %23:u32 = or %13, %22
+ %24:u32 = or %9, %23
+ %25:u32 = or %5, %24
+ %26:bool = eq %18, 0u
+ %result:u32 = select %25, 4294967295u, %26
+ ret %result
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ BuiltinPolyfillConfig config;
+ config.first_leading_bit = true;
+ Run(BuiltinPolyfill, config);
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BuiltinPolyfillTest, FirstLeadingBit_I32) {
+ Build(core::Function::kFirstLeadingBit, ty.i32(), Vector{ty.i32()});
+ auto* src = R"(
+%foo = func(%arg:i32):i32 -> %b1 {
+ %b1 = block {
+ %result:i32 = firstLeadingBit %arg
+ ret %result
+ }
+}
+)";
+ auto* expect = R"(
+%foo = func(%arg:i32):i32 -> %b1 {
+ %b1 = block {
+ %3:u32 = bitcast %arg
+ %4:u32 = complement %3
+ %5:bool = lt %3, 2147483648u
+ %6:u32 = select %4, %3, %5
+ %7:u32 = and %6, 4294901760u
+ %8:bool = eq %7, 0u
+ %9:u32 = select 16u, 0u, %8
+ %10:u32 = shiftr %6, %9
+ %11:u32 = and %10, 65280u
+ %12:bool = eq %11, 0u
+ %13:u32 = select 8u, 0u, %12
+ %14:u32 = shiftr %10, %13
+ %15:u32 = and %14, 240u
+ %16:bool = eq %15, 0u
+ %17:u32 = select 4u, 0u, %16
+ %18:u32 = shiftr %14, %17
+ %19:u32 = and %18, 12u
+ %20:bool = eq %19, 0u
+ %21:u32 = select 2u, 0u, %20
+ %22:u32 = shiftr %18, %21
+ %23:u32 = and %22, 2u
+ %24:bool = eq %23, 0u
+ %25:u32 = select 1u, 0u, %24
+ %26:u32 = or %21, %25
+ %27:u32 = or %17, %26
+ %28:u32 = or %13, %27
+ %29:u32 = or %9, %28
+ %30:bool = eq %22, 0u
+ %31:u32 = select %29, 4294967295u, %30
+ %result:i32 = bitcast %31
+ ret %result
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ BuiltinPolyfillConfig config;
+ config.first_leading_bit = true;
+ Run(BuiltinPolyfill, config);
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BuiltinPolyfillTest, FirstLeadingBit_Vec2U32) {
+ Build(core::Function::kFirstLeadingBit, ty.vec2<u32>(), Vector{ty.vec2<u32>()});
+ auto* src = R"(
+%foo = func(%arg:vec2<u32>):vec2<u32> -> %b1 {
+ %b1 = block {
+ %result:vec2<u32> = firstLeadingBit %arg
+ ret %result
+ }
+}
+)";
+ auto* expect = R"(
+%foo = func(%arg:vec2<u32>):vec2<u32> -> %b1 {
+ %b1 = block {
+ %3:vec2<u32> = and %arg, vec2<u32>(4294901760u)
+ %4:vec2<bool> = eq %3, vec2<u32>(0u)
+ %5:vec2<u32> = select vec2<u32>(16u), vec2<u32>(0u), %4
+ %6:vec2<u32> = shiftr %arg, %5
+ %7:vec2<u32> = and %6, vec2<u32>(65280u)
+ %8:vec2<bool> = eq %7, vec2<u32>(0u)
+ %9:vec2<u32> = select vec2<u32>(8u), vec2<u32>(0u), %8
+ %10:vec2<u32> = shiftr %6, %9
+ %11:vec2<u32> = and %10, vec2<u32>(240u)
+ %12:vec2<bool> = eq %11, vec2<u32>(0u)
+ %13:vec2<u32> = select vec2<u32>(4u), vec2<u32>(0u), %12
+ %14:vec2<u32> = shiftr %10, %13
+ %15:vec2<u32> = and %14, vec2<u32>(12u)
+ %16:vec2<bool> = eq %15, vec2<u32>(0u)
+ %17:vec2<u32> = select vec2<u32>(2u), vec2<u32>(0u), %16
+ %18:vec2<u32> = shiftr %14, %17
+ %19:vec2<u32> = and %18, vec2<u32>(2u)
+ %20:vec2<bool> = eq %19, vec2<u32>(0u)
+ %21:vec2<u32> = select vec2<u32>(1u), vec2<u32>(0u), %20
+ %22:vec2<u32> = or %17, %21
+ %23:vec2<u32> = or %13, %22
+ %24:vec2<u32> = or %9, %23
+ %25:vec2<u32> = or %5, %24
+ %26:vec2<bool> = eq %18, vec2<u32>(0u)
+ %result:vec2<u32> = select %25, vec2<u32>(4294967295u), %26
+ ret %result
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ BuiltinPolyfillConfig config;
+ config.first_leading_bit = true;
+ Run(BuiltinPolyfill, config);
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BuiltinPolyfillTest, FirstLeadingBit_Vec4I32) {
+ Build(core::Function::kFirstLeadingBit, ty.vec4<i32>(), Vector{ty.vec4<i32>()});
+ auto* src = R"(
+%foo = func(%arg:vec4<i32>):vec4<i32> -> %b1 {
+ %b1 = block {
+ %result:vec4<i32> = firstLeadingBit %arg
+ ret %result
+ }
+}
+)";
+ auto* expect = R"(
+%foo = func(%arg:vec4<i32>):vec4<i32> -> %b1 {
+ %b1 = block {
+ %3:vec4<u32> = bitcast %arg
+ %4:vec4<u32> = complement %3
+ %5:vec4<bool> = lt %3, vec4<u32>(2147483648u)
+ %6:vec4<u32> = select %4, %3, %5
+ %7:vec4<u32> = and %6, vec4<u32>(4294901760u)
+ %8:vec4<bool> = eq %7, vec4<u32>(0u)
+ %9:vec4<u32> = select vec4<u32>(16u), vec4<u32>(0u), %8
+ %10:vec4<u32> = shiftr %6, %9
+ %11:vec4<u32> = and %10, vec4<u32>(65280u)
+ %12:vec4<bool> = eq %11, vec4<u32>(0u)
+ %13:vec4<u32> = select vec4<u32>(8u), vec4<u32>(0u), %12
+ %14:vec4<u32> = shiftr %10, %13
+ %15:vec4<u32> = and %14, vec4<u32>(240u)
+ %16:vec4<bool> = eq %15, vec4<u32>(0u)
+ %17:vec4<u32> = select vec4<u32>(4u), vec4<u32>(0u), %16
+ %18:vec4<u32> = shiftr %14, %17
+ %19:vec4<u32> = and %18, vec4<u32>(12u)
+ %20:vec4<bool> = eq %19, vec4<u32>(0u)
+ %21:vec4<u32> = select vec4<u32>(2u), vec4<u32>(0u), %20
+ %22:vec4<u32> = shiftr %18, %21
+ %23:vec4<u32> = and %22, vec4<u32>(2u)
+ %24:vec4<bool> = eq %23, vec4<u32>(0u)
+ %25:vec4<u32> = select vec4<u32>(1u), vec4<u32>(0u), %24
+ %26:vec4<u32> = or %21, %25
+ %27:vec4<u32> = or %17, %26
+ %28:vec4<u32> = or %13, %27
+ %29:vec4<u32> = or %9, %28
+ %30:vec4<bool> = eq %22, vec4<u32>(0u)
+ %31:vec4<u32> = select %29, vec4<u32>(4294967295u), %30
+ %result:vec4<i32> = bitcast %31
+ ret %result
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ BuiltinPolyfillConfig config;
+ config.first_leading_bit = true;
+ Run(BuiltinPolyfill, config);
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BuiltinPolyfillTest, FirstTrailingBit_NoPolyfill) {
+ Build(core::Function::kFirstTrailingBit, ty.u32(), Vector{ty.u32()});
+ auto* src = R"(
+%foo = func(%arg:u32):u32 -> %b1 {
+ %b1 = block {
+ %result:u32 = firstTrailingBit %arg
+ ret %result
+ }
+}
+)";
+ auto* expect = src;
+
+ EXPECT_EQ(src, str());
+
+ BuiltinPolyfillConfig config;
+ config.first_trailing_bit = false;
+ Run(BuiltinPolyfill, config);
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BuiltinPolyfillTest, FirstTrailingBit_U32) {
+ Build(core::Function::kFirstTrailingBit, ty.u32(), Vector{ty.u32()});
+ auto* src = R"(
+%foo = func(%arg:u32):u32 -> %b1 {
+ %b1 = block {
+ %result:u32 = firstTrailingBit %arg
+ ret %result
+ }
+}
+)";
+ auto* expect = R"(
+%foo = func(%arg:u32):u32 -> %b1 {
+ %b1 = block {
+ %3:u32 = and %arg, 65535u
+ %4:bool = eq %3, 0u
+ %5:u32 = select 0u, 16u, %4
+ %6:u32 = shiftr %arg, %5
+ %7:u32 = and %6, 255u
+ %8:bool = eq %7, 0u
+ %9:u32 = select 0u, 8u, %8
+ %10:u32 = shiftr %6, %9
+ %11:u32 = and %10, 15u
+ %12:bool = eq %11, 0u
+ %13:u32 = select 0u, 4u, %12
+ %14:u32 = shiftr %10, %13
+ %15:u32 = and %14, 3u
+ %16:bool = eq %15, 0u
+ %17:u32 = select 0u, 2u, %16
+ %18:u32 = shiftr %14, %17
+ %19:u32 = and %18, 1u
+ %20:bool = eq %19, 0u
+ %21:u32 = select 0u, 1u, %20
+ %22:u32 = or %17, %21
+ %23:u32 = or %13, %22
+ %24:u32 = or %9, %23
+ %25:u32 = or %5, %24
+ %26:bool = eq %18, 0u
+ %result:u32 = select %25, 4294967295u, %26
+ ret %result
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ BuiltinPolyfillConfig config;
+ config.first_trailing_bit = true;
+ Run(BuiltinPolyfill, config);
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BuiltinPolyfillTest, FirstTrailingBit_I32) {
+ Build(core::Function::kFirstTrailingBit, ty.i32(), Vector{ty.i32()});
+ auto* src = R"(
+%foo = func(%arg:i32):i32 -> %b1 {
+ %b1 = block {
+ %result:i32 = firstTrailingBit %arg
+ ret %result
+ }
+}
+)";
+ auto* expect = R"(
+%foo = func(%arg:i32):i32 -> %b1 {
+ %b1 = block {
+ %3:u32 = bitcast %arg
+ %4:u32 = and %3, 65535u
+ %5:bool = eq %4, 0u
+ %6:u32 = select 0u, 16u, %5
+ %7:u32 = shiftr %3, %6
+ %8:u32 = and %7, 255u
+ %9:bool = eq %8, 0u
+ %10:u32 = select 0u, 8u, %9
+ %11:u32 = shiftr %7, %10
+ %12:u32 = and %11, 15u
+ %13:bool = eq %12, 0u
+ %14:u32 = select 0u, 4u, %13
+ %15:u32 = shiftr %11, %14
+ %16:u32 = and %15, 3u
+ %17:bool = eq %16, 0u
+ %18:u32 = select 0u, 2u, %17
+ %19:u32 = shiftr %15, %18
+ %20:u32 = and %19, 1u
+ %21:bool = eq %20, 0u
+ %22:u32 = select 0u, 1u, %21
+ %23:u32 = or %18, %22
+ %24:u32 = or %14, %23
+ %25:u32 = or %10, %24
+ %26:u32 = or %6, %25
+ %27:bool = eq %19, 0u
+ %28:u32 = select %26, 4294967295u, %27
+ %result:i32 = bitcast %28
+ ret %result
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ BuiltinPolyfillConfig config;
+ config.first_trailing_bit = true;
+ Run(BuiltinPolyfill, config);
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BuiltinPolyfillTest, FirstTrailingBit_Vec2U32) {
+ Build(core::Function::kFirstTrailingBit, ty.vec2<u32>(), Vector{ty.vec2<u32>()});
+ auto* src = R"(
+%foo = func(%arg:vec2<u32>):vec2<u32> -> %b1 {
+ %b1 = block {
+ %result:vec2<u32> = firstTrailingBit %arg
+ ret %result
+ }
+}
+)";
+ auto* expect = R"(
+%foo = func(%arg:vec2<u32>):vec2<u32> -> %b1 {
+ %b1 = block {
+ %3:vec2<u32> = and %arg, vec2<u32>(65535u)
+ %4:vec2<bool> = eq %3, vec2<u32>(0u)
+ %5:vec2<u32> = select vec2<u32>(0u), vec2<u32>(16u), %4
+ %6:vec2<u32> = shiftr %arg, %5
+ %7:vec2<u32> = and %6, vec2<u32>(255u)
+ %8:vec2<bool> = eq %7, vec2<u32>(0u)
+ %9:vec2<u32> = select vec2<u32>(0u), vec2<u32>(8u), %8
+ %10:vec2<u32> = shiftr %6, %9
+ %11:vec2<u32> = and %10, vec2<u32>(15u)
+ %12:vec2<bool> = eq %11, vec2<u32>(0u)
+ %13:vec2<u32> = select vec2<u32>(0u), vec2<u32>(4u), %12
+ %14:vec2<u32> = shiftr %10, %13
+ %15:vec2<u32> = and %14, vec2<u32>(3u)
+ %16:vec2<bool> = eq %15, vec2<u32>(0u)
+ %17:vec2<u32> = select vec2<u32>(0u), vec2<u32>(2u), %16
+ %18:vec2<u32> = shiftr %14, %17
+ %19:vec2<u32> = and %18, vec2<u32>(1u)
+ %20:vec2<bool> = eq %19, vec2<u32>(0u)
+ %21:vec2<u32> = select vec2<u32>(0u), vec2<u32>(1u), %20
+ %22:vec2<u32> = or %17, %21
+ %23:vec2<u32> = or %13, %22
+ %24:vec2<u32> = or %9, %23
+ %25:vec2<u32> = or %5, %24
+ %26:vec2<bool> = eq %18, vec2<u32>(0u)
+ %result:vec2<u32> = select %25, vec2<u32>(4294967295u), %26
+ ret %result
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ BuiltinPolyfillConfig config;
+ config.first_trailing_bit = true;
+ Run(BuiltinPolyfill, config);
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BuiltinPolyfillTest, FirstTrailingBit_Vec4I32) {
+ Build(core::Function::kFirstTrailingBit, ty.vec4<i32>(), Vector{ty.vec4<i32>()});
+ auto* src = R"(
+%foo = func(%arg:vec4<i32>):vec4<i32> -> %b1 {
+ %b1 = block {
+ %result:vec4<i32> = firstTrailingBit %arg
+ ret %result
+ }
+}
+)";
+ auto* expect = R"(
+%foo = func(%arg:vec4<i32>):vec4<i32> -> %b1 {
+ %b1 = block {
+ %3:vec4<u32> = bitcast %arg
+ %4:vec4<u32> = and %3, vec4<u32>(65535u)
+ %5:vec4<bool> = eq %4, vec4<u32>(0u)
+ %6:vec4<u32> = select vec4<u32>(0u), vec4<u32>(16u), %5
+ %7:vec4<u32> = shiftr %3, %6
+ %8:vec4<u32> = and %7, vec4<u32>(255u)
+ %9:vec4<bool> = eq %8, vec4<u32>(0u)
+ %10:vec4<u32> = select vec4<u32>(0u), vec4<u32>(8u), %9
+ %11:vec4<u32> = shiftr %7, %10
+ %12:vec4<u32> = and %11, vec4<u32>(15u)
+ %13:vec4<bool> = eq %12, vec4<u32>(0u)
+ %14:vec4<u32> = select vec4<u32>(0u), vec4<u32>(4u), %13
+ %15:vec4<u32> = shiftr %11, %14
+ %16:vec4<u32> = and %15, vec4<u32>(3u)
+ %17:vec4<bool> = eq %16, vec4<u32>(0u)
+ %18:vec4<u32> = select vec4<u32>(0u), vec4<u32>(2u), %17
+ %19:vec4<u32> = shiftr %15, %18
+ %20:vec4<u32> = and %19, vec4<u32>(1u)
+ %21:vec4<bool> = eq %20, vec4<u32>(0u)
+ %22:vec4<u32> = select vec4<u32>(0u), vec4<u32>(1u), %21
+ %23:vec4<u32> = or %18, %22
+ %24:vec4<u32> = or %14, %23
+ %25:vec4<u32> = or %10, %24
+ %26:vec4<u32> = or %6, %25
+ %27:vec4<bool> = eq %19, vec4<u32>(0u)
+ %28:vec4<u32> = select %26, vec4<u32>(4294967295u), %27
+ %result:vec4<i32> = bitcast %28
+ ret %result
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ BuiltinPolyfillConfig config;
+ config.first_trailing_bit = true;
+ Run(BuiltinPolyfill, config);
+ EXPECT_EQ(expect, str());
+}
+
} // namespace
} // namespace tint::ir::transform
diff --git a/src/tint/lang/spirv/writer/builtin_test.cc b/src/tint/lang/spirv/writer/builtin_test.cc
index d180b56..2ba7054 100644
--- a/src/tint/lang/spirv/writer/builtin_test.cc
+++ b/src/tint/lang/spirv/writer/builtin_test.cc
@@ -598,6 +598,275 @@
EXPECT_INST("%result = OpExtInst %v4float %9 UnpackUnorm4x8 %arg");
}
+TEST_F(SpirvWriterTest, Builtin_FirstLeadingBit_U32) {
+ auto* arg = b.FunctionParam("arg", ty.u32());
+ auto* func = b.Function("foo", ty.u32());
+ func->SetParams({arg});
+ b.Append(func->Block(), [&] {
+ auto* result = b.Call(ty.u32(), core::Function::kFirstLeadingBit, arg);
+ b.Return(func, result);
+ mod.SetName(result, "result");
+ });
+
+ ASSERT_TRUE(Generate()) << Error() << output_;
+ EXPECT_INST(R"(
+ %6 = OpBitwiseAnd %uint %arg %uint_4294901760
+ %8 = OpIEqual %bool %6 %uint_0
+ %11 = OpSelect %uint %8 %uint_0 %uint_16
+ %13 = OpShiftRightLogical %uint %arg %11
+ %14 = OpBitwiseAnd %uint %13 %uint_65280
+ %16 = OpIEqual %bool %14 %uint_0
+ %17 = OpSelect %uint %16 %uint_0 %uint_8
+ %19 = OpShiftRightLogical %uint %13 %17
+ %20 = OpBitwiseAnd %uint %19 %uint_240
+ %22 = OpIEqual %bool %20 %uint_0
+ %23 = OpSelect %uint %22 %uint_0 %uint_4
+ %25 = OpShiftRightLogical %uint %19 %23
+ %26 = OpBitwiseAnd %uint %25 %uint_12
+ %28 = OpIEqual %bool %26 %uint_0
+ %29 = OpSelect %uint %28 %uint_0 %uint_2
+ %31 = OpShiftRightLogical %uint %25 %29
+ %32 = OpBitwiseAnd %uint %31 %uint_2
+ %33 = OpIEqual %bool %32 %uint_0
+ %34 = OpSelect %uint %33 %uint_0 %uint_1
+ %36 = OpBitwiseOr %uint %29 %34
+ %37 = OpBitwiseOr %uint %23 %36
+ %38 = OpBitwiseOr %uint %17 %37
+ %39 = OpBitwiseOr %uint %11 %38
+ %40 = OpIEqual %bool %31 %uint_0
+ %result = OpSelect %uint %40 %uint_4294967295 %39
+)");
+}
+
+TEST_F(SpirvWriterTest, Builtin_FirstLeadingBit_I32) {
+ auto* arg = b.FunctionParam("arg", ty.i32());
+ auto* func = b.Function("foo", ty.i32());
+ func->SetParams({arg});
+ b.Append(func->Block(), [&] {
+ auto* result = b.Call(ty.i32(), core::Function::kFirstLeadingBit, arg);
+ b.Return(func, result);
+ mod.SetName(result, "result");
+ });
+
+ ASSERT_TRUE(Generate()) << Error() << output_;
+ EXPECT_INST(R"(
+ %7 = OpBitcast %uint %arg
+ %8 = OpNot %uint %7
+ %9 = OpULessThan %bool %7 %uint_2147483648
+ %12 = OpSelect %uint %9 %7 %8
+ %13 = OpBitwiseAnd %uint %12 %uint_4294901760
+ %15 = OpIEqual %bool %13 %uint_0
+ %17 = OpSelect %uint %15 %uint_0 %uint_16
+ %19 = OpShiftRightLogical %uint %12 %17
+ %20 = OpBitwiseAnd %uint %19 %uint_65280
+ %22 = OpIEqual %bool %20 %uint_0
+ %23 = OpSelect %uint %22 %uint_0 %uint_8
+ %25 = OpShiftRightLogical %uint %19 %23
+ %26 = OpBitwiseAnd %uint %25 %uint_240
+ %28 = OpIEqual %bool %26 %uint_0
+ %29 = OpSelect %uint %28 %uint_0 %uint_4
+ %31 = OpShiftRightLogical %uint %25 %29
+ %32 = OpBitwiseAnd %uint %31 %uint_12
+ %34 = OpIEqual %bool %32 %uint_0
+ %35 = OpSelect %uint %34 %uint_0 %uint_2
+ %37 = OpShiftRightLogical %uint %31 %35
+ %38 = OpBitwiseAnd %uint %37 %uint_2
+ %39 = OpIEqual %bool %38 %uint_0
+ %40 = OpSelect %uint %39 %uint_0 %uint_1
+ %42 = OpBitwiseOr %uint %35 %40
+ %43 = OpBitwiseOr %uint %29 %42
+ %44 = OpBitwiseOr %uint %23 %43
+ %45 = OpBitwiseOr %uint %17 %44
+ %46 = OpIEqual %bool %37 %uint_0
+ %47 = OpSelect %uint %46 %uint_4294967295 %45
+ %result = OpBitcast %int %47
+)");
+}
+
+TEST_F(SpirvWriterTest, Builtin_FirstLeadingBit_Vec2U32) {
+ auto* arg = b.FunctionParam("arg", ty.vec2<u32>());
+ auto* func = b.Function("foo", ty.vec2<u32>());
+ func->SetParams({arg});
+ b.Append(func->Block(), [&] {
+ auto* result = b.Call(ty.vec2<u32>(), core::Function::kFirstLeadingBit, arg);
+ b.Return(func, result);
+ mod.SetName(result, "result");
+ });
+
+ ASSERT_TRUE(Generate()) << Error() << output_;
+ EXPECT_INST("%8 = OpConstantComposite %v2uint %uint_4294901760 %uint_4294901760");
+ EXPECT_INST("%11 = OpConstantComposite %v2uint %uint_0 %uint_0");
+ EXPECT_INST("%16 = OpConstantComposite %v2uint %uint_16 %uint_16");
+ EXPECT_INST("%20 = OpConstantComposite %v2uint %uint_65280 %uint_65280");
+ EXPECT_INST("%24 = OpConstantComposite %v2uint %uint_8 %uint_8");
+ EXPECT_INST("%28 = OpConstantComposite %v2uint %uint_240 %uint_240");
+ EXPECT_INST("%32 = OpConstantComposite %v2uint %uint_4 %uint_4");
+ EXPECT_INST("%36 = OpConstantComposite %v2uint %uint_12 %uint_12");
+ EXPECT_INST("%40 = OpConstantComposite %v2uint %uint_2 %uint_2");
+ EXPECT_INST("%46 = OpConstantComposite %v2uint %uint_1 %uint_1");
+ EXPECT_INST("%54 = OpConstantComposite %v2uint %uint_4294967295 %uint_4294967295");
+ EXPECT_INST(R"(
+ %7 = OpBitwiseAnd %v2uint %arg %8
+ %10 = OpIEqual %v2bool %7 %11
+ %15 = OpSelect %v2uint %10 %11 %16
+ %18 = OpShiftRightLogical %v2uint %arg %15
+ %19 = OpBitwiseAnd %v2uint %18 %20
+ %22 = OpIEqual %v2bool %19 %11
+ %23 = OpSelect %v2uint %22 %11 %24
+ %26 = OpShiftRightLogical %v2uint %18 %23
+ %27 = OpBitwiseAnd %v2uint %26 %28
+ %30 = OpIEqual %v2bool %27 %11
+ %31 = OpSelect %v2uint %30 %11 %32
+ %34 = OpShiftRightLogical %v2uint %26 %31
+ %35 = OpBitwiseAnd %v2uint %34 %36
+ %38 = OpIEqual %v2bool %35 %11
+ %39 = OpSelect %v2uint %38 %11 %40
+ %42 = OpShiftRightLogical %v2uint %34 %39
+ %43 = OpBitwiseAnd %v2uint %42 %40
+ %44 = OpIEqual %v2bool %43 %11
+ %45 = OpSelect %v2uint %44 %11 %46
+ %48 = OpBitwiseOr %v2uint %39 %45
+ %49 = OpBitwiseOr %v2uint %31 %48
+ %50 = OpBitwiseOr %v2uint %23 %49
+ %51 = OpBitwiseOr %v2uint %15 %50
+ %52 = OpIEqual %v2bool %42 %11
+ %result = OpSelect %v2uint %52 %54 %51
+)");
+}
+
+TEST_F(SpirvWriterTest, Builtin_FirstTrailingBit_U32) {
+ auto* arg = b.FunctionParam("arg", ty.u32());
+ auto* func = b.Function("foo", ty.u32());
+ func->SetParams({arg});
+ b.Append(func->Block(), [&] {
+ auto* result = b.Call(ty.u32(), core::Function::kFirstTrailingBit, arg);
+ b.Return(func, result);
+ mod.SetName(result, "result");
+ });
+
+ ASSERT_TRUE(Generate()) << Error() << output_;
+ EXPECT_INST(R"(
+ %6 = OpBitwiseAnd %uint %arg %uint_65535
+ %8 = OpIEqual %bool %6 %uint_0
+ %11 = OpSelect %uint %8 %uint_16 %uint_0
+ %13 = OpShiftRightLogical %uint %arg %11
+ %14 = OpBitwiseAnd %uint %13 %uint_255
+ %16 = OpIEqual %bool %14 %uint_0
+ %17 = OpSelect %uint %16 %uint_8 %uint_0
+ %19 = OpShiftRightLogical %uint %13 %17
+ %20 = OpBitwiseAnd %uint %19 %uint_15
+ %22 = OpIEqual %bool %20 %uint_0
+ %23 = OpSelect %uint %22 %uint_4 %uint_0
+ %25 = OpShiftRightLogical %uint %19 %23
+ %26 = OpBitwiseAnd %uint %25 %uint_3
+ %28 = OpIEqual %bool %26 %uint_0
+ %29 = OpSelect %uint %28 %uint_2 %uint_0
+ %31 = OpShiftRightLogical %uint %25 %29
+ %32 = OpBitwiseAnd %uint %31 %uint_1
+ %34 = OpIEqual %bool %32 %uint_0
+ %35 = OpSelect %uint %34 %uint_1 %uint_0
+ %36 = OpBitwiseOr %uint %29 %35
+ %37 = OpBitwiseOr %uint %23 %36
+ %38 = OpBitwiseOr %uint %17 %37
+ %39 = OpBitwiseOr %uint %11 %38
+ %40 = OpIEqual %bool %31 %uint_0
+ %result = OpSelect %uint %40 %uint_4294967295 %39
+)");
+}
+
+TEST_F(SpirvWriterTest, Builtin_FirstTrailingBit_I32) {
+ auto* arg = b.FunctionParam("arg", ty.i32());
+ auto* func = b.Function("foo", ty.i32());
+ func->SetParams({arg});
+ b.Append(func->Block(), [&] {
+ auto* result = b.Call(ty.i32(), core::Function::kFirstTrailingBit, arg);
+ b.Return(func, result);
+ mod.SetName(result, "result");
+ });
+
+ ASSERT_TRUE(Generate()) << Error() << output_;
+ EXPECT_INST(R"(
+ %7 = OpBitcast %uint %arg
+ %8 = OpBitwiseAnd %uint %7 %uint_65535
+ %10 = OpIEqual %bool %8 %uint_0
+ %13 = OpSelect %uint %10 %uint_16 %uint_0
+ %15 = OpShiftRightLogical %uint %7 %13
+ %16 = OpBitwiseAnd %uint %15 %uint_255
+ %18 = OpIEqual %bool %16 %uint_0
+ %19 = OpSelect %uint %18 %uint_8 %uint_0
+ %21 = OpShiftRightLogical %uint %15 %19
+ %22 = OpBitwiseAnd %uint %21 %uint_15
+ %24 = OpIEqual %bool %22 %uint_0
+ %25 = OpSelect %uint %24 %uint_4 %uint_0
+ %27 = OpShiftRightLogical %uint %21 %25
+ %28 = OpBitwiseAnd %uint %27 %uint_3
+ %30 = OpIEqual %bool %28 %uint_0
+ %31 = OpSelect %uint %30 %uint_2 %uint_0
+ %33 = OpShiftRightLogical %uint %27 %31
+ %34 = OpBitwiseAnd %uint %33 %uint_1
+ %36 = OpIEqual %bool %34 %uint_0
+ %37 = OpSelect %uint %36 %uint_1 %uint_0
+ %38 = OpBitwiseOr %uint %31 %37
+ %39 = OpBitwiseOr %uint %25 %38
+ %40 = OpBitwiseOr %uint %19 %39
+ %41 = OpBitwiseOr %uint %13 %40
+ %42 = OpIEqual %bool %33 %uint_0
+ %43 = OpSelect %uint %42 %uint_4294967295 %41
+ %result = OpBitcast %int %43
+)");
+}
+
+TEST_F(SpirvWriterTest, Builtin_FirstTrailingBit_Vec2U32) {
+ auto* arg = b.FunctionParam("arg", ty.vec2<u32>());
+ auto* func = b.Function("foo", ty.vec2<u32>());
+ func->SetParams({arg});
+ b.Append(func->Block(), [&] {
+ auto* result = b.Call(ty.vec2<u32>(), core::Function::kFirstTrailingBit, arg);
+ b.Return(func, result);
+ mod.SetName(result, "result");
+ });
+
+ ASSERT_TRUE(Generate()) << Error() << output_;
+ EXPECT_INST("%8 = OpConstantComposite %v2uint %uint_65535 %uint_65535");
+ EXPECT_INST("%11 = OpConstantComposite %v2uint %uint_0 %uint_0");
+ EXPECT_INST("%16 = OpConstantComposite %v2uint %uint_16 %uint_16");
+ EXPECT_INST("%20 = OpConstantComposite %v2uint %uint_255 %uint_255");
+ EXPECT_INST("%24 = OpConstantComposite %v2uint %uint_8 %uint_8");
+ EXPECT_INST("%28 = OpConstantComposite %v2uint %uint_15 %uint_15");
+ EXPECT_INST("%32 = OpConstantComposite %v2uint %uint_4 %uint_4");
+ EXPECT_INST("%36 = OpConstantComposite %v2uint %uint_3 %uint_3");
+ EXPECT_INST("%40 = OpConstantComposite %v2uint %uint_2 %uint_2");
+ EXPECT_INST("%44 = OpConstantComposite %v2uint %uint_1 %uint_1");
+ EXPECT_INST("%54 = OpConstantComposite %v2uint %uint_4294967295 %uint_4294967295");
+ EXPECT_INST(R"(
+ %7 = OpBitwiseAnd %v2uint %arg %8
+ %10 = OpIEqual %v2bool %7 %11
+ %15 = OpSelect %v2uint %10 %16 %11
+ %18 = OpShiftRightLogical %v2uint %arg %15
+ %19 = OpBitwiseAnd %v2uint %18 %20
+ %22 = OpIEqual %v2bool %19 %11
+ %23 = OpSelect %v2uint %22 %24 %11
+ %26 = OpShiftRightLogical %v2uint %18 %23
+ %27 = OpBitwiseAnd %v2uint %26 %28
+ %30 = OpIEqual %v2bool %27 %11
+ %31 = OpSelect %v2uint %30 %32 %11
+ %34 = OpShiftRightLogical %v2uint %26 %31
+ %35 = OpBitwiseAnd %v2uint %34 %36
+ %38 = OpIEqual %v2bool %35 %11
+ %39 = OpSelect %v2uint %38 %40 %11
+ %42 = OpShiftRightLogical %v2uint %34 %39
+ %43 = OpBitwiseAnd %v2uint %42 %44
+ %46 = OpIEqual %v2bool %43 %11
+ %47 = OpSelect %v2uint %46 %44 %11
+ %48 = OpBitwiseOr %v2uint %39 %47
+ %49 = OpBitwiseOr %v2uint %31 %48
+ %50 = OpBitwiseOr %v2uint %23 %49
+ %51 = OpBitwiseOr %v2uint %15 %50
+ %52 = OpIEqual %v2bool %42 %11
+ %result = OpSelect %v2uint %52 %54 %51
+)");
+}
+
TEST_F(SpirvWriterTest, Builtin_Saturate_F32) {
auto* arg = b.FunctionParam("arg", ty.f32());
auto* func = b.Function("foo", ty.f32());
diff --git a/src/tint/lang/spirv/writer/raise/raise.cc b/src/tint/lang/spirv/writer/raise/raise.cc
index 52ca457..ab7ff42 100644
--- a/src/tint/lang/spirv/writer/raise/raise.cc
+++ b/src/tint/lang/spirv/writer/raise/raise.cc
@@ -40,6 +40,8 @@
} while (false)
ir::transform::BuiltinPolyfillConfig core_polyfills;
+ core_polyfills.first_leading_bit = true;
+ core_polyfills.first_trailing_bit = true;
core_polyfills.saturate = true;
RUN_TRANSFORM(ir::transform::BuiltinPolyfill, module, core_polyfills);