[1/2] [MooreToCore] Lower symbolic vtables to LLVM globals#10129
[1/2] [MooreToCore] Lower symbolic vtables to LLVM globals#10129
Conversation
d221c96 to
f126a93
Compare
|
Results of circt-tests run for f126a93 compared to results for 82f17f0: sv-testsChanges in emitted diagnostics:
|
|
Is |
We lowered a "placeholder malloc" for |
|
89ec546 to
e9b4693
Compare
Materialize Moore vtables as LLVM globals and convert referenced method bodies to LLVM-addressable functions on demand while keeping `func.func` functions for any methods not involved in virtual dispatch. This adds cached vtable metadata, flattens nested `moore.vtable` hierarchies into a concrete slot order, and emits one LLVM global per class vtable with real function-pointer entries. When a slot target is still a `func.func`, convert it to `llvm.func` before taking its address so the vtable initializer contains valid LLVM function pointers. [MooreToCore] Narrow vtable lowering integration
e9b4693 to
c67540b
Compare
fabianschuiki
left a comment
There was a problem hiding this comment.
Really cool! Just one concern regarding the infectiousness of LLVM 😬
| OpBuilder::InsertionGuard guard(rewriter); | ||
| rewriter.setInsertionPoint(funcOp); | ||
| auto converted = convertFuncOpToLLVMFuncOp( | ||
| funcOp, rewriter, typeConverter, &symbolTables); | ||
| if (failed(converted)) | ||
| return failure(); | ||
| llvmFunc = *converted; | ||
| rewriter.eraseOp(funcOp); |
There was a problem hiding this comment.
This conversion from func.func to llvm.func feels a bit weird, because it only converts a handful of functions that happen to be involved in a class vtable. What happens if the function is not a function but a task, or if it contains LLHD and other ops that interact with an event queue? The fact that the LLVM forces us to unravel most of the IR at this early stage is a bit of a red flag -- we might need another dialect layer in between here. (At the CIRCT core dialects we're still dealing with hardware concerns and higher-level simulation semantics; LLVM might be too low level for some of that 🤔.)
Does the llvm.mlir.addressof op enforce that the target is a global variable or a function? If it does, we might want to try and defer lowering to LLVM until a later point, and see if we can't build up vtables with other MLIR dialects instead of LLVM. Maybe the func.constant op could help create SSA values with a function type, which we could store in a struct and later call with func.call_indirect? We'll probably also need an equivalent for tasks/coroutines.
There was a problem hiding this comment.
Really good point about the tasks! Hadn't really thought about that yet 🤔
I think the "natural" representation of a task at this level would be a coroutine - in that way there would also be a LLVM structure to represent it.
I had tinkered with staying with func.func for as long as possible but ran into issues with materialising the vtable since I didn't find a way to properly indirect function calls with call.indirect (Hadn't had a look at func.constant, though🤦)
From my point of view this approach also doesn't really have to unravel any IR except for the direct lowering of func.func to llvm.func (mainly so I can get a function pointer to save into a dispatch table). The llvm.func body contains "the same things" a func.func would. In that sense it's definitely worth trying with func.constant, though - would feel like a nicer solution 👍
There was a problem hiding this comment.
🥳 My suspicion/fear is that once the outer op is an llvm.func, we'll start inheriting constraints from it -- maybe certain terminators no longer work, yield doesn't work (LLVM coroutines aren't really what we want in CIRCT), and things like llvm.call might not work in all contexts. LLVM feels a lot like an egress dialect, which really wants an all-LLVM-or-nothing setup. If we run into trouble with func.call_indirect or the vtables, we could consider extending the Sim dialect with a corresponding construct that then has a trivial lowering to the LLVM you're generating here... WDYT?
There was a problem hiding this comment.
I think if I manage to tickle those func.constant operators juuuust the right way and potentially materialise vtables a bit later (i.e between MooreToCore and convert-to-llvm) we might just get "de Foifer und 's weggli" with this one 👀
We might then consider legalising moore.vtable.* for core dialects, so we have a structure to record table entry order. But I guess that's a very small price to pay and can be worked around.
There was a problem hiding this comment.
Nothing hits quite like de Foifer und 's weggli! 😏 5️⃣ 🥖
About that moore.vtable.* legalization: one of the things you could do is place an op in between the LLVM lowering you were targeting in this PR, and the original moore.vtable.* ops. Something like sim.vtable that is just the flat, distilled-down list of functions that can be called, addressed by index. Something that has an almost trivial 1:1 lowering to LLVM. That allows you to hand-off the Moore-level abstraction into something that's closer to the target while we're at the core dialect level, and then there's a final mechanical switch in convert-to-llvm.
Materialize Moore vtables as LLVM globals and convert referenced method
bodies to LLVM-addressable functions on demand while keeping
func.funcfunctions for any methods not involved in virtual dispatch.
This adds cached vtable metadata, flattens nested
moore.vtablehierarchies into a concrete slot order, and emits one LLVM global per
class vtable with real function-pointer entries. When a slot target is
still a
func.func, convert it tollvm.funcbefore taking its addressso the vtable initializer contains valid LLVM function pointers.
This PR is mainly structural; I expect there will be coverage holes once dispatch functions
contain actually useful code. Consider this PR a step 1 towards full virtual dispatch :)