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