Assembler support for OpSpecConstantOp
authorDavid Neto <dneto@google.com>
Wed, 11 Nov 2015 06:56:49 +0000 (01:56 -0500)
committerDavid Neto <dneto@google.com>
Wed, 11 Nov 2015 17:12:04 +0000 (12:12 -0500)
Adds SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER.

include/libspirv/libspirv.h
source/assembly_grammar.cpp
source/assembly_grammar.h
source/opcode.cpp
source/operand.cpp
source/text.cpp
test/TextToBinary.Constant.cpp

index b61138c..1e95115 100644 (file)
@@ -182,6 +182,10 @@ typedef enum spv_operand_type_t {
   // number indicating which instruction to use from an extended instruction
   // set.
   SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER,
+  // The Opcode argument to OpSpecConstantOp. It determines the operation
+  // to be performed on constant operands to compute a specialization constant
+  // result.
+  SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER,
   // A literal number whose format and size are determined by a previous operand
   // in the same instruction.  It's a signed integer, an unsigned integer, or a
   // floating point number.  It also has a specified bit width.  The width
@@ -227,11 +231,11 @@ typedef enum spv_operand_type_t {
 #undef FIRST_CONCRETE
 #undef LAST_CONCRETE
 
-  // The remaining operand types are only used internally by the assembler.
-  // There are two categories:
-  //    Optional : expands to 0 or 1 operand, like ? in regular expressions.
-  //    Variable : expands to 0, 1 or many operands or pairs of operands.
-  //               This is similar to * in regular expressions.
+// The remaining operand types are only used internally by the assembler.
+// There are two categories:
+//    Optional : expands to 0 or 1 operand, like ? in regular expressions.
+//    Variable : expands to 0, 1 or many operands or pairs of operands.
+//               This is similar to * in regular expressions.
 
 // Macros for defining bounds on optional and variable operand types.
 // Any variable operand type is also optional.
index 98c8a3d..5e18f94 100644 (file)
@@ -109,6 +109,89 @@ spv_ext_inst_table GetDefaultExtInstTable() {
   return result;
 }
 
+// Associates an opcode with its name.
+struct SpecConstantOpcodeEntry {
+  SpvOp opcode;
+  const char* name;
+};
+
+// All the opcodes allowed as the operation for OpSpecConstantOp.
+// The name does not have the usual "Op" prefix. For example opcode SpvOpIAdd
+// is associated with the name "IAdd".
+//
+// clang-format off
+#define CASE(NAME) { SpvOp##NAME, #NAME }
+const SpecConstantOpcodeEntry kOpSpecConstantOpcodes[] = {
+    // Conversion
+    CASE(SConvert),
+    CASE(FConvert),
+    CASE(ConvertFToS),
+    CASE(ConvertSToF),
+    CASE(ConvertFToU),
+    CASE(ConvertUToF),
+    CASE(UConvert),
+    CASE(ConvertPtrToU),
+    CASE(ConvertUToPtr),
+    CASE(PtrCastToGeneric),
+    CASE(Bitcast),
+    CASE(QuantizeToF16),
+    // Arithmetic
+    CASE(SNegate),
+    CASE(Not),
+    CASE(IAdd),
+    CASE(ISub),
+    CASE(IMul),
+    CASE(UDiv),
+    CASE(SDiv),
+    CASE(UMod),
+    CASE(SRem),
+    CASE(SMod),
+    CASE(ShiftRightLogical),
+    CASE(ShiftRightArithmetic),
+    CASE(ShiftLeftLogical),
+    CASE(BitwiseOr),
+    CASE(BitwiseAnd),
+    CASE(FNegate),
+    CASE(FAdd),
+    CASE(FSub),
+    CASE(FMul),
+    CASE(FDiv),
+    CASE(FRem),
+    CASE(FMod),
+    // Composite
+    CASE(VectorShuffle),
+    CASE(CompositeExtract),
+    CASE(CompositeInsert),
+    // Logical
+    CASE(LogicalOr),
+    CASE(LogicalAnd),
+    CASE(LogicalNot),
+    CASE(LogicalEqual),
+    CASE(LogicalNotEqual),
+    CASE(Select),
+    // Comparison
+    CASE(IEqual),
+    CASE(ULessThan),
+    CASE(SLessThan),
+    CASE(UGreaterThan),
+    CASE(SGreaterThan),
+    CASE(ULessThanEqual),
+    CASE(SLessThanEqual),
+    CASE(SLessThanEqual),
+    CASE(UGreaterThanEqual),
+    CASE(SGreaterThanEqual),
+    // Memory
+    CASE(AccessChain),
+    CASE(InBoundsAccessChain),
+    CASE(PtrAccessChain),
+    CASE(InBoundsPtrAccessChain),
+};
+#undef CASE
+// clang-format on
+
+const size_t kNumOpSpecConstantOpcodes =
+    sizeof(kOpSpecConstantOpcodes) / sizeof(kOpSpecConstantOpcodes[0]);
+
 }  // anonymous namespace
 
 namespace libspirv {
@@ -145,6 +228,19 @@ spv_result_t AssemblyGrammar::lookupOperand(spv_operand_type_t type,
   return spvOperandTableValueLookup(operandTable_, type, operand, desc);
 }
 
+spv_result_t AssemblyGrammar::lookupSpecConstantOpcode(const char* name,
+                                                       SpvOp* opcode) const {
+  const auto* last = kOpSpecConstantOpcodes + kNumOpSpecConstantOpcodes;
+  const auto* found =
+      std::find_if(kOpSpecConstantOpcodes, last,
+                   [name](const SpecConstantOpcodeEntry& entry) {
+                     return 0 == strcmp(name, entry.name);
+                   });
+  if (found == last) return SPV_ERROR_INVALID_LOOKUP;
+  *opcode = found->opcode;
+  return SPV_SUCCESS;
+}
+
 spv_result_t AssemblyGrammar::parseMaskOperand(const spv_operand_type_t type,
                                                const char* textValue,
                                                uint32_t* pValue) const {
index da1663d..636e952 100644 (file)
@@ -71,6 +71,13 @@ class AssemblyGrammar {
   spv_result_t lookupOperand(spv_operand_type_t type, uint32_t operand,
                              spv_operand_desc* desc) const;
 
+  // Finds the opcode for the given OpSpecConstantOp opcode name. The name
+  // should not have the "Op" prefix.  For example, "IAdd" corresponds to
+  // the integer add opcode for OpSpecConstantOp.  On success, returns
+  // SPV_SUCCESS and sends the discovered operation code through the opcode
+  // parameter.  On failure, returns SPV_ERROR_INVALID_LOOKUP.
+  spv_result_t lookupSpecConstantOpcode(const char* name, SpvOp* opcode) const;
+
   // Parses a mask expression string for the given operand type.
   //
   // A mask expression is a sequence of one or more terms separated by '|',
index 588e2ad..c8f6851 100644 (file)
@@ -75,7 +75,8 @@ bool opcodeTableInitialized = false;
 // Opcode API
 
 // Converts the given operand class enum (from the SPIR-V document generation
-// logic) to the operand type required by the parser.
+// logic) to the operand type required by the parser.  The SPV_OPERAND_TYPE_NONE
+// value indicates there is no current operand and no further operands.
 // This only applies to logical operands.
 spv_operand_type_t convertOperandClassToType(SpvOp opcode,
                                              OperandClass operandClass) {
@@ -113,6 +114,13 @@ spv_operand_type_t convertOperandClassToType(SpvOp opcode,
     case OperandOptionalImage:
       return SPV_OPERAND_TYPE_OPTIONAL_IMAGE;
     case OperandVariableIds:
+      if (opcode == SpvOpSpecConstantOp) {
+        // These are the operands to the specialization constant opcode.
+        // The assembler and binary parser set up the extra Id and literal
+        // arguments when processing the opcode operand.  So don't add
+        // an operand type for them here.
+        return SPV_OPERAND_TYPE_NONE;
+      }
       return SPV_OPERAND_TYPE_VARIABLE_ID;
     // The spec only uses OptionalLiteral for an optional literal number.
     case OperandOptionalLiteral:
@@ -131,6 +139,12 @@ spv_operand_type_t convertOperandClassToType(SpvOp opcode,
         // TODO(dneto): Use a function to confirm the assumption, and to verify
         // that the index into the operandClass is 1, as expected.
         return SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER;
+      } else if (opcode == SpvOpSpecConstantOp) {
+        // Use a special operand type for the opcode operand, so we can
+        // use mnemonic names instead of the numbers.  For example, the
+        // assembler should accept "IAdd" instead of the numeric value of
+        // SpvOpIAdd.
+        return SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER;
       }
       return SPV_OPERAND_TYPE_LITERAL_INTEGER;
     case OperandLiteralString:
@@ -245,16 +259,20 @@ void spvOpcodeTableInitialize() {
          opcode.numTypes < maxNumOperands && classIndex < maxNumClasses;
          classIndex++) {
       const OperandClass operandClass = opcode.operandClass[classIndex];
-      opcode.operandTypes[opcode.numTypes++] =
+      const auto operandType =
           convertOperandClassToType(opcode.opcode, operandClass);
+      opcode.operandTypes[opcode.numTypes++] = operandType;
       // The OperandNone value is not explicitly represented in the .inc file.
       // However, it is the zero value, and is created via implicit value
-      // initialization.
-      if (operandClass == OperandNone) {
+      // initialization.  It converts to SPV_OPERAND_TYPE_NONE.
+      // The SPV_OPERAND_TYPE_NONE operand type indicates no current or futher
+      // operands.
+      if (operandType == SPV_OPERAND_TYPE_NONE) {
         opcode.numTypes--;
         break;
       }
     }
+
     // We should have written the terminating SPV_OPERAND_TYPE_NONE entry, but
     // also without overflowing.
     assert((opcode.numTypes < maxNumOperands) &&
index 40b9aea..a6b1ea7 100644 (file)
@@ -1191,6 +1191,8 @@ const char* spvOperandTypeStr(spv_operand_type_t type) {
       return "possibly multi-word literal number";
     case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER:
       return "extension instruction number";
+    case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER:
+      return "OpSpecConstantOp opcode";
     case SPV_OPERAND_TYPE_LITERAL_STRING:
     case SPV_OPERAND_TYPE_OPTIONAL_LITERAL_STRING:
       return "literal string";
index 8d42acd..b437ff0 100644 (file)
 #include <unordered_map>
 #include <vector>
 
+#include <libspirv/libspirv.h>
 #include "assembly_grammar.h"
 #include "binary.h"
 #include "diagnostic.h"
 #include "ext_inst.h"
 #include "instruction.h"
-#include <libspirv/libspirv.h>
 #include "opcode.h"
 #include "operand.h"
 #include "text_handler.h"
@@ -245,7 +245,6 @@ spv_result_t spvTextEncodeOperand(const libspirv::AssemblyGrammar& grammar,
         }
         pInst->extInstType = ext_inst_type;
       }
-
     } break;
 
     case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: {
@@ -260,8 +259,30 @@ spv_result_t spvTextEncodeOperand(const libspirv::AssemblyGrammar& grammar,
 
       // Prepare to parse the operands for the extended instructions.
       spvPrependOperandTypes(extInst->operandTypes, pExpectedOperands);
+    } break;
 
-      return SPV_SUCCESS;
+    case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: {
+      // The assembler accepts the symbolic name for the opcode, but without
+      // the "Op" prefix.  For example, "IAdd" is accepted.  The number
+      // of the opcode is emitted.
+      SpvOp opcode;
+      if (grammar.lookupSpecConstantOpcode(textValue, &opcode)) {
+        return context->diagnostic() << "Invalid " << spvOperandTypeStr(type)
+                                     << " '" << textValue << "'.";
+      }
+      spv_opcode_desc opcodeEntry = nullptr;
+      if (grammar.lookupOpcode(opcode, &opcodeEntry)) {
+        return context->diagnostic(SPV_ERROR_INTERNAL)
+               << "OpSpecConstant opcode table out of sync";
+      }
+      spvInstructionAddWord(pInst, uint32_t(opcodeEntry->opcode));
+
+      // Prepare to parse the operands for the opcode.  Except skip the
+      // type Id and result Id, since they've already been processed.
+      assert(opcodeEntry->hasType);
+      assert(opcodeEntry->hasResult);
+      assert(opcodeEntry->numTypes >= 2);
+      spvPrependOperandTypes(opcodeEntry->operandTypes + 2, pExpectedOperands);
     } break;
 
     case SPV_OPERAND_TYPE_LITERAL_INTEGER:
@@ -348,7 +369,8 @@ spv_result_t spvTextEncodeOperand(const libspirv::AssemblyGrammar& grammar,
                  << "Invalid extended instruction import '" << literal.value.str
                  << "'";
         }
-        if (auto error = context->recordIdAsExtInstImport(pInst->words[1], ext_inst_type))
+        if (auto error = context->recordIdAsExtInstImport(pInst->words[1],
+                                                          ext_inst_type))
           return error;
       }
 
index 63f6e05..ba76832 100644 (file)
@@ -471,6 +471,177 @@ INSTANTIATE_TEST_CASE_P(
         "%1 = OpTypeFloat 64\n%2 = OpSpecConstant %1 -1.79769e+308\n",
     }));
 
+// Test OpSpecConstantOp
+
+using OpSpecConstantOpTestWithIds =
+    spvtest::TextToBinaryTestBase<::testing::TestWithParam<EnumCase<SpvOp>>>;
+
+// The operands to the OpSpecConstantOp opcode are all Ids.
+TEST_P(OpSpecConstantOpTestWithIds, Assembly) {
+  std::stringstream input;
+  input << "%2 = OpSpecConstantOp %1 " << GetParam().name();
+  for (auto id : GetParam().operands()) input << " %" << id;
+  input << "\n";
+
+  EXPECT_THAT(CompiledInstructions(input.str()),
+              Eq(MakeInstruction(SpvOpSpecConstantOp,
+                                 {1, 2, uint32_t(GetParam().value())},
+                                 GetParam().operands())));
+}
+
+// clang-format off
+#define CASE1(NAME) { SpvOp##NAME, #NAME, {3} }
+#define CASE2(NAME) { SpvOp##NAME, #NAME, {3, 4} }
+#define CASE3(NAME) { SpvOp##NAME, #NAME, {3, 4, 5} }
+#define CASE4(NAME) { SpvOp##NAME, #NAME, {3, 4, 5, 6} }
+#define CASE5(NAME) { SpvOp##NAME, #NAME, {3, 4, 5, 6, 7} }
+#define CASE6(NAME) { SpvOp##NAME, #NAME, {3, 4, 5, 6, 7, 8} }
+INSTANTIATE_TEST_CASE_P(
+    TextToBinaryOpSpecConstantOp, OpSpecConstantOpTestWithIds,
+    ::testing::ValuesIn(std::vector<EnumCase<SpvOp>>{
+        // Conversion
+        CASE1(SConvert),
+        CASE1(FConvert),
+        CASE1(ConvertFToS),
+        CASE1(ConvertSToF),
+        CASE1(ConvertFToU),
+        CASE1(ConvertUToF),
+        CASE1(UConvert),
+        CASE1(ConvertPtrToU),
+        CASE1(ConvertUToPtr),
+        CASE1(PtrCastToGeneric),
+        CASE1(Bitcast),
+        CASE1(QuantizeToF16),
+        // Arithmetic
+        CASE1(SNegate),
+        CASE1(Not),
+        CASE2(IAdd),
+        CASE2(ISub),
+        CASE2(IMul),
+        CASE2(UDiv),
+        CASE2(SDiv),
+        CASE2(UMod),
+        CASE2(SRem),
+        CASE2(SMod),
+        CASE2(ShiftRightLogical),
+        CASE2(ShiftRightArithmetic),
+        CASE2(ShiftLeftLogical),
+        CASE2(BitwiseOr),
+        CASE2(BitwiseAnd),
+        CASE1(FNegate),
+        CASE2(FAdd),
+        CASE2(FSub),
+        CASE2(FMul),
+        CASE2(FDiv),
+        CASE2(FRem),
+        CASE2(FMod),
+        // Composite operations use literal numbers. So they're in another test.
+        // Logical
+        CASE2(LogicalOr),
+        CASE2(LogicalAnd),
+        CASE1(LogicalNot),
+        CASE2(LogicalEqual),
+        CASE2(LogicalNotEqual),
+        CASE3(Select),
+        // Comparison
+        CASE2(IEqual),
+        CASE2(ULessThan),
+        CASE2(SLessThan),
+        CASE2(UGreaterThan),
+        CASE2(SGreaterThan),
+        CASE2(ULessThanEqual),
+        CASE2(SLessThanEqual),
+        CASE2(SLessThanEqual),
+        CASE2(UGreaterThanEqual),
+        CASE2(SGreaterThanEqual),
+        // Memory
+        // For AccessChain, there is a base Id, then a sequence of index Ids.
+        // Having no index Ids is a corner case.
+        CASE1(AccessChain),
+        CASE2(AccessChain),
+        CASE6(AccessChain),
+        CASE1(InBoundsAccessChain),
+        CASE2(InBoundsAccessChain),
+        CASE6(InBoundsAccessChain),
+        // PtrAccessChain also has an element Id.
+        CASE2(PtrAccessChain),
+        CASE3(PtrAccessChain),
+        CASE6(PtrAccessChain),
+        CASE2(InBoundsPtrAccessChain),
+        CASE3(InBoundsPtrAccessChain),
+        CASE6(InBoundsPtrAccessChain),
+    }));
+#undef CASE1
+#undef CASE2
+#undef CASE3
+#undef CASE4
+#undef CASE5
+#undef CASE6
+// clang-format on
+
+using OpSpecConstantOpTestWithTwoIdsThenLiteralNumbers =
+    spvtest::TextToBinaryTestBase<::testing::TestWithParam<EnumCase<SpvOp>>>;
+
+// The operands to the OpSpecConstantOp opcode are two Ids followed by a
+// sequence of literal numbers.
+TEST_P(OpSpecConstantOpTestWithTwoIdsThenLiteralNumbers, Assembly) {
+  std::stringstream input;
+  input << "%2 = OpSpecConstantOp %1 " << GetParam().name() << " %3 %4";
+  for (auto number : GetParam().operands()) input << " " << number;
+  input << "\n";
+
+  EXPECT_THAT(CompiledInstructions(input.str()),
+              Eq(MakeInstruction(SpvOpSpecConstantOp,
+                                 {1, 2, uint32_t(GetParam().value()), 3, 4},
+                                 GetParam().operands())));
+}
+
+#define CASE(NAME) SpvOp##NAME, #NAME
+INSTANTIATE_TEST_CASE_P(
+    TextToBinaryOpSpecConstantOp,
+    OpSpecConstantOpTestWithTwoIdsThenLiteralNumbers,
+    ::testing::ValuesIn(std::vector<EnumCase<SpvOp>>{
+        // For VectorShuffle, there are two vector operands, and at least
+        // two selector Ids.  OpenCL can have up to 16-element vectors.
+        {CASE(VectorShuffle), {0, 0}},
+        {CASE(VectorShuffle), {4, 3, 2, 1}},
+        {CASE(VectorShuffle), {0, 2, 4, 6, 1, 3, 5, 7}},
+        {CASE(VectorShuffle),
+         {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}},
+        // For CompositeInsert, there is an object to insert, the target
+        // composite, and then literal indices.
+        {CASE(CompositeInsert), {0}},
+        {CASE(CompositeInsert), {4, 3, 99, 1}},
+    }));
+
+using OpSpecConstantOpTestWithOneIdThenLiteralNumbers =
+    spvtest::TextToBinaryTestBase<::testing::TestWithParam<EnumCase<SpvOp>>>;
+
+// The operands to the OpSpecConstantOp opcode are one Id followed by a
+// sequence of literal numbers.
+TEST_P(OpSpecConstantOpTestWithOneIdThenLiteralNumbers, Assembly) {
+  std::stringstream input;
+  input << "%2 = OpSpecConstantOp %1 " << GetParam().name() << " %3";
+  for (auto number : GetParam().operands()) input << " " << number;
+  input << "\n";
+
+  EXPECT_THAT(CompiledInstructions(input.str()),
+              Eq(MakeInstruction(SpvOpSpecConstantOp,
+                                 {1, 2, uint32_t(GetParam().value()), 3},
+                                 GetParam().operands())));
+}
+
+#define CASE(NAME) SpvOp##NAME, #NAME
+INSTANTIATE_TEST_CASE_P(
+    TextToBinaryOpSpecConstantOp,
+    OpSpecConstantOpTestWithOneIdThenLiteralNumbers,
+    ::testing::ValuesIn(std::vector<EnumCase<SpvOp>>{
+        // For CompositeExtract, the universal limit permits up to 255 literal
+        // indices.  Let's only test a few.
+        {CASE(CompositeExtract), {0}},
+        {CASE(CompositeExtract), {0, 99, 42, 16, 17, 12, 19}},
+    }));
+
 // TODO(dneto): OpConstantTrue
 // TODO(dneto): OpConstantFalse
 // TODO(dneto): OpConstantComposite
@@ -479,6 +650,6 @@ INSTANTIATE_TEST_CASE_P(
 // TODO(dneto): OpSpecConstantTrue
 // TODO(dneto): OpSpecConstantFalse
 // TODO(dneto): OpSpecConstantComposite
-// TODO(dneto): OpSpecConstantOp
+// TODO(dneto): Negative tests for OpSpecConstantOp
 
 }  // anonymous namespace