Jump to page: 1 2
Thread overview
Question about LDC and --gc-sections
Oct 25, 2018
H. S. Teoh
Oct 25, 2018
Jonathan Marler
Oct 26, 2018
H. S. Teoh
Oct 26, 2018
Joakim
Oct 26, 2018
H. S. Teoh
Oct 27, 2018
Joakim
Oct 30, 2018
H. S. Teoh
Oct 26, 2018
Kagamin
Oct 26, 2018
Nicholas Wilson
Oct 26, 2018
kinke
Oct 26, 2018
H. S. Teoh
October 25, 2018
I'm currently working on an Android project that has a significant D component, and using LDC to cross-compile to ARM. (Much thanks to Joakim, BTW, who wrote detailed instructions on the wiki on how to set this up.)  Since Android requires a .so file, I have to statically link everything into a single .so.  However, I'm finding that the resulting .so has tons of unused symbols that bloat the size to about 5MB (~1.6MB after the Android SDK tools compress everything, of which only about 100KB is my actual D code).

Since LDC's libdruntime.a and libphobos2.a already have every function in its own section, technically the linker *ought* to be able to strip out most of the unreferenced sections.  However, running the linker with --gc-sections doesn't seem to reduce the file size significantly, and many unused sections are still present.  Stripping the file with `strip` afterwards still leaves over 10,000 symbols, far too many given the current size of my D code, and clearly an indication of a ton of stuff in druntime/phobos that I don't actually use. I suspect it may be because the target is an .so rather than an executable, so the linker may be leaving in all public symbols as a precaution.

How do I tell the linker (clang) to drop everything except the small handful of entry points required by the Android API?


T

-- 
Public parking: euphemism for paid parking. -- Flora
October 25, 2018
On Thursday, 25 October 2018 at 17:19:05 UTC, H. S. Teoh wrote:
> I'm currently working on an Android project that has a significant D component, and using LDC to cross-compile to ARM. (Much thanks to Joakim, BTW, who wrote detailed instructions on the wiki on how to set this up.)  Since Android requires a .so file, I have to statically link everything into a single .so.  However, I'm finding that the resulting .so has tons of unused symbols that bloat the size to about 5MB (~1.6MB after the Android SDK tools compress everything, of which only about 100KB is my actual D code).
>
> Since LDC's libdruntime.a and libphobos2.a already have every function in its own section, technically the linker *ought* to be able to strip out most of the unreferenced sections.  However, running the linker with --gc-sections doesn't seem to reduce the file size significantly, and many unused sections are still present.  Stripping the file with `strip` afterwards still leaves over 10,000 symbols, far too many given the current size of my D code, and clearly an indication of a ton of stuff in druntime/phobos that I don't actually use. I suspect it may be because the target is an .so rather than an executable, so the linker may be leaving in all public symbols as a precaution.
>
> How do I tell the linker (clang) to drop everything except the small handful of entry points required by the Android API?
>
>
> T

I don't know the answer to this question, but one thing you might try.  As an experiment, you could write a dummy main function that calls all the entry points you need to expose and then link it as an executable with --gc-sections and see how many symbols you are left with.  I'm very curious about this one so hopefully someone knows the answer to this.
October 26, 2018
On Thursday, 25 October 2018 at 17:19:05 UTC, H. S. Teoh wrote:
> I'm currently working on an Android project that has a significant D component, and using LDC to cross-compile to ARM. (Much thanks to Joakim, BTW, who wrote detailed instructions on the wiki on how to set this up.)  Since Android requires a .so file, I have to statically link everything into a single .so.  However, I'm finding that the resulting .so has tons of unused symbols that bloat the size to about 5MB (~1.6MB after the Android SDK tools compress everything, of which only about 100KB is my actual D code).
>
> Since LDC's libdruntime.a and libphobos2.a already have every function in its own section, technically the linker *ought* to be able to strip out most of the unreferenced sections.  However, running the linker with --gc-sections doesn't seem to reduce the file size significantly, and many unused sections are still present.  Stripping the file with `strip` afterwards still leaves over 10,000 symbols, far too many given the current size of my D code, and clearly an indication of a ton of stuff in druntime/phobos that I don't actually use. I suspect it may be because the target is an .so rather than an executable, so the linker may be leaving in all public symbols as a precaution.
>
> How do I tell the linker (clang) to drop everything except the small handful of entry points required by the Android API?

Apparently this is a problem for C++ too:

https://stackoverflow.com/questions/18115598/how-to-remove-all-unused-symbols-from-a-shared-library

I don't know if D provides some way to change symbol visibility in object files, not an issue I've looked at.

Btw, are you writing a mostly native app that calls some D code or a Java app that calls some native code? I ask because I ran into an issue before depending on how it's set up, though you won't hit this if you're using D in betterC mode.

The difference is that there are two ways of calling native code on Android:

1. You can have the Android Java runtime directly call native code on _startup_ if you provide some standard native functions that it expects:

https://developer.android.com/ndk/reference/group/native-activity#group___native_activity_1ga02791d0d490839055169f39fdc905c5e

2. You have your Java app call some native functions using JNI.

Both have been tried with D, but the latter may present some difficulty for initializing the D runtime and GC. When I tried the latter with a sample app, it seemed to be loading the D library _every time_ the D functions were called, so I had to add a call to rt_init every time the function was called:

https://github.com/joakim-noah/android/blob/master/samples/bitmap-plasma/jni/plasma.d#L357

I initially had that call inside the initialization check just below, and it wouldn't work. I never investigated further though, as I have no interest in using D that way, I could be completely off on my guess as to why it didn't work.

For the way I use D and test it, the rt_init is called once on startup, as noted on the wiki, so everything works:

https://wiki.dlang.org/Build_D_for_Android#Changes_for_Android

Of course, you can always call Java functions through JNI if you go this route, as the Teapot sample app demonstrates, just something to consider in how you call D code.
October 26, 2018
On Thursday, 25 October 2018 at 17:19:05 UTC, H. S. Teoh wrote:
> Since LDC's libdruntime.a and libphobos2.a already have every function in its own section, technically the linker *ought* to be able to strip out most of the unreferenced sections.  However, running the linker with --gc-sections doesn't seem to reduce the file size significantly, and many unused sections are still present.

IIRC --gc-sections worked fine on executable, on windows I use llvm-lto tool https://forum.dlang.org/post/apwqbvaerqgmvnsxlttg@forum.dlang.org (mostly because --gc-sections doesn't work there), but you need everything in bitcode form.
October 26, 2018
On Friday, 26 October 2018 at 08:43:18 UTC, Kagamin wrote:
> but you need everything in bitcode form.

I can't remember if we ship LTO libraries, but ldc-build-runtime will do that for you for phobos + druntime.
October 26, 2018
On Thursday, 25 October 2018 at 17:19:05 UTC, H. S. Teoh wrote:
> I suspect it may be because the target is an .so rather than an executable, so the linker may be leaving in all public symbols as a precaution.

Seems like it. The size of a std.stdio hello-world executable with static druntime/Phobos on Linux x64 is about 900 KB, as shared lib it's > 6 MB.

> How do I tell the linker (clang) to drop everything except the small handful of entry points required by the Android API?

What you could try is using an ld version script to override symbol visibility - create a little text file like this:

---
CODEABI_1.0 {
    global: *entry_point*;
    local: *;
};
---

and then pass it to ld via `ldc2 ... -L--version-script=<path>`. It got the size down to ~900 KB for my dummy .so. Found here: http://anadoxin.org/blog/control-over-symbol-exports-in-gcc.html
October 26, 2018
On Thursday, 25 October 2018 at 18:09:35 UTC, Jonathan Marler wrote:
[...]
> [...]  As an experiment, you could write a dummy main function that calls all the entry points you need to expose and then link it as an executable with --gc-sections and see how many symbols you are left with.  I'm very curious about this one so hopefully someone knows the answer to this.

I did a quick test, and here are the results:

.so file size with (and without) --gc-sections: 6.1MB

Executable size with --gc-sections: 8.75KB


