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:
James Price 2021-06-17 08:35:54 +00:00 committed by Tint LUCI CQ
parent 52b6a004b8
commit c932b5535f
25 changed files with 243 additions and 10 deletions

View File

@ -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;

View File

@ -23,6 +23,7 @@ namespace ast {
/// The unary op
enum class UnaryOp {
kAddressOf, // &EXPR
kComplement, // ~EXPR
kIndirection, // *EXPR
kNegation, // -EXPR
kNot, // !EXPR

View File

@ -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;
}

View File

@ -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}

View File

@ -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;

View File

@ -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>;

View File

@ -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)) {

View File

@ -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

View File

@ -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 "^";

View File

@ -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; }

View File

@ -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.

View File

@ -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) {

View File

@ -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;

View File

@ -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(

View File

@ -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;

View File

@ -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(

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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(

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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

View File

@ -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);
}