// Copyright 2025 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.

#include <string>

#include "gmock/gmock.h"
#include "test/link/linker_fixture.h"

namespace spvtools {
namespace {

using FunctionVariants = spvtest::LinkerTest;

TEST_F(FunctionVariants, Dot4) {
  constexpr const char* const dot4_fp32 = R"(
    OpCapability Kernel
    OpCapability Addresses
    OpCapability Int8
    OpCapability Int64
    OpCapability Linkage
    %1 = OpExtInstImport "OpenCL.std"
    OpMemoryModel Physical64 OpenCL
    OpEntryPoint Kernel %2 "dot4" %3
    OpExecutionMode %2 ContractionOff
    OpSource OpenCL_C 102000
    OpName %2 "dot4"
    OpName %3 "__spirv_BuiltInGlobalInvocationId"
    OpName %4 "entry"
    %5 = OpTypeFloat 32
    %6 = OpTypePointer CrossWorkgroup %5
    %7 = OpTypeInt 8 0
    %8 = OpTypePointer CrossWorkgroup %7
    %9 = OpTypeVoid
    %10 = OpTypeFunction %9 %6 %6 %8
    %11 = OpTypeInt 64 0
    %12 = OpTypeInt 32 0
    %13 = OpTypeVector %11 3
    %14 = OpTypePointer Input %13
    %15 = OpTypeVector %5 4
    %16 = OpConstant %11 30
    %17 = OpConstant %11 32
    %18 = OpConstant %12 2
    %3 = OpVariable %14 Input
    %2 = OpFunction %9 None %10
    %19 = OpFunctionParameter %6
    %20 = OpFunctionParameter %6
    %21 = OpFunctionParameter %8
    %4 = OpLabel
    %22 = OpLoad %13 %3 Aligned 1
    %23 = OpCompositeExtract %11 %22 0
    %24 = OpUConvert %12 %23
    %25 = OpShiftLeftLogical %12 %24 %18
    %26 = OpSConvert %11 %25
    %27 = OpExtInst %15 %1 vloadn %26 %19 4
    %28 = OpExtInst %15 %1 vloadn %26 %20 4
    %29 = OpDot %5 %27 %28
    %30 = OpShiftLeftLogical %11 %23 %17
    %31 = OpShiftRightArithmetic %11 %30 %16
    %32 = OpInBoundsPtrAccessChain %8 %21 %31
    %33 = OpBitcast %6 %32
    OpStore %33 %29 Aligned 4
    OpReturn
    OpFunctionEnd
  )";

  constexpr const char* const dot4_fp16 = R"(
    OpCapability Kernel
    OpCapability Addresses
    OpCapability Int8
    OpCapability Int64
    OpCapability Float16
    OpCapability Linkage
    %1 = OpExtInstImport "OpenCL.std"
    OpMemoryModel Physical64 OpenCL
    OpEntryPoint Kernel %2 "dot4" %3
    OpExecutionMode %2 ContractionOff
    OpSource OpenCL_C 102000
    OpName %2 "dot4"
    OpName %3 "__spirv_BuiltInGlobalInvocationId"
    OpName %4 "entry"
    %5 = OpTypeFloat 16
    %6 = OpTypePointer CrossWorkgroup %5
    %7 = OpTypeInt 8 0
    %8 = OpTypePointer CrossWorkgroup %7
    %9 = OpTypeVoid
    %10 = OpTypeFunction %9 %6 %6 %8
    %11 = OpTypeInt 64 0
    %12 = OpTypeInt 32 0
    %13 = OpTypeVector %11 3
    %14 = OpTypePointer Input %13
    %15 = OpTypeVector %5 4
    %16 = OpConstant %11 31
    %17 = OpConstant %11 32
    %18 = OpConstant %12 2
    %3 = OpVariable %14 Input
    %2 = OpFunction %9 None %10
    %19 = OpFunctionParameter %6
    %20 = OpFunctionParameter %6
    %21 = OpFunctionParameter %8
    %4 = OpLabel
    %22 = OpLoad %13 %3 Aligned 1
    %23 = OpCompositeExtract %11 %22 0
    %24 = OpUConvert %12 %23
    %25 = OpShiftLeftLogical %12 %24 %18
    %26 = OpSConvert %11 %25
    %27 = OpExtInst %15 %1 vloadn %26 %19 4
    %28 = OpExtInst %15 %1 vloadn %26 %20 4
    %29 = OpDot %5 %27 %28
    %30 = OpShiftLeftLogical %11 %23 %17
    %31 = OpShiftRightArithmetic %11 %30 %16
    %32 = OpInBoundsPtrAccessChain %8 %21 %31
    %33 = OpBitcast %6 %32
    OpStore %33 %29 Aligned 4
    OpReturn
    OpFunctionEnd
  )";

