Skip to content

[Moore][ImportVerilog] Implement static $cast via materializeConversion#10156

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

[Moore][ImportVerilog] Implement static $cast via materializeConversion#10156
sunhailong2001 wants to merge 1 commit intollvm:mainfrom
sunhailong2001:hailong/cast-builtin

Conversation

@sunhailong2001
Copy link
Copy Markdown
Contributor

@sunhailong2001 sunhailong2001 commented Apr 8, 2026

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.

@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 🚀

Comment thread test/Conversion/ImportVerilog/basic.sv Outdated
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.)

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.

Done

@fabianschuiki
Copy link
Copy Markdown
Contributor

Your fallible conversion changes look fantastic @sunhailong2001! 🥳

@sunhailong2001 sunhailong2001 changed the title [Moore][ImportVerilog] Model $cast with dyn_cast and success flag [Moore][ImportVerilog] Implement static $cast via materializeConversion Apr 15, 2026
@sunhailong2001
Copy link
Copy Markdown
Contributor Author

sunhailong2001 commented Apr 15, 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).

I checked the LLVM IR for C++ dynamic_cast, RTTI is necessary :+1. I oversimplified this problem.

@sunhailong2001 sunhailong2001 marked this pull request as ready for review April 15, 2026 13:58
@sunhailong2001 sunhailong2001 force-pushed the hailong/cast-builtin branch 2 times, most recently from 7b8b3b0 to c3551b1 Compare April 15, 2026 14:54

if (dyn_cast<moore::IntType>(type).getDomain() == moore::Domain::FourValued)
return materializePackedToSBVConversion(*this, twoValInt, loc);
return builder.createOrFold<moore::IntToLogicOp>(loc, twoValInt);
Copy link
Copy Markdown
Contributor Author

@sunhailong2001 sunhailong2001 Apr 15, 2026

Choose a reason for hiding this comment

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

Please check function int DynCastRealToLogic(inout a) in the test, materializePackedToSBVConversion will directly return twoValInt without conversion. Therefore, there is not moore.int_to_logic when translate $cast(a, 2.3), and then it will report an error: 'moore.blocking_assign' op failed to verify that src and dst types match

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 is an interesting find! You might be able to materializeConversion here to do the domain casting and any potential simple-bit-vector unpacking automatically. Something like this:

if (dstInt && isa<moore::RealType>(value.getType())) {
    auto twoValInt = builder.createOrFold<moore::RealToIntOp>(
        loc, dstInt.getTwoValued(), value);
    return materializeConversion(type, twoValInt, true, loc, fallible);
}

This would check if the destination type is an int, or something like a packed struct or packed array that has a simple-bit-vector equivalent type. If it does, it would use RealToIntOp to convert to the two-valued simple-bit-vector, and then use materializeConversion to go from that to the actual type, which also handles int-to-logic and struct/array unpacking for you. WDYT?

This could allow for tests like these:

struct packed { shortint a; shortint b; } x;
logic [3:0][7:0] y;
$cast(x, 2.3);
$cast(y, 2.3);

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.

LGTM! Thanks a lot for implementing this 🥳 Just a minor nit, but this looks excellent!


if (dyn_cast<moore::IntType>(type).getDomain() == moore::Domain::FourValued)
return materializePackedToSBVConversion(*this, twoValInt, loc);
return builder.createOrFold<moore::IntToLogicOp>(loc, twoValInt);
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 is an interesting find! You might be able to materializeConversion here to do the domain casting and any potential simple-bit-vector unpacking automatically. Something like this:

if (dstInt && isa<moore::RealType>(value.getType())) {
    auto twoValInt = builder.createOrFold<moore::RealToIntOp>(
        loc, dstInt.getTwoValued(), value);
    return materializeConversion(type, twoValInt, true, loc, fallible);
}

This would check if the destination type is an int, or something like a packed struct or packed array that has a simple-bit-vector equivalent type. If it does, it would use RealToIntOp to convert to the two-valued simple-bit-vector, and then use materializeConversion to go from that to the actual type, which also handles int-to-logic and struct/array unpacking for you. WDYT?

This could allow for tests like these:

struct packed { shortint a; shortint b; } x;
logic [3:0][7:0] y;
$cast(x, 2.3);
$cast(y, 2.3);

Comment thread test/Conversion/ImportVerilog/basic.sv Outdated
// CHECK: return [[MAX]] : !moore.i32
// CHECK: }

function int DynCastRealToLogic(inout a);
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.

Nit-picking:

Suggested change
function int DynCastRealToLogic(inout a);
function int DynCastRealToLogic(inout integer a);

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.

Your suggestion is absolutely wonderful. 🥳

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