// Copyright (c) 2015-2016 The Khronos Group Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Source code for logical layout validation as described in section 2.4

#include <cassert>

#include "DebugInfo.h"
#include "NonSemanticShaderDebugInfo100.h"
#include "OpenCLDebugInfo100.h"
#include "source/diagnostic.h"
#include "source/opcode.h"
#include "source/operand.h"
#include "source/val/function.h"
#include "source/val/instruction.h"
#include "source/val/validate.h"
#include "source/val/validation_state.h"

namespace spvtools {
namespace val {
namespace {

// Module scoped instructions are processed by determining if the opcode
// is part of the current layout section. If it is not then the next sections is
// checked.
spv_result_t ModuleScopedInstructions(ValidationState_t& _,
                                      const Instruction* inst, spv::Op opcode) {
  switch (opcode) {
    case spv::Op::OpExtInst:
      if (spvExtInstIsDebugInfo(inst->ext_inst_type())) {
        const uint32_t ext_inst_index = inst->word(4);
        bool local_debug_info = false;
        if (inst->ext_inst_type() == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100) {
          const OpenCLDebugInfo100Instructions ext_inst_key =
              OpenCLDebugInfo100Instructions(ext_inst_index);
          if (ext_inst_key == OpenCLDebugInfo100DebugScope ||
              ext_inst_key == OpenCLDebugInfo100DebugNoScope ||
              ext_inst_key == OpenCLDebugInfo100DebugDeclare ||
              ext_inst_key == OpenCLDebugInfo100DebugValue) {
            local_debug_info = true;
          }
        } else if (inst->ext_inst_type() ==
                   SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) {
          const NonSemanticShaderDebugInfo100Instructions ext_inst_key =
              NonSemanticShaderDebugInfo100Instructions(ext_inst_index);
          if (ext_inst_key == NonSemanticShaderDebugInfo100DebugScope ||
              ext_inst_key == NonSemanticShaderDebugInfo100DebugNoScope ||
              ext_inst_key == NonSemanticShaderDebugInfo100DebugDeclare ||
              ext_inst_key == NonSemanticShaderDebugInfo100DebugValue ||
              ext_inst_key == NonSemanticShaderDebugInfo100DebugLine ||
              ext_inst_key == NonSemanticShaderDebugInfo100DebugNoLine ||
              ext_inst_key ==
                  NonSemanticShaderDebugInfo100DebugFunctionDefinition) {
            local_debug_info = true;
          }
        } else {
          const DebugInfoInstructions ext_inst_key =
              DebugInfoInstructions(ext_inst_index);
          if (ext_inst_key == DebugInfoDebugScope ||
              ext_inst_key == DebugInfoDebugNoScope ||
              ext_inst_key == DebugInfoDebugDeclare ||
              ext_inst_key == DebugInfoDebugValue) {
            local_debug_info = true;
          }
        }

        if (local_debug_info) {
          if (_.in_function_body() == false) {
            // DebugScope, DebugNoScope, DebugDeclare, DebugValue must
            // appear in a function body.
            return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
                   << "DebugScope, DebugNoScope, DebugDeclare, DebugValue "
                   << "of debug info extension must appear in a function "
                   << "body";
          }
        } else {
          // Debug info extinst opcodes other than DebugScope, DebugNoScope,
          // DebugDeclare, DebugValue must be placed between section 9 (types,
          // constants, global variables) and section 10 (function
          // declarations).
          if (_.current_layout_section() < kLayoutTypes ||
              _.current_layout_section() >= kLayoutFunctionDeclarations) {
            return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
                   << "Debug info extension instructions other than "
                   << "DebugScope, DebugNoScope, DebugDeclare, DebugValue "
                   << "must appear between section 9 (types, constants, "
                   << "global variables) and section 10 (function "
                   << "declarations)";
          }
        }
      } else if (spvExtInstIsNonSemantic(inst->ext_inst_type())) {
        // non-semantic extinst opcodes are allowed beginning in the types
        // section, but since they must name a return type they cannot be the
        // first instruction in the types section. Therefore check that we are
        // already in it.
        if (_.current_layout_section() < kLayoutTypes) {
          return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
                 << "Non-semantic OpExtInst must not appear before types "
                 << "section";
        }
      } else {
        // otherwise they must be used in a block
        if (_.current_layout_section() < kLayoutFunctionDefinitions) {
          return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
                 << spvOpcodeString(opcode) << " must appear in a block";
        }
      }
      break;
    default:
      break;
  }

  while (_.IsOpcodeInCurrentLayoutSection(opcode) == false) {
    if (_.IsOpcodeInPreviousLayoutSection(opcode)) {
      return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
             << spvOpcodeString(opcode) << " is in an invalid layout section";
    }

    _.ProgressToNextLayoutSectionOrder();

    switch (_.current_layout_section()) {
      case kLayoutMemoryModel:
        if (opcode != spv::Op::OpMemoryModel) {
          return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
                 << spvOpcodeString(opcode)
                 << " cannot appear before the memory model instruction";
        }
        break;
      case kLayoutFunctionDeclarations:
        // All module sections have been processed. Recursively call
        // ModuleLayoutPass to process the next section of the module
        return ModuleLayoutPass(_, inst);
      default:
        break;
    }
  }
  return SPV_SUCCESS;
}

// Function declaration validation is performed by making sure that the
// FunctionParameter and FunctionEnd instructions only appear inside of
// functions. It also ensures that the Function instruction does not appear
// inside of another function. This stage ends when the first label is
// encountered inside of a function.
spv_result_t FunctionScopedInstructions(ValidationState_t& _,
                                        const Instruction* inst,
                                        spv::Op opcode) {
  // Make sure we advance into the function definitions when we hit
  // non-function declaration instructions.
  if (_.current_layout_section() == kLayoutFunctionDeclarations &&
      !_.IsOpcodeInCurrentLayoutSection(opcode)) {
    _.ProgressToNextLayoutSectionOrder();

    if (_.in_function_body()) {
      if (auto error = _.current_function().RegisterSetFunctionDeclType(
              FunctionDecl::kFunctionDeclDefinition)) {
        return error;
      }
    }
  }

  if (_.IsOpcodeInCurrentLayoutSection(opcode)) {
    switch (opcode) {
      case spv::Op::OpFunction: {
        if (_.in_function_body()) {
          return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
                 << "Cannot declare a function in a function body";
        }
        auto control_mask = inst->GetOperandAs<spv::FunctionControlMask>(2);
        if (auto error =
                _.RegisterFunction(inst->id(), inst->type_id(), control_mask,
                                   inst->GetOperandAs<uint32_t>(3)))
          return error;
        if (_.current_layout_section() == kLayoutFunctionDefinitions) {
          if (auto error = _.current_function().RegisterSetFunctionDeclType(
                  FunctionDecl::kFunctionDeclDefinition))
            return error;
        }
      } break;

      case spv::Op::OpFunctionParameter:
        if (_.in_function_body() == false) {
          return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
                 << "Function parameter instructions must be in a "
                    "function body";
        }
        if (_.current_function().block_count() != 0) {
          return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
                 << "Function parameters must only appear immediately after "
                    "the function definition";
        }
        if (auto error = _.current_function().RegisterFunctionParameter(
                inst->id(), inst->type_id()))
          return error;
        break;

      case spv::Op::OpFunctionEnd:
        if (_.in_function_body() == false) {
          return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
                 << "Function end instructions must be in a function body";
        }
        if (_.in_block()) {
          return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
                 << "Function end cannot be called in blocks";
        }
        if (_.current_function().block_count() == 0 &&
            _.current_layout_section() == kLayoutFunctionDefinitions) {
          return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
                 << "Function declarations must appear before "
                    "function definitions.";
        }
        if (_.current_layout_section() == kLayoutFunctionDeclarations) {
          if (auto error = _.current_function().RegisterSetFunctionDeclType(
                  FunctionDecl::kFunctionDeclDeclaration))
            return error;
        }
        if (auto error = _.RegisterFunctionEnd()) return error;
        break;

      case spv::Op::OpLine:
      case spv::Op::OpNoLine:
        break;
      case spv::Op::OpLabel:
        // If the label is encountered then the current function is a
        // definition so set the function to a declaration and update the
        // module section
        if (_.in_function_body() == false) {
          return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
                 << "Label instructions must be in a function body";
        }
        if (_.in_block()) {
          return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
                 << "A block must end with a branch instruction.";
        }
        break;

      case spv::Op::OpExtInst:
        if (spvExtInstIsDebugInfo(inst->ext_inst_type())) {
          const uint32_t ext_inst_index = inst->word(4);
          bool local_debug_info = false;
          if (inst->ext_inst_type() == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100) {
            const OpenCLDebugInfo100Instructions ext_inst_key =
                OpenCLDebugInfo100Instructions(ext_inst_index);
            if (ext_inst_key == OpenCLDebugInfo100DebugScope ||
                ext_inst_key == OpenCLDebugInfo100DebugNoScope ||
                ext_inst_key == OpenCLDebugInfo100DebugDeclare ||
                ext_inst_key == OpenCLDebugInfo100DebugValue) {
              local_debug_info = true;
            }
          } else if (inst->ext_inst_type() ==
                     SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) {
            const NonSemanticShaderDebugInfo100Instructions ext_inst_key =
                NonSemanticShaderDebugInfo100Instructions(ext_inst_index);
            if (ext_inst_key == NonSemanticShaderDebugInfo100DebugScope ||
                ext_inst_key == NonSemanticShaderDebugInfo100DebugNoScope ||
                ext_inst_key == NonSemanticShaderDebugInfo100DebugDeclare ||
                ext_inst_key == NonSemanticShaderDebugInfo100DebugValue ||
                ext_inst_key == NonSemanticShaderDebugInfo100DebugLine ||
                ext_inst_key == NonSemanticShaderDebugInfo100DebugNoLine ||
                ext_inst_key ==
                    NonSemanticShaderDebugInfo100DebugFunctionDefinition) {
              local_debug_info = true;
            }
          } else {
            const DebugInfoInstructions ext_inst_key =
                DebugInfoInstructions(ext_inst_index);
            if (ext_inst_key == DebugInfoDebugScope ||
                ext_inst_key == DebugInfoDebugNoScope ||
                ext_inst_key == DebugInfoDebugDeclare ||
                ext_inst_key == DebugInfoDebugValue) {
              local_debug_info = true;
            }
          }

          if (local_debug_info) {
            if (_.in_function_body() == false) {
              // DebugScope, DebugNoScope, DebugDeclare, DebugValue must
              // appear in a function body.
              return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
                     << "DebugScope, DebugNoScope, DebugDeclare, DebugValue "
                     << "of debug info extension must appear in a function "
                     << "body";
            }
          } else {
            // Debug info extinst opcodes other than DebugScope, DebugNoScope,
            // DebugDeclare, DebugValue must be placed between section 9 (types,
            // constants, global variables) and section 10 (function
            // declarations).
            if (_.current_layout_section() < kLayoutTypes ||
                _.current_layout_section() >= kLayoutFunctionDeclarations) {
              return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
                     << "Debug info extension instructions other than "
                     << "DebugScope, DebugNoScope, DebugDeclare, DebugValue "
                     << "must appear between section 9 (types, constants, "
                     << "global variables) and section 10 (function "
                     << "declarations)";
            }
          }
        } else if (spvExtInstIsNonSemantic(inst->ext_inst_type())) {
          // non-semantic extinst opcodes are allowed beginning in the types
          // section, but must either be placed outside a function declaration,
          // or inside a block.
          if (_.current_layout_section() < kLayoutTypes) {
            return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
                   << "Non-semantic OpExtInst must not appear before types "
                   << "section";
          } else if (_.in_function_body() && _.in_block() == false) {
            return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
                   << "Non-semantic OpExtInst within function definition must "
                      "appear in a block";
          }
        } else {
          // otherwise they must be used in a block
          if (_.in_block() == false) {
            return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
                   << spvOpcodeString(opcode) << " must appear in a block";
          }
        }
        break;

      default:
        if (_.current_layout_section() == kLayoutFunctionDeclarations &&
            _.in_function_body()) {
          return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
                 << "A function must begin with a label";
        } else {
          if (_.in_block() == false) {
            return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
                   << spvOpcodeString(opcode) << " must appear in a block";
          }
        }
        break;
    }
  } else {
    return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
           << spvOpcodeString(opcode)
           << " cannot appear in a function declaration";
  }
  return SPV_SUCCESS;
}

}  // namespace

// TODO(umar): Check linkage capabilities for function declarations
// TODO(umar): Better error messages
// NOTE: This function does not handle CFG related validation
// Performs logical layout validation. See Section 2.4
spv_result_t ModuleLayoutPass(ValidationState_t& _, const Instruction* inst) {
  const spv::Op opcode = inst->opcode();

  switch (_.current_layout_section()) {
    case kLayoutCapabilities:
    case kLayoutExtensions:
    case kLayoutExtInstImport:
    case kLayoutMemoryModel:
    case kLayoutSamplerImageAddressMode:
    case kLayoutEntryPoint:
    case kLayoutExecutionMode:
    case kLayoutDebug1:
    case kLayoutDebug2:
    case kLayoutDebug3:
    case kLayoutAnnotations:
    case kLayoutTypes:
      if (auto error = ModuleScopedInstructions(_, inst, opcode)) return error;
      break;
    case kLayoutFunctionDeclarations:
    case kLayoutFunctionDefinitions:
      if (auto error = FunctionScopedInstructions(_, inst, opcode)) {
        return error;
      }
      break;
  }
  return SPV_SUCCESS;
}

}  // namespace val
}  // namespace spvtools
