AdaCore / gnat-llvm

LLVM based GNAT compiler
182 stars 18 forks source link

Android Clang cross-compilation #39

Open anonymix007 opened 3 months ago

anonymix007 commented 3 months ago

I've been trying to build gnat-llvm with clang-r475365b and after some changes it builds just fine. However, compiling Ada programs doesn't work. Shared library build fails at the linking stage:

ld.lld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'system__soft_links__abort_defer'; recompile with -fPIC
ld.lld: error: relocation R_AARCH64_LDST64_ABS_LO12_NC cannot be used against symbol 'system__soft_links__abort_defer'; recompile with -fPIC
ld.lld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'system__soft_links__abort_undefer'; recompile with -fPIC
ld.lld: error: relocation R_AARCH64_LDST64_ABS_LO12_NC cannot be used against symbol 'system__soft_links__abort_undefer'; recompile with -fPIC
ld.lld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'program_error'; recompile with -fPIC
ld.lld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'program_error'; recompile with -fPIC

I believe this is due to libgnat_llvm.a built for x86-64. Is there a way to build it for several architectures at once?

ArnaudCharlet commented 3 months ago

You cannot build libgnat for several architectures at once, although here you may be missing a -fPIC switch instead when building the library? Since we don't know which change you made to the build and what platform/environment you are targetting, it's hard to comment further.

anonymix007 commented 3 months ago

-fPIC flag is present. So unless it's not working properly, it shouldn't be the issue.

I'm targeting Android (Bionic libc, at least aarch64, ideally arm as well). Do you have any suggestions on how to build it? I guess I can just build it twice for different architectures, but I don't see an easy way to build for anything except host.

Changes were made to fix build with Android clang (just a few backports, also had to replace std::optional with llvm optional), I can upload them to GitHub later today.

anonymix007 commented 3 months ago

Object files are built using the following command (maybe with a different order, I'll make a minimal reproducible example later):

./llvm-gcc --target=aarch64-linux-android29 -fPIC -c -gnat2022 -O3 file.adb

The resulting file.o appears to have correct architecture (aarch64), but has those incompatible relocations.

In fact, I even tried to build with --emit-llvm and then used llc -relocation-model=pic with the same exact issue. Are the relocation type stored in LLVM IR?

ArnaudCharlet commented 3 months ago

-fPIC flag is present. So unless it's not working properly, it shouldn't be the issue.

OK, shouldn't be the issue then.

I'm targeting Android (Bionic libc, at least aarch64, ideally arm as well). Do you have any suggestions on how to build it? I guess I can just build it twice for different architectures, but I don't see an easy way to build for anything except host.

You can pass switches used when building gnatlib via e.g:

make -C llvm-interface CFLAGS="-O2 --target=..."

ArnaudCharlet commented 3 months ago

Object files are built using the following command (maybe with a different order, I'll make a minimal reproducible example later):

./llvm-gcc --target=aarch64-linux-android29 -fPIC -c -gnat2022 -O3 file.adb

The resulting file.o appears to have correct architecture (aarch64), but has those incompatible relocations.

OK, if you're already using the --target switch then this is something more subtle and specific to aarch64-linux then or to the LLVM toolchain or binutils you are using.

In fact, I even tried to build with --emit-llvm and then used llc -relocation-model=pic with the same exact issue. Are the relocation type stored in LLVM IR?

This unfortunately goes beyond the general help around GNAT LLVM that we can provide, you'll probably find more help in LLVM or Clang forums.

Arno

anonymix007 commented 3 months ago

make -C llvm-interface CFLAGS="-O2 --target=..."

Will it build cross-compiler or the resulting llvm-gcc will be for aarch64?

Lucretia commented 3 months ago

Can you upload your fork to github? I tried to compile gcc/gnat for Android last week and it failed because of headers, so having a build available would be useful for me.

In your fork, can you specify the installed NDK's llvm?

anonymix007 commented 3 months ago

I'll upload all my changes later today. I actually haven't used NDK because it lacks necessary LLVM libraries and executables (i.e.opt), one will need to build it from sources. Luckily, google has a build script (which I had to patch to retain those LLVM libraries and executables)

Lucretia commented 3 months ago

TBH, I'm not really bothered how it works as long as it does.

anonymix007 commented 3 months ago

It currently doesn't. That's why this issue exists.

I found somewhat similar issue and -Wl,-Bsymbolic helped. I'll try it when I get back to my PC.

ArnaudCharlet commented 3 months ago

make -C llvm-interface CFLAGS="-O2 --target=..."

Will it build cross-compiler or the resulting llvm-gcc will be for aarch64?

No, CFLAGS only influences the runtime build and you are already specifying it. Building a cross compiler is done when configuring LLVM itself via the cmake flag -DLLVM_TARGETS_TO_BUILD= this is what determines the default/only target supported. There might be another LLVM_xxx cmake flag to specify the default BTW.

anonymix007 commented 3 months ago

Runtime is libgnat_llvm.a, right? AFAIR it is for x86 currently, so will something like make gnatlib CFLAGS="-O2 --target=..." will build it for the required target?

I'm using Android LLVM fork with a few patches from this repo applied (+ cherry-picks to fix build because it's 16.0.2), which is already the cross-compiler.

ArnaudCharlet commented 3 months ago

Runtime is libgnat_llvm.a, right? AFAIR it is for x86 currently, so will something like make gnartlib CFLAGS="-O2 --target=..." will build it for the required target?

The runtime is called libgnat.a by default. I don't know where libgnat_llvm.a comes from, I suspect a customization on your end.

Your gnatlib build already includes the --target compiler switch, so I think your runtime is already aarch64 and the issue is aarch64 specific, and the x86_64 is likely a red herring.

I'm using Android LLVM fork with a few patches applied (+ cherry-picks, which is already the cross-compiler.

anonymix007 commented 3 months ago

I don't know where libgnat_llvm.a comes from, I suspect a customization on your end.

Nope, it's probably the compiler itself.

$ readelf -h libgnat.a | grep Machine
  Machine:                           Advanced Micro Devices X86-64
anonymix007 commented 3 months ago

Uploaded changes to gnat-llvm, llvm_android, llvm-project

I'm currently working on a minimal reproducible example of the issue.

anonymix007 commented 3 months ago

Uploaded to libmsg It builds just fine for host (Linux, x86-64) with gcc-ada from system repos using build-linux-ada.sh script once I rename libgnat_pic.a to just libgnat.a in /lib/gcc/x86_64-pc-linux-gnu/13.2.1/adalib. This is either GCC or ArchLinux packaging bug, I guess.

First issue I see is that llvm-gnatmake doesn't support --target option, but rather -target, which in turn isn't actually supported by llvm-gnat1. Using just llvm-gcc allows to build message.o, which is already good. In order to make libmessage.so I've tried to run ld.lld -shared message.o -o android/libmessage.so, but it produced exactly those errors:

ld.lld: error: relocation R_AARCH64_ADD_ABS_LO12_NC cannot be used against symbol 'program_error'; recompile with -fPIC
>>> defined in message.o
>>> referenced by a-conhel.adb:0 (/path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adainclude/a-conhel.adb:0)
>>>               message.o:(message__queue__implementation__te_check)

ld.lld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'system__soft_links__abort_defer'; recompile with -fPIC
>>> defined in message.o
>>> referenced by a-convec.adb:65 (/path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adainclude/a-convec.adb:65)
>>>               message.o:(message__queue__Oconcat__R908b___finalizer)

ld.lld: error: relocation R_AARCH64_LDST64_ABS_LO12_NC cannot be used against symbol 'system__soft_links__abort_defer'; recompile with -fPIC
>>> defined in message.o
>>> referenced by a-convec.adb:65 (/path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adainclude/a-convec.adb:65)
>>>               message.o:(message__queue__Oconcat__R908b___finalizer)

ld.lld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol 'system__soft_links__abort_undefer'; recompile with -fPIC
>>> defined in message.o
>>> referenced by a-convec.adb:0 (/path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adainclude/a-convec.adb:0)
>>>               message.o:(message__queue__Oconcat__R908b___finalizer)

ld.lld: error: relocation R_AARCH64_LDST64_ABS_LO12_NC cannot be used against symbol 'system__soft_links__abort_undefer'; recompile with -fPIC
>>> defined in message.o
>>> referenced by a-convec.adb:65 (/path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adainclude/a-convec.adb:65)
>>>               message.o:(message__queue__Oconcat__R908b___finalizer)

Apparently, I'm not supposed to run linker directly (thanks GCC for these great instructions), so let's try llvm-gnatlink:

/usr/bin/ld: b~message.o: Relocations in generic ELF (EM: 183)
/usr/bin/ld: b~message.o: Relocations in generic ELF (EM: 183)
/usr/bin/ld: b~message.o: Relocations in generic ELF (EM: 183)
/usr/bin/ld: b~message.o: Relocations in generic ELF (EM: 183)
/usr/bin/ld: b~message.o: Relocations in generic ELF (EM: 183)
/usr/bin/ld: b~message.o: Relocations in generic ELF (EM: 183)
/usr/bin/ld: b~message.o: Relocations in generic ELF (EM: 183)
/usr/bin/ld: b~message.o: error adding symbols: file in wrong format
clang: error: linker command failed with exit code 1 (use -v to see invocation)
llvm-gnatlink: error when calling /path/to/llvm-toolchain/gnat-llvm/llvm-interface/bin/llvm-gcc

This is definitely because of mixing aarch64 and x86-64, I've definitely seen similar errors before. Why it even calls host ld? Shouldn't it be using ld.lld from AOSP clang? UPD: There is --LINK option just for that. Seems to do something more interesting now:

ld.lld: warning: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a: archive member 'a-nbnbig.o' is neither ET_REL nor LLVM bitcode
ld.lld: warning: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a: archive member 's-vaispe.o' is neither ET_REL nor LLVM bitcode
ld.lld: warning: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a: archive member 's-valspe.o' is neither ET_REL nor LLVM bitcode
ld.lld: warning: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a: archive member 's-vauspe.o' is neither ET_REL nor LLVM bitcode
ld.lld: warning: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a: archive member 's-vs_int.o' is neither ET_REL nor LLVM bitcode
ld.lld: warning: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a: archive member 's-vs_lli.o' is neither ET_REL nor LLVM bitcode
ld.lld: warning: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a: archive member 's-vs_llu.o' is neither ET_REL nor LLVM bitcode
ld.lld: warning: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a: archive member 's-vs_uns.o' is neither ET_REL nor LLVM bitcode
ld.lld: warning: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a: archive member 's-vsllli.o' is neither ET_REL nor LLVM bitcode
ld.lld: warning: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a: archive member 's-vslllu.o' is neither ET_REL nor LLVM bitcode
ld.lld: error: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a(a-assert.o) is incompatible with b~message.o
ld.lld: error: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a(a-btgbso.o) is incompatible with b~message.o
ld.lld: error: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a(a-calari.o) is incompatible with b~message.o
ld.lld: error: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a(a-calcon.o) is incompatible with b~message.o
ld.lld: error: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a(a-caldel.o) is incompatible with b~message.o
ld.lld: error: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a(a-calend.o) is incompatible with b~message.o
ld.lld: error: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a(a-calfor.o) is incompatible with b~message.o
ld.lld: error: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a(a-catizo.o) is incompatible with b~message.o
ld.lld: error: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a(a-cbdlli.o) is incompatible with b~message.o
ld.lld: error: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a(a-cbhama.o) is incompatible with b~message.o
ld.lld: error: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a(a-cbhase.o) is incompatible with b~message.o
ld.lld: error: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a(a-cbmutr.o) is incompatible with b~message.o
ld.lld: error: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a(a-cborma.o) is incompatible with b~message.o
ld.lld: error: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a(a-cborse.o) is incompatible with b~message.o
ld.lld: error: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a(a-cbprqu.o) is incompatible with b~message.o
ld.lld: error: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a(a-cbsyqu.o) is incompatible with b~message.o
ld.lld: error: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a(a-cdlili.o) is incompatible with b~message.o
ld.lld: error: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a(a-cgaaso.o) is incompatible with b~message.o
ld.lld: error: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a(a-cgarso.o) is incompatible with b~message.o
ld.lld: error: /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a(a-cgcaso.o) is incompatible with b~message.o
ld.lld: error: too many errors emitted, stopping now (use --error-limit=0 to see all errors)
llvm-gnatlink: error when calling /path/to/llvm-toolchain/out/install/linux-x86/clang-dev/bin/ld.lld

So libgnat.a needs to be rebuilt for aarch64.

anonymix007 commented 3 months ago

Building libgnat.a using make gnatlib CFLAGS="-O2 --target=aarch64-linux-android29" fails:

/path/to/llvm-toolchain/out/install/linux-x86/clang-dev/bin/clang -c -x c -O2 --target=aarch64-linux-android29 -Wno-implicit-function-declaration -I/path/to/llvm-toolchain/out/install/linux-x86/clang-dev/include -I../include -DIN_RTS=1 -fexceptions -DSTANDALONE -gdwarf-aranges -I/path/to/llvm-toolchain/gnat-llvm/llvm-interface/lib/rts-native/adainclude /path/to/llvm-toolchain/gnat-llvm/llvm-interface/lib/rts-native/adainclude/initialize.c
/path/to/llvm-toolchain/gnat-llvm/llvm-interface/lib/rts-native/adainclude/init.c:2772:10: fatal error: 'sigtramp.h' file not found
#include "sigtramp.h"
         ^~~~~~~~~~~~
/path/to/llvm-toolchain/out/install/linux-x86/clang-dev/bin/clang -c -x c -O2 --target=aarch64-linux-android29 -Wno-implicit-function-declaration -I/path/to/llvm-toolchain/out/install/linux-x86/clang-dev/include -I../include -DIN_RTS=1 -fexceptions -DSTANDALONE -gdwarf-aranges -I/path/to/llvm-toolchain/gnat-llvm/llvm-interface/lib/rts-native/adainclude /path/to/llvm-toolchain/gnat-llvm/llvm-interface/lib/rts-native/adainclude/socket.c
1 error generated.
/path/to/llvm-toolchain/out/install/linux-x86/clang-dev/bin/clang -c -x c -O2 --target=aarch64-linux-android29 -Wno-implicit-function-declaration -I/path/to/llvm-toolchain/out/install/linux-x86/clang-dev/include -I../include -DIN_RTS=1 -fexceptions -DSTANDALONE -gdwarf-aranges -I/path/to/llvm-toolchain/gnat-llvm/llvm-interface/lib/rts-native/adainclude /path/to/llvm-toolchain/gnat-llvm/llvm-interface/lib/rts-native/adainclude/mkdir.c
9 warnings generated.

   compilation of init.c failed

gprbuild: *** compilation phase failed
make[2]: *** [Makefile:251: quicklib] Error 4
make[2]: Leaving directory '/path/to/llvm-toolchain/gnat-llvm/llvm-interface'
make[1]: *** [Makefile:215: gnatlib] Error 2
make[1]: Leaving directory '/path/to/llvm-toolchain/gnat-llvm/llvm-interface'
make: *** [Makefile:12: gnatlib] Error 2

Apparently, gnat_src directory needs to be in include paths. Still doesn't compile:

/path/to/llvm-toolchain/gnat-llvm/llvm-interface/lib/rts-native/adainclude/init.c:2782:30: error: no member named 'arm_pc' in 'mcontext_t'
  ((mcontext_t *) mcontext)->arm_pc += 2;

clang just picked up OS include, which is why it doesn't work.

However, this is still a GCC bug. aarch64's mcontext_t does not have arm_pc, it's just pc.

The final build command is:

make gnatlib CFLAGS="-O2 --target=aarch64-linux-android29 -fPIC -Darm_pc=pc -I$CLANG_R475365B_CUSTOM/../../../sysroots/ndk/arm64/usr/include -I$CLANG_R475365B_CUSTOM/../../../sysroots/ndk/arm64/usr/include/aarch64-linux-android"
anonymix007 commented 3 months ago

And now we go back to those relocations. There was an -fPIC flag during the build of libgnat.a, but ld.lld still fails with the exact same errors. There are many of them and they're repetitive (except symbol names), so showing just one:

ld.lld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol '__gl_xdr_stream'; recompile with -fPIC
>>> defined in /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a(init.o)
>>> referenced by s-stratt.adb
>>>               s-stratt.o:(system__stream_attributes__i_ad) in archive /path/to/llvm-toolchain/gnat-llvm/llvm-interface//lib/rts-native/adalib/libgnat.a

Interestingly, __gl_xdr_stream is defined in llvm-interface/gnat_src/init.c:

$ readelf -a init.o | grep __gl_xdr_stream
    51: 0000000000000040     4 OBJECT  GLOBAL DEFAULT    5 __gl_xdr_stream

And s-stratt.o has references to it (limited to first 2 lines):

$ readelf -a s-stratt.o -W | grep __gl_xdr_stream
0000000000000004  0000000c00000113 R_AARCH64_ADR_PREL_PG_HI21 0000000000000000 __gl_xdr_stream + 0
0000000000000008  0000000c0000011d R_AARCH64_LDST32_ABS_LO12_NC 0000000000000000 __gl_xdr_stream + 0

Found how it was compiled:

/path/to/llvm-toolchain/gnat-llvm/llvm-interface/bin/llvm-gcc -c -x ada -gnatA -O2 --target=aarch64-linux-android29 -fPIC -Darm_pc=pc -I/path/to/llvm-toolchain/out/install/linux-x86/clang-dev/../../../sysroots/ndk/arm64/usr/include -I/path/to/llvm-toolchain/out/install/linux-x86/clang-dev/../../../sysroots/ndk/arm64/usr/include/aarch64-linux-android -I/path/to/llvm-toolchain/gnat-llvm/llvm-interface/gnat_src -Wno-implicit-function-declaration -I/path/to/llvm-toolchain/out/install/linux-x86/clang-dev/include -nostdinc -I../adainclude -gnatg -gnatpg -gnatec=/tmp/GNAT-TEMP-000035.TMP -gnatem=/tmp/GNAT-TEMP-000048.TMP /path/to/llvm-toolchain/gnat-llvm/llvm-interface/lib/rts-native/adainclude/s-stratt.adb

Note that -fPIC flag is present. Tried to build the following C program just for sanity check:

extern int __gl_xdr_stream;

int get_gl_xdr_stream(void) {
    return __gl_xdr_stream;
}

And it appears to produce different relocation types:

$ readelf -a test.o -W | grep __gl_xdr_stream
0000000000000000  0000000700000137 R_AARCH64_ADR_GOT_PAGE 0000000000000000 __gl_xdr_stream + 0
0000000000000004  0000000700000138 R_AARCH64_LD64_GOT_LO12_NC 0000000000000000 __gl_xdr_stream + 0
     7: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND __gl_xdr_stream

Same with Ada: test_ada.ads:

with Interfaces.C; use Interfaces.C;

package Test_Ada is
   XDR_Stream : int;
   pragma Import (C, XDR_Stream, "__gl_xdr_stream");

   function Get_XDR_Stream return int
     with
       Export        => True,
       Convention    => C,
       External_Name => "get_gl_xdr_stream_ada";
end;

test_ada.adb:

with Interfaces.C; use Interfaces.C;

package body Test_Ada is
   function Get_XDR_Stream return int is
   begin
       return XDR_Stream;
   end;
end;

And these wrong relocations are back again:

$ readelf -a test_ada.o -W | grep __gl_xdr_stream
0000000000000000  0000000500000113 R_AARCH64_ADR_PREL_PG_HI21 0000000000000000 __gl_xdr_stream + 0
0000000000000004  000000050000011d R_AARCH64_LDST32_ABS_LO12_NC 0000000000000000 __gl_xdr_stream + 0
     5: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND __gl_xdr_stream

This is either llvm or gnat-llvm bug, there's literally nothing except them used here. Actually, this is not even aarch64-specific:

$ readelf -a test_ada_x64.o -W | grep __gl_xdr_stream
0000000000000002  0000000300000002 R_X86_64_PC32          0000000000000000 __gl_xdr_stream - 4
     3: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND __gl_xdr_stream

Any suggestions on how to debug this further?

anonymix007 commented 3 months ago

LLVM IR dumps:

; ModuleID = 'test.c'
source_filename = "test.c"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-android29"

@__gl_xdr_stream = external global i32, align 4

; Function Attrs: noinline nounwind optnone uwtable
define i32 @get_gl_xdr_stream() #0 {
  %1 = load i32, ptr @__gl_xdr_stream, align 4
  ret i32 %1
}

attributes #0 = { noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+crc32,+cx16,+cx8,+fxsr,+mmx,+popcnt,+sse,+sse2,+sse3,+sse4.1,+sse4.2,+ssse3,+x87" "tune-cpu"="generic" }

!llvm.module.flags = !{!0, !1, !2, !3}
!llvm.ident = !{!4}

!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{i32 8, !"PIC Level", i32 2}
!2 = !{i32 7, !"uwtable", i32 2}
!3 = !{i32 7, !"frame-pointer", i32 2}
!4 = !{!"Android (dev, NO PGO PROFILE, NO BOLT PROFILE, based on r475365b) clang version 16.0.2 (https://android.googlesource.com/toolchain/llvm-project 080f09fc86284fa4fa7ca0824d87c10c9a763a4b)"}
; ModuleID = 'test_ada.adb'
source_filename = "test_ada.adb"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-android29"

@test_ada_E = dso_local global i16 0, align 2
@__gl_xdr_stream = external dso_local global i32, align 4

define i32 @get_gl_xdr_stream_ada() {
entry:
  %0 = load i32, ptr @__gl_xdr_stream, align 4, !tbaa !2
  ret i32 %0
}

!llvm.module.flags = !{!0, !1}

!0 = !{i32 8, !"PIC Level", i32 2}
!1 = !{i32 7, !"PIE Level", i32 0}
!2 = !{!3, !3, i64 0, i64 4}
!3 = !{!4, i64 4, !"interfaces__c__int#T1"}
!4 = !{!5, i64 4, !"interfaces__c__int#TN"}
!5 = !{!6, i64 4, !"interfaces__c__TintB#TN"}
!6 = !{!"Ada Root"}

@__gl_xdr_stream = external dso_local global i32, align 4

Wow, how nice of llvm-gcc to silently ignore -fPIC.

anonymix007 commented 3 months ago
$ ./main
Error: dlopen failed: cannot locate symbol "_r_debug" referenced by "/data/data/com.termux/files/home/libmsg/libmessage.so"...

There are few other undefined symbols in library (after I added some system libraries to libmessage.so dependencies only these left):

nm -D libmessage.so | grep -i ' u ' | grep -v LIBC
                 U __gnat_sigtramp
                 U _r_debug

Any ideas where can I find them?

anonymix007 commented 3 months ago

I guess I can live without those. Exceptions will not work, but who cares. There are more serious issues. Returning structures doesn't work. Source code is available here. Here's what it prints on Android (some strings omitted):

/data/data/com.termux/files/home/libmsg # ./main
GetColor: hexValue: 0x00000000, color: {0x00, 0x00, 0x00, 0x00}
ColorToInt: hexValue: 0x00680000, color: {0x00, 0x68, 0x00, 0x00}
c_uint_str: value: 0x00680000
GetColor: hexValue: 0x00680000, color: {0x00, 0x68, 0x00, 0x00}
0x00680000

(R =>  0,
 G =>  104,
 B =>  0,
 A =>  0)

(R =>  0,
 G =>  104,
 B =>  0,
 A =>  0)

And on Linux:

GetColor: hexValue: 0x00000000, color: {0x00, 0x00, 0x00, 0x00}
ColorToInt: hexValue: 0x00000000, color: {0x00, 0x00, 0x00, 0x00}
c_uint_str: value: 0x00000000
GetColor: hexValue: 0x00000000, color: {0x00, 0x00, 0x00, 0x00}
0x00000000

(R =>  0,
 G =>  0,
 B =>  0,
 A =>  0)

(R =>  0,
 G =>  0,
 B =>  0,
 A =>  0)

@ArnaudCharlet please reopen. While I found what was the cause of original issue with relocations (and it's certainly gnat-llvm bug), this is most likely another one.

Edit: with hexValue of 0x12345678 it returns 0x12680000. Something is clearly wrong here.

anonymix007 commented 3 months ago

Minimal example would be: test.adb:

with Interfaces.C; use Interfaces.C;

function Test(hexValue: unsigned) return unsigned is
    type Color is record
        r: unsigned_char;
        g: unsigned_char;
        b: unsigned_char;
        a: unsigned_char;
    end record
        with Convention => C_Pass_By_Copy;

    function Get_Color(hexValue: unsigned) return Color
        with
            Import => True,
            Convention => C,
            External_Name => "GetColor";

    function Color_To_Int(C: Color) return unsigned
        with
            Import => True,
            Convention => C,
            External_Name => "ColorToInt";

begin
    return Color_To_Int(Get_Color(hexValue));
end;

test.c:

typedef struct {
    unsigned char r;
    unsigned char g; 
    unsigned char b;
    unsigned char a;
} Color;

extern Color GetColor(unsigned int hexValue);
extern unsigned ColorToInt(Color color);

unsigned c_test(unsigned hexValue) {
    return ColorToInt(GetColor(hexValue));
}

LLVM IR Dumps:

; ModuleID = 'test.adb'
source_filename = "test.adb"
target datalayout = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128"
target triple = "aarch64-unknown-linux-android29"

%test__color = type <{ i8, i8, i8, i8 }>

define i32 @_ada_test(i32 %hexvalue) local_unnamed_addr #0 {
entry:
  %0 = tail call %test__color @GetColor(i32 %hexvalue)
  %.fca.0.extract = extractvalue %test__color %0, 0
  %.fca.1.extract = extractvalue %test__color %0, 1
  %.fca.2.extract = extractvalue %test__color %0, 2
  %.fca.3.extract = extractvalue %test__color %0, 3
  %.sroa.4.0.insert.ext = zext i8 %.fca.3.extract to i32
  %.sroa.4.0.insert.shift = shl nuw i32 %.sroa.4.0.insert.ext, 24
  %.sroa.3.0.insert.ext = zext i8 %.fca.2.extract to i32
  %.sroa.3.0.insert.shift = shl nuw nsw i32 %.sroa.3.0.insert.ext, 16
  %.sroa.3.0.insert.insert = or i32 %.sroa.4.0.insert.shift, %.sroa.3.0.insert.shift
  %.sroa.2.0.insert.ext = zext i8 %.fca.1.extract to i32
  %.sroa.2.0.insert.shift = shl nuw nsw i32 %.sroa.2.0.insert.ext, 8
  %.sroa.2.0.insert.insert = or i32 %.sroa.3.0.insert.insert, %.sroa.2.0.insert.shift
  %.sroa.0.0.insert.ext = zext i8 %.fca.0.extract to i32
  %.sroa.0.0.insert.insert = or i32 %.sroa.2.0.insert.insert, %.sroa.0.0.insert.ext
  %1 = tail call i32 @ColorToInt(i32 %.sroa.0.0.insert.insert)
  ret i32 %1
}

declare %test__color @GetColor(i32) local_unnamed_addr #0

declare i32 @ColorToInt(i32) local_unnamed_addr #0

attributes #0 = { "target-features"="+neon,+v8a," }

!llvm.module.flags = !{!0, !1}

!0 = !{i32 8, !"PIC Level", i32 2}
!1 = !{i32 7, !"PIE Level", i32 0}
; ModuleID = 'test.c'
source_filename = "test.c"
target datalayout = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128"
target triple = "aarch64-unknown-linux-android29"

; Function Attrs: nounwind sspstrong uwtable
define i32 @c_test(i32 noundef %0) local_unnamed_addr #0 {
  %2 = tail call i32 @GetColor(i32 noundef %0) #2
  %3 = zext i32 %2 to i64
  %4 = tail call i32 @ColorToInt(i64 %3) #2
  ret i32 %4
}

declare i32 @ColorToInt(i64) local_unnamed_addr #1

declare i32 @GetColor(i32 noundef) local_unnamed_addr #1

attributes #0 = { nounwind sspstrong uwtable "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+fix-cortex-a53-835769,+fp-armv8,+neon,+outline-atomics,+v8a" }
attributes #1 = { "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+fix-cortex-a53-835769,+fp-armv8,+neon,+outline-atomics,+v8a" }
attributes #2 = { nounwind }

