Skip to content

[1/2] [MooreToCore] Lower symbolic vtables to LLVM globals#10129

Draft
Scheremo wants to merge 1 commit intollvm:mainfrom
Scheremo:pr-class-vtable-3
Draft

[1/2] [MooreToCore] Lower symbolic vtables to LLVM globals#10129
Scheremo wants to merge 1 commit intollvm:mainfrom
Scheremo:pr-class-vtable-3

Conversation

@Scheremo
Copy link
Copy Markdown
Contributor

@Scheremo Scheremo commented Apr 5, 2026

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.

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 :)

@Scheremo Scheremo force-pushed the pr-class-vtable-3 branch 3 times, most recently from d221c96 to f126a93 Compare April 5, 2026 18:27
@Scheremo Scheremo changed the title DRAFT: [3/4] [MooreToCore] Materialize class RTTI globals DRAFT: [3/4] [MooreToCore] Lower symbolic vtables to LLVM globals Apr 5, 2026
@fabianschuiki
Copy link
Copy Markdown
Contributor

Results of circt-tests run for f126a93 compared to results for 82f17f0:

sv-tests

Changes in emitted diagnostics:

  • +1 total change
  • -4 error: failed to legalize operation 'moore.vtable'
  • +2 error: 'llvm.return' op operand #0 must be LLVM dialect-compatible type, but got '!sim.queue<i32, 0>'
  • -1 error: failed to legalize operation 'func.call'
  • +1 error: 'llvm.call' op 'malloc' does not reference a valid LLVM function or IFunc
  • +1 error: 'llvm.call' op result #0 must be LLVM dialect-compatible type, but got '!sim.queue<i32, 0>'
  • +1 error: failed to legalize operation 'moore.fmt.string'
  • +1 error: failed to legalize operation 'moore.vtable.load_method'

@tobiasgrosser
Copy link
Copy Markdown
Contributor

Is 1 error: 'llvm.call' op 'malloc' does not reference a valid LLVM function or IFunc something to worry about?

@Scheremo
Copy link
Copy Markdown
Contributor Author

Scheremo commented Apr 6, 2026

Is 1 error: 'llvm.call' op 'malloc' does not reference a valid LLVM function or IFunc something to worry about?

We lowered a "placeholder malloc" for moore.class.new in a previous PR. Looks like linkage doesn't quite work out, but should be fixable.

@Scheremo
Copy link
Copy Markdown
Contributor Author

Scheremo commented Apr 6, 2026

Is 1 error: 'llvm.call' op 'malloc' does not reference a valid LLVM function or IFunc something to worry about?

We lowered a "placeholder malloc" for moore.class.new in a previous PR. Looks like linkage doesn't quite work out, but should be fixable.

Addressed here

@Scheremo Scheremo force-pushed the pr-class-vtable-3 branch 2 times, most recently from 89ec546 to e9b4693 Compare April 9, 2026 07:20
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
@Scheremo Scheremo force-pushed the pr-class-vtable-3 branch from e9b4693 to c67540b Compare April 9, 2026 07:23
@Scheremo Scheremo marked this pull request as ready for review April 9, 2026 07:23
@Scheremo Scheremo changed the title DRAFT: [3/4] [MooreToCore] Lower symbolic vtables to LLVM globals [1/3] [MooreToCore] Lower symbolic vtables to LLVM globals Apr 9, 2026
@Scheremo Scheremo changed the title [1/3] [MooreToCore] Lower symbolic vtables to LLVM globals [1/2] [MooreToCore] Lower symbolic vtables to LLVM globals Apr 9, 2026
Copy link
Copy Markdown
Contributor

@fabianschuiki fabianschuiki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really cool! Just one concern regarding the infectiousness of LLVM 😬

Comment on lines +345 to +352
OpBuilder::InsertionGuard guard(rewriter);
rewriter.setInsertionPoint(funcOp);
auto converted = convertFuncOpToLLVMFuncOp(
funcOp, rewriter, typeConverter, &symbolTables);
if (failed(converted))
return failure();
llvmFunc = *converted;
rewriter.eraseOp(funcOp);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 👍

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🥳 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?

Copy link
Copy Markdown
Contributor Author

@Scheremo Scheremo Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@Scheremo Scheremo marked this pull request as draft April 9, 2026 18:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants