Skip to content

Commit

Permalink
[Metal] Add experimental Metal support
Browse files Browse the repository at this point in the history
This adds a new `-metal` flag to DXC which can be used to generate
Metal's IR directly from DXC after compilation. There are some
limitations in this flag which are worth noting:

1) It does not support library shaders (yet)
2) It does not support disassembly (yet)
3) It is _wildly_ under tested because wtihout (2) we can't do anything
to really verify correct output (yay?)
  • Loading branch information
llvm-beanz committed Jul 18, 2024
1 parent 4a81bdf commit 6c2ba40
Show file tree
Hide file tree
Showing 13 changed files with 170 additions and 1 deletion.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ Development kits containing only the dxc.exe driver app, the dxcompiler.dll, and

As an example of community contribution, this project can also target the [SPIR-V](https://www.khronos.org/registry/spir-v/) intermediate representation. Please see the [doc](docs/SPIR-V.rst) for how HLSL features are mapped to SPIR-V, and the [wiki](https://github.com/microsoft/DirectXShaderCompiler/wiki/SPIR%E2%80%90V-CodeGen) page for how to build, use, and contribute to the SPIR-V CodeGen.

### Metal CodeGen

When built from source DXC can utilize the [Metal Shader
Converter](https://developer.apple.com/metal/shader-converter/) if it is
available during build and configuration time. This allows DXC to generate Metal
shader libraries directly using the `-metal` flag.

Note: DXC cannot currently disassemble Metal shaders so the `-Fc` flag cannot be
used in conjunction with the `-Fo` flag.

## Building Sources

See the full documentation for [Building and testing DXC](docs/BuildingAndTestingDXC.rst) for detailed instructions.
Expand Down
9 changes: 9 additions & 0 deletions cmake/config-ix.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -568,3 +568,12 @@ else()
endif()

string(REPLACE " " ";" LLVM_BINDINGS_LIST "${LLVM_BINDINGS}")

# HLSL Change Begin - Metal IR Converter
find_package(MetalIRConverter)
if (METAL_IRCONVERTER_FOUND)
set(ENABLE_METAL_CODEGEN On)
message(STATUS "Enabling Metal Support")
add_definitions(-DENABLE_METAL_CODEGEN)
endif()
# HLSL Change End - Metal IR Converter
16 changes: 16 additions & 0 deletions cmake/modules/FindMetalIRConverter.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
find_path(METAL_IRCONVERTER_INCLUDE_DIR metal_irconverter.h
HINTS /usr/local/include/metal_irconverter
DOC "Path to metal IR converter headers"
)

find_library(METAL_IRCONVERTER_LIB NAMES metalirconverter
PATH_SUFFIXES lib
)

include(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(METAL_IRCONVERTER
REQUIRED_VARS METAL_IRCONVERTER_LIB METAL_IRCONVERTER_INCLUDE_DIR)

message(STATUS "Metal IR Converter Include Dir: ${METAL_IRCONVERTER_INCLUDE_DIR}")
message(STATUS "Metal IR Converter Library: ${METAL_IRCONVERTER_LIB}")
mark_as_advanced(METAL_IRCONVERTER_LIB METAL_IRCONVERTER_INCLUDE_DIR)
2 changes: 2 additions & 0 deletions include/dxc/Support/HLSLOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ class DxcOpts {
SpirvOptions; // All SPIR-V CodeGen-related options
#endif
// SPIRV Change Ends

bool GenMetal = false; // OPT_metal
};

/// Use this class to capture, convert and handle the lifetime for the
Expand Down
3 changes: 3 additions & 0 deletions include/dxc/Support/HLSLOptions.td
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,9 @@ def disable_exception_handling : Flag<["-", "/"], "disable-exception-handling">,
def skip_serialization : Flag<["-", "/"], "skip-serialization">, Group<hlslcore_Group>, Flags<[CoreOption, HelpHidden]>,
HelpText<"Return a module interface instead of serialized output">;

def metal : Flag<["-"], "metal">, Group<spirv_Group>, Flags<[CoreOption, DriverOption]>,
HelpText<"Generate Metal code">;

// SPIRV Change Starts
def spirv : Flag<["-"], "spirv">, Group<spirv_Group>, Flags<[CoreOption, DriverOption]>,
HelpText<"Generate SPIR-V code">;
Expand Down
17 changes: 17 additions & 0 deletions lib/DxcSupport/HLSLOptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,8 @@ int ReadDxcOpts(const OptTable *optionTable, unsigned flagsToInclude,

addDiagnosticArgs(Args, OPT_W_Group, OPT_W_value_Group, opts.Warnings);

opts.GenMetal = Args.hasFlag(OPT_metal, OPT_INVALID, false);

// SPIRV Change Starts
#ifdef ENABLE_SPIRV_CODEGEN
opts.GenSPIRV = Args.hasFlag(OPT_spirv, OPT_INVALID, false);
Expand Down Expand Up @@ -1253,6 +1255,21 @@ int ReadDxcOpts(const OptTable *optionTable, unsigned flagsToInclude,
#endif // ENABLE_SPIRV_CODEGEN
// SPIRV Change Ends

#ifndef ENABLE_METAL_CODEGEN
if (opts.GenMetal) {
errors << "Metal CodeGen not available. "
"Please rebuild with Metal IR Converter installed.";
return 1;
}
#endif

if (opts.GenMetal) {
if (!opts.OutputObject.empty() && opts.AssemblyCode.empty()) {
errors << "Disassembly of Metal IR not supported (yet).";
return 1;
}
}

// Validation for DebugInfo here because spirv uses same DebugInfo opt,
// and legacy wrappers will add EmbedDebug in this case, leading to this
// failing if placed before spirv path sets DebugInfo to true.
Expand Down
5 changes: 5 additions & 0 deletions tools/clang/test/DXC/metal.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// REQUIRES: metal

// RUN: %dxc /T ps_6_0 %S/Inputs/smoke.hlsl -metal | FileCheck %s

// CHECK: define void @main()
4 changes: 4 additions & 0 deletions tools/clang/test/DXC/no_metal.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// UNSUPPORTED: metal

// RUN:not %dxc %S/Inputs/smoke.hlsl /T ps_6_0 -metal 2>&1 | FileCheck %s
// CHECK:Metal CodeGen not available
4 changes: 4 additions & 0 deletions tools/clang/test/DXC/no_metal_disassembly.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// REQUIRES: metal

// RUN:not %dxc %S/Inputs/smoke.hlsl /T ps_6_0 -metal -Fo Tmp.metal -Fc Tmp.air 2>&1 | FileCheck %s
// CHECK: Disassembly of Metal IR not supported (yet).
3 changes: 3 additions & 0 deletions tools/clang/test/lit.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,9 @@ if config.enable_backtrace == "1":
if config.spirv:
config.available_features.add("spirv")

if config.metal:
config.available_features.add("metal")

# Check supported dxil version
def get_dxil_version():
result = subprocess.run([lit.util.which('dxc', llvm_tools_dir), "--version"], stdout=subprocess.PIPE)
Expand Down
1 change: 1 addition & 0 deletions tools/clang/test/lit.site.cfg.in
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ config.enable_shared = @ENABLE_SHARED@
config.enable_backtrace = "@ENABLE_BACKTRACES@"
config.host_arch = "@HOST_ARCH@"
config.spirv = "@ENABLE_SPIRV_CODEGEN@" =="ON"
config.metal = "@ENABLE_METAL_CODEGEN@".upper() == "ON"

# Support substitution of the tools and libs dirs with user parameters. This is
# used when we can't determine the tool dir at configuration time.
Expand Down
8 changes: 8 additions & 0 deletions tools/clang/tools/dxcompiler/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,14 @@ target_link_libraries(dxcompiler PRIVATE ${LIBRARIES})
if (ENABLE_SPIRV_CODEGEN)
target_link_libraries(dxcompiler PRIVATE clangSPIRV)
endif (ENABLE_SPIRV_CODEGEN)
if (ENABLE_METAL_CODEGEN)
target_link_libraries(dxcompiler PRIVATE ${METAL_IRCONVERTER_LIB})
target_include_directories(dxcompiler PRIVATE ${METAL_IRCONVERTER_INCLUDE_DIR})

get_filename_component(METAL_IRCONVERTER_LIB_DIR ${METAL_IRCONVERTER_LIB} DIRECTORY CACHE)
set_property(TARGET dxcompiler APPEND_STRING
PROPERTY LINK_FLAGS " -Wl,-rpath,${METAL_IRCONVERTER_LIB_DIR}")
endif (ENABLE_METAL_CODEGEN)
include_directories(AFTER ${LLVM_INCLUDE_DIR}/dxc/Tracing ${DIASDK_INCLUDE_DIRS} ${HLSL_VERSION_LOCATION})

set_target_properties(dxcompiler
Expand Down
89 changes: 88 additions & 1 deletion tools/clang/tools/dxcompiler/dxcompilerobj.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@
#include "clang/Basic/Version.h"
#endif // SUPPORT_QUERY_GIT_COMMIT_INFO

#ifdef ENABLE_METAL_CODEGEN
#include "metal_irconverter.h"
#endif

#define CP_UTF16 1200

using namespace llvm;
Expand Down Expand Up @@ -828,6 +832,10 @@ class DxcCompiler : public IDxcCompiler3,
}
compiler.getLangOpts().IsHLSLLibrary = opts.IsLibraryProfile();

if (compiler.getLangOpts().IsHLSLLibrary && opts.GenMetal)
return ErrorWithString("Shader libraries unsupported in Metal (yet)",
riid, ppResult);

// Clear entry function if library target
if (compiler.getLangOpts().IsHLSLLibrary)
compiler.getLangOpts().HLSLEntryFunction =
Expand Down Expand Up @@ -1117,7 +1125,86 @@ class DxcCompiler : public IDxcCompiler3,
&pHashBlob));
IFT(pResult->SetOutputObject(DXC_OUT_SHADER_HASH, pHashBlob));
} // SUCCEEDED(valHR)
} // compileOK && !opts.CodeGenHighLevel
#ifdef ENABLE_METAL_CODEGEN
// This is a bit hacky because we don't currently have a good way to
// disassemble AIR.
if (opts.GenMetal && produceFullContainer &&
!opts.OutputObject.empty()) {
IRCompiler *MetalCompiler = IRCompilerCreate();
IRCompilerSetEntryPointName(
MetalCompiler,
compiler.getCodeGenOpts().HLSLEntryFunction.c_str());

IRObject *DXILObj = IRObjectCreateFromDXIL(
static_cast<const uint8_t *>(pOutputBlob->GetBufferPointer()),
pOutputBlob->GetBufferSize(), IRBytecodeOwnershipNone);

// Compile DXIL to Metal IR:
IRError *Error = nullptr;
IRObject *AIR = IRCompilerAllocCompileAndLink(MetalCompiler, NULL,
DXILObj, &Error);

if (!AIR) {
IRObjectDestroy(DXILObj);
IRCompilerDestroy(MetalCompiler);
IRErrorDestroy(Error);
return ErrorWithString(
"Error occurred in Metal Shader Conversion", riid, ppResult);
}

IRMetalLibBinary *MetalLib = IRMetalLibBinaryCreate();
IRShaderStage Stage = IRShaderStageInvalid;
const ShaderModel *SM = hlsl::ShaderModel::GetByName(
compiler.getLangOpts().HLSLProfile);
switch (SM->GetKind()) {
case DXIL::ShaderKind::Vertex:
Stage = IRShaderStageVertex;
break;
case DXIL::ShaderKind::Pixel:
Stage = IRShaderStageFragment;
break;
case DXIL::ShaderKind::Hull:
Stage = IRShaderStageHull;
break;
case DXIL::ShaderKind::Domain:
Stage = IRShaderStageDomain;
break;
case DXIL::ShaderKind::Mesh:
Stage = IRShaderStageMesh;
break;
case DXIL::ShaderKind::Amplification:
Stage = IRShaderStageAmplification;
break;
case DXIL::ShaderKind::Geometry:
Stage = IRShaderStageGeometry;
break;
case DXIL::ShaderKind::Compute:
Stage = IRShaderStageCompute;
break;
}
assert(Stage != IRShaderStageInvalid &&
"Library targets not supported for Metal (yet).");
IRObjectGetMetalLibBinary(AIR, Stage, MetalLib);
size_t MetalLibSize = IRMetalLibGetBytecodeSize(MetalLib);
uint8_t *MetalLibBytes = new uint8_t[MetalLibSize];
IRMetalLibGetBytecode(MetalLib, MetalLibBytes);

// Store the metallib to custom format or disk, or use to create a
// MTLLibrary.

CComPtr<IDxcBlob> MetalBlob;
IFT(hlsl::DxcCreateBlobOnHeapCopy(
MetalLibBytes, (uint32_t)MetalLibSize, &MetalBlob));
std::swap(pOutputBlob, MetalBlob);

delete[] MetalLibBytes;
IRMetalLibBinaryDestroy(MetalLib);
IRObjectDestroy(DXILObj);
IRObjectDestroy(AIR);
IRCompilerDestroy(MetalCompiler);
}
#endif
} // compileOK && !opts.CodeGenHighLevel
}

std::string remarks;
Expand Down

0 comments on commit 6c2ba40

Please sign in to comment.