From e53f3a3fad757c870e3f39bd608b718164c213cd Mon Sep 17 00:00:00 2001
From: Yong He <yonghe@outlook.com>
Date: Wed, 26 Feb 2025 12:43:12 -0800
Subject: [PATCH 1/2] Map `SV_InstanceID` to `gl_InstanceIndex-gl_BaseInstance`

---
 source/slang/glsl.meta.slang                  | 22 ++++-
 source/slang/slang-artifact-output-util.cpp   |  2 +
 source/slang/slang-compiler.cpp               | 22 +++++
 source/slang/slang-compiler.h                 | 20 -----
 source/slang/slang-emit-c-like.cpp            | 19 +++++
 source/slang/slang-emit-c-like.h              |  2 +-
 source/slang/slang-emit-glsl.cpp              |  6 ++
 source/slang/slang-emit-spirv.cpp             | 16 +++-
 source/slang/slang-ir-glsl-legalize.cpp       | 80 ++++++++++++++++++-
 source/slang/slang-ir-inst-defs.h             |  3 +
 source/slang/slang-ir-insts.h                 | 32 ++++++++
 source/slang/slang-ir-spirv-legalize.cpp      | 52 +++++++-----
 tests/bugs/gh-3087-multi-entry-point.slang    | 18 +++--
 .../system-values-draw-parameters.slang       |  2 +-
 tests/spirv/sv_instance.slang                 | 14 ++++
 15 files changed, 258 insertions(+), 52 deletions(-)
 create mode 100644 tests/spirv/sv_instance.slang

diff --git a/source/slang/glsl.meta.slang b/source/slang/glsl.meta.slang
index 6f0ca1bf34..eed6cc6908 100644
--- a/source/slang/glsl.meta.slang
+++ b/source/slang/glsl.meta.slang
@@ -136,7 +136,27 @@ public property uint3 gl_WorkGroupSize
 // TODO: define overload for tessellation control stage.
 public in int gl_InvocationID : SV_GSInstanceID;
 
-public in int gl_InstanceIndex : SV_InstanceID;
+internal in int __sv_InstanceIndex : SV_InstanceID;
+
+// SPIRV InstanceIndex builtin for vertex shader
+public property int gl_InstanceIndex
+{
+    [require(vertex)]
+    get
+    {
+        __target_switch
+        {
+        default:
+            return __sv_InstanceIndex;
+        case glsl:
+            __intrinsic_asm "gl_InstanceIndex";
+        case spirv:
+            return spirv_asm {
+                result:$$int = OpLoad builtin(InstanceIndex:int);
+            };
+        }
+    }
+}
 public in bool gl_FrontFacing : SV_IsFrontFace;
 
 // TODO: define overload for geometry stage.
diff --git a/source/slang/slang-artifact-output-util.cpp b/source/slang/slang-artifact-output-util.cpp
index 2d9dae1cf0..0e297955f5 100644
--- a/source/slang/slang-artifact-output-util.cpp
+++ b/source/slang/slang-artifact-output-util.cpp
@@ -6,6 +6,8 @@
 #include "../core/slang-string-util.h"
 #include "../core/slang-type-text-util.h"
 
+#include <chrono>
+
 // Artifact
 #include "../compiler-core/slang-artifact-desc-util.h"
 #include "../compiler-core/slang-artifact-util.h"
diff --git a/source/slang/slang-compiler.cpp b/source/slang/slang-compiler.cpp
index 04ebb753c1..58cc55e713 100644
--- a/source/slang/slang-compiler.cpp
+++ b/source/slang/slang-compiler.cpp
@@ -16,6 +16,8 @@
 #include "slang-check-impl.h"
 #include "slang-check.h"
 
+#include <chrono>
+
 // Artifact
 #include "../compiler-core/slang-artifact-associated.h"
 #include "../compiler-core/slang-artifact-container-util.h"
@@ -1835,6 +1837,26 @@ SlangResult CodeGenContext::_emitEntryPoints(ComPtr<IArtifact>& outArtifact)
     return SLANG_FAIL;
 }
 