  // clang-format off
  const std::vector<const char*> expected_lines = {
    "OpCapability FunctionVariantsINTEL",
    "OpCapability SpecConditionalINTEL",
    "OpCapability Kernel",
    "OpCapability Addresses",
    "OpCapability Int8",
    "OpCapability Int64",
    "OpCapability Linkage",
    "OpConditionalCapabilityINTEL %dot4_fp16_spv Float16",
    "OpExtension \"SPV_INTEL_function_variants\"",
    "OpConditionalEntryPointINTEL %dot4_fp32_spv Kernel %dot4 \"dot4\" %__spirv_BuiltInGlobalInvocationId",
    "OpConditionalEntryPointINTEL %dot4_fp16_spv Kernel %dot4_0 \"dot4\" %__spirv_BuiltInGlobalInvocationId_0",
    "OpModuleProcessed \"SPV_INTEL_function_variants registry version 0\"",
    "OpDecorate %ulong_30 ConditionalINTEL %dot4_fp32_spv",
    "OpDecorate %ulong_32 ConditionalINTEL %dot4_fp32_spv",
    "OpDecorate %uint_2 ConditionalINTEL %dot4_fp32_spv",
    "OpDecorate %__spirv_BuiltInGlobalInvocationId ConditionalINTEL %dot4_fp32_spv",
    "OpDecorate %dot4 ConditionalINTEL %dot4_fp32_spv",
    "OpDecorate %ulong_31 ConditionalINTEL %dot4_fp16_spv",
    "OpDecorate %ulong_32_0 ConditionalINTEL %dot4_fp16_spv",
    "OpDecorate %uint_2_0 ConditionalINTEL %dot4_fp16_spv",
    "OpDecorate %__spirv_BuiltInGlobalInvocationId_0 ConditionalINTEL %dot4_fp16_spv",
    "OpDecorate %dot4_0 ConditionalINTEL %dot4_fp16_spv",
    "OpDecorate %float ConditionalINTEL %dot4_fp32_spv",
    "OpDecorate %_ptr_CrossWorkgroup_float ConditionalINTEL %dot4_fp32_spv",
    "OpDecorate %18 ConditionalINTEL %dot4_fp32_spv",
    "OpDecorate %v4float ConditionalINTEL %dot4_fp32_spv",
    "OpDecorate %half ConditionalINTEL %dot4_fp16_spv",
    "OpDecorate %_ptr_CrossWorkgroup_half ConditionalINTEL %dot4_fp16_spv",
    "OpDecorate %22 ConditionalINTEL %dot4_fp16_spv",
    "OpDecorate %v4half ConditionalINTEL %dot4_fp16_spv",
    "%float = OpTypeFloat 32",
    "%_ptr_CrossWorkgroup_float = OpTypePointer CrossWorkgroup %float",
    "%18 = OpTypeFunction %void %_ptr_CrossWorkgroup_float %_ptr_CrossWorkgroup_float %_ptr_CrossWorkgroup_uchar",
    "%v4float = OpTypeVector %float 4",
    "%ulong_30 = OpConstant %ulong 30",
    "%ulong_32 = OpConstant %ulong 32",
    "%uint_2 = OpConstant %uint 2",
    "%__spirv_BuiltInGlobalInvocationId = OpVariable %_ptr_Input_v3ulong Input",
    "%bool = OpTypeBool",
    "%32 = OpSpecConstantArchitectureINTEL %bool 2 3 174 0",
    "%33 = OpSpecConstantTargetINTEL %bool 7",
    "%34 = OpSpecConstantTargetINTEL %bool 8",
    "%35 = OpSpecConstantCapabilitiesINTEL %bool Addresses Linkage Kernel Int64 Int8",
    "%36 = OpSpecConstantOp %bool LogicalOr %33 %34",
    "%37 = OpSpecConstantOp %bool LogicalAnd %35 %32",
    "%38 = OpSpecConstantOp %bool LogicalAnd %37 %36",
    "%half = OpTypeFloat 16",
    "%_ptr_CrossWorkgroup_half = OpTypePointer CrossWorkgroup %half",
    "%22 = OpTypeFunction %void %_ptr_CrossWorkgroup_half %_ptr_CrossWorkgroup_half %_ptr_CrossWorkgroup_uchar",
    "%v4half = OpTypeVector %half 4",
    "%ulong_31 = OpConstant %ulong 31",
    "%ulong_32_0 = OpConstant %ulong 32",
    "%uint_2_0 = OpConstant %uint 2",
    "%__spirv_BuiltInGlobalInvocationId_0 = OpVariable %_ptr_Input_v3ulong Input",
    "%39 = OpSpecConstantArchitectureINTEL %bool 2 3 174 4",
    "%40 = OpSpecConstantTargetINTEL %bool 7",
    "%41 = OpSpecConstantTargetINTEL %bool 8",
    "%42 = OpSpecConstantCapabilitiesINTEL %bool Addresses Linkage Kernel Float16 Int64 Int8",
    "%43 = OpSpecConstantOp %bool LogicalOr %40 %41",
    "%44 = OpSpecConstantOp %bool LogicalAnd %42 %39",
    "%dot4_fp16_spv = OpSpecConstantOp %bool LogicalAnd %44 %43",
    "%45 = OpSpecConstantOp %bool LogicalNot %dot4_fp16_spv",
    "%dot4_fp32_spv = OpSpecConstantOp %bool LogicalAnd %38 %45",
    "%dot4 = OpFunction %void None %18",
    "%56 = OpDot %float %54 %55",
    "%dot4_0 = OpFunction %void None %22",
    "%71 = OpDot %half %69 %70",
  };
  // clang-format on

