Implement bitwise complement operator
This translates to/from OpNot for SPIR-V, and ~ for all three textual language backends. Fixed: tint:866 Change-Id: Id934fb309221e3fca0e7efa33edaaae137fd8085 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/54980 Auto-Submit: James Price <jrprice@google.com> Commit-Queue: Ben Clayton <bclayton@google.com> Kokoro: Kokoro <noreply+kokoro@google.com> Reviewed-by: Ben Clayton <bclayton@google.com>
This commit is contained in:
parent
52b6a004b8
commit
c932b5535f
|
@ -23,6 +23,10 @@ std::ostream& operator<<(std::ostream& out, UnaryOp mod) {
|
|||
out << "address-of";
|
||||
break;
|
||||
}
|
||||
case UnaryOp::kComplement: {
|
||||
out << "complement";
|
||||
break;
|
||||
}
|
||||
case UnaryOp::kIndirection: {
|
||||
out << "indirection";
|
||||
break;
|
||||
|
|
|
@ -23,6 +23,7 @@ namespace ast {
|
|||
/// The unary op
|
||||
enum class UnaryOp {
|
||||
kAddressOf, // &EXPR
|
||||
kComplement, // ~EXPR
|
||||
kIndirection, // *EXPR
|
||||
kNegation, // -EXPR
|
||||
kNot, // !EXPR
|
||||
|
|
|
@ -155,9 +155,11 @@ bool GetUnaryOp(SpvOp opcode, ast::UnaryOp* ast_unary_op) {
|
|||
*ast_unary_op = ast::UnaryOp::kNegation;
|
||||
return true;
|
||||
case SpvOpLogicalNot:
|
||||
case SpvOpNot:
|
||||
*ast_unary_op = ast::UnaryOp::kNot;
|
||||
return true;
|
||||
case SpvOpNot:
|
||||
*ast_unary_op = ast::UnaryOp::kComplement;
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -1136,7 +1136,7 @@ TEST_F(SpvUnaryBitTest, Not_Int_Int) {
|
|||
__i32
|
||||
{
|
||||
UnaryOp[not set]{
|
||||
not
|
||||
complement
|
||||
ScalarConstructor[not set]{30}
|
||||
}
|
||||
}
|
||||
|
@ -1164,7 +1164,7 @@ TEST_F(SpvUnaryBitTest, Not_Int_Uint) {
|
|||
{
|
||||
Bitcast[not set]<__i32>{
|
||||
UnaryOp[not set]{
|
||||
not
|
||||
complement
|
||||
ScalarConstructor[not set]{10u}
|
||||
}
|
||||
}
|
||||
|
@ -1193,7 +1193,7 @@ TEST_F(SpvUnaryBitTest, Not_Uint_Int) {
|
|||
{
|
||||
Bitcast[not set]<__u32>{
|
||||
UnaryOp[not set]{
|
||||
not
|
||||
complement
|
||||
ScalarConstructor[not set]{30}
|
||||
}
|
||||
}
|
||||
|
@ -1221,7 +1221,7 @@ TEST_F(SpvUnaryBitTest, Not_Uint_Uint) {
|
|||
__u32
|
||||
{
|
||||
UnaryOp[not set]{
|
||||
not
|
||||
complement
|
||||
ScalarConstructor[not set]{10u}
|
||||
}
|
||||
}
|
||||
|
@ -1248,7 +1248,7 @@ TEST_F(SpvUnaryBitTest, Not_SignedVec_SignedVec) {
|
|||
__vec_2__i32
|
||||
{
|
||||
UnaryOp[not set]{
|
||||
not
|
||||
complement
|
||||
TypeConstructor[not set]{
|
||||
__vec_2__i32
|
||||
ScalarConstructor[not set]{30}
|
||||
|
@ -1280,7 +1280,7 @@ TEST_F(SpvUnaryBitTest, Not_SignedVec_UnsignedVec) {
|
|||
{
|
||||
Bitcast[not set]<__vec_2__i32>{
|
||||
UnaryOp[not set]{
|
||||
not
|
||||
complement
|
||||
TypeConstructor[not set]{
|
||||
__vec_2__u32
|
||||
ScalarConstructor[not set]{10u}
|
||||
|
@ -1313,7 +1313,7 @@ TEST_F(SpvUnaryBitTest, Not_UnsignedVec_SignedVec) {
|
|||
{
|
||||
Bitcast[not set]<__vec_2__u32>{
|
||||
UnaryOp[not set]{
|
||||
not
|
||||
complement
|
||||
TypeConstructor[not set]{
|
||||
__vec_2__i32
|
||||
ScalarConstructor[not set]{30}
|
||||
|
@ -1344,7 +1344,7 @@ TEST_F(SpvUnaryBitTest, Not_UnsignedVec_UnsignedVec) {
|
|||
__vec_2__u32
|
||||
{
|
||||
UnaryOp[not set]{
|
||||
not
|
||||
complement
|
||||
TypeConstructor[not set]{
|
||||
__vec_2__u32
|
||||
ScalarConstructor[not set]{10u}
|
||||
|
|
|
@ -456,6 +456,10 @@ Token Lexer::try_punctuation() {
|
|||
type = Token::Type::kStar;
|
||||
pos_ += 1;
|
||||
location_.column += 1;
|
||||
} else if (matches(pos_, "~")) {
|
||||
type = Token::Type::kTilde;
|
||||
pos_ += 1;
|
||||
location_.column += 1;
|
||||
} else if (matches(pos_, "^")) {
|
||||
type = Token::Type::kXor;
|
||||
pos_ += 1;
|
||||
|
|
|
@ -395,6 +395,7 @@ INSTANTIATE_TEST_SUITE_P(
|
|||
TokenData{")", Token::Type::kParenRight},
|
||||
TokenData{";", Token::Type::kSemicolon},
|
||||
TokenData{"*", Token::Type::kStar},
|
||||
TokenData{"~", Token::Type::kTilde},
|
||||
TokenData{"^", Token::Type::kXor}));
|
||||
|
||||
using KeywordTest = testing::TestWithParam<TokenData>;
|
||||
|
|
|
@ -2308,6 +2308,7 @@ Expect<ast::ExpressionList> ParserImpl::expect_argument_expression_list(
|
|||
// : singular_expression
|
||||
// | MINUS unary_expression
|
||||
// | BANG unary_expression
|
||||
// | TILDE unary_expression
|
||||
// | STAR unary_expression
|
||||
// | AND unary_expression
|
||||
Maybe<ast::Expression*> ParserImpl::unary_expression() {
|
||||
|
@ -2318,6 +2319,8 @@ Maybe<ast::Expression*> ParserImpl::unary_expression() {
|
|||
op = ast::UnaryOp::kNegation;
|
||||
} else if (match(Token::Type::kBang)) {
|
||||
op = ast::UnaryOp::kNot;
|
||||
} else if (match(Token::Type::kTilde)) {
|
||||
op = ast::UnaryOp::kComplement;
|
||||
} else if (match(Token::Type::kStar)) {
|
||||
op = ast::UnaryOp::kIndirection;
|
||||
} else if (match(Token::Type::kAnd)) {
|
||||
|
|
|
@ -157,6 +157,26 @@ TEST_F(ParserImplTest, UnaryExpression_Bang_InvalidRHS) {
|
|||
EXPECT_EQ(p->error(), "1:2: unable to parse right side of ! expression");
|
||||
}
|
||||
|
||||
TEST_F(ParserImplTest, UnaryExpression_Tilde) {
|
||||
auto p = parser("~1");
|
||||
auto e = p->unary_expression();
|
||||
EXPECT_TRUE(e.matched);
|
||||
EXPECT_FALSE(e.errored);
|
||||
EXPECT_FALSE(p->has_error()) << p->error();
|
||||
ASSERT_NE(e.value, nullptr);
|
||||
ASSERT_TRUE(e->Is<ast::UnaryOpExpression>());
|
||||
|
||||
auto* u = e->As<ast::UnaryOpExpression>();
|
||||
ASSERT_EQ(u->op(), ast::UnaryOp::kComplement);
|
||||
|
||||
ASSERT_TRUE(u->expr()->Is<ast::ConstructorExpression>());
|
||||
ASSERT_TRUE(u->expr()->Is<ast::ScalarConstructorExpression>());
|
||||
|
||||
auto* init = u->expr()->As<ast::ScalarConstructorExpression>();
|
||||
ASSERT_TRUE(init->literal()->Is<ast::SintLiteral>());
|
||||
EXPECT_EQ(init->literal()->As<ast::SintLiteral>()->value(), 1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace wgsl
|
||||
} // namespace reader
|
||||
|
|
|
@ -102,6 +102,8 @@ std::string Token::TypeToName(Type type) {
|
|||
return ";";
|
||||
case Token::Type::kStar:
|
||||
return "*";
|
||||
case Token::Type::kTilde:
|
||||
return "~";
|
||||
case Token::Type::kXor:
|
||||
return "^";
|
||||
|
||||
|
|
|
@ -110,6 +110,8 @@ class Token {
|
|||
kSemicolon,
|
||||
/// A '*'
|
||||
kStar,
|
||||
/// A '~'
|
||||
kTilde,
|
||||
/// A '^'
|
||||
kXor,
|
||||
|
||||
|
@ -445,6 +447,8 @@ class Token {
|
|||
bool IsSemicolon() const { return type_ == Type::kSemicolon; }
|
||||
/// @returns true if token is a '*'
|
||||
bool IsStar() const { return type_ == Type::kStar; }
|
||||
/// @returns true if token is a '~'
|
||||
bool IsTilde() const { return type_ == Type::kTilde; }
|
||||
/// @returns true if token is a '^'
|
||||
bool IsXor() const { return type_ == Type::kXor; }
|
||||
|
||||
|
|
|
@ -2500,6 +2500,7 @@ bool Resolver::UnaryOp(ast::UnaryOpExpression* unary) {
|
|||
const sem::Type* type = nullptr;
|
||||
|
||||
switch (unary->op()) {
|
||||
case ast::UnaryOp::kComplement:
|
||||
case ast::UnaryOp::kNegation:
|
||||
case ast::UnaryOp::kNot:
|
||||
// Result type matches the deref'd inner type.
|
||||
|
|
|
@ -1946,7 +1946,8 @@ TEST_P(UnaryOpExpressionTest, Expr_UnaryOp) {
|
|||
}
|
||||
INSTANTIATE_TEST_SUITE_P(ResolverTest,
|
||||
UnaryOpExpressionTest,
|
||||
testing::Values(ast::UnaryOp::kNegation,
|
||||
testing::Values(ast::UnaryOp::kComplement,
|
||||
ast::UnaryOp::kNegation,
|
||||
ast::UnaryOp::kNot));
|
||||
|
||||
TEST_F(ResolverTest, StorageClass_SetsIfMissing) {
|
||||
|
|
|
@ -2448,6 +2448,9 @@ bool GeneratorImpl::EmitUnaryOp(std::ostream& pre,
|
|||
case ast::UnaryOp::kIndirection:
|
||||
case ast::UnaryOp::kAddressOf:
|
||||
return EmitExpression(pre, out, expr->expr());
|
||||
case ast::UnaryOp::kComplement:
|
||||
out << "~";
|
||||
break;
|
||||
case ast::UnaryOp::kNot:
|
||||
out << "!";
|
||||
break;
|
||||
|
|
|
@ -33,6 +33,18 @@ TEST_F(HlslUnaryOpTest, AddressOf) {
|
|||
EXPECT_EQ(result(), "expr");
|
||||
}
|
||||
|
||||
TEST_F(HlslUnaryOpTest, Complement) {
|
||||
Global("expr", ty.f32(), ast::StorageClass::kPrivate);
|
||||
auto* op =
|
||||
create<ast::UnaryOpExpression>(ast::UnaryOp::kComplement, Expr("expr"));
|
||||
WrapInFunction(op);
|
||||
|
||||
GeneratorImpl& gen = Build();
|
||||
|
||||
ASSERT_TRUE(gen.EmitExpression(pre, out, op)) << gen.error();
|
||||
EXPECT_EQ(result(), "~(expr)");
|
||||
}
|
||||
|
||||
TEST_F(HlslUnaryOpTest, Indirection) {
|
||||
Global("G", ty.f32(), ast::StorageClass::kPrivate);
|
||||
auto* p = Const(
|
||||
|
|
|
@ -2288,6 +2288,9 @@ bool GeneratorImpl::EmitUnaryOp(ast::UnaryOpExpression* expr) {
|
|||
case ast::UnaryOp::kAddressOf:
|
||||
out_ << "&";
|
||||
break;
|
||||
case ast::UnaryOp::kComplement:
|
||||
out_ << "~";
|
||||
break;
|
||||
case ast::UnaryOp::kIndirection:
|
||||
out_ << "*";
|
||||
break;
|
||||
|
|
|
@ -33,6 +33,18 @@ TEST_F(MslUnaryOpTest, AddressOf) {
|
|||
EXPECT_EQ(gen.result(), "&(expr)");
|
||||
}
|
||||
|
||||
TEST_F(MslUnaryOpTest, Complement) {
|
||||
Global("expr", ty.f32(), ast::StorageClass::kPrivate);
|
||||
auto* op =
|
||||
create<ast::UnaryOpExpression>(ast::UnaryOp::kComplement, Expr("expr"));
|
||||
WrapInFunction(op);
|
||||
|
||||
GeneratorImpl& gen = Build();
|
||||
|
||||
ASSERT_TRUE(gen.EmitExpression(op)) << gen.error();
|
||||
EXPECT_EQ(gen.result(), "~(expr)");
|
||||
}
|
||||
|
||||
TEST_F(MslUnaryOpTest, Indirection) {
|
||||
Global("G", ty.f32(), ast::StorageClass::kPrivate);
|
||||
auto* p = Const(
|
||||
|
|
|
@ -1158,6 +1158,9 @@ uint32_t Builder::GenerateUnaryOpExpression(ast::UnaryOpExpression* expr) {
|
|||
|
||||
spv::Op op = spv::Op::OpNop;
|
||||
switch (expr->op()) {
|
||||
case ast::UnaryOp::kComplement:
|
||||
op = spv::Op::OpNot;
|
||||
break;
|
||||
case ast::UnaryOp::kNegation:
|
||||
if (TypeOf(expr)->is_float_scalar_or_vector()) {
|
||||
op = spv::Op::OpFNegate;
|
||||
|
|
|
@ -55,6 +55,23 @@ TEST_F(BuilderTest, UnaryOp_Negation_Float) {
|
|||
)");
|
||||
}
|
||||
|
||||
TEST_F(BuilderTest, UnaryOp_Complement) {
|
||||
auto* expr =
|
||||
create<ast::UnaryOpExpression>(ast::UnaryOp::kComplement, Expr(1));
|
||||
WrapInFunction(expr);
|
||||
|
||||
spirv::Builder& b = Build();
|
||||
|
||||
b.push_function(Function{});
|
||||
EXPECT_EQ(b.GenerateUnaryOpExpression(expr), 1u) << b.error();
|
||||
EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
|
||||
%3 = OpConstant %2 1
|
||||
)");
|
||||
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
|
||||
R"(%1 = OpNot %2 %3
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_F(BuilderTest, UnaryOp_Not) {
|
||||
auto* expr = create<ast::UnaryOpExpression>(ast::UnaryOp::kNot, Expr(false));
|
||||
WrapInFunction(expr);
|
||||
|
|
|
@ -761,6 +761,9 @@ bool GeneratorImpl::EmitUnaryOp(ast::UnaryOpExpression* expr) {
|
|||
case ast::UnaryOp::kAddressOf:
|
||||
out_ << "&";
|
||||
break;
|
||||
case ast::UnaryOp::kComplement:
|
||||
out_ << "~";
|
||||
break;
|
||||
case ast::UnaryOp::kIndirection:
|
||||
out_ << "*";
|
||||
break;
|
||||
|
|
|
@ -33,6 +33,18 @@ TEST_F(WgslUnaryOpTest, AddressOf) {
|
|||
EXPECT_EQ(gen.result(), "&(expr)");
|
||||
}
|
||||
|
||||
TEST_F(WgslUnaryOpTest, Complement) {
|
||||
Global("expr", ty.f32(), ast::StorageClass::kPrivate);
|
||||
auto* op =
|
||||
create<ast::UnaryOpExpression>(ast::UnaryOp::kComplement, Expr("expr"));
|
||||
WrapInFunction(op);
|
||||
|
||||
GeneratorImpl& gen = Build();
|
||||
|
||||
ASSERT_TRUE(gen.EmitExpression(op)) << gen.error();
|
||||
EXPECT_EQ(gen.result(), "~(expr)");
|
||||
}
|
||||
|
||||
TEST_F(WgslUnaryOpTest, Indirection) {
|
||||
Global("G", ty.f32(), ast::StorageClass::kPrivate);
|
||||
auto* p = Const(
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
fn i(x : i32) -> i32 {
|
||||
return ~x;
|
||||
}
|
||||
|
||||
fn u(x : u32) -> u32 {
|
||||
return ~x;
|
||||
}
|
||||
|
||||
fn vi(x : vec4<i32>) -> vec4<i32> {
|
||||
return ~x;
|
||||
}
|
||||
|
||||
fn vu(x : vec4<u32>) -> vec4<u32> {
|
||||
return ~x;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
[numthreads(1, 1, 1)]
|
||||
void unused_entry_point() {
|
||||
return;
|
||||
}
|
||||
|
||||
int i(int x) {
|
||||
return ~(x);
|
||||
}
|
||||
|
||||
uint u(uint x) {
|
||||
return ~(x);
|
||||
}
|
||||
|
||||
int4 vi(int4 x) {
|
||||
return ~(x);
|
||||
}
|
||||
|
||||
uint4 vu(uint4 x) {
|
||||
return ~(x);
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
#include <metal_stdlib>
|
||||
|
||||
using namespace metal;
|
||||
int i(int x) {
|
||||
return ~(x);
|
||||
}
|
||||
|
||||
uint u(uint x) {
|
||||
return ~(x);
|
||||
}
|
||||
|
||||
int4 vi(int4 x) {
|
||||
return ~(x);
|
||||
}
|
||||
|
||||
uint4 vu(uint4 x) {
|
||||
return ~(x);
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
; SPIR-V
|
||||
; Version: 1.3
|
||||
; Generator: Google Tint Compiler; 0
|
||||
; Bound: 29
|
||||
; Schema: 0
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %unused_entry_point "unused_entry_point"
|
||||
OpExecutionMode %unused_entry_point LocalSize 1 1 1
|
||||
OpName %unused_entry_point "unused_entry_point"
|
||||
OpName %i "i"
|
||||
OpName %x "x"
|
||||
OpName %u "u"
|
||||
OpName %x_0 "x"
|
||||
OpName %vi "vi"
|
||||
OpName %x_1 "x"
|
||||
OpName %vu "vu"
|
||||
OpName %x_2 "x"
|
||||
%void = OpTypeVoid
|
||||
%1 = OpTypeFunction %void
|
||||
%int = OpTypeInt 32 1
|
||||
%5 = OpTypeFunction %int %int
|
||||
%uint = OpTypeInt 32 0
|
||||
%11 = OpTypeFunction %uint %uint
|
||||
%v4int = OpTypeVector %int 4
|
||||
%17 = OpTypeFunction %v4int %v4int
|
||||
%v4uint = OpTypeVector %uint 4
|
||||
%23 = OpTypeFunction %v4uint %v4uint
|
||||
%unused_entry_point = OpFunction %void None %1
|
||||
%4 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
%i = OpFunction %int None %5
|
||||
%x = OpFunctionParameter %int
|
||||
%9 = OpLabel
|
||||
%10 = OpNot %int %x
|
||||
OpReturnValue %10
|
||||
OpFunctionEnd
|
||||
%u = OpFunction %uint None %11
|
||||
%x_0 = OpFunctionParameter %uint
|
||||
%15 = OpLabel
|
||||
%16 = OpNot %uint %x_0
|
||||
OpReturnValue %16
|
||||
OpFunctionEnd
|
||||
%vi = OpFunction %v4int None %17
|
||||
%x_1 = OpFunctionParameter %v4int
|
||||
%21 = OpLabel
|
||||
%22 = OpNot %v4int %x_1
|
||||
OpReturnValue %22
|
||||
OpFunctionEnd
|
||||
%vu = OpFunction %v4uint None %23
|
||||
%x_2 = OpFunctionParameter %v4uint
|
||||
%27 = OpLabel
|
||||
%28 = OpNot %v4uint %x_2
|
||||
OpReturnValue %28
|
||||
OpFunctionEnd
|
|
@ -0,0 +1,15 @@
|
|||
fn i(x : i32) -> i32 {
|
||||
return ~(x);
|
||||
}
|
||||
|
||||
fn u(x : u32) -> u32 {
|
||||
return ~(x);
|
||||
}
|
||||
|
||||
fn vi(x : vec4<i32>) -> vec4<i32> {
|
||||
return ~(x);
|
||||
}
|
||||
|
||||
fn vu(x : vec4<u32>) -> vec4<u32> {
|
||||
return ~(x);
|
||||
}
|
Loading…
Reference in New Issue