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 {...@@ -974,7 +974,7 @@ pub fn update(self: *Module) !void {
974 }974 }
975975
976 // This is needed before reading the error flags.976 // This is needed before reading the error flags.
977 try self.bin_file.flush();977 try self.bin_file.flush(self);
978978
979 self.link_error_flags = self.bin_file.errorFlags();979 self.link_error_flags = self.bin_file.errorFlags();
980980
...@@ -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+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 {...@@ -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,15 +95,18 @@ pub const File = struct {...@@ -93,15 +95,18 @@ 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
100 pub fn makeExecutable(base: *File) !void {102 pub fn makeExecutable(base: *File) !void {
101 std.debug.assert(base.tag != .c);103 switch (base.tag) {
102 if (base.file) |f| {104 .c => unreachable,
103 f.close();105 .wasm => {},
104 base.file = null;106 else => if (base.file) |f| {
107 f.close();
108 base.file = null;
109 },
105 }110 }
106 }111 }
107112
...@@ -110,6 +115,7 @@ pub const File = struct {...@@ -110,6 +115,7 @@ pub const File = struct {
110 .elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl),115 .elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl),
111 .macho => return @fieldParentPtr(MachO, "base", base).updateDecl(module, decl),116 .macho => return @fieldParentPtr(MachO, "base", base).updateDecl(module, decl),
112 .c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl),117 .c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl),
118 .wasm => return @fieldParentPtr(Wasm, "base", base).updateDecl(module, decl),
113 }119 }
114 }120 }
115121
...@@ -117,7 +123,7 @@ pub const File = struct {...@@ -117,7 +123,7 @@ pub const File = struct {
117 switch (base.tag) {123 switch (base.tag) {
118 .elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl),124 .elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl),
119 .macho => return @fieldParentPtr(MachO, "base", base).updateDeclLineNumber(module, decl),125 .macho => return @fieldParentPtr(MachO, "base", base).updateDeclLineNumber(module, decl),
120 .c => {},126 .c, .wasm => {},
121 }127 }
122 }128 }
123129
...@@ -125,7 +131,7 @@ pub const File = struct {...@@ -125,7 +131,7 @@ pub const File = struct {
125 switch (base.tag) {131 switch (base.tag) {
126 .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),132 .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),
127 .macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl),133 .macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl),
128 .c => {},134 .c, .wasm => {},
129 }135 }
130 }136 }
131137
...@@ -135,6 +141,7 @@ pub const File = struct {...@@ -135,6 +141,7 @@ pub const File = struct {
135 .elf => @fieldParentPtr(Elf, "base", base).deinit(),141 .elf => @fieldParentPtr(Elf, "base", base).deinit(),
136 .macho => @fieldParentPtr(MachO, "base", base).deinit(),142 .macho => @fieldParentPtr(MachO, "base", base).deinit(),
137 .c => @fieldParentPtr(C, "base", base).deinit(),143 .c => @fieldParentPtr(C, "base", base).deinit(),
144 .wasm => @fieldParentPtr(Wasm, "base", base).deinit(),
138 }145 }
139 }146 }
140147
...@@ -155,18 +162,23 @@ pub const File = struct {...@@ -155,18 +162,23 @@ pub const File = struct {
155 parent.deinit();162 parent.deinit();
156 base.allocator.destroy(parent);163 base.allocator.destroy(parent);
157 },164 },
165 .wasm => {
166 const parent = @fieldParentPtr(Wasm, "base", base);
167 parent.deinit();
168 base.allocator.destroy(parent);
169 },
158 }170 }
159 }171 }
160172
161 /// Commit pending changes and write headers.173 pub fn flush(base: *File, module: *Module) !void {
162 pub fn flush(base: *File) !void {
163 const tracy = trace(@src());174 const tracy = trace(@src());
164 defer tracy.end();175 defer tracy.end();
165176
166 try switch (base.tag) {177 try switch (base.tag) {
167 .elf => @fieldParentPtr(Elf, "base", base).flush(),178 .elf => @fieldParentPtr(Elf, "base", base).flush(module),
168 .macho => @fieldParentPtr(MachO, "base", base).flush(),179 .macho => @fieldParentPtr(MachO, "base", base).flush(module),
169 .c => @fieldParentPtr(C, "base", base).flush(),180 .c => @fieldParentPtr(C, "base", base).flush(module),
181 .wasm => @fieldParentPtr(Wasm, "base", base).flush(module),
170 };182 };
171 }183 }
172184
...@@ -175,6 +187,7 @@ pub const File = struct {...@@ -175,6 +187,7 @@ pub const File = struct {
175 .elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl),187 .elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl),
176 .macho => @fieldParentPtr(MachO, "base", base).freeDecl(decl),188 .macho => @fieldParentPtr(MachO, "base", base).freeDecl(decl),
177 .c => unreachable,189 .c => unreachable,
190 .wasm => @fieldParentPtr(Wasm, "base", base).freeDecl(decl),
178 }191 }
179 }192 }
180193
...@@ -183,6 +196,7 @@ pub const File = struct {...@@ -183,6 +196,7 @@ pub const File = struct {
183 .elf => @fieldParentPtr(Elf, "base", base).error_flags,196 .elf => @fieldParentPtr(Elf, "base", base).error_flags,
184 .macho => @fieldParentPtr(MachO, "base", base).error_flags,197 .macho => @fieldParentPtr(MachO, "base", base).error_flags,
185 .c => return .{ .no_entry_point_found = false },198 .c => return .{ .no_entry_point_found = false },
199 .wasm => return ErrorFlags{},
186 };200 };
187 }201 }
188202
...@@ -197,6 +211,7 @@ pub const File = struct {...@@ -197,6 +211,7 @@ pub const File = struct {
197 .elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports),211 .elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports),
198 .macho => return @fieldParentPtr(MachO, "base", base).updateDeclExports(module, decl, exports),212 .macho => return @fieldParentPtr(MachO, "base", base).updateDeclExports(module, decl, exports),
199 .c => return {},213 .c => return {},
214 .wasm => return @fieldParentPtr(Wasm, "base", base).updateDeclExports(module, decl, exports),
200 }215 }
201 }216 }
202217
...@@ -204,6 +219,7 @@ pub const File = struct {...@@ -204,6 +219,7 @@ pub const File = struct {
204 elf,219 elf,
205 macho,220 macho,
206 c,221 c,
222 wasm,
207 };223 };
208224
209 pub const ErrorFlags = struct {225 pub const ErrorFlags = struct {
...@@ -270,7 +286,7 @@ pub const File = struct {...@@ -270,7 +286,7 @@ pub const File = struct {
270 };286 };
271 }287 }
272288
273 pub fn flush(self: *File.C) !void {289 pub fn flush(self: *File.C, module: *Module) !void {
274 const writer = self.base.file.?.writer();290 const writer = self.base.file.?.writer();
275 try writer.writeAll(@embedFile("cbe.h"));291 try writer.writeAll(@embedFile("cbe.h"));
276 var includes = false;292 var includes = false;
...@@ -1023,7 +1039,8 @@ pub const File = struct {...@@ -1023,7 +1039,8 @@ pub const File = struct {
1023 pub const abbrev_pad1 = 5;1039 pub const abbrev_pad1 = 5;
1024 pub const abbrev_parameter = 6;1040 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 {
1027 const target_endian = self.base.options.target.cpu.arch.endian();1044 const target_endian = self.base.options.target.cpu.arch.endian();
1028 const foreign_endian = target_endian != std.Target.current.cpu.arch.endian();1045 const foreign_endian = target_endian != std.Target.current.cpu.arch.endian();
1029 const ptr_width_bytes: u8 = self.ptrWidthBytes();1046 const ptr_width_bytes: u8 = self.ptrWidthBytes();
...@@ -2832,6 +2849,7 @@ pub const File = struct {...@@ -2832,6 +2849,7 @@ pub const File = struct {
2832 };2849 };
28332850
2834 pub const MachO = @import("link/MachO.zig");2851 pub const MachO = @import("link/MachO.zig");
2852 const Wasm = @import("link/Wasm.zig");
2835};2853};
28362854
2837/// Saturating multiplication2855/// 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...@@ -73,7 +73,7 @@ fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Mach
73 }73 }
74}74}
7575
76pub fn flush(self: *MachO) !void {}76pub fn flush(self: *MachO, module: *Module) !void {}
7777
78pub fn deinit(self: *MachO) void {}78pub 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 =...@@ -152,6 +152,7 @@ const usage_build_generic =
152 \\ -ofmt=[mode] Override target object format152 \\ -ofmt=[mode] Override target object format
153 \\ elf Executable and Linking Format153 \\ elf Executable and Linking Format
154 \\ c Compile to C source code154 \\ c Compile to C source code
155 \\ wasm WebAssembly
155 \\ coff (planned) Common Object File Format (Windows)156 \\ coff (planned) Common Object File Format (Windows)
156 \\ pe (planned) Portable Executable (Windows)157 \\ pe (planned) Portable Executable (Windows)
157 \\ macho (planned) macOS relocatables158 \\ macho (planned) macOS relocatables
test/stage2/compare_output.zig+36
...@@ -12,6 +12,11 @@ const linux_riscv64 = std.zig.CrossTarget{...@@ -12,6 +12,11 @@ const linux_riscv64 = std.zig.CrossTarget{
12 .os_tag = .linux,12 .os_tag = .linux,
13};13};
1414
15const wasi = std.zig.CrossTarget{
16 .cpu_arch = .wasm32,
17 .os_tag = .wasi,
18};
19
15pub fn addCases(ctx: *TestContext) !void {20pub fn addCases(ctx: *TestContext) !void {
16 {21 {
17 var case = ctx.exe("hello world with updates", linux_x64);22 var case = ctx.exe("hello world with updates", linux_x64);
...@@ -539,4 +544,35 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -539,4 +544,35 @@ pub fn addCases(ctx: *TestContext) !void {
539 "",544 "",
540 );545 );
541 }546 }
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 }
542}578}