Adds SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER.
// 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
#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.
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 {
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 {
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 '|',
// 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) {
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:
// 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:
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) &&
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";
#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"
}
pInst->extInstType = ext_inst_type;
}
-
} break;
case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: {
// 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:
<< "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;
}
"%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
// TODO(dneto): OpSpecConstantTrue
// TODO(dneto): OpSpecConstantFalse
// TODO(dneto): OpSpecConstantComposite
-// TODO(dneto): OpSpecConstantOp
+// TODO(dneto): Negative tests for OpSpecConstantOp
} // anonymous namespace