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

Webassembly and --export-dynamic causes too much bloat #3122

Closed
skoppe opened this issue Aug 5, 2019 · 11 comments · Fixed by #3129
Closed

Webassembly and --export-dynamic causes too much bloat #3122

skoppe opened this issue Aug 5, 2019 · 11 comments · Fixed by #3129

Comments

@skoppe
Copy link
Contributor

skoppe commented Aug 5, 2019

First of all, I am sorry, since I was probably the reason --export-dynamic got put in by default, but it is causing too much bloat.

It will actually export all symbols, including things which aren't even used. In the case of the todo-mvc example in spasm, as a consequence the binary grows from 62Kb to 4Mb.

I know I can remove the switch from ldc2.conf, but this doesn't scale well to end users.

Again, I am sorry for not noticing this earlier. Next beta's I will definitely check. Probably will setup my CI to do the tests for me.

I would prefer if we can remove --export-dynamic again. Then if people want to export functions they need to annotate the function with extern(C) export @assumeUsed (when https://reviews.llvm.org/D57869 is released), or alternatively they can drop the @assumeUsed attribute and add --export=funcname to the linker invocation.

@kinke
Copy link
Member

kinke commented Aug 5, 2019

I guess we need some more insight into this. Please correct me if I'm wrong, just trying to recap this:

Before LLD 8, everything was fine, using implicit -fvisibility=default yielded a bloated binary, and -fvisibility=hidden + selective export D visibility reduced the size.

Then there came LLD 8, stripping everything except for the _start entry point by default, and now requiring --export-dynamic for LLD. From the docs:

Finally, just like with native ELF linker the --export-dynamic flag can be used to export symbol in the executable which are marked as visibility=default.

But that's now leading to extreme bloat even with -fvisibility=hidden. Questions: Why? LLD or LDC bug? Is it still an issue with LLVM/LLD 8.0.1? What effect does it have with LLD 7?

Expecting the user to export the functions manually in the linker cmdline would be a pretty ugly regression IMO.

In the meantime, it was apparently decided to (ab)use the llvm.used array in LLVM IR to denote symbols to be wasm-exported, for future LLD versions. As long as that then actually works as expected (i.e., as with LLD 7.x), we can do so automatically for exported symbols and wasm targets (so as not to bother the user about @assumeUsed). But that's only a solution for future LLD (well, I haven't checked wether LLD 8.0.1 supports llvm.used).

@skoppe
Copy link
Contributor Author

skoppe commented Aug 5, 2019

Before LLD 8, everything was fine, using implicit -fvisibility=default yielded a bloated binary, and -fvisibility=hidden + selective export D visibility reduced the size.

Correct. Although all the **__initZ globals were still being exported.

Ldc 1.15 got rid of them, but required you to explicitly mark those functions you wanted to keep via the --export flag.

But that's now leading to extreme bloat even with -fvisibility=hidden. Questions: Why?

I forgot to put a -fvisibility=hidden on the base project. So the bloat isn't as extreme as I portrayed. Still, the binary does increase from 62kb to 169kb. Which is still quite substantial.

If I look at the wasm binary I see a lot of **__initZ globals being exported, plus the following symbols are added to the wasm function table $_D6object9Throwable8toStringMFZAya $_D6object6Object6toHashMFNbNeZk $_D6object6Object5opCmpMFCQqZi $_D6object6Object8opEqualsMFCQtZb $_D6object9Throwable4nextMNgFNaNbNdNiNjNfZNgCQBqQBm $_D6object9Throwable4nextMFNaNbNdNiNlNfCQBlQBhZv $_D6object9Throwable7opApplyMFMDFCQBfQBbZiZi $_D6object9Throwable8toStringMxFMDFxAaZvZv $_D6object9Throwable7messageMxFZAxa and they are being imported as well since ldc doesn't have the body of those functions in betterC. (I have no idea where they come from, and I will try with more minimal examples. [since just compiling an empty _start function doesn't have those.])

LLD or LDC bug? Is it still an issue with LLVM/LLD 8.0.1? What effect does it have with LLD 7?

I have to try and find out. I will link with lld 8.0.1 as well as 7 and see what it gives. Expect a few days before I have time for it.

In the meantime, it was apparently decided to (ab)use the llvm.used array in LLVM IR to denote symbols to be wasm-exported, for future LLD versions. As long as that then actually works as expected (i.e., as with LLD 7.x), we can do so automatically for exported symbols and wasm targets (so as not to bother the user about @assumeUsed). But that's only a solution for future LLD (well, I haven't checked wether LLD 8.0.1 supports llvm.used).

Yeah, I think that is a good long term solution.

@kinke
Copy link
Member

kinke commented Aug 5, 2019

Wrt. to the *__initZ init symbols, that's an issue at the LDC side - -fvisibility currently only affects functions and regular globals, but no compiler-generated symbols tied to an aggregate, like init symbols, class/interface vtables, TypeInfos etc. That shouldn't be that hard to fix though (using the aggregate visibility, i.e., checking if the aggregate is exported).

@skoppe
Copy link
Contributor Author

skoppe commented Aug 6, 2019

Ok. If we can fix the visibility issue and use the llvm.used then I think we are golden. I will try to take a stab at both, but I don't know how far I will get.

I will first figure out why I have the object symbols in my wasm binary. If I can get rid of them then at least I will have a runnable wasm binary again. As for the bloat I can wait until the visibility issue is fixed.

@dukc
Copy link
Contributor

dukc commented Aug 6, 2019

I would prefer if we can remove --export-dynamic again. Then if people want to export functions they need to annotate the function with extern(C) export @assumeUsed (when https://reviews.llvm.org/D57869 is released), or alternatively they can drop the @assumeUsed attribute and add --export=funcname to the linker invocation.

Currently, I depend on the linker to not export everything so I cannot use --export-dynamic, and thus I have to list exported symbols at compiler invocation for 1.15.0. Fortunately nothing intolerable, as I need to export only a little, and can list the needed stuff into dub.sdl. But I agree that I'd prefer what you just described, with one addition: export should imply @assumeUsed. If I'm not assuming that something is used, I would mark such a function public instead.

@skoppe
Copy link
Contributor Author

skoppe commented Aug 6, 2019

Currently, I depend on the linker to not export everything so I cannot use --export-dynamic, and thus I have to list exported symbols at compiler invocation for 1.15.0.

Same here.

export should imply @assumeUsed

Yeah, kinke said something similar. Good idea.

@skoppe
Copy link
Contributor Author

skoppe commented Aug 8, 2019

@kinke I found the reason why the object.d functions were being imported.

app.d

class C {}
extern(C) void _start() {}

ldc2 -mtriple=wasm32-unknown-unknown-wasm -betterC app.d

outputs:

wasm-ld: error: app.o: undefined symbol: _D6object6Object8toStringMFZAya
wasm-ld: error: app.o: undefined symbol: _D6object6Object6toHashMFNbNeZk
wasm-ld: error: app.o: undefined symbol: _D6object6Object5opCmpMFCQqZi
wasm-ld: error: app.o: undefined symbol: _D6object6Object8opEqualsMFCQtZb
Error: /usr/sbin/wasm-ld failed with status: 1

@kinke
Copy link
Member

kinke commented Aug 8, 2019

Yeah well extern(D) classes aren't supported with -betterC, as they derive from Object (with virtual functions, required here for the C vtable). extern(C++) classes should work, or using a custom object.d (see #2765 (comment)).

@skoppe
Copy link
Contributor Author

skoppe commented Aug 8, 2019

I know, it happened to be inside a dependency I used. The point is that without --export-dynamic they will be removed by the linker because they are not instantiated.

@kinke
Copy link
Member

kinke commented Aug 8, 2019

Alright then, but so the same thing should happen with LLD < 8 too, so the culprit isn't --export-dynamic, but the non-hidden visibility of the class vtable due to aforementioned limitation of -fvisibility, and is extemely closely related to the init symbols bloat.

@skoppe
Copy link
Contributor Author

skoppe commented Aug 9, 2019

I see your point. Removing --export-dynamic does remove the symbols, but the real issue is the visibility.

(btw I checked, ldc 1.14 also exports the symbols).

Cool. Two birds with one stone then.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants