www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Question about LDC and --gc-sections

reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
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
Oct 25 2018
next sibling parent reply Jonathan Marler <johnnymarler gmail.com> writes:
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.
Oct 25 2018
parent H. S. Teoh <hsteoh quickfur.ath.cx> writes:
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
Oct 26 2018
prev sibling next sibling parent reply Joakim <dlang joakim.fea.st> writes:
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.
Oct 25 2018
parent reply H. S. Teoh <hsteoh quickfur.ath.cx> writes:
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
Oct 26 2018
parent reply Joakim <dlang joakim.fea.st> writes:
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.
Oct 26 2018
parent H. S. Teoh <hsteoh quickfur.ath.cx> writes:
On Saturday, 27 October 2018 at 06:03:02 UTC, Joakim wrote:
 On Friday, 26 October 2018 at 16:24:50 UTC, H. S. Teoh wrote:
[...]
 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.
Well, this is my first time writing an Android app, and if it ever gets to the point where I feel it's fit for public consumption, then I certainly would publish it. But for the time being, it's just a pet project. [...]
 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.
That's good news. Looking forward to the next LDC release! Side note: I notice that the latest LDC release is basically on par with the current DMD release. This is an extremely good thing IMO, since it makes LDC a real and serious offer among the "official" D compilers. I thought I should publicly state how happy I am with the amazing work the LDC maintainers have done to make this happen. Kudos! And what with the recent official inclusion of GDC into GCC upstream, D is shaping up to be a powerful force indeed.
Oct 30 2018
prev sibling next sibling parent reply Kagamin <spam here.lot> writes:
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.
Oct 26 2018
parent Nicholas Wilson <iamthewilsonator hotmail.com> writes:
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.
Oct 26 2018
prev sibling parent reply kinke <noone nowhere.com> writes:
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
Oct 26 2018
parent H. S. Teoh <hsteoh quickfur.ath.cx> writes:
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.
Oct 26 2018