-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[mlir][emitc] Add 'emitc.while' and 'emitc.do' ops to the dialect #143008
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
Open
Vladislave0-0
wants to merge
1
commit into
llvm:main
Choose a base branch
from
Vladislave0-0:cpp-emitter-cf-while
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,212
−17
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1345,7 +1345,7 @@ def EmitC_AssignOp : EmitC_Op<"assign", []> { | |
} | ||
|
||
def EmitC_YieldOp : EmitC_Op<"yield", | ||
[Pure, Terminator, ParentOneOf<["ExpressionOp", "IfOp", "ForOp", "SwitchOp"]>]> { | ||
[Pure, Terminator, ParentOneOf<["DoOp", "ExpressionOp", "ForOp", "IfOp", "SwitchOp", "WhileOp"]>]> { | ||
let summary = "Block termination operation"; | ||
let description = [{ | ||
The `emitc.yield` terminates its parent EmitC op's region, optionally yielding | ||
|
@@ -1572,4 +1572,156 @@ def EmitC_SwitchOp : EmitC_Op<"switch", [RecursiveMemoryEffects, | |
let hasVerifier = 1; | ||
} | ||
|
||
def EmitC_WhileOp : EmitC_Op<"while", | ||
[HasOnlyGraphRegion, RecursiveMemoryEffects, NoRegionArguments, OpAsmOpInterface, NoTerminator]> { | ||
let summary = "While operation"; | ||
let description = [{ | ||
The `emitc.while` operation represents a C/C++ while loop construct that | ||
repeatedly executes a body region as long as a condition region evaluates to | ||
true. The operation has two regions: | ||
|
||
1. A condition region that must yield a boolean value (i1) | ||
2. A body region that contains the loop body | ||
|
||
The condition region is evaluated before each iteration. If it yields true, | ||
the body region is executed. The loop terminates when the condition yields | ||
false. The condition region must contain exactly one block that terminates | ||
with an `emitc.yield` operation producing an i1 value. | ||
|
||
Example: | ||
|
||
```mlir | ||
emitc.func @foo(%arg0 : !emitc.ptr<i32>) { | ||
%var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue<i32> | ||
%0 = emitc.literal "10" : i32 | ||
%1 = emitc.literal "1" : i32 | ||
|
||
emitc.while { | ||
%var_load = load %var : <i32> | ||
%res = emitc.cmp le, %var_load, %0 : (i32, i32) -> i1 | ||
emitc.yield %res : i1 | ||
} do { | ||
emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32> | ||
%var_load = load %var : <i32> | ||
%tmp_add = add %var_load, %1 : (i32, i32) -> i32 | ||
"emitc.assign"(%var, %tmp_add) : (!emitc.lvalue<i32>, i32) -> () | ||
} | ||
|
||
return | ||
} | ||
``` | ||
|
||
```c++ | ||
// Code emitted for the operation above. | ||
void foo(int32_t* v1) { | ||
int32_t v2 = 0; | ||
while (v2 <= 10) { | ||
printf("%d", *v1); | ||
int32_t v3 = v2; | ||
int32_t v4 = v3 + 1; | ||
v2 = v4; | ||
} | ||
return; | ||
} | ||
``` | ||
}]; | ||
|
||
let arguments = (ins); | ||
let results = (outs); | ||
let regions = (region MaxSizedRegion<1>:$conditionRegion, | ||
MaxSizedRegion<1>:$bodyRegion); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
let hasCustomAssemblyFormat = 1; | ||
let hasVerifier = 1; | ||
|
||
let extraClassDeclaration = [{ | ||
Operation *getRootOp(); | ||
|
||
//===------------------------------------------------------------------===// | ||
// OpAsmOpInterface Methods | ||
//===------------------------------------------------------------------===// | ||
|
||
/// EmitC ops in the body can omit their 'emitc.' prefix in the assembly. | ||
static ::llvm::StringRef getDefaultDialect() { | ||
return "emitc"; | ||
} | ||
}]; | ||
} | ||
|
||
def EmitC_DoOp : EmitC_Op<"do", | ||
[RecursiveMemoryEffects, NoRegionArguments, OpAsmOpInterface, NoTerminator]> { | ||
let summary = "Do-while operation"; | ||
let description = [{ | ||
The `emitc.do` operation represents a C/C++ do-while loop construct that | ||
executes a body region first and then repeatedly executes it as long as a | ||
condition region evaluates to true. The operation has two regions: | ||
|
||
1. A body region that contains the loop body | ||
2. A condition region that must yield a boolean value (i1) | ||
|
||
Unlike a while loop, the body region is executed before the first evaluation | ||
of the condition. The loop terminates when the condition yields false. The | ||
condition region must contain exactly one block that terminates with an | ||
`emitc.yield` operation producing an i1 value. | ||
|
||
Example: | ||
|
||
```mlir | ||
emitc.func @foo(%arg0 : !emitc.ptr<i32>) { | ||
%var = "emitc.variable"() <{value = 0 : i32}> : () -> !emitc.lvalue<i32> | ||
%0 = emitc.literal "10" : i32 | ||
%1 = emitc.literal "1" : i32 | ||
|
||
emitc.do { | ||
emitc.verbatim "printf(\"%d\", *{});" args %arg0 : !emitc.ptr<i32> | ||
%var_load = load %var : <i32> | ||
%tmp_add = add %var_load, %1 : (i32, i32) -> i32 | ||
"emitc.assign"(%var, %tmp_add) : (!emitc.lvalue<i32>, i32) -> () | ||
} while { | ||
%var_load = load %var : <i32> | ||
%res = emitc.cmp le, %var_load, %0 : (i32, i32) -> i1 | ||
emitc.yield %res : i1 | ||
} | ||
|
||
return | ||
} | ||
``` | ||
|
||
```c++ | ||
// Code emitted for the operation above. | ||
void foo(int32_t* v1) { | ||
int32_t v2 = 0; | ||
do { | ||
printf("%d", *v1); | ||
int32_t v3 = v2; | ||
int32_t v4 = v3 + 1; | ||
v2 = v4; | ||
} while (v2 <= 10); | ||
return; | ||
} | ||
``` | ||
}]; | ||
|
||
let arguments = (ins); | ||
let results = (outs); | ||
let regions = (region MaxSizedRegion<1>:$bodyRegion, | ||
MaxSizedRegion<1>:$conditionRegion); | ||
|
||
let hasCustomAssemblyFormat = 1; | ||
let hasVerifier = 1; | ||
|
||
let extraClassDeclaration = [{ | ||
Operation *getRootOp(); | ||
|
||
//===------------------------------------------------------------------===// | ||
// OpAsmOpInterface Methods | ||
//===------------------------------------------------------------------===// | ||
|
||
/// EmitC ops in the body can omit their 'emitc.' prefix in the assembly. | ||
static ::llvm::StringRef getDefaultDialect() { | ||
return "emitc"; | ||
} | ||
}]; | ||
} | ||
|
||
#endif // MLIR_DIALECT_EMITC_IR_EMITC |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Should we put
condition
to arguments? In CPP, the condition should be an expression (or simple declaration).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 don't quite understand what you mean.
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 did it in a similar way to the SCF dialect to simplify the translation.
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 maybe keep it similar to 'emitc.if'. No need to use a region for condition.
Something like
let arguments = (ins I1:$condition);
is fine.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.
No, this won't work, since you need to
load
, otherwise what would be the point of suchwhile
anddo
. If we go withinline
, you'd also must ensure then that it always happens into thewhile
cond and at this point, it seems like simpler to just put thecond
into it.if
on the otherside doesn't have a problem ofload
-ing, etc and also doesn't care about inline, since it generally.You also won't be able to translate to such
emitc.while
fromscf.while
into anything other thanexit = false; do { cond = eval(); } while (cond)
.Don't get me wrong, we've tried using plain
cond
, but the fact that you must haveload
working and that you must havescf
translation, so operation is not dead made us use the current approach.One could rely on
emitc.expression
being inlined, but it's much less clear from the user perspective, since you generally pass the result, not the entire expression in such cases.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.
Yes,
emitc.load
is usually used in while's condition. But I thinkemitc.expression
is enough for this case. And the condition region in verify and translation has similar logic with inlinedemitc.expression
.Uh oh!
There was an error while loading. Please reload this page.
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.
How do you propose to deal with no-inline
emitc.expression
that have loads and ensure translation correctness.We can not pass
emitc.expression
as an argument, we can only passi1
as an argument that may be backed byemitc.expression
, which may get inlined based on some conditions. That's a lot ofmay
, while the proposed approach looks likescf
and uses the same logic withemitc.expression
, but is much more clear in my opinion, since you know exactly how it'll end up in the users code.Unless you can pass
emitc.experssion
as an argument itself, I don't see how to do so.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'd add for clarification, since I've been helping @Vladislave0-0 to make it up and did some internal reviews before sending upstream, I'd also speak on his behalf on things we've agreed upon sending.
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 @jacquesguan regarding the relevance of
emitc.expression
. The C while loop condition is defined as a full expression IINM, so the code lowered to the proposed region will have to conform to those constraints, and replicatingemitc.expression
's behavior doesn't seem right.OTOH, if the condition's
emitc.expression
is located outside the loop then inlining that expression would change program semantics from a single to multiple evaluations of the expression.How about requiring the condition region of these loop ops to contain a single
emitc.expression
op feeding anemitc.yield
? That should both utilizeemitc.expression
and avoid any inlining issues.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.
The problem is that you can not use
emitc.expression
as an operand because it's not a type, so you can only pass abool
pretty much.This will make
while
completely useless, it must always be inlined forload
to be a part of the condition and be inside the condwhile (cond)
.Do you want to nest it one more time? That should work, a bit ugly, but I guess it's fine.