-
Notifications
You must be signed in to change notification settings - Fork 14.9k
[mlir][emitc] Inline expressions with side-effects #161356
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
[mlir][emitc] Inline expressions with side-effects #161356
Conversation
@llvm/pr-subscribers-mlir-emitc Author: Gil Rapaport (aniragil) ChangesSo far the translator only inlined expressions having no side effects, as rescheduling their evaluation doesn't break semantics. This patch adds inlining of expressions containing side effects if defined just before their use, e.g., %c = emitc.expression %a, %b : (i32, !emitc.ptr<i32>) -> i32 {
%e = emitc.sub %a, %b : (!emitc.ptr<i32>, i32) -> !emitc.ptr<i32>
%d = emitc.apply "*"(%e) : (!emitc.ptr<i32>) -> i32
emitc.yield %d : i32
}
emitc.return %c : i32 This restriction is meant to keep the translator as simple as possible, leaving it to transformations to analyze and reorder ops as needed in more complicated cases. Full diff: https://github.com/llvm/llvm-project/pull/161356.diff 2 Files Affected:
diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
index a5bd80e9d6b8b..8b87582324c45 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -355,11 +355,6 @@ static bool shouldBeInlined(ExpressionOp expressionOp) {
if (expressionOp.getDoNotInline())
return false;
- // Do not inline expressions with side effects to prevent side-effect
- // reordering.
- if (expressionOp.hasSideEffects())
- return false;
-
// Do not inline expressions with multiple uses.
Value result = expressionOp.getResult();
if (!result.hasOneUse())
@@ -375,7 +370,34 @@ static bool shouldBeInlined(ExpressionOp expressionOp) {
// Do not inline expressions used by other expressions or by ops with the
// CExpressionInterface. If this was intended, the user could have been merged
// into the expression op.
- return !isa<emitc::ExpressionOp, emitc::CExpressionInterface>(*user);
+ if (isa<emitc::ExpressionOp, emitc::CExpressionInterface>(*user))
+ return false;
+
+ // Expressions with no side-effects can safely be inlined.
+ if (!expressionOp.hasSideEffects())
+ return true;
+
+ // Expressions with side-effects can be only inlined if side-effect ordering
+ // in the program is provably retained.
+
+ // Require the user to immediately follow the expression.
+ if (++Block::iterator(expressionOp) != Block::iterator(user))
+ return false;
+
+ // These single-operand ops are safe.
+ if (isa<emitc::IfOp, emitc::SwitchOp, emitc::ReturnOp>(user))
+ return true;
+
+ // For assignment look for specific cases to inline as evaluation order of
+ // its lvalue and rvalue is undefined in C.
+ if (auto assignOp = dyn_cast<emitc::AssignOp>(user)) {
+ // Inline if this assignment is of the form `<var> = <expression>`.
+ if (expressionOp.getResult() == assignOp.getValue() &&
+ isa_and_present<VariableOp>(assignOp.getVar().getDefiningOp()))
+ return true;
+ }
+
+ return false;
}
static LogicalResult printConstantOp(CppEmitter &emitter, Operation *operation,
diff --git a/mlir/test/Target/Cpp/expressions.mlir b/mlir/test/Target/Cpp/expressions.mlir
index 4281f41d0b3fb..9f1c816ddabbc 100644
--- a/mlir/test/Target/Cpp/expressions.mlir
+++ b/mlir/test/Target/Cpp/expressions.mlir
@@ -315,16 +315,13 @@ func.func @different_expressions(%arg0: i32, %arg1: i32, %arg2: i32, %arg3: i32)
}
// CPP-DEFAULT: int32_t expression_with_dereference(int32_t [[VAL_1:v[0-9]+]], int32_t* [[VAL_2]]) {
-// CPP-DEFAULT-NEXT: int32_t [[VAL_3:v[0-9]+]] = *([[VAL_2]] - [[VAL_1]]);
-// CPP-DEFAULT-NEXT: return [[VAL_3]];
+// CPP-DEFAULT-NEXT: return *([[VAL_2]] - [[VAL_1]]);
// CPP-DEFAULT-NEXT: }
// CPP-DECLTOP: int32_t expression_with_dereference(int32_t [[VAL_1:v[0-9]+]], int32_t* [[VAL_2]]) {
-// CPP-DECLTOP-NEXT: int32_t [[VAL_3:v[0-9]+]];
-// CPP-DECLTOP-NEXT: [[VAL_3]] = *([[VAL_2]] - [[VAL_1]]);
-// CPP-DECLTOP-NEXT: return [[VAL_3]];
+// CPP-DECLTOP-NEXT: return *([[VAL_2]] - [[VAL_1]]);
// CPP-DECLTOP-NEXT: }
-func.func @expression_with_dereference(%arg1: i32, %arg2: !emitc.ptr<i32>) -> i32 {
+emitc.func @expression_with_dereference(%arg1: i32, %arg2: !emitc.ptr<i32>) -> i32 {
%c = emitc.expression %arg1, %arg2 : (i32, !emitc.ptr<i32>) -> i32 {
%e = emitc.sub %arg2, %arg1 : (!emitc.ptr<i32>, i32) -> !emitc.ptr<i32>
%d = emitc.apply "*"(%e) : (!emitc.ptr<i32>) -> i32
@@ -384,19 +381,16 @@ func.func @expression_with_subscript_user(%arg0: !emitc.ptr<!emitc.opaque<"void"
// CPP-DEFAULT: bool expression_with_load(int32_t [[VAL_1:v.+]], int32_t [[VAL_2:v.+]], int32_t* [[VAL_3:v.+]]) {
// CPP-DEFAULT-NEXT: int64_t [[VAL_4:v.+]] = 0;
// CPP-DEFAULT-NEXT: int32_t [[VAL_5:v.+]] = 42;
-// CPP-DEFAULT-NEXT: bool [[VAL_6:v.+]] = [[VAL_5]] + [[VAL_2]] < [[VAL_3]][[[VAL_4]]] + [[VAL_1]];
-// CPP-DEFAULT-NEXT: return [[VAL_6]];
+// CPP-DEFAULT-NEXT: return [[VAL_5]] + [[VAL_2]] < [[VAL_3]][[[VAL_4]]] + [[VAL_1]];
// CPP-DECLTOP: bool expression_with_load(int32_t [[VAL_1:v.+]], int32_t [[VAL_2:v.+]], int32_t* [[VAL_3:v.+]]) {
// CPP-DECLTOP-NEXT: int64_t [[VAL_4:v.+]];
// CPP-DECLTOP-NEXT: int32_t [[VAL_5:v.+]];
-// CPP-DECLTOP-NEXT: bool [[VAL_6:v.+]];
// CPP-DECLTOP-NEXT: [[VAL_4]] = 0;
// CPP-DECLTOP-NEXT: [[VAL_5]] = 42;
-// CPP-DECLTOP-NEXT: [[VAL_6]] = [[VAL_5]] + [[VAL_2]] < [[VAL_3]][[[VAL_4]]] + [[VAL_1]];
-// CPP-DECLTOP-NEXT: return [[VAL_6]];
+// CPP-DECLTOP-NEXT: return [[VAL_5]] + [[VAL_2]] < [[VAL_3]][[[VAL_4]]] + [[VAL_1]];
-func.func @expression_with_load(%arg0: i32, %arg1: i32, %arg2: !emitc.ptr<i32>) -> i1 {
+emitc.func @expression_with_load(%arg0: i32, %arg1: i32, %arg2: !emitc.ptr<i32>) -> i1 {
%c0 = "emitc.constant"() {value = 0 : i64} : () -> i64
%0 = "emitc.variable"() <{value = #emitc.opaque<"42">}> : () -> !emitc.lvalue<i32>
%ptr = emitc.subscript %arg2[%c0] : (!emitc.ptr<i32>, i64) -> !emitc.lvalue<i32>
@@ -408,22 +402,19 @@ func.func @expression_with_load(%arg0: i32, %arg1: i32, %arg2: !emitc.ptr<i32>)
%e = emitc.cmp lt, %b, %d :(i32, i32) -> i1
yield %e : i1
}
- return %result : i1
+ emitc.return %result : i1
}
// CPP-DEFAULT: bool expression_with_load_and_call(int32_t* [[VAL_1:v.+]]) {
// CPP-DEFAULT-NEXT: int64_t [[VAL_2:v.+]] = 0;
-// CPP-DEFAULT-NEXT: bool [[VAL_3:v.+]] = [[VAL_1]][[[VAL_2]]] + bar([[VAL_1]][[[VAL_2]]]) < [[VAL_1]][[[VAL_2]]];
-// CPP-DEFAULT-NEXT: return [[VAL_3]];
+// CPP-DEFAULT-NEXT: return [[VAL_1]][[[VAL_2]]] + bar([[VAL_1]][[[VAL_2]]]) < [[VAL_1]][[[VAL_2]]];
// CPP-DECLTOP: bool expression_with_load_and_call(int32_t* [[VAL_1:v.+]]) {
// CPP-DECLTOP-NEXT: int64_t [[VAL_2:v.+]];
-// CPP-DECLTOP-NEXT: bool [[VAL_3:v.+]];
// CPP-DECLTOP-NEXT: [[VAL_2]] = 0;
-// CPP-DECLTOP-NEXT: [[VAL_3]] = [[VAL_1]][[[VAL_2]]] + bar([[VAL_1]][[[VAL_2]]]) < [[VAL_1]][[[VAL_2]]];
-// CPP-DECLTOP-NEXT: return [[VAL_3]];
+// CPP-DECLTOP-NEXT: return [[VAL_1]][[[VAL_2]]] + bar([[VAL_1]][[[VAL_2]]]) < [[VAL_1]][[[VAL_2]]];
-func.func @expression_with_load_and_call(%arg0: !emitc.ptr<i32>) -> i1 {
+emitc.func @expression_with_load_and_call(%arg0: !emitc.ptr<i32>) -> i1 {
%c0 = "emitc.constant"() {value = 0 : i64} : () -> i64
%ptr = emitc.subscript %arg0[%c0] : (!emitc.ptr<i32>, i64) -> !emitc.lvalue<i32>
%result = emitc.expression %ptr : (!emitc.lvalue<i32>) -> i1 {
@@ -435,7 +426,7 @@ func.func @expression_with_load_and_call(%arg0: !emitc.ptr<i32>) -> i1 {
%f = emitc.cmp lt, %e, %b :(i32, i32) -> i1
yield %f : i1
}
- return %result : i1
+ emitc.return %result : i1
}
@@ -458,3 +449,204 @@ emitc.func @expression_with_call_opaque_with_args_array(%0 : i32, %1 : i32) {
}
return
}
+
+// CPP-DEFAULT: void inline_side_effects_into_assign(int32_t [[VAL_1:v[0-9]+]], int32_t* [[VAL_2:v[0-9]+]]) {
+// CPP-DEFAULT-NEXT: int64_t [[VAL_3:v[0-9]+]] = 0;
+// CPP-DEFAULT-NEXT: int32_t [[VAL_4:v[0-9]+]] = 42;
+// CPP-DEFAULT-NEXT: [[VAL_4]] = [[VAL_4]] * [[VAL_1]] + [[VAL_2]][[[VAL_3]]];
+// CPP-DEFAULT-NEXT: return;
+// CPP-DEFAULT-NEXT: }
+
+// CPP-DECLTOP: void inline_side_effects_into_assign(int32_t [[VAL_1:v[0-9]+]], int32_t* [[VAL_2:v[0-9]+]]) {
+// CPP-DECLTOP-NEXT: int64_t [[VAL_3:v[0-9]+]];
+// CPP-DECLTOP-NEXT: int32_t [[VAL_4:v[0-9]+]];
+// CPP-DECLTOP-NEXT: [[VAL_3]] = 0;
+// CPP-DECLTOP-NEXT: [[VAL_4]] = 42;
+// CPP-DECLTOP-NEXT: [[VAL_4]] = [[VAL_4]] * [[VAL_1]] + [[VAL_2]][[[VAL_3]]];
+// CPP-DECLTOP-NEXT: return;
+// CPP-DECLTOP-NEXT: }
+
+emitc.func @inline_side_effects_into_assign(%arg0: i32, %arg1: !emitc.ptr<i32>) {
+ %c0 = "emitc.constant"() {value = 0 : i64} : () -> i64
+ %0 = "emitc.variable"() <{value = #emitc.opaque<"42">}> : () -> !emitc.lvalue<i32>
+ %ptr = emitc.subscript %arg1[%c0] : (!emitc.ptr<i32>, i64) -> !emitc.lvalue<i32>
+ %result = emitc.expression %arg0, %0, %ptr : (i32, !emitc.lvalue<i32>, !emitc.lvalue<i32>) -> i32 {
+ %a = emitc.load %0 : !emitc.lvalue<i32>
+ %b = emitc.mul %a, %arg0 : (i32, i32) -> i32
+ %c = emitc.load %ptr : !emitc.lvalue<i32>
+ %d = emitc.add %b, %c : (i32, i32) -> i32
+ yield %d : i32
+ }
+ emitc.assign %result : i32 to %0 : !emitc.lvalue<i32>
+ emitc.return
+}
+
+// CPP-DEFAULT: void do_not_inline_side_effects_into_assign(int32_t [[VAL_1:v[0-9]+]], int32_t* [[VAL_2:v[0-9]+]]) {
+// CPP-DEFAULT-NEXT: int64_t [[VAL_3:v[0-9]+]] = 0;
+// CPP-DEFAULT-NEXT: int32_t [[VAL_4:v[0-9]+]] = 42;
+// CPP-DEFAULT-NEXT: int32_t [[VAL_5:v[0-9]+]] = [[VAL_4]] * [[VAL_1]];
+// CPP-DEFAULT-NEXT: [[VAL_2]][[[VAL_3]]] = [[VAL_5]];
+// CPP-DEFAULT-NEXT: return;
+// CPP-DEFAULT-NEXT: }
+
+// CPP-DECLTOP: void do_not_inline_side_effects_into_assign(int32_t [[VAL_1:v[0-9]+]], int32_t* [[VAL_2:v[0-9]+]]) {
+// CPP-DECLTOP-NEXT: int64_t [[VAL_3:v[0-9]+]];
+// CPP-DECLTOP-NEXT: int32_t [[VAL_4:v[0-9]+]];
+// CPP-DECLTOP-NEXT: int32_t [[VAL_5:v[0-9]+]];
+// CPP-DECLTOP-NEXT: [[VAL_3]] = 0;
+// CPP-DECLTOP-NEXT: [[VAL_4]] = 42;
+// CPP-DECLTOP-NEXT: [[VAL_5:v[0-9]+]] = [[VAL_4]] * [[VAL_1]];
+// CPP-DECLTOP-NEXT: [[VAL_2]][[[VAL_3]]] = [[VAL_5]];
+// CPP-DECLTOP-NEXT: return;
+// CPP-DECLTOP-NEXT: }
+
+emitc.func @do_not_inline_side_effects_into_assign(%arg0: i32, %arg1: !emitc.ptr<i32>) {
+ %c0 = "emitc.constant"() {value = 0 : i64} : () -> i64
+ %0 = "emitc.variable"() <{value = #emitc.opaque<"42">}> : () -> !emitc.lvalue<i32>
+ %ptr = emitc.subscript %arg1[%c0] : (!emitc.ptr<i32>, i64) -> !emitc.lvalue<i32>
+ %result = emitc.expression %arg0, %0 : (i32, !emitc.lvalue<i32>) -> i32 {
+ %a = emitc.load %0 : !emitc.lvalue<i32>
+ %b = emitc.mul %a, %arg0 : (i32, i32) -> i32
+ yield %b : i32
+ }
+ emitc.assign %result : i32 to %ptr : !emitc.lvalue<i32>
+ emitc.return
+}
+
+// CPP-DEFAULT: int32_t do_not_inline_non_preceding_side_effects(int32_t [[VAL_1:v[0-9]+]], int32_t* [[VAL_2:v[0-9]+]]) {
+// CPP-DEFAULT-NEXT: int64_t [[VAL_3:v[0-9]+]] = 0;
+// CPP-DEFAULT-NEXT: int32_t [[VAL_4:v[0-9]+]] = 42;
+// CPP-DEFAULT-NEXT: int32_t [[VAL_5:v[0-9]+]] = [[VAL_4]] * [[VAL_1]];
+// CPP-DEFAULT-NEXT: [[VAL_2]][[[VAL_3]]] = [[VAL_1]];
+// CPP-DEFAULT-NEXT: return [[VAL_5]];
+// CPP-DEFAULT-NEXT: }
+
+// CPP-DECLTOP: int32_t do_not_inline_non_preceding_side_effects(int32_t [[VAL_1:v[0-9]+]], int32_t* [[VAL_2:v[0-9]+]]) {
+// CPP-DECLTOP-NEXT: int64_t [[VAL_3:v[0-9]+]];
+// CPP-DECLTOP-NEXT: int32_t [[VAL_4:v[0-9]+]];
+// CPP-DECLTOP-NEXT: int32_t [[VAL_5:v[0-9]+]];
+// CPP-DECLTOP-NEXT: [[VAL_3:v[0-9]+]] = 0;
+// CPP-DECLTOP-NEXT: [[VAL_4:v[0-9]+]] = 42;
+// CPP-DECLTOP-NEXT: [[VAL_5:v[0-9]+]] = [[VAL_4]] * [[VAL_1]];
+// CPP-DECLTOP-NEXT: [[VAL_2]][[[VAL_3]]] = [[VAL_1]];
+// CPP-DECLTOP-NEXT: return [[VAL_5]];
+// CPP-DECLTOP-NEXT: }
+
+emitc.func @do_not_inline_non_preceding_side_effects(%arg0: i32, %arg1: !emitc.ptr<i32>) -> i32 {
+ %c0 = "emitc.constant"() {value = 0 : i64} : () -> i64
+ %0 = "emitc.variable"() <{value = #emitc.opaque<"42">}> : () -> !emitc.lvalue<i32>
+ %ptr = emitc.subscript %arg1[%c0] : (!emitc.ptr<i32>, i64) -> !emitc.lvalue<i32>
+ %result = emitc.expression %arg0, %0 : (i32, !emitc.lvalue<i32>) -> i32 {
+ %a = emitc.load %0 : !emitc.lvalue<i32>
+ %b = emitc.mul %a, %arg0 : (i32, i32) -> i32
+ yield %b : i32
+ }
+ emitc.assign %arg0 : i32 to %ptr : !emitc.lvalue<i32>
+ emitc.return %result : i32
+}
+
+// CPP-DEFAULT: int32_t inline_side_effects_into_if(int32_t [[VAL_1:v[0-9]+]], int32_t [[VAL_2:v[0-9]+]], int32_t [[VAL_3:v[0-9]+]]) {
+// CPP-DEFAULT-NEXT: int32_t [[VAL_4:v[0-9]+]];
+// CPP-DEFAULT-NEXT: if (bar([[VAL_1]], [[VAL_2]]) < [[VAL_3]]) {
+// CPP-DEFAULT-NEXT: [[VAL_4]] = [[VAL_1]];
+// CPP-DEFAULT-NEXT: } else {
+// CPP-DEFAULT-NEXT: [[VAL_4]] = [[VAL_2]];
+// CPP-DEFAULT-NEXT: }
+// CPP-DEFAULT-NEXT: int32_t [[VAL_5:v[0-9]+]] = [[VAL_4]];
+// CPP-DEFAULT-NEXT: return [[VAL_5]];
+// CPP-DEFAULT-NEXT: }
+
+// CPP-DECLTOP: int32_t inline_side_effects_into_if(int32_t [[VAL_1:v[0-9]+]], int32_t [[VAL_2:v[0-9]+]], int32_t [[VAL_3:v[0-9]+]]) {
+// CPP-DECLTOP-NEXT: int32_t [[VAL_4:v[0-9]+]];
+// CPP-DECLTOP-NEXT: int32_t [[VAL_5:v[0-9]+]];
+// CPP-DECLTOP-NEXT: ;
+// CPP-DECLTOP-NEXT: if (bar([[VAL_1]], [[VAL_2]]) < [[VAL_3]]) {
+// CPP-DECLTOP-NEXT: [[VAL_4]] = [[VAL_1]];
+// CPP-DECLTOP-NEXT: } else {
+// CPP-DECLTOP-NEXT: [[VAL_4]] = [[VAL_2]];
+// CPP-DECLTOP-NEXT: }
+// CPP-DECLTOP-NEXT: [[VAL_5]] = [[VAL_4]];
+// CPP-DECLTOP-NEXT: return [[VAL_5]];
+// CPP-DECLTOP-NEXT: }
+
+func.func @inline_side_effects_into_if(%arg0: i32, %arg1: i32, %arg2: i32) -> i32 {
+ %v = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.lvalue<i32>
+ %cond = emitc.expression %arg0, %arg1, %arg2 : (i32, i32, i32) -> i1 {
+ %a = emitc.call_opaque "bar" (%arg0, %arg1) : (i32, i32) -> (i32)
+ %b = emitc.cmp lt, %a, %arg2 :(i32, i32) -> i1
+ emitc.yield %b : i1
+ }
+ emitc.if %cond {
+ emitc.assign %arg0 : i32 to %v : !emitc.lvalue<i32>
+ emitc.yield
+ } else {
+ emitc.assign %arg1 : i32 to %v : !emitc.lvalue<i32>
+ emitc.yield
+ }
+ %v_load = emitc.load %v : !emitc.lvalue<i32>
+ return %v_load : i32
+}
+
+// CPP-DEFAULT: void inline_side_effects_into_switch(int32_t [[VAL_1:v[0-9]+]], int32_t [[VAL_2:v[0-9]+]], int32_t [[VAL_3:v[0-9]+]]) {
+// CPP-DEFAULT-NEXT: switch (bar([[VAL_1]], [[VAL_2]]) + [[VAL_3]]) {
+// CPP-DEFAULT-NEXT: case 2: {
+// CPP-DEFAULT-NEXT: int32_t [[VAL_4:v[0-9]+]] = func_b();
+// CPP-DEFAULT-NEXT: break;
+// CPP-DEFAULT-NEXT: }
+// CPP-DEFAULT-NEXT: case 5: {
+// CPP-DEFAULT-NEXT: int32_t [[VAL_5:v[0-9]+]] = func_a();
+// CPP-DEFAULT-NEXT: break;
+// CPP-DEFAULT-NEXT: }
+// CPP-DEFAULT-NEXT: default: {
+// CPP-DEFAULT-NEXT: float [[VAL_6:v[0-9]+]] = 4.200000000e+01f;
+// CPP-DEFAULT-NEXT: func2([[VAL_6]]);
+// CPP-DEFAULT-NEXT: break;
+// CPP-DEFAULT-NEXT: }
+// CPP-DEFAULT-NEXT: }
+// CPP-DEFAULT-NEXT: return;
+// CPP-DEFAULT-NEXT: }
+
+// CPP-DECLTOP: void inline_side_effects_into_switch(int32_t [[VAL_1:v[0-9]+]], int32_t [[VAL_2:v[0-9]+]], int32_t [[VAL_3:v[0-9]+]]) {
+// CPP-DECLTOP-NEXT: float [[VAL_6:v[0-9]+]];
+// CPP-DECLTOP-NEXT: int32_t [[VAL_4:v[0-9]+]];
+// CPP-DECLTOP-NEXT: int32_t [[VAL_5:v[0-9]+]];
+// CPP-DECLTOP-NEXT: switch (bar([[VAL_1]], [[VAL_2]]) + [[VAL_3]]) {
+// CPP-DECLTOP-NEXT: case 2: {
+// CPP-DECLTOP-NEXT: [[VAL_4]] = func_b();
+// CPP-DECLTOP-NEXT: break;
+// CPP-DECLTOP-NEXT: }
+// CPP-DECLTOP-NEXT: case 5: {
+// CPP-DECLTOP-NEXT: [[VAL_5]] = func_a();
+// CPP-DECLTOP-NEXT: break;
+// CPP-DECLTOP-NEXT: }
+// CPP-DECLTOP-NEXT: default: {
+// CPP-DECLTOP-NEXT: [[VAL_6]] = 4.200000000e+01f;
+// CPP-DECLTOP-NEXT: func2([[VAL_6]]);
+// CPP-DECLTOP-NEXT: break;
+// CPP-DECLTOP-NEXT: }
+// CPP-DECLTOP-NEXT: }
+// CPP-DECLTOP-NEXT: return;
+// CPP-DECLTOP-NEXT: }
+
+func.func @inline_side_effects_into_switch(%arg0: i32, %arg1: i32, %arg2: i32) {
+ %0 = emitc.expression %arg0, %arg1, %arg2 : (i32, i32, i32) -> i32 {
+ %a = emitc.call_opaque "bar" (%arg0, %arg1) : (i32, i32) -> (i32)
+ %b = emitc.add %a, %arg2 :(i32, i32) -> i32
+ emitc.yield %b : i32
+ }
+ emitc.switch %0 : i32
+ case 2 {
+ %1 = emitc.call_opaque "func_b" () : () -> i32
+ emitc.yield
+ }
+ case 5 {
+ %2 = emitc.call_opaque "func_a" () : () -> i32
+ emitc.yield
+ }
+ default {
+ %3 = "emitc.constant"(){value = 42.0 : f32} : () -> f32
+ emitc.call_opaque "func2" (%3) : (f32) -> ()
+ emitc.yield
+ }
+ return
+}
|
@llvm/pr-subscribers-mlir Author: Gil Rapaport (aniragil) ChangesSo far the translator only inlined expressions having no side effects, as rescheduling their evaluation doesn't break semantics. This patch adds inlining of expressions containing side effects if defined just before their use, e.g., %c = emitc.expression %a, %b : (i32, !emitc.ptr<i32>) -> i32 {
%e = emitc.sub %a, %b : (!emitc.ptr<i32>, i32) -> !emitc.ptr<i32>
%d = emitc.apply "*"(%e) : (!emitc.ptr<i32>) -> i32
emitc.yield %d : i32
}
emitc.return %c : i32 This restriction is meant to keep the translator as simple as possible, leaving it to transformations to analyze and reorder ops as needed in more complicated cases. Full diff: https://github.com/llvm/llvm-project/pull/161356.diff 2 Files Affected:
diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
index a5bd80e9d6b8b..8b87582324c45 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -355,11 +355,6 @@ static bool shouldBeInlined(ExpressionOp expressionOp) {
if (expressionOp.getDoNotInline())
return false;
- // Do not inline expressions with side effects to prevent side-effect
- // reordering.
- if (expressionOp.hasSideEffects())
- return false;
-
// Do not inline expressions with multiple uses.
Value result = expressionOp.getResult();
if (!result.hasOneUse())
@@ -375,7 +370,34 @@ static bool shouldBeInlined(ExpressionOp expressionOp) {
// Do not inline expressions used by other expressions or by ops with the
// CExpressionInterface. If this was intended, the user could have been merged
// into the expression op.
- return !isa<emitc::ExpressionOp, emitc::CExpressionInterface>(*user);
+ if (isa<emitc::ExpressionOp, emitc::CExpressionInterface>(*user))
+ return false;
+
+ // Expressions with no side-effects can safely be inlined.
+ if (!expressionOp.hasSideEffects())
+ return true;
+
+ // Expressions with side-effects can be only inlined if side-effect ordering
+ // in the program is provably retained.
+
+ // Require the user to immediately follow the expression.
+ if (++Block::iterator(expressionOp) != Block::iterator(user))
+ return false;
+
+ // These single-operand ops are safe.
+ if (isa<emitc::IfOp, emitc::SwitchOp, emitc::ReturnOp>(user))
+ return true;
+
+ // For assignment look for specific cases to inline as evaluation order of
+ // its lvalue and rvalue is undefined in C.
+ if (auto assignOp = dyn_cast<emitc::AssignOp>(user)) {
+ // Inline if this assignment is of the form `<var> = <expression>`.
+ if (expressionOp.getResult() == assignOp.getValue() &&
+ isa_and_present<VariableOp>(assignOp.getVar().getDefiningOp()))
+ return true;
+ }
+
+ return false;
}
static LogicalResult printConstantOp(CppEmitter &emitter, Operation *operation,
diff --git a/mlir/test/Target/Cpp/expressions.mlir b/mlir/test/Target/Cpp/expressions.mlir
index 4281f41d0b3fb..9f1c816ddabbc 100644
--- a/mlir/test/Target/Cpp/expressions.mlir
+++ b/mlir/test/Target/Cpp/expressions.mlir
@@ -315,16 +315,13 @@ func.func @different_expressions(%arg0: i32, %arg1: i32, %arg2: i32, %arg3: i32)
}
// CPP-DEFAULT: int32_t expression_with_dereference(int32_t [[VAL_1:v[0-9]+]], int32_t* [[VAL_2]]) {
-// CPP-DEFAULT-NEXT: int32_t [[VAL_3:v[0-9]+]] = *([[VAL_2]] - [[VAL_1]]);
-// CPP-DEFAULT-NEXT: return [[VAL_3]];
+// CPP-DEFAULT-NEXT: return *([[VAL_2]] - [[VAL_1]]);
// CPP-DEFAULT-NEXT: }
// CPP-DECLTOP: int32_t expression_with_dereference(int32_t [[VAL_1:v[0-9]+]], int32_t* [[VAL_2]]) {
-// CPP-DECLTOP-NEXT: int32_t [[VAL_3:v[0-9]+]];
-// CPP-DECLTOP-NEXT: [[VAL_3]] = *([[VAL_2]] - [[VAL_1]]);
-// CPP-DECLTOP-NEXT: return [[VAL_3]];
+// CPP-DECLTOP-NEXT: return *([[VAL_2]] - [[VAL_1]]);
// CPP-DECLTOP-NEXT: }
-func.func @expression_with_dereference(%arg1: i32, %arg2: !emitc.ptr<i32>) -> i32 {
+emitc.func @expression_with_dereference(%arg1: i32, %arg2: !emitc.ptr<i32>) -> i32 {
%c = emitc.expression %arg1, %arg2 : (i32, !emitc.ptr<i32>) -> i32 {
%e = emitc.sub %arg2, %arg1 : (!emitc.ptr<i32>, i32) -> !emitc.ptr<i32>
%d = emitc.apply "*"(%e) : (!emitc.ptr<i32>) -> i32
@@ -384,19 +381,16 @@ func.func @expression_with_subscript_user(%arg0: !emitc.ptr<!emitc.opaque<"void"
// CPP-DEFAULT: bool expression_with_load(int32_t [[VAL_1:v.+]], int32_t [[VAL_2:v.+]], int32_t* [[VAL_3:v.+]]) {
// CPP-DEFAULT-NEXT: int64_t [[VAL_4:v.+]] = 0;
// CPP-DEFAULT-NEXT: int32_t [[VAL_5:v.+]] = 42;
-// CPP-DEFAULT-NEXT: bool [[VAL_6:v.+]] = [[VAL_5]] + [[VAL_2]] < [[VAL_3]][[[VAL_4]]] + [[VAL_1]];
-// CPP-DEFAULT-NEXT: return [[VAL_6]];
+// CPP-DEFAULT-NEXT: return [[VAL_5]] + [[VAL_2]] < [[VAL_3]][[[VAL_4]]] + [[VAL_1]];
// CPP-DECLTOP: bool expression_with_load(int32_t [[VAL_1:v.+]], int32_t [[VAL_2:v.+]], int32_t* [[VAL_3:v.+]]) {
// CPP-DECLTOP-NEXT: int64_t [[VAL_4:v.+]];
// CPP-DECLTOP-NEXT: int32_t [[VAL_5:v.+]];
-// CPP-DECLTOP-NEXT: bool [[VAL_6:v.+]];
// CPP-DECLTOP-NEXT: [[VAL_4]] = 0;
// CPP-DECLTOP-NEXT: [[VAL_5]] = 42;
-// CPP-DECLTOP-NEXT: [[VAL_6]] = [[VAL_5]] + [[VAL_2]] < [[VAL_3]][[[VAL_4]]] + [[VAL_1]];
-// CPP-DECLTOP-NEXT: return [[VAL_6]];
+// CPP-DECLTOP-NEXT: return [[VAL_5]] + [[VAL_2]] < [[VAL_3]][[[VAL_4]]] + [[VAL_1]];
-func.func @expression_with_load(%arg0: i32, %arg1: i32, %arg2: !emitc.ptr<i32>) -> i1 {
+emitc.func @expression_with_load(%arg0: i32, %arg1: i32, %arg2: !emitc.ptr<i32>) -> i1 {
%c0 = "emitc.constant"() {value = 0 : i64} : () -> i64
%0 = "emitc.variable"() <{value = #emitc.opaque<"42">}> : () -> !emitc.lvalue<i32>
%ptr = emitc.subscript %arg2[%c0] : (!emitc.ptr<i32>, i64) -> !emitc.lvalue<i32>
@@ -408,22 +402,19 @@ func.func @expression_with_load(%arg0: i32, %arg1: i32, %arg2: !emitc.ptr<i32>)
%e = emitc.cmp lt, %b, %d :(i32, i32) -> i1
yield %e : i1
}
- return %result : i1
+ emitc.return %result : i1
}
// CPP-DEFAULT: bool expression_with_load_and_call(int32_t* [[VAL_1:v.+]]) {
// CPP-DEFAULT-NEXT: int64_t [[VAL_2:v.+]] = 0;
-// CPP-DEFAULT-NEXT: bool [[VAL_3:v.+]] = [[VAL_1]][[[VAL_2]]] + bar([[VAL_1]][[[VAL_2]]]) < [[VAL_1]][[[VAL_2]]];
-// CPP-DEFAULT-NEXT: return [[VAL_3]];
+// CPP-DEFAULT-NEXT: return [[VAL_1]][[[VAL_2]]] + bar([[VAL_1]][[[VAL_2]]]) < [[VAL_1]][[[VAL_2]]];
// CPP-DECLTOP: bool expression_with_load_and_call(int32_t* [[VAL_1:v.+]]) {
// CPP-DECLTOP-NEXT: int64_t [[VAL_2:v.+]];
-// CPP-DECLTOP-NEXT: bool [[VAL_3:v.+]];
// CPP-DECLTOP-NEXT: [[VAL_2]] = 0;
-// CPP-DECLTOP-NEXT: [[VAL_3]] = [[VAL_1]][[[VAL_2]]] + bar([[VAL_1]][[[VAL_2]]]) < [[VAL_1]][[[VAL_2]]];
-// CPP-DECLTOP-NEXT: return [[VAL_3]];
+// CPP-DECLTOP-NEXT: return [[VAL_1]][[[VAL_2]]] + bar([[VAL_1]][[[VAL_2]]]) < [[VAL_1]][[[VAL_2]]];
-func.func @expression_with_load_and_call(%arg0: !emitc.ptr<i32>) -> i1 {
+emitc.func @expression_with_load_and_call(%arg0: !emitc.ptr<i32>) -> i1 {
%c0 = "emitc.constant"() {value = 0 : i64} : () -> i64
%ptr = emitc.subscript %arg0[%c0] : (!emitc.ptr<i32>, i64) -> !emitc.lvalue<i32>
%result = emitc.expression %ptr : (!emitc.lvalue<i32>) -> i1 {
@@ -435,7 +426,7 @@ func.func @expression_with_load_and_call(%arg0: !emitc.ptr<i32>) -> i1 {
%f = emitc.cmp lt, %e, %b :(i32, i32) -> i1
yield %f : i1
}
- return %result : i1
+ emitc.return %result : i1
}
@@ -458,3 +449,204 @@ emitc.func @expression_with_call_opaque_with_args_array(%0 : i32, %1 : i32) {
}
return
}
+
+// CPP-DEFAULT: void inline_side_effects_into_assign(int32_t [[VAL_1:v[0-9]+]], int32_t* [[VAL_2:v[0-9]+]]) {
+// CPP-DEFAULT-NEXT: int64_t [[VAL_3:v[0-9]+]] = 0;
+// CPP-DEFAULT-NEXT: int32_t [[VAL_4:v[0-9]+]] = 42;
+// CPP-DEFAULT-NEXT: [[VAL_4]] = [[VAL_4]] * [[VAL_1]] + [[VAL_2]][[[VAL_3]]];
+// CPP-DEFAULT-NEXT: return;
+// CPP-DEFAULT-NEXT: }
+
+// CPP-DECLTOP: void inline_side_effects_into_assign(int32_t [[VAL_1:v[0-9]+]], int32_t* [[VAL_2:v[0-9]+]]) {
+// CPP-DECLTOP-NEXT: int64_t [[VAL_3:v[0-9]+]];
+// CPP-DECLTOP-NEXT: int32_t [[VAL_4:v[0-9]+]];
+// CPP-DECLTOP-NEXT: [[VAL_3]] = 0;
+// CPP-DECLTOP-NEXT: [[VAL_4]] = 42;
+// CPP-DECLTOP-NEXT: [[VAL_4]] = [[VAL_4]] * [[VAL_1]] + [[VAL_2]][[[VAL_3]]];
+// CPP-DECLTOP-NEXT: return;
+// CPP-DECLTOP-NEXT: }
+
+emitc.func @inline_side_effects_into_assign(%arg0: i32, %arg1: !emitc.ptr<i32>) {
+ %c0 = "emitc.constant"() {value = 0 : i64} : () -> i64
+ %0 = "emitc.variable"() <{value = #emitc.opaque<"42">}> : () -> !emitc.lvalue<i32>
+ %ptr = emitc.subscript %arg1[%c0] : (!emitc.ptr<i32>, i64) -> !emitc.lvalue<i32>
+ %result = emitc.expression %arg0, %0, %ptr : (i32, !emitc.lvalue<i32>, !emitc.lvalue<i32>) -> i32 {
+ %a = emitc.load %0 : !emitc.lvalue<i32>
+ %b = emitc.mul %a, %arg0 : (i32, i32) -> i32
+ %c = emitc.load %ptr : !emitc.lvalue<i32>
+ %d = emitc.add %b, %c : (i32, i32) -> i32
+ yield %d : i32
+ }
+ emitc.assign %result : i32 to %0 : !emitc.lvalue<i32>
+ emitc.return
+}
+
+// CPP-DEFAULT: void do_not_inline_side_effects_into_assign(int32_t [[VAL_1:v[0-9]+]], int32_t* [[VAL_2:v[0-9]+]]) {
+// CPP-DEFAULT-NEXT: int64_t [[VAL_3:v[0-9]+]] = 0;
+// CPP-DEFAULT-NEXT: int32_t [[VAL_4:v[0-9]+]] = 42;
+// CPP-DEFAULT-NEXT: int32_t [[VAL_5:v[0-9]+]] = [[VAL_4]] * [[VAL_1]];
+// CPP-DEFAULT-NEXT: [[VAL_2]][[[VAL_3]]] = [[VAL_5]];
+// CPP-DEFAULT-NEXT: return;
+// CPP-DEFAULT-NEXT: }
+
+// CPP-DECLTOP: void do_not_inline_side_effects_into_assign(int32_t [[VAL_1:v[0-9]+]], int32_t* [[VAL_2:v[0-9]+]]) {
+// CPP-DECLTOP-NEXT: int64_t [[VAL_3:v[0-9]+]];
+// CPP-DECLTOP-NEXT: int32_t [[VAL_4:v[0-9]+]];
+// CPP-DECLTOP-NEXT: int32_t [[VAL_5:v[0-9]+]];
+// CPP-DECLTOP-NEXT: [[VAL_3]] = 0;
+// CPP-DECLTOP-NEXT: [[VAL_4]] = 42;
+// CPP-DECLTOP-NEXT: [[VAL_5:v[0-9]+]] = [[VAL_4]] * [[VAL_1]];
+// CPP-DECLTOP-NEXT: [[VAL_2]][[[VAL_3]]] = [[VAL_5]];
+// CPP-DECLTOP-NEXT: return;
+// CPP-DECLTOP-NEXT: }
+
+emitc.func @do_not_inline_side_effects_into_assign(%arg0: i32, %arg1: !emitc.ptr<i32>) {
+ %c0 = "emitc.constant"() {value = 0 : i64} : () -> i64
+ %0 = "emitc.variable"() <{value = #emitc.opaque<"42">}> : () -> !emitc.lvalue<i32>
+ %ptr = emitc.subscript %arg1[%c0] : (!emitc.ptr<i32>, i64) -> !emitc.lvalue<i32>
+ %result = emitc.expression %arg0, %0 : (i32, !emitc.lvalue<i32>) -> i32 {
+ %a = emitc.load %0 : !emitc.lvalue<i32>
+ %b = emitc.mul %a, %arg0 : (i32, i32) -> i32
+ yield %b : i32
+ }
+ emitc.assign %result : i32 to %ptr : !emitc.lvalue<i32>
+ emitc.return
+}
+
+// CPP-DEFAULT: int32_t do_not_inline_non_preceding_side_effects(int32_t [[VAL_1:v[0-9]+]], int32_t* [[VAL_2:v[0-9]+]]) {
+// CPP-DEFAULT-NEXT: int64_t [[VAL_3:v[0-9]+]] = 0;
+// CPP-DEFAULT-NEXT: int32_t [[VAL_4:v[0-9]+]] = 42;
+// CPP-DEFAULT-NEXT: int32_t [[VAL_5:v[0-9]+]] = [[VAL_4]] * [[VAL_1]];
+// CPP-DEFAULT-NEXT: [[VAL_2]][[[VAL_3]]] = [[VAL_1]];
+// CPP-DEFAULT-NEXT: return [[VAL_5]];
+// CPP-DEFAULT-NEXT: }
+
+// CPP-DECLTOP: int32_t do_not_inline_non_preceding_side_effects(int32_t [[VAL_1:v[0-9]+]], int32_t* [[VAL_2:v[0-9]+]]) {
+// CPP-DECLTOP-NEXT: int64_t [[VAL_3:v[0-9]+]];
+// CPP-DECLTOP-NEXT: int32_t [[VAL_4:v[0-9]+]];
+// CPP-DECLTOP-NEXT: int32_t [[VAL_5:v[0-9]+]];
+// CPP-DECLTOP-NEXT: [[VAL_3:v[0-9]+]] = 0;
+// CPP-DECLTOP-NEXT: [[VAL_4:v[0-9]+]] = 42;
+// CPP-DECLTOP-NEXT: [[VAL_5:v[0-9]+]] = [[VAL_4]] * [[VAL_1]];
+// CPP-DECLTOP-NEXT: [[VAL_2]][[[VAL_3]]] = [[VAL_1]];
+// CPP-DECLTOP-NEXT: return [[VAL_5]];
+// CPP-DECLTOP-NEXT: }
+
+emitc.func @do_not_inline_non_preceding_side_effects(%arg0: i32, %arg1: !emitc.ptr<i32>) -> i32 {
+ %c0 = "emitc.constant"() {value = 0 : i64} : () -> i64
+ %0 = "emitc.variable"() <{value = #emitc.opaque<"42">}> : () -> !emitc.lvalue<i32>
+ %ptr = emitc.subscript %arg1[%c0] : (!emitc.ptr<i32>, i64) -> !emitc.lvalue<i32>
+ %result = emitc.expression %arg0, %0 : (i32, !emitc.lvalue<i32>) -> i32 {
+ %a = emitc.load %0 : !emitc.lvalue<i32>
+ %b = emitc.mul %a, %arg0 : (i32, i32) -> i32
+ yield %b : i32
+ }
+ emitc.assign %arg0 : i32 to %ptr : !emitc.lvalue<i32>
+ emitc.return %result : i32
+}
+
+// CPP-DEFAULT: int32_t inline_side_effects_into_if(int32_t [[VAL_1:v[0-9]+]], int32_t [[VAL_2:v[0-9]+]], int32_t [[VAL_3:v[0-9]+]]) {
+// CPP-DEFAULT-NEXT: int32_t [[VAL_4:v[0-9]+]];
+// CPP-DEFAULT-NEXT: if (bar([[VAL_1]], [[VAL_2]]) < [[VAL_3]]) {
+// CPP-DEFAULT-NEXT: [[VAL_4]] = [[VAL_1]];
+// CPP-DEFAULT-NEXT: } else {
+// CPP-DEFAULT-NEXT: [[VAL_4]] = [[VAL_2]];
+// CPP-DEFAULT-NEXT: }
+// CPP-DEFAULT-NEXT: int32_t [[VAL_5:v[0-9]+]] = [[VAL_4]];
+// CPP-DEFAULT-NEXT: return [[VAL_5]];
+// CPP-DEFAULT-NEXT: }
+
+// CPP-DECLTOP: int32_t inline_side_effects_into_if(int32_t [[VAL_1:v[0-9]+]], int32_t [[VAL_2:v[0-9]+]], int32_t [[VAL_3:v[0-9]+]]) {
+// CPP-DECLTOP-NEXT: int32_t [[VAL_4:v[0-9]+]];
+// CPP-DECLTOP-NEXT: int32_t [[VAL_5:v[0-9]+]];
+// CPP-DECLTOP-NEXT: ;
+// CPP-DECLTOP-NEXT: if (bar([[VAL_1]], [[VAL_2]]) < [[VAL_3]]) {
+// CPP-DECLTOP-NEXT: [[VAL_4]] = [[VAL_1]];
+// CPP-DECLTOP-NEXT: } else {
+// CPP-DECLTOP-NEXT: [[VAL_4]] = [[VAL_2]];
+// CPP-DECLTOP-NEXT: }
+// CPP-DECLTOP-NEXT: [[VAL_5]] = [[VAL_4]];
+// CPP-DECLTOP-NEXT: return [[VAL_5]];
+// CPP-DECLTOP-NEXT: }
+
+func.func @inline_side_effects_into_if(%arg0: i32, %arg1: i32, %arg2: i32) -> i32 {
+ %v = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.lvalue<i32>
+ %cond = emitc.expression %arg0, %arg1, %arg2 : (i32, i32, i32) -> i1 {
+ %a = emitc.call_opaque "bar" (%arg0, %arg1) : (i32, i32) -> (i32)
+ %b = emitc.cmp lt, %a, %arg2 :(i32, i32) -> i1
+ emitc.yield %b : i1
+ }
+ emitc.if %cond {
+ emitc.assign %arg0 : i32 to %v : !emitc.lvalue<i32>
+ emitc.yield
+ } else {
+ emitc.assign %arg1 : i32 to %v : !emitc.lvalue<i32>
+ emitc.yield
+ }
+ %v_load = emitc.load %v : !emitc.lvalue<i32>
+ return %v_load : i32
+}
+
+// CPP-DEFAULT: void inline_side_effects_into_switch(int32_t [[VAL_1:v[0-9]+]], int32_t [[VAL_2:v[0-9]+]], int32_t [[VAL_3:v[0-9]+]]) {
+// CPP-DEFAULT-NEXT: switch (bar([[VAL_1]], [[VAL_2]]) + [[VAL_3]]) {
+// CPP-DEFAULT-NEXT: case 2: {
+// CPP-DEFAULT-NEXT: int32_t [[VAL_4:v[0-9]+]] = func_b();
+// CPP-DEFAULT-NEXT: break;
+// CPP-DEFAULT-NEXT: }
+// CPP-DEFAULT-NEXT: case 5: {
+// CPP-DEFAULT-NEXT: int32_t [[VAL_5:v[0-9]+]] = func_a();
+// CPP-DEFAULT-NEXT: break;
+// CPP-DEFAULT-NEXT: }
+// CPP-DEFAULT-NEXT: default: {
+// CPP-DEFAULT-NEXT: float [[VAL_6:v[0-9]+]] = 4.200000000e+01f;
+// CPP-DEFAULT-NEXT: func2([[VAL_6]]);
+// CPP-DEFAULT-NEXT: break;
+// CPP-DEFAULT-NEXT: }
+// CPP-DEFAULT-NEXT: }
+// CPP-DEFAULT-NEXT: return;
+// CPP-DEFAULT-NEXT: }
+
+// CPP-DECLTOP: void inline_side_effects_into_switch(int32_t [[VAL_1:v[0-9]+]], int32_t [[VAL_2:v[0-9]+]], int32_t [[VAL_3:v[0-9]+]]) {
+// CPP-DECLTOP-NEXT: float [[VAL_6:v[0-9]+]];
+// CPP-DECLTOP-NEXT: int32_t [[VAL_4:v[0-9]+]];
+// CPP-DECLTOP-NEXT: int32_t [[VAL_5:v[0-9]+]];
+// CPP-DECLTOP-NEXT: switch (bar([[VAL_1]], [[VAL_2]]) + [[VAL_3]]) {
+// CPP-DECLTOP-NEXT: case 2: {
+// CPP-DECLTOP-NEXT: [[VAL_4]] = func_b();
+// CPP-DECLTOP-NEXT: break;
+// CPP-DECLTOP-NEXT: }
+// CPP-DECLTOP-NEXT: case 5: {
+// CPP-DECLTOP-NEXT: [[VAL_5]] = func_a();
+// CPP-DECLTOP-NEXT: break;
+// CPP-DECLTOP-NEXT: }
+// CPP-DECLTOP-NEXT: default: {
+// CPP-DECLTOP-NEXT: [[VAL_6]] = 4.200000000e+01f;
+// CPP-DECLTOP-NEXT: func2([[VAL_6]]);
+// CPP-DECLTOP-NEXT: break;
+// CPP-DECLTOP-NEXT: }
+// CPP-DECLTOP-NEXT: }
+// CPP-DECLTOP-NEXT: return;
+// CPP-DECLTOP-NEXT: }
+
+func.func @inline_side_effects_into_switch(%arg0: i32, %arg1: i32, %arg2: i32) {
+ %0 = emitc.expression %arg0, %arg1, %arg2 : (i32, i32, i32) -> i32 {
+ %a = emitc.call_opaque "bar" (%arg0, %arg1) : (i32, i32) -> (i32)
+ %b = emitc.add %a, %arg2 :(i32, i32) -> i32
+ emitc.yield %b : i32
+ }
+ emitc.switch %0 : i32
+ case 2 {
+ %1 = emitc.call_opaque "func_b" () : () -> i32
+ emitc.yield
+ }
+ case 5 {
+ %2 = emitc.call_opaque "func_a" () : () -> i32
+ emitc.yield
+ }
+ default {
+ %3 = "emitc.constant"(){value = 42.0 : f32} : () -> f32
+ emitc.call_opaque "func2" (%3) : (f32) -> ()
+ emitc.yield
+ }
+ return
+}
|
47059ed
to
1a23377
Compare
@kchibisov, this PR is a step towards improving expression inlining where side effects are involved (per the motivation behind your patch), would be great if you could take a look. |
So far the translator only inlined expressions having no side effects, as rescheduling their evaluation doesn't break semantics. This patch adds inlining of expressions containing side effects if defined just before their use, e.g., ```mlir %c = emitc.expression %a, %b : (i32, !emitc.ptr<i32>) -> i32 { %e = emitc.sub %a, %b : (!emitc.ptr<i32>, i32) -> !emitc.ptr<i32> %d = emitc.apply "*"(%e) : (!emitc.ptr<i32>) -> i32 emitc.yield %d : i32 } emitc.return %c : i32 ``` This restriction is meant to keep the translator as simple as possible, leaving it to transformations to analyze and reorder ops as needed in more complicated cases. The patch handles inlining into `emitc.return`, `emitc.if`, `emitc.switch` and (to some extent) `emitc.assign`.
1a23377
to
e74d448
Compare
ping |
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.
LGTM
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.
LGTM
So far the translator only inlined expressions having no side effects, as rescheduling their evaluation doesn't break semantics. This patch adds inlining of expressions containing side effects if defined just before their use, e.g., ```mlir %c = emitc.expression %a, %b : (i32, !emitc.ptr<i32>) -> i32 { %e = emitc.sub %a, %b : (!emitc.ptr<i32>, i32) -> !emitc.ptr<i32> %d = emitc.apply "*"(%e) : (!emitc.ptr<i32>) -> i32 emitc.yield %d : i32 } emitc.return %c : i32 ``` This restriction is meant to keep the translator as simple as possible, leaving it to transformations to analyze and reorder ops as needed in more complicated cases. The patch handles inlining into `emitc.return`, `emitc.if`, `emitc.switch` and (to some extent) `emitc.assign`.
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.
LGTM
Thanks for working on this. I was on vacation, and had no time to look into it.
no specific comments to this patch.
// Require the user to immediately follow the expression. | ||
if (++Block::iterator(expressionOp) != Block::iterator(user)) | ||
return false; |
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 we can in the future, tune it a bit for
%a = load %a1
%b = load %b2
%c = add %a, %b
Not sure you can properly write it generally in a safe way and not just for chain of loads
, though, chain of loads needs it the most, since for loads it'll give the most.
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.
Right. As C doesn't define the evaluation order in most cases (e.g. between the operands of +
) inlining expressions as the operands requires making sure that their accesses are independent (e.g. that none of the variables have its address taken, since it could then be passed to some call that would store to it).
Where evaluation order is defined, e.g. between the arguments to a function call, we should be able to trivially extend it to inline all arguments that are just before the call and in the right order.
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.
Similar to placing the expression just before the user, I think that in order to keep the translator simple this extension:
Right. As C doesn't define the evaluation order in most cases (e.g. between the operands of
+
) inlining expressions as the operands requires making sure that their accesses are independent (e.g. that none of the variables have its address taken, since it could then be passed to some call that would store to it).
should be implemented in the form-expressions
pass, while this extension:
Where evaluation order is defined, e.g. between the arguments to a function call, we should be able to trivially extend it to inline all arguments that are just before the call and in the right order.
can only be implemented in the translator.
So far the translator only inlined expressions having no side effects, as rescheduling their evaluation doesn't break semantics. This patch adds inlining of expressions containing side effects if defined just before their use, e.g., ```mlir %c = emitc.expression %a, %b : (i32, !emitc.ptr<i32>) -> i32 { %e = emitc.sub %a, %b : (!emitc.ptr<i32>, i32) -> !emitc.ptr<i32> %d = emitc.apply "*"(%e) : (!emitc.ptr<i32>) -> i32 emitc.yield %d : i32 } emitc.return %c : i32 ``` This restriction is meant to keep the translator as simple as possible, leaving it to transformations to analyze and reorder ops as needed in more complicated cases. The patch handles inlining into `emitc.return`, `emitc.if`, `emitc.switch` and (to some extent) `emitc.assign`.
So far the translator only inlined expressions having no side effects, as rescheduling their evaluation doesn't break semantics. This patch adds inlining of expressions containing side effects if defined just before their use, e.g.,
This restriction is meant to keep the translator as simple as possible, leaving it to transformations to analyze and reorder ops as needed in more complicated cases.
The patch handles inlining into
emitc.return
,emitc.if
,emitc.switch
and (to some extent)emitc.assign
.