Skip to content

[mlir][emitc] Inline constant when translate #143485

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
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

jacquesguan
Copy link
Contributor

This pr makes one used constant inlined during translation to cpp. Trying to make the generated cpp code more readable.

This pr makes one used constant inlined during translation to cpp. Trying to make the generated cpp code more readable.
@llvmbot
Copy link
Member

llvmbot commented Jun 10, 2025

@llvm/pr-subscribers-mlir

@llvm/pr-subscribers-mlir-emitc

Author: Jianjian Guan (jacquesguan)

Changes

This pr makes one used constant inlined during translation to cpp. Trying to make the generated cpp code more readable.


Patch is 30.65 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/143485.diff

5 Files Affected:

  • (modified) mlir/lib/Target/Cpp/TranslateToCpp.cpp (+29-1)
  • (modified) mlir/test/Target/Cpp/for.mlir (+6-16)
  • (modified) mlir/test/Target/Cpp/lvalue.mlir (+1-2)
  • (modified) mlir/test/Target/Cpp/stdops.mlir (+4-10)
  • (modified) mlir/test/Target/Cpp/switch.mlir (+94-152)
diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
index 5abc112ab8c7a..028fc007fb012 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -334,6 +334,24 @@ static bool shouldBeInlined(ExpressionOp expressionOp) {
   return !user->hasTrait<OpTrait::emitc::CExpression>();
 }
 
