authorgravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2020-08-07 00:53:55+02:00
committergravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2020-08-18 00:32:58+02:00
log3370b5f1097d0610048e29b322b4cbf81ff0a431
tree5e21fa0c927c13619d9e7a128eccaf8b3f5bba80
parent96a27557e2ef53b8d1d3132f02c83b716c966277
signature Commit is signed but in an unrecognized format.

stage2/wasm: implement basic container generation

Thus far, we only generate the type, function, export, and code sections. These are sufficient to generate and export simple functions. Codegen is currently hardcoded to `i32.const 42`, the main goal of this commit is to create infrastructure for the container format which will work with incremental compilation.

5 files changed, 539 insertions(+), 5 deletions(-)

src-self-hosted/Module.zig+3-1
......@@ -1571,7 +1571,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
15711571 .macho => {
15721572 // TODO Implement for MachO
15731573 },
1574 .c => {},
1574 .c, .wasm => {},
15751575 }
15761576 }
15771577 } else {
......@@ -1781,11 +1781,13 @@ fn allocateNewDecl(
17811781 .elf => .{ .elf = link.File.Elf.TextBlock.empty },
17821782 .macho => .{ .macho = link.File.MachO.TextBlock.empty },
17831783 .c => .{ .c = {} },
1784 .wasm => .{ .wasm = {} },
17841785 },
17851786 .fn_link = switch (self.bin_file.tag) {
17861787 .elf => .{ .elf = link.File.Elf.SrcFn.empty },
17871788 .macho => .{ .macho = link.File.MachO.SrcFn.empty },
17881789 .c => .{ .c = {} },
1790 .wasm => .{ .wasm = null },
17891791 },
17901792 .generation = 0,
17911793 };
src-self-hosted/codegen/wasm.zig created+70
......@@ -0,0 +1,70 @@
1const std = @import("std");
2const Allocator = std.mem.Allocator;
3const ArrayList = std.ArrayList;
4const assert = std.debug.assert;
5const leb = std.debug.leb;
6
7const Decl = @import("../Module.zig").Decl;
8const Type = @import("../type.zig").Type;
9
10fn genValtype(ty: Type) u8 {
11 return switch (ty.tag()) {
12 .u32, .i32 => 0x7F,
13 .u64, .i64 => 0x7E,
14 .f32 => 0x7D,
15 .f64 => 0x7C,
16 else => @panic("TODO: Implement more types for wasm."),
17 };
18}
19
20pub fn genFunctype(buf: *ArrayList(u8), decl: *Decl) !void {
21 const ty = decl.typed_value.most_recent.typed_value.ty;
22 const writer = buf.writer();
23
24 // functype magic
25 try writer.writeByte(0x60);
26
27 // param types
28 try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen()));
29 if (ty.fnParamLen() != 0) {
30 const params = try buf.allocator.alloc(Type, ty.fnParamLen());
31 defer buf.allocator.free(params);
32 ty.fnParamTypes(params);
33 for (params) |param_type| try writer.writeByte(genValtype(param_type));
34 }
35
36 // return type
37 const return_type = ty.fnReturnType();
38 switch (return_type.tag()) {
39 .void, .noreturn => try leb.writeULEB128(writer, @as(u32, 0)),
40 else => {
41 try leb.writeULEB128(writer, @as(u32, 1));
42 try writer.writeByte(genValtype(return_type));
43 },
44 }
45}
46
47pub fn genCode(buf: *ArrayList(u8), decl: *Decl) !void {
48 assert(buf.items.len == 0);
49 const writer = buf.writer();
50
51 // Reserve space to write the size after generating the code
52 try writer.writeAll(&([1]u8{undefined} ** 5));
53
54 // Write the size of the locals vec
55 // TODO: implement locals
56 try leb.writeULEB128(writer, @as(u32, 0));
57
58 // Write instructions
59
60 // TODO: actually implement codegen
61 try writer.writeByte(0x41); // i32.const
62 try leb.writeILEB128(writer, @as(i32, 42));
63
64 // Write 'end' opcode
65 try writer.writeByte(0x0B);
66
67 // Fill in the size of the generated code to the reserved space at the
68 // beginning of the buffer.
69 leb.writeUnsignedFixed(5, buf.items[0..5], @intCast(u32, buf.items.len - 5));
70}
src-self-hosted/link.zig+20-4
......@@ -46,12 +46,14 @@ pub const File = struct {
4646 elf: Elf.TextBlock,
4747 macho: MachO.TextBlock,
4848 c: void,
49 wasm: void,
4950 };
5051
5152 pub const LinkFn = union {
5253 elf: Elf.SrcFn,
5354 macho: MachO.SrcFn,
5455 c: void,
56 wasm: ?Wasm.FnData,
5557 };
5658
5759 tag: Tag,
......@@ -69,7 +71,7 @@ pub const File = struct {
6971 .coff => return error.TODOImplementCoff,
7072 .elf => return Elf.openPath(allocator, dir, sub_path, options),
7173 .macho => return MachO.openPath(allocator, dir, sub_path, options),
72 .wasm => return error.TODOImplementWasm,
74 .wasm => return Wasm.openPath(allocator, dir, sub_path, options),
7375 .c => return C.openPath(allocator, dir, sub_path, options),
7476 .hex => return error.TODOImplementHex,
7577 .raw => return error.TODOImplementRaw,
......@@ -93,7 +95,7 @@ pub const File = struct {
9395 .mode = determineMode(base.options),
9496 });
9597 },
96 .c => {},
98 .c, .wasm => {},
9799 }
98100 }
99101
......@@ -102,6 +104,7 @@ pub const File = struct {
102104 if (base.file) |f| {
103105 f.close();
104106 base.file = null;
107
105108 }
106109 }
107110
......@@ -110,6 +113,7 @@ pub const File = struct {
110113 .elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl),
111114 .macho => return @fieldParentPtr(MachO, "base", base).updateDecl(module, decl),
112115 .c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl),
116 .wasm => return @fieldParentPtr(Wasm, "base", base).updateDecl(module, decl),
113117 }
114118 }
115119
......@@ -117,7 +121,7 @@ pub const File = struct {
117121 switch (base.tag) {
118122 .elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl),
119123 .macho => return @fieldParentPtr(MachO, "base", base).updateDeclLineNumber(module, decl),
120 .c => {},
124 .c, .wasm => {},
121125 }
122126 }
123127
......@@ -125,7 +129,7 @@ pub const File = struct {
125129 switch (base.tag) {
126130 .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),
127131 .macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl),
128 .c => {},
132 .c, .wasm => {},
129133 }
130134 }
131135
......@@ -135,6 +139,7 @@ pub const File = struct {
135139 .elf => @fieldParentPtr(Elf, "base", base).deinit(),
136140 .macho => @fieldParentPtr(MachO, "base", base).deinit(),
137141 .c => @fieldParentPtr(C, "base", base).deinit(),
142 .wasm => @fieldParentPtr(Wasm, "base", base).deinit(),
138143 }
139144 }
140145
......@@ -155,6 +160,11 @@ pub const File = struct {
155160 parent.deinit();
156161 base.allocator.destroy(parent);
157162 },
163 .wasm => {
164 const parent = @fieldParentPtr(Wasm, "base", base);
165 parent.deinit();
166 base.allocator.destroy(parent);
167 },
158168 }
159169 }
160170
......@@ -167,6 +177,7 @@ pub const File = struct {
167177 .elf => @fieldParentPtr(Elf, "base", base).flush(),
168178 .macho => @fieldParentPtr(MachO, "base", base).flush(),
169179 .c => @fieldParentPtr(C, "base", base).flush(),
180 .wasm => @fieldParentPtr(Wasm, "base", base).flush(),
170181 };
171182 }
172183
......@@ -175,6 +186,7 @@ pub const File = struct {
175186 .elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl),
176187 .macho => @fieldParentPtr(MachO, "base", base).freeDecl(decl),
177188 .c => unreachable,
189 .wasm => @fieldParentPtr(Wasm, "base", base).freeDecl(decl),
178190 }
179191 }
180192
......@@ -183,6 +195,7 @@ pub const File = struct {
183195 .elf => @fieldParentPtr(Elf, "base", base).error_flags,
184196 .macho => @fieldParentPtr(MachO, "base", base).error_flags,
185197 .c => return .{ .no_entry_point_found = false },
198 .wasm => return ErrorFlags{},
186199 };
187200 }
188201
......@@ -197,6 +210,7 @@ pub const File = struct {
197210 .elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports),
198211 .macho => return @fieldParentPtr(MachO, "base", base).updateDeclExports(module, decl, exports),
199212 .c => return {},
213 .wasm => return @fieldParentPtr(Wasm, "base", base).updateDeclExports(module, decl, exports),
200214 }
201215 }
202216
......@@ -204,6 +218,7 @@ pub const File = struct {
204218 elf,
205219 macho,
206220 c,
221 wasm,
207222 };
208223
209224 pub const ErrorFlags = struct {
......@@ -2832,6 +2847,7 @@ pub const File = struct {
28322847 };
28332848
28342849 pub const MachO = @import("link/MachO.zig");
2850 const Wasm = @import("link/Wasm.zig");
28352851};
28362852
28372853/// Saturating multiplication
src-self-hosted/link/Wasm.zig created+445
......@@ -0,0 +1,445 @@
1const Wasm = @This();
2
3const std = @import("std");
4const Allocator = std.mem.Allocator;
5const assert = std.debug.assert;
6const fs = std.fs;
7const leb = std.debug.leb;
8
9const Module = @import("../Module.zig");
10const codegen = @import("../codegen/wasm.zig");
11const link = @import("../link.zig");
12
13/// Various magic numbers defined by the wasm spec
14const spec = struct {
15 const magic = [_]u8{ 0x00, 0x61, 0x73, 0x6D }; // \0asm
16 const version = [_]u8{ 0x01, 0x00, 0x00, 0x00 }; // version 1
17
18 const custom_id = 0;
19 const types_id = 1;
20 const imports_id = 2;
21 const funcs_id = 3;
22 const tables_id = 4;
23 const memories_id = 5;
24 const globals_id = 6;
25 const exports_id = 7;
26 const start_id = 8;
27 const elements_id = 9;
28 const code_id = 10;
29 const data_id = 11;
30};
31
32pub const base_tag = link.File.Tag.wasm;
33
34pub const FnData = struct {
35 funcidx: u32,
36 typeidx: u32,
37};
38
39base: link.File,
40
41types: Types,
42funcs: Funcs,
43exports: Exports,
44
45/// Array over the section structs used in the various sections above to
46/// allow iteration when shifting sections to make space.
47/// TODO: this should eventually be size 11 when we use all the sections.
48sections: [4]*Section,
49
50pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: link.Options) !*link.File {
51 assert(options.object_format == .wasm);
52
53 // TODO: read the file and keep vaild parts instead of truncating
54 const file = try dir.createFile(sub_path, .{ .truncate = true, .read = true });
55 errdefer file.close();
56
57 const wasm = try allocator.create(Wasm);
58 errdefer allocator.destroy(wasm);
59
60 try file.writeAll(&(spec.magic ++ spec.version));
61
62 wasm.base = .{
63 .tag = .wasm,
64 .options = options,
65 .file = file,
66 .allocator = allocator,
67 };
68
69 // TODO: this should vary depending on the section and be less arbitrary
70 const size = 1024;
71 const offset = @sizeOf(@TypeOf(spec.magic ++ spec.version));
72
73 wasm.types = try Types.init(file, offset, size);
74 wasm.funcs = try Funcs.init(file, offset + size, size, offset + 3 * size, size);
75 wasm.exports = try Exports.init(file, offset + 2 * size, size);
76 try file.setEndPos(offset + 4 * size);
77
78 wasm.sections = [_]*Section{
79 &wasm.types.typesec.section,
80 &wasm.funcs.funcsec,
81 &wasm.exports.exportsec,
82 &wasm.funcs.codesec.section,
83 };
84
85 return &wasm.base;
86}
87
88pub fn deinit(self: *Wasm) void {
89 if (self.base.file) |f| f.close();
90 self.types.deinit();
91 self.funcs.deinit();
92}
93
94pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
95 if (decl.typed_value.most_recent.typed_value.ty.zigTypeTag() != .Fn)
96 return error.TODOImplementNonFnDeclsForWasm;
97
98 if (decl.fn_link.wasm) |fn_data| {
99 self.types.free(fn_data.typeidx);
100 self.funcs.free(fn_data.funcidx);
101 }
102
103 var buf = std.ArrayList(u8).init(self.base.allocator);
104 defer buf.deinit();
105
106 try codegen.genFunctype(&buf, decl);
107 const typeidx = try self.types.new(buf.items);
108 buf.items.len = 0;
109
110 try codegen.genCode(&buf, decl);
111 const funcidx = try self.funcs.new(typeidx, buf.items);
112
113 decl.fn_link.wasm = .{ .typeidx = typeidx, .funcidx = funcidx };
114
115 try self.exports.writeAll(module);
116}
117
118pub fn updateDeclExports(
119 self: *Wasm,
120 module: *Module,
121 decl: *const Module.Decl,
122 exports: []const *Module.Export,
123) !void {
124 // TODO: updateDeclExports() may currently be called before updateDecl,
125 // presumably due to a bug. For now just rely on the following call
126 // being made in updateDecl().
127
128 //try self.exports.writeAll(module);
129}
130
131pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
132 // TODO: remove this assert when non-function Decls are implemented
133 assert(decl.typed_value.most_recent.typed_value.ty.zigTypeTag() == .Fn);
134 if (decl.fn_link.wasm) |fn_data| {
135 self.types.free(fn_data.typeidx);
136 self.funcs.free(fn_data.funcidx);
137 decl.fn_link.wasm = null;
138 }
139}
140
141pub fn flush(self: *Wasm) !void {}
142
143/// This struct describes the location of a named section + custom section
144/// padding in the output file. This is all the data we need to allow for
145/// shifting sections around when padding runs out.
146const Section = struct {
147 /// The size of a section header: 1 byte section id + 5 bytes
148 /// for the fixed-width ULEB128 encoded contents size.
149 const header_size = 1 + 5;
150 /// Offset of the section id byte from the start of the file.
151 offset: u64,
152 /// Size of the section, including the header and directly
153 /// following custom section used for padding if any.
154 size: u64,
155
156 /// Resize the usable part of the section, handling the following custom
157 /// section used for padding. If there is not enough padding left, shift
158 /// all following sections to make space. Takes the current and target
159 /// contents sizes of the section as arguments.
160 fn resize(self: *Section, file: fs.File, current: u32, target: u32) !void {
161 // Section header + target contents size + custom section header
162 // + custom section name + empty custom section > owned chunk of the file
163 if (header_size + target + header_size + 1 + 0 > self.size)
164 return error.TODOImplementSectionShifting;
165
166 const new_custom_start = self.offset + header_size + target;
167 const new_custom_contents_size = self.size - target - 2 * header_size;
168 assert(new_custom_contents_size >= 1);
169 // +1 for the name of the custom section, which we set to an empty string
170 var custom_header: [header_size + 1]u8 = undefined;
171 custom_header[0] = spec.custom_id;
172 leb.writeUnsignedFixed(5, custom_header[1..header_size], @intCast(u32, new_custom_contents_size));
173 custom_header[header_size] = 0;
174 try file.pwriteAll(&custom_header, new_custom_start);
175 }
176};
177
178/// This can be used to manage the contents of any section which uses a vector
179/// of contents. This interface maintains index stability while allowing for
180/// reuse of "dead" indexes.
181const VecSection = struct {
182 /// Represents a single entry in the vector (e.g. a type in the type section)
183 const Entry = struct {
184 /// Offset from the start of the section contents in bytes
185 offset: u32,
186 /// Size in bytes of the entry
187 size: u32,
188 };
189 section: Section,
190 /// Size in bytes of the contents of the section. Does not include
191 /// the "header" containing the section id and this value.
192 contents_size: u32,
193 /// List of all entries in the contents of the section.
194 entries: std.ArrayListUnmanaged(Entry) = std.ArrayListUnmanaged(Entry){},
195 /// List of indexes of unreferenced entries which may be
196 /// overwritten and reused.
197 dead_list: std.ArrayListUnmanaged(u32) = std.ArrayListUnmanaged(u32){},
198
199 /// Write the headers of the section and custom padding section
200 fn init(comptime section_id: u8, file: fs.File, offset: u64, initial_size: u64) !VecSection {
201 // section id, section size, empty vector, custom section id,
202 // custom section size, empty custom section name
203 var initial_data: [1 + 5 + 5 + 1 + 5 + 1]u8 = undefined;
204
205 assert(initial_size >= initial_data.len);
206
207 comptime var i = 0;
208 initial_data[i] = section_id;
209 i += 1;
210 leb.writeUnsignedFixed(5, initial_data[i..(i + 5)], 5);
211 i += 5;
212 leb.writeUnsignedFixed(5, initial_data[i..(i + 5)], 0);
213 i += 5;
214 initial_data[i] = spec.custom_id;
215 i += 1;
216 leb.writeUnsignedFixed(5, initial_data[i..(i + 5)], @intCast(u32, initial_size - @sizeOf(@TypeOf(initial_data))));
217 i += 5;
218 initial_data[i] = 0;
219
220 try file.pwriteAll(&initial_data, offset);
221
222 return VecSection{
223 .section = .{
224 .offset = offset,
225 .size = initial_size,
226 },
227 .contents_size = 5,
228 };
229 }
230
231 fn deinit(self: *VecSection, allocator: *Allocator) void {
232 self.entries.deinit(allocator);
233 self.dead_list.deinit(allocator);
234 }
235
236 /// Write a new entry into the file, returning the index used.
237 fn addEntry(self: *VecSection, file: fs.File, allocator: *Allocator, data: []const u8) !u32 {
238 // First look for a dead entry we can reuse
239 for (self.dead_list.items) |dead_idx, i| {
240 const dead_entry = &self.entries.items[dead_idx];
241 if (dead_entry.size == data.len) {
242 // Found a dead entry of the right length, overwrite it
243 try file.pwriteAll(data, self.section.offset + Section.header_size + dead_entry.offset);
244 _ = self.dead_list.swapRemove(i);
245 return dead_idx;
246 }
247 }
248
249 // TODO: We can be more efficient if we special-case one or
250 // more consecutive dead entries at the end of the vector.
251
252 // We failed to find a dead entry to reuse, so write the new
253 // entry to the end of the section.
254 try self.section.resize(file, self.contents_size, self.contents_size + @intCast(u32, data.len));
255 try file.pwriteAll(data, self.section.offset + Section.header_size + self.contents_size);
256 try self.entries.append(allocator, .{
257 .offset = self.contents_size,
258 .size = @intCast(u32, data.len),
259 });
260 self.contents_size += @intCast(u32, data.len);
261 // Make sure the dead list always has enough space to store all free'd
262 // entries. This makes it so that delEntry() cannot fail.
263 // TODO: figure out a better way that doesn't waste as much memory
264 try self.dead_list.ensureCapacity(allocator, self.entries.items.len);
265
266 // Update the size in the section header and the item count of
267 // the contents vector.
268 var size_and_count: [10]u8 = undefined;
269 leb.writeUnsignedFixed(5, size_and_count[0..5], self.contents_size);
270 leb.writeUnsignedFixed(5, size_and_count[5..], @intCast(u32, self.entries.items.len));
271 try file.pwriteAll(&size_and_count, self.section.offset + 1);
272
273 return @intCast(u32, self.entries.items.len - 1);
274 }
275
276 /// Mark the type referenced by the given index as dead.
277 fn delEntry(self: *VecSection, index: u32) void {
278 self.dead_list.appendAssumeCapacity(index);
279 }
280};
281
282const Types = struct {
283 typesec: VecSection,
284
285 fn init(file: fs.File, offset: u64, initial_size: u64) !Types {
286 return Types{ .typesec = try VecSection.init(spec.types_id, file, offset, initial_size) };
287 }
288
289 fn deinit(self: *Types) void {
290 const wasm = @fieldParentPtr(Wasm, "types", self);
291 self.typesec.deinit(wasm.base.allocator);
292 }
293
294 fn new(self: *Types, data: []const u8) !u32 {
295 const wasm = @fieldParentPtr(Wasm, "types", self);
296 return self.typesec.addEntry(wasm.base.file.?, wasm.base.allocator, data);
297 }
298
299 fn free(self: *Types, typeidx: u32) void {
300 self.typesec.delEntry(typeidx);
301 }
302};
303
304const Funcs = struct {
305 /// This section needs special handling to keep the indexes matching with
306 /// the codesec, so we cant just use a VecSection.
307 funcsec: Section,
308 /// Number of functions listed in the funcsec. Must be kept in sync with
309 /// codesec.entries.items.len.
310 funcs_count: u32,
311 codesec: VecSection,
312
313 fn init(file: fs.File, funcs_offset: u64, funcs_size: u64, code_offset: u64, code_size: u64) !Funcs {
314 return Funcs{
315 .funcsec = (try VecSection.init(spec.funcs_id, file, funcs_offset, funcs_size)).section,
316 .funcs_count = 0,
317 .codesec = try VecSection.init(spec.code_id, file, code_offset, code_size),
318 };
319 }
320
321 fn deinit(self: *Funcs) void {
322 const wasm = @fieldParentPtr(Wasm, "funcs", self);
323 self.codesec.deinit(wasm.base.allocator);
324 }
325
326 /// Add a new function to the binary, first finding space for and writing
327 /// the code then writing the typeidx to the corresponding index in the
328 /// funcsec. Returns the function index used.
329 fn new(self: *Funcs, typeidx: u32, code: []const u8) !u32 {
330 const wasm = @fieldParentPtr(Wasm, "funcs", self);
331 const file = wasm.base.file.?;
332 const allocator = wasm.base.allocator;
333
334 assert(self.funcs_count == self.codesec.entries.items.len);
335
336 // TODO: consider nop-padding the code if there is a close but not perfect fit
337 const funcidx = try self.codesec.addEntry(file, allocator, code);
338
339 if (self.funcs_count < self.codesec.entries.items.len) {
340 // u32 vector length + funcs_count u32s in the vector
341 const current = 5 + self.funcs_count * 5;
342 try self.funcsec.resize(file, current, current + 5);
343 self.funcs_count += 1;
344
345 // Update the size in the section header and the item count of
346 // the contents vector.
347 var size_and_count: [10]u8 = undefined;
348 leb.writeUnsignedFixed(5, size_and_count[0..5], 5 + self.funcs_count * 5);
349 leb.writeUnsignedFixed(5, size_and_count[5..], self.funcs_count);
350 try file.pwriteAll(&size_and_count, self.funcsec.offset + 1);
351 }
352 assert(self.funcs_count == self.codesec.entries.items.len);
353
354 var typeidx_leb: [5]u8 = undefined;
355 leb.writeUnsignedFixed(5, &typeidx_leb, typeidx);
356 try file.pwriteAll(&typeidx_leb, self.funcsec.offset + Section.header_size + 5 + funcidx * 5);
357
358 return funcidx;
359 }
360
361 fn free(self: *Funcs, funcidx: u32) void {
362 self.codesec.delEntry(funcidx);
363 }
364};
365
366/// Exports are tricky. We can't leave dead entries in the binary as they
367/// would obviously be visible from the execution environment. The simplest
368/// way to work around this is to re-emit the export section whenever
369/// something changes. This also makes it easier to ensure exported function
370/// and global indexes are updated as they change.
371const Exports = struct {
372 exportsec: Section,
373 /// Size in bytes of the contents of the section. Does not include
374 /// the "header" containing the section id and this value.
375 contents_size: u32,
376
377 fn init(file: fs.File, offset: u64, initial_size: u64) !Exports {
378 return Exports{
379 .exportsec = (try VecSection.init(spec.exports_id, file, offset, initial_size)).section,
380 .contents_size = 5,
381 };
382 }
383
384 fn writeAll(self: *Exports, module: *Module) !void {
385 const wasm = @fieldParentPtr(Wasm, "exports", self);
386 const file = wasm.base.file.?;
387 var buf: [5]u8 = undefined;
388
389 // First ensure the section is the right size
390 var export_count: u32 = 0;
391 var new_contents_size: u32 = 5;
392 for (module.decl_exports.entries.items) |entry| {
393 for (entry.value) |e| {
394 export_count += 1;
395 new_contents_size += calcSize(e);
396 }
397 }
398 if (new_contents_size != self.contents_size) {
399 try self.exportsec.resize(file, self.contents_size, new_contents_size);
400 leb.writeUnsignedFixed(5, &buf, new_contents_size);
401 try file.pwriteAll(&buf, self.exportsec.offset + 1);
402 }
403
404 try file.seekTo(self.exportsec.offset + Section.header_size);
405 const writer = file.writer();
406
407 // Length of the exports vec
408 leb.writeUnsignedFixed(5, &buf, export_count);
409 try writer.writeAll(&buf);
410
411 for (module.decl_exports.entries.items) |entry|
412 for (entry.value) |e| try writeExport(writer, e);
413 }
414
415 /// Return the total number of bytes an export will take.
416 /// TODO: fixed-width LEB128 is currently used for simplicity, but should
417 /// be replaced with proper variable-length LEB128 as it is inefficient.
418 fn calcSize(e: *Module.Export) u32 {
419 // LEB128 name length + name bytes + export type + LEB128 index
420 return 5 + @intCast(u32, e.options.name.len) + 1 + 5;
421 }
422
423 /// Write the data for a single export to the given file at a given offset.
424 /// TODO: fixed-width LEB128 is currently used for simplicity, but should
425 /// be replaced with proper variable-length LEB128 as it is inefficient.
426 fn writeExport(writer: anytype, e: *Module.Export) !void {
427 var buf: [5]u8 = undefined;
428
429 // Export name length + name
430 leb.writeUnsignedFixed(5, &buf, @intCast(u32, e.options.name.len));
431 try writer.writeAll(&buf);
432 try writer.writeAll(e.options.name);
433
434 switch (e.exported_decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) {
435 .Fn => {
436 // Type of the export
437 try writer.writeByte(0x00);
438 // Exported function index
439 leb.writeUnsignedFixed(5, &buf, e.exported_decl.fn_link.wasm.?.funcidx);
440 try writer.writeAll(&buf);
441 },
442 else => return error.TODOImplementNonFnDeclsForWasm,
443 }
444 }
445};
src-self-hosted/main.zig+1
......@@ -150,6 +150,7 @@ const usage_build_generic =
150150 \\ -ofmt=[mode] Override target object format
151151 \\ elf Executable and Linking Format
152152 \\ c Compile to C source code
153 \\ wasm WebAssembly
153154 \\ coff (planned) Common Object File Format (Windows)
154155 \\ pe (planned) Portable Executable (Windows)
155156 \\ macho (planned) macOS relocatables