authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-08-18 21:37:20-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-08-18 21:37:20-04:00
log626d94c2a11aecebf59348d5031df58e7337cfb1
treea6c43da4312a4aba2d853908b5f6c1e4cb10bfe2
parent741fb8d30675f85316f7df100801e7cf8c2b3194
parent6242ae35f37a6e67d9da514fa7a3508843515667
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #6088 from ifreund/s2-wasm-rework

stage2/wasm: do incremental compilation in-memory

3 files changed, 243 insertions(+), 398 deletions(-)

src-self-hosted/codegen/wasm.zig+59-37
...@@ -62,58 +62,80 @@ pub fn genCode(buf: *ArrayList(u8), decl: *Decl) !void {...@@ -62,58 +62,80 @@ pub fn genCode(buf: *ArrayList(u8), decl: *Decl) !void {
62 // TODO: check for and handle death of instructions62 // TODO: check for and handle death of instructions
63 const tv = decl.typed_value.most_recent.typed_value;63 const tv = decl.typed_value.most_recent.typed_value;
64 const mod_fn = tv.val.cast(Value.Payload.Function).?.func;64 const mod_fn = tv.val.cast(Value.Payload.Function).?.func;
65 for (mod_fn.analysis.success.instructions) |inst| try genInst(writer, inst);65 for (mod_fn.analysis.success.instructions) |inst| try genInst(buf, decl, inst);
6666
67 // Write 'end' opcode67 // Write 'end' opcode
68 try writer.writeByte(0x0B);68 try writer.writeByte(0x0B);
6969
70 // Fill in the size of the generated code to the reserved space at the70 // Fill in the size of the generated code to the reserved space at the
71 // beginning of the buffer.71 // beginning of the buffer.
72 leb.writeUnsignedFixed(5, buf.items[0..5], @intCast(u32, buf.items.len - 5));72 const size = buf.items.len - 5 + decl.fn_link.wasm.?.idx_refs.items.len * 5;
73 leb.writeUnsignedFixed(5, buf.items[0..5], @intCast(u32, size));
73}74}
7475
75fn genInst(writer: ArrayList(u8).Writer, inst: *Inst) !void {76fn genInst(buf: *ArrayList(u8), decl: *Decl, inst: *Inst) !void {
76 return switch (inst.tag) {77 return switch (inst.tag) {
78 .call => genCall(buf, decl, inst.castTag(.call).?),
79 .constant => genConstant(buf, decl, inst.castTag(.constant).?),
77 .dbg_stmt => {},80 .dbg_stmt => {},
78 .ret => genRet(writer, inst.castTag(.ret).?),81 .ret => genRet(buf, decl, inst.castTag(.ret).?),
82 .retvoid => {},
79 else => error.TODOImplementMoreWasmCodegen,83 else => error.TODOImplementMoreWasmCodegen,
80 };84 };
81}85}
8286
83fn genRet(writer: ArrayList(u8).Writer, inst: *Inst.UnOp) !void {87fn genConstant(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.Constant) !void {
84 switch (inst.operand.tag) {88 const writer = buf.writer();
85 .constant => {89 switch (inst.base.ty.tag()) {
86 const constant = inst.operand.castTag(.constant).?;90 .u32 => {
87 switch (inst.operand.ty.tag()) {91 try writer.writeByte(0x41); // i32.const
88 .u32 => {92 try leb.writeILEB128(writer, inst.val.toUnsignedInt());
89 try writer.writeByte(0x41); // i32.const93 },
90 try leb.writeILEB128(writer, constant.val.toUnsignedInt());94 .i32 => {
91 },95 try writer.writeByte(0x41); // i32.const
92 .i32 => {96 try leb.writeILEB128(writer, inst.val.toSignedInt());
93 try writer.writeByte(0x41); // i32.const97 },
94 try leb.writeILEB128(writer, constant.val.toSignedInt());98 .u64 => {
95 },99 try writer.writeByte(0x42); // i64.const
96 .u64 => {100 try leb.writeILEB128(writer, inst.val.toUnsignedInt());
97 try writer.writeByte(0x42); // i64.const101 },
98 try leb.writeILEB128(writer, constant.val.toUnsignedInt());102 .i64 => {
99 },103 try writer.writeByte(0x42); // i64.const
100 .i64 => {104 try leb.writeILEB128(writer, inst.val.toSignedInt());
101 try writer.writeByte(0x42); // i64.const
102 try leb.writeILEB128(writer, constant.val.toSignedInt());
103 },
104 .f32 => {
105 try writer.writeByte(0x43); // f32.const
106 // TODO: enforce LE byte order
107 try writer.writeAll(mem.asBytes(&constant.val.toFloat(f32)));
108 },
109 .f64 => {
110 try writer.writeByte(0x44); // f64.const
111 // TODO: enforce LE byte order
112 try writer.writeAll(mem.asBytes(&constant.val.toFloat(f64)));
113 },
114 else => return error.TODOImplementMoreWasmCodegen,
115 }
116 },105 },
106 .f32 => {
107 try writer.writeByte(0x43); // f32.const
108 // TODO: enforce LE byte order
109 try writer.writeAll(mem.asBytes(&inst.val.toFloat(f32)));
110 },
111 .f64 => {
112 try writer.writeByte(0x44); // f64.const
113 // TODO: enforce LE byte order
114 try writer.writeAll(mem.asBytes(&inst.val.toFloat(f64)));
115 },
116 .void => {},
117 else => return error.TODOImplementMoreWasmCodegen,117 else => return error.TODOImplementMoreWasmCodegen,
118 }118 }
119}119}
120
121fn genRet(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.UnOp) !void {
122 try genInst(buf, decl, inst.operand);
123}
124
125fn genCall(buf: *ArrayList(u8), decl: *Decl, inst: *Inst.Call) !void {
126 const func_inst = inst.func.castTag(.constant).?;
127 const func_val = func_inst.val.cast(Value.Payload.Function).?;
128 const target = func_val.func.owner_decl;
129 const target_ty = target.typed_value.most_recent.typed_value.ty;
130
131 if (inst.args.len != 0) return error.TODOImplementMoreWasmCodegen;
132
133 try buf.append(0x10); // call
134
135 // The function index immediate argument will be filled in using this data
136 // in link.Wasm.flush().
137 try decl.fn_link.wasm.?.idx_refs.append(buf.allocator, .{
138 .offset = @intCast(u32, buf.items.len),
139 .decl = target,
140 });
141}
src-self-hosted/link/Wasm.zig+158-360
...@@ -32,19 +32,22 @@ const spec = struct {...@@ -32,19 +32,22 @@ const spec = struct {
32pub const base_tag = link.File.Tag.wasm;32pub const base_tag = link.File.Tag.wasm;
3333
34pub const FnData = struct {34pub const FnData = struct {
35 funcidx: u32,35 /// Generated code for the type of the function
36 functype: std.ArrayListUnmanaged(u8) = .{},
37 /// Generated code for the body of the function
38 code: std.ArrayListUnmanaged(u8) = .{},
39 /// Locations in the generated code where function indexes must be filled in.
40 /// This must be kept ordered by offset.
41 idx_refs: std.ArrayListUnmanaged(struct { offset: u32, decl: *Module.Decl }) = .{},
36};42};
3743
38base: link.File,44base: link.File,
3945
40types: Types,46/// List of all function Decls to be written to the output file. The index of
41funcs: Funcs,47/// each Decl in this list at the time of writing the binary is used as the
42exports: Exports,48/// function index.
4349/// TODO: can/should we access some data structure in Module directly?
44/// Array over the section structs used in the various sections above to50funcs: std.ArrayListUnmanaged(*Module.Decl) = .{},
45/// allow iteration when shifting sections to make space.
46/// TODO: this should eventually be size 11 when we use all the sections.
47sections: [4]*Section,
4851
49pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: link.Options) !*link.File {52pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: link.Options) !*link.File {
50 assert(options.object_format == .wasm);53 assert(options.object_format == .wasm);
...@@ -58,10 +61,6 @@ pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, option...@@ -58,10 +61,6 @@ pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, option
5861
59 try file.writeAll(&(spec.magic ++ spec.version));62 try file.writeAll(&(spec.magic ++ spec.version));
6063
61 // TODO: this should vary depending on the section and be less arbitrary
62 const size = 1024;
63 const offset = @sizeOf(@TypeOf(spec.magic ++ spec.version));
64
65 wasm.* = .{64 wasm.* = .{
66 .base = .{65 .base = .{
67 .tag = .wasm,66 .tag = .wasm,
...@@ -69,52 +68,42 @@ pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, option...@@ -69,52 +68,42 @@ pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, option
69 .file = file,68 .file = file,
70 .allocator = allocator,69 .allocator = allocator,
71 },70 },
72
73 .types = try Types.init(file, offset, size),
74 .funcs = try Funcs.init(file, offset + size, size, offset + 3 * size, size),
75 .exports = try Exports.init(file, offset + 2 * size, size),
76
77 // These must be ordered as they will appear in the output file
78 .sections = [_]*Section{
79 &wasm.types.typesec.section,
80 &wasm.funcs.funcsec,
81 &wasm.exports.exportsec,
82 &wasm.funcs.codesec.section,
83 },
84 };71 };
8572
86 try file.setEndPos(offset + 4 * size);
87
88 return &wasm.base;73 return &wasm.base;
89}74}
9075
91pub fn deinit(self: *Wasm) void {76pub fn deinit(self: *Wasm) void {
92 self.types.deinit();77 for (self.funcs.items) |decl| {
93 self.funcs.deinit();78 decl.fn_link.wasm.?.functype.deinit(self.base.allocator);
79 decl.fn_link.wasm.?.code.deinit(self.base.allocator);
80 decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator);
81 }
82 self.funcs.deinit(self.base.allocator);
94}83}
9584
85// Generate code for the Decl, storing it in memory to be later written to
86// the file on flush().
96pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {87pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
97 if (decl.typed_value.most_recent.typed_value.ty.zigTypeTag() != .Fn)88 if (decl.typed_value.most_recent.typed_value.ty.zigTypeTag() != .Fn)
98 return error.TODOImplementNonFnDeclsForWasm;89 return error.TODOImplementNonFnDeclsForWasm;
9990
100 if (decl.fn_link.wasm) |fn_data| {91 if (decl.fn_link.wasm) |*fn_data| {
101 self.funcs.free(fn_data.funcidx);92 fn_data.functype.items.len = 0;
102 }93 fn_data.code.items.len = 0;
10394 fn_data.idx_refs.items.len = 0;
104 var buf = std.ArrayList(u8).init(self.base.allocator);95 } else {
105 defer buf.deinit();96 decl.fn_link.wasm = .{};
10697 try self.funcs.append(self.base.allocator, decl);
107 try codegen.genFunctype(&buf, decl);98 }
108 const typeidx = try self.types.new(buf.items);99 const fn_data = &decl.fn_link.wasm.?;
109 buf.items.len = 0;100
110101 var managed_functype = fn_data.functype.toManaged(self.base.allocator);
111 try codegen.genCode(&buf, decl);102 var managed_code = fn_data.code.toManaged(self.base.allocator);
112 const funcidx = try self.funcs.new(typeidx, buf.items);103 try codegen.genFunctype(&managed_functype, decl);
113104 try codegen.genCode(&managed_code, decl);
114 decl.fn_link.wasm = .{ .funcidx = funcidx };105 fn_data.functype = managed_functype.toUnmanaged();
115106 fn_data.code = managed_code.toUnmanaged();
116 // TODO: we should be more smart and set this only when needed
117 self.exports.dirty = true;
118}107}
119108
120pub fn updateDeclExports(109pub fn updateDeclExports(
...@@ -122,332 +111,141 @@ pub fn updateDeclExports(...@@ -122,332 +111,141 @@ pub fn updateDeclExports(
122 module: *Module,111 module: *Module,
123 decl: *const Module.Decl,112 decl: *const Module.Decl,
124 exports: []const *Module.Export,113 exports: []const *Module.Export,
125) !void {114) !void {}
126 self.exports.dirty = true;
127}
128115
129pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {116pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
130 // TODO: remove this assert when non-function Decls are implemented117 // TODO: remove this assert when non-function Decls are implemented
131 assert(decl.typed_value.most_recent.typed_value.ty.zigTypeTag() == .Fn);118 assert(decl.typed_value.most_recent.typed_value.ty.zigTypeTag() == .Fn);
132 if (decl.fn_link.wasm) |fn_data| {119 _ = self.funcs.swapRemove(self.getFuncidx(decl).?);
133 self.funcs.free(fn_data.funcidx);120 decl.fn_link.wasm.?.functype.deinit(self.base.allocator);
134 decl.fn_link.wasm = null;121 decl.fn_link.wasm.?.code.deinit(self.base.allocator);
135 }122 decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator);
123 decl.fn_link.wasm = null;
136}124}
137125
138pub fn flush(self: *Wasm, module: *Module) !void {126pub fn flush(self: *Wasm, module: *Module) !void {
139 if (self.exports.dirty) try self.exports.writeAll(module);127 const file = self.base.file.?;
140}128 const header_size = 5 + 1;
141129
142/// This struct describes the location of a named section + custom section130 // No need to rewrite the magic/version header
143/// padding in the output file. This is all the data we need to allow for131 try file.setEndPos(@sizeOf(@TypeOf(spec.magic ++ spec.version)));
144/// shifting sections around when padding runs out.132 try file.seekTo(@sizeOf(@TypeOf(spec.magic ++ spec.version)));
145const Section = struct {133
146 /// The size of a section header: 1 byte section id + 5 bytes134 // Type section
147 /// for the fixed-width ULEB128 encoded contents size.135 {
148 const header_size = 1 + 5;136 const header_offset = try reserveVecSectionHeader(file);
149 /// Offset of the section id byte from the start of the file.137 for (self.funcs.items) |decl| {
150 offset: u64,138 try file.writeAll(decl.fn_link.wasm.?.functype.items);
151 /// Size of the section, including the header and directly
152 /// following custom section used for padding if any.
153 size: u64,
154
155 /// Resize the usable part of the section, handling the following custom
156 /// section used for padding. If there is not enough padding left, shift
157 /// all following sections to make space. Takes the current and target
158 /// contents sizes of the section as arguments.
159 fn resize(self: *Section, file: fs.File, current: u32, target: u32) !void {
160 // Section header + target contents size + custom section header
161 // + custom section name + empty custom section > owned chunk of the file
162 if (header_size + target + header_size + 1 + 0 > self.size)
163 return error.TODOImplementSectionShifting;
164
165 const new_custom_start = self.offset + header_size + target;
166 const new_custom_contents_size = self.size - target - 2 * header_size;
167 assert(new_custom_contents_size >= 1);
168 // +1 for the name of the custom section, which we set to an empty string
169 var custom_header: [header_size + 1]u8 = undefined;
170 custom_header[0] = spec.custom_id;
171 leb.writeUnsignedFixed(5, custom_header[1..header_size], @intCast(u32, new_custom_contents_size));
172 custom_header[header_size] = 0;
173 try file.pwriteAll(&custom_header, new_custom_start);
174 }
175};
176
177/// This can be used to manage the contents of any section which uses a vector
178/// of contents. This interface maintains index stability while allowing for
179/// reuse of "dead" indexes.
180const VecSection = struct {
181 /// Represents a single entry in the vector (e.g. a type in the type section)
182 const Entry = struct {
183 /// Offset from the start of the section contents in bytes
184 offset: u32,
185 /// Size in bytes of the entry
186 size: u32,
187 };
188 section: Section,
189 /// Size in bytes of the contents of the section. Does not include
190 /// the "header" containing the section id and this value.
191 contents_size: u32,
192 /// List of all entries in the contents of the section.
193 entries: std.ArrayListUnmanaged(Entry) = std.ArrayListUnmanaged(Entry){},
194 /// List of indexes of unreferenced entries which may be
195 /// overwritten and reused.
196 dead_list: std.ArrayListUnmanaged(u32) = std.ArrayListUnmanaged(u32){},
197
198 /// Write the headers of the section and custom padding section
199 fn init(comptime section_id: u8, file: fs.File, offset: u64, initial_size: u64) !VecSection {
200 // section id, section size, empty vector, custom section id,
201 // custom section size, empty custom section name
202 var initial_data: [1 + 5 + 5 + 1 + 5 + 1]u8 = undefined;
203
204 assert(initial_size >= initial_data.len);
205
206 comptime var i = 0;
207 initial_data[i] = section_id;
208 i += 1;
209 leb.writeUnsignedFixed(5, initial_data[i..(i + 5)], 5);
210 i += 5;
211 leb.writeUnsignedFixed(5, initial_data[i..(i + 5)], 0);
212 i += 5;
213 initial_data[i] = spec.custom_id;
214 i += 1;
215 leb.writeUnsignedFixed(5, initial_data[i..(i + 5)], @intCast(u32, initial_size - @sizeOf(@TypeOf(initial_data))));
216 i += 5;
217 initial_data[i] = 0;
218
219 try file.pwriteAll(&initial_data, offset);
220
221 return VecSection{
222 .section = .{
223 .offset = offset,
224 .size = initial_size,
225 },
226 .contents_size = 5,
227 };
228 }
229
230 fn deinit(self: *VecSection, allocator: *Allocator) void {
231 self.entries.deinit(allocator);
232 self.dead_list.deinit(allocator);
233 }
234
235 /// Write a new entry into the file, returning the index used.
236 fn addEntry(self: *VecSection, file: fs.File, allocator: *Allocator, data: []const u8) !u32 {
237 // First look for a dead entry we can reuse
238 for (self.dead_list.items) |dead_idx, i| {
239 const dead_entry = &self.entries.items[dead_idx];
240 if (dead_entry.size == data.len) {
241 // Found a dead entry of the right length, overwrite it
242 try file.pwriteAll(data, self.section.offset + Section.header_size + dead_entry.offset);
243 _ = self.dead_list.swapRemove(i);
244 return dead_idx;
245 }
246 }139 }
247140 try writeVecSectionHeader(
248 // TODO: We can be more efficient if we special-case one or141 file,
249 // more consecutive dead entries at the end of the vector.142 header_offset,
250143 spec.types_id,
251 // We failed to find a dead entry to reuse, so write the new144 @intCast(u32, (try file.getPos()) - header_offset - header_size),
252 // entry to the end of the section.145 @intCast(u32, self.funcs.items.len),
253 try self.section.resize(file, self.contents_size, self.contents_size + @intCast(u32, data.len));146 );
254 try file.pwriteAll(data, self.section.offset + Section.header_size + self.contents_size);147 }
255 try self.entries.append(allocator, .{148
256 .offset = self.contents_size,149 // Function section
257 .size = @intCast(u32, data.len),150 {
258 });151 const header_offset = try reserveVecSectionHeader(file);
259 self.contents_size += @intCast(u32, data.len);152 const writer = file.writer();
260 // Make sure the dead list always has enough space to store all free'd153 for (self.funcs.items) |_, typeidx| try leb.writeULEB128(writer, @intCast(u32, typeidx));
261 // entries. This makes it so that delEntry() cannot fail.154 try writeVecSectionHeader(
262 // TODO: figure out a better way that doesn't waste as much memory155 file,
263 try self.dead_list.ensureCapacity(allocator, self.entries.items.len);156 header_offset,
264157 spec.funcs_id,
265 // Update the size in the section header and the item count of158 @intCast(u32, (try file.getPos()) - header_offset - header_size),
266 // the contents vector.159 @intCast(u32, self.funcs.items.len),
267 var size_and_count: [10]u8 = undefined;160 );
268 leb.writeUnsignedFixed(5, size_and_count[0..5], self.contents_size);161 }
269 leb.writeUnsignedFixed(5, size_and_count[5..], @intCast(u32, self.entries.items.len));162
270 try file.pwriteAll(&size_and_count, self.section.offset + 1);163 // Export section
271164 {
272 return @intCast(u32, self.entries.items.len - 1);165 const header_offset = try reserveVecSectionHeader(file);
273 }166 const writer = file.writer();
274167 var count: u32 = 0;
275 /// Mark the type referenced by the given index as dead.
276 fn delEntry(self: *VecSection, index: u32) void {
277 self.dead_list.appendAssumeCapacity(index);
278 }
279};
280
281const Types = struct {
282 typesec: VecSection,
283
284 fn init(file: fs.File, offset: u64, initial_size: u64) !Types {
285 return Types{ .typesec = try VecSection.init(spec.types_id, file, offset, initial_size) };
286 }
287
288 fn deinit(self: *Types) void {
289 const wasm = @fieldParentPtr(Wasm, "types", self);
290 self.typesec.deinit(wasm.base.allocator);
291 }
292
293 fn new(self: *Types, data: []const u8) !u32 {
294 const wasm = @fieldParentPtr(Wasm, "types", self);
295 return self.typesec.addEntry(wasm.base.file.?, wasm.base.allocator, data);
296 }
297
298 fn free(self: *Types, typeidx: u32) void {
299 self.typesec.delEntry(typeidx);
300 }
301};
302
303const Funcs = struct {
304 /// This section needs special handling to keep the indexes matching with
305 /// the codesec, so we cant just use a VecSection.
306 funcsec: Section,
307 /// The typeidx stored for each function, indexed by funcidx.
308 func_types: std.ArrayListUnmanaged(u32) = std.ArrayListUnmanaged(u32){},
309 codesec: VecSection,
310
311 fn init(file: fs.File, funcs_offset: u64, funcs_size: u64, code_offset: u64, code_size: u64) !Funcs {
312 return Funcs{
313 .funcsec = (try VecSection.init(spec.funcs_id, file, funcs_offset, funcs_size)).section,
314 .codesec = try VecSection.init(spec.code_id, file, code_offset, code_size),
315 };
316 }
317
318 fn deinit(self: *Funcs) void {
319 const wasm = @fieldParentPtr(Wasm, "funcs", self);
320 self.func_types.deinit(wasm.base.allocator);
321 self.codesec.deinit(wasm.base.allocator);
322 }
323
324 /// Add a new function to the binary, first finding space for and writing
325 /// the code then writing the typeidx to the corresponding index in the
326 /// funcsec. Returns the function index used.
327 fn new(self: *Funcs, typeidx: u32, code: []const u8) !u32 {
328 const wasm = @fieldParentPtr(Wasm, "funcs", self);
329 const file = wasm.base.file.?;
330 const allocator = wasm.base.allocator;
331
332 assert(self.func_types.items.len == self.codesec.entries.items.len);
333
334 // TODO: consider nop-padding the code if there is a close but not perfect fit
335 const funcidx = try self.codesec.addEntry(file, allocator, code);
336
337 if (self.func_types.items.len < self.codesec.entries.items.len) {
338 // u32 vector length + funcs_count u32s in the vector
339 const current = 5 + @intCast(u32, self.func_types.items.len) * 5;
340 try self.funcsec.resize(file, current, current + 5);
341 try self.func_types.append(allocator, typeidx);
342
343 // Update the size in the section header and the item count of
344 // the contents vector.
345 const count = @intCast(u32, self.func_types.items.len);
346 var size_and_count: [10]u8 = undefined;
347 leb.writeUnsignedFixed(5, size_and_count[0..5], 5 + count * 5);
348 leb.writeUnsignedFixed(5, size_and_count[5..], count);
349 try file.pwriteAll(&size_and_count, self.funcsec.offset + 1);
350 } else {
351 // We are overwriting a dead function and may now free the type
352 wasm.types.free(self.func_types.items[funcidx]);
353 }
354
355 assert(self.func_types.items.len == self.codesec.entries.items.len);
356
357 var typeidx_leb: [5]u8 = undefined;
358 leb.writeUnsignedFixed(5, &typeidx_leb, typeidx);
359 try file.pwriteAll(&typeidx_leb, self.funcsec.offset + Section.header_size + 5 + funcidx * 5);
360
361 return funcidx;
362 }
363
364 fn free(self: *Funcs, funcidx: u32) void {
365 self.codesec.delEntry(funcidx);
366 }
367};
368
369/// Exports are tricky. We can't leave dead entries in the binary as they
370/// would obviously be visible from the execution environment. The simplest
371/// way to work around this is to re-emit the export section whenever
372/// something changes. This also makes it easier to ensure exported function
373/// and global indexes are updated as they change.
374const Exports = struct {
375 exportsec: Section,
376 /// Size in bytes of the contents of the section. Does not include
377 /// the "header" containing the section id and this value.
378 contents_size: u32,
379 /// If this is true, then exports will be rewritten on flush()
380 dirty: bool,
381
382 fn init(file: fs.File, offset: u64, initial_size: u64) !Exports {
383 return Exports{
384 .exportsec = (try VecSection.init(spec.exports_id, file, offset, initial_size)).section,
385 .contents_size = 5,
386 .dirty = false,
387 };
388 }
389
390 fn writeAll(self: *Exports, module: *Module) !void {
391 const wasm = @fieldParentPtr(Wasm, "exports", self);
392 const file = wasm.base.file.?;
393 var buf: [5]u8 = undefined;
394
395 // First ensure the section is the right size
396 var export_count: u32 = 0;
397 var new_contents_size: u32 = 5;
398 for (module.decl_exports.entries.items) |entry| {168 for (module.decl_exports.entries.items) |entry| {
399 for (entry.value) |e| {169 for (entry.value) |exprt| {
400 export_count += 1;170 // Export name length + name
401 new_contents_size += calcSize(e);171 try leb.writeULEB128(writer, @intCast(u32, exprt.options.name.len));
172 try writer.writeAll(exprt.options.name);
173
174 switch (exprt.exported_decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) {
175 .Fn => {
176 // Type of the export
177 try writer.writeByte(0x00);
178 // Exported function index
179 try leb.writeULEB128(writer, self.getFuncidx(exprt.exported_decl).?);
180 },
181 else => return error.TODOImplementNonFnDeclsForWasm,
182 }
183
184 count += 1;
402 }185 }
403 }186 }
404 if (new_contents_size != self.contents_size) {187 try writeVecSectionHeader(
405 try self.exportsec.resize(file, self.contents_size, new_contents_size);188 file,
406 leb.writeUnsignedFixed(5, &buf, new_contents_size);189 header_offset,
407 try file.pwriteAll(&buf, self.exportsec.offset + 1);190 spec.exports_id,
408 }191 @intCast(u32, (try file.getPos()) - header_offset - header_size),
409192 count,
410 try file.seekTo(self.exportsec.offset + Section.header_size);193 );
194 }
195
196 // Code section
197 {
198 const header_offset = try reserveVecSectionHeader(file);
411 const writer = file.writer();199 const writer = file.writer();
200 for (self.funcs.items) |decl| {
201 const fn_data = &decl.fn_link.wasm.?;
202
203 // Write the already generated code to the file, inserting
204 // function indexes where required.
205 var current: u32 = 0;
206 for (fn_data.idx_refs.items) |idx_ref| {
207 try writer.writeAll(fn_data.code.items[current..idx_ref.offset]);
208 current = idx_ref.offset;
209 // Use a fixed width here to make calculating the code size
210 // in codegen.wasm.genCode() simpler.
211 var buf: [5]u8 = undefined;
212 leb.writeUnsignedFixed(5, &buf, self.getFuncidx(idx_ref.decl).?);
213 try writer.writeAll(&buf);
214 }
412215
413 // Length of the exports vec216 try writer.writeAll(fn_data.code.items[current..]);
414 leb.writeUnsignedFixed(5, &buf, export_count);217 }
415 try writer.writeAll(&buf);218 try writeVecSectionHeader(
416219 file,
417 for (module.decl_exports.entries.items) |entry|220 header_offset,
418 for (entry.value) |e| try writeExport(writer, e);221 spec.code_id,
419222 @intCast(u32, (try file.getPos()) - header_offset - header_size),
420 self.dirty = false;223 @intCast(u32, self.funcs.items.len),
224 );
421 }225 }
226}
422227
423 /// Return the total number of bytes an export will take.228/// Get the current index of a given Decl in the function list
424 /// TODO: fixed-width LEB128 is currently used for simplicity, but should229/// TODO: we could maintain a hash map to potentially make this
425 /// be replaced with proper variable-length LEB128 as it is inefficient.230fn getFuncidx(self: Wasm, decl: *Module.Decl) ?u32 {
426 fn calcSize(e: *Module.Export) u32 {231 return for (self.funcs.items) |func, idx| {
427 // LEB128 name length + name bytes + export type + LEB128 index232 if (func == decl) break @intCast(u32, idx);
428 return 5 + @intCast(u32, e.options.name.len) + 1 + 5;233 } else null;
429 }234}
430235
431 /// Write the data for a single export to the given file at a given offset.236fn reserveVecSectionHeader(file: fs.File) !u64 {
432 /// TODO: fixed-width LEB128 is currently used for simplicity, but should237 // section id + fixed leb contents size + fixed leb vector length
433 /// be replaced with proper variable-length LEB128 as it is inefficient.238 const header_size = 1 + 5 + 5;
434 fn writeExport(writer: anytype, e: *Module.Export) !void {239 // TODO: this should be a single lseek(2) call, but fs.File does not
435 var buf: [5]u8 = undefined;240 // currently provide a way to do this.
436241 try file.seekBy(header_size);
437 // Export name length + name242 return (try file.getPos()) - header_size;
438 leb.writeUnsignedFixed(5, &buf, @intCast(u32, e.options.name.len));243}
439 try writer.writeAll(&buf);244
440 try writer.writeAll(e.options.name);245fn writeVecSectionHeader(file: fs.File, offset: u64, section: u8, size: u32, items: u32) !void {
441246 var buf: [1 + 5 + 5]u8 = undefined;
442 switch (e.exported_decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) {247 buf[0] = section;
443 .Fn => {248 leb.writeUnsignedFixed(5, buf[1..6], size);
444 // Type of the export249 leb.writeUnsignedFixed(5, buf[6..], items);
445 try writer.writeByte(0x00);250 try file.pwriteAll(&buf, offset);
446 // Exported function index251}
447 leb.writeUnsignedFixed(5, &buf, e.exported_decl.fn_link.wasm.?.funcidx);
448 try writer.writeAll(&buf);
449 },
450 else => return error.TODOImplementNonFnDeclsForWasm,
451 }
452 }
453};
test/stage2/compare_output.zig+26-1
...@@ -546,28 +546,53 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -546,28 +546,53 @@ pub fn addCases(ctx: *TestContext) !void {
546 }546 }
547547
548 {548 {
549 var case = ctx.exe("wasm returns", wasi);549 var case = ctx.exe("wasm function calls", wasi);
550550
551 case.addCompareOutput(551 case.addCompareOutput(
552 \\export fn _start() u32 {552 \\export fn _start() u32 {
553 \\ foo();
554 \\ bar();
553 \\ return 42;555 \\ return 42;
554 \\}556 \\}
557 \\fn foo() void {
558 \\ bar();
559 \\ bar();
560 \\}
561 \\fn bar() void {}
555 ,562 ,
556 "42\n",563 "42\n",
557 );564 );
558565
559 case.addCompareOutput(566 case.addCompareOutput(
560 \\export fn _start() i64 {567 \\export fn _start() i64 {
568 \\ bar();
569 \\ foo();
570 \\ foo();
571 \\ bar();
572 \\ foo();
573 \\ bar();
561 \\ return 42;574 \\ return 42;
562 \\}575 \\}
576 \\fn foo() void {
577 \\ bar();
578 \\}
579 \\fn bar() void {}
563 ,580 ,
564 "42\n",581 "42\n",
565 );582 );
566583
567 case.addCompareOutput(584 case.addCompareOutput(
568 \\export fn _start() f32 {585 \\export fn _start() f32 {
586 \\ bar();
587 \\ foo();
569 \\ return 42.0;588 \\ return 42.0;
570 \\}589 \\}
590 \\fn foo() void {
591 \\ bar();
592 \\ bar();
593 \\ bar();
594 \\}
595 \\fn bar() void {}
571 ,596 ,
572 // This is what you get when you take the bits of the IEE-754597 // This is what you get when you take the bits of the IEE-754
573 // representation of 42.0 and reinterpret them as an unsigned598 // representation of 42.0 and reinterpret them as an unsigned