blob: ab341cd19c0cb05a272b76da68a1497bd690e32d [file]
// Copyright 2023 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#ifndef IREE_COMPILER_CODEGEN_LLVMCPU_DISPATCHABI_H_
#define IREE_COMPILER_CODEGEN_LLVMCPU_DISPATCHABI_H_
#include "iree/compiler/Dialect/HAL/IR/HALDialect.h"
#include "iree/compiler/Dialect/HAL/IR/HALOps.h"
#include "llvm/Support/Mutex.h"
#include "llvm/Support/raw_ostream.h"
#include "mlir/Conversion/LLVMCommon/MemRefBuilder.h"
#include "mlir/Conversion/LLVMCommon/TypeConverter.h"
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
#include "mlir/Dialect/LLVMIR/LLVMTypes.h"
#include "mlir/Dialect/MemRef/IR/MemRef.h"
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/Pass/Pass.h"
namespace mlir::iree_compiler {
// NOTE: HALDispatchABI and the associated conversion patterns should maybe live
// under compiler/plugins/target/LLVMCPU/ instead of here as they have nothing
// to do with linalg. If we need to use the patterns in this conversion we can
// expose a populate*Patterns() function to access them without needing them
// defined here.
//------------------------------------------------------------------------------
// ExecutableLibraryDI
//------------------------------------------------------------------------------
// Debug information adapter for the executable_library.h types.
// Manually synchronized with the runtime header as needed.
class ExecutableLibraryDI {
public:
// Initializes a cached DI provider using |typeConverter| to determine
// variable-width types such as index/size_t.
explicit ExecutableLibraryDI(const LLVMTypeConverter *typeConverter);
// Returns `void*`.
LLVM::DIDerivedTypeAttr getVoidPtr() { return voidPtr; }
// Returns `int8_t`.
LLVM::DIDerivedTypeAttr getInt8T() { return int8T; }
// Returns `uint8_t`.
LLVM::DIDerivedTypeAttr getUint8T() { return uint8T; }
// Returns `int16_t`.
LLVM::DIDerivedTypeAttr getInt16T() { return int16T; }
// Returns `uint16_t`.
LLVM::DIDerivedTypeAttr getUint16T() { return uint16T; }
// Returns `int32_t`.
LLVM::DIDerivedTypeAttr getInt32T() { return int32T; }
// Returns `uint32_t`.
LLVM::DIDerivedTypeAttr getUint32T() { return uint32T; }
// Returns `int64_t`.
LLVM::DIDerivedTypeAttr getInt64T() { return int64T; }
// Returns `uint64_t`.
LLVM::DIDerivedTypeAttr getUint64T() { return uint64T; }
// Returns `intptr_t`.
LLVM::DIDerivedTypeAttr getIntptrT() { return intptrT; }
// Returns `size_t`.
LLVM::DIDerivedTypeAttr getSizeT() { return sizeT; }
// Returns `const |typeAttr|`.
LLVM::DIDerivedTypeAttr getConstOf(LLVM::DITypeAttr typeAttr);
// Returns `|typeAttr|*`.
LLVM::DIDerivedTypeAttr getPtrOf(LLVM::DITypeAttr typeAttr);
// Returns `|typeAttr|[count]`.
LLVM::DICompositeTypeAttr getArrayOf(LLVM::DITypeAttr typeAttr,
int64_t count);
// Returns `using |name| = |typeAttr|`.
LLVM::DIDerivedTypeAttr getTypedefOf(StringRef name,
LLVM::DITypeAttr typeAttr);
// Returns a member |name| of |typeAttr| at bit offset |offsetInBits|.
// Upon return |offsetInBits| is updated to point after the member.
LLVM::DIDerivedTypeAttr getMemberOf(StringRef name, LLVM::DITypeAttr typeAttr,
unsigned *offsetInBits);
// Returns the DI type for the given LLVM type.
LLVM::DITypeAttr getBasicType(Type type);
// Returns `iree_hal_processor_v0_t`.
LLVM::DICompositeTypeAttr getProcessorV0T();
// Returns `iree_hal_executable_environment_v0_t`.
LLVM::DIDerivedTypeAttr getEnvironmentV0T();
// Returns `iree_hal_executable_dispatch_state_v0_t`.
LLVM::DIDerivedTypeAttr getDispatchStateV0T();
// Returns `iree_hal_executable_workgroup_state_v0_t`.
LLVM::DIDerivedTypeAttr getWorkgroupStateV0T();
private:
Builder builder;
LLVM::DIFileAttr fileAttr;
unsigned ptrBitwidth;
// Cached as they are commonly constructed with any usage of the DI info.
LLVM::DIDerivedTypeAttr voidPtr;
LLVM::DIDerivedTypeAttr int8T;
LLVM::DIDerivedTypeAttr uint8T;
LLVM::DIDerivedTypeAttr int16T;
LLVM::DIDerivedTypeAttr uint16T;
LLVM::DIDerivedTypeAttr int32T;
LLVM::DIDerivedTypeAttr uint32T;
LLVM::DIDerivedTypeAttr int64T;
LLVM::DIDerivedTypeAttr uint64T;
LLVM::DIDerivedTypeAttr intptrT;
LLVM::DIDerivedTypeAttr sizeT;
};
//------------------------------------------------------------------------------
// HALDispatchABI
//------------------------------------------------------------------------------
// Utility for accessing the IREE HAL dispatch function ABI values.
// All accesses should route through this vs directly manipulating LLVM function
// arguments so that we can adjust the ABI over time and support multiple
// versions in the same compiled output.
class HALDispatchABI {
public:
// Matches the field order in iree_hal_processor_v0_t.
enum class ProcessorField {
data = 0,
};
// Matches IREE_HAL_PROCESSOR_DATA_CAPACITY_V0.
static constexpr int ProcessorDataCapacity = 8;
// Returns a Type representing iree_hal_processor_v0_t.
static LLVM::LLVMStructType
getProcessorType(MLIRContext *context,
const LLVMTypeConverter *typeConverter);
// Matches the field order in iree_hal_executable_environment_v0_t.
enum class EnvironmentField {
constants,
import_thunk,
import_funcs,
import_contexts,
processor,
};
// Returns a Type representing iree_hal_executable_environment_v0_t.
static LLVM::LLVMStructType
getEnvironmentType(MLIRContext *context,
const LLVMTypeConverter *typeConverter,
LLVM::LLVMStructType processorType);
// Matches the field order in iree_hal_executable_dispatch_state_v0_t.
enum class DispatchStateField {
/*uint32_t*/ workgroup_size_x,
/*uint32_t*/ workgroup_size_y,
/*uint16_t*/ workgroup_size_z,
/*uint16_t*/ constant_count,
/*uint32_t*/ workgroup_count_x,
/*uint32_t*/ workgroup_count_y,
/*uint16_t*/ workgroup_count_z,
/*uint8_t*/ max_concurrency,
/*uint8_t*/ binding_count,
/*intptr_t*/ constants,
/*intptr_t*/ binding_ptrs,
/*intptr_t*/ binding_lengths,
};
friend DispatchStateField operator+(DispatchStateField lhs, int32_t rhs) {
return static_cast<DispatchStateField>(static_cast<int32_t>(lhs) + rhs);
}
// Returns a Type representing iree_hal_executable_dispatch_state_v0_t.
static LLVM::LLVMStructType
getDispatchStateType(MLIRContext *context,
const LLVMTypeConverter *typeConverter);
enum class WorkgroupStateField {
/*uint32_t*/ workgroup_id_x = 0,
/*uint32_t*/ workgroup_id_y,
/*uint16_t*/ workgroup_id_z,
/*uint16_t*/ reserved,
/*uint32_t*/ processor_id,
/*intptr_t*/ local_memory,
/*uint32_t*/ local_memory_size,
};
friend WorkgroupStateField operator+(WorkgroupStateField lhs, int32_t rhs) {
return static_cast<WorkgroupStateField>(static_cast<int32_t>(lhs) + rhs);
}
// Returns a Type representing iree_hal_executable_workgroup_state_v0_t.
static LLVM::LLVMStructType
getWorkgroupStateType(MLIRContext *context,
const LLVMTypeConverter *typeConverter);
// Returns the types of the LLVM function inputs for the ABI.
// This matches the signature of `iree_hal_executable_dispatch_v0_t` in
// `iree/hal/local/executable_library.h`.
static SmallVector<Type, 5>
getInputTypes(MLIRContext *context, const LLVMTypeConverter *typeConverter);
// Builds a DISubprogram for |llvmFuncOp| function in |moduleOp|.
// This is required in order to get any debug information (including line
// tables) from MLIR into LLVM IR. It does not need to match the exact
// definition but the closer we can make it to the real thing the more useful
// downstream tools will be.
static LLVM::DISubprogramAttr
buildScopeAttr(mlir::ModuleOp moduleOp, LLVM::LLVMFuncOp llvmFuncOp,
const LLVMTypeConverter *typeConverter);
explicit HALDispatchABI(LLVMTypeConverter *typeConverter)
: context(&typeConverter->getContext()), typeConverter(typeConverter),
processorType(getProcessorType(context, typeConverter)),
environmentType(
getEnvironmentType(context, typeConverter, processorType)),
dispatchStateType(getDispatchStateType(context, typeConverter)),
workgroupStateType(getWorkgroupStateType(context, typeConverter)),
di(typeConverter) {}
// Loads the workgroup_id[dim] value (XYZ) and casts it to |resultType|.
Value loadWorkgroupID(Operation *forOp, int32_t dim, Type resultType,
OpBuilder &builder);
// Loads the workgroup_count[dim] value (XYZ) and casts it to |resultType|.
Value loadWorkgroupCount(Operation *forOp, int32_t dim, Type resultType,
OpBuilder &builder);
// Loads the workgroup_size[dim] value (XYZ) and casts it to |resultType|.
Value loadWorkgroupSize(Operation *forOp, int32_t dim, Type resultType,
OpBuilder &builder);
// Returns the estimated maximum concurrency as an index-converted type.
Value loadMaxConcurrency(Operation *forOp, OpBuilder &builder);
// Returns the total number of bytes available in workgroup local memory.
// This may be larger than the requested size.
Value loadWorkgroupLocalMemorySize(Operation *forOp, OpBuilder &builder);
// Loads the base pointer of the workgroup local memory.
// Note that this may be NULL if no workgroup local memory was requested.
Value loadWorkgroupLocalMemoryPtr(Operation *forOp, OpBuilder &builder);
// Returns the total push constant count as an index-converted type.
Value loadPushConstantCount(Operation *forOp, OpBuilder &builder);
// Loads a push constant at |offset| and casts it to |resultType|.
Value loadPushConstant(Operation *forOp, int64_t offset, Type resultType,
OpBuilder &builder);
// Returns the total binding count as an index-converted type.
Value loadBindingCount(Operation *forOp, OpBuilder &builder);
// Loads the base pointer of the binding |ordinal| as an `i8**`.
// Equivalent to:
// int8_t** base_ptr = &state->binding_ptrs[ordinal];
Value loadBindingPtr(Operation *forOp, int64_t ordinal, OpBuilder &builder);
// Loads the byte length of the binding |ordinal| as an index-converted type.
Value loadBindingLength(Operation *forOp, int64_t ordinal,
OpBuilder &builder);
// Loads a binding as a constructed MemRefDescriptor.
// |baseOffset| can optionally adjust the base byte offset of the buffer.
MemRefDescriptor loadBinding(Operation *forOp, int64_t ordinal,
Value baseOffsetValue, MemRefType memRefType,
ValueRange dynamicDims, OpBuilder &builder);
// Loads the processor ID the code is (most likely) being run on.
// Equivalent to:
// uint32_t processor_id = state->processor_id;
Type getProcessorIDType();
Value loadProcessorID(Operation *forOp, OpBuilder &builder);
// Loads a pointer to the processor information data fields.
Type getProcessorDataType();
Value loadProcessorData(Operation *forOp, OpBuilder &builder);
// Loads a processor information data field at the given index.
// May be 0 if the field is not available.
Value loadProcessorData(Operation *forOp, int64_t index, OpBuilder &builder);
// Loads an executable constant with |key| and casts it to |resultType|.
// A placeholder global will be added for the ordinal.
Value loadExecutableConstant(Operation *forOp, StringRef key, Type resultType,
OpBuilder &builder);
// Loads the ordinal of the import with the given |importName|.
// A placeholder global will be inserted that will be updated with the
// assigned ordinal after linking.
Value loadImportOrdinal(Operation *forOp, StringRef importName, bool weak,
OpBuilder &builder);
// Loads the import function pointer of the import |ordinal|.
// Equivalent to:
// iree_hal_executable_import_v0_t fn_ptr = state->import_funcs[ordinal];
// void* context = state->import_contexts[ordinal];
std::pair<Value, Value> loadImportFunc(Operation *forOp, Value importOrdinal,
OpBuilder &builder);
// Returns an i1 indicating whether the optional import with |importName| is
// defined. Equivalent to:
// state->import_funcs[ordinal] != NULL
Value isImportFuncAvailable(Operation *forOp, StringRef importName,
OpBuilder &builder);
// Emits a call to the import with the given |importName|.
// The provided |params| struct containing the function-specific arguments
// is passed without modification.
// Returns 0 on success and non-zero otherwise.
Value callImport(Operation *forOp, StringRef importName, bool weak,
Value params, OpBuilder &builder);
// Emits a call to a dynamically linked import using the given |importName|
// as a template.
//
// The provided |resultTypes| and |args| are packed in a struct and transit
// through memory so that we can expose a single void* argument. Optionally
// |extraFields| can be specified with an ordered list of field names to be
// appended to the end of the struct.
//
// Returns 0 on success and non-zero otherwise.
SmallVector<Value> wrapAndCallImport(Operation *forOp, StringRef importName,
bool weak, TypeRange resultTypes,
ValueRange args,
ArrayRef<StringRef> extraFields,
OpBuilder &builder);
//===--------------------------------------------------------------------==//
// External/bitcode function ABI handling methods.
//===--------------------------------------------------------------------==//
// Methods required for handling ABI for functions whose definitions are
// external.
/// Check if the `funcType` is equivalent to a function with return being
/// of type `resultTypes` and operands of type `paramTypes`.
static bool hasCompatibleFunctionSignature(MLIRContext *context,
LLVM::LLVMFunctionType funcType,
TypeRange resultTypes,
TypeRange paramTypes);
/// Given a calling convention `cConv`, and callee with return of
/// `resultTypes` and operands with type `argTypes`, along with extra fields
/// to append to argument list specified in `extraFields`; return the function
/// type of use for the function that implements the specified calling
/// convention.
FailureOr<LLVM::LLVMFunctionType>
getABIFunctionType(Operation *forOp, IREE::HAL::CallingConvention cConv,
TypeRange resultTypes, TypeRange argTypes,
ArrayRef<StringRef> extraFields);
/// Given a calling convention `cConv`, and callee with return of
/// `resultTypes` and operands with type `argTypes`, along with extra fields
/// to append to argument list specified in `extraFields`; modify the `callOp`
/// to implement the specified ABI. The calleee signature is expected to have
/// been/to be modified separately, i.e. it isnt done within this method.
FailureOr<SmallVector<Value>>
materializeABI(Operation *callOp, StringRef symbolName,
IREE::HAL::CallingConvention cConv, TypeRange resultTypes,
ValueRange args, ArrayRef<StringRef> extraFields,
RewriterBase &builder);
private:
Value getIndexValue(Location loc, int64_t value, OpBuilder &builder);
Value castValueToType(Location loc, Value value, Type resultType,
OpBuilder &builder);
Value loadFieldValue(Operation *forOp, EnvironmentField field,
OpBuilder &builder);
Value loadFieldValue(Operation *forOp, ProcessorField field,
OpBuilder &builder);
Value loadFieldValue(Operation *forOp, DispatchStateField field,
OpBuilder &builder);
Type getFieldType(WorkgroupStateField field);
Value loadFieldValue(Operation *forOp, WorkgroupStateField field,
OpBuilder &builder);
Type getExtraFieldType(StringRef extraField);
Value getExtraField(Operation *forOp, StringRef extraField,
OpBuilder &builder);
// Update the processor data based on the `cpu_features` present in
// `executable.target` attribute.
Value updateProcessorDataFromTargetAttr(Operation *forOp,
Value processorDataPtrValue,
OpBuilder &builder);
// Return LLVM Struct type that represents a container for arguments
// and return types. The struct type are ordered [results..., args...]
std::optional<Type> getParameterStructType(TypeRange resultTypes,
ValueRange args,
TypeRange extraFieldsTypes);
// For a given call operation, generate the struct that is the container
// for passing the arguments.
//
// The provided |resultTypes| and |args| are packed in a struct and transit
// through memory so that we can expose a single void* argument. Optionally
// |extraFields| can be specified with an ordered list of field names to be
// appended to the end of the struct.
std::tuple<Type, Value> packIntoParameterStruct(Operation *forOp,
TypeRange resultTypes,
ValueRange args,
ValueRange extraFields,
OpBuilder &builder);
mlir::MLIRContext *context;
const LLVMTypeConverter *typeConverter;
LLVM::LLVMStructType processorType;
LLVM::LLVMStructType environmentType;
LLVM::LLVMStructType dispatchStateType;
LLVM::LLVMStructType workgroupStateType;
LLVM::DISubprogramAttr scopeAttr;
ExecutableLibraryDI di;
// Used to lock around mutations of shared LLVM type information, e.g.
// mlir::LLVM::LLVMStructType::getIdentified.
static llvm::sys::Mutex sMutex;
};
} // namespace mlir::iree_compiler
#endif // IREE_COMPILER_CODEGEN_LLVMCPU_DISPATCHABI_H_