spirv-opt: Add support to prevent functions from being inlined if they have DontInline flag (#3858)

This commit add support for optimizer to not inline functions with DontInline control flag, so that the [noinline] attribute in HLSL will be useful in DXC SPIR-V generation.

This is part of work of github.com/microsoft/DirectXShaderCompiler/issues/3158
diff --git a/source/opt/function.h b/source/opt/function.h
index b7c17a6..4b20dcb 100644
--- a/source/opt/function.h
+++ b/source/opt/function.h
@@ -94,6 +94,9 @@
   // Returns function's return type id
   inline uint32_t type_id() const { return def_inst_->type_id(); }
 
+  // Returns the function's control mask
+  inline uint32_t control_mask() const { return def_inst_->GetSingleWordInOperand(0); }
+
   // Returns the entry basic block for this function.
   const std::unique_ptr<BasicBlock>& entry() const { return blocks_.front(); }
 
diff --git a/source/opt/inline_pass.cpp b/source/opt/inline_pass.cpp
index 6021a7c..eaf29aa 100644
--- a/source/opt/inline_pass.cpp
+++ b/source/opt/inline_pass.cpp
@@ -727,6 +727,12 @@
 bool InlinePass::IsInlinableFunction(Function* func) {
   // We can only inline a function if it has blocks.
   if (func->cbegin() == func->cend()) return false;
+
+  // Do not inline functions with DontInline flag.
+  if (func->control_mask() & SpvFunctionControlDontInlineMask) {
+    return false;
+  }
+
   // Do not inline functions with returns in loops. Currently early return
   // functions are inlined by wrapping them in a one trip loop and implementing
   // the returns as a branch to the loop's merge block. However, this can only
diff --git a/test/opt/inline_test.cpp b/test/opt/inline_test.cpp
index 951721b..c0ca6da 100644
--- a/test/opt/inline_test.cpp
+++ b/test/opt/inline_test.cpp
@@ -2405,6 +2405,38 @@
   SinglePassRunAndCheck<InlineExhaustivePass>(test, test, false, true);
 }
 
+TEST_F(InlineTest, DontInlineFuncWithDontInline) {
+  // Check that the function with DontInline flag is not inlined.
+  const std::string text = R"(
+; CHECK: %foo = OpFunction %int DontInline
+; CHECK: OpReturnValue %int_0
+
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource HLSL 600
+OpName %main "main"
+OpName %foo "foo"
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%7 = OpTypeFunction %int
+%main = OpFunction %void None %6
+%8 = OpLabel
+%9 = OpFunctionCall %int %foo
+OpReturn
+OpFunctionEnd
+%foo = OpFunction %int DontInline %7
+%10 = OpLabel
+OpReturnValue %int_0
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<InlineExhaustivePass>(text, true);
+}
+
 TEST_F(InlineTest, InlineFuncWithOpKillNotInContinue) {
   const std::string before =
       R"(OpCapability Shader