--T
October 26, 2018
On Friday, 26 October 2018 at 06:02:51 UTC, Joakim wrote:
[...]
> Btw, are you writing a mostly native app that calls some D code or a Java app that calls some native code? I ask because I ran into an issue before depending on how it's set up, though you won't hit this if you're using D in betterC mode.

My main code is in D, but consists of a bunch of functions repeatedly called from the Java wrapper as native functions. Main reason is that for GUI interactions it makes more sense to leverage the Java APIs provided by Android, rather than to go through the pain of marshalling and calling Java functions via JNIEnv. (There are a couple of places where this is necessary, e.g., when D code needs to trigger a GUI action, but I'm trying to keep this to a minimum.)


> The difference is that there are two ways of calling native code on Android:
>
> 1. You can have the Android Java runtime directly call native code on _startup_ if you provide some standard native functions that it expects:
>
> https://developer.android.com/ndk/reference/group/native-activity#group___native_activity_1ga02791d0d490839055169f39fdc905c5e

That's something to look into, I suppose.


> 2. You have your Java app call some native functions using JNI.
>
> Both have been tried with D, but the latter may present some difficulty for initializing the D runtime and GC. When I tried the latter with a sample app, it seemed to be loading the D library _every time_ the D functions were called, so I had to add a call to rt_init every time the function was called:
>
> https://github.com/joakim-noah/android/blob/master/samples/bitmap-plasma/jni/plasma.d#L357
>
> I initially had that call inside the initialization check just below, and it wouldn't work. I never investigated further though, as I have no interest in using D that way, I could be completely off on my guess as to why it didn't work.

Currently, I call rt_init() from a native function called from Java whenever onSurfaceCreated is called (I'm using GLES2).  I don't know if Android reloads the library every time, but I see why it might, seeing that it can shut down activities anytime without (much) notice.  So far, I haven't encountered any problems.

Other native functions do save static state in TLS, and so far I haven't encountered any problems.  But maybe I just haven't triggered the problematic cases yet.


> For the way I use D and test it, the rt_init is called once on startup, as noted on the wiki, so everything works:
>
> https://wiki.dlang.org/Build_D_for_Android#Changes_for_Android
>
> Of course, you can always call Java functions through JNI if you go this route, as the Teapot sample app demonstrates, just something to consider in how you call D code.

Currently I'm expecting to just write the Activity code in Java and have it easily interact with the Android GUI APIs, and call the main logic in D via JNI.  I *could* use the NDK APIs if I really wanted to, I suppose, but I don't really have a strong reason to do that currently.  (The main reason I looked into using D at all in this project was because I got frustrated trying to write complex non-GUI code in Java.  So far, I've migrated most of the original Java code to D, leaving the Java code merely as thin wrappers that just forward the main logic to D via JNI.  It has worked well thus far, and I'll probably just leave it this way, unless I run into something that would be far better off written in D.  I'm not looking forward to interfacing with Android GUI classes via JNI, though.  JNI is a royal pain to use, esp. when going from D to Java.)

I do have an empty main() in the .so, though, per your recommendations on that wiki page.  Does that make a difference?


--T
October 26, 2018
On Friday, 26 October 2018 at 13:03:58 UTC, kinke wrote:
[...]
> What you could try is using an ld version script to override symbol visibility - create a little text file like this:
>
> ---
> CODEABI_1.0 {
>     global: *entry_point*;
>     local: *;
> };
> ---
>
> and then pass it to ld via `ldc2 ... -L--version-script=<path>`. It got the size down to ~900 KB for my dummy .so. Found here: http://anadoxin.org/blog/control-over-symbol-exports-in-gcc.html

Whoa.  That did the trick!

Before: 6.1MB
After: 1.1MB

Not quite the 600KB of the executable, but good enough, in the same ballpark.  Now my APK is back down to a sane size (relative to its contents)!

Thanks for the tip!!!

P.S. For anyone else interested in this: since I'm mainly calling my D code from Java via JNI, I set up the version script to have symbols of the form `Java_*` be marked global, and everything else local.  Then throw in --gc-sections, and voila, most of the redundant symbols are gone.  There are still a few that aren't, I'm trying to track down where exactly they're referenced, but most of the work has already been done.
October 27, 2018
On Friday, 26 October 2018 at 16:24:50 UTC, H. S. Teoh wrote:
> On Friday, 26 October 2018 at 06:02:51 UTC, Joakim wrote:
> [...]
>> Btw, are you writing a mostly native app that calls some D code or a Java app that calls some native code? I ask because I ran into an issue before depending on how it's set up, though you won't hit this if you're using D in betterC mode.
>
> My main code is in D, but consists of a bunch of functions repeatedly called from the Java wrapper as native functions. Main reason is that for GUI interactions it makes more sense to leverage the Java APIs provided by Android, rather than to go through the pain of marshalling and calling Java functions via JNIEnv. (There are a couple of places where this is necessary, e.g., when D code needs to trigger a GUI action, but I'm trying to keep this to a minimum.)

Will this app be publicly disclosed at some point or is it an internal app that you won't be making public? I ask because AFAIK it would be the first real project to use D on Android, so it would be good to publicize it.

>> The difference is that there are two ways of calling native code on Android:
>>
>> 1. You can have the Android Java runtime directly call native code on _startup_ if you provide some standard native functions that it expects:
>>
>> https://developer.android.com/ndk/reference/group/native-activity#group___native_activity_1ga02791d0d490839055169f39fdc905c5e
>
> That's something to look into, I suppose.
>
>
>> 2. You have your Java app call some native functions using JNI.
>>
>> Both have been tried with D, but the latter may present some difficulty for initializing the D runtime and GC. When I tried the latter with a sample app, it seemed to be loading the D library _every time_ the D functions were called, so I had to add a call to rt_init every time the function was called:
>>
>> https://github.com/joakim-noah/android/blob/master/samples/bitmap-plasma/jni/plasma.d#L357
>>
>> I initially had that call inside the initialization check just below, and it wouldn't work. I never investigated further though, as I have no interest in using D that way, I could be completely off on my guess as to why it didn't work.
>
> Currently, I call rt_init() from a native function called from Java whenever onSurfaceCreated is called (I'm using GLES2).  I don't know if Android reloads the library every time, but I see why it might, seeing that it can shut down activities anytime without (much) notice.  So far, I haven't encountered any problems.
>
> Other native functions do save static state in TLS, and so far I haven't encountered any problems.  But maybe I just haven't triggered the problematic cases yet.

Something to watch for or investigate further.

>> For the way I use D and test it, the rt_init is called once on startup, as noted on the wiki, so everything works:
>>
>> https://wiki.dlang.org/Build_D_for_Android#Changes_for_Android
>>
>> Of course, you can always call Java functions through JNI if you go this route, as the Teapot sample app demonstrates, just something to consider in how you call D code.
>
> Currently I'm expecting to just write the Activity code in Java and have it easily interact with the Android GUI APIs, and call the main logic in D via JNI.  I *could* use the NDK APIs if I really wanted to, I suppose, but I don't really have a strong reason to do that currently.  (The main reason I looked into using D at all in this project was because I got frustrated trying to write complex non-GUI code in Java.  So far, I've migrated most of the original Java code to D, leaving the Java code merely as thin wrappers that just forward the main logic to D via JNI.  It has worked well thus far, and I'll probably just leave it this way, unless I run into something that would be far better off written in D.  I'm not looking forward to interfacing with Android GUI classes via JNI, though.  JNI is a royal pain to use, esp. when going from D to Java.)

Good to hear everything's working fine, :) as I haven't really stressed mixed D/Java usage through JNI, just made sure it worked through that Teapot sample app.

> I do have an empty main() in the .so, though, per your recommendations on that wiki page.  Does that make a difference?

It's not a recommendation, the wiki page notes that the emulated TLS scheme used "requires some changes." However, I'm currently reworking the way emulated TLS data is stored to remove all three of those listed requirements: kinke suggested reading the ELF section headers instead, and so far it seems to work with the ld.bfd, gold, and lld linkers, but I'm still optimizing it and haven't tested it with a shared library yet. Hopefully, those changes for emulated TLS on Android listed on the wiki won't be needed with the upcoming 1.13 release.
« First   ‹ Prev
1 2