Skip to content

[Moore][ImportVerilog] Model $cast with dyn_cast and success flag#10156

Draft
sunhailong2001 wants to merge 1 commit intollvm:mainfrom
sunhailong2001:hailong/cast-builtin
Draft

[Moore][ImportVerilog] Model $cast with dyn_cast and success flag#10156
sunhailong2001 wants to merge 1 commit intollvm:mainfrom
sunhailong2001:hailong/cast-builtin

Conversation

@sunhailong2001
Copy link
Copy Markdown
Contributor

This change adds AggregateType and SingularType, and extends the moore.builtin.dyn_cast (CastBIOp) op with a 1-bit success flag to model $cast. And each $cast will be lowered into dyn_cast + blocking_assign ops.

@sunhailong2001 sunhailong2001 marked this pull request as draft April 8, 2026 16:49
@sunhailong2001
Copy link
Copy Markdown
Contributor Author

Hey, @fabianschuiki. 😃 If my thought is accepted, I will add more tests.

@fabianschuiki
Copy link
Copy Markdown
Contributor

fabianschuiki commented Apr 9, 2026

Hey @sunhailong2001! I love your idea of doing casting and assignment in separate operations, that's very elegant. The dynamic casting is an interesting challenge. I see a few different groups that a $cast can fall into:

  1. Legal conversions like int to real. These are the ones for which materializeConversion would return success() and implement the cast with various specialized conversion ops. We know at compile time that the cast works.
  2. Illegal conversions like int to queue. These are the ones for which materializeConversion would return failure() and report an error. We know at compile time that the cast fails.
  3. Upcasting from a subclass to a superclass. I'm not sure whether we handle this yet. (@Scheremo might know more.) Since casting to a superclass is always legal, we know at compile time that the cast works.
  4. Casting from a class to an unrelated class. We know at compile time that the cast fails.
  5. Downcasting from a superclass to a subclass. (@Scheremo is currently working on supporting this in his class/vtable implementation.) This is the truly dynamic cast, where we only know at runtime if it works or not.

Groups 1-4 are all static casts: we know at compile time whether they succeed or not. To implement these, we could directly call materializeConversion. At the moment, that function reports errors when the conversion fails, which is the correct thing to do almost everywhere. But we could add a bool fallible = false argument to the function and make it not report errors if the conversion fails; instead, it should just indicate a conversion failure by returning Value{} (which it already does anyway). To implement the casting of $cast for groups 1-4, we could call materializeConversion with fallible = true, and do one of two things:

  • If the cast succeeds, make the blocking assignment and create a moore.constant true op as the result of $cast.
  • If the cast fails, don't make the assignment and create a moore.constant false op as the result of $cast.

Group 5 is the truly dynamic one. When we see that a $cast goes from superclass to subclass, we can emit the dynamic cast that @Scheremo is working on, and use its boolean result as the result of $cast. If that's true, we also make the blocking assignment.

WDYT? Does it make sense to separate $cast into separate categories, and eagerly handle the cases that are static directly in ImportVerilog?

@Scheremo
Copy link
Copy Markdown
Contributor

Scheremo commented Apr 9, 2026

+1 for the idea of trying to implement dynamic casts statically at compile time! Imo this could also be left for a follow-up.

With regards to class-typed cast I think this work could be orthogonalised. We would likely just have to exclude ClassHandleType from SingularType. Imo keeping class instances casting separate is a good call, as statically inferable "upcasts" are already implemented and "downcasts" want some additional info (RTTI of the instance).

@fabianschuiki
Copy link
Copy Markdown
Contributor

That sounds like we could do almost all casting statically with the existing materializeConversion machinery (does that also do class upcasts @Scheremo?), without needing a separate moore.dyn_cast operation. If we handle all statically-known casts and only class downcasts remain, we could skip downcasts in this PR and add them later once @Scheremo's work on the vtables is done. What do you guys think?

@Scheremo
Copy link
Copy Markdown
Contributor

Scheremo commented Apr 9, 2026

That sounds like we could do almost all casting statically with the existing materializeConversion machinery (does that also do class upcasts @Scheremo?), without needing a separate moore.dyn_cast operation. If we handle all statically-known casts and only class downcasts remain, we could skip downcasts in this PR and add them later once @Scheremo's work on the vtables is done. What do you guys think?

Upcasts generally go through materializeConversion but there might be some sharp edges. Definitely supposed to work though!

I agree with the suggestion. I'm sure we'll get vtables materialised one of these days 🚀

Implement statically inferable $cast lowering through materializeConversion. Use fallible conversion to produce the $cast success flag: emit blocking assignment only on success, and return i1 true/false accordingly.
Comment on lines +5170 to +5197
// CHECK-LABEL: func.func private @DynCastRealToInt(
// CHECK-SAME: %arg0: !moore.i32
// CHECK-SAME: ) -> !moore.i32 {
// CHECK: [[A:%.+]] = moore.variable %arg0 : <i32>
// CHECK: [[V1:%.+]] = moore.variable : <i32>
// CHECK: [[CR:%.+]] = moore.constant_real 2.300000e+00 : f64
// CHECK: [[REAL_TO_INT:%.+]] = moore.real_to_int [[CR]] : f64 -> i32
// CHECK: moore.blocking_assign [[A]], [[REAL_TO_INT]] : i32
// CHECK: [[SUCC:%.+]] = moore.constant 1 : i1
// CHECK: [[READ:%.+]] = moore.read [[V1]] : <i32>
// CHECK: return [[READ]] : !moore.i32
// CHECK: }

function int DynCastRealToInt(int a);
$cast(a, 2.3);
endfunction

// CHECK-LABEL: moore.coroutine private @DynCastRealToString() {
// CHECK: [[S:%.+]] = moore.variable : <string>
// CHECK: [[CR:%.+]] = moore.constant_real 2.300000e+00 : f64
// CHECK: [[FAIL:%.+]] = moore.constant 0 : i1
// CHECK: moore.return
// CHECK: }

task DynCastRealToString;
string s;
$cast(s, 2.3);
endtask
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.

Really cool! 🥳 You could try to use inout a and return $cast(...); to make the pass/fail of the cast show up in the return IR op, and to treat a as a reference that can be directly assigned to (that might remove a few helper variables in the IR and make it easier to write CHECK lines.)

@fabianschuiki
Copy link
Copy Markdown
Contributor

Your fallible conversion changes look fantastic @sunhailong2001! 🥳

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants