From d4ac0b6bdb507eda1da17ae439eabd6980e758d8 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Fri, 20 Jun 2025 19:13:02 +0900 Subject: [PATCH] [mlir][emitc] Relax `hasSideEffect` rules for Var/Member The load side effect semantic depends on the operation that was used to declare the load's operand. In case of `VariableOp` and `MemberOp` the operation storage is on the stack, thus can be considered to not have side effects compared to e.g. subscript operation. --- mlir/include/mlir/Dialect/EmitC/IR/EmitC.td | 11 +++++++++ mlir/test/Dialect/EmitC/transforms.mlir | 23 ++++++++---------- mlir/test/Target/Cpp/expressions.mlir | 26 ++++++++++++++++++--- 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td index 9ecdb74f4d82e..4654b1c9a269e 100644 --- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td +++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td @@ -966,6 +966,17 @@ def EmitC_LoadOp : EmitC_Op<"load", [CExpressionInterface, let results = (outs AnyType:$result); let assemblyFormat = "$operand attr-dict `:` type($operand)"; + + let extraClassDeclaration = [{ + bool hasSideEffects() { + auto *defOp = this->getOperand().getDefiningOp(); + // The load side effect semantic depends on the operation that was used to + // declare the load's operand, `VariableOp` and `MemberOp` perform loading + // from the stack, thus we may consider them as not having side effect to + // make end code more readable by letting those loads to get inlined. + return !defOp || !(isa(defOp) || isa(defOp)); + } + }]; } def EmitC_MulOp : EmitC_BinaryOp<"mul", []> { diff --git a/mlir/test/Dialect/EmitC/transforms.mlir b/mlir/test/Dialect/EmitC/transforms.mlir index a38f396dad953..687ebe2cd3612 100644 --- a/mlir/test/Dialect/EmitC/transforms.mlir +++ b/mlir/test/Dialect/EmitC/transforms.mlir @@ -135,21 +135,18 @@ func.func @single_result_requirement() -> (i32, i32) { // CHECK-SAME: %[[VAL_1:.*]]: !emitc.ptr) -> i1 { // CHECK: %[[VAL_2:.*]] = "emitc.constant"() <{value = 0 : i64}> : () -> i64 // CHECK: %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"42">}> : () -> !emitc.lvalue -// CHECK: %[[VAL_4:.*]] = emitc.expression : i32 { -// CHECK: %[[VAL_5:.*]] = load %[[VAL_3]] : -// CHECK: yield %[[VAL_5]] : i32 +// CHECK: %[[VAL_4:.*]] = emitc.subscript %[[VAL_1]]{{\[}}%[[VAL_2]]] : (!emitc.ptr, i64) -> !emitc.lvalue +// CHECK: %[[VAL_5:.*]] = emitc.expression : i32 { +// CHECK: %[[VAL_6:.*]] = load %[[VAL_4]] : +// CHECK: yield %[[VAL_6]] : i32 // CHECK: } -// CHECK: %[[VAL_6:.*]] = emitc.subscript %[[VAL_1]]{{\[}}%[[VAL_2]]] : (!emitc.ptr, i64) -> !emitc.lvalue -// CHECK: %[[VAL_7:.*]] = emitc.expression : i32 { -// CHECK: %[[VAL_8:.*]] = load %[[VAL_6]] : -// CHECK: yield %[[VAL_8]] : i32 +// CHECK: %[[VAL_7:.*]] = emitc.expression : i1 { +// CHECK: %[[VAL_8:.*]] = load %[[VAL_3]] : +// CHECK: %[[VAL_9:.*]] = add %[[VAL_8]], %[[VAL_5]] : (i32, i32) -> i32 +// CHECK: %[[VAL_10:.*]] = cmp lt, %[[VAL_9]], %[[VAL_0]] : (i32, i32) -> i1 +// CHECK: yield %[[VAL_10]] : i1 // CHECK: } -// CHECK: %[[VAL_9:.*]] = emitc.expression : i1 { -// CHECK: %[[VAL_10:.*]] = add %[[VAL_4]], %[[VAL_7]] : (i32, i32) -> i32 -// CHECK: %[[VAL_11:.*]] = cmp lt, %[[VAL_10]], %[[VAL_0]] : (i32, i32) -> i1 -// CHECK: yield %[[VAL_11]] : i1 -// CHECK: } -// CHECK: return %[[VAL_9]] : i1 +// CHECK: return %[[VAL_7]] : i1 // CHECK: } diff --git a/mlir/test/Target/Cpp/expressions.mlir b/mlir/test/Target/Cpp/expressions.mlir index 9316d7b77619b..b07c5f1804d38 100644 --- a/mlir/test/Target/Cpp/expressions.mlir +++ b/mlir/test/Target/Cpp/expressions.mlir @@ -343,13 +343,13 @@ func.func @expression_with_subscript_user(%arg0: !emitc.ptr) -> i1 { +func.func @expression_with_var_load_and_subscript(%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 @@ -373,6 +373,26 @@ func.func @expression_with_load(%arg0: i32, %arg1: i32, %arg2: !emitc.ptr) return %result : i1 } +// CPP-DEFAULT: bool expression_with_var_load(int32_t [[VAL_1:v.+]], int32_t [[VAL_2:v.+]]) { +// CPP-DEFAULT-NEXT: int32_t [[VAL_3:v.+]] = 42; +// CPP-DEFAULT-NEXT: return [[VAL_3]] + [[VAL_1]] < [[VAL_2]]; + +// CPP-DECLTOP: bool expression_with_var_load(int32_t [[VAL_1:v.+]], int32_t [[VAL_2:v.+]]) { +// CPP-DECLTOP-NEXT: int32_t [[VAL_3:v.+]]; +// CPP-DECLTOP-NEXT: [[VAL_3]] = 42; +// CPP-DECLTOP-NEXT: return [[VAL_3]] + [[VAL_1]] < [[VAL_2]]; + +func.func @expression_with_var_load(%arg0: i32, %arg1: i32) -> i1 { + %0 = "emitc.variable"() <{value = #emitc.opaque<"42">}> : () -> !emitc.lvalue + %result = emitc.expression : i1 { + %a = emitc.load %0 : !emitc.lvalue + %b = emitc.add %a, %arg0 : (i32, i32) -> i32 + %d = emitc.cmp lt, %b, %arg1 :(i32, i32) -> i1 + yield %d : i1 + } + 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]]];