about summary refs log tree commit diff
diff options
context:
space:
mode:
authorTrevor Gross <t.gross35@gmail.com>2024-07-19 03:27:46 -0500
committerGitHub <noreply@github.com>2024-07-19 03:27:46 -0500
commit986d6bf9fb7aeb3032014183cdd7c30d90810360 (patch)
treeb4febbf4ea14cca50c9653ae66e0d491d4b97a04
parent3d68afc9e821b00d59058abc9bda670b07639955 (diff)
parent1de046fa24a8a0304be274963b43819cfe56013a (diff)
downloadrust-986d6bf9fb7aeb3032014183cdd7c30d90810360.tar.gz
rust-986d6bf9fb7aeb3032014183cdd7c30d90810360.zip
Rollup merge of #121533 - ratmice:wasm_init_fini_array, r=nnethercote
Handle .init_array link_section specially on wasm

Given that wasm-ld now has support for [.init_array](https://github.com/llvm/llvm-project/blob/8f2bd8ae68883592a333f4bdbed9798d66e68630/llvm/lib/MC/WasmObjectWriter.cpp#L1852), it appears we can easily implement that section by falling through to the normal path rather than taking the typical custom_section path for wasm.

The wasm-ld appears to have a bunch of limitations. Only one static with the `link_section` in a crate or else you hit the fatal error in the link above "only one .init_array section fragment supported". They do not get merged.

You can still call multiple constructors by setting it to an array.

```
unsafe extern "C" fn ctor() {
    println!("foo");
}
#[used]
#[link_section = ".init_array"]
static FOO: [unsafe extern "C" fn(); 2] = [ctor, ctor];
```

Another issue appears to be that if crate *A* depends on crate *B*, but *A* doesn't call any symbols from *B* and *B* doesn't `#[export_name = ...]` any symbols, then crate *B*'s constructor will not be called.  The workaround to this is to provide an exported symbol in crate *B*.
-rw-r--r--compiler/rustc_codegen_llvm/src/consts.rs10
-rw-r--r--compiler/rustc_hir_analysis/src/check/mod.rs35
2 files changed, 36 insertions, 9 deletions
diff --git a/compiler/rustc_codegen_llvm/src/consts.rs b/compiler/rustc_codegen_llvm/src/consts.rs
index a2314f4850c..164d1681a36 100644
--- a/compiler/rustc_codegen_llvm/src/consts.rs
+++ b/compiler/rustc_codegen_llvm/src/consts.rs
@@ -495,8 +495,14 @@ impl<'ll> CodegenCx<'ll, '_> {
             }
 
             // Wasm statics with custom link sections get special treatment as they
-            // go into custom sections of the wasm executable.
-            if self.tcx.sess.target.is_like_wasm {
+            // go into custom sections of the wasm executable. The exception to this
+            // is the `.init_array` section which are treated specially by the wasm linker.
+            if self.tcx.sess.target.is_like_wasm
+                && attrs
+                    .link_section
+                    .map(|link_section| !link_section.as_str().starts_with(".init_array"))
+                    .unwrap_or(true)
+            {
                 if let Some(section) = attrs.link_section {
                     let section = llvm::LLVMMDStringInContext2(
                         self.llcx,
diff --git a/compiler/rustc_hir_analysis/src/check/mod.rs b/compiler/rustc_hir_analysis/src/check/mod.rs
index 9fef31acef8..24aeb024461 100644
--- a/compiler/rustc_hir_analysis/src/check/mod.rs
+++ b/compiler/rustc_hir_analysis/src/check/mod.rs
@@ -166,21 +166,42 @@ fn maybe_check_static_with_link_section(tcx: TyCtxt<'_>, id: LocalDefId) {
         return;
     }
 
-    // For the wasm32 target statics with `#[link_section]` are placed into custom
-    // sections of the final output file, but this isn't link custom sections of
-    // other executable formats. Namely we can only embed a list of bytes,
-    // nothing with provenance (pointers to anything else). If any provenance
-    // show up, reject it here.
+    // For the wasm32 target statics with `#[link_section]` other than `.init_array`
+    // are placed into custom sections of the final output file, but this isn't like
+    // custom sections of other executable formats. Namely we can only embed a list
+    // of bytes, nothing with provenance (pointers to anything else). If any
+    // provenance show up, reject it here.
     // `#[link_section]` may contain arbitrary, or even undefined bytes, but it is
     // the consumer's responsibility to ensure all bytes that have been read
     // have defined values.
+    //
+    // The `.init_array` section is left to go through the normal custom section code path.
+    // When dealing with `.init_array` wasm-ld currently has several limitations. This manifests
+    // in workarounds in user-code.
+    //
+    //   * The linker fails to merge multiple items in a crate into the .init_array section.
+    //     To work around this, a single array can be used placing multiple items in the array.
+    //     #[link_section = ".init_array"]
+    //     static FOO: [unsafe extern "C" fn(); 2] = [ctor, ctor];
+    //   * Even symbols marked used get gc'd from dependant crates unless at least one symbol
+    //     in the crate is marked with an `#[export_name]`
+    //
+    //  Once `.init_array` support in wasm-ld is complete, the user code workarounds should
+    //  continue to work, but would no longer be necessary.
+
     if let Ok(alloc) = tcx.eval_static_initializer(id.to_def_id())
         && alloc.inner().provenance().ptrs().len() != 0
     {
-        let msg = "statics with a custom `#[link_section]` must be a \
+        if attrs
+            .link_section
+            .map(|link_section| !link_section.as_str().starts_with(".init_array"))
+            .unwrap()
+        {
+            let msg = "statics with a custom `#[link_section]` must be a \
                         simple list of bytes on the wasm target with no \
                         extra levels of indirection such as references";
-        tcx.dcx().span_err(tcx.def_span(id), msg);
+            tcx.dcx().span_err(tcx.def_span(id), msg);
+        }
     }
 }