Skip to content

Commit 7935135

Browse files
authored
Merge pull request #14157 from Luukdegram/wasm-linker-misc
wasm-linker: various fixes & improvements
2 parents 1ec74f1 + b9224c1 commit 7935135

11 files changed

Lines changed: 366 additions & 147 deletions

File tree

src/link/Wasm.zig

Lines changed: 169 additions & 116 deletions
Large diffs are not rendered by default.

src/link/Wasm/Atom.zig

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -90,24 +90,26 @@ pub fn getFirst(atom: *Atom) *Atom {
9090
return tmp;
9191
}
9292

93-
/// Unlike `getFirst` this returns the first `*Atom` that was
94-
/// produced from Zig code, rather than an object file.
95-
/// This is useful for debug sections where we want to extend
96-
/// the bytes, and don't want to overwrite existing Atoms.
97-
pub fn getFirstZigAtom(atom: *Atom) *Atom {
98-
if (atom.file == null) return atom;
99-
var tmp = atom;
100-
return while (tmp.prev) |prev| {
101-
if (prev.file == null) break prev;
102-
tmp = prev;
103-
} else unreachable; // must allocate an Atom first!
104-
}
105-
10693
/// Returns the location of the symbol that represents this `Atom`
10794
pub fn symbolLoc(atom: Atom) Wasm.SymbolLoc {
10895
return .{ .file = atom.file, .index = atom.sym_index };
10996
}
11097

