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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions