Skip to content
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

clang-repl value unit test fails on 32 bit Arm #94994

Closed
DavidSpickett opened this issue Jun 10, 2024 · 19 comments · Fixed by #97071
Closed

clang-repl value unit test fails on 32 bit Arm #94994

DavidSpickett opened this issue Jun 10, 2024 · 19 comments · Fixed by #97071

Comments

@DavidSpickett
Copy link
Collaborator

Since #89811 landed one of the unit tests has been failing on Arm 32 bit:

******************** TEST 'Clang-Unit :: Interpreter/./ClangReplInterpreterTests/10/26' FAILED ********************
Script(shard):
--
GTEST_OUTPUT=json:/home/tcwg-buildbot/worker/clang-armv8-quick/stage1/tools/clang/unittests/Interpreter/./ClangReplInterpreterTests-Clang-Unit-2678467-10-26.json GTEST_SHUFFLE=0 GTEST_TOTAL_SHARDS=26 GTEST_SHARD_INDEX=10 /home/tcwg-buildbot/worker/clang-armv8-quick/stage1/tools/clang/unittests/Interpreter/./ClangReplInterpreterTests
--
Script:
--
/home/tcwg-buildbot/worker/clang-armv8-quick/stage1/tools/clang/unittests/Interpreter/./ClangReplInterpreterTests --gtest_filter=InterpreterTest.Value
--
../llvm/clang/unittests/Interpreter/InterpreterTest.cpp:293: Failure
Expected equality of these values:
  V1.getInt()
    Which is: 0
  42

It does not fail on AArch64. It does fail under qemu emulation too, but with different results. Which points to some UB in the program.

@DavidSpickett DavidSpickett added the clang Clang issues not falling into any other category label Jun 10, 2024
@DavidSpickett
Copy link
Collaborator Author

DavidSpickett commented Jun 10, 2024

Compiling debug builds natively is a nightmare these days due to RAM constraints, so I cross compiled it using the instructions I wrote in #94741 (comment).

When run under qemu we get different numbers:

[ RUN      ] InterpreterTest.Value
/work/open_source/llvm-project/clang/unittests/Interpreter/InterpreterTest.cpp:98: Failure
Expected equality of these values:
  V1.getInt()
    Which is: -390410176
  42

/work/open_source/llvm-project/clang/unittests/Interpreter/InterpreterTest.cpp:99: Failure
Expected equality of these values:
  V1.convertTo<int>()
    Which is: -390410176
  42

Then I debugged it using qemu's GDB stub and gdb-multiarch:

$ gdb-multiarch /work/open_source/build-llvm-arm/tools/clang/unittests/Interpreter/ClangReplInterpreterTests -ex "set sysroot /work/open_source/arm-gnu-toolchain-13.2.Rel1-x86_64-arm-none-linux-gnueabihf/arm-none-linux-gnueabihf/libc/" -ex "set solib-search-path /work/open_source/build-llvm-arm/lib/" -ex "target remote :1234" -ex "b clang/lib/Interpreter/Interpreter.cpp:870" -ex "c"
(gdb) bt
#0  __clang_Interpreter_SetValueNoAlloc (This=0xa4b98, OutVal=0xa4bb8, OpaqueType=0xcfa70) at /work/open_source/llvm-project/clang/lib/Interpreter/Interpreter.cpp:872
#1  0xe8bad030 in __stmts__0 ()
#2  0xe8bad058 in _GLOBAL__sub_I_incr_module_3 ()
#3  0xe8bad048 in __orc_init_func.incr_module_3 ()
#4  0xf9f418d8 in (anonymous namespace)::GenericLLVMIRPlatformSupport::initialize (this=0x141d40, JD=...) at /work/open_source/llvm-project/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp:240
#5  0xff715dd0 in llvm::orc::LLJIT::initialize (this=0xa3ca8, JD=...) at /work/open_source/llvm-project/llvm/include/llvm/ExecutionEngine/Orc/LLJIT.h:199
#6  0xff71389c in clang::IncrementalExecutor::runCtors (this=0x140398) at /work/open_source/llvm-project/clang/lib/Interpreter/IncrementalExecutor.cpp:98
#7  0xff73931c in clang::Interpreter::Execute (this=0xa4b98, T=...) at /work/open_source/llvm-project/clang/lib/Interpreter/Interpreter.cpp:450
#8  0xff739458 in clang::Interpreter::ParseAndExecute (this=0xa4b98, Code=..., V=0xfffef010) at /work/open_source/llvm-project/clang/lib/Interpreter/Interpreter.cpp:462
#9  0x0002d1c4 in (anonymous namespace)::InterpreterTest_Value_Test::TestBody (this=0x7da08) at /work/open_source/llvm-project/clang/unittests/Interpreter/InterpreterTest.cpp:95
#10 0xf8b7d9ac in testing::internal::HandleSehExceptionsInMethodIfSupported<testing::Test, void> (object=0x7da08, method=&virtual testing::Test::TestBody(),
    location=0xf8b18b70 "the test body") at /work/open_source/llvm-project/third-party/unittest/googletest/src/gtest.cc:2612
#11 0xf8b71f30 in testing::internal::HandleExceptionsInMethodIfSupported<testing::Test, void> (object=0x7da08, method=&virtual testing::Test::TestBody(), location=0xf8b18b70 "the test body")
    at /work/open_source/llvm-project/third-party/unittest/googletest/src/gtest.cc:2667
#12 0xf8b49cac in testing::Test::Run (this=0x7da08) at /work/open_source/llvm-project/third-party/unittest/googletest/src/gtest.cc:2687
#13 0xf8b4a568 in testing::TestInfo::Run (this=0x68d08) at /work/open_source/llvm-project/third-party/unittest/googletest/src/gtest.cc:2836
#14 0xf8b4af68 in testing::TestSuite::Run (this=0x7fb40) at /work/open_source/llvm-project/third-party/unittest/googletest/src/gtest.cc:3015
<...>

What's interesting is that the value seen in the failure:

[ RUN      ] InterpreterTest.Value
/work/open_source/llvm-project/clang/unittests/Interpreter/InterpreterTest.cpp:98: Failure
Expected equality of these values:
  V1.getInt()
    Which is: -390410176
  42

Turns up in the varargs as the first argument:

(gdb) p *(int*)args.__ap
$5 = -390410176

Which suggests that the code in __clang_Interpreter_SetValueNoAlloc is doing what it's supposed to, but being given incorrect values.

If I go up the callstack and track the value of what would be the first vararg, r3, I eventually get back to:

(gdb) up
#1  0xe8bad030 in __stmts__0 ()
(gdb) p/x $r3
$5 = 0xe8bad040
(gdb) up
#2  0xe8bad058 in _GLOBAL__sub_I_incr_module_3 ()
(gdb) p/x $r3
$6 = 0xe8bad040
(gdb) up
#3  0xe8bad048 in __orc_init_func.incr_module_3 ()
(gdb) p/x $r3
$7 = 0xe8bad040
(gdb) up
#4  0xf9f418d8 in (anonymous namespace)::GenericLLVMIRPlatformSupport::initialize (this=0x141d40, JD=...) at /work/open_source/llvm-project/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp:240
(gdb) p/x $r3
$8 = 0xe8bad040

Which is branching to the address of __orc_init_func.incr_module_3:

│   0xf9f418cc <(anonymous namespace)::GenericLLVMIRPlatformSupport::initialize(llvm::orc::JITDylib&)+540>  str     r0, [r11, #-20] ; 0xffffffec                                              │
│   0xf9f418d0 <(anonymous namespace)::GenericLLVMIRPlatformSupport::initialize(llvm::orc::JITDylib&)+544>  ldr     r3, [r11, #-20] ; 0xffffffec                                              │
│   0xf9f418d4 <(anonymous namespace)::GenericLLVMIRPlatformSupport::initialize(llvm::orc::JITDylib&)+548>  blx     r3                                                                        │
│  >0xf9f418d8 <(anonymous namespace)::GenericLLVMIRPlatformSupport::initialize(llvm::orc::JITDylib&)+552>  sub     r3, r11, #88    ; 0x58 

According to the ABI and compiler generated code, the value of the int would be in r3 (see https://godbolt.org/z/n6r1Tv9sP). So I think something between GenericLLVMIRPlatformSupport::initialize and __clang_Interpreter_SetValueNoAlloc should have setup the value of r3.

I checked this on AArch64 and saw that __stmts__0 does this.

We get the same set of function calls:

(lldb) bt
* thread #1, name = 'ClangReplInterp', stop reason = signal SIGTRAP
  * frame #0: 0x0000aaaaadd27e98 ClangReplInterpreterTests`__clang_Interpreter_SetValueNoAlloc(This=0x0000aaaabac90a60, OutVal=0x0000aaaabac90aa0, OpaqueType=0x0000aaaabacc72a0) at Interpreter.cpp:871:3
    frame #1: 0x0000fffff7ffa030 JIT(0xfffff7ff6000)`__stmts__0 + 48
    frame #2: 0x0000fffff7ffa05c JIT(0xfffff7ff6000)`_GLOBAL__sub_I_incr_module_3 + 12
    frame #3: 0x0000fffff7ffa048 JIT(0xfffff7ff6000)`__orc_init_func.incr_module_3 + 8
    frame #4: 0x0000aaaaacec3370 ClangReplInterpreterTests`(anonymous namespace)::GenericLLVMIRPlatformSupport::initialize(this=0x0000aaaabac40300, JD=0x0000aaaabadd07d0) at LLJIT.cpp:240:9
    frame #5: 0x0000aaaaadd4011c ClangReplInterpreterTests`llvm::orc::LLJIT::initialize(this=0x0000aaaabac85190, JD=0x0000aaaabadd07d0) at LLJIT.h:199:16
    frame #6: 0x0000aaaaadd3f6f4 ClangReplInterpreterTests`clang::IncrementalExecutor::runCtors(this=0x0000aaaabadc2390) const at IncrementalExecutor.cpp:98:15
    frame #7: 0x0000aaaaadd26270 ClangReplInterpreterTests`clang::Interpreter::Execute(this=0x0000aaaabac90a60, T=0x0000aaaabac9ab90) at Interpreter.cpp:450:32
    frame #8: 0x0000aaaaadd26ea0 ClangReplInterpreterTests`clang::Interpreter::ParseAndExecute(this=0x0000aaaabac90a60, Code=(Data = "x", Length = 1), V=0x0000ffffffffeb70) at Interpreter.cpp:462:27
    frame #9: 0x0000aaaaac3ee9e0 ClangReplInterpreterTests`(anonymous namespace)::InterpreterTest_Value_Test::TestBody(this=0x0000aaaabac84930) at InterpreterTest.cpp:99:26
    frame #10: 0x0000aaaaad1d4d38 ClangReplInterpreterTests`void testing::internal::HandleSehExceptionsInMethodIfSupported<testing::Test, void>(object=0x0000aaaabac84930, method=0x00000000000000010000000000000020, location="the test body") at gtest.cc:2612:10
    frame #11: 0x0000aaaaad1bcee8 ClangReplInterpreterTests`void testing::internal::HandleExceptionsInMethodIfSupported<testing::Test, void>(object=0x0000aaaabac84930, method=0x00000000000000010000000000000020, location="the test body") at gtest.cc:2667:12

And in __clang_Interpreter_SetValueNoAlloc, x3 is correct:

(lldb) register read x3
      x3 = 0x000000000000002a

And we see that it gets set before the call:

(lldb) dis
JIT(0xfffff7ff6000)`__stmts__0:
    0xfffff7ffa000 <+0>:  str    x30, [sp, #-0x10]!
    0xfffff7ffa004 <+4>:  adrp   x8, 0
    0xfffff7ffa008 <+8>:  mov    x0, #0xa60                ; =2656
    0xfffff7ffa00c <+12>: mov    x2, #0x72a0               ; =29344
    0xfffff7ffa010 <+16>: ldr    x8, [x8, #0x70]
    0xfffff7ffa014 <+20>: movk   x0, #0xbac9, lsl #16
    0xfffff7ffa018 <+24>: movk   x2, #0xbacc, lsl #16
    0xfffff7ffa01c <+28>: movk   x0, #0xaaaa, lsl #32
    0xfffff7ffa020 <+32>: movk   x2, #0xaaaa, lsl #32
    0xfffff7ffa024 <+36>: add    x1, x0, #0x40
    0xfffff7ffa028 <+40>: ldrsw  x3, [x8]
    0xfffff7ffa02c <+44>: bl     0xfffff7ffa078
->  0xfffff7ffa030 <+48>: ldr    x30, [sp], #0x10
    0xfffff7ffa034 <+52>: ret

Do we see anything like that in Arm's version?

│   0xe8bad000 <__stmts__0>                         push    {r11, lr}                                                                                                                         │
│   0xe8bad004 <__stmts__0+4>                       sub     sp, sp, #8                                                                                                                        │
│   0xe8bad008 <__stmts__0+8>                       ldr     r0, [pc, #44]   ; 0xe8bad03c <__stmts__0+60>                                                                                      │
│   0xe8bad00c <__stmts__0+12>                      mov     r2, #2672       ; 0xa70                                                                                                           │
│   0xe8bad010 <__stmts__0+16>                      orr     r2, r2, #847872 ; 0xcf000                                                                                                         │
│   0xe8bad014 <__stmts__0+20>                      ldr     r0, [pc, r0]                                                                                                                      │
│   0xe8bad018 <__stmts__0+24>                      ldr     r0, [r0]                                                                                                                          │
│   0xe8bad01c <__stmts__0+28>                      asr     r1, r0, #31                                                                                                                       │
│   0xe8bad020 <__stmts__0+32>                      strd    r0, [sp]                                                                                                                          │
│   0xe8bad024 <__stmts__0+36>                      ldr     r0, [pc, #12]   ; 0xe8bad038 <__stmts__0+56>                                                                                      │
│   0xe8bad028 <__stmts__0+40>                      orr     r1, r0, #32                                                                                                                       │
│   0xe8bad02c <__stmts__0+44>                      bl      0xe8bad060                                                                                                                        │
│  >0xe8bad030 <__stmts__0+48>                      add     sp, sp, #8                                                                                                                        │
│   0xe8bad034 <__stmts__0+52>                      pop     {r11, pc}                                                                                                                         │
│   0xe8bad038 <__stmts__0+56>                      muleq   r10, r8, r11                                                                                                                      │
│   0xe8bad03c <__stmts__0+60>                                      ; <UNDEFINED> instruction: 0xffffeffc

I need to look more to confirm but though there are loads they look to be close to the PC, and they don't write to r3. This leaves r3 set to the function address and causes the test failure.

DavidSpickett added a commit that referenced this issue Jun 10, 2024
The original bug is legitimate UB, but this is in the clang constant
expression interpreter, not clang-repl.

#94994 covers investigation
of this specific bug in clang-repl.
@vgvassilev
Copy link
Contributor

Adding @lhames and @weliveindetail for more wisdom about the JIT infrastructure.

@EugeneZelenko EugeneZelenko added clang-tools-extra test-suite and removed clang Clang issues not falling into any other category labels Jun 10, 2024
@DavidSpickett
Copy link
Collaborator Author

FWIW, I am able to use printf fine on Arm, and that's variadic:

$ qemu-arm-static -L /work/open_source/arm-gnu-toolchain-13.2.Rel1-x86_64-arm-none-linux-gnueabihf/arm-none-linux-gnueabihf/libc/  ./bin/clang-repl
clang-repl> #include <stdio.h>
clang-repl> printf("%d\n", 42);
42
clang-repl> int x = 42;
clang-repl> x
Not implement yet.
clang-repl> printf("%d\n", 42);
42

I also thought maybe there is some circumstance where __clang_Interpreter_SetValueNoAlloc ends up called with no arguments. Perhaps if int x = 42; is actually processed as int x; then x = 42. The first call would be undefined behaviour as there's no argument to get from the varargs.

@vgvassilev
Copy link
Contributor

Perhaps it is the whole typecasting business we do but you say that it is already wrong there.

In that case perhaps it is the way we take the ownership of the execution result around here

static ExprResult transformForValuePrinting(RuntimeInterfaceBuilder *Builder,
?

@vgvassilev
Copy link
Contributor

Another place to look is here

where we fetch from an union-like struct and maybe the casts went wrong but I do not see how…

@weliveindetail
Copy link
Contributor

I cannot look into it more closely right now, but is there a difference between Arm and Thumb code? If so, it might be a relocation issue.

@DavidSpickett
Copy link
Collaborator Author

I tried adding -marm or -mthumb to the flags for building clang-repl, and the flags here:

Args ClangArgs = {"-Xclang", "-emit-llvm-only"};

No combination of those fixed the issue. Assuming that's how I was supposed to try it. I did see __stmts__0's code change when Thumb was enabled in the unit test, so that did something at least.

@weliveindetail
Copy link
Contributor

I did see __stmts__0's code change when Thumb was enabled in the unit test, so that did something at least

Yes, that's what I wanted to confirm. If it happens in both ISA modes, Arm and Thumb, then it's likely not an issue with the AArch32 backend in JITLink. They use different relocation types, indirection stubs, etc. and probably they aren't both broken the same way.

@weliveindetail
Copy link
Contributor

If this issue is still open in 2 weeks, I can take a look :)

Lukacma pushed a commit to Lukacma/llvm-project that referenced this issue Jun 12, 2024
The original bug is legitimate UB, but this is in the clang constant
expression interpreter, not clang-repl.

llvm#94994 covers investigation
of this specific bug in clang-repl.
DavidSpickett added a commit that referenced this issue Jun 18, 2024
…)"

This reverts commit edd6f0c.

The newly added test uncovered a pre-existing issue on Arm 32 bit,
so as we did #94994, disable
it while we find the problem.
@DavidSpickett
Copy link
Collaborator Author

#95911 may have the same cause as this.

@weliveindetail
Copy link
Contributor

The issue reproduces with both, GCC and Clang. So it's likely not a toolchain issue. I didn't find anything suspicious in the jitlink debug dumps and the repro hits for different sub arches (armv6kz and armv7) and both modes, arm and thumb. So it's likely not a relocation issue or anything else in jitlink.

I checked the patch again that apparently introduced the bug. It keeps passing the value of the clang::Value::Storage type to __clang_Interpreter_SetValueNoAlloc but it now uses a variable argument list!

extern "C" int clangInterpreterSetValueRepro(void *, void *, void *, ...) {
  va_list args;
  va_start(args, /*last named param*/ OpaqueType);
  int Result = va_arg(args, int);
  va_end(args);
  return Result;
}

int main() {
  printf("sizeof(clang::Value::Storage) = %lu\n", sizeof(Storage));

  Storage test;
  test.m_Int = 42;
  printf("Bytes in Storage =");
  const char *ptr = reinterpret_cast<const char *>(&test);
  for (unsigned i = 0; i < sizeof(Storage); i += 1, ptr++)
    printf(" %02x", *ptr);
  printf("\n");

  int Result = clangInterpreterSetValueRepro(nullptr, nullptr, nullptr, test);
  printf("Result via va_arg = 0x%08x\n", Result);
  return 0;
}

I didn't yet investigate further, but this looks really suspicious:

$ clang++-17 MinTest2.cpp -o MinTest2-x86_64
$ ./MinTest2-x86_64
sizeof(clang::Value::Storage) = 16
Bytes in Storage = 2a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Result via va_arg = 0x0000002a
$ arm-none-linux-gnueabihf-g++ MinTest2.cpp -o MinTest2-aarch32
$ qemu-arm-static -L arm-none-linux-gnueabihf-12.2/arm-none-linux-gnueabihf/libc/ MinTest2-aaarch32
sizeof(clang::Value::Storage) = 8
Bytes in Storage = 2a 00 00 00 38 f6 fc 3f
Result via va_arg = 3ffcf638

@weliveindetail
Copy link
Contributor

weliveindetail commented Jun 27, 2024

Ok, short summary from what I found:

  • on the caller ARM passes va_args in registers, but since Storage doesn't fit it's distributed over 2 registers (unconditionally)
  • on the callee side the value gets restored correctly for types that span 2 registers (long double)
  • for all other types, we only read a single register: we get the uninitialized memory half of Storage first (3ffcf638 in the above example) and the actual value in the next va_arg (0000002a)

I wonder if that actually is the expected behavior and x86_64 only works by accident, because we happen to be lucky with va_args order? @DavidSpickett Maybe you know ways to align va_args behavior on ARM or in general?

Here is a draft that fixes the issue by consuming the unused half where necessary: #96900

I could formalize that approach and we submit that, if there are no better solutions? What do you think?

@DavidSpickett
Copy link
Collaborator Author

DavidSpickett commented Jun 28, 2024

extern "C" void REPL_EXTERNAL_VISIBILITY __clang_Interpreter_SetValueNoAlloc(
    void *This, void *OutVal, void *OpaqueType, ...) {

Assuming that this is called as:

__clang_Interpreter_SetValueNoAlloc(<ptr>, <ptr>, union Storage {});

Is it not undefined behaviour to then in __clang_Interpreter_SetValueNoAlloc treat the first va_arg as a different type than it's actually defined as? I have a feeling it could work, but it really feels like UB and I wouldn't be surprised if other architectures than Arm have quirks here.

Would it make more sense to have:

extern "C" void REPL_EXTERNAL_VISIBILITY __clang_Interpreter_SetValueNoAlloc(
    void *This, void *OutVal, void *OpaqueType, union Storage value) {

Then just access the correct member of the union, which should work as normal.

Or does this conflict with the final intent of #89811 ? Is the use of var args crucial to this effort? I assume it is doing something, because we only have one var arg right now but presumably the reason to use them is you might have many in future.

The other option would be to cast all instances of Storage to the type stored within it, before passing it to __clang_Interpreter_SetValueNoAlloc, but that seems like repeating work that __clang_Interpreter_SetValueNoAlloc itself does.

Or you can have var args, but each var arg is always of type Storage and you access the union that way.

extern "C" void REPL_EXTERNAL_VISIBILITY __clang_Interpreter_SetValueNoAlloc(
    void *This, void *OutVal, void *OpaqueType, ...) {
<...>
  va_list args;
  va_start(args, /*last named param*/ OpaqueType);
<...>
  clang::value::Storage str = va_arg(args, clang::value::Storage);
<...>
    case BuiltinType::Bool:
      VRef.setBool(storage.bool);
      break;
<...>
}

Depends what's important here, that the var arg has the concrete type, or that you can have any number of them, or both. If it's just the ability to have any number of them, maybe the fix here is very simple.

@vgvassilev can you comment on that? And if that's a confusing ramble to you, just lay out the goals of using var args from your point of view.

@weliveindetail
Copy link
Contributor

FYI: I got to the bottom of the issue and post a new PR in a bit 🤞

@weliveindetail
Copy link
Contributor

I checked IR dumps from clang-repl today and found that integral types are always extended to 64-bit no matter which system we run on. The runtime interface bindings are generated on to fly in RuntimeInterfaceBuilder and it hard-coded the cast to unsigned long long. PR #97071 fixes that by casting to the size of a pointer, which should match the native register size. What do you think?

@vgvassilev
Copy link
Contributor

I took a few days off and was worried about that issue, I come back and it's solved. @weliveindetail and @DavidSpickett, you both rock!

@vgvassilev
Copy link
Contributor

extern "C" void REPL_EXTERNAL_VISIBILITY __clang_Interpreter_SetValueNoAlloc(
    void *This, void *OutVal, void *OpaqueType, ...) {

Assuming that this is called as:

__clang_Interpreter_SetValueNoAlloc(<ptr>, <ptr>, union Storage {});

Is it not undefined behaviour to then in __clang_Interpreter_SetValueNoAlloc treat the first va_arg as a different type than it's actually defined as? I have a feeling it could work, but it really feels like UB and I wouldn't be surprised if other architectures than Arm have quirks here.

Would it make more sense to have:

extern "C" void REPL_EXTERNAL_VISIBILITY __clang_Interpreter_SetValueNoAlloc(
    void *This, void *OutVal, void *OpaqueType, union Storage value) {

Then just access the correct member of the union, which should work as normal.

Or does this conflict with the final intent of #89811 ? Is the use of var args crucial to this effort? I assume it is doing something, because we only have one var arg right now but presumably the reason to use them is you might have many in future.

The other option would be to cast all instances of Storage to the type stored within it, before passing it to __clang_Interpreter_SetValueNoAlloc, but that seems like repeating work that __clang_Interpreter_SetValueNoAlloc itself does.

Or you can have var args, but each var arg is always of type Storage and you access the union that way.

extern "C" void REPL_EXTERNAL_VISIBILITY __clang_Interpreter_SetValueNoAlloc(
    void *This, void *OutVal, void *OpaqueType, ...) {
<...>
  va_list args;
  va_start(args, /*last named param*/ OpaqueType);
<...>
  clang::value::Storage str = va_arg(args, clang::value::Storage);
<...>
    case BuiltinType::Bool:
      VRef.setBool(storage.bool);
      break;
<...>
}

Depends what's important here, that the var arg has the concrete type, or that you can have any number of them, or both. If it's just the ability to have any number of them, maybe the fix here is very simple.

@vgvassilev can you comment on that? And if that's a confusing ramble to you, just lay out the goals of using var args from your point of view.

The intent of this interface is to provide some conversion between interpreted and compiled values. The trick with the va_args is that I can push practically a void* and reinterpret_cast it to what the type was according to the clang::Value it was represented with. I could use overload dispatch to achieve the same goals but that approach works both for C and C++.

@weliveindetail
Copy link
Contributor

The trick with the va_args is that I can push practically a void* and reinterpret_cast it

Maybe it's worth noting that (compared to the void* approach) va_args can deal with temporaries passed by value. There is no need for results to have a memory address and thus, clang-repl doesn't have to force the respective symbols to l-values. Basically, that's an optimization for in-process execution (which is the only execution form clang-repl supports right now).

@DavidSpickett
Copy link
Collaborator Author

Bots are all green. Thanks @weliveindetail, excellent work!

lravenclaw pushed a commit to lravenclaw/llvm-project that referenced this issue Jul 3, 2024
When generating runtime interface bindings, extend integral types to the
native register size rather than 64-bit per se

Fixes llvm#94994
kbluck pushed a commit to kbluck/llvm-project that referenced this issue Jul 6, 2024
When generating runtime interface bindings, extend integral types to the
native register size rather than 64-bit per se

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

Successfully merging a pull request may close this issue.

4 participants