-
Notifications
You must be signed in to change notification settings - Fork 322
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Moore] Introduce Mem2Reg to eliminate local variables #7082
Conversation
I'm not sure the changes(like |
I've been digging into this a bit to see how Mem2Reg does its magic and why this isn't promoted. I've looked at the following: func.func @Foo() -> !moore.i32 {
%x = moore.variable : !moore.i32
%0 = moore.constant 42 : !moore.i32
moore.blocking_assign %x, %0 : !moore.i32
return %x : !moore.i32
} My trace of Mem2Reg:
So the problem is that we're using the variable func.func @Foo() -> i32 {
%x = memref.alloca() : memref<i32>
%0 = arith.constant 42 : i32
memref.store %x, %0[] : memref<i32>
%1 = memref.load %x[] : memref<i32>
return %1 : i32
} Instead of having variable/alloca directly return an We should probably do the same thing in the Moore dialect: |
I referred to |
Hey! I designed the mem2reg pass. Fabian’s analysis is correct, the pointer must only be used by promotable operations for any promotion to happen. In particular, returning the pointer is not allowed (as it would escape). You need a dedicated operation to read from the value. In the LLVM implementation, the slots are pointed to via an llvm.ptr, not an element value directly. |
Thanks @Moxinilian ❤️ ! Whether can I understand that we should use dedicated ops like |
But if |
I would recommend a dedicated type yes. I think you may technically not absolutely need it because the MemorySlot would model it already, but I think it would be better to make sure your slots do not accidentally escape like in our case. Load/store operations on the other hand are absolutely necessary. I’ll give a proper review to your code later when I have time if you want. |
Thanks! I need to reimplement the related method. In order to prevent the accident slot escape, we can add a wrapper for the type. What do @fabianschuiki think? |
f6683a1
to
e4987b6
Compare
The |
auto *readOp = context.convertExpression(expr.left()).getDefiningOp(); | ||
auto lhs = dyn_cast<moore::ReadLValueOp>(readOp).getOperand(); | ||
readOp->erase(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no way to estimate when to create moore.read_lvalue
. So call op->erase()
to remove it if it is created by expr.left()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a good idea!
I have a feeling that we are going to introduce a dedicated expression lowering for left-hand sides of assignments. This is done in a similar fashion in C/C++:
int x, y;
x = y
would lower to something like the following pseudo-code:
%x = alloca : ptr<int>
%y = alloca : ptr<int>
%0 = load %y // y lowered as rvalue
store %x, %0 // x lowered as lvalue
If arrays or structs are involved, you'd have dedicated ops that allow you to convert a pointer/reference to the struct into a reference to the field you're interested in:
struct { int a, b; } x, y;
x.b = y.a;
would lower to something like this:
%x = alloca : ptr<struct<a: int, b: int>>
%y = alloca : ptr<struct<a: int, b: int>>
// x.b lowered as lvalue
%0 = struct_field_ref %x, "b" : ptr<struct<a: int, b: int>> -> ptr<int>
// y.a lowered as rvalue
%1 = load %y : ptr<struct<a: int, b: int>> -> struct<a: int, b: int>
%2 = struct_field %x, "a" : struct<a: int, b: int> -> int
// assignment
store %0, %2 : ptr<int>, int
These ops would allow us to reason about assignable lvalues that are just references to a storage location, and rvalues that are actual values. We would probably create two separate expression lowering functions, context.convertLvalueExpression
and context.convertRvalueExpression
: the former would always return a ref<T>
type, and the latter would always return an actual value T
.
I think that's a fantastic idea! All types are now in ODS, so it should be pretty easy to create these simple wrapper types. We could start with a |
I'm trying to add a new type named |
Yeah absolutely… I think we anticipated that we would need an lvalue/rvalue split at some point, but it got in the way of the initial lowering work back then. Now that we know exactly what we need, we're in a much better position to add a ref type back in that handles this 😃 |
In my private branch, the |
That is great to hear 😍! Maybe once #7095 lands, you can rebase this PR onto that work and get everything up and running? |
Yeah, right! I'll just turn this draft into the PR later! |
e4987b6
to
7e145d6
Compare
Hey, @fabianschuiki. I added a new test file(optimization.sv) to check whether Mem2Reg can work. |
Hey, @Moxinilian. We designed a dedicated (reference)type wrapper to ensure slots don't accidentally escape. You can rest assured. Thanks again for your help! 😃 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
THis test contains import verilog and mem2reg 2passes. Personally think you just need to check only mem2reg pass.
Here is a HW test which just test canonicalize.
// RUN: circt-opt -canonicalize='top-down=true region-simplify=true' %s | FileCheck %s
// CHECK-LABEL: hw.module @extract_noop(in %arg0 : i3, out "" : i3) {
// CHECK-NEXT: hw.output %arg0
hw.module @extract_noop(in %arg0 : i3, out "": i3) {
%x = comb.extract %arg0 from 0 : (i3) -> i3
hw.output %x : i3
}
// Constant Folding
// CHECK-LABEL: hw.module @extract_cstfold(out result : i3) {
// CHECK-NEXT: %c-3_i3 = hw.constant -3 : i3
// CHECK-NEXT: hw.output %c-3_i3
hw.module @extract_cstfold(out result : i3) {
%c42_i12 = hw.constant 42 : i12
%x = comb.extract %c42_i12 from 3 : (i12) -> i3
hw.output %x : i3
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @mingzheTerapines 🙂! Since you are specifically checking whether mem2reg works on the Moore dialect, I would add a mem2reg.mlir
test instead, use something like // RUN: circt-opt --mem2reg %s | FileCheck %s
and use Moore dialect ops instead of SV inputs there.
The reasoning behind this is that you want a test that only checks the Verilog-to-Moore conversion, and a test that only checks whether mem2reg works on the Moore dialect. But you don't want to do both in the same file, since it makes tests very brittle and easy to break.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey, @mingzheTerapines @fabianschuiki. I added the mem2reg
to circt-opt
, which only verifies whether the mem2reg
works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test and tool option looks perfect!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me beyond the newline nits and the test.
Oh! Thanks! I'll be careful later. |
29ea7e7
to
59b4814
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice! Great to see mem2reg in action 😎
[Moore] Introduce Mem2Reg to elminate local variables. Co-authored-by: Fabian Schuiki <[email protected]>
59b4814
to
57c71fd
Compare
Please just view the
MooreOps.cpp
file to check the related implementation.First
Second