+/// Determine whether constant \p constantOp should be emitted inline, i.e.
+/// as part of its user. This function Only inline constant with one use.
+static bool shouldBeInlined(ConstantOp constantOp) {
+  // Do not inline expressions with multiple uses.
+  Value result = constantOp.getResult();
+  if (!result.hasOneUse())
+    return false;
+
+  Operation *user = *result.getUsers().begin();
+
+  // Do not inline expressions used by operations with deferred emission, since
+  // their translation requires the materialization of variables.
+  if (hasDeferredEmission(user))
+    return false;
+
+  return true;
+}
+
 static LogicalResult printConstantOp(CppEmitter &emitter, Operation *operation,
                                      Attribute value) {
   OpResult result = operation->getResult(0);
@@ -368,6 +386,9 @@ static LogicalResult printConstantOp(CppEmitter &emitter, Operation *operation,
 
 static LogicalResult printOperation(CppEmitter &emitter,
                                     emitc::ConstantOp constantOp) {
+  if (shouldBeInlined(constantOp))
+    return success();
+
   Operation *operation = constantOp.getOperation();
   Attribute value = constantOp.getValue();
 
@@ -1454,6 +1475,11 @@ LogicalResult CppEmitter::emitOperand(Value value) {
   if (expressionOp && shouldBeInlined(expressionOp))
     return emitExpression(expressionOp);
 
+  auto constantOp = dyn_cast_if_present<ConstantOp>(value.getDefiningOp());
+  if (constantOp && shouldBeInlined(constantOp)) {
+    return emitAttribute(constantOp.getLoc(), constantOp.getValue());
+  }
+
   os << getOrCreateName(value);
   return success();
 }
@@ -1650,7 +1676,9 @@ LogicalResult CppEmitter::emitOperation(Operation &op, bool trailingSemicolon) {
 
   if (getEmittedExpression() ||
       (isa<emitc::ExpressionOp>(op) &&
-       shouldBeInlined(cast<emitc::ExpressionOp>(op))))
+       shouldBeInlined(cast<emitc::ExpressionOp>(op))) ||
+      (isa<emitc::ConstantOp>(op) &&
+       shouldBeInlined(cast<emitc::ConstantOp>(op))))
     return success();
 
   // Never emit a semicolon for some operations, especially if endening with
diff --git a/mlir/test/Target/Cpp/for.mlir b/mlir/test/Target/Cpp/for.mlir
index 7cd3d5d646da6..d49a1379b2716 100644
--- a/mlir/test/Target/Cpp/for.mlir
+++ b/mlir/test/Target/Cpp/for.mlir
@@ -63,18 +63,13 @@ func.func @test_for_yield() {
   return
 }
 // CPP-DEFAULT: void test_for_yield() {
-// CPP-DEFAULT-NEXT: size_t [[START:[^ ]*]] = 0;
-// CPP-DEFAULT-NEXT: size_t [[STOP:[^ ]*]] = 10;
-// CPP-DEFAULT-NEXT: size_t [[STEP:[^ ]*]] = 1;
-// CPP-DEFAULT-NEXT: int32_t [[S0:[^ ]*]] = 0;
-// CPP-DEFAULT-NEXT: float [[P0:[^ ]*]] = 1.000000000e+00f;
 // CPP-DEFAULT-NEXT: int32_t [[SE:[^ ]*]];
 // CPP-DEFAULT-NEXT: float [[PE:[^ ]*]];
 // CPP-DEFAULT-NEXT: int32_t [[SI:[^ ]*]];
 // CPP-DEFAULT-NEXT: float [[PI:[^ ]*]];
-// CPP-DEFAULT-NEXT: [[SI:[^ ]*]] = [[S0]];
-// CPP-DEFAULT-NEXT: [[PI:[^ ]*]] = [[P0]];
-// CPP-DEFAULT-NEXT: for (size_t [[ITER:[^ ]*]] = [[START]]; [[ITER]] < [[STOP]]; [[ITER]] += [[STEP]]) {
+// CPP-DEFAULT-NEXT: [[SI:[^ ]*]] = 0;
+// CPP-DEFAULT-NEXT: [[PI:[^ ]*]] = 1.000000000e+00f;
+// CPP-DEFAULT-NEXT: for (size_t [[ITER:[^ ]*]] = 0; [[ITER]] < 10; [[ITER]] += 1) {
 // CPP-DEFAULT-NEXT: int32_t [[SI_LOAD:[^ ]*]] = [[SI]];
 // CPP-DEFAULT-NEXT: int32_t [[SN:[^ ]*]] = add([[SI_LOAD]], [[ITER]]);
 // CPP-DEFAULT-NEXT: float [[PI_LOAD:[^ ]*]] = [[PI]];
@@ -104,18 +99,13 @@ func.func @test_for_yield() {
 // CPP-DECLTOP-NEXT: float [[PN:[^ ]*]];
 // CPP-DECLTOP-NEXT: int32_t [[SI_LOAD2:[^ ]*]];
 // CPP-DECLTOP-NEXT: float [[PI_LOAD2:[^ ]*]];
-// CPP-DECLTOP-NEXT: [[START]] = 0;
-// CPP-DECLTOP-NEXT: [[STOP]] = 10;
-// CPP-DECLTOP-NEXT: [[STEP]] = 1;
-// CPP-DECLTOP-NEXT: [[S0]] = 0;
-// CPP-DECLTOP-NEXT: [[P0]] = 1.000000000e+00f;
 // CPP-DECLTOP-NEXT: ;
 // CPP-DECLTOP-NEXT: ;
 // CPP-DECLTOP-NEXT: ;
 // CPP-DECLTOP-NEXT: ;
-// CPP-DECLTOP-NEXT: [[SI]] = [[S0]];
-// CPP-DECLTOP-NEXT: [[PI]] = [[P0]];
-// CPP-DECLTOP-NEXT: for (size_t [[ITER:[^ ]*]] = [[START]]; [[ITER]] < [[STOP]]; [[ITER]] += [[STEP]]) {
+// CPP-DECLTOP-NEXT: [[SI]] = 0;
+// CPP-DECLTOP-NEXT: [[PI]] = 1.000000000e+00f;
+// CPP-DECLTOP-NEXT: for (size_t [[ITER:[^ ]*]] = 0; [[ITER]] < 10; [[ITER]] += 1) {
 // CPP-DECLTOP-NEXT: [[SI_LOAD]] = [[SI]];
 // CPP-DECLTOP-NEXT: [[SN]] = add([[SI_LOAD]], [[ITER]]);
 // CPP-DECLTOP-NEXT: [[PI_LOAD]] = [[PI]];
diff --git a/mlir/test/Target/Cpp/lvalue.mlir b/mlir/test/Target/Cpp/lvalue.mlir
index 2aa438eb6371c..0581c514a447b 100644
--- a/mlir/test/Target/Cpp/lvalue.mlir
+++ b/mlir/test/Target/Cpp/lvalue.mlir
@@ -19,8 +19,7 @@ emitc.func @lvalue_variables(%v1: i32, %v2: i32) -> i32 {
 // CHECK-NEXT: int32_t* [[VAR_PTR:[^ ]*]] = &[[VAR]];
 // CHECK-NEXT: zero([[VAR_PTR]]);
 // CHECK-NEXT: int32_t [[VAR_LOAD:[^ ]*]] = [[VAR]]; 
-// CHECK-NEXT: int32_t [[NEG_ONE:[^ ]*]] = -1; 
-// CHECK-NEXT: [[VAR]] = [[NEG_ONE]];
+// CHECK-NEXT: [[VAR]] = -1;
 // CHECK-NEXT: return [[VAR_LOAD]];
 
 
diff --git a/mlir/test/Target/Cpp/stdops.mlir b/mlir/test/Target/Cpp/stdops.mlir
index 589e5f2e96aff..235caa01033c2 100644
--- a/mlir/test/Target/Cpp/stdops.mlir
+++ b/mlir/test/Target/Cpp/stdops.mlir
@@ -44,13 +44,11 @@ func.func @one_result() -> i32 {
   return %0 : i32
 }
 // CPP-DEFAULT: int32_t one_result() {
-// CPP-DEFAULT-NEXT: int32_t [[V0:[^ ]*]] = 0;
-// CPP-DEFAULT-NEXT: return [[V0]];
+// CPP-DEFAULT-NEXT: return 0;
 
 // CPP-DECLTOP: int32_t one_result() {
 // CPP-DECLTOP-NEXT: int32_t [[V0:[^ ]*]];
-// CPP-DECLTOP-NEXT: [[V0]] = 0;
-// CPP-DECLTOP-NEXT: return [[V0]];
+// CPP-DECLTOP-NEXT: return 0;
 
 
 func.func @two_results() -> (i32, f32) {
@@ -59,16 +57,12 @@ func.func @two_results() -> (i32, f32) {
   return %0, %1 : i32, f32
 }
 // CPP-DEFAULT: std::tuple<int32_t, float> two_results() {
-// CPP-DEFAULT: int32_t [[V0:[^ ]*]] = 0;
-// CPP-DEFAULT: float [[V1:[^ ]*]] = 1.000000000e+00f;
-// CPP-DEFAULT: return std::make_tuple([[V0]], [[V1]]);
+// CPP-DEFAULT: return std::make_tuple(0, 1.000000000e+00f);
 
 // CPP-DECLTOP: std::tuple<int32_t, float> two_results() {
 // CPP-DECLTOP: int32_t [[V0:[^ ]*]];
 // CPP-DECLTOP: float [[V1:[^ ]*]];
-// CPP-DECLTOP: [[V0]] = 0;
-// CPP-DECLTOP: [[V1]] = 1.000000000e+00f;
-// CPP-DECLTOP: return std::make_tuple([[V0]], [[V1]]);
+// CPP-DECLTOP: return std::make_tuple(0, 1.000000000e+00f);
 
 
 func.func @single_return_statement(%arg0 : i32) -> i32 {
diff --git a/mlir/test/Target/Cpp/switch.mlir b/mlir/test/Target/Cpp/switch.mlir
index 4e20c1fc6536a..70c35c28a3bc4 100644
--- a/mlir/test/Target/Cpp/switch.mlir
+++ b/mlir/test/Target/Cpp/switch.mlir
@@ -2,32 +2,30 @@
 // RUN: mlir-translate -mlir-to-cpp -declare-variables-at-top %s | FileCheck --match-full-lines %s -check-prefix=CPP-DECLTOP
 
 // CPP-DEFAULT-LABEL: void emitc_switch_ptrdiff_t() {
-// CPP-DEFAULT:         ptrdiff_t v1 = 1;
-// CPP-DEFAULT:         switch (v1) {
+// CPP-DEFAULT:         switch (1) {
 // CPP-DEFAULT:         case 2: {
-// CPP-DEFAULT:           int32_t v2 = func_b();
+// CPP-DEFAULT:           int32_t v1 = func_b();
 // CPP-DEFAULT:           break;
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         case 5: {
-// CPP-DEFAULT:           int32_t v3 = func_a();
+// CPP-DEFAULT:           int32_t v2 = func_a();
 // CPP-DEFAULT:           break;
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         default: {
-// CPP-DEFAULT:           float v4 = 4.200000000e+01f;
-// CPP-DEFAULT:           func2(v4);
+// CPP-DEFAULT:           func2(4.200000000e+01f);
 // CPP-DEFAULT:           break;
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         return;
 // CPP-DEFAULT:       }
 
+
 // CPP-DECLTOP-LABEL: void emitc_switch_ptrdiff_t() {
 // CPP-DECLTOP:         ptrdiff_t v1;
 // CPP-DECLTOP:         float v2;
 // CPP-DECLTOP:         int32_t v3;
 // CPP-DECLTOP:         int32_t v4;
-// CPP-DECLTOP:         v1 = 1;
-// CPP-DECLTOP:         switch (v1) {
+// CPP-DECLTOP:         switch (1) {
 // CPP-DECLTOP:         case 2: {
 // CPP-DECLTOP:           v3 = func_b();
 // CPP-DECLTOP:           break;
@@ -37,8 +35,7 @@
 // CPP-DECLTOP:           break;
 // CPP-DECLTOP:         }
 // CPP-DECLTOP:         default: {
-// CPP-DECLTOP:           v2 = 4.200000000e+01f;
-// CPP-DECLTOP:           func2(v2);
+// CPP-DECLTOP:           func2(4.200000000e+01f);
 // CPP-DECLTOP:           break;
 // CPP-DECLTOP:         }
 // CPP-DECLTOP:         }
@@ -63,20 +60,17 @@ func.func @emitc_switch_ptrdiff_t() {
   return
 }
 
-// CPP-DEFAULT-LABEL: void emitc_switch_ssize_t() {
-// CPP-DEFAULT:         ssize_t v1 = 1;
-// CPP-DEFAULT:         switch (v1) {
+// CPP-DEFAULT:         switch (1) {
 // CPP-DEFAULT:         case 2: {
-// CPP-DEFAULT:           int32_t v2 = func_b();
+// CPP-DEFAULT:           int32_t v1 = func_b();
 // CPP-DEFAULT:           break;
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         case 5: {
-// CPP-DEFAULT:           int32_t v3 = func_a();
+// CPP-DEFAULT:           int32_t v2 = func_a();
 // CPP-DEFAULT:           break;
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         default: {
-// CPP-DEFAULT:           float v4 = 4.200000000e+01f;
-// CPP-DEFAULT:           func2(v4);
+// CPP-DEFAULT:           func2(4.200000000e+01f);
 // CPP-DEFAULT:           break;
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         }
@@ -88,8 +82,7 @@ func.func @emitc_switch_ptrdiff_t() {
 // CPP-DECLTOP:         float v2;
 // CPP-DECLTOP:         int32_t v3;
 // CPP-DECLTOP:         int32_t v4;
-// CPP-DECLTOP:         v1 = 1;
-// CPP-DECLTOP:         switch (v1) {
+// CPP-DECLTOP:         switch (1) {
 // CPP-DECLTOP:         case 2: {
 // CPP-DECLTOP:           v3 = func_b();
 // CPP-DECLTOP:           break;
@@ -99,8 +92,7 @@ func.func @emitc_switch_ptrdiff_t() {
 // CPP-DECLTOP:           break;
 // CPP-DECLTOP:         }
 // CPP-DECLTOP:         default: {
-// CPP-DECLTOP:           v2 = 4.200000000e+01f;
-// CPP-DECLTOP:           func2(v2);
+// CPP-DECLTOP:           func2(4.200000000e+01f);
 // CPP-DECLTOP:           break;
 // CPP-DECLTOP:         }
 // CPP-DECLTOP:         }
@@ -126,20 +118,18 @@ func.func @emitc_switch_ssize_t() {
   return
 }
 
-// CPP-DEFAULT-LABEL: void emitc_switch_size_t() {
-// CPP-DEFAULT:         size_t v1 = 1;
-// CPP-DEFAULT:         switch (v1) {
+// CPP-DEFAULT:       void emitc_switch_size_t() {
+// CPP-DEFAULT:         switch (1) {
 // CPP-DEFAULT:         case 2: {
-// CPP-DEFAULT:           int32_t v2 = func_b();
+// CPP-DEFAULT:           int32_t v1 = func_b();
 // CPP-DEFAULT:           break;
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         case 5: {
-// CPP-DEFAULT:           int32_t v3 = func_a();
+// CPP-DEFAULT:           int32_t v2 = func_a();
 // CPP-DEFAULT:           break;
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         default: {
-// CPP-DEFAULT:           float v4 = 4.200000000e+01f;
-// CPP-DEFAULT:           func2(v4);
+// CPP-DEFAULT:           func2(4.200000000e+01f);
 // CPP-DEFAULT:           break;
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         }
@@ -151,8 +141,7 @@ func.func @emitc_switch_ssize_t() {
 // CPP-DECLTOP:         float v2;
 // CPP-DECLTOP:         int32_t v3;
 // CPP-DECLTOP:         int32_t v4;
-// CPP-DECLTOP:         v1 = 1;
-// CPP-DECLTOP:         switch (v1) {
+// CPP-DECLTOP:         switch (1) {
 // CPP-DECLTOP:         case 2: {
 // CPP-DECLTOP:           v3 = func_b();
 // CPP-DECLTOP:           break;
@@ -162,8 +151,7 @@ func.func @emitc_switch_ssize_t() {
 // CPP-DECLTOP:           break;
 // CPP-DECLTOP:         }
 // CPP-DECLTOP:         default: {
-// CPP-DECLTOP:           v2 = 4.200000000e+01f;
-// CPP-DECLTOP:           func2(v2);
+// CPP-DECLTOP:           func2(4.200000000e+01f);
 // CPP-DECLTOP:           break;
 // CPP-DECLTOP:         }
 // CPP-DECLTOP:         }
@@ -189,20 +177,18 @@ func.func @emitc_switch_size_t() {
   return
 }
 
-// CPP-DEFAULT-LABEL: void emitc_switch_index() {
-// CPP-DEFAULT:         size_t v1 = 1;
-// CPP-DEFAULT:         switch (v1) {
+// CPP-DEFAULT:       void emitc_switch_index() {
+// CPP-DEFAULT:         switch (1) {
 // CPP-DEFAULT:         case 2: {
-// CPP-DEFAULT:           int32_t v2 = func_b();
+// CPP-DEFAULT:           int32_t v1 = func_b();
 // CPP-DEFAULT:           break;
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         case 5: {
-// CPP-DEFAULT:           int32_t v3 = func_a();
+// CPP-DEFAULT:           int32_t v2 = func_a();
 // CPP-DEFAULT:           break;
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         default: {
-// CPP-DEFAULT:           float v4 = 4.200000000e+01f;
-// CPP-DEFAULT:           func2(v4);
+// CPP-DEFAULT:           func2(4.200000000e+01f);
 // CPP-DEFAULT:           break;
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         }
@@ -214,8 +200,7 @@ func.func @emitc_switch_size_t() {
 // CPP-DECLTOP:         float v2;
 // CPP-DECLTOP:         int32_t v3;
 // CPP-DECLTOP:         int32_t v4;
-// CPP-DECLTOP:         v1 = 1;
-// CPP-DECLTOP:         switch (v1) {
+// CPP-DECLTOP:         switch (1) {
 // CPP-DECLTOP:         case 2: {
 // CPP-DECLTOP:           v3 = func_b();
 // CPP-DECLTOP:           break;
@@ -225,8 +210,7 @@ func.func @emitc_switch_size_t() {
 // CPP-DECLTOP:           break;
 // CPP-DECLTOP:         }
 // CPP-DECLTOP:         default: {
-// CPP-DECLTOP:           v2 = 4.200000000e+01f;
-// CPP-DECLTOP:           func2(v2);
+// CPP-DECLTOP:           func2(4.200000000e+01f);
 // CPP-DECLTOP:           break;
 // CPP-DECLTOP:         }
 // CPP-DECLTOP:         }
@@ -252,20 +236,18 @@ func.func @emitc_switch_index() {
   return
 }
 
-// CPP-DEFAULT-LABEL: void emitc_switch_opaque() {
-// CPP-DEFAULT:         size_t v1 = 1;
-// CPP-DEFAULT:         switch (v1) {
+// CPP-DEFAULT:       void emitc_switch_opaque() {
+// CPP-DEFAULT:         switch (1) {
 // CPP-DEFAULT:         case 2: {
-// CPP-DEFAULT:           int32_t v2 = func_b();
+// CPP-DEFAULT:           int32_t v1 = func_b();
 // CPP-DEFAULT:           break;
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         case 5: {
-// CPP-DEFAULT:           int32_t v3 = func_a();
+// CPP-DEFAULT:           int32_t v2 = func_a();
 // CPP-DEFAULT:           break;
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         default: {
-// CPP-DEFAULT:           float v4 = 4.200000000e+01f;
-// CPP-DEFAULT:           func2(v4);
+// CPP-DEFAULT:           func2(4.200000000e+01f);
 // CPP-DEFAULT:           break;
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         }
@@ -277,8 +259,7 @@ func.func @emitc_switch_index() {
 // CPP-DECLTOP:         float v2;
 // CPP-DECLTOP:         int32_t v3;
 // CPP-DECLTOP:         int32_t v4;
-// CPP-DECLTOP:         v1 = 1;
-// CPP-DECLTOP:         switch (v1) {
+// CPP-DECLTOP:         switch (1) {
 // CPP-DECLTOP:         case 2: {
 // CPP-DECLTOP:           v3 = func_b();
 // CPP-DECLTOP:           break;
@@ -288,8 +269,7 @@ func.func @emitc_switch_index() {
 // CPP-DECLTOP:           break;
 // CPP-DECLTOP:         }
 // CPP-DECLTOP:         default: {
-// CPP-DECLTOP:           v2 = 4.200000000e+01f;
-// CPP-DECLTOP:           func2(v2);
+// CPP-DECLTOP:           func2(4.200000000e+01f);
 // CPP-DECLTOP:           break;
 // CPP-DECLTOP:         }
 // CPP-DECLTOP:         }
@@ -316,20 +296,18 @@ func.func @emitc_switch_opaque() {
   return
 }
 
-// CPP-DEFAULT-LABEL: void emitc_switch_i1() {
-// CPP-DEFAULT:         bool v1 = true;
-// CPP-DEFAULT:         switch (v1) {
+// CPP-DEFAULT:       void emitc_switch_i1() {
+// CPP-DEFAULT:         switch (true) {
 // CPP-DEFAULT:         case 2: {
-// CPP-DEFAULT:           int32_t v2 = func_b();
+// CPP-DEFAULT:           int32_t v1 = func_b();
 // CPP-DEFAULT:           break;
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         case 5: {
-// CPP-DEFAULT:           int32_t v3 = func_a();
+// CPP-DEFAULT:           int32_t v2 = func_a();
 // CPP-DEFAULT:           break;
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         default: {
-// CPP-DEFAULT:           float v4 = 4.200000000e+01f;
-// CPP-DEFAULT:           func2(v4);
+// CPP-DEFAULT:           func2(4.200000000e+01f);
 // CPP-DEFAULT:           break;
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         }
@@ -341,8 +319,7 @@ func.func @emitc_switch_opaque() {
 // CPP-DECLTOP:         float v2;
 // CPP-DECLTOP:         int32_t v3;
 // CPP-DECLTOP:         int32_t v4;
-// CPP-DECLTOP:         v1 = true;
-// CPP-DECLTOP:         switch (v1) {
+// CPP-DECLTOP:         switch (true) {
 // CPP-DECLTOP:         case 2: {
 // CPP-DECLTOP:           v3 = func_b();
 // CPP-DECLTOP:           break;
@@ -352,8 +329,7 @@ func.func @emitc_switch_opaque() {
 // CPP-DECLTOP:           break;
 // CPP-DECLTOP:         }
 // CPP-DECLTOP:         default: {
-// CPP-DECLTOP:           v2 = 4.200000000e+01f;
-// CPP-DECLTOP:           func2(v2);
+// CPP-DECLTOP:           func2(4.200000000e+01f);
 // CPP-DECLTOP:           break;
 // CPP-DECLTOP:         }
 // CPP-DECLTOP:         }
@@ -379,20 +355,18 @@ func.func @emitc_switch_i1() {
   return
 }
 
-// CPP-DEFAULT-LABEL: void emitc_switch_i8() {
-// CPP-DEFAULT:         int8_t v1 = 1;
-// CPP-DEFAULT:         switch (v1) {
+// CPP-DEFAULT:       void emitc_switch_i8() {
+// CPP-DEFAULT:         switch (1) {
 // CPP-DEFAULT:         case 2: {
-// CPP-DEFAULT:           int32_t v2 = func_b();
+// CPP-DEFAULT:           int32_t v1 = func_b();
 // CPP-DEFAULT:           break;
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         case 5: {
-// CPP-DEFAULT:           int32_t v3 = func_a();
+// CPP-DEFAULT:           int32_t v2 = func_a();
 // CPP-DEFAULT:           break;
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         default: {
-// CPP-DEFAULT:           float v4 = 4.200000000e+01f;
-// CPP-DEFAULT:           func2(v4);
+// CPP-DEFAULT:           func2(4.200000000e+01f);
 // CPP-DEFAULT:           break;
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         }
@@ -404,8 +378,7 @@ func.func @emitc_switch_i1() {
 // CPP-DECLTOP:         float v2;
 // CPP-DECLTOP:         int32_t v3;
 // CPP-DECLTOP:         int32_t v4;
-// CPP-DECLTOP:         v1 = 1;
-// CPP-DECLTOP:         switch (v1) {
+// CPP-DECLTOP:         switch (1) {
 // CPP-DECLTOP:         case 2: {
 // CPP-DECLTOP:           v3 = func_b();
 // CPP-DECLTOP:           break;
@@ -415,8 +388,7 @@ func.func @emitc_switch_i1() {
 // CPP-DECLTOP:           break;
 // CPP-DECLTOP:         }
 // CPP-DECLTOP:         default: {
-// CPP-DECLTOP:           v2 = 4.200000000e+01f;
-// CPP-DECLTOP:           func2(v2);
+// CPP-DECLTOP:           func2(4.200000000e+01f);
 // CPP-DECLTOP:           break;
 // CPP-DECLTOP:         }
 // CPP-DECLTOP:         }
@@ -442,20 +414,18 @@ func.func @emitc_switch_i8() {
   return
 }
 
-// CPP-DEFAULT-LABEL: void emitc_switch_ui8() {
-// CPP-DEFAULT:         uint8_t v1 = 1;
-// CPP-DEFAULT:         switch (v1) {
+// CPP-DEFAULT:       void emitc_switch_ui8() {
+// CPP-DEFAULT:         switch (1) {
 // CPP-DEFAULT:         case 2: {
-// CPP-DEFAULT:           int32_t v2 = func_b();
+// CPP-DEFAULT:           int32_t v1 = func_b();
 // CPP-DEFAULT:           break;
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         case 5: {
-// CPP-DEFAULT:           int32_t v3 = func_a();
+// CPP-DEFAULT:           int32_t v2 = func_a();
 // CPP-DEFAULT:           break;
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         default: {
-// CPP-DEFAULT:           float v4 = 4.200000000e+01f;
-// CPP-DEFAULT:           func2(v4);
+// CPP-DEFAULT:           func2(4.200000000e+01f);
 // CPP-DEFAULT:           break;
 // CPP-DEFAULT:         }
 // CPP-DEFAULT:         }
@@ -467,8 +437,7 @@ func.func @emitc_switch_i8() {
 // CPP-DECLTOP:         float v2;
 // CPP-DECLTOP:         int32_t v3;
 // CPP-DECLTOP:         int32_t v4;
-// CPP-DECLTOP:         v1 = 1;
-// CPP-DECLTOP:         switch (v1) {
+// CPP-DECLTOP:         switch (1) {
 // CPP-DECLTOP:         case 2: {
 // CPP-DECLTOP:           v3 = func_b();
 // CPP-DECLTOP:           break;
@@ -478,8 +447,7 @@ func.func @emitc_switch_i8() {
 // CPP-DECLTOP:           break;
 // CPP-DECLTOP:         }
 // ...
[truncated]

// CPP-DEFAULT: int32_t [[V0:[^ ]*]] = 0;
// CPP-DEFAULT: float [[V1:[^ ]*]] = 1.000000000e+00f;
// CPP-DEFAULT: return std::make_tuple([[V0]], [[V1]]);
// CPP-DEFAULT: return std::make_tuple(0, 1.000000000e+00f);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we emit casts to ensure that the integer literal has the right type? Otherwise this will cause template type inference to change. E.g. If V0 was int64_t here, how would the C++ code look? Maybe you can add a test case to show that

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks to point this, I think maybe we can use literal suffix? By example, emit constant of int64_t to 0ll?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These suffixes map to the C types (Like Long long int) and the width of these are architecture dependent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, same integer width doesn't mean the same type, so when the user of constant is a call or std::tuple and std::tie, we need emit a explicit type cast around it.

@simon-camp
Copy link
Contributor

simon-camp commented Jun 10, 2025

Can this be moved from the Translation layer to a conversion pass? We have the Expression op which gets inlined under the right conditions. On top of that there ist already the FormExpressions pass, that does something like this. Maybe that can be reused/extended to have more control on which ops get turned into expressions. This would also have the advantage to make this feature opt in and not immediately break downstream Users.

@marbre
Copy link
Member

marbre commented Jun 10, 2025

Can this be moved from the Translation layer to a conversion pass?

I agree here. If a pass can be used for this, I would strongly prefer this over adding it to the translation which we try to keep as dump as possible.

@aniragil
Copy link
Contributor

Can this be moved from the Translation layer to a conversion pass? We have the Expression op which gets inlined under the right conditions. On top of that there ist already the FormExpressions pass, that does something like this. Maybe that can be reused/extended to have more control on which ops get turned into expressions. This would also have the advantage to make this feature opt in and not immediately break downstream Users.

+1.
Any reason why emitc.constant cannot be added the CExpression trait?

@jacquesguan
Copy link
Contributor Author

Can this be moved from the Translation layer to a conversion pass? We have the Expression op which gets inlined under the right conditions. On top of that there ist already the FormExpressions pass, that does something like this. Maybe that can be reused/extended to have more control on which ops get turned into expressions. This would also have the advantage to make this feature opt in and not immediately break downstream Users.

+1. Any reason why emitc.constant cannot be added the CExpression trait?

A constant op, such as emitc.constant, will be moved out to the parent region during applying patterns because of constant CSE. If we add CExpression to emitc.constant, In the FormExpressions pass, we will get an empty expression with only terminator yield.

@simon-camp
Copy link
Contributor

One could argue that the emitc.constant shouldn't have the ConstantLike trait as it's emitted as local variables and thus has side effects. What's your opinion on this @marbre @aniragil @mgehre-amd?

That would also be disruptive downstream though as constants wouldn't be deduplicated anymore.

@aniragil
Copy link
Contributor

A constant op, such as emitc.constant, will be moved out to the parent region during applying patterns because of constant CSE. If we add CExpression to emitc.constant, In the FormExpressions pass, we will get an empty expression with only terminator yield.

Right, though being sort of a "prepare for translation" transformation, form-expressions is meant to run after all other transformations, which is why it only inlines single-use expressions, avoiding re-materialization that would undo deduplication. We could however make emitc.expression IsolatedFromAbove, which IINM would prevent CSE from simplifying it out of the expression. This would both allow form-expressions to run at any point in the pipeline and allow lowering passes to generate expressions safely if desired. WDYT?

One could argue that the emitc.constant shouldn't have the ConstantLike trait as it's emitted as local variables and thus has side effects. What's your opinion on this @marbre @aniragil @mgehre-amd?

That would also be disruptive downstream though as constants wouldn't be deduplicated anymore.

IINM emitc.constant is emitted as an "SSA" C variable rather than an lvalue variable, so it doesn't have side effects in the MLIR sense. BTW, might also be good to have more emitc ops (notably operators) have the pure trait for better CSE/DCE behavior.

@kchibisov
Copy link
Contributor

We could however make emitc.expression IsolatedFromAbove, which IINM would prevent CSE from simplifying it out of the expression. This would both allow form-expressions to run at any point in the pipeline and allow lowering passes to generate expressions safely if desired. WDYT?

Isn't IsolatedFromAbove prevents all uses of above operations, so even if they are in args it won't work, so the whole use of emitc.expression would be pointless since it won't be able to refer any data, where the point is to refer data and apply CExpression operations to it to yield a result?

@simon-camp
Copy link
Contributor

IINM emitc.constant is emitted as an "SSA" C variable rather than an lvalue variable, so it doesn't have side effects in the MLIR sense. BTW, might also be good to have more emitc ops (notably operators) have the pure trait for better CSE/DCE behavior.

I'm open to further discussions, but if it's pure then hoisting on the MLIR side is not wrong.

I've thought about side effects in the past and I don't know if we want to simply mark the ops as pure as you might target overloaded C++ classes that do whatever. But especially on the call_opaque op I want to be able to mark an instance as pure with an attribute for example and then override getMemoryEffects accordingly.

@kchibisov
Copy link
Contributor

I've thought about side effects in the past and I don't know if we want to simply mark the ops as pure as you might target overloaded C++ classes that do whatever. But especially on the call_opaque op I want to be able to mark an instance as pure with an attribute for example and then override getMemoryEffects accordingly.

Wouldn't the constant op be able to check for that with the new interface proposed here #142771 ? We can check if the type is scalar and then e.g. say that it doesn't have side effects.

@marbre
Copy link
Member

marbre commented Jun 16, 2025

I've thought about side effects in the past and I don't know if we want to simply mark the ops as pure as you might target overloaded C++ classes that do whatever. But especially on the call_opaque op I want to be able to mark an instance as pure with an attribute for example and then override getMemoryEffects accordingly.

Wouldn't the constant op be able to check for that with the new interface proposed here #142771 ? We can check if the type is scalar and then e.g. say that it doesn't have side effects.

That could be indeed an interesting solution but I haven't looked to close to the new interface and don't know if it allows to do so.

@aniragil
Copy link
Contributor

We could however make emitc.expression IsolatedFromAbove, which IINM would prevent CSE from simplifying it out of the expression. This would both allow form-expressions to run at any point in the pipeline and allow lowering passes to generate expressions safely if desired. WDYT?

Isn't IsolatedFromAbove prevents all uses of above operations, so even if they are in args it won't work, so the whole use of emitc.expression would be pointless since it won't be able to refer any data, where the point is to refer data and apply CExpression operations to it to yield a result?

Sorry, maybe I'm missing something: IsolatedFromAbove indeed prevents using any value defined outside the op, so the CExpressions in emitc.expression would be using their block's args instead of other emitc.expressions directly, but how would that prevent form-expressions from wrapping emitc.constant (as a CExpression) in emitc.expressions and then folding those emitc.expressions into their emitc.expression users?
Applying CSE after form-expressions would still fold identical emitc.constant ops within emitc.expressions (breaking emitc.expression's tree structure), but this could be handled by the translator.

@jacquesguan
Copy link
Contributor Author

I've thought about side effects in the past and I don't know if we want to simply mark the ops as pure as you might target overloaded C++ classes that do whatever. But especially on the call_opaque op I want to be able to mark an instance as pure with an attribute for example and then override getMemoryEffects accordingly.

Wouldn't the constant op be able to check for that with the new interface proposed here #142771 ? We can check if the type is scalar and then e.g. say that it doesn't have side effects.

I think not only for constantOp but also other ops, we should mark it have no side effect only for builtin types. Because the op with opaque type may use overloaded operator that may access memory or have other side effect.

@kchibisov
Copy link
Contributor

kchibisov commented Jun 18, 2025

I think not only for constantOp but also other ops, we should mark it have no side effect only for builtin types. Because the op with opaque type may use overloaded operator that may access memory or have other side effect.

The other op I think is load at this point, the rest already have checks in place. The point of this interface was to make it also for load when load uses primitive type, so it won't have side effect, instead of prohibiting everyload as we do now.

@jacquesguan
Copy link
Contributor Author

I think not only for constantOp but also other ops, we should mark it have no side effect only for builtin types. Because the op with opaque type may use overloaded operator that may access memory or have other side effect.

The other op I think is load at this point, the rest already have checks in place. The point of this interface was to make it also for load when load uses primitive type, so it won't have side effect, instead of prohibiting everyload as we do now.

Yes, they have check but most just mark it as no sideeffect. By example, all binary operator such as add is set with no side effect, but in C++, we may have an overloaded operator add which may access memory. So I think any op with opaque type should be treated as sideeffect.

@kchibisov
Copy link
Contributor

I agree, but for the PR bringing interface in, the reason was to make the current logic more manageable, but preserve the present behavior, since refactoring and behavior changes are separate matters, but interface was needed to make further more granular adjustments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy