Assembler support for OpSpecConstantOp

Adds SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER.
This commit is contained in:
David Neto 2015-11-11 01:56:49 -05:00
parent ce9cef71ac
commit 0f166be68d
7 changed files with 334 additions and 14 deletions

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

View 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 {

View 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 '|',

View 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) &&

View 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";

View File

@ -38,12 +38,12 @@
#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;
}

View 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