Skip to content

HLSL: support SPIR-V library modules (no OpEntryPoint, only Export-decorated functions) #2622

Description

@castano

spirv-cross --hlsl cannot consume SPIR-V "library" modules, that is, modules with OpCapability Linkage, no OpEntryPoint, and one or more functions decorated with OpDecorate LinkageAttributes "" Export. Such modules are produced by dxc -T lib_6_* and are useful for distributing pre-compiled shader libraries that callers can include from HLSL/GLSL source.

Today, parsing fails immediately:

SPIRV-Cross threw an exception: There is no entry point in the SPIR-V module.

at spirv_parser.cpp:155.

Repro:

// lib.hlsl
static const uint table[4] = { 10u, 20u, 30u, 40u };

export uint lookup(uint i) {
    return table[i];
}

dxc -T lib_6_3 -spirv -fspv-target-env=universal1.5 lib.hlsl -Fo lib.spv
spirv-cross --hlsl lib.spv
# SPIRV-Cross threw an exception: There is no entry point in the SPIR-V module.

(For the constant table to actually carry its values into the SPV, a separate DXC fix is needed tracked at microsoft/DirectXShaderCompiler#8425. The spirv-cross issue here is independent, even with that fix, spirv-cross can't process the module because there's no entry point.)

Desired Behavior:

When the module has no entry point but does have Export-decorated functions, emit each exported function as a top-level free function in the target language (HLSL/GLSL), with no [numthreads]/main() boilerplate. Output should be includable as a header by HLSL/GLSL source code.

Expected output for the repro:

static const uint table[4] = { 10u, 20u, 30u, 40u };

uint lookup(uint i) {
    return table[i];
}

Motivation:

I'd like to distribute the Spark codecs as SPIR-V libraries that users can transform to HLSL/GLSL header-only libraries and include in their shader code.

Proposed approach:

  1. Parser (spirv_parser.cpp): collect function IDs decorated with LinkageAttributes ... Export. If no OpEntryPoint exists but exports do, synthesize a stub entry point pointing at the first export (model GLCompute) so the rest of the pipeline (analyses keyed on default_entry_point) keeps working. Mark the IR as a library module via a new is_library_module flag on ParsedIR. Fall back to the linkage name when OpName was stripped (e.g. by spirv-opt --strip-debug).
  2. CFG analysis (spirv_cross.cpp): extend build_function_control_flow_graphs_and_analyze to walk each library export's call tree (not just default_entry_point's).
  3. HLSL emitter (spirv_hlsl.cpp): in library mode, replace the emit_function(default_entry_point) + emit_hlsl_entry_point() calls with a loop emitting each library export. Skip the main rename. Patch emit_function_prototype so the first export, which happens to be default_entry_point for analysis purposes, is emitted as a normal function, not as the entry point.
  4. Reachability filter for declarations: in library mode, build a set of IDs referenced anywhere in the SPIR-V stream (excluding each instruction's own result id). Gate emit_specialization_constants_and_structs and emit_composite_constants on that set so unused constants/undefs/specializations don't leak into the output.

I have a working implementation locally on top of vulkan-sdk-1.4.341.0. The HLSL pipeline produces clean, includable output for all our shaders. I'd be happy to open a PR. Would you be open to reviewing it? Or would you suggest a different approach?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions