Skip to content

LTO archive test#1050

Merged
davidlattimore merged 5 commits into
wild-linker:mainfrom
AadiWaghray:lto-undefined
Aug 15, 2025
Merged

LTO archive test#1050
davidlattimore merged 5 commits into
wild-linker:mainfrom
AadiWaghray:lto-undefined

Conversation

@AadiWaghray
Copy link
Copy Markdown
Contributor

PRs #91, #744, and #979 should be sufficient to resolve issue #464. But, I think this test highlights the need for more work on the issue. However, I'm not entirely sure if the test is erroneously written. It gets past the LTO guard, but it doesn't error on an undefined symbol as one would expect. Instead, it errors on "Binary ran for too long."

Comment thread wild/tests/sources/lto-undefined.c Outdated
@@ -0,0 +1,5 @@
//#LinkArgs:-L./ -lexample

extern int CRC32_ProcessSingleBuffer(void const*, int);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Declaring an extern function won't actually cause a reference to the symbol to be emitted. The code needs to actually refer to the symbol - e.g. from main.

Comment thread wild/tests/sources/libexample.a Outdated
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fine to add this file to experimentation, but I think if we wanted to merge this PR, we'd need to figure out how to build a similar file from source.

@AadiWaghray
Copy link
Copy Markdown
Contributor Author

AadiWaghray commented Aug 14, 2025

Apologies for pushing prematurely. There is an issue I need to fix. But, the changes mentioned above have been added.


extern int addition(int, int);

int main(int argc, char **argv) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of our tests use _start rather than main. _start is the default entry point for the binary. main is called sometime later by the libc runtime. A few of our tests do use main, but we only do that when necessary, since it requires linking against libc, which substantially slows down linking. I suspect the reason you're seeing a failure is because you haven't defined _start and also haven't linked against libc. I'd suggest looking at a test like trivial.c to see how the _start-based tests are written.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. I incorrectly assumed libc was linked automatically for testing since I used main in my z-defs testing. I saw the Requires set of flags but didn't put two and two together. Pushing fixes now, the error is now, correctly, the undefined symbol error showing archives avoid the LTO guard. I still have the issue we discussed earlier of accessing the archive's sections at the point we emit the undefined error to emit an LTO error rather than an undefined error.

@davidlattimore
Copy link
Copy Markdown
Member

Looking at one of the test failures:

Error: Failed to build program `lto-undefined.c` with linker `ld` config `default`
  Caused by:
    Linker failed:
/usr/bin/ld.bfd: /__w/wild/wild/wild/tests/build/archive_lto.default-host-2d02e06e5b1d047f.a(archive_lto.default-host-2d02e06e5b1d047f.o): plugin needed to handle lto object
/usr/bin/ld.bfd: /__w/wild/wild/wild/tests/build/archive_lto.default-host-2d02e06e5b1d047f.a(archive_lto.default-host-2d02e06e5b1d047f.o): plugin needed to handle lto object
/usr/bin/ld.bfd: /__w/wild/wild/wild/tests/build/lto-undefined.default-host-c78a7f268027252.o: in function `_start':
lto-undefined.c:(.text+0x24): undefined reference to `addition'

This is failing to link with ld. i.e. it hasn't actually gotten to linking with wild, which happens after GNU ld. GNU ld looks to be failing because the linker plugin wasn't passed as an argument. I guess that suggests that for this test, we do need to use the C compiler to drive the linker rather than invoking the linker directly. If you look at trivial-main.c, you can see how we set up to use GCC as the linker driver - pretty much set //#LinkerDriver:gcc.

@AadiWaghray
Copy link
Copy Markdown
Contributor Author

The test should be good now!

@davidlattimore
Copy link
Copy Markdown
Member

Cool, yep that failure does indeed show that we're still giving an undefined symbol error rather than a more appropriate error. I suspect #979 might have actually regressed that, but I don't think we want to undo that change, since that would hurt performance. The issue is that we now only look for LTO sections in archive entries that we activate, but we never activate the relevant archive entry because it doesn't define any any needed symbols in its symtab.

One possibility would be that if we detect that we've got an undefined symbol error, we go back and scan all archived objects that we didn't already process. If any turn out to have LTO bitcode, then make that be the error instead of the undefined symbol error.

@AadiWaghray
Copy link
Copy Markdown
Contributor Author

There was something about the tests that was confusing me. If you run cargo test multiple times, then ld will fail every time on the first run. But, every cargo test after that will succeed...As I'm writing this, I realize its because the test assumes the error will be for both linkers. I'll fix that and the formatting in the morning.

fixed: test to build own archive and use function

removed: unecessary linker args

fixed: lto archive test no longer requires libc link

fixed: ld needs lto plugin from gcc to work

fixed: lto test to avoid ld error
improved: error message and test

fixed: cargo fmt
@AadiWaghray
Copy link
Copy Markdown
Contributor Author

AadiWaghray commented Aug 14, 2025

I'm not quite sure why the tests aren't passing. They pass locally and are giving the error message I expect and catch in the test header.

Edit: I realize what the problem is. I'll push a fix.

@AadiWaghray AadiWaghray marked this pull request as ready for review August 14, 2025 21:11
Comment thread libwild/src/layout.rs Outdated
.expect("Canonicalized path can't have ../ at end")
.to_str()
.expect("File name should be valid UTF-8")
.split(".")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you only taking the filename up to '.'?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file generated is archive_lto.a. I believe that the linker adds something to the end of the object file extracted into memory when parsing the archive. I thought it would better to give the name of the actual archive file and avoid the random values. This is the error I mentioned fixing above where the test would pass locally but not in github actions. I never checked where these values are being added onto the name. If we are able to know what it is and add the appropriate value to the Expected header value, it is possible that this is entirely unnecessary.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect the extra stuff in the filename is added by the integration testing framework, so it's actually part of the filename.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After reading integration_tests.rs and reviewing the commits, I see you're 100% right. The command is hashed as part of the file name, and I added the //#SkipLinker:ld directive between commits. I'll change that and everything should be good.

Comment thread libwild/src/layout.rs Outdated
.symbol_name(symbol_id)
.expect("Errored on symbol {symbol_name} so it exists");
let mut lto_file: Option<&PathBuf> = None;
let lto_unresolved = resources.symbol_db.groups.iter().any(|group| match group {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is lto_unresolved actually needed? It looks like it's always true when lto_file is Some and false when lto_file is None. Perhaps just replace the two any calls with find_map.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair. I was originally just checking if the undefined symbol came from a file with lto, but I then realized I needed to add the file name in the error message.

Comment thread libwild/src/layout.rs Outdated
if args.error_unresolved_symbols {
let raw_symbol_name = symbol_db
.symbol_name(symbol_id)
.expect("Errored on symbol {symbol_name} so it exists");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the {symbol_name} won't substitute the variable symbol_name.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know that expect doesn't format strings. Thanks for pointing it out!

Comment thread libwild/src/layout.rs Outdated
let raw_symbol_name = symbol_db
.symbol_name(symbol_id)
.expect("Errored on symbol {symbol_name} so it exists");
let mut lto_file: Option<&PathBuf> = None;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is already pretty large and adding this makes it ever larger. Could something be moved out to a separate function? Perhaps the code to compute lto_file?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. Will work on it.

@davidlattimore davidlattimore merged commit 6e709b8 into wild-linker:main Aug 15, 2025
39 of 40 checks passed
@AadiWaghray AadiWaghray deleted the lto-undefined branch August 15, 2025 17:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants