authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-08-18 00:28:05-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-08-18 00:28:05-04:00
log3cc1f8b62477c37c938863cba0ec0409e4c9c0be
treeb39ff5060590ce285f27a5e7fda036808b29523d
parentce8b9c0c5cdbe4161952e6f2aa875f722949d4cb
parent9f44284ad5ed5ae94b7a4bc1f5972c60c1739bb6
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #6056 from ifreund/wasm-backend

stage2: add a wasm backend

7 files changed, 647 insertions(+), 18 deletions(-)

src-self-hosted/Module.zig+4-2
......@@ -974,7 +974,7 @@ pub fn update(self: *Module) !void {
974974 }
975975
976976 // This is needed before reading the error flags.
977 try self.bin_file.flush();
977 try self.bin_file.flush(self);
978978
979979 self.link_error_flags = self.bin_file.errorFlags();
980980
......@@ -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+119
......@@ -0,0 +1,119 @@
1const std = @import("std");
2const Allocator = std.mem.Allocator;
3const ArrayList = std.ArrayList;
4const assert = std.debug.assert;
5const leb = std.debug.leb;
6const mem = std.mem;
7
8const Decl = @import("../Module.zig").Decl;
9const Inst = @import("../ir.zig").Inst;
10const Type = @import("../type.zig").Type;
11const Value = @import("../value.zig").Value;
12
13fn genValtype(ty: Type) u8 {
14 return switch (ty.tag()) {
15 .u32, .i32 => 0x7F,
16 .u64, .i64 => 0x7E,
17 .f32 => 0x7D,
18 .f64 => 0x7C,
19 else => @panic("TODO: Implement more types for wasm."),
20 };
21}
22
23pub fn genFunctype(buf: *ArrayList(u8), decl: *Decl) !void {
24 const ty = decl.typed_value.most_recent.typed_value.ty;
25 const writer = buf.writer();
26
27 // functype magic
28 try writer.writeByte(0x60);
29
30 // param types
31 try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen()));
32 if (ty.fnParamLen() != 0) {
33 const params = try buf.allocator.alloc(Type, ty.fnParamLen());
34 defer buf.allocator.free(params);
35 ty.fnParamTypes(params);
36 for (params) |param_type| try writer.writeByte(genValtype(param_type));
37 }
38
39 // return type
40 const return_type = ty.fnReturnType();
41 switch (return_type.tag()) {
42 .void, .noreturn => try leb.writeULEB128(writer, @as(u32, 0)),
43 else => {
44 try leb.writeULEB128(writer, @as(u32, 1));
45 try writer.writeByte(genValtype(return_type));
46 },
47 }
48}
49
50pub fn genCode(buf: *ArrayList(u8), decl: *Decl) !void {
51 assert(buf.items.len == 0);
52 const writer = buf.writer();
53
54 // Reserve space to write the size after generating the code
55 try buf.resize(5);
56
57 // Write the size of the locals vec
58 // TODO: implement locals
59 try leb.writeULEB128(writer, @as(u32, 0));
60
61 // Write instructions
62 // TODO: check for and handle death of instructions
63 const tv = decl.typed_value.most_recent.typed_value;
64 const mod_fn = tv.val.cast(Value.Payload.Function).?.func;
65 for (mod_fn.analysis.success.instructions) |inst| try genInst(writer, inst);
66
67 // Write 'end' opcode
68 try writer.writeByte(0x0B);
69
70 // Fill in the size of the generated code to the reserved space at the
71 // beginning of the buffer.
72 leb.writeUnsignedFixed(5, buf.items[0..5], @intCast(u32, buf.items.len - 5));
73}
74
75fn genInst(writer: ArrayList(u8).Writer, inst: *Inst) !void {
76 return switch (inst.tag) {
77 .dbg_stmt => {},
78 .ret => genRet(writer, inst.castTag(.ret).?),
79 else => error.TODOImplementMoreWasmCodegen,
80 };
81}
82
83fn genRet(writer: ArrayList(u8).Writer, inst: *Inst.UnOp) !void {
84 switch (inst.operand.tag) {
85 .constant => {
86 const constant = inst.operand.castTag(.constant).?;
87 switch (inst.operand.ty.tag()) {
88 .u32 => {
89 try writer.writeByte(0x41); // i32.const
90 try leb.writeILEB128(writer, constant.val.toUnsignedInt());
91 },
92 .i32 => {
93 try writer.writeByte(0x41); // i32.const
94 try leb.writeILEB128(writer, constant.val.toSignedInt());
95 },
96 .u64 => {
97 try writer.writeByte(0x42); // i64.const
98 try leb.writeILEB128(writer, constant.val.toUnsignedInt());
99 },
100 .i64 => {
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 },
117 else => return error.TODOImplementMoreWasmCodegen,
118 }
119}
src-self-hosted/link.zig+33-15
......@@ -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,15 +95,18 @@ pub const File = struct {
9395 .mode = determineMode(base.options),
9496 });
9597 },
96 .c => {},
98 .c, .wasm => {},
9799 }
98100 }
99101
100102 pub fn makeExecutable(base: *File) !void {
101 std.debug.assert(base.tag != .c);
102 if (base.file) |f| {
103 f.close();
104 base.file = null;
103 switch (base.tag) {
104 .c => unreachable,
105 .wasm => {},
106 else => if (base.file) |f| {
107 f.close();
108 base.file = null;
109 },
105110 }
106111 }
107112
......@@ -110,6 +115,7 @@ pub const File = struct {
110115 .elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl),
111116 .macho => return @fieldParentPtr(MachO, "base", base).updateDecl(module, decl),
112117 .c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl),
118 .wasm => return @fieldParentPtr(Wasm, "base", base).updateDecl(module, decl),
113119 }
114120 }
115121
......@@ -117,7 +123,7 @@ pub const File = struct {
117123 switch (base.tag) {
118124 .elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl),
119125 .macho => return @fieldParentPtr(MachO, "base", base).updateDeclLineNumber(module, decl),
120 .c => {},
126 .c, .wasm => {},
121127 }
122128 }
123129
......@@ -125,7 +131,7 @@ pub const File = struct {
125131 switch (base.tag) {
126132 .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),
127133 .macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl),
128 .c => {},
134 .c, .wasm => {},
129135 }
130136 }
131137
......@@ -135,6 +141,7 @@ pub const File = struct {
135141 .elf => @fieldParentPtr(Elf, "base", base).deinit(),
136142 .macho => @fieldParentPtr(MachO, "base", base).deinit(),
137143 .c => @fieldParentPtr(C, "base", base).deinit(),
144 .wasm => @fieldParentPtr(Wasm, "base", base).deinit(),
138145 }
139146 }
140147
......@@ -155,18 +162,23 @@ pub const File = struct {
155162 parent.deinit();
156163 base.allocator.destroy(parent);
157164 },
165 .wasm => {
166 const parent = @fieldParentPtr(Wasm, "base", base);
167 parent.deinit();
168 base.allocator.destroy(parent);
169 },
158170 }
159171 }
160172
161 /// Commit pending changes and write headers.
162 pub fn flush(base: *File) !void {
173 pub fn flush(base: *File, module: *Module) !void {
163174 const tracy = trace(@src());
164175 defer tracy.end();
165176
166177 try switch (base.tag) {
167 .elf => @fieldParentPtr(Elf, "base", base).flush(),
168 .macho => @fieldParentPtr(MachO, "base", base).flush(),
169 .c => @fieldParentPtr(C, "base", base).flush(),
178 .elf => @fieldParentPtr(Elf, "base", base).flush(module),
179 .macho => @fieldParentPtr(MachO, "base", base).flush(module),
180 .c => @fieldParentPtr(C, "base", base).flush(module),
181 .wasm => @fieldParentPtr(Wasm, "base", base).flush(module),
170182 };
171183 }
172184
......@@ -175,6 +187,7 @@ pub const File = struct {
175187 .elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl),
176188 .macho => @fieldParentPtr(MachO, "base", base).freeDecl(decl),
177189 .c => unreachable,
190 .wasm => @fieldParentPtr(Wasm, "base", base).freeDecl(decl),
178191 }
179192 }
180193
......@@ -183,6 +196,7 @@ pub const File = struct {
183196 .elf => @fieldParentPtr(Elf, "base", base).error_flags,
184197 .macho => @fieldParentPtr(MachO, "base", base).error_flags,
185198 .c => return .{ .no_entry_point_found = false },
199 .wasm => return ErrorFlags{},
186200 };
187201 }
188202
......@@ -197,6 +211,7 @@ pub const File = struct {
197211 .elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports),
198212 .macho => return @fieldParentPtr(MachO, "base", base).updateDeclExports(module, decl, exports),
199213 .c => return {},
214 .wasm => return @fieldParentPtr(Wasm, "base", base).updateDeclExports(module, decl, exports),
200215 }
201216 }
202217
......@@ -204,6 +219,7 @@ pub const File = struct {
204219 elf,
205220 macho,
206221 c,
222 wasm,
207223 };
208224
209225 pub const ErrorFlags = struct {
......@@ -270,7 +286,7 @@ pub const File = struct {
270286 };
271287 }
272288
273 pub fn flush(self: *File.C) !void {
289 pub fn flush(self: *File.C, module: *Module) !void {
274290 const writer = self.base.file.?.writer();
275291 try writer.writeAll(@embedFile("cbe.h"));
276292 var includes = false;
......@@ -1023,7 +1039,8 @@ pub const File = struct {
10231039 pub const abbrev_pad1 = 5;
10241040 pub const abbrev_parameter = 6;
10251041
1026 pub fn flush(self: *Elf) !void {
1042 /// Commit pending changes and write headers.
1043 pub fn flush(self: *Elf, module: *Module) !void {
10271044 const target_endian = self.base.options.target.cpu.arch.endian();
10281045 const foreign_endian = target_endian != std.Target.current.cpu.arch.endian();
10291046 const ptr_width_bytes: u8 = self.ptrWidthBytes();
......@@ -2832,6 +2849,7 @@ pub const File = struct {
28322849 };
28332850
28342851 pub const MachO = @import("link/MachO.zig");
2852 const Wasm = @import("link/Wasm.zig");
28352853};
28362854
28372855/// Saturating multiplication
src-self-hosted/link/MachO.zig+1-1
......@@ -73,7 +73,7 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Mach
7373 }
7474}
7575
76pub fn flush(self: *MachO) !void {}
76pub fn flush(self: *MachO, module: *Module) !void {}
7777
7878pub fn deinit(self: *MachO) void {}
7979
src-self-hosted/link/Wasm.zig created+453
......@@ -0,0 +1,453 @@
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};
37
38base: link.File,
39
40types: Types,
41funcs: Funcs,
42exports: Exports,
43
44/// Array over the section structs used in the various sections above to
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,
48
49pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: link.Options) !*link.File {
50 assert(options.object_format == .wasm);
51
52 // TODO: read the file and keep vaild parts instead of truncating
53 const file = try dir.createFile(sub_path, .{ .truncate = true, .read = true });
54 errdefer file.close();
55
56 const wasm = try allocator.create(Wasm);
57 errdefer allocator.destroy(wasm);
58
59 try file.writeAll(&(spec.magic ++ spec.version));
60
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.* = .{
66 .base = .{
67 .tag = .wasm,
68 .options = options,
69 .file = file,
70 .allocator = allocator,
71 },
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 };
85
86 try file.setEndPos(offset + 4 * size);
87
88 return &wasm.base;
89}
90
91pub fn deinit(self: *Wasm) void {
92 self.types.deinit();
93 self.funcs.deinit();
94}
95
96pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
97 if (decl.typed_value.most_recent.typed_value.ty.zigTypeTag() != .Fn)
98 return error.TODOImplementNonFnDeclsForWasm;
99
100 if (decl.fn_link.wasm) |fn_data| {
101 self.funcs.free(fn_data.funcidx);
102 }
103
104 var buf = std.ArrayList(u8).init(self.base.allocator);
105 defer buf.deinit();
106
107 try codegen.genFunctype(&buf, decl);
108 const typeidx = try self.types.new(buf.items);
109 buf.items.len = 0;
110
111 try codegen.genCode(&buf, decl);
112 const funcidx = try self.funcs.new(typeidx, buf.items);
113
114 decl.fn_link.wasm = .{ .funcidx = funcidx };
115
116 // TODO: we should be more smart and set this only when needed
117 self.exports.dirty = true;
118}
119
120pub fn updateDeclExports(
121 self: *Wasm,
122 module: *Module,
123 decl: *const Module.Decl,
124 exports: []const *Module.Export,
125) !void {
126 self.exports.dirty = true;
127}
128
129pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
130 // TODO: remove this assert when non-function Decls are implemented
131 assert(decl.typed_value.most_recent.typed_value.ty.zigTypeTag() == .Fn);
132 if (decl.fn_link.wasm) |fn_data| {
133 self.funcs.free(fn_data.funcidx);
134 decl.fn_link.wasm = null;
135 }
136}
137
138pub fn flush(self: *Wasm, module: *Module) !void {
139 if (self.exports.dirty) try self.exports.writeAll(module);
140}
141
142/// This struct describes the location of a named section + custom section
143/// padding in the output file. This is all the data we need to allow for
144/// shifting sections around when padding runs out.
145const Section = struct {
146 /// The size of a section header: 1 byte section id + 5 bytes
147 /// for the fixed-width ULEB128 encoded contents size.
148 const header_size = 1 + 5;
149 /// Offset of the section id byte from the start of the file.
150 offset: u64,
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 }
247
248 // TODO: We can be more efficient if we special-case one or
249 // more consecutive dead entries at the end of the vector.
250
251 // We failed to find a dead entry to reuse, so write the new
252 // entry to the end of the section.
253 try self.section.resize(file, self.contents_size, self.contents_size + @intCast(u32, data.len));
254 try file.pwriteAll(data, self.section.offset + Section.header_size + self.contents_size);
255 try self.entries.append(allocator, .{
256 .offset = self.contents_size,
257 .size = @intCast(u32, data.len),
258 });
259 self.contents_size += @intCast(u32, data.len);
260 // Make sure the dead list always has enough space to store all free'd
261 // entries. This makes it so that delEntry() cannot fail.
262 // TODO: figure out a better way that doesn't waste as much memory
263 try self.dead_list.ensureCapacity(allocator, self.entries.items.len);
264
265 // Update the size in the section header and the item count of
266 // the contents vector.
267 var size_and_count: [10]u8 = undefined;
268 leb.writeUnsignedFixed(5, size_and_count[0..5], self.contents_size);
269 leb.writeUnsignedFixed(5, size_and_count[5..], @intCast(u32, self.entries.items.len));
270 try file.pwriteAll(&size_and_count, self.section.offset + 1);
271
272 return @intCast(u32, self.entries.items.len - 1);
273 }
274
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| {
399 for (entry.value) |e| {
400 export_count += 1;
401 new_contents_size += calcSize(e);
402 }
403 }
404 if (new_contents_size != self.contents_size) {
405 try self.exportsec.resize(file, self.contents_size, new_contents_size);
406 leb.writeUnsignedFixed(5, &buf, new_contents_size);
407 try file.pwriteAll(&buf, self.exportsec.offset + 1);
408 }
409
410 try file.seekTo(self.exportsec.offset + Section.header_size);
411 const writer = file.writer();
412
413 // Length of the exports vec
414 leb.writeUnsignedFixed(5, &buf, export_count);
415 try writer.writeAll(&buf);
416
417 for (module.decl_exports.entries.items) |entry|
418 for (entry.value) |e| try writeExport(writer, e);
419
420 self.dirty = false;
421 }
422
423 /// Return the total number of bytes an export will take.
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 calcSize(e: *Module.Export) u32 {
427 // LEB128 name length + name bytes + export type + LEB128 index
428 return 5 + @intCast(u32, e.options.name.len) + 1 + 5;
429 }
430
431 /// Write the data for a single export to the given file at a given offset.
432 /// TODO: fixed-width LEB128 is currently used for simplicity, but should
433 /// be replaced with proper variable-length LEB128 as it is inefficient.
434 fn writeExport(writer: anytype, e: *Module.Export) !void {
435 var buf: [5]u8 = undefined;
436
437 // Export name length + name
438 leb.writeUnsignedFixed(5, &buf, @intCast(u32, e.options.name.len));
439 try writer.writeAll(&buf);
440 try writer.writeAll(e.options.name);
441
442 switch (e.exported_decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) {
443 .Fn => {
444 // Type of the export
445 try writer.writeByte(0x00);
446 // Exported function index
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};
src-self-hosted/main.zig+1
......@@ -152,6 +152,7 @@ const usage_build_generic =
152152 \\ -ofmt=[mode] Override target object format
153153 \\ elf Executable and Linking Format
154154 \\ c Compile to C source code
155 \\ wasm WebAssembly
155156 \\ coff (planned) Common Object File Format (Windows)
156157 \\ pe (planned) Portable Executable (Windows)
157158 \\ macho (planned) macOS relocatables
test/stage2/compare_output.zig+36
......@@ -12,6 +12,11 @@ const linux_riscv64 = std.zig.CrossTarget{
1212 .os_tag = .linux,
1313};
1414
15const wasi = std.zig.CrossTarget{
16 .cpu_arch = .wasm32,
17 .os_tag = .wasi,
18};
19
1520pub fn addCases(ctx: *TestContext) !void {
1621 {
1722 var case = ctx.exe("hello world with updates", linux_x64);
......@@ -539,4 +544,35 @@ pub fn addCases(ctx: *TestContext) !void {
539544 "",
540545 );
541546 }
547
548 {
549 var case = ctx.exe("wasm returns", wasi);
550
551 case.addCompareOutput(
552 \\export fn _start() u32 {
553 \\ return 42;
554 \\}
555 ,
556 "42\n",
557 );
558
559 case.addCompareOutput(
560 \\export fn _start() i64 {
561 \\ return 42;
562 \\}
563 ,
564 "42\n",
565 );
566
567 case.addCompareOutput(
568 \\export fn _start() f32 {
569 \\ return 42.0;
570 \\}
571 ,
572 // This is what you get when you take the bits of the IEE-754
573 // representation of 42.0 and reinterpret them as an unsigned
574 // integer. Guess that's a bug in wasmtime.
575 "1109917696\n",
576 );
577 }
542578}