llvmorg-github-actions[bot] wrote:

<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang-codegen

Author: Mike Lothian (FireBurn)

<details>
<summary>Changes</summary>

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(&amp;foo_type_info), __builtin_imgrel32(&amp;foo_hierarchy), 
__builtin_imgrel32(&amp;foo_rtti) };
```

I considered making `(int)((char *)&amp;x - &amp;__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


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


9 Files Affected:

- (modified) clang/docs/LanguageExtensions.md (+24) 
- (modified) clang/docs/ReleaseNotes.md (+4) 
- (modified) clang/include/clang/Basic/Builtins.td (+6) 
- (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+6) 
- (modified) clang/lib/AST/Expr.cpp (+6) 
- (modified) clang/lib/CodeGen/CGExprConstant.cpp (+27) 
- (modified) clang/lib/Sema/SemaChecking.cpp (+37) 
- (added) clang/test/CodeGen/builtin-imgrel32.c (+27) 
- (added) clang/test/Sema/builtin-imgrel32.c (+16) 


``````````diff
diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index 3db8d083c4075..3cca2fba131cf 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 53d688d2e11e7..ee1687062f5df 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 d04fd899eb63a..326a1c461e133 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 56cc972178106..2ba37ecb8e4ff 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 1ba2ccdf697f9..51c574edff4e3 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 e2ec07bbae711..7c060d28fac4c 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 406f03a17f7eb..357b55feb9c9f 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 0000000000000..426b7526b527e
--- /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 0000000000000..99d45ef199e2c
--- /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}}
+}

``````````

</details>


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

Reply via email to