Commit d3e7816d authored by Lei Zhang's avatar Lei Zhang
Browse files

[mlir][spirv] Introduce spv.func

Thus far we have been using builtin func op to model SPIR-V functions.
It was because builtin func op used to have special treatment in
various parts of the core codebase (e.g., pass pipelines, etc.) and
it's easy to bootstrap the development of the SPIR-V dialect. But
nowadays with general op concepts and region support we don't have
such limitations and it's time to tighten the SPIR-V dialect for
completeness.

This commits introduces a spv.func op to properly model SPIR-V
functions. Compared to builtin func op, it can provide the following
benefits:

* We can control the full op so we can integrate SPIR-V information
  bits (e.g., function control) in a more integrated way and define
  our own assembly form and enforcing better verification.
* We can have a better dialect and library boundary. At the current
  moment only functions are modelled with an external op. With this
  change, all ops modelling SPIR-V concpets will be spv.* ops and
  registered to the SPIR-V dialect.
* We don't need to special-case func op anymore when creating
  ConversionTarget declaring SPIR-V dialect as legal. This is quite
  important given we'll see more and more conversions in the future.

In the process, bumps a few FuncOp methods to the FunctionLike trait.

Differential Revision: https://reviews.llvm.org/D74226
parent ffeb64db
Loading
Loading
Loading
Loading
+0 −5
Original line number Diff line number Diff line
@@ -128,11 +128,6 @@ def GPU_GPUFuncOp : GPU_Op<"func", [FunctionLike, IsolatedFromAbove, Symbol]> {
             nullptr;
    }

    /// Returns the type of the function this Op defines.
    FunctionType getType() {
      return getTypeAttr().getValue().cast<FunctionType>();
    }

    /// Change the type of this function in place. This is an extremely
    /// dangerous operation and it is up to the caller to ensure that this is
    /// legal for this function, and to restore invariants:
+4 −2
Original line number Diff line number Diff line
@@ -25,7 +25,7 @@ namespace mlir {
/// For composite types, this converter additionally performs type wrapping to
/// satisfy shader interface requirements: shader interface types must be
/// pointers to structs.
class SPIRVTypeConverter final : public TypeConverter {
class SPIRVTypeConverter : public TypeConverter {
public:
  using TypeConverter::TypeConverter;

@@ -59,6 +59,7 @@ void populateBuiltinFuncToSPIRVPatterns(MLIRContext *context,

namespace spirv {
class AccessChainOp;
class FuncOp;

class SPIRVConversionTarget : public ConversionTarget {
public:
@@ -104,7 +105,8 @@ spirv::AccessChainOp getElementPtr(SPIRVTypeConverter &typeConverter,

/// Sets the InterfaceVarABIAttr and EntryPointABIAttr for a function and its
/// arguments.
LogicalResult setABIAttrs(FuncOp funcOp, EntryPointABIAttr entryPointInfo,
LogicalResult setABIAttrs(spirv::FuncOp funcOp,
                          EntryPointABIAttr entryPointInfo,
                          ArrayRef<InterfaceVarABIAttr> argABIInfo);
} // namespace spirv
} // namespace mlir
+36 −0
Original line number Diff line number Diff line
@@ -43,4 +43,40 @@ namespace spirv {
} // end namespace spirv
} // end namespace mlir

namespace llvm {

/// spirv::Function ops hash just like pointers.
template <>
struct DenseMapInfo<mlir::spirv::FuncOp> {
  static mlir::spirv::FuncOp getEmptyKey() {
    auto pointer = llvm::DenseMapInfo<void *>::getEmptyKey();
    return mlir::spirv::FuncOp::getFromOpaquePointer(pointer);
  }
  static mlir::spirv::FuncOp getTombstoneKey() {
    auto pointer = llvm::DenseMapInfo<void *>::getTombstoneKey();
    return mlir::spirv::FuncOp::getFromOpaquePointer(pointer);
  }
  static unsigned getHashValue(mlir::spirv::FuncOp val) {
    return hash_value(val.getAsOpaquePointer());
  }
  static bool isEqual(mlir::spirv::FuncOp LHS, mlir::spirv::FuncOp RHS) {
    return LHS == RHS;
  }
};

/// Allow stealing the low bits of spirv::Function ops.
template <>
struct PointerLikeTypeTraits<mlir::spirv::FuncOp> {
public:
  static inline void *getAsVoidPointer(mlir::spirv::FuncOp I) {
    return const_cast<void *>(I.getAsOpaquePointer());
  }
  static inline mlir::spirv::FuncOp getFromVoidPointer(void *P) {
    return mlir::spirv::FuncOp::getFromOpaquePointer(P);
  }
  static constexpr int NumLowBitsAvailable = 3;
};

} // namespace llvm

#endif // MLIR_DIALECT_SPIRV_SPIRVOPS_H_
+1 −1
Original line number Diff line number Diff line
@@ -210,7 +210,7 @@ def SPV_ExecutionModeOp : SPV_Op<"ExecutionMode", [InModuleScope]> {
  let autogenSerialization = 0;

  let builders = [OpBuilder<[{Builder *builder, OperationState &state,
                              FuncOp function,
                              spirv::FuncOp function,
                              spirv::ExecutionMode executionMode,
                              ArrayRef<int32_t> params}]>];
}
+104 −4
Original line number Diff line number Diff line
@@ -15,8 +15,11 @@
#ifndef SPIRV_STRUCTURE_OPS
#define SPIRV_STRUCTURE_OPS

include "mlir/Analysis/CallInterfaces.td"
include "mlir/Dialect/SPIRV/SPIRVBase.td"

// -----

def SPV_AddressOfOp : SPV_Op<"_address_of", [InFunctionScope, NoSideEffect]> {
  let summary = "Get the address of a global variable.";

@@ -61,6 +64,8 @@ def SPV_AddressOfOp : SPV_Op<"_address_of", [InFunctionScope, NoSideEffect]> {
  let assemblyFormat = "$variable attr-dict `:` type($pointer)";
}

// -----

def SPV_ConstantOp : SPV_Op<"constant", [NoSideEffect]> {
  let summary = "The op that declares a SPIR-V normal constant";

@@ -125,6 +130,8 @@ def SPV_ConstantOp : SPV_Op<"constant", [NoSideEffect]> {
  let autogenSerialization = 0;
}

// -----

def SPV_EntryPointOp : SPV_Op<"EntryPoint", [InModuleScope]> {
  let summary = [{
    Declare an entry point, its execution model, and its interface.
@@ -182,10 +189,93 @@ def SPV_EntryPointOp : SPV_Op<"EntryPoint", [InModuleScope]> {

  let builders = [OpBuilder<[{Builder *builder, OperationState &state,
                              spirv::ExecutionModel executionModel,
                              FuncOp function,
                              spirv::FuncOp function,
                              ArrayRef<Attribute> interfaceVars}]>];
}

// -----

def SPV_FuncOp : SPV_Op<"func", [
    DeclareOpInterfaceMethods<CallableOpInterface>,
    FunctionLike, InModuleScope, IsolatedFromAbove, Symbol
  ]> {
  let summary = "Declare or define a function";

  let description = [{
    This op declares or defines a SPIR-V function using one region, which
    contains one or more blocks.

    Different from the SPIR-V binary format, this op is not allowed to
    implicitly capture global values, and all external references must use
    function arguments or symbol references. This op itself defines a symbol
    that is unique in the enclosing module op.

    This op itself takes no operands and generates no results. Its region
    can take zero or more arguments and return zero or one values.

    ### Custom assembly form

    ```
    spv-function-control ::= "None" | "Inline" | "DontInline" | ...
    spv-function-op ::= `spv.func` function-signature
                         spv-function-control region
    ```

    For example:

    ```
    spv.func @foo() -> () "None" { ... }
    spv.func @bar() -> () "Inline|Pure" { ... }
    ```
  }];

  let arguments = (ins
    TypeAttr:$type,
    StrAttr:$sym_name,
    SPV_FunctionControlAttr:$function_control
  );

  let results = (outs);

  let regions = (region AnyRegion:$body);

  let verifier = [{ return success(); }];

  let builders = [OpBuilder<[{
    Builder *, OperationState &state,
    StringRef name, FunctionType type,
    spirv::FunctionControl control = spirv::FunctionControl::None,
    ArrayRef<NamedAttribute> attrs = {}
  }]>];

  let hasOpcode = 0;

  let autogenSerialization = 0;

  let extraClassDeclaration = [{
  private:
    // This trait needs access to the hooks defined below.
    friend class OpTrait::FunctionLike<FuncOp>;

    /// Returns the number of arguments. Hook for OpTrait::FunctionLike.
    unsigned getNumFuncArguments() { return getType().getNumInputs(); }

    /// Returns the number of results. Hook for OpTrait::FunctionLike.
    unsigned getNumFuncResults() { return getType().getNumResults(); }

    /// Hook for OpTrait::FunctionLike, called after verifying that the 'type'
    /// attribute is present and checks if it holds a function type. Ensures
    /// getType, getNumFuncArguments, and getNumFuncResults can be called safely
    LogicalResult verifyType();

    /// Hook for OpTrait::FunctionLike, called after verifying the function
    /// type and the presence of the (potentially empty) function body.
    /// Ensures SPIR-V specific semantics.
    LogicalResult verifyBody();
  }];
}

// -----

def SPV_GlobalVariableOp : SPV_Op<"globalVariable", [InModuleScope, Symbol]> {
  let summary = [{
@@ -238,6 +328,8 @@ def SPV_GlobalVariableOp : SPV_Op<"globalVariable", [InModuleScope, Symbol]> {
    OptionalAttr<FlatSymbolRefAttr>:$initializer
  );

  let results = (outs);

  let builders = [
    OpBuilder<"Builder *builder, OperationState &state, "
      "TypeAttr type, ArrayRef<NamedAttribute> namedAttrs", [{
@@ -251,8 +343,6 @@ def SPV_GlobalVariableOp : SPV_Op<"globalVariable", [InModuleScope, Symbol]> {
                Type type, StringRef name, spirv::BuiltIn builtin}]>
  ];

  let results = (outs);

  let hasOpcode = 0;

  let autogenSerialization = 0;
@@ -264,10 +354,12 @@ def SPV_GlobalVariableOp : SPV_Op<"globalVariable", [InModuleScope, Symbol]> {
  }];
}

// -----

def SPV_ModuleOp : SPV_Op<"module",
                          [IsolatedFromAbove,
                           SingleBlockImplicitTerminator<"ModuleEndOp">,
                           NativeOpTrait<"SymbolTable">]> {
                           SymbolTable]> {
  let summary = "The top-level op that defines a SPIR-V module";

  let description = [{
@@ -351,6 +443,8 @@ def SPV_ModuleOp : SPV_Op<"module",
  }];
}

// -----

def SPV_ModuleEndOp : SPV_Op<"_module_end", [InModuleScope, Terminator]> {
  let summary = "The pseudo op that ends a SPIR-V module";

@@ -374,6 +468,8 @@ def SPV_ModuleEndOp : SPV_Op<"_module_end", [InModuleScope, Terminator]> {
  let autogenSerialization = 0;
}

// -----

def SPV_ReferenceOfOp : SPV_Op<"_reference_of", [NoSideEffect]> {
  let summary = "Reference a specialization constant.";

@@ -414,6 +510,8 @@ def SPV_ReferenceOfOp : SPV_Op<"_reference_of", [NoSideEffect]> {
  let assemblyFormat = "$spec_const attr-dict `:` type($reference)";
}

// -----

def SPV_SpecConstantOp : SPV_Op<"specConstant", [InModuleScope, Symbol]> {
  let summary = "The op that declares a SPIR-V specialization constant";

@@ -461,4 +559,6 @@ def SPV_SpecConstantOp : SPV_Op<"specConstant", [InModuleScope, Symbol]> {
  let autogenSerialization = 0;
}

// -----

#endif // SPIRV_STRUCTURE_OPS
Loading