diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp index 5fe5f4181b71d..12435119b98a1 100644 --- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp +++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp @@ -357,11 +357,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()) @@ -377,7 +372,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(*user); + if (isa(*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(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(user)) { + // Inline if this assignment is of the form ` = `. + if (expressionOp.getResult() == assignOp.getValue() && + isa_and_present(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 { +emitc.func @expression_with_dereference(%arg1: i32, %arg2: !emitc.ptr) -> i32 { %c = emitc.expression %arg1, %arg2 : (i32, !emitc.ptr) -> i32 { %e = emitc.sub %arg2, %arg1 : (!emitc.ptr, i32) -> !emitc.ptr %d = emitc.apply "*"(%e) : (!emitc.ptr) -> i32 @@ -384,19 +381,16 @@ func.func @expression_with_subscript_user(%arg0: !emitc.ptr) -> i1 { +emitc.func @expression_with_load(%arg0: i32, %arg1: i32, %arg2: !emitc.ptr) -> i1 { %c0 = "emitc.constant"() {value = 0 : i64} : () -> i64 %0 = "emitc.variable"() <{value = #emitc.opaque<"42">}> : () -> !emitc.lvalue %ptr = emitc.subscript %arg2[%c0] : (!emitc.ptr, i64) -> !emitc.lvalue @@ -408,22 +402,19 @@ func.func @expression_with_load(%arg0: i32, %arg1: i32, %arg2: !emitc.ptr) %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) -> i1 { +emitc.func @expression_with_load_and_call(%arg0: !emitc.ptr) -> i1 { %c0 = "emitc.constant"() {value = 0 : i64} : () -> i64 %ptr = emitc.subscript %arg0[%c0] : (!emitc.ptr, i64) -> !emitc.lvalue %result = emitc.expression %ptr : (!emitc.lvalue) -> i1 { @@ -435,7 +426,7 @@ func.func @expression_with_load_and_call(%arg0: !emitc.ptr) -> 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) { + %c0 = "emitc.constant"() {value = 0 : i64} : () -> i64 + %0 = "emitc.variable"() <{value = #emitc.opaque<"42">}> : () -> !emitc.lvalue + %ptr = emitc.subscript %arg1[%c0] : (!emitc.ptr, i64) -> !emitc.lvalue + %result = emitc.expression %arg0, %0, %ptr : (i32, !emitc.lvalue, !emitc.lvalue) -> i32 { + %a = emitc.load %0 : !emitc.lvalue + %b = emitc.mul %a, %arg0 : (i32, i32) -> i32 + %c = emitc.load %ptr : !emitc.lvalue + %d = emitc.add %b, %c : (i32, i32) -> i32 + yield %d : i32 + } + emitc.assign %result : i32 to %0 : !emitc.lvalue + 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) { + %c0 = "emitc.constant"() {value = 0 : i64} : () -> i64 + %0 = "emitc.variable"() <{value = #emitc.opaque<"42">}> : () -> !emitc.lvalue + %ptr = emitc.subscript %arg1[%c0] : (!emitc.ptr, i64) -> !emitc.lvalue + %result = emitc.expression %arg0, %0 : (i32, !emitc.lvalue) -> i32 { + %a = emitc.load %0 : !emitc.lvalue + %b = emitc.mul %a, %arg0 : (i32, i32) -> i32 + yield %b : i32 + } + emitc.assign %result : i32 to %ptr : !emitc.lvalue + 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 { + %c0 = "emitc.constant"() {value = 0 : i64} : () -> i64 + %0 = "emitc.variable"() <{value = #emitc.opaque<"42">}> : () -> !emitc.lvalue + %ptr = emitc.subscript %arg1[%c0] : (!emitc.ptr, i64) -> !emitc.lvalue + %result = emitc.expression %arg0, %0 : (i32, !emitc.lvalue) -> i32 { + %a = emitc.load %0 : !emitc.lvalue + %b = emitc.mul %a, %arg0 : (i32, i32) -> i32 + yield %b : i32 + } + emitc.assign %arg0 : i32 to %ptr : !emitc.lvalue + 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 + %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 + emitc.yield + } else { + emitc.assign %arg1 : i32 to %v : !emitc.lvalue + emitc.yield + } + %v_load = emitc.load %v : !emitc.lvalue + 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 +}