From 0f166be68d4b6624a10d6bf312679505d391ec22 Mon Sep 17 00:00:00 2001 From: David Neto Date: Wed, 11 Nov 2015 01:56:49 -0500 Subject: [PATCH] Assembler support for OpSpecConstantOp Adds SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER. --- include/libspirv/libspirv.h | 14 ++-- source/assembly_grammar.cpp | 96 +++++++++++++++++++++++ source/assembly_grammar.h | 7 ++ source/opcode.cpp | 26 ++++++- source/operand.cpp | 2 + source/text.cpp | 30 ++++++- test/TextToBinary.Constant.cpp | 173 ++++++++++++++++++++++++++++++++++++++++- 7 files changed, 334 insertions(+), 14 deletions(-) diff --git a/include/libspirv/libspirv.h b/include/libspirv/libspirv.h index b61138c..1e95115 100644 --- a/include/libspirv/libspirv.h +++ b/include/libspirv/libspirv.h @@ -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. diff --git a/source/assembly_grammar.cpp b/source/assembly_grammar.cpp index 98c8a3d..5e18f94 100644 --- a/source/assembly_grammar.cpp +++ b/source/assembly_grammar.cpp @@ -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 { diff --git a/source/assembly_grammar.h b/source/assembly_grammar.h index da1663d..636e952 100644 --- a/source/assembly_grammar.h +++ b/source/assembly_grammar.h @@ -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 '|', diff --git a/source/opcode.cpp b/source/opcode.cpp index 588e2ad..c8f6851 100644 --- a/source/opcode.cpp +++ b/source/opcode.cpp @@ -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) && diff --git a/source/operand.cpp b/source/operand.cpp index 40b9aea..a6b1ea7 100644 --- a/source/operand.cpp +++ b/source/operand.cpp @@ -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"; diff --git a/source/text.cpp b/source/text.cpp index 8d42acd..b437ff0 100644 --- a/source/text.cpp +++ b/source/text.cpp @@ -38,12 +38,12 @@ #include #include +#include #include "assembly_grammar.h" #include "binary.h" #include "diagnostic.h" #include "ext_inst.h" #include "instruction.h" -#include #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; } diff --git a/test/TextToBinary.Constant.cpp b/test/TextToBinary.Constant.cpp index 63f6e05..ba76832 100644 --- a/test/TextToBinary.Constant.cpp +++ b/test/TextToBinary.Constant.cpp @@ -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>>; + +// 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>{ + // 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>>; + +// 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>{ + // 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>>; + +// 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>{ + // 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 -- 2.7.4