This is an archive of the discontinued LLVM Phabricator instance.

[mlir][spirv] Introduce spv.function
ClosedPublic

Authored by antiagainst on Feb 7 2020, 8:42 AM.

Details

Summary

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.function 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.

The operation is explicitly named differently than func op to
avoid potential confusion and symbol collision.

Diff Detail

Event Timeline

antiagainst created this revision.Feb 7 2020, 8:42 AM

I would actually prefer that we call this FuncOp('func'). It is consistent with all of the other functions that have been defined, and seems a little weird to deviate. For example, spirv::ModuleOp/ConstantOp are also named the same as other common ops.

mlir/include/mlir/Dialect/SPIRV/SPIRVOps.h
49

Could we auto-generate these in OpDefGen?

mlir/lib/Dialect/SPIRV/SPIRVOps.cpp
79

if (auto funcOp = ...)

return funcOp.getType();

return FunctionType();

?

1724

Drop trivial brace.

I would actually prefer that we call this FuncOp('func'). It is consistent with all of the other functions that have been defined, and seems a little weird to deviate.

+1

mlir/include/mlir/Dialect/SPIRV/SPIRVStructureOps.td
277

The 4 methods above all seem like they could be added to FunctionLike trait?

287

getType().getNumResults(); is shorter?

mlir/lib/Dialect/SPIRV/SPIRVOps.cpp
1687

Seems like this should be checked in verifyType()?

I'd expect verifyBody to actually find the return ops and check for the matching, as well as other possible SPIRV specific properties.

mravishankar requested changes to this revision.Feb 8 2020, 2:29 PM

THanks for taking this one now. Its already touching so much code, if we left it for longer it would be more effort to use spv.function

mlir/include/mlir/Dialect/SPIRV/SPIRVStructureOps.td
251

I think we can leave hasOpcode = 1. The autogenSerialization = 0 should avoid the serialization method to from being autogenerated.

mlir/lib/Conversion/StandardToSPIRV/ConvertStandardToSPIRVPass.cpp
40–41

I dont think we need this. This is only for testing. So not adding this, but leaving the conversion as applyPartialConversion is fine.

mlir/lib/Dialect/SPIRV/SPIRVOps.cpp
2525

I dont think we should do this. spv.return must stay in spv.func. If for testing we can use func and return and the body can have spv.* operations. We just need to not do applyFullConversion and it should be fine.

mlir/lib/Dialect/SPIRV/Serialization/Serializer.cpp
1645

If you set hasOpCode = 1 in the SPIRVBase.td this should be generated by ODS automatically.

This revision now requires changes to proceed.Feb 8 2020, 2:29 PM
antiagainst marked 17 inline comments as done.

Address comments

I would actually prefer that we call this FuncOp('func'). It is consistent with all of the other functions that have been defined, and seems a little weird to deviate. For example, spirv::ModuleOp/ConstantOp are also named the same as other common ops.

The purpose was trying to avoid potential confusions between std ones but you are right this is already happening for other SPIR-V ops. So consistency wins. Done.

mlir/include/mlir/Dialect/SPIRV/SPIRVOps.h
49

Yup we can. But do we want to do that for each op? With so many ops having this specialization, it can increase C++ compilers' burden quite a bit. I'd defer that to when there are more ops wanting this.

mlir/include/mlir/Dialect/SPIRV/SPIRVStructureOps.td
251

We cannot here because this is not a direct mapping.

277

Other function-like ops (like gpu.func and llvm.func) might want slightly different behavior for some of the ops but yes this can be moved to FunctionLike to be a default impl. Done.

mlir/lib/Conversion/StandardToSPIRV/ConvertStandardToSPIRVPass.cpp
40–41

It's tricky here. We need to pull in func to spv.func conversion so that we can materialize function signature conversion, which is needed for load/store op conversion in subview tests. Once that pattern is pulled in, it might cause that some test IR is in an invalid state. For example, mulf %arg, %arg: tensor<4xf32> won't be converted because we don't have a pattern to convert binary ops that require type conversion. But the %arg will be converted anyway because of func to spv.func change. So we will see a validation failure on mulf. I was trying to address this by turning the pass to do full conversion here given this pass is mainly for testing so we don't care that much. I think another way to address this is to disable the test for mulf %arg, %arg: tensor<4xf32>. It's actually just a negative test that checks we are missing a pattern. Done.

mlir/lib/Dialect/SPIRV/SPIRVOps.cpp
1687

Good points. Moved this check to verifyBody and bumped checks for return ops here.

2525

Bumped the check to spv.function so we don't need special-casing the enclosing function types here. But yes it's allowing return ops to be used at more places, which is more friendly to interop between different dialects. If ops from other dialects would like to use SPIR-V return ops then it's their responsibility to make sure the semantics match with their cases. This is consistent with how we handle ops within SPIR-V dialect: we allow non-region ops to be mixable with other dialects but we enforce checks in region ops (like spv.module, spv.func) to verify SPIR-V semantics at the scope of those ops.

More reasons of allowing return ops to be interop with other dialect ops: ideally I'd like to enforce the spv.func op to verify its region to only contain SPIR-V ops, and I'd like to make the func to spv.func op conversion to only kick in when a func op's region already only contains SPIR-V ops. That means, we need to allow other ops that can be placed inside functions to be converted first. If instead we require SPIR-V return ops to only work with spv.func, that causes a circular dependency... Theoretically it can be solved by throwing all patterns in the same pass maybe, but I'm not sure we want to go that far right now. Besides, func and std.return actually are in different dialects and likely we will have pass boundaries between their patterns anyway.

mlir/lib/Dialect/SPIRV/Serialization/Serializer.cpp
1645

We cannot here because this is not a direct mapping: the op is not named as spv.Function; it was named spv.function and now is spv.func.

mravishankar accepted this revision.Feb 10 2020, 10:01 AM

Thanks Lei! Accepting conditioned on addressing other comments.

mlir/include/mlir/Dialect/SPIRV/SPIRVStructureOps.td
251

Does it not map directly to OpFunction . If you name the op spv.Function , then the ODS will generate the dispatch for the serialization and deserialization. You just need to specialize the implementation of processOp in the (de)serializer for this to work. (See comment here : https://github.com/llvm/llvm-project/blob/master/mlir/lib/Dialect/SPIRV/Serialization/Serializer.cpp#L1664)
Anyway, this would be nice to have, is not necessary

mlir/lib/Dialect/SPIRV/Serialization/Serializer.cpp
1645

Why not name it spv.Function ? Curious. Wont make it a requirement. THis is fine too.

rriddle added inline comments.Feb 11 2020, 2:23 PM
mlir/include/mlir/Dialect/SPIRV/SPIRVOps.h
48

///

49

This needs to be formatted.

mlir/include/mlir/IR/FunctionSupport.h
148

None of these are valid if the type of the function isn't the builtin FunctionType, i.e. LLVMFuncOp doesn't work with any of these methods.

mlir/lib/Dialect/SPIRV/SPIRVOps.cpp
1693

You can return the errors directly. failure implicitly converts to WalkResult::interrupt. You just need to set the return type on the lambda to WalkResult.

rriddle requested changes to this revision.Feb 11 2020, 2:27 PM
rriddle added inline comments.
mlir/include/mlir/IR/FunctionSupport.h
148

If you want to add these, the things that rely on FunctionType should be moved to a separate section and there should be disclaimers/comments on the top-level comment of FunctionLike denoting that these methods need to be hidden/overridden if the underlying type isn't FunctionType. You should also make sure that these are all hidden for LLVMFuncOp.

This revision now requires changes to proceed.Feb 11 2020, 2:27 PM
rriddle added inline comments.Feb 11 2020, 2:33 PM
mlir/include/mlir/IR/FunctionSupport.h
148

An alternative that may work is to static assert that the type returned by std::declval<ConcreteType>().getType() is equal to FunctionType inside of each of those methods.

rriddle accepted this revision.Feb 11 2020, 2:36 PM

LGTM after comments are resolved.

This revision is now accepted and ready to land.Feb 11 2020, 2:36 PM
antiagainst marked 10 inline comments as done.

Address comments

mlir/include/mlir/Dialect/SPIRV/SPIRVOps.h
49

This is actually the result after running format fix internally. Maybe because clang-format version differences.

mlir/include/mlir/Dialect/SPIRV/SPIRVStructureOps.td
251

OpFunction is just one instruction composing the whole function; others include OpFunctionParameter, OpFunctionEnd. spv.func here actually includes all of them. We have been following the convention to use lower case names for such cases.

mlir/include/mlir/IR/FunctionSupport.h
148

Yup. But won't this still be good defaults for other func ops (std, gpu, spv)? llvm side one can hide these methods. (And that's what happening per the current impl.) Let me know if this is okay, I can always put these back and copy on the SPIR-V side.

mlir/lib/Dialect/SPIRV/SPIRVOps.cpp
1693

Oh, that's nice! Thanks for the suggestion! Changed.

mlir/lib/Dialect/SPIRV/Serialization/Serializer.cpp
1645

Explained at another place.

antiagainst marked an inline comment as done.Feb 11 2020, 2:50 PM
antiagainst added inline comments.
mlir/include/mlir/IR/FunctionSupport.h
148

Oh, replied before seeing your new comments. Sure, I'll address these. :)

antiagainst marked 3 inline comments as done.

Add comments on FunctionLike

antiagainst marked an inline comment as done.Feb 11 2020, 3:58 PM
antiagainst added inline comments.
mlir/include/mlir/IR/FunctionSupport.h
148

Added comments to explain for now. Tried the following

static_assert(
    std::is_same<FunctionType,
                 decltype(std::declval<ConcreteType>().getType())>::value,
    "must be hidden by the concrete type");

But didn't seem to guard anything. (Removing the LLVM overrides still can compile successfully.)

mehdi_amini added inline comments.Feb 11 2020, 9:51 PM
mlir/include/mlir/Dialect/SPIRV/SPIRVOps.h
49

Google internal does not honor .clang-format files: source of truth is the bot at the moment. Please use git clang-format

antiagainst marked 2 inline comments as done.Feb 12 2020, 4:45 AM
antiagainst added inline comments.
mlir/include/mlir/Dialect/SPIRV/SPIRVOps.h
49

Oh that's good to know! Thanks Mehdi! I formatted this.

This revision was automatically updated to reflect the committed changes.
antiagainst marked an inline comment as done.