98+
/// Returns the virtual address of the `Atom`. This is the address starting
99+
/// from the first entry within a section.
100+
pub fn getVA(atom: Atom, wasm: *const Wasm, symbol: *const Symbol) u32 {
101+
if (symbol.tag == .function) return atom.offset;
102+
std.debug.assert(symbol.tag == .data);
103+
const merge_segment = wasm.base.options.output_mode != .Obj;
104+
const segment_info = if (atom.file) |object_index| blk: {
105+
break :blk wasm.objects.items[object_index].segment_info;
106+
} else wasm.segment_info.values();
107+
const segment_name = segment_info[symbol.index].outputName(merge_segment);
108+
const segment_index = wasm.data_segments.get(segment_name).?;
109+
const segment = wasm.segments.items[segment_index];
110+
return segment.offset + atom.offset;
111+
}
112+
111113
/// Resolves the relocations within the atom, writing the new value
112114
/// at the calculated offset.
113115
pub fn resolveRelocs(atom: *Atom, wasm_bin: *const Wasm) void {
@@ -159,7 +161,7 @@ pub fn resolveRelocs(atom: *Atom, wasm_bin: *const Wasm) void {
159161
/// The final value must be casted to the correct size.
160162
fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wasm) u64 {
161163
const target_loc = (Wasm.SymbolLoc{ .file = atom.file, .index = relocation.index }).finalLoc(wasm_bin);
162-
const symbol = target_loc.getSymbol(wasm_bin).*;
164+
const symbol = target_loc.getSymbol(wasm_bin);
163165
switch (relocation.relocation_type) {
164166
.R_WASM_FUNCTION_INDEX_LEB => return symbol.index,
165167
.R_WASM_TABLE_NUMBER_LEB => return symbol.index,
@@ -190,17 +192,9 @@ fn relocationValue(atom: Atom, relocation: types.Relocation, wasm_bin: *const Wa
190192
if (symbol.isUndefined()) {
191193
return 0;
192194
}
193-
194-
const merge_segment = wasm_bin.base.options.output_mode != .Obj;
195195
const target_atom = wasm_bin.symbol_atom.get(target_loc).?;
196-
const segment_info = if (target_atom.file) |object_index| blk: {
197-
break :blk wasm_bin.objects.items[object_index].segment_info;
198-
} else wasm_bin.segment_info.values();
199-
const segment_name = segment_info[symbol.index].outputName(merge_segment);
200-
const segment_index = wasm_bin.data_segments.get(segment_name).?;
201-
const segment = wasm_bin.segments.items[segment_index];
202-
const rel_value = @intCast(i32, target_atom.offset + segment.offset) + relocation.addend;
203-
return @intCast(u32, rel_value);
196+
const va = @intCast(i32, target_atom.getVA(wasm_bin, symbol));
197+
return @intCast(u32, va + relocation.addend);
204198
},
205199
.R_WASM_EVENT_INDEX_LEB => return symbol.index,
206200
.R_WASM_SECTION_OFFSET_I32 => {

src/link/Wasm/Symbol.zig

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,10 @@ pub fn isNoStrip(symbol: Symbol) bool {
139139
return symbol.flags & @enumToInt(Flag.WASM_SYM_NO_STRIP) != 0;
140140
}
141141

142-
pub fn isExported(symbol: Symbol) bool {
142+
pub fn isExported(symbol: Symbol, is_dynamic: bool) bool {
143143
if (symbol.isUndefined() or symbol.isLocal()) return false;
144-
if (symbol.isHidden()) return false;
145-
if (symbol.hasFlag(.WASM_SYM_EXPORTED)) return true;
146-
if (symbol.hasFlag(.WASM_SYM_BINDING_WEAK)) return false;
147-
return true;
144+
if (is_dynamic and symbol.isVisible()) return true;
145+
return symbol.hasFlag(.WASM_SYM_EXPORTED);
148146
}
149147

150148
pub fn isWeak(symbol: Symbol) bool {

test/link.zig

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@ fn addWasmCases(cases: *tests.StandaloneContext) void {
4242
.requires_stage2 = true,
4343
});
4444

45+
cases.addBuildFile("test/link/wasm/export/build.zig", .{
46+
.build_modes = true,
47+
.requires_stage2 = true,
48+
});
49+
50+
// TODO: Fix open handle in wasm-linker refraining rename from working on Windows.
51+
if (builtin.os.tag != .windows) {
52+
cases.addBuildFile("test/link/wasm/export-data/build.zig", .{});
53+
}
54+
4555
cases.addBuildFile("test/link/wasm/extern/build.zig", .{
4656
.build_modes = true,
4757
.requires_stage2 = true,
@@ -53,6 +63,11 @@ fn addWasmCases(cases: *tests.StandaloneContext) void {
5363
.requires_stage2 = true,
5464
});
5565

66+
cases.addBuildFile("test/link/wasm/function-table/build.zig", .{
67+
.build_modes = true,
68+
.requires_stage2 = true,
69+
});
70+
5671
cases.addBuildFile("test/link/wasm/infer-features/build.zig", .{
5772
.requires_stage2 = true,
5873
});

test/link/wasm/bss/build.zig

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ pub fn build(b: *Builder) void {
2626
check_lib.checkNext("name memory"); // as per linker specification
2727

2828
// since we are importing memory, ensure it's not exported
29-
check_lib.checkStart("Section export");
30-
check_lib.checkNext("entries 1"); // we're exporting function 'foo' so only 1 entry
29+
check_lib.checkNotPresent("Section export");
3130

3231
// validate the name of the stack pointer
3332
check_lib.checkStart("Section custom");
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
const std = @import("std");
2+
const Builder = std.build.Builder;
3+
4+
pub fn build(b: *Builder) void {
5+
const test_step = b.step("test", "Test");
6+
test_step.dependOn(b.getInstallStep());
7+
8+
const lib = b.addSharedLibrary("lib", "lib.zig", .unversioned);
9+
lib.setBuildMode(.ReleaseSafe); // to make the output deterministic in address positions
10+
lib.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
11+
lib.use_lld = false;
12+
lib.export_symbol_names = &.{ "foo", "bar" };
13+
lib.global_base = 0; // put data section at address 0 to make data symbols easier to parse
14+
15+
const check_lib = lib.checkObject(.wasm);
16+
17+
check_lib.checkStart("Section global");
18+
check_lib.checkNext("entries 3");
19+
check_lib.checkNext("type i32"); // stack pointer so skip other fields
20+
check_lib.checkNext("type i32");
21+
check_lib.checkNext("mutable false");
22+
check_lib.checkNext("i32.const {foo_address}");
23+
check_lib.checkNext("type i32");
24+
check_lib.checkNext("mutable false");
25+
check_lib.checkNext("i32.const {bar_address}");
26+
check_lib.checkComputeCompare("foo_address", .{ .op = .eq, .value = .{ .literal = 0 } });
27+
check_lib.checkComputeCompare("bar_address", .{ .op = .eq, .value = .{ .literal = 4 } });
28+
29+
check_lib.checkStart("Section export");
30+
check_lib.checkNext("entries 3");
31+
check_lib.checkNext("name foo");
32+
check_lib.checkNext("kind global");
33+
check_lib.checkNext("index 1");
34+
check_lib.checkNext("name bar");
35+
check_lib.checkNext("kind global");
36+
check_lib.checkNext("index 2");
37+
38+
test_step.dependOn(&check_lib.step);
39+
}

test/link/wasm/export-data/lib.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const foo: u32 = 0xbbbbbbbb;
2+
export const bar: u32 = 0xbbbbbbbb;

test/link/wasm/export/build.zig

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
const std = @import("std");
2+
3+
pub fn build(b: *std.build.Builder) void {
4+
const mode = b.standardReleaseOptions();
5+
6+
const no_export = b.addSharedLibrary("no-export", "main.zig", .unversioned);
7+
no_export.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
8+
no_export.setBuildMode(mode);
9+
no_export.use_llvm = false;
10+
no_export.use_lld = false;
11+
12+
const dynamic_export = b.addSharedLibrary("dynamic", "main.zig", .unversioned);
13+
dynamic_export.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
14+
dynamic_export.setBuildMode(mode);
15+
dynamic_export.rdynamic = true;
16+
dynamic_export.use_llvm = false;
17+
dynamic_export.use_lld = false;
18+
19+
const force_export = b.addSharedLibrary("force", "main.zig", .unversioned);
20+
force_export.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
21+
force_export.setBuildMode(mode);
22+
force_export.export_symbol_names = &.{"foo"};
23+
force_export.use_llvm = false;
24+
force_export.use_lld = false;
25+
26+
const check_no_export = no_export.checkObject(.wasm);
27+
check_no_export.checkStart("Section export");
28+
check_no_export.checkNext("entries 1");
29+
check_no_export.checkNext("name memory");
30+
check_no_export.checkNext("kind memory");
31+
32+
const check_dynamic_export = dynamic_export.checkObject(.wasm);
33+
check_dynamic_export.checkStart("Section export");
34+
check_dynamic_export.checkNext("entries 2");
35+
check_dynamic_export.checkNext("name foo");
36+
check_dynamic_export.checkNext("kind function");
37+
38+
const check_force_export = force_export.checkObject(.wasm);
39+
check_force_export.checkStart("Section export");
40+
check_force_export.checkNext("entries 2");
41+
check_force_export.checkNext("name foo");
42+
check_force_export.checkNext("kind function");
43+
44+
const test_step = b.step("test", "Run linker test");
45+
test_step.dependOn(&check_no_export.step);
46+
test_step.dependOn(&check_dynamic_export.step);
47+
test_step.dependOn(&check_force_export.step);
48+
}

test/link/wasm/export/main.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export fn foo() void {}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
const std = @import("std");
2+
const Builder = std.build.Builder;
3+
4+
pub fn build(b: *Builder) void {
5+
const mode = b.standardReleaseOptions();
6+
7+
const test_step = b.step("test", "Test");
8+
test_step.dependOn(b.getInstallStep());
9+
10+
const import_table = b.addSharedLibrary("lib", "lib.zig", .unversioned);
11+
import_table.setBuildMode(mode);
12+
import_table.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
13+
import_table.use_llvm = false;
14+
import_table.use_lld = false;
15+
import_table.import_table = true;
16+
17+
const export_table = b.addSharedLibrary("lib", "lib.zig", .unversioned);
18+
export_table.setBuildMode(mode);
19+
export_table.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
20+
export_table.use_llvm = false;
21+
export_table.use_lld = false;
22+
export_table.export_table = true;
23+
24+
const regular_table = b.addSharedLibrary("lib", "lib.zig", .unversioned);
25+
regular_table.setBuildMode(mode);
26+
regular_table.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
27+
regular_table.use_llvm = false;
28+
regular_table.use_lld = false;
29+
30+
const check_import = import_table.checkObject(.wasm);
31+
const check_export = export_table.checkObject(.wasm);
32+
const check_regular = regular_table.checkObject(.wasm);
33+
34+
check_import.checkStart("Section import");
35+
check_import.checkNext("entries 1");
36+
check_import.checkNext("module env");
37+
check_import.checkNext("name __indirect_function_table");
38+
check_import.checkNext("kind table");
39+
check_import.checkNext("type funcref");
40+
check_import.checkNext("min 1"); // 1 function pointer
41+
check_import.checkNotPresent("max"); // when importing, we do not provide a max
42+
check_import.checkNotPresent("Section table"); // we're importing it
43+
44+
check_export.checkStart("Section export");
45+
check_export.checkNext("entries 2");
46+
check_export.checkNext("name __indirect_function_table"); // as per linker specification
47+
check_export.checkNext("kind table");
48+
49+
check_regular.checkStart("Section table");
50+
check_regular.checkNext("entries 1");
51+
check_regular.checkNext("type funcref");
52+
check_regular.checkNext("min 2"); // index starts at 1 & 1 function pointer = 2.
53+
check_regular.checkNext("max 2");
54+
check_regular.checkStart("Section element");
55+
check_regular.checkNext("entries 1");
56+
check_regular.checkNext("table index 0");
57+
check_regular.checkNext("i32.const 1"); // we want to start function indexes at 1
58+
check_regular.checkNext("indexes 1"); // 1 function pointer
59+
60+
test_step.dependOn(&check_import.step);
61+
test_step.dependOn(&check_export.step);
62+
test_step.dependOn(&check_regular.step);
63+
}

0 commit comments

Comments
 (0)