diff --git a/compiler/src/iree/compiler/Dialect/HAL/IR/HALBase.td b/compiler/src/iree/compiler/Dialect/HAL/IR/HALBase.td index e01d81e771ee..986356ffca2c 100644 --- a/compiler/src/iree/compiler/Dialect/HAL/IR/HALBase.td +++ b/compiler/src/iree/compiler/Dialect/HAL/IR/HALBase.td @@ -177,11 +177,11 @@ def HAL_DescriptorFlagsAttr : } def HAL_DescriptorSetLayoutFlags_None : I32BitEnumAttrCase<"None", 0x0000>; -def HAL_DescriptorSetLayoutFlags_Reserved : I32BitEnumAttrCase<"Reserved", 0x0001>; +def HAL_DescriptorSetLayoutFlags_Indirect : I32BitEnumAttrCase<"Indirect", 0x0001>; def HAL_DescriptorSetLayoutFlagsAttr : I32BitEnumAttr<"DescriptorSetLayoutFlags", "valid DescriptorSetLayout flags", [ HAL_DescriptorSetLayoutFlags_None, - HAL_DescriptorSetLayoutFlags_Reserved, // to make tblgen happy + HAL_DescriptorSetLayoutFlags_Indirect, ]> { let cppNamespace = "::mlir::iree_compiler::IREE::HAL"; } @@ -614,12 +614,14 @@ def HAL_DescriptorSetLayoutAttr : }]; let parameters = (ins AttrParameter<"int64_t", "">:$ordinal, - ArrayRefParameter<"DescriptorSetBindingAttr", "">:$bindings + ArrayRefParameter<"DescriptorSetBindingAttr", "">:$bindings, + OptionalParameter<"std::optional">:$flags ); let assemblyFormat = [{ `<` $ordinal `,` `bindings` `=` `[` $bindings `]` + (`,` `flags` `=` $flags^)? `>` }]; } @@ -714,7 +716,7 @@ def HAL_DeviceTargetAttr : bool hasConfigurationAttr(StringRef name); // Returns zero or more executable targets that this device supports. - SmallVector getExecutableTargets(); + SmallVector getExecutableTargets(); // Returns a list of target devices that may be active for the given // operation. This will recursively walk parent operations until one with @@ -752,7 +754,7 @@ def HAL_DeviceTargetAttr : // Returns a list of all target executable configurations that may be // required for the given operation. - static SmallVector + static SmallVector lookupExecutableTargets(Operation *op); }]; let hasCustomAssemblyFormat = 1; @@ -807,6 +809,10 @@ def HAL_ExecutableTargetAttr : // device that can load an executable of this target. Attribute getMatchExpression(); + // Returns true if there's an attribute with the given name in the + // configuration dictionary. + bool hasConfigurationAttr(StringRef name); + // Returns true if this attribute is a generic version of |specificAttr|. // A more generic version will match with many specific versions. bool isGenericOf(IREE::HAL::ExecutableTargetAttr specificAttr); @@ -815,7 +821,7 @@ def HAL_ExecutableTargetAttr : // This will recursively walk parent operations until one with the // `hal.executable.target` attribute is found or a `hal.executable.variant` // specifies a value. Returns nullptr if no target specification can be found. - static ExecutableTargetAttr lookup(Operation *op); + static IREE::HAL::ExecutableTargetAttr lookup(Operation *op); }]; let hasCustomAssemblyFormat = 1; diff --git a/compiler/src/iree/compiler/Dialect/HAL/IR/HALTypes.cpp b/compiler/src/iree/compiler/Dialect/HAL/IR/HALTypes.cpp index 6f436fe32984..1ffa83789e0c 100644 --- a/compiler/src/iree/compiler/Dialect/HAL/IR/HALTypes.cpp +++ b/compiler/src/iree/compiler/Dialect/HAL/IR/HALTypes.cpp @@ -516,6 +516,11 @@ Attribute ExecutableTargetAttr::getMatchExpression() { return DeviceMatchExecutableFormatAttr::get(getContext(), getFormat()); } +bool ExecutableTargetAttr::hasConfigurationAttr(StringRef name) { + auto configAttr = getConfiguration(); + return configAttr && configAttr.get(name); +} + // For now this is very simple: if there are any specified fields that are // present in this attribute they must match. We could allow target backends // to customize this via attribute interfaces in the future if we needed. diff --git a/compiler/src/iree/compiler/Dialect/HAL/IR/HALTypes.h b/compiler/src/iree/compiler/Dialect/HAL/IR/HALTypes.h index e77d258f5993..04e0c2df476c 100644 --- a/compiler/src/iree/compiler/Dialect/HAL/IR/HALTypes.h +++ b/compiler/src/iree/compiler/Dialect/HAL/IR/HALTypes.h @@ -194,6 +194,31 @@ operator<<(AsmPrinter &printer, return printer; } +template <> +struct FieldParser< + std::optional> { + static FailureOr + parse(AsmParser &parser) { + std::string value; + if (parser.parseKeywordOrString(&value)) + return failure(); + auto result = mlir::iree_compiler::IREE::HAL::symbolizeEnum< + mlir::iree_compiler::IREE::HAL::DescriptorSetLayoutFlags>(value); + if (!result.has_value()) + return failure(); + return result.value(); + } +}; +static inline AsmPrinter &operator<<( + AsmPrinter &printer, + std::optional + param) { + printer << (param.has_value() + ? mlir::iree_compiler::IREE::HAL::stringifyEnum(param.value()) + : StringRef{""}); + return printer; +} + template <> struct FieldParser< std::optional> { diff --git a/compiler/src/iree/compiler/Dialect/HAL/Target/VulkanSPIRV/VulkanSPIRVTarget.cpp b/compiler/src/iree/compiler/Dialect/HAL/Target/VulkanSPIRV/VulkanSPIRVTarget.cpp index f008aaa09420..829dbefa7b87 100644 --- a/compiler/src/iree/compiler/Dialect/HAL/Target/VulkanSPIRV/VulkanSPIRVTarget.cpp +++ b/compiler/src/iree/compiler/Dialect/HAL/Target/VulkanSPIRV/VulkanSPIRVTarget.cpp @@ -47,18 +47,26 @@ VulkanSPIRVTargetOptions getVulkanSPIRVTargetOptionsFromFlags() { // "IREE Vulkan/SPIR-V backend options"); static llvm::cl::opt clVulkanTargetTriple( - "iree-vulkan-target-triple", llvm::cl::desc("Vulkan target triple"), + "iree-vulkan-target-triple", + llvm::cl::desc( + "Vulkan target triple controlling the SPIR-V environment."), llvm::cl::init("unknown-unknown-unknown")); static llvm::cl::opt clVulkanTargetEnv( "iree-vulkan-target-env", llvm::cl::desc( - "Vulkan target environment as #vk.target_env attribute assembly"), + "Vulkan target environment as #vk.target_env attribute assembly."), llvm::cl::init("")); + static llvm::cl::opt clVulkanIndirectBindings( + "iree-vulkan-experimental-indirect-bindings", + llvm::cl::desc("Force indirect bindings for all generated dispatches."), + llvm::cl::init(false)); + VulkanSPIRVTargetOptions targetOptions; - targetOptions.vulkanTargetEnv = clVulkanTargetEnv; - targetOptions.vulkanTargetTriple = clVulkanTargetTriple; + targetOptions.targetEnv = clVulkanTargetEnv; + targetOptions.targetTriple = clVulkanTargetTriple; + targetOptions.indirectBindings = clVulkanIndirectBindings; return targetOptions; } @@ -291,23 +299,30 @@ class VulkanSPIRVTargetBackend : public TargetBackend { // If we had multiple target environments we would generate one target attr // per environment, with each setting its own environment attribute. targetAttrs.push_back(getExecutableTarget( - context, getSPIRVTargetEnv(options_.vulkanTargetEnv, - options_.vulkanTargetTriple, context))); + context, + getSPIRVTargetEnv(options_.targetEnv, options_.targetTriple, context), + options_.indirectBindings)); return ArrayAttr::get(context, targetAttrs); } IREE::HAL::ExecutableTargetAttr - getExecutableTarget(MLIRContext *context, - spirv::TargetEnvAttr targetEnv) const { + getExecutableTarget(MLIRContext *context, spirv::TargetEnvAttr targetEnv, + bool indirectBindings) const { Builder b(context); SmallVector configItems; configItems.emplace_back(b.getStringAttr(spirv::getTargetEnvAttrName()), targetEnv); + if (indirectBindings) { + configItems.emplace_back(b.getStringAttr("hal.bindings.indirect"), + UnitAttr::get(context)); + } auto configAttr = b.getDictionaryAttr(configItems); return IREE::HAL::ExecutableTargetAttr::get( - context, b.getStringAttr("vulkan"), b.getStringAttr("vulkan-spirv-fb"), + context, b.getStringAttr("vulkan"), + indirectBindings ? b.getStringAttr("vulkan-spirv-fb-ptr") + : b.getStringAttr("vulkan-spirv-fb"), configAttr); } diff --git a/compiler/src/iree/compiler/Dialect/HAL/Target/VulkanSPIRV/VulkanSPIRVTarget.h b/compiler/src/iree/compiler/Dialect/HAL/Target/VulkanSPIRV/VulkanSPIRVTarget.h index 49eeea3ba72e..9c316e4042d7 100644 --- a/compiler/src/iree/compiler/Dialect/HAL/Target/VulkanSPIRV/VulkanSPIRVTarget.h +++ b/compiler/src/iree/compiler/Dialect/HAL/Target/VulkanSPIRV/VulkanSPIRVTarget.h @@ -18,9 +18,11 @@ namespace HAL { // Options controlling the SPIR-V translation. struct VulkanSPIRVTargetOptions { // Vulkan target environment as #vk.target_env attribute assembly. - std::string vulkanTargetEnv; + std::string targetEnv; // Vulkan target triple. - std::string vulkanTargetTriple; + std::string targetTriple; + // Whether to use indirect bindings for all generated dispatches. + bool indirectBindings = false; }; // Returns a VulkanSPIRVTargetOptions struct initialized with Vulkan/SPIR-V diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/MaterializeInterfaces.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/MaterializeInterfaces.cpp index 55cd7960ac7b..716625f32b12 100644 --- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/MaterializeInterfaces.cpp +++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/MaterializeInterfaces.cpp @@ -170,6 +170,7 @@ static LogicalResult verifyEntryPointTypes(mlir::func::FuncOp entryFuncOp) { // Creates an pipeline layout attr from the analysis results. static IREE::HAL::PipelineLayoutAttr makePipelineLayoutAttr(const PipelineLayout &pipelineLayout, + IREE::HAL::ExecutableTargetAttr targetAttr, OpBuilder &builder) { SmallVector setLayoutAttrs; for (const auto &setLayout : pipelineLayout.setLayouts) { @@ -181,8 +182,12 @@ makePipelineLayoutAttr(const PipelineLayout &pipelineLayout, ? binding.flags : std::optional{})); } + std::optional flags; + if (targetAttr.hasConfigurationAttr("hal.bindings.indirect")) { + flags = IREE::HAL::DescriptorSetLayoutFlags::Indirect; + } setLayoutAttrs.push_back(IREE::HAL::DescriptorSetLayoutAttr::get( - builder.getContext(), setLayout.ordinal, bindingAttrs)); + builder.getContext(), setLayout.ordinal, bindingAttrs, flags)); } return IREE::HAL::PipelineLayoutAttr::get( builder.getContext(), pipelineLayout.pushConstantCount, setLayoutAttrs); @@ -312,8 +317,9 @@ declareEntryPointOps(IREE::Stream::ExecutableOp sourceExecutableOp, OpBuilder executableBuilder(&targetExecutableOp.getBlock().front()); // Build a map of source function definitions to their version with the - // updated interface. - DenseMap targetFuncOps; + // updated interface per variant. + DenseMap> + targetFuncOps; int nextOrdinal = 0; for (auto exportOp : sourceExecutableOp.getBody() .getOps()) { @@ -325,7 +331,6 @@ declareEntryPointOps(IREE::Stream::ExecutableOp sourceExecutableOp, // Create the interface for this entry point based on the analysis of its // usage within the program. const auto &pipelineLayout = layoutAnalysis.getPipelineLayout(exportOp); - auto layoutAttr = makePipelineLayoutAttr(pipelineLayout, executableBuilder); // Update all dispatch sites with the binding information required for // conversion into the HAL dialect. By doing this here we ensure that the @@ -338,7 +343,6 @@ declareEntryPointOps(IREE::Stream::ExecutableOp sourceExecutableOp, // Clone the updated function declaration into each variant. int ordinal = nextOrdinal++; for (auto variantOp : variantOps) { - // Declare the entry point on the target. OpBuilder targetBuilder(variantOp.getInnerModule()); // Check if workgroup size is set externally. ArrayAttr workgroupSize; @@ -356,6 +360,10 @@ declareEntryPointOps(IREE::Stream::ExecutableOp sourceExecutableOp, break; } } + + // Declare the entry point on the target. + auto layoutAttr = makePipelineLayoutAttr( + pipelineLayout, variantOp.getTargetAttr(), targetBuilder); auto newExportOp = targetBuilder.create( exportOp.getLoc(), targetBuilder.getStringAttr(exportOp.getFunctionRef()), @@ -380,25 +388,30 @@ declareEntryPointOps(IREE::Stream::ExecutableOp sourceExecutableOp, newExportOp.getWorkgroupCount().insertArgument(0u, deviceType, newExportOp.getLoc()); } - } - // Clone the source function and update it to use the new interface. - auto targetFuncOp = - cloneFuncWithInterface(sourceFuncOp, pipelineLayout, layoutAttr); - targetFuncOps[sourceFuncOp] = targetFuncOp; + // Clone the source function and update it to use the new interface. + auto variantFuncOp = + cloneFuncWithInterface(sourceFuncOp, pipelineLayout, layoutAttr); + targetFuncOps[sourceFuncOp][variantOp] = variantFuncOp; + } } // Clone all of the ops in the source module to each variant. // We'll use the exported functions with the updated interfaces in place of // the original versions and copy everything else verbatim. + // Note that we do this as a cleanup setup because there may be multiple + // functions and multiple exports (with an N:M mapping) and in this way we + // perform the variant construction in a single pass with deterministic + // ordering that preserves the unmodified ops. for (auto variantOp : variantOps) { auto targetBuilder = OpBuilder::atBlockBegin( &variantOp.getInnerModule().getBodyRegion().front()); for (auto &op : sourceModuleOp.getOps()) { - auto targetFuncOp = targetFuncOps.find(&op); - if (targetFuncOp != targetFuncOps.end()) { - // Clone the updated function instead of the original. - targetBuilder.clone(*targetFuncOp->second); + auto targetVariantFuncOps = targetFuncOps.find(&op); + if (targetVariantFuncOps != targetFuncOps.end()) { + // Move the updated function into place. + auto variantFuncOp = targetVariantFuncOps->second[variantOp]; + targetBuilder.insert(variantFuncOp); } else { // Regular op (globals, external function declarations, etc). targetBuilder.clone(op); @@ -406,13 +419,6 @@ declareEntryPointOps(IREE::Stream::ExecutableOp sourceExecutableOp, } } - // Drop the temporary target functions. We could avoid an additional clone if - // we only had one variant but this is relatively small in cost (once per - // variant). - for (auto it : targetFuncOps) - it.second->erase(); - targetFuncOps.clear(); - return success(); } diff --git a/compiler/src/iree/compiler/Dialect/HAL/Transforms/MaterializeResourceCaches.cpp b/compiler/src/iree/compiler/Dialect/HAL/Transforms/MaterializeResourceCaches.cpp index 2faed9b0bedf..e96254256e9d 100644 --- a/compiler/src/iree/compiler/Dialect/HAL/Transforms/MaterializeResourceCaches.cpp +++ b/compiler/src/iree/compiler/Dialect/HAL/Transforms/MaterializeResourceCaches.cpp @@ -118,9 +118,12 @@ class MaterializeResourceCachesPass } private: - IREE::Util::GlobalOp defineDescriptorSetLayoutOp(Location loc, - ArrayAttr bindingAttrs) { - auto existingIt = descriptorSetLayoutCache_.find(bindingAttrs); + IREE::Util::GlobalOp + defineDescriptorSetLayoutOp(Location loc, ArrayAttr bindingAttrs, + IREE::HAL::DescriptorSetLayoutFlags flags) { + std::pair key = { + bindingAttrs, flags}; + auto existingIt = descriptorSetLayoutCache_.find(key); if (existingIt != descriptorSetLayoutCache_.end()) { return existingIt->second; } @@ -134,15 +137,14 @@ class MaterializeResourceCachesPass loc, symbolName, /*isMutable=*/false, layoutType); globalOp.setPrivate(); - descriptorSetLayoutCache_.try_emplace(bindingAttrs, globalOp); + descriptorSetLayoutCache_.try_emplace(key, globalOp); auto initializerOp = moduleBuilder.create(loc); OpBuilder blockBuilder = OpBuilder::atBlockEnd(initializerOp.addEntryBlock()); auto deviceValue = blockBuilder.createOrFold(loc); - auto layoutFlags = IREE::HAL::DescriptorSetLayoutFlags::None; auto layoutValue = blockBuilder.createOrFold( - loc, layoutType, deviceValue, layoutFlags, bindingAttrs); + loc, layoutType, deviceValue, flags, bindingAttrs); blockBuilder.create(loc, layoutValue, globalOp.getName()); blockBuilder.create(loc); @@ -167,7 +169,9 @@ class MaterializeResourceCachesPass bindingAttrs.push_back(bindingAttr); } setLayoutGlobalOps.push_back(defineDescriptorSetLayoutOp( - loc, ArrayAttr::get(loc.getContext(), bindingAttrs))); + loc, ArrayAttr::get(loc.getContext(), bindingAttrs), + setLayoutAttr.getFlags().value_or( + IREE::HAL::DescriptorSetLayoutFlags::None))); } auto symbolName = (StringRef("_pipeline_layout_") + @@ -319,8 +323,8 @@ class MaterializeResourceCachesPass void replaceDescriptorSetLayoutLookupOp(DescriptorSetLayoutLookupOp &lookupOp) { OpBuilder builder(lookupOp); - auto globalOp = - defineDescriptorSetLayoutOp(lookupOp.getLoc(), lookupOp.getBindings()); + auto globalOp = defineDescriptorSetLayoutOp( + lookupOp.getLoc(), lookupOp.getBindings(), lookupOp.getFlags()); auto loadOp = builder.create( lookupOp.getLoc(), DescriptorSetLayoutType::get(lookupOp.getContext()), globalOp.getSymName()); @@ -355,7 +359,9 @@ class MaterializeResourceCachesPass TargetOptions targetOptions_; OpBuilder moduleBuilder{static_cast(nullptr)}; - DenseMap descriptorSetLayoutCache_; + DenseMap, + IREE::Util::GlobalOp> + descriptorSetLayoutCache_; DenseMap pipelineLayoutCache_; DenseMap executableCache_; diff --git a/compiler/src/iree/compiler/InputConversion/Common/IREEImportPublic.cpp b/compiler/src/iree/compiler/InputConversion/Common/IREEImportPublic.cpp index 672a56cce9ff..9581bbf6f8fa 100644 --- a/compiler/src/iree/compiler/InputConversion/Common/IREEImportPublic.cpp +++ b/compiler/src/iree/compiler/InputConversion/Common/IREEImportPublic.cpp @@ -91,7 +91,6 @@ static std::optional convertDescriptorFlags(std::optional src) { if (!src.has_value()) return std::nullopt; - switch (*src) { case IREE::Input::DescriptorFlags::None: return IREE::HAL::DescriptorFlags::None; @@ -109,12 +108,28 @@ convertDescriptorSetBinding(IREE::Input::DescriptorSetBindingAttr src) { convertDescriptorFlags(src.getFlags())); } +static std::optional +convertDescriptorSetLayoutFlags( + std::optional src) { + if (!src.has_value()) + return std::nullopt; + switch (*src) { + case IREE::Input::DescriptorSetLayoutFlags::None: + return IREE::HAL::DescriptorSetLayoutFlags::None; + case IREE::Input::DescriptorSetLayoutFlags::Indirect: + return IREE::HAL::DescriptorSetLayoutFlags::Indirect; + default: + return std::nullopt; + } +} + static IREE::HAL::DescriptorSetLayoutAttr convertDescriptorSetLayout(IREE::Input::DescriptorSetLayoutAttr src) { return IREE::HAL::DescriptorSetLayoutAttr::get( src.getContext(), src.getOrdinal(), convertAttributes( - src.getBindings(), convertDescriptorSetBinding)); + src.getBindings(), convertDescriptorSetBinding), + convertDescriptorSetLayoutFlags(src.getFlags())); } static IREE::HAL::PipelineLayoutAttr diff --git a/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/Input/InputBase.td b/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/Input/InputBase.td index cb24441a432a..9c871f6c9f6e 100644 --- a/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/Input/InputBase.td +++ b/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/Input/InputBase.td @@ -281,6 +281,18 @@ def IREEInput_DescriptorSetBindingAttr : }]; } +def IREEInput_DescriptorSetLayoutFlags_None : + I32BitEnumAttrCase<"None", 0x0000>; +def IREEInput_DescriptorSetLayoutFlags_Indirect : + I32BitEnumAttrCase<"Indirect", 0x0001>; +def IREEInput_DescriptorSetLayoutFlagsAttr : + I32BitEnumAttr<"DescriptorSetLayoutFlags", "valid DescriptorSetLayout flags", [ + IREEInput_DescriptorSetLayoutFlags_None, + IREEInput_DescriptorSetLayoutFlags_Indirect, + ]> { + let cppNamespace = "::mlir::iree_compiler::IREE::Input"; +} + def IREEInput_DescriptorSetLayoutAttr : AttrDef { let mnemonic = "descriptor_set.layout"; @@ -288,13 +300,15 @@ def IREEInput_DescriptorSetLayoutAttr : let parameters = (ins AttrParameter<"int64_t", "">:$ordinal, - ArrayRefParameter<"DescriptorSetBindingAttr", "">:$bindings + ArrayRefParameter<"DescriptorSetBindingAttr", "">:$bindings, + OptionalParameter<"std::optional">:$flags ); let assemblyFormat = [{ `<` $ordinal `,` `bindings` `=` `[` $bindings `]` + (`,` `flags` `=` $flags^)? `>` }]; } diff --git a/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/Input/InputDialect.h b/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/Input/InputDialect.h index 280932bedf18..41ecf9fc1f54 100644 --- a/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/Input/InputDialect.h +++ b/llvm-external-projects/iree-dialects/include/iree-dialects/Dialect/Input/InputDialect.h @@ -46,6 +46,33 @@ std::optional getEncodingTypeValue(Attribute attr); namespace mlir { +template <> +struct FieldParser< + std::optional> { + static FailureOr + parse(AsmParser &parser) { + std::string value; + if (parser.parseKeywordOrString(&value)) + return failure(); + auto result = mlir::iree_compiler::IREE::Input::symbolizeEnum< + mlir::iree_compiler::IREE::Input::DescriptorSetLayoutFlags>(value); + if (!result.has_value()) + return failure(); + return result.value(); + } +}; + +static inline AsmPrinter &operator<<( + AsmPrinter &printer, + std::optional + param) { + printer << (param.has_value() + ? mlir::iree_compiler::IREE::Input::stringifyEnum( + param.value()) + : StringRef{""}); + return printer; +} + template <> struct FieldParser< std::optional> { diff --git a/runtime/src/iree/hal/pipeline_layout.h b/runtime/src/iree/hal/pipeline_layout.h index 5589b4ec279d..bd15bb1a51c5 100644 --- a/runtime/src/iree/hal/pipeline_layout.h +++ b/runtime/src/iree/hal/pipeline_layout.h @@ -26,7 +26,11 @@ typedef struct iree_hal_device_t iree_hal_device_t; // A bitmask of flags controlling the behavior of a descriptor set. enum iree_hal_descriptor_set_layout_flag_bits_t { IREE_HAL_DESCRIPTOR_SET_LAYOUT_FLAG_NONE = 0u, - // TODO(benvanik): add flag bits for binding table usage modes. + + // Indicates the descriptor sets are 'bindless' and passed via implementation- + // specific parameter buffers stored in memory instead of API-level calls. + // Ignored by implementations that don't have a concept of indirect bindings. + IREE_HAL_DESCRIPTOR_SET_LAYOUT_FLAG_INDIRECT = 1u << 0, }; typedef uint32_t iree_hal_descriptor_set_layout_flags_t;