+// Helper class for recording compile time.
+struct CompileTimerRAII
+{
+    std::chrono::high_resolution_clock::time_point startTime;
+    Session* session;
+    CompileTimerRAII(Session* inSession)
+    {
+        startTime = std::chrono::high_resolution_clock::now();
+        session = inSession;
+    }
+    ~CompileTimerRAII()
+    {
+        double elapsedTime = std::chrono::duration_cast<std::chrono::microseconds>(
+                                 std::chrono::high_resolution_clock::now() - startTime)
+                                 .count() /
+                             1e6;
+        session->addTotalCompileTime(elapsedTime);
+    }
+};
+
 // Do emit logic for a zero or more entry points
 SlangResult CodeGenContext::emitEntryPoints(ComPtr<IArtifact>& outArtifact)
 {
diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h
index 056c1fc835..db7f018ec6 100644
--- a/source/slang/slang-compiler.h
+++ b/source/slang/slang-compiler.h
@@ -3755,26 +3755,6 @@ SLANG_FORCE_INLINE SlangSourceLanguage asExternal(SourceLanguage sourceLanguage)
     return (SlangSourceLanguage)sourceLanguage;
 }
 
-// Helper class for recording compile time.
-struct CompileTimerRAII
-{
-    std::chrono::high_resolution_clock::time_point startTime;
-    Session* session;
-    CompileTimerRAII(Session* inSession)
-    {
-        startTime = std::chrono::high_resolution_clock::now();
-        session = inSession;
-    }
-    ~CompileTimerRAII()
-    {
-        double elapsedTime = std::chrono::duration_cast<std::chrono::microseconds>(
-                                 std::chrono::high_resolution_clock::now() - startTime)
-                                 .count() /
-                             1e6;
-        session->addTotalCompileTime(elapsedTime);
-    }
-};
-
 // helpers for error/warning reporting
 enum class DiagnosticCategory
 {
diff --git a/source/slang/slang-emit-c-like.cpp b/source/slang/slang-emit-c-like.cpp
index db2c0150f7..946e9c429f 100644
--- a/source/slang/slang-emit-c-like.cpp
+++ b/source/slang/slang-emit-c-like.cpp
@@ -345,6 +345,20 @@ IRNumThreadsDecoration* CLikeSourceEmitter::getComputeThreadGroupSize(
     return decor;
 }
 
+String CLikeSourceEmitter::getTargetBuiltinVarName(IRInst* inst, IRTargetBuiltinVarName builtinName)
+{
+    switch (builtinName)
+    {
+    case IRTargetBuiltinVarName::SpvInstanceIndex:
+        return "gl_InstanceIndex";
+    case IRTargetBuiltinVarName::SpvBaseInstance:
+        return "gl_BaseInstance";
+    }
+    if (auto linkage = inst->findDecoration<IRLinkageDecoration>())
+        return linkage->getMangledName();
+    return generateName(inst);
+}
+
 List<IRWitnessTableEntry*> CLikeSourceEmitter::getSortedWitnessTableEntries(
     IRWitnessTable* witnessTable)
 {
@@ -1208,6 +1222,11 @@ String CLikeSourceEmitter::generateName(IRInst* inst)
         return externCppDecoration->getName();
     }
 
+    if (auto builtinTargetVarDecoration = inst->findDecoration<IRTargetBuiltinVarDecoration>())
+    {
+        return getTargetBuiltinVarName(inst, builtinTargetVarDecoration->getBuiltinVarName());
+    }
+
     // If we have a name hint on the instruction, then we will try to use that
     // to provide the basis for the actual name in the output code.
     if (auto nameHintDecoration = inst->findDecoration<IRNameHintDecoration>())
diff --git a/source/slang/slang-emit-c-like.h b/source/slang/slang-emit-c-like.h
index e83b6e5861..6fe7f5d34d 100644
--- a/source/slang/slang-emit-c-like.h
+++ b/source/slang/slang-emit-c-like.h
@@ -243,7 +243,6 @@ class CLikeSourceEmitter : public SourceEmitterBase
     Linkage* getLinkage() { return m_codeGenContext->getLinkage(); }
     ComponentType* getProgram() { return m_codeGenContext->getProgram(); }
     TargetProgram* getTargetProgram() { return m_codeGenContext->getTargetProgram(); }
-
     //
     // Types
     //
@@ -519,6 +518,7 @@ class CLikeSourceEmitter : public SourceEmitterBase
 protected:
     virtual void emitGlobalParamDefaultVal(IRGlobalParam* inst) { SLANG_UNUSED(inst); }
     virtual void emitPostDeclarationAttributesForType(IRInst* type) { SLANG_UNUSED(type); }
+    virtual String getTargetBuiltinVarName(IRInst* inst, IRTargetBuiltinVarName builtinName);
     virtual bool doesTargetSupportPtrTypes() { return false; }
     virtual bool isResourceTypeBindless(IRType* type)
     {
diff --git a/source/slang/slang-emit-glsl.cpp b/source/slang/slang-emit-glsl.cpp
index 25dab3fb35..776c539b48 100644
--- a/source/slang/slang-emit-glsl.cpp
+++ b/source/slang/slang-emit-glsl.cpp
@@ -1791,6 +1791,12 @@ bool GLSLSourceEmitter::tryEmitGlobalParamImpl(IRGlobalParam* varDecl, IRType* v
         }
     }
 
+    if (varDecl->findDecoration<IRTargetBuiltinVarDecoration>())
+    {
+        // By default, we don't need to emit a definition for target builtin variables.
+        return true;
+    }
+
     // Do the default thing
     return false;
 }
diff --git a/source/slang/slang-emit-spirv.cpp b/source/slang/slang-emit-spirv.cpp
index ef015df7f9..f210b97686 100644
--- a/source/slang/slang-emit-spirv.cpp
+++ b/source/slang/slang-emit-spirv.cpp
@@ -4344,8 +4344,11 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex
                                                 continue;
                                         }
                                     }
-                                    paramsSet.add(spvGlobalInst);
                                     referencedBuiltinIRVars.add(globalInst);
+                                    // Don't add duplicate vars to the interface list.
+                                    bool paramAdded = paramsSet.add(spvGlobalInst);
+                                    if (!paramAdded)
+                                        continue;
 
                                     // Don't add a global param to the interface if it is a
                                     // specialization constant.
@@ -5215,6 +5218,17 @@ struct SPIRVEmitContext : public SourceEmitterBase, public SPIRVEmitSharedContex
     {
         IRBuilder builder(m_irModule);
         builder.setInsertBefore(inst);
+        if (auto builtinVarDecor = inst->findDecoration<IRTargetBuiltinVarDecoration>())
+        {
+            switch (builtinVarDecor->getBuiltinVarName())
+            {
+            case IRTargetBuiltinVarName::SpvInstanceIndex:
+                return getBuiltinGlobalVar(inst->getFullType(), SpvBuiltInInstanceIndex, inst);
+            case IRTargetBuiltinVarName::SpvBaseInstance:
+                requireSPIRVCapability(SpvCapabilityDrawParameters);
+                return getBuiltinGlobalVar(inst->getFullType(), SpvBuiltInBaseInstance, inst);
+            }
+        }
         if (auto layout = getVarLayout(inst))
         {
             if (auto systemValueAttr = layout->findAttr<IRSystemValueSemanticAttr>())
diff --git a/source/slang/slang-ir-glsl-legalize.cpp b/source/slang/slang-ir-glsl-legalize.cpp
index 11d5399cf8..1123e1f2ad 100644
--- a/source/slang/slang-ir-glsl-legalize.cpp
+++ b/source/slang/slang-ir-glsl-legalize.cpp
@@ -236,6 +236,9 @@ struct GLSLSystemValueInfo
 
     // The kind of the system value that requires special treatment.
     GLSLSystemValueKind kind = GLSLSystemValueKind::General;
+
+    // The target builtin name.
+    IRTargetBuiltinVarName targetVarName = IRTargetBuiltinVarName::Unknown;
 };
 
 static void leafAddressesImpl(List<IRInst*>& ret, const ScalarizedVal& v)
@@ -283,6 +286,7 @@ struct GLSLLegalizationContext
     DiagnosticSink* sink;
     Stage stage;
     IRFunc* entryPointFunc;
+    Dictionary<IRTargetBuiltinVarName, IRInst*> builtinVarMap;
 
     /// This dictionary stores all bindings of 'VaryingIn/VaryingOut'. We assume 'space' is 0.
     Dictionary<LayoutResourceKind, UIntSet> usedBindingIndex;
@@ -414,7 +418,7 @@ GLSLSystemValueInfo* getGLSLSystemValueInfo(
     char const* outerArrayName = nullptr;
     int arrayIndex = -1;
     GLSLSystemValueKind systemValueKind = GLSLSystemValueKind::General;
-
+    IRTargetBuiltinVarName targetVarName = IRTargetBuiltinVarName::Unknown;
     auto semanticInst = varLayout->findSystemValueSemanticAttr();
     if (!semanticInst)
         return nullptr;
@@ -621,6 +625,9 @@ GLSLSystemValueInfo* getGLSLSystemValueInfo(
 
         requiredType = builder->getBasicType(BaseType::Int);
         name = "gl_InstanceIndex";
+        targetVarName = IRTargetBuiltinVarName::HlslInstanceID;
+        context->requireSPIRVVersion(SemanticVersion(1, 3));
+        context->requireGLSLExtension(toSlice("GL_ARB_shader_draw_parameters"));
     }
     else if (semanticName == "sv_isfrontface")
     {
@@ -869,6 +876,7 @@ GLSLSystemValueInfo* getGLSLSystemValueInfo(
         name = "gl_BaseInstance";
     }
 
+    inStorage->targetVarName = targetVarName;
     if (name)
     {
         inStorage->name = name;
@@ -976,6 +984,12 @@ void createVarLayoutForLegalizedGlobalParam(
         default:
             break;
         }
+
+        if (systemValueInfo->targetVarName != IRTargetBuiltinVarName::Unknown)
+        {
+            builder->addTargetBuiltinVarDecoration(globalParam, systemValueInfo->targetVarName);
+            context->builtinVarMap[systemValueInfo->targetVarName] = globalParam;
+        }
     }
 }
 
@@ -1261,8 +1275,8 @@ ScalarizedVal createSimpleGLSLGlobalVarying(
         auto systemSemantic = inVarLayout->findAttr<IRSystemValueSemanticAttr>();
         // Validate the system value, convert to a regular parameter if this is not a valid system
         // value for a given target.
-        if (systemSemantic && isSPIRV(codeGenContext->getTargetFormat()) &&
-            systemSemantic->getName().caseInsensitiveEquals(UnownedStringSlice("sv_instanceid")) &&
+        if (systemSemantic && systemValueInfo && isSPIRV(codeGenContext->getTargetFormat()) &&
+            systemValueInfo->targetVarName == IRTargetBuiltinVarName::HlslInstanceID &&
             ((stage == Stage::Fragment) ||
              (stage == Stage::Vertex &&
               inVarLayout->usesResourceKind(LayoutResourceKind::VaryingOutput))))
@@ -1287,6 +1301,7 @@ ScalarizedVal createSimpleGLSLGlobalVarying(
             newVarLayout->sourceLoc = inVarLayout->sourceLoc;
 
             inVarLayout->replaceUsesWith(newVarLayout);
+            systemValueInfo->targetVarName = IRTargetBuiltinVarName::Unknown;
         }
     }
 
@@ -3746,6 +3761,60 @@ ScalarizedVal legalizeEntryPointReturnValueForGLSL(
     return result;
 }
 
+void legalizeTargetBuiltinVar(GLSLLegalizationContext& context)
+{
+    List<KeyValuePair<IRTargetBuiltinVarName, IRInst*>> workItems;
+    for (auto [builtinVarName, varInst] : context.builtinVarMap)
+    {
+        if (builtinVarName == IRTargetBuiltinVarName::HlslInstanceID)
+        {
+            workItems.add(KeyValuePair(builtinVarName, varInst));
+        }
+    }
+
+    auto getOrCreateBuiltinVar = [&](IRTargetBuiltinVarName name, IRType* type)
+    {
+        if (auto var = context.builtinVarMap.tryGetValue(name))
+            return *var;
+        IRBuilder builder(context.entryPointFunc);
+        builder.setInsertBefore(context.entryPointFunc);
+        IRInst* var = builder.createGlobalParam(type);
+        builder.addTargetBuiltinVarDecoration(var, name);
+        return var;
+    };
+    for (auto& kv : workItems)
+    {
+        auto builtinVarName = kv.key;
+        auto varInst = kv.value;
+
+        // Repalce SV_InstanceID with gl_InstanceIndex - gl_BaseInstance.
+        if (builtinVarName == IRTargetBuiltinVarName::HlslInstanceID)
+        {
+            auto instanceIndex = getOrCreateBuiltinVar(
+                IRTargetBuiltinVarName::SpvInstanceIndex,
+                varInst->getDataType());
+            auto baseInstance = getOrCreateBuiltinVar(
+                IRTargetBuiltinVarName::SpvBaseInstance,
+                varInst->getDataType());
+            traverseUses(
+                varInst,
+                [&](IRUse* use)
+                {
+                    auto user = use->getUser();
+                    if (user->getOp() == kIROp_Load)
+                    {
+                        IRBuilder builder(use->getUser());
+                        builder.setInsertBefore(use->getUser());
+                        auto sub = builder.emitSub(
+                            tryGetPointedToType(&builder, varInst->getDataType()),
+                            builder.emitLoad(instanceIndex),
+                            builder.emitLoad(baseInstance));
+                        user->replaceUsesWith(sub);
+                    }
+                });
+        }
+    }
+}
 
 void legalizeEntryPointForGLSL(
     Session* session,
@@ -3937,6 +4006,11 @@ void legalizeEntryPointForGLSL(
             value.globalParam->setFullType(sizedArrayType);
         }
     }
+
+    // Some system value vars can't be mapped 1:1 to a GLSL/Vulkan builtin,
+    // for example, SV_InstanceID should map to gl_InstanceIndex - gl_BaseInstance,
+    // we will replace these builtins with additional compute logic here.
+    legalizeTargetBuiltinVar(context);
 }
 
 void decorateModuleWithSPIRVVersion(IRModule* module, SemanticVersion spirvVersion)
diff --git a/source/slang/slang-ir-inst-defs.h b/source/slang/slang-ir-inst-defs.h
index 9ffaeeeb9d..3e2872cb7a 100644
--- a/source/slang/slang-ir-inst-defs.h
+++ b/source/slang/slang-ir-inst-defs.h
@@ -926,6 +926,9 @@ INST_RANGE(BindingQuery, GetRegisterIndex, GetRegisterSpace)
         INST(ExportDecoration, export, 1, 0)
     INST_RANGE(LinkageDecoration, ImportDecoration, ExportDecoration)
 
+        /// Mark a global variable as a target builtin variable.
+    INST(TargetBuiltinVarDecoration, TargetBuiltinVar, 1, 0)
+
         /// Marks an inst as coming from an `extern` symbol defined in the user code.
     INST(UserExternDecoration, UserExtern, 0, 0)
 
diff --git a/source/slang/slang-ir-insts.h b/source/slang/slang-ir-insts.h
index 7c975cfcdf..4efb7d6715 100644
--- a/source/slang/slang-ir-insts.h
+++ b/source/slang/slang-ir-insts.h
@@ -193,6 +193,14 @@ enum class IRInterpolationMode
     PerVertex,
 };
 
+enum class IRTargetBuiltinVarName
+{
+    Unknown,
+    HlslInstanceID,
+    SpvInstanceIndex,
+    SpvBaseInstance,
+};
+
 struct IRInterpolationModeDecoration : IRDecoration
 {
     enum
@@ -705,6 +713,18 @@ struct IRLinkageDecoration : IRDecoration
     UnownedStringSlice getMangledName() { return getMangledNameOperand()->getStringSlice(); }
 };
 
+// Mark a global variable as a target buitlin variable.
+struct IRTargetBuiltinVarDecoration : IRDecoration
+{
+    IR_LEAF_ISA(TargetBuiltinVarDecoration)
+
+    IRIntLit* getBuiltinVarOperand() { return cast<IRIntLit>(getOperand(0)); }
+    IRTargetBuiltinVarName getBuiltinVarName()
+    {
+        return IRTargetBuiltinVarName(getBuiltinVarOperand()->getValue());
+    }
+};
+
 struct IRUserExternDecoration : IRDecoration
 {
     enum
@@ -4658,6 +4678,18 @@ struct IRBuilder
     //    void addLayoutDecoration(IRInst* value, Layout* layout);
     IRLayoutDecoration* addLayoutDecoration(IRInst* value, IRLayout* layout);
 
+    IRDecoration* addTargetBuiltinVarDecoration(
+        IRInst* value,
+        IRTargetBuiltinVarName builtinVarName)
+    {
+        IRInst* operands[] = {getIntValue((IRIntegerValue)builtinVarName)};
+        return addDecoration(
+            value,
+            kIROp_TargetBuiltinVarDecoration,
+            operands,
+            SLANG_COUNT_OF(operands));
+    }
+
     //    IRLayout* getLayout(Layout* astLayout);
 
     IRTypeSizeAttr* getTypeSizeAttr(LayoutResourceKind kind, LayoutSize size);
diff --git a/source/slang/slang-ir-spirv-legalize.cpp b/source/slang/slang-ir-spirv-legalize.cpp
index c672180b70..b19af364e0 100644
--- a/source/slang/slang-ir-spirv-legalize.cpp
+++ b/source/slang/slang-ir-spirv-legalize.cpp
@@ -422,20 +422,10 @@ struct SPIRVLegalizationContext : public SourceEmitterBase
             }
 
             AddressSpace addressSpace = AddressSpace::ThreadLocal;
-            // Figure out storage class based on var layout.
-            if (auto layout = getVarLayout(inst))
-            {
-                auto cls = getGlobalParamAddressSpace(layout);
-                if (cls != AddressSpace::Generic)
-                    addressSpace = cls;
-                else if (auto systemValueAttr = layout->findAttr<IRSystemValueSemanticAttr>())
-                {
-                    String semanticName = systemValueAttr->getName();
-                    semanticName = semanticName.toLower();
-                    if (semanticName == "sv_pointsize")
-                        addressSpace = AddressSpace::BuiltinInput;
-                }
-            }
+            // Figure out storage class based on builtin info or var layout.
+            auto cls = getGlobalParamAddressSpace(inst);
+            if (cls != AddressSpace::Generic)
+                addressSpace = cls;
 
             // Don't do any processing for specialization constants.
             if (addressSpace == AddressSpace::SpecializationConstant)
@@ -635,8 +625,22 @@ struct SPIRVLegalizationContext : public SourceEmitterBase
         return addressSpace;
     }
 
-    AddressSpace getGlobalParamAddressSpace(IRVarLayout* varLayout)
+    AddressSpace getGlobalParamAddressSpace(IRInst* varInst)
     {
+        if (auto builtinDecor = varInst->findDecoration<IRTargetBuiltinVarDecoration>())
+        {
+            switch (builtinDecor->getBuiltinVarName())
+            {
+            case IRTargetBuiltinVarName::SpvInstanceIndex:
+            case IRTargetBuiltinVarName::SpvBaseInstance:
+                return AddressSpace::BuiltinInput;
+            }
+        }
+
+        auto varLayout = getVarLayout(varInst);
+        if (!varLayout)
+            return AddressSpace::Generic;
+
         auto typeLayout = varLayout->getTypeLayout()->unwrapArray();
         if (auto parameterGroupTypeLayout = as<IRParameterGroupTypeLayout>(typeLayout))
         {
@@ -663,15 +667,25 @@ struct SPIRVLegalizationContext : public SourceEmitterBase
                                      "resolve a storage class address space.");
             }
         }
+        auto systemValueAttr = varLayout->findSystemValueSemanticAttr();
+
+        if (systemValueAttr)
+        {
+            // TODO: is this needed?
+            String semanticName = systemValueAttr->getName();
+            semanticName = semanticName.toLower();
+            if (semanticName == "sv_pointsize")
+                result = AddressSpace::BuiltinInput;
+        }
 
         switch (result)
         {
         case AddressSpace::Input:
-            if (varLayout->findSystemValueSemanticAttr())
+            if (systemValueAttr)
                 result = AddressSpace::BuiltinInput;
             break;
         case AddressSpace::Output:
-            if (varLayout->findSystemValueSemanticAttr())
+            if (systemValueAttr)
                 result = AddressSpace::BuiltinOutput;
             break;
         }
@@ -781,9 +795,9 @@ struct SPIRVLegalizationContext : public SourceEmitterBase
         {
             addressSpace = AddressSpace::GroupShared;
         }
-        else if (const auto varLayout = getVarLayout(inst))
+        else
         {
-            auto cls = getGlobalParamAddressSpace(varLayout);
+            auto cls = getGlobalParamAddressSpace(inst);
             if (cls != AddressSpace::Generic)
                 addressSpace = cls;
         }
diff --git a/tests/bugs/gh-3087-multi-entry-point.slang b/tests/bugs/gh-3087-multi-entry-point.slang
index d3aa574c7c..edfda08afe 100644
--- a/tests/bugs/gh-3087-multi-entry-point.slang
+++ b/tests/bugs/gh-3087-multi-entry-point.slang
@@ -3,14 +3,20 @@
 // CHECK-DAG: OpEntryPoint Vertex
 // CHECK-DAG: OpEntryPoint Fragment
 
-// we should only have 1 'BuiltIn InstanceIndex' since the `Output` and `Input` semantic
-// for `InstanceIndex` should be converted to a non-builtin
-// CHECK-DAG: OpDecorate %gl_InstanceIndex BuiltIn InstanceIndex
-// CHECK-NOT: OpDecorate %gl_InstanceIndex BuiltIn InstanceIndex
-
 // We require 1 `Flat` for the fragment input `uint`
 // CHECK-DAG: OpDecorate %{{[1-9][0-9]*}} Flat
-// CHECK-NOT: OpDecorate %{{[1-9][0-9]*}} Flat
+
+// CHECK-DAG: OpDecorate %gl_InstanceIndex BuiltIn InstanceIndex
+
+// The vertex shader should be using the builtin InstanceIndex var.
+// CHECK: %vmain = OpFunction
+// CHECK: InstanceIndex
+// CHECK: OpFunctionEnd
+
+// The fragment shader should not be using the builtin InstanceIndex var.
+// CHECK: %pmain = OpFunction
+// CHECK-NOT: InstanceIndex
+// CHECK: OpFunctionEnd
 
 struct VIn
 {
diff --git a/tests/hlsl-intrinsic/system-values-draw-parameters.slang b/tests/hlsl-intrinsic/system-values-draw-parameters.slang
index e453669398..009efffa1f 100644
--- a/tests/hlsl-intrinsic/system-values-draw-parameters.slang
+++ b/tests/hlsl-intrinsic/system-values-draw-parameters.slang
@@ -32,7 +32,7 @@ VSOutput main(VSInput input,
     // CHECK_HLSL: SV_StartVertexLocation
     // CHECK_METAL: base_vertex
 
-    // CHECK_SPIRV: BuiltIn BaseInstance
+    // CHECK_SPIRV: BuiltIn InstanceIndex
     // CHECK_GLSL: gl_BaseInstance
     // CHECK_HLSL: SV_StartInstanceLocation
     // CHECK_METAL: base_instance
diff --git a/tests/spirv/sv_instance.slang b/tests/spirv/sv_instance.slang
new file mode 100644
index 0000000000..e71c6c7faf
--- /dev/null
+++ b/tests/spirv/sv_instance.slang
@@ -0,0 +1,14 @@
+//TEST:SIMPLE(filecheck=GLSL): -target glsl -entry vertMain -stage vertex
+//TEST:SIMPLE(filecheck=CHECK): -target spirv
+
+// CHECK-DAG: %[[REG1:[0-9a-zA-Z_]+]] = OpLoad %int %gl_BaseInstance
+// CHECK-DAG: %[[REG2:[0-9a-zA-Z_]+]] = OpLoad %int %gl_InstanceIndex
+// CHECK-DAG: OpISub %int %[[REG2]] %[[REG1]]
+
+// GLSL: gl_InstanceIndex - gl_BaseInstance
+
+[shader("vertex")]
+float4 vertMain(int i : SV_InstanceID) : SV_Position
+{
+    return i;
+}
\ No newline at end of file

From 04da85c1e69cb68021437a3ec8385686dfd7f49e Mon Sep 17 00:00:00 2001
From: Yong He <yonghe@outlook.com>
Date: Thu, 27 Feb 2025 15:38:03 -0800
Subject: [PATCH 2/2] Fix ci.

---
 .github/workflows/ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6cf72fdee1..ffc64ee35c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -176,7 +176,7 @@ jobs:
               -expected-failure-list tests/expected-failure-record-replay-tests.txt
           fi
       - name: Run Slang examples
-        if: steps.filter.outputs.should-run == 'true' && matrix.platform != 'wasm'
+        if: steps.filter.outputs.should-run == 'true' && matrix.platform != 'wasm' && matrix.full-gpu-tests
         run: |
           .github/workflows/ci-examples.sh \
            --bin-dir "$bin_dir" \