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
signaturelock-open 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 {...@@ -1571,7 +1571,7 @@ fn analyzeRootSrcFile(self: *Module, root_scope: *Scope.File) !void {
1571 .macho => {1571 .macho => {
1572 // TODO Implement for MachO1572 // TODO Implement for MachO
1573 },1573 },
1574 .c => {},1574 .c, .wasm => {},
1575 }1575 }
1576 }1576 }
1577 } else {1577 } else {
...@@ -1781,11 +1781,13 @@ fn allocateNewDecl(...@@ -1781,11 +1781,13 @@ fn allocateNewDecl(
1781 .elf => .{ .elf = link.File.Elf.TextBlock.empty },1781 .elf => .{ .elf = link.File.Elf.TextBlock.empty },
1782 .macho => .{ .macho = link.File.MachO.TextBlock.empty },1782 .macho => .{ .macho = link.File.MachO.TextBlock.empty },
1783 .c => .{ .c = {} },1783 .c => .{ .c = {} },
1784 .wasm => .{ .wasm = {} },
1784 },1785 },
1785 .fn_link = switch (self.bin_file.tag) {1786 .fn_link = switch (self.bin_file.tag) {
1786 .elf => .{ .elf = link.File.Elf.SrcFn.empty },1787 .elf => .{ .elf = link.File.Elf.SrcFn.empty },
1787 .macho => .{ .macho = link.File.MachO.SrcFn.empty },1788 .macho => .{ .macho = link.File.MachO.SrcFn.empty },
1788 .c => .{ .c = {} },1789 .c => .{ .c = {} },
1790 .wasm => .{ .wasm = null },
1789 },1791 },
1790 .generation = 0,1792 .generation = 0,
1791 };1793 };
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 {...@@ -46,12 +46,14 @@ pub const File = struct {
46 elf: Elf.TextBlock,46 elf: Elf.TextBlock,
47 macho: MachO.TextBlock,47 macho: MachO.TextBlock,
48 c: void,48 c: void,
49 wasm: void,
49 };50 };
5051
51 pub const LinkFn = union {52 pub const LinkFn = union {
52 elf: Elf.SrcFn,53 elf: Elf.SrcFn,
53 macho: MachO.SrcFn,54 macho: MachO.SrcFn,
54 c: void,55 c: void,
56 wasm: ?Wasm.FnData,
55 };57 };
5658
57 tag: Tag,59 tag: Tag,
...@@ -69,7 +71,7 @@ pub const File = struct {...@@ -69,7 +71,7 @@ pub const File = struct {
69 .coff => return error.TODOImplementCoff,71 .coff => return error.TODOImplementCoff,
70 .elf => return Elf.openPath(allocator, dir, sub_path, options),72 .elf => return Elf.openPath(allocator, dir, sub_path, options),
71 .macho => return MachO.openPath(allocator, dir, sub_path, options),73 .macho => return MachO.openPath(allocator, dir, sub_path, options),
72 .wasm => return error.TODOImplementWasm,74 .wasm => return Wasm.openPath(allocator, dir, sub_path, options),
73 .c => return C.openPath(allocator, dir, sub_path, options),75 .c => return C.openPath(allocator, dir, sub_path, options),
74 .hex => return error.TODOImplementHex,76 .hex => return error.TODOImplementHex,
75 .raw => return error.TODOImplementRaw,77 .raw => return error.TODOImplementRaw,
...@@ -93,7 +95,7 @@ pub const File = struct {...@@ -93,7 +95,7 @@ pub const File = struct {
93 .mode = determineMode(base.options),95 .mode = determineMode(base.options),
94 });96 });
95 },97 },
96 .c => {},98 .c, .wasm => {},
97 }99 }
98 }100 }
99101
...@@ -102,6 +104,7 @@ pub const File = struct {...@@ -102,6 +104,7 @@ pub const File = struct {
102 if (base.file) |f| {104 if (base.file) |f| {
103 f.close();105 f.close();
104 base.file = null;106 base.file = null;
107
105 }108 }
106 }109 }
107110
...@@ -110,6 +113,7 @@ pub const File = struct {...@@ -110,6 +113,7 @@ pub const File = struct {
110 .elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl),113 .elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl),
111 .macho => return @fieldParentPtr(MachO, "base", base).updateDecl(module, decl),114 .macho => return @fieldParentPtr(MachO, "base", base).updateDecl(module, decl),
112 .c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl),115 .c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl),
116 .wasm => return @fieldParentPtr(Wasm, "base", base).updateDecl(module, decl),
113 }117 }
114 }118 }
115119
...@@ -117,7 +121,7 @@ pub const File = struct {...@@ -117,7 +121,7 @@ pub const File = struct {
117 switch (base.tag) {121 switch (base.tag) {
118 .elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl),122 .elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl),
119 .macho => return @fieldParentPtr(MachO, "base", base).updateDeclLineNumber(module, decl),123 .macho => return @fieldParentPtr(MachO, "base", base).updateDeclLineNumber(module, decl),
120 .c => {},124 .c, .wasm => {},
121 }125 }
122 }126 }
123127
...@@ -125,7 +129,7 @@ pub const File = struct {...@@ -125,7 +129,7 @@ pub const File = struct {
125 switch (base.tag) {129 switch (base.tag) {
126 .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),130 .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),
127 .macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl),131 .macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl),
128 .c => {},132 .c, .wasm => {},
129 }133 }
130 }134 }
131135
...@@ -135,6 +139,7 @@ pub const File = struct {...@@ -135,6 +139,7 @@ pub const File = struct {
135 .elf => @fieldParentPtr(Elf, "base", base).deinit(),139 .elf => @fieldParentPtr(Elf, "base", base).deinit(),
136 .macho => @fieldParentPtr(MachO, "base", base).deinit(),140 .macho => @fieldParentPtr(MachO, "base", base).deinit(),
137 .c => @fieldParentPtr(C, "base", base).deinit(),141 .c => @fieldParentPtr(C, "base", base).deinit(),
142 .wasm => @fieldParentPtr(Wasm, "base", base).deinit(),
138 }143 }
139 }144 }
140145
...@@ -155,6 +160,11 @@ pub const File = struct {...@@ -155,6 +160,11 @@ pub const File = struct {
155 parent.deinit();160 parent.deinit();
156 base.allocator.destroy(parent);161 base.allocator.destroy(parent);
157 },162 },
163 .wasm => {
164 const parent = @fieldParentPtr(Wasm, "base", base);
165 parent.deinit();
166 base.allocator.destroy(parent);
167 },
158 }168 }
159 }169 }
160170
...@@ -167,6 +177,7 @@ pub const File = struct {...@@ -167,6 +177,7 @@ pub const File = struct {
167 .elf => @fieldParentPtr(Elf, "base", base).flush(),177 .elf => @fieldParentPtr(Elf, "base", base).flush(),
168 .macho => @fieldParentPtr(MachO, "base", base).flush(),178 .macho => @fieldParentPtr(MachO, "base", base).flush(),
169 .c => @fieldParentPtr(C, "base", base).flush(),179 .c => @fieldParentPtr(C, "base", base).flush(),
180 .wasm => @fieldParentPtr(Wasm, "base", base).flush(),
170 };181 };
171 }182 }
172183
...@@ -175,6 +186,7 @@ pub const File = struct {...@@ -175,6 +186,7 @@ pub const File = struct {
175 .elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl),186 .elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl),
176 .macho => @fieldParentPtr(MachO, "base", base).freeDecl(decl),187 .macho => @fieldParentPtr(MachO, "base", base).freeDecl(decl),
177 .c => unreachable,188 .c => unreachable,
189 .wasm => @fieldParentPtr(Wasm, "base", base).freeDecl(decl),
178 }190 }
179 }191 }
180192
...@@ -183,6 +195,7 @@ pub const File = struct {...@@ -183,6 +195,7 @@ pub const File = struct {
183 .elf => @fieldParentPtr(Elf, "base", base).error_flags,195 .elf => @fieldParentPtr(Elf, "base", base).error_flags,
184 .macho => @fieldParentPtr(MachO, "base", base).error_flags,196 .macho => @fieldParentPtr(MachO, "base", base).error_flags,
185 .c => return .{ .no_entry_point_found = false },197 .c => return .{ .no_entry_point_found = false },
198 .wasm => return ErrorFlags{},
186 };199 };
187 }200 }
188201
...@@ -197,6 +210,7 @@ pub const File = struct {...@@ -197,6 +210,7 @@ pub const File = struct {
197 .elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports),210 .elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports),
198 .macho => return @fieldParentPtr(MachO, "base", base).updateDeclExports(module, decl, exports),211 .macho => return @fieldParentPtr(MachO, "base", base).updateDeclExports(module, decl, exports),
199 .c => return {},212 .c => return {},
213 .wasm => return @fieldParentPtr(Wasm, "base", base).updateDeclExports(module, decl, exports),
200 }214 }
201 }215 }
202216
...@@ -204,6 +218,7 @@ pub const File = struct {...@@ -204,6 +218,7 @@ pub const File = struct {
204 elf,218 elf,
205 macho,219 macho,
206 c,220 c,
221 wasm,
207 };222 };
208223
209 pub const ErrorFlags = struct {224 pub const ErrorFlags = struct {
...@@ -2832,6 +2847,7 @@ pub const File = struct {...@@ -2832,6 +2847,7 @@ pub const File = struct {
2832 };2847 };
28332848
2834 pub const MachO = @import("link/MachO.zig");2849 pub const MachO = @import("link/MachO.zig");
2850 const Wasm = @import("link/Wasm.zig");
2835};2851};
28362852
2837/// Saturating multiplication2853/// 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 =...@@ -150,6 +150,7 @@ const usage_build_generic =
150 \\ -ofmt=[mode] Override target object format150 \\ -ofmt=[mode] Override target object format
151 \\ elf Executable and Linking Format151 \\ elf Executable and Linking Format
152 \\ c Compile to C source code152 \\ c Compile to C source code
153 \\ wasm WebAssembly
153 \\ coff (planned) Common Object File Format (Windows)154 \\ coff (planned) Common Object File Format (Windows)
154 \\ pe (planned) Portable Executable (Windows)155 \\ pe (planned) Portable Executable (Windows)
155 \\ macho (planned) macOS relocatables156 \\ macho (planned) macOS relocatables