https://github.com/FireBurn created 
https://github.com/llvm/llvm-project/pull/237119

This adds `__builtin_imgrel32(const void *)`, which returns the offset of a 
global variable or function from the image base as an `unsigned` constant. It 
is only available on COFF targets, and the argument has to be the address of a 
global, an element of one, or a function. It is accepted in constant 
initializers and emitted as `trunc(ptrtoint(P) - ptrtoint(__ImageBase))`, which 
the backend lowers to an image relative relocation (`IMAGE_REL_AMD64_ADDR32NB` 
on x86-64). The MinGW case needs #237118

There is currently no way to write this in C. MSVC style RTTI and exception 
tables hold 32-bit image relative references to other objects, and Wine, which 
builds its PE modules with MinGW, writes them in assembly inside a function 
body. That assembly is not visible to the ThinLTO symbol table, so a symbol 
defined there is undefined at link time, and the only workarounds are to 
exclude files from LTO or to initialise the tables at run time. With the 
builtin the tables are ordinary static data, for example

```c
const rtti_object_locator foo_rtti = { 1, 0, 0, 
__builtin_imgrel32(&foo_type_info), __builtin_imgrel32(&foo_hierarchy), 
__builtin_imgrel32(&foo_rtti) };
```

I considered making `(int)((char *)&x - &__ImageBase)` constant instead. Clang 
rejects it as a non-constant initializer, and the result cannot be an `APValue` 
lvalue because it is narrower than a pointer, so a builtin that is handled in 
`ConstExprEmitter` was the smaller change. It is not handled by the constant 
evaluator, so it cannot be used in `constexpr` or in integer constant 
expressions. I would like to know if that limitation is acceptable or if you 
would rather see evaluator support

The patch adds the builtin to `Builtins.td`, the Sema check (COFF only, 
constant address argument), `Expr::isConstantInitializer`, `ConstExprEmitter`, 
two tests, and documentation and a release note

AI disclosure: this patch was written with Claude Code. I built clang and lld 
from this change plus #237118 and ran the RUN lines of both new tests by hand; 
they pass. I did not run the clang lit suite. I also used the builtin in Wine's 
`msvcrt` and `msvcp90` RTTI and exception type tables, linked them with thin 
LTO using the lld from the same build, and ran Wine's msvcrt `cpp` and msvcp90 
`ios`, `misc` and `string` tests: 0 failures. The same code with a deliberately 
wrong offset fails, and the system lld without #237118 fails on it with the 
`__ImageBase` error

Assisted-by: Claude


>From dfc6cfb92fa09c7b41712c0a2c433afbefaa71d6 Mon Sep 17 00:00:00 2001
From: Mike Lothian <[email protected]>
Date: Fri, 30 Oct 2026 07:07:51 +0000
Subject: [PATCH] [clang] Add __builtin_imgrel32

The builtin returns the offset of a global variable or function from the
image base as an unsigned 32-bit constant. It is accepted in constant
initializers on COFF targets and is emitted as a difference from
__ImageBase, which the backend lowers to an image relative relocation.

C has no other way to write this. MSVC style run-time type information
and exception tables hold 32-bit image relative references to other
objects, and without a way to express them in C they have to be written
in assembly. Assembly in a function body is invisible to ThinLTO, so a
symbol defined there is undefined at link time.

Assisted-by: Claude
---
 clang/docs/LanguageExtensions.md              | 24 ++++++++++++
 clang/docs/ReleaseNotes.md                    |  4 ++
 clang/include/clang/Basic/Builtins.td         |  6 +++
 .../clang/Basic/DiagnosticSemaKinds.td        |  6 +++
 clang/lib/AST/Expr.cpp                        |  6 +++
 clang/lib/CodeGen/CGExprConstant.cpp          | 27 ++++++++++++++
 clang/lib/Sema/SemaChecking.cpp               | 37 +++++++++++++++++++
 clang/test/CodeGen/builtin-imgrel32.c         | 27 ++++++++++++++
 clang/test/Sema/builtin-imgrel32.c            | 16 ++++++++
 9 files changed, 153 insertions(+)
 create mode 100644 clang/test/CodeGen/builtin-imgrel32.c
 create mode 100644 clang/test/Sema/builtin-imgrel32.c

diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index 3db8d083c40756..3cca2fba131cff 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -4055,6 +4055,30 @@ template<typename T> constexpr T *addressof(T &value) {
 }
 ```
 
+### `__builtin_imgrel32`
+
+`__builtin_imgrel32` returns the offset of a global variable or function from
+the image base, as a 32-bit unsigned integer. It is only available on COFF
+targets, and the argument must be the address of a global variable, an element
+of one, or a function. The result is an address constant that the linker
+resolves, so it can initialize static data. It lowers to an image relative
+relocation (`IMAGE_REL_AMD64_ADDR32NB` on x86-64), the same relocation MSVC
+uses for C++ run-time type information.
+
+**Syntax**:
+
+```c++
+unsigned __builtin_imgrel32(const void *address)
+```
+
+**Example of use**:
+
+```c++
+extern int data;
+struct locator { unsigned signature, data_rva; };
+const struct locator locator = { 1, __builtin_imgrel32(&data) };
+```
+
 ### `__builtin_function_start`
 
 `__builtin_function_start` returns the address of a function body.
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 53d688d2e11e7c..ee1687062f5df5 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -329,6 +329,10 @@ documentation](CIR/index.md) for more information 
regarding ClangIR's status and
 
 - Added support for the `__builtin_strlcat` and `__builtin_strlcpy` builtins.
 
+- Added `__builtin_imgrel32`, which returns the offset of a global from the 
image
+  base as a constant, for static data that needs image relative references on
+  COFF targets.
+
 - Added `__builtin_sort_pack` to sort a pack of types using the same
   order as `__builtin_type_order`.
 
diff --git a/clang/include/clang/Basic/Builtins.td 
b/clang/include/clang/Basic/Builtins.td
index d04fd899eb63a6..326a1c461e1334 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -4963,6 +4963,12 @@ def BuiltinFunctionStart : Builtin {
   let Prototype = "void*(void&)";
 }
 
+def BuiltinImgrel32 : Builtin {
+  let Spellings = ["__builtin_imgrel32"];
+  let Attributes = [NoThrow, Const, CustomTypeChecking];
+  let Prototype = "unsigned int(void const*)";
+}
+
 def BuiltinOperatorNew : Builtin {
   let Spellings = ["__builtin_operator_new"];
   let Attributes = [Const, CustomTypeChecking, Constexpr];
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td 
b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 56cc9721781063..2ba37ecb8e4ff9 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1062,6 +1062,12 @@ def warn_fortify_source_overread : Warning<
 def err_function_start_invalid_type: Error<
   "argument must be a function">;
 
+def err_imgrel32_requires_coff : Error<
+  "'__builtin_imgrel32' is only supported on COFF targets">;
+def err_imgrel32_not_address_constant : Error<
+  "argument to '__builtin_imgrel32' must be the address of a global variable "
+  "or function">;
+
 def err_ptrauth_disabled :
   Error<"this target does not support pointer authentication">;
 def err_ptrauth_invalid_key :
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 1ba2ccdf697f9d..51c574edff4e35 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -3412,6 +3412,12 @@ bool Expr::isConstantInitializer(ASTContext &Ctx, bool 
IsForRef,
 
   switch (getStmtClass()) {
   default: break;
+  case CallExprClass:
+    // The offset from the image base is resolved by the linker.
+    if (cast<CallExpr>(this)->getBuiltinCallee() ==
+        Builtin::BI__builtin_imgrel32)
+      return true;
+    break;
   case Stmt::ExprWithCleanupsClass:
     return cast<ExprWithCleanups>(this)->getSubExpr()->isConstantInitializer(
         Ctx, IsForRef, Culprit);
diff --git a/clang/lib/CodeGen/CGExprConstant.cpp 
b/clang/lib/CodeGen/CGExprConstant.cpp
index e2ec07bbae711a..7c060d28fac4ca 100644
--- a/clang/lib/CodeGen/CGExprConstant.cpp
+++ b/clang/lib/CodeGen/CGExprConstant.cpp
@@ -1241,6 +1241,33 @@ class ConstExprEmitter
     return nullptr;
   }
 
+  llvm::Constant *VisitCallExpr(const CallExpr *E, QualType T) {
+    if (E->getBuiltinCallee() != Builtin::BI__builtin_imgrel32)
+      return nullptr;
+
+    // trunc(ptrtoint(P) - ptrtoint(__ImageBase)), which the backend lowers to
+    // an image relative relocation.
+    llvm::Constant *Ptr =
+        Emitter.tryEmitPrivateForMemory(E->getArg(0), E->getArg(0)->getType());
+    if (!Ptr)
+      return nullptr;
+
+    llvm::Module &M = CGM.getModule();
+    llvm::GlobalVariable *ImageBase = M.getNamedGlobal("__ImageBase");
+    if (!ImageBase) {
+      ImageBase =
+          new llvm::GlobalVariable(M, CGM.Int8Ty, /*isConstant=*/true,
+                                   llvm::GlobalValue::ExternalLinkage,
+                                   /*Initializer=*/nullptr, "__ImageBase");
+      CGM.setDSOLocal(ImageBase);
+    }
+
+    llvm::Constant *Diff = llvm::ConstantExpr::getSub(
+        llvm::ConstantExpr::getPtrToInt(Ptr, CGM.IntPtrTy),
+        llvm::ConstantExpr::getPtrToInt(ImageBase, CGM.IntPtrTy));
+    return llvm::ConstantExpr::getTrunc(Diff, CGM.getTypes().ConvertType(T));
+  }
+
   llvm::Constant *VisitCastExpr(const CastExpr *E, QualType destType) {
     if (const auto *ECE = dyn_cast<ExplicitCastExpr>(E))
       CGM.EmitExplicitCastExprType(ECE, Emitter.CGF);
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 406f03a17f7ebe..357b55feb9c9f3 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -307,6 +307,39 @@ static bool BuiltinFunctionStart(Sema &S, CallExpr 
*TheCall) {
                                               TheCall->getBeginLoc());
 }
 
+/// Check the argument to __builtin_imgrel32, the 32-bit offset of a global
+/// from the image base. It is only meaningful as a constant.
+static bool BuiltinImgrel32(Sema &S, CallExpr *TheCall) {
+  if (S.checkArgCount(TheCall, 1))
+    return true;
+
+  if (TheCall->getArg(0)->containsErrors())
+    return true;
+
+  if (!S.Context.getTargetInfo().getTriple().isOSBinFormatCOFF()) {
+    S.Diag(TheCall->getBeginLoc(), diag::err_imgrel32_requires_coff)
+        << TheCall->getSourceRange();
+    return true;
+  }
+
+  ExprResult Arg = S.DefaultFunctionArrayLvalueConversion(TheCall->getArg(0));
+  if (Arg.isInvalid())
+    return true;
+  TheCall->setArg(0, Arg.get());
+
+  Expr::EvalResult Result;
+  if (!Arg.get()->EvaluateAsRValue(Result, S.Context) ||
+      !Result.Val.isLValue() || !Result.Val.getLValueBase() ||
+      !Result.Val.getLValueBase().is<const ValueDecl *>()) {
+    S.Diag(TheCall->getBeginLoc(), diag::err_imgrel32_not_address_constant)
+        << TheCall->getSourceRange();
+    return true;
+  }
+
+  TheCall->setType(S.Context.UnsignedIntTy);
+  return false;
+}
+
 /// Check the number of arguments and set the result type to
 /// the argument type.
 static bool BuiltinPreserveAI(Sema &S, CallExpr *TheCall) {
@@ -3640,6 +3673,10 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, 
unsigned BuiltinID,
     if (BuiltinFunctionStart(*this, TheCall))
       return ExprError();
     break;
+  case Builtin::BI__builtin_imgrel32:
+    if (BuiltinImgrel32(*this, TheCall))
+      return ExprError();
+    break;
   case Builtin::BI__builtin_is_aligned:
   case Builtin::BI__builtin_align_up:
   case Builtin::BI__builtin_align_down:
diff --git a/clang/test/CodeGen/builtin-imgrel32.c 
b/clang/test/CodeGen/builtin-imgrel32.c
new file mode 100644
index 00000000000000..426b7526b527e3
--- /dev/null
+++ b/clang/test/CodeGen/builtin-imgrel32.c
@@ -0,0 +1,27 @@
+// RUN: %clang_cc1 -triple x86_64-w64-mingw32 -emit-llvm -o - %s | FileCheck %s
+// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -emit-llvm -o - %s | 
FileCheck %s
+// RUN: %clang_cc1 -triple aarch64-w64-mingw32 -emit-llvm -o - %s | FileCheck 
%s
+
+// __builtin_imgrel32 is the offset of a global from the image base, which the
+// linker resolves. It is emitted as a difference from __ImageBase.
+
+struct locator {
+  unsigned signature;
+  unsigned self;
+  unsigned data;
+  unsigned element;
+};
+
+int data;
+int array[4];
+void func(void) {}
+
+// CHECK: @locator = {{.*}}constant %struct.locator { i32 1, i32 trunc (i64 
sub (i64 ptrtoint (ptr @locator to i64), i64 ptrtoint (ptr @__ImageBase to 
i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr @data to i64), i64 
ptrtoint (ptr @__ImageBase to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint 
(ptr getelementptr {{.*}}(i8, ptr @array, i64 8) to i64), i64 ptrtoint (ptr 
@__ImageBase to i64)) to i32) }
+// CHECK: @__ImageBase = external {{(dso_local )?}}constant i8
+const struct locator locator = {
+  1, __builtin_imgrel32(&locator), __builtin_imgrel32(&data),
+  __builtin_imgrel32(&array[2]),
+};
+
+// CHECK: @func_rva = {{.*}}constant i32 trunc (i64 sub (i64 ptrtoint (ptr 
@func to i64), i64 ptrtoint (ptr @__ImageBase to i64)) to i32)
+const unsigned func_rva = __builtin_imgrel32(func);
diff --git a/clang/test/Sema/builtin-imgrel32.c 
b/clang/test/Sema/builtin-imgrel32.c
new file mode 100644
index 00000000000000..99d45ef199e2ce
--- /dev/null
+++ b/clang/test/Sema/builtin-imgrel32.c
@@ -0,0 +1,16 @@
+// RUN: %clang_cc1 -triple x86_64-w64-mingw32 -fsyntax-only -verify=coff %s
+// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -fsyntax-only -verify=coff %s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsyntax-only -verify=elf %s
+
+int data;
+
+void test(int *p) {
+  unsigned a = __builtin_imgrel32(&data);
+  // elf-error@-1 {{'__builtin_imgrel32' is only supported on COFF targets}}
+  unsigned b = __builtin_imgrel32(p);
+  // coff-error@-1 {{argument to '__builtin_imgrel32' must be the address of a 
global variable or function}}
+  // elf-error@-2 {{'__builtin_imgrel32' is only supported on COFF targets}}
+  unsigned c = __builtin_imgrel32();
+  // coff-error@-1 {{too few arguments to function call, expected 1, have 0}}
+  // elf-error@-2 {{too few arguments to function call, expected 1, have 0}}
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to