Home | History | Annotate | Download | only in source
      1 // Copyright (c) 2015-2016 The Khronos Group Inc.
      2 //
      3 // Permission is hereby granted, free of charge, to any person obtaining a
      4 // copy of this software and/or associated documentation files (the
      5 // "Materials"), to deal in the Materials without restriction, including
      6 // without limitation the rights to use, copy, modify, merge, publish,
      7 // distribute, sublicense, and/or sell copies of the Materials, and to
      8 // permit persons to whom the Materials are furnished to do so, subject to
      9 // the following conditions:
     10 //
     11 // The above copyright notice and this permission notice shall be included
     12 // in all copies or substantial portions of the Materials.
     13 //
     14 // MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
     15 // KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
     16 // SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
     17 //    https://www.khronos.org/registry/
     18 //
     19 // THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
     20 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
     21 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
     22 // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
     23 // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
     24 // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
     25 // MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
     26 
     27 #include "validate.h"
     28 
     29 #include <cassert>
     30 #include <cstdio>
     31 
     32 #include <algorithm>
     33 #include <functional>
     34 #include <iterator>
     35 #include <sstream>
     36 #include <string>
     37 #include <vector>
     38 
     39 #include "binary.h"
     40 #include "diagnostic.h"
     41 #include "instruction.h"
     42 #include "opcode.h"
     43 #include "operand.h"
     44 #include "spirv-tools/libspirv.h"
     45 #include "spirv_constant.h"
     46 #include "spirv_endian.h"
     47 #include "val/Construct.h"
     48 #include "val/Function.h"
     49 #include "val/ValidationState.h"
     50 
     51 using std::function;
     52 using std::ostream_iterator;
     53 using std::placeholders::_1;
     54 using std::string;
     55 using std::stringstream;
     56 using std::transform;
     57 using std::vector;
     58 
     59 using libspirv::CfgPass;
     60 using libspirv::InstructionPass;
     61 using libspirv::ModuleLayoutPass;
     62 using libspirv::SsaPass;
     63 using libspirv::ValidationState_t;
     64 
     65 spv_result_t spvValidateIDs(
     66     const spv_instruction_t* pInsts, const uint64_t count,
     67     const spv_opcode_table opcodeTable, const spv_operand_table operandTable,
     68     const spv_ext_inst_table extInstTable, const ValidationState_t& state,
     69     spv_position position, spv_diagnostic* pDiagnostic) {
     70   auto undefd = state.usedefs().FindUsesWithoutDefs();
     71   for (auto id : undefd) {
     72     DIAGNOSTIC << "Undefined ID: " << id;
     73   }
     74   position->index = SPV_INDEX_INSTRUCTION;
     75   spvCheckReturn(spvValidateInstructionIDs(pInsts, count, opcodeTable,
     76                                            operandTable, extInstTable, state,
     77                                            position, pDiagnostic));
     78   return undefd.empty() ? SPV_SUCCESS : SPV_ERROR_INVALID_ID;
     79 }
     80 
     81 namespace {
     82 
     83 // TODO(umar): Validate header
     84 // TODO(umar): The Id bound should be validated also. But you can only do that
     85 // after you've seen all the instructions in the module.
     86 // TODO(umar): The binary parser validates the magic word, and the length of the
     87 // header, but nothing else.
     88 spv_result_t setHeader(void* user_data, spv_endianness_t endian, uint32_t magic,
     89                        uint32_t version, uint32_t generator, uint32_t id_bound,
     90                        uint32_t reserved) {
     91   (void)user_data;
     92   (void)endian;
     93   (void)magic;
     94   (void)version;
     95   (void)generator;
     96   (void)id_bound;
     97   (void)reserved;
     98   return SPV_SUCCESS;
     99 }
    100 
    101 // Improves diagnostic messages by collecting names of IDs
    102 // NOTE: This function returns void and is not involved in validation
    103 void DebugInstructionPass(ValidationState_t& _,
    104                           const spv_parsed_instruction_t* inst) {
    105   switch (inst->opcode) {
    106     case SpvOpName: {
    107       const uint32_t target = *(inst->words + inst->operands[0].offset);
    108       const char* str =
    109           reinterpret_cast<const char*>(inst->words + inst->operands[1].offset);
    110       _.AssignNameToId(target, str);
    111     } break;
    112     case SpvOpMemberName: {
    113       const uint32_t target = *(inst->words + inst->operands[0].offset);
    114       const char* str =
    115           reinterpret_cast<const char*>(inst->words + inst->operands[2].offset);
    116       _.AssignNameToId(target, str);
    117     } break;
    118     case SpvOpSourceContinued:
    119     case SpvOpSource:
    120     case SpvOpSourceExtension:
    121     case SpvOpString:
    122     case SpvOpLine:
    123     case SpvOpNoLine:
    124 
    125     default:
    126       break;
    127   }
    128 }
    129 
    130 // Collects use-def info about an instruction's IDs.
    131 void ProcessIds(ValidationState_t& _, const spv_parsed_instruction_t& inst) {
    132   if (inst.result_id) {
    133     _.usedefs().AddDef(
    134         {inst.result_id, inst.type_id, static_cast<SpvOp>(inst.opcode),
    135          std::vector<uint32_t>(inst.words, inst.words + inst.num_words)});
    136   }
    137   for (auto op = inst.operands; op != inst.operands + inst.num_operands; ++op) {
    138     if (spvIsIdType(op->type)) _.usedefs().AddUse(inst.words[op->offset]);
    139   }
    140 }
    141 
    142 spv_result_t ProcessInstruction(void* user_data,
    143                                 const spv_parsed_instruction_t* inst) {
    144   ValidationState_t& _ = *(reinterpret_cast<ValidationState_t*>(user_data));
    145   _.increment_instruction_count();
    146   if (static_cast<SpvOp>(inst->opcode) == SpvOpEntryPoint)
    147     _.entry_points().push_back(inst->words[2]);
    148 
    149   DebugInstructionPass(_, inst);
    150   // TODO(umar): Perform data rules pass
    151   ProcessIds(_, *inst);
    152   spvCheckReturn(ModuleLayoutPass(_, inst));
    153   spvCheckReturn(CfgPass(_, inst));
    154   spvCheckReturn(SsaPass(_, inst));
    155   spvCheckReturn(InstructionPass(_, inst));
    156 
    157   return SPV_SUCCESS;
    158 }
    159 
    160 }  // anonymous namespace
    161 
    162 spv_result_t spvValidate(const spv_const_context context,
    163                          const spv_const_binary binary,
    164                          spv_diagnostic* pDiagnostic) {
    165   if (!pDiagnostic) return SPV_ERROR_INVALID_DIAGNOSTIC;
    166 
    167   spv_endianness_t endian;
    168   spv_position_t position = {};
    169   if (spvBinaryEndianness(binary, &endian)) {
    170     DIAGNOSTIC << "Invalid SPIR-V magic number.";
    171     return SPV_ERROR_INVALID_BINARY;
    172   }
    173 
    174   spv_header_t header;
    175   if (spvBinaryHeaderGet(binary, endian, &header)) {
    176     DIAGNOSTIC << "Invalid SPIR-V header.";
    177     return SPV_ERROR_INVALID_BINARY;
    178   }
    179 
    180   // NOTE: Parse the module and perform inline validation checks. These
    181   // checks do not require the the knowledge of the whole module.
    182   ValidationState_t vstate(pDiagnostic, context);
    183   spvCheckReturn(spvBinaryParse(context, &vstate, binary->code,
    184                                 binary->wordCount, setHeader,
    185                                 ProcessInstruction, pDiagnostic));
    186 
    187   if (vstate.in_function_body())
    188     return vstate.diag(SPV_ERROR_INVALID_LAYOUT)
    189            << "Missing OpFunctionEnd at end of module.";
    190 
    191   // TODO(umar): Add validation checks which require the parsing of the entire
    192   // module. Use the information from the ProcessInstruction pass to make the
    193   // checks.
    194   if (vstate.unresolved_forward_id_count() > 0) {
    195     stringstream ss;
    196     vector<uint32_t> ids = vstate.UnresolvedForwardIds();
    197 
    198     transform(begin(ids), end(ids), ostream_iterator<string>(ss, " "),
    199               bind(&ValidationState_t::getIdName, vstate, _1));
    200 
    201     auto id_str = ss.str();
    202     return vstate.diag(SPV_ERROR_INVALID_ID)
    203            << "The following forward referenced IDs have not be defined:\n"
    204            << id_str.substr(0, id_str.size() - 1);
    205   }
    206 
    207   // CFG checks are performed after the binary has been parsed
    208   // and the CFGPass has collected information about the control flow
    209   spvCheckReturn(PerformCfgChecks(vstate));
    210 
    211   // NOTE: Copy each instruction for easier processing
    212   std::vector<spv_instruction_t> instructions;
    213   uint64_t index = SPV_INDEX_INSTRUCTION;
    214   while (index < binary->wordCount) {
    215     uint16_t wordCount;
    216     uint16_t opcode;
    217     spvOpcodeSplit(spvFixWord(binary->code[index], endian), &wordCount,
    218                    &opcode);
    219     spv_instruction_t inst;
    220     spvInstructionCopy(&binary->code[index], static_cast<SpvOp>(opcode),
    221                        wordCount, endian, &inst);
    222     instructions.push_back(inst);
    223     index += wordCount;
    224   }
    225 
    226   position.index = SPV_INDEX_INSTRUCTION;
    227   spvCheckReturn(spvValidateIDs(instructions.data(), instructions.size(),
    228                                 context->opcode_table, context->operand_table,
    229                                 context->ext_inst_table, vstate, &position,
    230                                 pDiagnostic));
    231 
    232   return SPV_SUCCESS;
    233 }
    234