Commit dafbc6eb25

Jakub Konka <kubkon@jakubkonka.com>
2022-01-11 13:34:43
Merge branch 'Luukdegram-linker-eport-symbols'
1 parent 6f49233
lib/std/build.zig
@@ -2651,6 +2651,8 @@ pub const LibExeObjStep = struct {
                         try zig_args.append(bin_name);
                         try zig_args.append("--test-cmd");
                         try zig_args.append("--dir=.");
+                        try zig_args.append("--test-cmd");
+                        try zig_args.append("--allow-unknown-exports"); // TODO: Remove when stage2 is default compiler
                         try zig_args.append("--test-cmd-bin");
                     } else {
                         try zig_args.append("--test-no-exec");
src/link/Wasm.zig
@@ -1058,8 +1058,7 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
     }
 
     const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path});
-
-    if (self.base.options.output_mode == .Obj) {
+    if (is_obj) {
         // LLD's WASM driver does not support the equivalent of `-r` so we do a simple file copy
         // here. TODO: think carefully about how we can avoid this redundant operation when doing
         // build-obj. See also the corresponding TODO in linkAsArchive.
@@ -1125,14 +1124,45 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
             try argv.append("--stack-first");
         }
 
+        var auto_export_symbols = true;
         // Users are allowed to specify which symbols they want to export to the wasm host.
         for (self.base.options.export_symbol_names) |symbol_name| {
             const arg = try std.fmt.allocPrint(arena, "--export={s}", .{symbol_name});
             try argv.append(arg);
+            auto_export_symbols = false;
         }
 
         if (self.base.options.rdynamic) {
             try argv.append("--export-dynamic");
+            auto_export_symbols = false;
+        }
+
+        if (auto_export_symbols) {
+            if (self.base.options.module) |module| {
+                // when we use stage1, we use the exports that stage1 provided us.
+                // For stage2, we can directly retrieve them from the module.
+                const use_stage1 = build_options.is_stage1 and self.base.options.use_stage1;
+                if (use_stage1) {
+                    for (comp.export_symbol_names.items) |symbol_name| {
+                        try argv.append(try std.fmt.allocPrint(arena, "--export={s}", .{symbol_name}));
+                    }
+                } else {
+                    const skip_export_non_fn = target.os.tag == .wasi and
+                        self.base.options.wasi_exec_model == .command;
+                    for (module.decl_exports.values()) |exports| {
+                        for (exports) |exprt| {
+                            if (skip_export_non_fn and exprt.exported_decl.ty.zigTypeTag() != .Fn) {
+                                // skip exporting symbols when we're building a WASI command
+                                // and the symbol is not a function
+                                continue;
+                            }
+                            const symbol_name = exprt.exported_decl.name;
+                            const arg = try std.fmt.allocPrint(arena, "--export={s}", .{symbol_name});
+                            try argv.append(arg);
+                        }
+                    }
+                }
+            }
         }
 
         if (self.base.options.output_mode == .Exe) {
@@ -1146,12 +1176,6 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
             if (self.base.options.wasi_exec_model == .reactor) {
                 // Reactor execution model does not have _start so lld doesn't look for it.
                 try argv.append("--no-entry");
-                // Make sure "_initialize" and other used-defined functions are exported if this is WASI reactor.
-                // If rdynamic is true, it will already be appended, so only verify if the user did not specify
-                // the flag in which case, we ensure `--export-dynamic` is called.
-                if (!self.base.options.rdynamic) {
-                    try argv.append("--export-dynamic");
-                }
             }
         } else {
             if (self.base.options.stack_size_override) |stack_size| {
@@ -1159,12 +1183,6 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
                 const arg = try std.fmt.allocPrint(arena, "stack-size={d}", .{stack_size});
                 try argv.append(arg);
             }
-
-            // Only when the user has not specified how they want to export the symbols, do we want
-            // to export all symbols.
-            if (self.base.options.export_symbol_names.len == 0 and !self.base.options.rdynamic) {
-                try argv.append("--export-all");
-            }
             try argv.append("--no-entry"); // So lld doesn't look for _start.
         }
         try argv.appendSlice(&[_][]const u8{
src/stage1/codegen.cpp
@@ -9905,6 +9905,18 @@ void codegen_build_object(CodeGen *g) {
 
     codegen_add_time_event(g, "Done");
     codegen_switch_sub_prog_node(g, nullptr);
+   
+    // append all export symbols to stage2 so we can provide them to the linker
+    if (target_is_wasm(g->zig_target)){
+        Error err;
+        auto export_it = g->exported_symbol_names.entry_iterator();
+        decltype(g->exported_symbol_names)::Entry *curr_entry = nullptr;
+        while ((curr_entry = export_it.next()) != nullptr) {
+            if ((err = stage2_append_symbol(&g->stage1, buf_ptr(curr_entry->key), buf_len(curr_entry->key)))) {
+                fprintf(stderr, "Unable to export symbol '%s': %s\n", buf_ptr(curr_entry->key), err_str(err));
+            }
+        }
+    }
 }
 
 ZigPackage *codegen_create_package(CodeGen *g, const char *root_src_dir, const char *root_src_path,
src/stage1/stage2.h
@@ -182,4 +182,7 @@ ZIG_EXTERN_C const char *stage2_add_link_lib(struct ZigStage1 *stage1,
         const char *lib_name_ptr, size_t lib_name_len,
         const char *symbol_name_ptr, size_t symbol_name_len);
 
+// ABI warning
+ZIG_EXTERN_C enum Error stage2_append_symbol(struct ZigStage1 *stage1, const char *name_ptr, size_t name_len);
+
 #endif
src/stage1/zig0.cpp
@@ -554,3 +554,8 @@ const char *stage2_version_string(void) {
 struct Stage2SemVer stage2_version(void) {
     return {ZIG_VERSION_MAJOR, ZIG_VERSION_MINOR, ZIG_VERSION_PATCH};
 }
+
+Error stage2_append_symbol(struct ZigStage1 *stage1, const char *name_ptr, size_t name_len)
+{
+    return ErrorNone;
+}
src/Compilation.zig
@@ -158,6 +158,10 @@ emit_docs: ?EmitLoc,
 work_queue_wait_group: WaitGroup,
 astgen_wait_group: WaitGroup,
 
+/// Exported symbol names. This is only for when the target is wasm.
+/// TODO: Remove this when Stage2 becomes the default compiler as it will already have this information.
+export_symbol_names: std.ArrayListUnmanaged([]const u8) = .{},
+
 pub const SemaError = Module.SemaError;
 
 pub const CRTFile = struct {
@@ -1791,6 +1795,11 @@ pub fn destroy(self: *Compilation) void {
     self.work_queue_wait_group.deinit();
     self.astgen_wait_group.deinit();
 
+    for (self.export_symbol_names.items) |symbol_name| {
+        gpa.free(symbol_name);
+    }
+    self.export_symbol_names.deinit(gpa);
+
     // This destroys `self`.
     self.arena_state.promote(gpa).deinit();
 }
src/stage1.zig
@@ -464,3 +464,11 @@ export fn stage2_fetch_file(
     if (contents.len == 0) return @intToPtr(?[*]const u8, 0x1);
     return contents.ptr;
 }
+
+export fn stage2_append_symbol(stage1: *Module, name_ptr: [*c]const u8, name_len: usize) Error {
+    if (name_len == 0) return Error.None;
+    const comp = @intToPtr(*Compilation, stage1.userdata);
+    const sym_name = comp.gpa.dupe(u8, name_ptr[0..name_len]) catch return Error.OutOfMemory;
+    comp.export_symbol_names.append(comp.gpa, sym_name) catch return Error.OutOfMemory;
+    return Error.None;
+}