mirror of
https://github.com/encounter/dawn-cmake.git
synced 2025-12-13 07:06:11 +00:00
builtins: Add insertBits
CTS tests: https://github.com/gpuweb/cts/pull/1012 Bug: tint:1371 Change-Id: Idd55c0bc9dad1dffb558d0bc57d744f65e9041b5 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/81701 Kokoro: Kokoro <noreply+kokoro@google.com> Reviewed-by: David Neto <dneto@google.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -336,6 +336,8 @@ fn frexp<N: num>(vec<N, f32>) -> __frexp_result_vec<N>
|
||||
[[stage("fragment")]] fn fwidthCoarse<N: num>(vec<N, f32>) -> vec<N, f32>
|
||||
[[stage("fragment")]] fn fwidthFine(f32) -> f32
|
||||
[[stage("fragment")]] fn fwidthFine<N: num>(vec<N, f32>) -> vec<N, f32>
|
||||
fn insertBits<T: iu32>(T, T, u32, u32) -> T
|
||||
fn insertBits<N: num, T: iu32>(vec<N, T>, vec<N, T>, u32, u32) -> vec<N, T>
|
||||
fn inverseSqrt(f32) -> f32
|
||||
fn inverseSqrt<N: num>(vec<N, f32>) -> vec<N, f32>
|
||||
[[deprecated]] fn isFinite(f32) -> bool
|
||||
|
||||
@@ -1760,6 +1760,16 @@ class ProgramBuilder {
|
||||
Expr(std::forward<RHS>(rhs)));
|
||||
}
|
||||
|
||||
/// @param lhs the left hand argument to the xor operation
|
||||
/// @param rhs the right hand argument to the xor operation
|
||||
/// @returns a `ast::BinaryExpression` bitwise xor-ing `lhs` and `rhs`
|
||||
template <typename LHS, typename RHS>
|
||||
const ast::BinaryExpression* Xor(LHS&& lhs, RHS&& rhs) {
|
||||
return create<ast::BinaryExpression>(ast::BinaryOp::kXor,
|
||||
Expr(std::forward<LHS>(lhs)),
|
||||
Expr(std::forward<RHS>(rhs)));
|
||||
}
|
||||
|
||||
/// @param lhs the left hand argument to the greater than operation
|
||||
/// @param rhs the right hand argument to the greater than operation
|
||||
/// @returns a `ast::BinaryExpression` of `lhs` > `rhs`
|
||||
|
||||
@@ -443,6 +443,11 @@ sem::BuiltinType GetBuiltin(SpvOp opcode) {
|
||||
switch (opcode) {
|
||||
case SpvOpBitCount:
|
||||
return sem::BuiltinType::kCountOneBits;
|
||||
case SpvOpBitFieldInsert:
|
||||
return sem::BuiltinType::kInsertBits;
|
||||
case SpvOpBitFieldSExtract:
|
||||
case SpvOpBitFieldUExtract:
|
||||
return sem::BuiltinType::kExtractBits;
|
||||
case SpvOpBitReverse:
|
||||
return sem::BuiltinType::kReverseBits;
|
||||
case SpvOpDot:
|
||||
@@ -463,9 +468,6 @@ sem::BuiltinType GetBuiltin(SpvOp opcode) {
|
||||
return sem::BuiltinType::kDpdxCoarse;
|
||||
case SpvOpDPdyCoarse:
|
||||
return sem::BuiltinType::kDpdyCoarse;
|
||||
case SpvOpBitFieldSExtract:
|
||||
case SpvOpBitFieldUExtract:
|
||||
return sem::BuiltinType::kExtractBits;
|
||||
case SpvOpFwidthCoarse:
|
||||
return sem::BuiltinType::kFwidthCoarse;
|
||||
default:
|
||||
|
||||
@@ -901,6 +901,78 @@ TEST_F(SpvUnaryBitTest, BitReverse_IntVector_IntVector) {
|
||||
<< body;
|
||||
}
|
||||
|
||||
TEST_F(SpvUnaryBitTest, InsertBits_Int) {
|
||||
const auto assembly = BitTestPreamble() + R"(
|
||||
%1 = OpBitFieldInsert %v2int %int_30 %int_40 %uint_10 %uint_20
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
auto p = parser(test::Assemble(assembly));
|
||||
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
|
||||
auto fe = p->function_emitter(100);
|
||||
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
||||
auto ast_body = fe.ast_body();
|
||||
auto body = test::ToString(p->program(), ast_body);
|
||||
EXPECT_THAT(body,
|
||||
HasSubstr("let x_1 : vec2<i32> = insertBits(30, 40, 10u, 20u);"))
|
||||
<< body;
|
||||
}
|
||||
|
||||
TEST_F(SpvUnaryBitTest, InsertBits_IntVector) {
|
||||
const auto assembly = BitTestPreamble() + R"(
|
||||
%1 = OpBitFieldInsert %v2int %v2int_30_40 %v2int_40_30 %uint_10 %uint_20
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
auto p = parser(test::Assemble(assembly));
|
||||
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
|
||||
auto fe = p->function_emitter(100);
|
||||
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
||||
auto ast_body = fe.ast_body();
|
||||
auto body = test::ToString(p->program(), ast_body);
|
||||
EXPECT_THAT(
|
||||
body,
|
||||
HasSubstr(
|
||||
R"(let x_1 : vec2<i32> = insertBits(vec2<i32>(30, 40), vec2<i32>(40, 30), 10u, 20u);)"))
|
||||
<< body;
|
||||
}
|
||||
|
||||
TEST_F(SpvUnaryBitTest, InsertBits_Uint) {
|
||||
const auto assembly = BitTestPreamble() + R"(
|
||||
%1 = OpBitFieldInsert %v2uint %uint_20 %uint_10 %uint_10 %uint_20
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
auto p = parser(test::Assemble(assembly));
|
||||
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
|
||||
auto fe = p->function_emitter(100);
|
||||
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
||||
auto ast_body = fe.ast_body();
|
||||
auto body = test::ToString(p->program(), ast_body);
|
||||
EXPECT_THAT(
|
||||
body, HasSubstr("let x_1 : vec2<u32> = insertBits(20u, 10u, 10u, 20u);"))
|
||||
<< body;
|
||||
}
|
||||
|
||||
TEST_F(SpvUnaryBitTest, InsertBits_UintVector) {
|
||||
const auto assembly = BitTestPreamble() + R"(
|
||||
%1 = OpBitFieldInsert %v2uint %v2uint_10_20 %v2uint_20_10 %uint_10 %uint_20
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
auto p = parser(test::Assemble(assembly));
|
||||
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
|
||||
auto fe = p->function_emitter(100);
|
||||
EXPECT_TRUE(fe.EmitBody()) << p->error();
|
||||
auto ast_body = fe.ast_body();
|
||||
auto body = test::ToString(p->program(), ast_body);
|
||||
EXPECT_THAT(
|
||||
body,
|
||||
HasSubstr(
|
||||
R"(let x_1 : vec2<u32> = insertBits(vec2<u32>(10u, 20u), vec2<u32>(20u, 10u), 10u, 20u);)"))
|
||||
<< body;
|
||||
}
|
||||
|
||||
TEST_F(SpvUnaryBitTest, ExtractBits_Int) {
|
||||
const auto assembly = BitTestPreamble() + R"(
|
||||
%1 = OpBitFieldSExtract %v2int %int_30 %uint_10 %uint_20
|
||||
@@ -973,8 +1045,6 @@ TEST_F(SpvUnaryBitTest, ExtractBits_UintVector) {
|
||||
<< body;
|
||||
}
|
||||
|
||||
// TODO(dneto): OpBitFieldInsert
|
||||
|
||||
} // namespace
|
||||
} // namespace spirv
|
||||
} // namespace reader
|
||||
|
||||
@@ -147,6 +147,9 @@ BuiltinType ParseBuiltinType(const std::string& name) {
|
||||
if (name == "fwidthFine") {
|
||||
return BuiltinType::kFwidthFine;
|
||||
}
|
||||
if (name == "insertBits") {
|
||||
return BuiltinType::kInsertBits;
|
||||
}
|
||||
if (name == "inverseSqrt") {
|
||||
return BuiltinType::kInverseSqrt;
|
||||
}
|
||||
@@ -436,6 +439,8 @@ const char* str(BuiltinType i) {
|
||||
return "fwidthCoarse";
|
||||
case BuiltinType::kFwidthFine:
|
||||
return "fwidthFine";
|
||||
case BuiltinType::kInsertBits:
|
||||
return "insertBits";
|
||||
case BuiltinType::kInverseSqrt:
|
||||
return "inverseSqrt";
|
||||
case BuiltinType::kIsFinite:
|
||||
|
||||
@@ -73,6 +73,7 @@ enum class BuiltinType {
|
||||
kFwidth,
|
||||
kFwidthCoarse,
|
||||
kFwidthFine,
|
||||
kInsertBits,
|
||||
kInverseSqrt,
|
||||
kIsFinite,
|
||||
kIsInf,
|
||||
|
||||
@@ -55,7 +55,7 @@ struct BuiltinPolyfill::State {
|
||||
if (width == 1) {
|
||||
return b.ty.u32();
|
||||
}
|
||||
return b.ty.vec<ProgramBuilder::u32>(width);
|
||||
return b.ty.vec<u32>(width);
|
||||
};
|
||||
auto V = [&](uint32_t value) -> const ast::Expression* {
|
||||
return ScalarOrVector(width, value);
|
||||
@@ -117,7 +117,7 @@ struct BuiltinPolyfill::State {
|
||||
if (width == 1) {
|
||||
return b.ty.u32();
|
||||
}
|
||||
return b.ty.vec<ProgramBuilder::u32>(width);
|
||||
return b.ty.vec<u32>(width);
|
||||
};
|
||||
auto V = [&](uint32_t value) -> const ast::Expression* {
|
||||
return ScalarOrVector(width, value);
|
||||
@@ -187,7 +187,7 @@ struct BuiltinPolyfill::State {
|
||||
if (width == 1) {
|
||||
return value;
|
||||
}
|
||||
return b.Construct(b.ty.vec<ProgramBuilder::u32>(width), value);
|
||||
return b.Construct(b.ty.vec<u32>(width), value);
|
||||
};
|
||||
|
||||
ast::StatementList body = {
|
||||
@@ -236,7 +236,7 @@ struct BuiltinPolyfill::State {
|
||||
if (width == 1) {
|
||||
return b.ty.u32();
|
||||
}
|
||||
return b.ty.vec<ProgramBuilder::u32>(width);
|
||||
return b.ty.vec<u32>(width);
|
||||
};
|
||||
auto V = [&](uint32_t value) -> const ast::Expression* {
|
||||
return ScalarOrVector(width, value);
|
||||
@@ -317,7 +317,7 @@ struct BuiltinPolyfill::State {
|
||||
if (width == 1) {
|
||||
return b.ty.u32();
|
||||
}
|
||||
return b.ty.vec<ProgramBuilder::u32>(width);
|
||||
return b.ty.vec<u32>(width);
|
||||
};
|
||||
auto V = [&](uint32_t value) -> const ast::Expression* {
|
||||
return ScalarOrVector(width, value);
|
||||
@@ -373,16 +373,90 @@ struct BuiltinPolyfill::State {
|
||||
return name;
|
||||
}
|
||||
|
||||
/// Builds the polyfill function for the `insertBits` builtin
|
||||
/// @param ty the parameter and return type for the function
|
||||
/// @return the polyfill function name
|
||||
Symbol insertBits(const sem::Type* ty) {
|
||||
auto name = b.Symbols().New("tint_insert_bits");
|
||||
uint32_t width = WidthOf(ty);
|
||||
|
||||
constexpr uint32_t W = 32u; // 32-bit
|
||||
|
||||
auto V = [&](auto value) -> const ast::Expression* {
|
||||
const ast::Expression* expr = b.Expr(value);
|
||||
if (!ty->is_unsigned_scalar_or_vector()) {
|
||||
expr = b.Construct<i32>(expr);
|
||||
}
|
||||
if (ty->Is<sem::Vector>()) {
|
||||
expr = b.Construct(T(ty), expr);
|
||||
}
|
||||
return expr;
|
||||
};
|
||||
auto U = [&](auto value) -> const ast::Expression* {
|
||||
if (width == 1) {
|
||||
return b.Expr(value);
|
||||
}
|
||||
return b.vec(b.ty.u32(), width, value);
|
||||
};
|
||||
|
||||
ast::StatementList body = {
|
||||
b.Decl(b.Const("s", nullptr, b.Call("min", "offset", W))),
|
||||
b.Decl(b.Const("e", nullptr, b.Call("min", W, b.Add("s", "count")))),
|
||||
};
|
||||
|
||||
switch (polyfill.insert_bits) {
|
||||
case Level::kFull:
|
||||
// let mask = ((1 << s) - 1) ^ ((1 << e) - 1)
|
||||
body.emplace_back(b.Decl(b.Const(
|
||||
"mask", nullptr,
|
||||
b.Xor(b.Sub(b.Shl(1u, "s"), 1u), b.Sub(b.Shl(1u, "e"), 1u)))));
|
||||
// return ((n << s) & mask) | (v & ~mask)
|
||||
body.emplace_back(b.Return(b.Or(b.And(b.Shl("n", U("s")), V("mask")),
|
||||
b.And("v", V(b.Complement("mask"))))));
|
||||
break;
|
||||
case Level::kClampParameters:
|
||||
body.emplace_back(
|
||||
b.Return(b.Call("insertBits", "v", "n", "s", b.Sub("e", "s"))));
|
||||
break;
|
||||
default:
|
||||
TINT_ICE(Transform, b.Diagnostics())
|
||||
<< "unhandled polyfill level: "
|
||||
<< static_cast<int>(polyfill.insert_bits);
|
||||
return {};
|
||||
}
|
||||
|
||||
b.Func(name,
|
||||
{
|
||||
b.Param("v", T(ty)),
|
||||
b.Param("n", T(ty)),
|
||||
b.Param("offset", b.ty.u32()),
|
||||
b.Param("count", b.ty.u32()),
|
||||
},
|
||||
T(ty), body);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
private:
|
||||
/// Aliases
|
||||
using u32 = ProgramBuilder::u32;
|
||||
using i32 = ProgramBuilder::i32;
|
||||
|
||||
/// @returns the AST type for the given sem type
|
||||
const ast::Type* T(const sem::Type* ty) const {
|
||||
return CreateASTTypeFor(ctx, ty);
|
||||
}
|
||||
|
||||
/// @returns 1 if `ty` is not a vector, otherwise the vector width
|
||||
uint32_t WidthOf(const sem::Type* ty) const {
|
||||
if (auto* v = ty->As<sem::Vector>()) {
|
||||
return v->Width();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// @returns a scalar or vector with the given width, with each element with
|
||||
/// the given value.
|
||||
template <typename T>
|
||||
const ast::Expression* ScalarOrVector(uint32_t width, T value) const {
|
||||
if (width == 1) {
|
||||
@@ -430,6 +504,11 @@ bool BuiltinPolyfill::ShouldRun(const Program* program,
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case sem::BuiltinType::kInsertBits:
|
||||
if (builtins.insert_bits != Level::kNone) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -494,6 +573,13 @@ void BuiltinPolyfill::Run(CloneContext& ctx,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case sem::BuiltinType::kInsertBits:
|
||||
if (builtins.insert_bits != Level::kNone) {
|
||||
polyfill = utils::GetOrCreate(polyfills, builtin, [&] {
|
||||
return s.insertBits(builtin->ReturnType());
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -50,6 +50,8 @@ class BuiltinPolyfill : public Castable<BuiltinPolyfill, Transform> {
|
||||
bool first_leading_bit = false;
|
||||
/// Should `firstTrailingBit()` be polyfilled?
|
||||
bool first_trailing_bit = false;
|
||||
/// Should `insertBits()` be polyfilled?
|
||||
Level insert_bits = Level::kNone;
|
||||
};
|
||||
|
||||
/// Config is consumed by the BuiltinPolyfill transform.
|
||||
|
||||
@@ -889,6 +889,233 @@ fn f() {
|
||||
EXPECT_EQ(expect, str(got));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// insertBits
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
DataMap polyfillInsertBits(Level level) {
|
||||
BuiltinPolyfill::Builtins builtins;
|
||||
builtins.insert_bits = level;
|
||||
DataMap data;
|
||||
data.Add<BuiltinPolyfill::Config>(builtins);
|
||||
return data;
|
||||
}
|
||||
|
||||
TEST_F(BuiltinPolyfillTest, ShouldRunInsertBits) {
|
||||
auto* src = R"(
|
||||
fn f() {
|
||||
insertBits(1234, 5678, 5u, 6u);
|
||||
}
|
||||
)";
|
||||
|
||||
EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src));
|
||||
EXPECT_FALSE(
|
||||
ShouldRun<BuiltinPolyfill>(src, polyfillInsertBits(Level::kNone)));
|
||||
EXPECT_TRUE(ShouldRun<BuiltinPolyfill>(
|
||||
src, polyfillInsertBits(Level::kClampParameters)));
|
||||
EXPECT_TRUE(
|
||||
ShouldRun<BuiltinPolyfill>(src, polyfillInsertBits(Level::kFull)));
|
||||
}
|
||||
|
||||
TEST_F(BuiltinPolyfillTest, InsertBits_Full_i32) {
|
||||
auto* src = R"(
|
||||
fn f() {
|
||||
let r : i32 = insertBits(1234, 5678, 5u, 6u);
|
||||
}
|
||||
)";
|
||||
|
||||
auto* expect = R"(
|
||||
fn tint_insert_bits(v : i32, n : i32, offset : u32, count : u32) -> i32 {
|
||||
let s = min(offset, 32u);
|
||||
let e = min(32u, (s + count));
|
||||
let mask = (((1u << s) - 1u) ^ ((1u << e) - 1u));
|
||||
return (((n << s) & i32(mask)) | (v & i32(~(mask))));
|
||||
}
|
||||
|
||||
fn f() {
|
||||
let r : i32 = tint_insert_bits(1234, 5678, 5u, 6u);
|
||||
}
|
||||
)";
|
||||
|
||||
auto got = Run<BuiltinPolyfill>(src, polyfillInsertBits(Level::kFull));
|
||||
|
||||
EXPECT_EQ(expect, str(got));
|
||||
}
|
||||
|
||||
TEST_F(BuiltinPolyfillTest, InsertBits_Full_u32) {
|
||||
auto* src = R"(
|
||||
fn f() {
|
||||
let r : u32 = insertBits(1234u, 5678u, 5u, 6u);
|
||||
}
|
||||
)";
|
||||
|
||||
auto* expect = R"(
|
||||
fn tint_insert_bits(v : u32, n : u32, offset : u32, count : u32) -> u32 {
|
||||
let s = min(offset, 32u);
|
||||
let e = min(32u, (s + count));
|
||||
let mask = (((1u << s) - 1u) ^ ((1u << e) - 1u));
|
||||
return (((n << s) & mask) | (v & ~(mask)));
|
||||
}
|
||||
|
||||
fn f() {
|
||||
let r : u32 = tint_insert_bits(1234u, 5678u, 5u, 6u);
|
||||
}
|
||||
)";
|
||||
|
||||
auto got = Run<BuiltinPolyfill>(src, polyfillInsertBits(Level::kFull));
|
||||
|
||||
EXPECT_EQ(expect, str(got));
|
||||
}
|
||||
|
||||
TEST_F(BuiltinPolyfillTest, InsertBits_Full_vec3_i32) {
|
||||
auto* src = R"(
|
||||
fn f() {
|
||||
let r : vec3<i32> = insertBits(vec3<i32>(1234), vec3<i32>(5678), 5u, 6u);
|
||||
}
|
||||
)";
|
||||
|
||||
auto* expect = R"(
|
||||
fn tint_insert_bits(v : vec3<i32>, n : vec3<i32>, offset : u32, count : u32) -> vec3<i32> {
|
||||
let s = min(offset, 32u);
|
||||
let e = min(32u, (s + count));
|
||||
let mask = (((1u << s) - 1u) ^ ((1u << e) - 1u));
|
||||
return (((n << vec3<u32>(s)) & vec3<i32>(i32(mask))) | (v & vec3<i32>(i32(~(mask)))));
|
||||
}
|
||||
|
||||
fn f() {
|
||||
let r : vec3<i32> = tint_insert_bits(vec3<i32>(1234), vec3<i32>(5678), 5u, 6u);
|
||||
}
|
||||
)";
|
||||
|
||||
auto got = Run<BuiltinPolyfill>(src, polyfillInsertBits(Level::kFull));
|
||||
|
||||
EXPECT_EQ(expect, str(got));
|
||||
}
|
||||
|
||||
TEST_F(BuiltinPolyfillTest, InsertBits_Full_vec3_u32) {
|
||||
auto* src = R"(
|
||||
fn f() {
|
||||
let r : vec3<u32> = insertBits(vec3<u32>(1234u), vec3<u32>(5678u), 5u, 6u);
|
||||
}
|
||||
)";
|
||||
|
||||
auto* expect = R"(
|
||||
fn tint_insert_bits(v : vec3<u32>, n : vec3<u32>, offset : u32, count : u32) -> vec3<u32> {
|
||||
let s = min(offset, 32u);
|
||||
let e = min(32u, (s + count));
|
||||
let mask = (((1u << s) - 1u) ^ ((1u << e) - 1u));
|
||||
return (((n << vec3<u32>(s)) & vec3<u32>(mask)) | (v & vec3<u32>(~(mask))));
|
||||
}
|
||||
|
||||
fn f() {
|
||||
let r : vec3<u32> = tint_insert_bits(vec3<u32>(1234u), vec3<u32>(5678u), 5u, 6u);
|
||||
}
|
||||
)";
|
||||
|
||||
auto got = Run<BuiltinPolyfill>(src, polyfillInsertBits(Level::kFull));
|
||||
|
||||
EXPECT_EQ(expect, str(got));
|
||||
}
|
||||
|
||||
TEST_F(BuiltinPolyfillTest, InsertBits_Clamp_i32) {
|
||||
auto* src = R"(
|
||||
fn f() {
|
||||
let r : i32 = insertBits(1234, 5678, 5u, 6u);
|
||||
}
|
||||
)";
|
||||
|
||||
auto* expect = R"(
|
||||
fn tint_insert_bits(v : i32, n : i32, offset : u32, count : u32) -> i32 {
|
||||
let s = min(offset, 32u);
|
||||
let e = min(32u, (s + count));
|
||||
return insertBits(v, n, s, (e - s));
|
||||
}
|
||||
|
||||
fn f() {
|
||||
let r : i32 = tint_insert_bits(1234, 5678, 5u, 6u);
|
||||
}
|
||||
)";
|
||||
|
||||
auto got =
|
||||
Run<BuiltinPolyfill>(src, polyfillInsertBits(Level::kClampParameters));
|
||||
|
||||
EXPECT_EQ(expect, str(got));
|
||||
}
|
||||
|
||||
TEST_F(BuiltinPolyfillTest, InsertBits_Clamp_u32) {
|
||||
auto* src = R"(
|
||||
fn f() {
|
||||
let r : u32 = insertBits(1234u, 5678u, 5u, 6u);
|
||||
}
|
||||
)";
|
||||
|
||||
auto* expect = R"(
|
||||
fn tint_insert_bits(v : u32, n : u32, offset : u32, count : u32) -> u32 {
|
||||
let s = min(offset, 32u);
|
||||
let e = min(32u, (s + count));
|
||||
return insertBits(v, n, s, (e - s));
|
||||
}
|
||||
|
||||
fn f() {
|
||||
let r : u32 = tint_insert_bits(1234u, 5678u, 5u, 6u);
|
||||
}
|
||||
)";
|
||||
|
||||
auto got =
|
||||
Run<BuiltinPolyfill>(src, polyfillInsertBits(Level::kClampParameters));
|
||||
|
||||
EXPECT_EQ(expect, str(got));
|
||||
}
|
||||
|
||||
TEST_F(BuiltinPolyfillTest, InsertBits_Clamp_vec3_i32) {
|
||||
auto* src = R"(
|
||||
fn f() {
|
||||
let r : vec3<i32> = insertBits(vec3<i32>(1234), vec3<i32>(5678), 5u, 6u);
|
||||
}
|
||||
)";
|
||||
|
||||
auto* expect = R"(
|
||||
fn tint_insert_bits(v : vec3<i32>, n : vec3<i32>, offset : u32, count : u32) -> vec3<i32> {
|
||||
let s = min(offset, 32u);
|
||||
let e = min(32u, (s + count));
|
||||
return insertBits(v, n, s, (e - s));
|
||||
}
|
||||
|
||||
fn f() {
|
||||
let r : vec3<i32> = tint_insert_bits(vec3<i32>(1234), vec3<i32>(5678), 5u, 6u);
|
||||
}
|
||||
)";
|
||||
|
||||
auto got =
|
||||
Run<BuiltinPolyfill>(src, polyfillInsertBits(Level::kClampParameters));
|
||||
|
||||
EXPECT_EQ(expect, str(got));
|
||||
}
|
||||
|
||||
TEST_F(BuiltinPolyfillTest, InsertBits_Clamp_vec3_u32) {
|
||||
auto* src = R"(
|
||||
fn f() {
|
||||
let r : vec3<u32> = insertBits(vec3<u32>(1234u), vec3<u32>(5678u), 5u, 6u);
|
||||
}
|
||||
)";
|
||||
|
||||
auto* expect = R"(
|
||||
fn tint_insert_bits(v : vec3<u32>, n : vec3<u32>, offset : u32, count : u32) -> vec3<u32> {
|
||||
let s = min(offset, 32u);
|
||||
let e = min(32u, (s + count));
|
||||
return insertBits(v, n, s, (e - s));
|
||||
}
|
||||
|
||||
fn f() {
|
||||
let r : vec3<u32> = tint_insert_bits(vec3<u32>(1234u), vec3<u32>(5678u), 5u, 6u);
|
||||
}
|
||||
)";
|
||||
|
||||
auto got =
|
||||
Run<BuiltinPolyfill>(src, polyfillInsertBits(Level::kClampParameters));
|
||||
|
||||
EXPECT_EQ(expect, str(got));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace transform
|
||||
} // namespace tint
|
||||
|
||||
@@ -59,6 +59,7 @@ Output Glsl::Run(const Program* in, const DataMap& inputs) const {
|
||||
polyfills.extract_bits = BuiltinPolyfill::Level::kClampParameters;
|
||||
polyfills.first_leading_bit = true;
|
||||
polyfills.first_trailing_bit = true;
|
||||
polyfills.insert_bits = BuiltinPolyfill::Level::kClampParameters;
|
||||
data.Add<BuiltinPolyfill::Config>(polyfills);
|
||||
manager.Add<BuiltinPolyfill>();
|
||||
}
|
||||
|
||||
@@ -607,6 +607,9 @@ bool GeneratorImpl::EmitBuiltinCall(std::ostream& out,
|
||||
if (builtin->Type() == sem::BuiltinType::kExtractBits) {
|
||||
return EmitExtractBits(out, expr);
|
||||
}
|
||||
if (builtin->Type() == sem::BuiltinType::kInsertBits) {
|
||||
return EmitInsertBits(out, expr);
|
||||
}
|
||||
if (builtin->IsDataPacking()) {
|
||||
return EmitDataPackingCall(out, expr, builtin);
|
||||
}
|
||||
@@ -834,6 +837,28 @@ bool GeneratorImpl::EmitExtractBits(std::ostream& out,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GeneratorImpl::EmitInsertBits(std::ostream& out,
|
||||
const ast::CallExpression* expr) {
|
||||
out << "bitfieldInsert(";
|
||||
if (!EmitExpression(out, expr->args[0])) {
|
||||
return false;
|
||||
}
|
||||
out << ", ";
|
||||
if (!EmitExpression(out, expr->args[1])) {
|
||||
return false;
|
||||
}
|
||||
out << ", int(";
|
||||
if (!EmitExpression(out, expr->args[2])) {
|
||||
return false;
|
||||
}
|
||||
out << "), int(";
|
||||
if (!EmitExpression(out, expr->args[3])) {
|
||||
return false;
|
||||
}
|
||||
out << "))";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GeneratorImpl::EmitSelectCall(std::ostream& out,
|
||||
const ast::CallExpression* expr) {
|
||||
auto* expr_false = expr->args[0];
|
||||
|
||||
@@ -176,6 +176,11 @@ class GeneratorImpl : public TextGenerator {
|
||||
/// @param expr the call expression
|
||||
/// @returns true if the expression is emitted
|
||||
bool EmitExtractBits(std::ostream& out, const ast::CallExpression* expr);
|
||||
/// Handles generating a call to `bitfieldInsert`
|
||||
/// @param out the output of the expression stream
|
||||
/// @param expr the call expression
|
||||
/// @returns true if the expression is emitted
|
||||
bool EmitInsertBits(std::ostream& out, const ast::CallExpression* expr);
|
||||
/// Handles generating a call to a texture function (`textureSample`,
|
||||
/// `textureSampleGrad`, etc)
|
||||
/// @param out the output of the expression stream
|
||||
|
||||
@@ -615,6 +615,42 @@ void main() {
|
||||
}
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_F(GlslGeneratorImplTest_Builtin, InsertBits) {
|
||||
auto* v = Var("v", ty.vec3<u32>());
|
||||
auto* n = Var("n", ty.vec3<u32>());
|
||||
auto* offset = Var("offset", ty.u32());
|
||||
auto* count = Var("count", ty.u32());
|
||||
auto* call = Call("insertBits", v, n, offset, count);
|
||||
WrapInFunction(v, n, offset, count, call);
|
||||
|
||||
GeneratorImpl& gen = SanitizeAndBuild();
|
||||
|
||||
ASSERT_TRUE(gen.Generate()) << gen.error();
|
||||
EXPECT_EQ(gen.result(), R"(#version 310 es
|
||||
|
||||
uvec3 tint_insert_bits(uvec3 v, uvec3 n, uint offset, uint count) {
|
||||
uint s = min(offset, 32u);
|
||||
uint e = min(32u, (s + count));
|
||||
return bitfieldInsert(v, n, int(s), int((e - s)));
|
||||
}
|
||||
|
||||
void test_function() {
|
||||
uvec3 v = uvec3(0u, 0u, 0u);
|
||||
uvec3 n = uvec3(0u, 0u, 0u);
|
||||
uint offset = 0u;
|
||||
uint count = 0u;
|
||||
uvec3 tint_symbol = tint_insert_bits(v, n, offset, count);
|
||||
}
|
||||
|
||||
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
|
||||
void main() {
|
||||
test_function();
|
||||
return;
|
||||
}
|
||||
)");
|
||||
}
|
||||
|
||||
#if 0
|
||||
TEST_F(GlslGeneratorImplTest_Builtin, Pack4x8Snorm) {
|
||||
auto* call = Call("pack4x8snorm", "p1");
|
||||
|
||||
@@ -150,6 +150,7 @@ SanitizedResult Sanitize(
|
||||
polyfills.extract_bits = transform::BuiltinPolyfill::Level::kFull;
|
||||
polyfills.first_leading_bit = true;
|
||||
polyfills.first_trailing_bit = true;
|
||||
polyfills.insert_bits = transform::BuiltinPolyfill::Level::kFull;
|
||||
data.Add<transform::BuiltinPolyfill::Config>(polyfills);
|
||||
manager.Add<transform::BuiltinPolyfill>();
|
||||
}
|
||||
|
||||
@@ -134,6 +134,7 @@ SanitizedResult Sanitize(
|
||||
transform::BuiltinPolyfill::Level::kClampParameters;
|
||||
polyfills.first_leading_bit = true;
|
||||
polyfills.first_trailing_bit = true;
|
||||
polyfills.insert_bits = transform::BuiltinPolyfill::Level::kClampParameters;
|
||||
data.Add<transform::BuiltinPolyfill::Config>(polyfills);
|
||||
manager.Add<transform::BuiltinPolyfill>();
|
||||
}
|
||||
@@ -1378,6 +1379,9 @@ std::string GeneratorImpl::generate_builtin_name(const sem::Builtin* builtin) {
|
||||
case sem::BuiltinType::kExtractBits:
|
||||
out += "extract_bits";
|
||||
break;
|
||||
case sem::BuiltinType::kInsertBits:
|
||||
out += "insert_bits";
|
||||
break;
|
||||
case sem::BuiltinType::kFwidth:
|
||||
case sem::BuiltinType::kFwidthCoarse:
|
||||
case sem::BuiltinType::kFwidthFine:
|
||||
|
||||
@@ -131,6 +131,8 @@ const ast::CallExpression* GenerateCall(BuiltinType builtin,
|
||||
return builder->Call(str.str(), "u2");
|
||||
case BuiltinType::kExtractBits:
|
||||
return builder->Call(str.str(), "u2", "u1", "u1");
|
||||
case BuiltinType::kInsertBits:
|
||||
return builder->Call(str.str(), "u2", "u2", "u1", "u1");
|
||||
case BuiltinType::kMax:
|
||||
case BuiltinType::kMin:
|
||||
if (type == ParamType::kF32) {
|
||||
@@ -239,6 +241,7 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
BuiltinData{BuiltinType::kFwidth, ParamType::kF32, "fwidth"},
|
||||
BuiltinData{BuiltinType::kFwidthCoarse, ParamType::kF32, "fwidth"},
|
||||
BuiltinData{BuiltinType::kFwidthFine, ParamType::kF32, "fwidth"},
|
||||
BuiltinData{BuiltinType::kInsertBits, ParamType::kU32, "insert_bits"},
|
||||
BuiltinData{BuiltinType::kInverseSqrt, ParamType::kF32, "rsqrt"},
|
||||
BuiltinData{BuiltinType::kIsFinite, ParamType::kF32, "isfinite"},
|
||||
BuiltinData{BuiltinType::kIsInf, ParamType::kF32, "isinf"},
|
||||
|
||||
@@ -267,6 +267,7 @@ SanitizedResult Sanitize(const Program* in,
|
||||
transform::BuiltinPolyfill::Level::kClampParameters;
|
||||
polyfills.first_leading_bit = true;
|
||||
polyfills.first_trailing_bit = true;
|
||||
polyfills.insert_bits = transform::BuiltinPolyfill::Level::kClampParameters;
|
||||
data.Add<transform::BuiltinPolyfill::Config>(polyfills);
|
||||
manager.Add<transform::BuiltinPolyfill>();
|
||||
}
|
||||
@@ -2520,6 +2521,9 @@ uint32_t Builder::GenerateBuiltinCall(const sem::Call* call,
|
||||
case BuiltinType::kFwidthFine:
|
||||
op = spv::Op::OpFwidthFine;
|
||||
break;
|
||||
case BuiltinType::kInsertBits:
|
||||
op = spv::Op::OpBitFieldInsert;
|
||||
break;
|
||||
case BuiltinType::kIsInf:
|
||||
op = spv::Op::OpIsInf;
|
||||
break;
|
||||
|
||||
@@ -2708,6 +2708,176 @@ OpFunctionEnd
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_F(BuiltinBuilderTest, Call_InsertBits_i32) {
|
||||
auto* v = Var("v", ty.i32());
|
||||
auto* n = Var("n", ty.i32());
|
||||
auto* offset = Var("offset", ty.u32());
|
||||
auto* count = Var("count", ty.u32());
|
||||
auto* call = Call("insertBits", v, n, offset, count);
|
||||
auto* func = WrapInFunction(v, n, offset, count, call);
|
||||
|
||||
spirv::Builder& b = Build();
|
||||
|
||||
ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
|
||||
|
||||
EXPECT_EQ(DumpBuilder(b), R"(OpEntryPoint GLCompute %3 "test_function"
|
||||
OpExecutionMode %3 LocalSize 1 1 1
|
||||
OpName %3 "test_function"
|
||||
OpName %5 "v"
|
||||
OpName %9 "n"
|
||||
OpName %10 "offset"
|
||||
OpName %14 "count"
|
||||
%2 = OpTypeVoid
|
||||
%1 = OpTypeFunction %2
|
||||
%7 = OpTypeInt 32 1
|
||||
%6 = OpTypePointer Function %7
|
||||
%8 = OpConstantNull %7
|
||||
%12 = OpTypeInt 32 0
|
||||
%11 = OpTypePointer Function %12
|
||||
%13 = OpConstantNull %12
|
||||
%3 = OpFunction %2 None %1
|
||||
%4 = OpLabel
|
||||
%5 = OpVariable %6 Function %8
|
||||
%9 = OpVariable %6 Function %8
|
||||
%10 = OpVariable %11 Function %13
|
||||
%14 = OpVariable %11 Function %13
|
||||
%16 = OpLoad %7 %5
|
||||
%17 = OpLoad %7 %9
|
||||
%18 = OpLoad %12 %10
|
||||
%19 = OpLoad %12 %14
|
||||
%15 = OpBitFieldInsert %7 %16 %17 %18 %19
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_F(BuiltinBuilderTest, Call_InsertBits_u32) {
|
||||
auto* v = Var("v", ty.u32());
|
||||
auto* n = Var("n", ty.u32());
|
||||
auto* offset = Var("offset", ty.u32());
|
||||
auto* count = Var("count", ty.u32());
|
||||
auto* call = Call("insertBits", v, n, offset, count);
|
||||
auto* func = WrapInFunction(v, n, offset, count, call);
|
||||
|
||||
spirv::Builder& b = Build();
|
||||
|
||||
ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
|
||||
|
||||
EXPECT_EQ(DumpBuilder(b), R"(OpEntryPoint GLCompute %3 "test_function"
|
||||
OpExecutionMode %3 LocalSize 1 1 1
|
||||
OpName %3 "test_function"
|
||||
OpName %5 "v"
|
||||
OpName %9 "n"
|
||||
OpName %10 "offset"
|
||||
OpName %11 "count"
|
||||
%2 = OpTypeVoid
|
||||
%1 = OpTypeFunction %2
|
||||
%7 = OpTypeInt 32 0
|
||||
%6 = OpTypePointer Function %7
|
||||
%8 = OpConstantNull %7
|
||||
%3 = OpFunction %2 None %1
|
||||
%4 = OpLabel
|
||||
%5 = OpVariable %6 Function %8
|
||||
%9 = OpVariable %6 Function %8
|
||||
%10 = OpVariable %6 Function %8
|
||||
%11 = OpVariable %6 Function %8
|
||||
%13 = OpLoad %7 %5
|
||||
%14 = OpLoad %7 %9
|
||||
%15 = OpLoad %7 %10
|
||||
%16 = OpLoad %7 %11
|
||||
%12 = OpBitFieldInsert %7 %13 %14 %15 %16
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_F(BuiltinBuilderTest, Call_InsertBits_vec3_i32) {
|
||||
auto* v = Var("v", ty.vec3<i32>());
|
||||
auto* n = Var("n", ty.vec3<i32>());
|
||||
auto* offset = Var("offset", ty.u32());
|
||||
auto* count = Var("count", ty.u32());
|
||||
auto* call = Call("insertBits", v, n, offset, count);
|
||||
auto* func = WrapInFunction(v, n, offset, count, call);
|
||||
|
||||
spirv::Builder& b = Build();
|
||||
|
||||
ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
|
||||
|
||||
EXPECT_EQ(DumpBuilder(b), R"(OpEntryPoint GLCompute %3 "test_function"
|
||||
OpExecutionMode %3 LocalSize 1 1 1
|
||||
OpName %3 "test_function"
|
||||
OpName %5 "v"
|
||||
OpName %10 "n"
|
||||
OpName %11 "offset"
|
||||
OpName %15 "count"
|
||||
%2 = OpTypeVoid
|
||||
%1 = OpTypeFunction %2
|
||||
%8 = OpTypeInt 32 1
|
||||
%7 = OpTypeVector %8 3
|
||||
%6 = OpTypePointer Function %7
|
||||
%9 = OpConstantNull %7
|
||||
%13 = OpTypeInt 32 0
|
||||
%12 = OpTypePointer Function %13
|
||||
%14 = OpConstantNull %13
|
||||
%3 = OpFunction %2 None %1
|
||||
%4 = OpLabel
|
||||
%5 = OpVariable %6 Function %9
|
||||
%10 = OpVariable %6 Function %9
|
||||
%11 = OpVariable %12 Function %14
|
||||
%15 = OpVariable %12 Function %14
|
||||
%17 = OpLoad %7 %5
|
||||
%18 = OpLoad %7 %10
|
||||
%19 = OpLoad %13 %11
|
||||
%20 = OpLoad %13 %15
|
||||
%16 = OpBitFieldInsert %7 %17 %18 %19 %20
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_F(BuiltinBuilderTest, Call_InsertBits_vec3_u32) {
|
||||
auto* v = Var("v", ty.vec3<u32>());
|
||||
auto* n = Var("n", ty.vec3<u32>());
|
||||
auto* offset = Var("offset", ty.u32());
|
||||
auto* count = Var("count", ty.u32());
|
||||
auto* call = Call("insertBits", v, n, offset, count);
|
||||
auto* func = WrapInFunction(v, n, offset, count, call);
|
||||
|
||||
spirv::Builder& b = Build();
|
||||
|
||||
ASSERT_TRUE(b.GenerateFunction(func)) << b.error();
|
||||
|
||||
EXPECT_EQ(DumpBuilder(b), R"(OpEntryPoint GLCompute %3 "test_function"
|
||||
OpExecutionMode %3 LocalSize 1 1 1
|
||||
OpName %3 "test_function"
|
||||
OpName %5 "v"
|
||||
OpName %10 "n"
|
||||
OpName %11 "offset"
|
||||
OpName %14 "count"
|
||||
%2 = OpTypeVoid
|
||||
%1 = OpTypeFunction %2
|
||||
%8 = OpTypeInt 32 0
|
||||
%7 = OpTypeVector %8 3
|
||||
%6 = OpTypePointer Function %7
|
||||
%9 = OpConstantNull %7
|
||||
%12 = OpTypePointer Function %8
|
||||
%13 = OpConstantNull %8
|
||||
%3 = OpFunction %2 None %1
|
||||
%4 = OpLabel
|
||||
%5 = OpVariable %6 Function %9
|
||||
%10 = OpVariable %6 Function %9
|
||||
%11 = OpVariable %12 Function %13
|
||||
%14 = OpVariable %12 Function %13
|
||||
%16 = OpLoad %7 %5
|
||||
%17 = OpLoad %7 %10
|
||||
%18 = OpLoad %8 %11
|
||||
%19 = OpLoad %8 %14
|
||||
%15 = OpBitFieldInsert %7 %16 %17 %18 %19
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace spirv
|
||||
} // namespace writer
|
||||
|
||||
Reference in New Issue
Block a user