[Metal] Carry export name separately from MSL entry point for name lookup (#24642)
spirv-cross rewrites SPIR-V entry point names to be legal MSL/C
identifiers when serializing Metal executables: '.' characters (codegen
dispatch names like _dot_general_matmul_i32.i32.i32_...) become '_', and
reserved '__' prefixes (stream builtins like __builtin_splat_i64) are
dropped. The MetalSPIRV target stored only the rewritten MSL function
name in PipelineDef.entry_point, and the runtime loaded pipeline->name
from that same field -- so hal.executable.lookup.function, which
references the original un-rewritten hal.executable.export symbol name,
could never match for any export whose name spirv-cross rewrites. This
broke every metal-spirv e2e test using such exports
(dot/dot_general/convolution matmul kernels and
compare/dynamic_slice/gather stream builtins).
Add a dedicated always-present name field to PipelineDef carrying the
canonical hal.executable.export symbol name (populated from
exportOp.getName() in the compiler), appended at the end of the table
for backward-compatible vtable layout. The runtime now sources
pipeline->name (used by lookup_function_by_name) from this field,
keeping entry_point as the MTLFunction name for -[MTLLibrary
newFunctionWithName:]. The loader falls back to entry_point when name is
absent for compatibility.
Verified: dot_general (8/8), compare (15/15), and the full Metal CTS
suite
(command_buffer_tests/queue_tests/dispatch_tests/queue_dispatch_tests,
4/4) pass. Signed-off-by: Alex Vasile
<48962821+Alex-Vasile@users.noreply.github.com>
Signed-off-by: Alex Vasile <48962821+Alex-Vasile@users.noreply.github.com>
diff --git a/compiler/plugins/target/MetalSPIRV/MetalSPIRVTarget.cpp b/compiler/plugins/target/MetalSPIRV/MetalSPIRVTarget.cpp
index 5618a5b..feb61b2 100644
--- a/compiler/plugins/target/MetalSPIRV/MetalSPIRVTarget.cpp
+++ b/compiler/plugins/target/MetalSPIRV/MetalSPIRVTarget.cpp
@@ -157,9 +157,11 @@
".spv", spvBinary);
}
- // The runtime use ordinals instead of names but Metal requires function
- // names for constructing pipeline states. Get an ordered list of the entry
- // point names.
+ // Collect the SPIR-V entry point function names. These are cross-compiled
+ // to MSL and used to construct Metal pipeline states; they are the MSL
+ // function names (which spirv-cross may rewrite to be MSL-legal) and are
+ // distinct from the hal.executable.export symbol names the runtime uses to
+ // look up exports by name.
SmallVector<StringRef, 8> spirvEntryPointNames;
spvModuleOp.walk([&](spirv::EntryPointOp exportOp) {
spirvEntryPointNames.push_back(exportOp.getFn());
@@ -262,6 +264,7 @@
llvm::zip_equal(llvm::seq(mslShaders.size()), mslShaders,
mslEntryPointNames, exportOps)) {
auto entryPointRef = builder.createString(entryPoint);
+ auto exportNameRef = builder.createString(exportOp.getName());
iree_hal_metal_ThreadgroupSize_t threadgroupSize = {
shader.threadgroupSize.x,
@@ -286,6 +289,7 @@
iree_hal_metal_PipelineDef_start(builder);
iree_hal_metal_PipelineDef_library_ordinal_add(builder, i);
iree_hal_metal_PipelineDef_entry_point_add(builder, entryPointRef);
+ iree_hal_metal_PipelineDef_name_add(builder, exportNameRef);
iree_hal_metal_PipelineDef_threadgroup_size_add(builder,
&threadgroupSize);
// TODO: embed additional metadata on threadgroup info if available.
diff --git a/runtime/src/iree/hal/drivers/metal/executable.m b/runtime/src/iree/hal/drivers/metal/executable.m
index 87c6447..f4a990a 100644
--- a/runtime/src/iree/hal/drivers/metal/executable.m
+++ b/runtime/src/iree/hal/drivers/metal/executable.m
@@ -424,18 +424,23 @@
iree_hal_metal_ExecutableDef_pipelines_get(executable_def);
iree_host_size_t pipeline_count = flatbuffers_string_vec_len(pipelines_vec);
- // Calculate the total number of characters across all entry point names so
- // that we can store copies of the names as the flatbuffer storing the strings
- // may be released while the executable is still live.
+ // Calculate the total number of characters across all export names so that we
+ // can store copies of the names as the flatbuffer storing the strings may be
+ // released while the executable is still live.
iree_host_size_t total_pipeline_name_length = 0;
iree_host_size_t total_debug_info_length = 0;
for (iree_host_size_t i = 0; i < pipeline_count; ++i) {
iree_hal_metal_PipelineDef_table_t pipeline_def =
iree_hal_metal_PipelineDef_vec_at(pipelines_vec, i);
- if (IREE_UNLIKELY(!iree_host_size_checked_add(
- total_pipeline_name_length,
- flatbuffers_string_len(iree_hal_metal_PipelineDef_entry_point_get(pipeline_def)),
- &total_pipeline_name_length))) {
+ flatbuffers_string_t export_name = iree_hal_metal_PipelineDef_name_get(pipeline_def);
+ if (flatbuffers_string_len(export_name) == 0) {
+ // Fall back to the entry point name for executables that don't carry an
+ // explicit export name (equivalent to the pre-name-field behavior).
+ export_name = iree_hal_metal_PipelineDef_entry_point_get(pipeline_def);
+ }
+ if (IREE_UNLIKELY(!iree_host_size_checked_add(total_pipeline_name_length,
+ flatbuffers_string_len(export_name),
+ &total_pipeline_name_length))) {
return iree_make_status(IREE_STATUS_OUT_OF_RANGE, "pipeline name storage size overflow");
}
IREE_TRACE({
@@ -487,11 +492,14 @@
status = iree_hal_metal_create_pipeline(device, library, pipeline_def, pipeline);
if (!iree_status_is_ok(status)) break;
- flatbuffers_string_t entry_point = iree_hal_metal_PipelineDef_entry_point_get(pipeline_def);
- const iree_host_size_t entry_point_length = flatbuffers_string_len(entry_point);
- pipeline->name = iree_make_string_view(pipeline_name_ptr, entry_point_length);
- memcpy(pipeline_name_ptr, entry_point, entry_point_length);
- pipeline_name_ptr += entry_point_length;
+ flatbuffers_string_t export_name = iree_hal_metal_PipelineDef_name_get(pipeline_def);
+ if (flatbuffers_string_len(export_name) == 0) {
+ export_name = iree_hal_metal_PipelineDef_entry_point_get(pipeline_def);
+ }
+ const iree_host_size_t export_name_length = flatbuffers_string_len(export_name);
+ pipeline->name = iree_make_string_view(pipeline_name_ptr, export_name_length);
+ memcpy(pipeline_name_ptr, export_name, export_name_length);
+ pipeline_name_ptr += export_name_length;
IREE_TRACE({
iree_hal_debug_export_info_t* export_info = (iree_hal_debug_export_info_t*)export_info_ptr;
diff --git a/runtime/src/iree/schemas/metal_executable_def.fbs b/runtime/src/iree/schemas/metal_executable_def.fbs
index fbee2ad..550b7e8 100644
--- a/runtime/src/iree/schemas/metal_executable_def.fbs
+++ b/runtime/src/iree/schemas/metal_executable_def.fbs
@@ -31,7 +31,11 @@
// libraries list.
library_ordinal:uint32;
- // String name of the entry point MTLFunction in the library.
+ // String name of the entry point MTLFunction in the library. This is the MSL
+ // function name produced by spirv-cross, which rewrites symbols that are
+ // illegal or reserved in MSL/C (for example '.' characters become '_' and a
+ // leading reserved '__' prefix is dropped). Used to construct the
+ // MTLComputePipelineState via -[MTLLibrary newFunctionWithName:].
entry_point:string;
// The maximum number of threads in a threadgroup that can be dispatched to
@@ -55,6 +59,12 @@
// Optional debug information related to the export.
debug_info:iree.hal.debug.ExportDef;
+
+ // Canonical hal.executable.export symbol name used by the runtime to look up
+ // the export by name (hal.executable.lookup.function). This carries the
+ // original, un-rewritten name and may differ from entry_point because MSL
+ // cannot represent all legal symbol names; dispatch lookups must match it.
+ name:string;
}
// MSL (Metal Shading Language) source code.