  const std::string targets_csv =
      "module,target,features\n"
      "dot4_fp32.spv,7,\n"
      "dot4_fp32.spv,8,\n"
      "dot4_fp16.spv,7,\n"
      "dot4_fp16.spv,8,\n";
  const std::string architectures_csv =
      "module,category,family,op,architecture\n"
      "dot4_fp32.spv,2,3,174,0\n"
      "dot4_fp16.spv,2,3,174,4\n";
  const std::vector<std::string> sources = {dot4_fp32, dot4_fp16};
  const std::vector<std::string> in_files = {"dot4_fp32.spv", "dot4_fp16.spv"};

  LinkerOptions options;
  options.SetInFiles(in_files);
  options.SetFnVarTargetsCsv(targets_csv);
  options.SetFnVarArchitecturesCsv(architectures_csv);
  options.SetHasFnVarCapabilities(true);
  options.SetCreateLibrary(true);
  options.SetVerifyIds(true);

  spvtest::Binary linked_binary;
  spv_result_t res = AssembleAndLink(sources, &linked_binary, options);
  EXPECT_EQ(SPV_SUCCESS, res) << GetErrorMessage();
  EXPECT_THAT(GetErrorMessage(), std::string());
  EXPECT_TRUE(Validate(linked_binary));

  std::string linked_asm;
  res = Disassemble(linked_binary, &linked_asm);
  EXPECT_EQ(SPV_SUCCESS, res) << GetErrorMessage();
  for (const auto& expected : expected_lines) {
    EXPECT_THAT(linked_asm, testing::HasSubstr(expected));
  }
}

TEST_F(FunctionVariants, FAddAsm) {
  constexpr const char* const foo_base = R"(
    OpCapability Kernel
    OpCapability Addresses
    OpCapability Int8
    OpCapability Int64
    OpCapability Linkage
    %1 = OpExtInstImport "OpenCL.std"
    OpMemoryModel Physical64 OpenCL
    OpSource OpenCL_CPP 100000
    OpName %2 "foo"
    OpName %3 "add"
    OpName %4 "entry"
    OpName %5 "work"
    OpName %6 "call"
    OpName %7 "entry"
    OpDecorate %2 LinkageAttributes "foo" Export
    OpDecorate %5 LinkageAttributes "work" Export
    %8 = OpTypeFloat 32
    %9 = OpTypePointer Function %8
    %10 = OpTypeFunction %8 %9 %9 %9
    %11 = OpTypeInt 8 0
    %12 = OpTypePointer Function %11
    %13 = OpTypeInt 64 0
    %14 = OpConstant %13 28
    %15 = OpConstant %13 24
    %16 = OpConstant %13 20
    %17 = OpConstant %13 16
    %18 = OpConstant %13 12
    %19 = OpConstant %13 8
    %20 = OpConstant %13 4
    %2 = OpFunction %8 DontInline %10
    %21 = OpFunctionParameter %9
    %22 = OpFunctionParameter %9
    %23 = OpFunctionParameter %9
    %4 = OpLabel
    %24 = OpLoad %8 %21 Aligned 4
    %25 = OpLoad %8 %22 Aligned 4
    %3 = OpFAdd %8 %24 %25
    OpStore %23 %3 Aligned 4
    OpReturnValue %3
    OpFunctionEnd
    %5 = OpFunction %8 None %10
    %26 = OpFunctionParameter %9
    %27 = OpFunctionParameter %9
    %28 = OpFunctionParameter %9
    %7 = OpLabel
    %6 = OpFunctionCall %8 %2 %26 %27 %28
    %29 = OpFDiv %8 %6 %6
    %30 = OpFDiv %8 %29 %6
    OpReturnValue %30
    OpFunctionEnd
  )";

  constexpr const char* const foo_asm = R"(
    OpCapability Kernel
    OpCapability Addresses
    OpCapability Linkage
    OpCapability AsmINTEL
    OpExtension "SPV_INTEL_inline_assembly"
    %1 = OpExtInstImport "OpenCL.std"
    OpMemoryModel Physical64 OpenCL
    OpSource OpenCL_CPP 100000
    OpName %2 "a"
    OpName %3 "b"
    OpName %4 "c"
    OpName %5 "foo"
    OpName %6 "add"
    OpName %7 "entry"
    OpDecorate %2 FuncParamAttr NoWrite
    OpDecorate %2 FuncParamAttr NoAlias
    OpDecorate %3 FuncParamAttr NoWrite
    OpDecorate %3 FuncParamAttr NoAlias
    OpDecorate %4 FuncParamAttr NoAlias
    OpDecorate %5 LinkageAttributes "foo" Export
    OpDecorate %8 SideEffectsINTEL
    %9 = OpTypeFloat 32
    %10 = OpTypePointer Function %9
    %11 = OpTypeFunction %9 %10 %10 %10
    %12 = OpTypeVoid
    %13 = OpTypeFunction %12
    %14 = OpAsmTargetINTEL "spirv64-unknown-unknown"
    %8 = OpAsmINTEL %12 %13 %14 "nop1" ""
    %5 = OpFunction %9 None %11
    %2 = OpFunctionParameter %10
    %3 = OpFunctionParameter %10
    %4 = OpFunctionParameter %10
    %7 = OpLabel
    %15 = OpLoad %9 %2 Aligned 4
    %16 = OpLoad %9 %3 Aligned 4
    %6 = OpFSub %9 %15 %16
    OpStore %4 %6 Aligned 4
    %17 = OpAsmCallINTEL %12 %8
    OpReturnValue %6
    OpFunctionEnd
	)";

  // same as foo_asm, just with OpFMul and a different assembly string in
  // OpAsmINTEL
  constexpr const char* const foo_asm2 = R"(
    OpCapability Kernel
    OpCapability Addresses
    OpCapability Linkage
    OpCapability AsmINTEL
    OpExtension "SPV_INTEL_inline_assembly"
    %1 = OpExtInstImport "OpenCL.std"
    OpMemoryModel Physical64 OpenCL
    OpSource OpenCL_CPP 100000
    OpName %2 "a"
    OpName %3 "b"
    OpName %4 "c"
    OpName %5 "foo"
    OpName %6 "add"
    OpName %7 "entry"
    OpDecorate %2 FuncParamAttr NoWrite
    OpDecorate %2 FuncParamAttr NoAlias
    OpDecorate %3 FuncParamAttr NoWrite
    OpDecorate %3 FuncParamAttr NoAlias
    OpDecorate %4 FuncParamAttr NoAlias
    OpDecorate %5 LinkageAttributes "foo" Export
    OpDecorate %8 SideEffectsINTEL
    %9 = OpTypeFloat 32
    %10 = OpTypePointer Function %9
    %11 = OpTypeFunction %9 %10 %10 %10
    %12 = OpTypeVoid
    %13 = OpTypeFunction %12
    %14 = OpAsmTargetINTEL "spirv64-unknown-unknown"
    %8 = OpAsmINTEL %12 %13 %14 "nop2" ""
    %5 = OpFunction %9 None %11
    %2 = OpFunctionParameter %10
    %3 = OpFunctionParameter %10
    %4 = OpFunctionParameter %10
    %7 = OpLabel
    %15 = OpLoad %9 %2 Aligned 4
    %16 = OpLoad %9 %3 Aligned 4
    %6 = OpFMul %9 %15 %16
    OpStore %4 %6 Aligned 4
    %17 = OpAsmCallINTEL %12 %8
    OpReturnValue %6
    OpFunctionEnd
  )";

  // clang-format off
  const std::vector<const char*> expected_lines = {
    "OpCapability FunctionVariantsINTEL",
    "OpCapability SpecConditionalINTEL",
    "OpCapability Kernel",
    "OpCapability Addresses",
    "OpConditionalCapabilityINTEL %foo_spv Int8",
    "OpConditionalCapabilityINTEL %foo_spv Int64",
    "OpCapability Linkage",
    "OpConditionalCapabilityINTEL %2 AsmINTEL",
    "OpExtension \"SPV_INTEL_function_variants\"",
    "OpConditionalExtensionINTEL %2 \"SPV_INTEL_inline_assembly\"",
    "OpModuleProcessed \"SPV_INTEL_function_variants registry version 0\"",
    "OpDecorate %foo LinkageAttributes \"foo\" Export",
    "OpDecorate %work LinkageAttributes \"work\" Export",
    "OpDecorate %ulong_28 ConditionalINTEL %foo_spv",
    "OpDecorate %ulong_24 ConditionalINTEL %foo_spv",
    "OpDecorate %ulong_20 ConditionalINTEL %foo_spv",
    "OpDecorate %ulong_16 ConditionalINTEL %foo_spv",
    "OpDecorate %ulong_12 ConditionalINTEL %foo_spv",
    "OpDecorate %ulong_8 ConditionalINTEL %foo_spv",
    "OpDecorate %ulong_4 ConditionalINTEL %foo_spv",
    "OpDecorate %foo ConditionalINTEL %foo_spv",
    "OpDecorate %foo_0 LinkageAttributes \"foo\" Export",
    "OpDecorate %32 ConditionalINTEL %foo_asm_spv",
    "OpDecorate %31 ConditionalINTEL %foo_asm_spv",
    "OpDecorate %foo_0 ConditionalINTEL %foo_asm_spv",
    "OpDecorate %foo_1 LinkageAttributes \"foo\" Export",
    "OpDecorate %34 ConditionalINTEL %foo_asm2_spv",
    "OpDecorate %33 ConditionalINTEL %foo_asm2_spv",
    "OpDecorate %foo_1 ConditionalINTEL %foo_asm2_spv",
    "OpDecorate %call ConditionalINTEL %foo_spv",
    "OpDecorate %35 ConditionalINTEL %foo_asm_spv",
    "OpDecorate %36 ConditionalINTEL %foo_asm2_spv",
    "OpDecorate %uchar ConditionalINTEL %foo_spv",
    "OpDecorate %_ptr_Function_uchar ConditionalINTEL %foo_spv",
    "OpDecorate %ulong ConditionalINTEL %foo_spv",
    "OpDecorate %void ConditionalINTEL %2",
    "OpDecorate %41 ConditionalINTEL %2",
    "%ulong_28 = OpConstant %ulong 28",
    "%ulong_24 = OpConstant %ulong 24",
    "%ulong_20 = OpConstant %ulong 20",
    "%ulong_16 = OpConstant %ulong 16",
    "%ulong_12 = OpConstant %ulong 12",
    "%ulong_8 = OpConstant %ulong 8",
    "%ulong_4 = OpConstant %ulong 4",
    "%bool = OpTypeBool",
    "%46 = OpSpecConstantTargetINTEL %bool 4",
    "%47 = OpSpecConstantCapabilitiesINTEL %bool Addresses Linkage Kernel Int64 Int8",
    "%48 = OpSpecConstantOp %bool LogicalAnd %47 %46",
    "%31 = OpAsmINTEL %void %41 %32 \"nop1\" \"\"",
    "%49 = OpSpecConstantArchitectureINTEL %bool 1 1 170 1",
    "%50 = OpSpecConstantTargetINTEL %bool 4 9 10",
    "%51 = OpSpecConstantCapabilitiesINTEL %bool Addresses Linkage Kernel AsmINTEL",
    "%52 = OpSpecConstantOp %bool LogicalAnd %51 %49",
    "%foo_asm_spv = OpSpecConstantOp %bool LogicalAnd %52 %50",
    "%33 = OpAsmINTEL %void %41 %34 \"nop2\" \"\"",
    "%53 = OpSpecConstantArchitectureINTEL %bool 1 7 174 1",
    "%54 = OpSpecConstantArchitectureINTEL %bool 1 7 178 3",
    "%55 = OpSpecConstantArchitectureINTEL %bool 1 8 170 1",
    "%56 = OpSpecConstantArchitectureINTEL %bool 1 9 174 1",
    "%57 = OpSpecConstantArchitectureINTEL %bool 1 9 178 3",
    "%58 = OpSpecConstantTargetINTEL %bool 5 2 4 5",
    "%59 = OpSpecConstantTargetINTEL %bool 6 2 4 5",
    "%60 = OpSpecConstantCapabilitiesINTEL %bool Addresses Linkage Kernel AsmINTEL",
    "%61 = OpSpecConstantOp %bool LogicalAnd %53 %54",
    "%62 = OpSpecConstantOp %bool LogicalAnd %56 %57",
    "%63 = OpSpecConstantOp %bool LogicalOr %61 %55",
    "%64 = OpSpecConstantOp %bool LogicalOr %63 %62",
    "%65 = OpSpecConstantOp %bool LogicalOr %58 %59",
    "%66 = OpSpecConstantOp %bool LogicalAnd %60 %64",
    "%foo_asm2_spv = OpSpecConstantOp %bool LogicalAnd %66 %65",
    "%67 = OpSpecConstantOp %bool LogicalOr %foo_asm_spv %foo_asm2_spv",
    "%68 = OpSpecConstantOp %bool LogicalNot %67",
    "%foo_spv = OpSpecConstantOp %bool LogicalAnd %48 %68",
    "%2 = OpSpecConstantOp %bool LogicalOr %foo_asm_spv %foo_asm2_spv",
    "%foo = OpFunction %float DontInline %44",
    "%add = OpFAdd %float %72 %73",
    "%work = OpFunction %float None %44",
    "%call = OpFunctionCall %float %foo %74 %75 %76",
    "%35 = OpFunctionCall %float %foo_0 %74 %75 %76",
    "%36 = OpFunctionCall %float %foo_1 %74 %75 %76",
    "%77 = OpConditionalCopyObjectINTEL %float %foo_spv %call %foo_asm_spv %35 %foo_asm2_spv %36",
    "%78 = OpFDiv %float %77 %77",
    "%79 = OpFDiv %float %78 %77",
    "OpReturnValue %79",
    "%foo_0 = OpFunction %float None %44",
    "%add_0 = OpFSub %float %80 %81",
    "%82 = OpAsmCallINTEL %void %31",
    "%foo_1 = OpFunction %float None %44",
    "%add_1 = OpFMul %float %83 %84",
    "%85 = OpAsmCallINTEL %void %33",
  };
  // clang-format on

  const std::string targets_csv =
      "module,target,features\n"
      "foo.spv,04,\n"           // test leading zeros
      "foo_asm.spv,4,9/0010\n"  // test leading zeros
      "foo_asm2.spv,5,2/4/5\n"
      "foo_asm2.spv,6,2/4/5\n";
  const std::string architectures_csv =
      "module,category,family,op,architecture\n"
      "foo_asm.spv,1,1,170,1\n"
      "foo_asm2.spv,1,7,174,1\n"
      "foo_asm2.spv,1,7,178,3\n"
      "foo_asm2.spv,1,8,170,1\n"
      "foo_asm2.spv,1,9,174,1\n"
      "foo_asm2.spv,1,9,178,3\n";
  const std::vector<std::string> sources = {foo_base, foo_asm, foo_asm2};
  const std::vector<std::string> in_files = {"foo.spv", "foo_asm.spv",
                                             "foo_asm2.spv"};

  LinkerOptions options;
  options.SetInFiles(in_files);
  options.SetFnVarTargetsCsv(targets_csv);
  options.SetFnVarArchitecturesCsv(architectures_csv);
  options.SetHasFnVarCapabilities(true);
  options.SetCreateLibrary(true);
  options.SetVerifyIds(true);

  spvtest::Binary linked_binary;
  spv_result_t res = AssembleAndLink(sources, &linked_binary, options);
  EXPECT_EQ(SPV_SUCCESS, res) << GetErrorMessage();
  EXPECT_THAT(GetErrorMessage(), std::string());
  EXPECT_TRUE(Validate(linked_binary));

  std::string linked_asm;
  res = Disassemble(linked_binary, &linked_asm);
  EXPECT_EQ(SPV_SUCCESS, res) << GetErrorMessage();
  for (const auto& expected : expected_lines) {
    EXPECT_THAT(linked_asm, testing::HasSubstr(expected));
  }
}

TEST_F(FunctionVariants, InvalidNumber1) {
  const std::string targets_csv =
      "module,target,features\n"
      "foo.spv,-4,9/10\n";
  const std::string architectures_csv = "";
  const std::vector<std::string> sources = {""};
  const std::vector<std::string> in_files = {"foo.spv"};

  LinkerOptions options;
  options.SetInFiles(in_files);
  options.SetFnVarTargetsCsv(targets_csv);
  options.SetFnVarArchitecturesCsv(architectures_csv);
  options.SetHasFnVarCapabilities(true);
  options.SetCreateLibrary(true);
  options.SetVerifyIds(true);

  spvtest::Binary linked_binary;
  spv_result_t res = AssembleAndLink(sources, &linked_binary, options);
  EXPECT_EQ(SPV_ERROR_FNVAR, res) << GetErrorMessage();
  EXPECT_THAT(GetErrorMessage(), "ERROR: 0: Error converting -4 to target.");
}

TEST_F(FunctionVariants, InvalidNumber2) {
  const std::string targets_csv =
      "module,target,features\n"
      "foo.spv,4,9/-10\n";
  const std::string architectures_csv = "";
  const std::vector<std::string> sources = {""};
  const std::vector<std::string> in_files = {"foo.spv"};

  LinkerOptions options;
  options.SetInFiles(in_files);
  options.SetFnVarTargetsCsv(targets_csv);
  options.SetFnVarArchitecturesCsv(architectures_csv);
  options.SetHasFnVarCapabilities(true);
  options.SetCreateLibrary(true);
  options.SetVerifyIds(true);

  spvtest::Binary linked_binary;
  spv_result_t res = AssembleAndLink(sources, &linked_binary, options);
  EXPECT_EQ(SPV_ERROR_FNVAR, res) << GetErrorMessage();
  EXPECT_THAT(GetErrorMessage(),
              "ERROR: 0: Error converting -10 in 9/-10 to target feature.");
}

TEST_F(FunctionVariants, InvalidNumber3) {
  const std::string targets_csv =
      "module,target,features\n"
      "foo.spv,4.0,9/10\n";
  const std::string architectures_csv = "";
  const std::vector<std::string> sources = {""};
  const std::vector<std::string> in_files = {"foo.spv"};

  LinkerOptions options;
  options.SetInFiles(in_files);
  options.SetFnVarTargetsCsv(targets_csv);
  options.SetFnVarArchitecturesCsv(architectures_csv);
  options.SetHasFnVarCapabilities(true);
  options.SetCreateLibrary(true);
  options.SetVerifyIds(true);

  spvtest::Binary linked_binary;
  spv_result_t res = AssembleAndLink(sources, &linked_binary, options);
  EXPECT_EQ(SPV_ERROR_FNVAR, res) << GetErrorMessage();
  EXPECT_THAT(GetErrorMessage(), "ERROR: 0: Error converting 4.0 to target.");
}

}  // namespace
}  // namespace spvtools