Repository: davidlattimore/wild (rust)

Issue:
Add support for --whole-archive
See `man ld` for a description of what `--whole-archive` needs to do.

`--whole-archive` is a modifier that is pushed / popped by `--push-state` / `--pop-state`, so the value of the flag should be added to the `Modifiers` struct (in `args.rs`).

The method where the `whole_archive` attribute should be used is `ParsedInputObject::is_optional`.

It'd be good to have a test that verifies that `--whole-archive` is behaving as expected. There are probably a few ways such a test could be written. The key observation is that whatever is defined in the archive needs to not be directly referenced from the main test object, otherwise the archived object would be included even without `--whole-archive`. One option is to have an archive that puts something into a custom section, then have the `_start_` function check the custom section for that value. You can look at the test `custom_section.c` for how this is done.

Pull request description:
Add `--whole-archive`
Closes #272.

I was getting some funny behaviour where the added test would pass but the option wouldn't work when actually using the linker, but I'm no longer seeing this after rebasing onto latest `main`.

Patch:
diff --git a/libwild/src/args.rs b/libwild/src/args.rs
index 584bbb5..abd0404 100644
--- a/libwild/src/args.rs
+++ b/libwild/src/args.rs
@@ -118,6 +118,10 @@ pub(crate) struct Modifiers {
 
     /// Whether we're currently allowed to link against shared libraries.
     pub(crate) allow_shared: bool,
+
+    /// Whether object files in archives should be linked even if they do not contain symbols that
+    /// are referenced.
+    pub(crate) whole_archive: bool,
 }
 
 #[derive(Debug, Eq, PartialEq)]
@@ -417,6 +421,10 @@ pub(crate) fn parse<S: AsRef<str>, I: Iterator<Item = S>>(mut input: I) -> Resul
             modifier_stack.last_mut().unwrap().as_needed = true;
         } else if long_arg_eq("no-as-needed") {
             modifier_stack.last_mut().unwrap().as_needed = false;
+        } else if long_arg_eq("whole-archive") {
+            modifier_stack.last_mut().unwrap().whole_archive = true;
+        } else if long_arg_eq("no-whole-archive") {
+            modifier_stack.last_mut().unwrap().whole_archive = false;
         } else if long_arg_eq("push-state") {
             modifier_stack.push(*modifier_stack.last().unwrap());
         } else if long_arg_eq("pop-state") {
@@ -687,6 +695,7 @@ impl Default for Modifiers {
         Self {
             as_needed: false,
             allow_shared: true,
+            whole_archive: false,
         }
     }
 }
diff --git a/libwild/src/parsing.rs b/libwild/src/parsing.rs
index 14e01b0..70a539d 100644
--- a/libwild/src/parsing.rs
+++ b/libwild/src/parsing.rs
@@ -129,11 +129,12 @@ impl<'data> ParsedInputObject<'data> {
         self.is_dynamic
     }
 
-    /// Returns whether this input should be skipped if there are no non-weak reference to symbols
-    /// it defines. This is true for archive entries and shared objects for which --as-needed is
-    /// true.
+    /// Returns whether this input should be skipped if there are no non-weak references to symbols
+    /// it defines. This is true for archive entries for which --whole-archive is false and shared
+    /// objects for which --as-needed is true.
     pub(crate) fn is_optional(&self) -> bool {
-        self.input.entry.is_some() || (self.is_dynamic() && self.modifiers.as_needed)
+        (self.input.entry.is_some() && !self.modifiers.whole_archive)
+            || (self.is_dynamic() && self.modifiers.as_needed)
     }
 
     fn filename(&self) -> &'data Path {


Test patch:
diff --git a/wild/tests/integration_tests.rs b/wild/tests/integration_tests.rs
index 6e8be3e..6d4894c 100644
--- a/wild/tests/integration_tests.rs
+++ b/wild/tests/integration_tests.rs
@@ -1673,7 +1673,8 @@ fn integration_test(
         "input_does_not_exist.c",
         "ifunc2.c",
         "tls-local-exec.c",
-        "undefined_symbols.c"
+        "undefined_symbols.c",
+        "whole_archive.c"
     )]
     program_name: &'static str,
     #[allow(unused_variables)] setup_symlink: (),
diff --git a/wild/tests/sources/whole_archive.c b/wild/tests/sources/whole_archive.c
new file mode 100644
index 0000000..e2c74a5
--- /dev/null
+++ b/wild/tests/sources/whole_archive.c
@@ -0,0 +1,19 @@
+//#Object:exit.c
+
+//#LinkArgs:--whole-archive
+//#Archive:whole_archive0.c
+
+#include "exit.h"
+
+extern int __start_foo[];
+extern int __stop_foo[];
+
+void _start(void) {
+    int value = 0;
+
+    for (int *foo = __start_foo; foo < __stop_foo; ++foo) {
+        value += *foo;
+    }
+
+    exit_syscall(value);
+}
diff --git a/wild/tests/sources/whole_archive0.c b/wild/tests/sources/whole_archive0.c
new file mode 100644
index 0000000..1401d7c
--- /dev/null
+++ b/wild/tests/sources/whole_archive0.c
@@ -0,0 +1,1 @@
+static int foo1 __attribute__ ((used, retain, section ("foo"))) = 42;