!llvm.module.flags = !{!0, !1, !2, !3}
!llvm.ident = !{!4}

!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{i32 8, !"PIC Level", i32 2}
!2 = !{i32 7, !"uwtable", i32 2}
!3 = !{i32 7, !"frame-pointer", i32 1}

Once again, this is not aarch64-specific. I'm getting the same errors on x86-64 (not even Android!). You may want to reproduce with build-linux-llvm-ada.sh.

ArnaudCharlet commented 3 months ago

I've reopened but I'm afraid this Issue is just too confused and with too many different issues mentioned to be actionable. The last one seems to be an issue with the C_Pass_By_Copy convention.

anonymix007 commented 3 months ago

Do you want me to split them? Currently I found these:

  1. C_Pass_By_Copy doesn't work properly at least for returning structures. Something fishy is also going on with passing them. LoadImage segfaults literally on the first line:
    
    typedef struct Image {
    void *data;
    int width;
    int height;
    int mipmaps;
    int format;
    } Image;

Image LoadImage(const char *fileName) { Image image = { 0 }; //... return image; }

```Ada
type Addr is mod 2 ** Standard'Address_Size;
type Image is record
    Data: Addr;
    Width: int;
    Height: int;
    Mipmaps: int;
    Format: int;
end record
    with Convention => C_Pass_By_Copy;
function Load_Image(File_Name: Char_Array) return Image
    with
        Import => True,
        Convention => C,
        External_Name => "LoadImage";

It actually doesn't segfault in the minimal example and only prints garbage on aarch64. This structure is too big to be passed by copy as it seems, but using just C leads to the same results. UPD As a workaround for passing issue I'm adding pragma Volatile_Full_Access(Addr);. I have no idea what it does, but with it last 2 fields are consistently 0 (as they should be). UPD2 And for returning issue using the following glue code works (unless C_Pass_By_Copy is really needed, i.e. in those GetColor examples):

Image * __glue_LoadImage(Image *__return_storage_ptr__, char *fileName) {
    Image image = LoadImage(fileName);
    memcpy(__return_storage_ptr__, &image, sizeof(image));
    return __return_storage_ptr__;
}

It doesn't segfault anymore, but this is not a good solution.

  1. __gnat_sigtramp is missing. Probably needs to be implemented for aarch64. Looks like GCC issue though.
  2. _r_debug is missing. It appears to be present in linker though:
    $ strings /system/bin/linker | grep -i _r_debug                             __dl__ZL16g__r_debug_mutex
    __dl__r_debug
  3. -fPIC is silently ignored. Fixed in 7f9614d96d2d3da8ee027acdf317c54172369b2b in my fork (please cherry-pick it from there)
  4. target_* flags are parsed incorrectly. They assume no cross-compilation. Found this during research about the __gnat_sigtramp. Attempted to fix in 75323602367d6741475457ca256deb228ed53714
  5. AOSP Clang-specific issues (c67ef99f090394d660b208d314a9c53b7f451b9b), some are due to it being 16.0.2. But others were caused by the use of libstdc++.so
  6. Related to 2 and also probably GCC issue:
    gnat-llvm/llvm-interface/lib/rts-native/adainclude/init.c:2782:30: error: no member named 'arm_pc' in 'struct sigcontext'
    ((mcontext_t *) mcontext)->arm_pc += 2;

    Fixed by -Darm_pc=pc in CFLAGS (though it might be broken completely, I have no idea what is this for).