Skip to content

Conversation

@AmrDeveloper
Copy link
Member

Implement support for the OffsetOfExpr

@llvmbot llvmbot added clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project labels Nov 12, 2025
@llvmbot
Copy link
Member

llvmbot commented Nov 12, 2025

@llvm/pr-subscribers-clangir

Author: Amr Hesham (AmrDeveloper)

Changes

Implement support for the OffsetOfExpr


Full diff: https://github.com/llvm/llvm-project/pull/167726.diff

2 Files Affected:

  • (modified) clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp (+17)
  • (added) clang/test/CIR/CodeGen/offset-of.cpp (+93)
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
index c1a36134d8942..c62e30d7e9c98 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
@@ -197,6 +197,8 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
     return emitNullValue(e->getType(), cgf.getLoc(e->getSourceRange()));
   }
 
+  mlir::Value VisitOffsetOfExpr(OffsetOfExpr *e);
+
   mlir::Value VisitOpaqueValueExpr(OpaqueValueExpr *e) {
     if (e->isGLValue())
       return emitLoadOfLValue(cgf.getOrCreateOpaqueLValueMapping(e),
@@ -2164,6 +2166,21 @@ mlir::Value ScalarExprEmitter::VisitUnaryLNot(const UnaryOperator *e) {
   return maybePromoteBoolResult(boolVal, cgf.convertType(e->getType()));
 }
 
+mlir::Value ScalarExprEmitter::VisitOffsetOfExpr(OffsetOfExpr *e) {
+  // Try folding the offsetof to a constant.
+  Expr::EvalResult evalResult;
+  if (e->EvaluateAsInt(evalResult, cgf.getContext())) {
+    mlir::Type type = cgf.convertType(e->getType());
+    llvm::APSInt value = evalResult.Val.getInt();
+    return builder.getConstAPInt(cgf.getLoc(e->getExprLoc()), type, value);
+  }
+
+  cgf.getCIRGenModule().errorNYI(
+      e->getSourceRange(),
+      "ScalarExprEmitter::VisitOffsetOfExpr Can't eval expr as int");
+  return {};
+}
+
 mlir::Value ScalarExprEmitter::VisitUnaryReal(const UnaryOperator *e) {
   QualType promotionTy = getPromotionType(e->getSubExpr()->getType());
   mlir::Value result = VisitRealImag(e, promotionTy);
diff --git a/clang/test/CIR/CodeGen/offset-of.cpp b/clang/test/CIR/CodeGen/offset-of.cpp
new file mode 100644
index 0000000000000..f62da9e6d9413
--- /dev/null
+++ b/clang/test/CIR/CodeGen/offset-of.cpp
@@ -0,0 +1,93 @@
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -Wno-unused-value -emit-llvm %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG
+
+struct Struct {
+  int a;
+  float b;
+  double c;
+  bool d;
+};
+
+void offset_of_builtin() {
+  unsigned long a = __builtin_offsetof(Struct, a);
+  unsigned long b = __builtin_offsetof(Struct, b);
+  unsigned long c = __builtin_offsetof(Struct, c);
+  unsigned long d = __builtin_offsetof(Struct, d);
+}
+
+// CIR: %[[A_ADDR:.*]] = cir.alloca !u64i, !cir.ptr<!u64i>, ["a", init]
+// CIR: %[[B_ADDR:.*]] = cir.alloca !u64i, !cir.ptr<!u64i>, ["b", init]
+// CIR: %[[C_ADDR:.*]] = cir.alloca !u64i, !cir.ptr<!u64i>, ["c", init]
+// CIR: %[[D_ADDR:.*]] = cir.alloca !u64i, !cir.ptr<!u64i>, ["d", init]
+// CIR: %[[CONST_0:.*]] = cir.const #cir.int<0> : !u64i
+// CIR: cir.store {{.*}} %[[CONST_0]], %[[A_ADDR]] : !u64i, !cir.ptr<!u64i>
+// CIR: %[[CONST_4:.*]] = cir.const #cir.int<4> : !u64i
+// CIR: cir.store {{.*}} %[[CONST_4]], %[[B_ADDR]] : !u64i, !cir.ptr<!u64i>
+// CIR: %[[CONST_8:.*]] = cir.const #cir.int<8> : !u64i
+// CIR: cir.store {{.*}} %[[CONST_8]], %[[C_ADDR]] : !u64i, !cir.ptr<!u64i>
+// CIR: %[[CONST_16:.*]] = cir.const #cir.int<16> : !u64i
+// CIR: cir.store {{.*}} %[[CONST_16]], %[[D_ADDR]] : !u64i, !cir.ptr<!u64i>
+
+// LLVM: %[[A_ADDR:.*]] = alloca i64, i64 1, align 8
+// LLVM: %[[B_ADDR:.*]] = alloca i64, i64 1, align 8
+// LLVM: %[[C_ADDR:.*]] = alloca i64, i64 1, align 8
+// LLVM: %[[D_ADDR:.*]] = alloca i64, i64 1, align 8
+// LLVM: store i64 0, ptr %[[A_ADDR]], align 8
+// LLVM: store i64 4, ptr %[[B_ADDR]], align 8
+// LLVM: store i64 8, ptr %[[C_ADDR]], align 8
+// LLVM: store i64 16, ptr %[[D_ADDR]], align 8
+
+// OGCG: %[[A_ADDR:.*]] = alloca i64, align 8
+// OGCG: %[[B_ADDR:.*]] = alloca i64, align 8
+// OGCG: %[[C_ADDR:.*]] = alloca i64, align 8
+// OGCG: %[[D_ADDR:.*]] = alloca i64, align 8
+// OGCG: store i64 0, ptr %[[A_ADDR]], align 8
+// OGCG: store i64 4, ptr %[[B_ADDR]], align 8
+// OGCG: store i64 8, ptr %[[C_ADDR]], align 8
+// OGCG: store i64 16, ptr %[[D_ADDR]], align 8
+
+struct StructWithArray {
+  Struct array[4][4];
+};
+
+void offset_of_builtin_from_array_element() {
+  unsigned long a = __builtin_offsetof(StructWithArray, array[0][0].a);
+  unsigned long b = __builtin_offsetof(StructWithArray, array[1][1].b);
+  unsigned long c = __builtin_offsetof(StructWithArray, array[2][2].c);
+  unsigned long d = __builtin_offsetof(StructWithArray, array[3][3].d);
+}
+
+// CIR: %[[A_ADDR:.*]] = cir.alloca !u64i, !cir.ptr<!u64i>, ["a", init]
+// CIR: %[[B_ADDR:.*]] = cir.alloca !u64i, !cir.ptr<!u64i>, ["b", init]
+// CIR: %[[C_ADDR:.*]] = cir.alloca !u64i, !cir.ptr<!u64i>, ["c", init]
+// CIR: %[[D_ADDR:.*]] = cir.alloca !u64i, !cir.ptr<!u64i>, ["d", init]
+// CIR: %[[CONST_0:.*]] = cir.const #cir.int<0> : !u64i
+// CIR: cir.store {{.*}} %[[CONST_0]], %[[A_ADDR]] : !u64i, !cir.ptr<!u64i>
+// CIR: %[[CONST_124:.*]] = cir.const #cir.int<124> : !u64i
+// CIR: cir.store {{.*}} %[[CONST_124]], %[[B_ADDR]] : !u64i, !cir.ptr<!u64i>
+// CIR: %[[CONST_248:.*]] = cir.const #cir.int<248> : !u64i
+// CIR: cir.store {{.*}} %[[CONST_248]], %[[C_ADDR]] : !u64i, !cir.ptr<!u64i>
+// CIR: %[[CONST_376:.*]] = cir.const #cir.int<376> : !u64i
+// CIR: cir.store {{.*}} %[[CONST_376]], %[[D_ADDR]] : !u64i, !cir.ptr<!u64i>
+
+// LLVM: %[[A_ADDR:.*]] = alloca i64, i64 1, align 8
+// LLVM: %[[B_ADDR:.*]] = alloca i64, i64 1, align 8
+// LLVM: %[[C_ADDR:.*]] = alloca i64, i64 1, align 8
+// LLVM: %[[D_ADDR:.*]] = alloca i64, i64 1, align 8
+// LLVM: store i64 0, ptr %[[A_ADDR]], align 8
+// LLVM: store i64 124, ptr %[[B_ADDR]], align 8
+// LLVM: store i64 248, ptr %[[C_ADDR]], align 8
+// LLVM: store i64 376, ptr %[[D_ADDR]], align 8
+
+// OGCG: %[[A_ADDR:.*]] = alloca i64, align 8
+// OGCG: %[[B_ADDR:.*]] = alloca i64, align 8
+// OGCG: %[[C_ADDR:.*]] = alloca i64, align 8
+// OGCG: %[[D_ADDR:.*]] = alloca i64, align 8
+// OGCG: store i64 0, ptr %[[A_ADDR]], align 8
+// OGCG: store i64 124, ptr %[[B_ADDR]], align 8
+// OGCG: store i64 248, ptr %[[C_ADDR]], align 8
+// OGCG: store i64 376, ptr %[[D_ADDR]], align 8

@llvmbot
Copy link
Member

llvmbot commented Nov 12, 2025

@llvm/pr-subscribers-clang

Author: Amr Hesham (AmrDeveloper)

Changes

Implement support for the OffsetOfExpr


Full diff: https://github.com/llvm/llvm-project/pull/167726.diff

2 Files Affected:

  • (modified) clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp (+17)
  • (added) clang/test/CIR/CodeGen/offset-of.cpp (+93)
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
index c1a36134d8942..c62e30d7e9c98 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
@@ -197,6 +197,8 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
     return emitNullValue(e->getType(), cgf.getLoc(e->getSourceRange()));
   }
 
+  mlir::Value VisitOffsetOfExpr(OffsetOfExpr *e);
+
   mlir::Value VisitOpaqueValueExpr(OpaqueValueExpr *e) {
     if (e->isGLValue())
       return emitLoadOfLValue(cgf.getOrCreateOpaqueLValueMapping(e),
@@ -2164,6 +2166,21 @@ mlir::Value ScalarExprEmitter::VisitUnaryLNot(const UnaryOperator *e) {
   return maybePromoteBoolResult(boolVal, cgf.convertType(e->getType()));
 }
 
+mlir::Value ScalarExprEmitter::VisitOffsetOfExpr(OffsetOfExpr *e) {
+  // Try folding the offsetof to a constant.
+  Expr::EvalResult evalResult;
+  if (e->EvaluateAsInt(evalResult, cgf.getContext())) {
+    mlir::Type type = cgf.convertType(e->getType());
+    llvm::APSInt value = evalResult.Val.getInt();
+    return builder.getConstAPInt(cgf.getLoc(e->getExprLoc()), type, value);
+  }
+
+  cgf.getCIRGenModule().errorNYI(
+      e->getSourceRange(),
+      "ScalarExprEmitter::VisitOffsetOfExpr Can't eval expr as int");
+  return {};
+}
+
 mlir::Value ScalarExprEmitter::VisitUnaryReal(const UnaryOperator *e) {
   QualType promotionTy = getPromotionType(e->getSubExpr()->getType());
   mlir::Value result = VisitRealImag(e, promotionTy);
diff --git a/clang/test/CIR/CodeGen/offset-of.cpp b/clang/test/CIR/CodeGen/offset-of.cpp
new file mode 100644
index 0000000000000..f62da9e6d9413
--- /dev/null
+++ b/clang/test/CIR/CodeGen/offset-of.cpp
@@ -0,0 +1,93 @@
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
+// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -Wno-unused-value -emit-llvm %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG
+
+struct Struct {
+  int a;
+  float b;
+  double c;
+  bool d;
+};
+
+void offset_of_builtin() {
+  unsigned long a = __builtin_offsetof(Struct, a);
+  unsigned long b = __builtin_offsetof(Struct, b);
+  unsigned long c = __builtin_offsetof(Struct, c);
+  unsigned long d = __builtin_offsetof(Struct, d);
+}
+
+// CIR: %[[A_ADDR:.*]] = cir.alloca !u64i, !cir.ptr<!u64i>, ["a", init]
+// CIR: %[[B_ADDR:.*]] = cir.alloca !u64i, !cir.ptr<!u64i>, ["b", init]
+// CIR: %[[C_ADDR:.*]] = cir.alloca !u64i, !cir.ptr<!u64i>, ["c", init]
+// CIR: %[[D_ADDR:.*]] = cir.alloca !u64i, !cir.ptr<!u64i>, ["d", init]
+// CIR: %[[CONST_0:.*]] = cir.const #cir.int<0> : !u64i
+// CIR: cir.store {{.*}} %[[CONST_0]], %[[A_ADDR]] : !u64i, !cir.ptr<!u64i>
+// CIR: %[[CONST_4:.*]] = cir.const #cir.int<4> : !u64i
+// CIR: cir.store {{.*}} %[[CONST_4]], %[[B_ADDR]] : !u64i, !cir.ptr<!u64i>
+// CIR: %[[CONST_8:.*]] = cir.const #cir.int<8> : !u64i
+// CIR: cir.store {{.*}} %[[CONST_8]], %[[C_ADDR]] : !u64i, !cir.ptr<!u64i>
+// CIR: %[[CONST_16:.*]] = cir.const #cir.int<16> : !u64i
+// CIR: cir.store {{.*}} %[[CONST_16]], %[[D_ADDR]] : !u64i, !cir.ptr<!u64i>
+
+// LLVM: %[[A_ADDR:.*]] = alloca i64, i64 1, align 8
+// LLVM: %[[B_ADDR:.*]] = alloca i64, i64 1, align 8
+// LLVM: %[[C_ADDR:.*]] = alloca i64, i64 1, align 8
+// LLVM: %[[D_ADDR:.*]] = alloca i64, i64 1, align 8
+// LLVM: store i64 0, ptr %[[A_ADDR]], align 8
+// LLVM: store i64 4, ptr %[[B_ADDR]], align 8
+// LLVM: store i64 8, ptr %[[C_ADDR]], align 8
+// LLVM: store i64 16, ptr %[[D_ADDR]], align 8
+
+// OGCG: %[[A_ADDR:.*]] = alloca i64, align 8
+// OGCG: %[[B_ADDR:.*]] = alloca i64, align 8
+// OGCG: %[[C_ADDR:.*]] = alloca i64, align 8
+// OGCG: %[[D_ADDR:.*]] = alloca i64, align 8
+// OGCG: store i64 0, ptr %[[A_ADDR]], align 8
+// OGCG: store i64 4, ptr %[[B_ADDR]], align 8
+// OGCG: store i64 8, ptr %[[C_ADDR]], align 8
+// OGCG: store i64 16, ptr %[[D_ADDR]], align 8
+
+struct StructWithArray {
+  Struct array[4][4];
+};
+
+void offset_of_builtin_from_array_element() {
+  unsigned long a = __builtin_offsetof(StructWithArray, array[0][0].a);
+  unsigned long b = __builtin_offsetof(StructWithArray, array[1][1].b);
+  unsigned long c = __builtin_offsetof(StructWithArray, array[2][2].c);
+  unsigned long d = __builtin_offsetof(StructWithArray, array[3][3].d);
+}
+
+// CIR: %[[A_ADDR:.*]] = cir.alloca !u64i, !cir.ptr<!u64i>, ["a", init]
+// CIR: %[[B_ADDR:.*]] = cir.alloca !u64i, !cir.ptr<!u64i>, ["b", init]
+// CIR: %[[C_ADDR:.*]] = cir.alloca !u64i, !cir.ptr<!u64i>, ["c", init]
+// CIR: %[[D_ADDR:.*]] = cir.alloca !u64i, !cir.ptr<!u64i>, ["d", init]
+// CIR: %[[CONST_0:.*]] = cir.const #cir.int<0> : !u64i
+// CIR: cir.store {{.*}} %[[CONST_0]], %[[A_ADDR]] : !u64i, !cir.ptr<!u64i>
+// CIR: %[[CONST_124:.*]] = cir.const #cir.int<124> : !u64i
+// CIR: cir.store {{.*}} %[[CONST_124]], %[[B_ADDR]] : !u64i, !cir.ptr<!u64i>
+// CIR: %[[CONST_248:.*]] = cir.const #cir.int<248> : !u64i
+// CIR: cir.store {{.*}} %[[CONST_248]], %[[C_ADDR]] : !u64i, !cir.ptr<!u64i>
+// CIR: %[[CONST_376:.*]] = cir.const #cir.int<376> : !u64i
+// CIR: cir.store {{.*}} %[[CONST_376]], %[[D_ADDR]] : !u64i, !cir.ptr<!u64i>
+
+// LLVM: %[[A_ADDR:.*]] = alloca i64, i64 1, align 8
+// LLVM: %[[B_ADDR:.*]] = alloca i64, i64 1, align 8
+// LLVM: %[[C_ADDR:.*]] = alloca i64, i64 1, align 8
+// LLVM: %[[D_ADDR:.*]] = alloca i64, i64 1, align 8
+// LLVM: store i64 0, ptr %[[A_ADDR]], align 8
+// LLVM: store i64 124, ptr %[[B_ADDR]], align 8
+// LLVM: store i64 248, ptr %[[C_ADDR]], align 8
+// LLVM: store i64 376, ptr %[[D_ADDR]], align 8
+
+// OGCG: %[[A_ADDR:.*]] = alloca i64, align 8
+// OGCG: %[[B_ADDR:.*]] = alloca i64, align 8
+// OGCG: %[[C_ADDR:.*]] = alloca i64, align 8
+// OGCG: %[[D_ADDR:.*]] = alloca i64, align 8
+// OGCG: store i64 0, ptr %[[A_ADDR]], align 8
+// OGCG: store i64 124, ptr %[[B_ADDR]], align 8
+// OGCG: store i64 248, ptr %[[C_ADDR]], align 8
+// OGCG: store i64 376, ptr %[[D_ADDR]], align 8

Copy link
Contributor

@andykaylor andykaylor left a comment

Choose a reason for hiding this comment

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

lgtm

@AmrDeveloper AmrDeveloper merged commit 30c8465 into llvm:main Nov 15, 2025
9 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants