authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-11-24 19:09:37+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2021-11-27 15:02:01+01:00
logf56ae69edd8c96a5f6525f20bf0a22704a826f00
tree84778ce7a9eba390227e043e99fb6663e940f946
parent17f057c5568bda6b011a213c95aa9538f6fb6a78
signature Commit is signed but in an unrecognized format.

wasm-linker: Upstream zwld into stage2

- Converts previous `DeclBlock` into `Atom`'s to also make them compatible when the rest of zlwd gets upstreamed and we can link with other object files. - Resolves function signatures and removes any duplicates, saving us a lot of potential bytes for larger projects. - We now create symbols for each decl of the respective type - We can now (but not implemented yet) perform proper relocations. - Having symbols and segment_info allows us to create an object file for wasm.

7 files changed, 1085 insertions(+), 339 deletions(-)

lib/std/wasm.zig+139-3
...@@ -1,4 +1,8 @@...@@ -1,4 +1,8 @@
1const testing = @import("std.zig").testing;1///! Contains all constants and types representing the wasm
2///! binary format, as specified by:
3///! https://webassembly.github.io/spec/core/
4const std = @import("std.zig");
5const testing = std.testing;
26
3// TODO: Add support for multi-byte ops (e.g. table operations)7// TODO: Add support for multi-byte ops (e.g. table operations)
48
...@@ -222,6 +226,18 @@ pub fn valtype(value: Valtype) u8 {...@@ -222,6 +226,18 @@ pub fn valtype(value: Valtype) u8 {
222 return @enumToInt(value);226 return @enumToInt(value);
223}227}
224228
229/// Reference types, where the funcref references to a function regardless of its type
230/// and ref references an object from the embedder.
231pub const RefType = enum(u8) {
232 funcref = 0x70,
233 externref = 0x6F,
234};
235
236/// Returns the integer value of a `Reftype`
237pub fn reftype(value: RefType) u8 {
238 return @enumToInt(value);
239}
240
225test "Wasm - valtypes" {241test "Wasm - valtypes" {
226 const _i32 = valtype(.i32);242 const _i32 = valtype(.i32);
227 const _i64 = valtype(.i64);243 const _i64 = valtype(.i64);
...@@ -234,6 +250,124 @@ test "Wasm - valtypes" {...@@ -234,6 +250,124 @@ test "Wasm - valtypes" {
234 try testing.expectEqual(@as(u8, 0x7C), _f64);250 try testing.expectEqual(@as(u8, 0x7C), _f64);
235}251}
236252
253/// Limits classify the size range of resizeable storage associated with memory types and table types.
254pub const Limits = struct {
255 min: u32,
256 max: ?u32,
257};
258
259/// Initialization expressions are used to set the initial value on an object
260/// when a wasm module is being loaded.
261pub const InitExpression = union(enum) {
262 i32_const: i32,
263 i64_const: i64,
264 f32_const: f32,
265 f64_const: f64,
266 global_get: u32,
267};
268
269///
270pub const Func = struct {
271 type_index: u32,
272};
273
274/// Tables are used to hold pointers to opaque objects.
275/// This can either by any function, or an object from the host.
276pub const Table = struct {
277 limits: Limits,
278 reftype: RefType,
279};
280
281/// Describes the layout of the memory where `min` represents
282/// the minimal amount of pages, and the optional `max` represents
283/// the max pages. When `null` will allow the host to determine the
284/// amount of pages.
285pub const Memory = struct {
286 limits: Limits,
287};
288
289/// Represents the type of a `Global` or an imported global.
290pub const GlobalType = struct {
291 valtype: Valtype,
292 mutable: bool,
293};
294
295pub const Global = struct {
296 global_type: GlobalType,
297 init: InitExpression,
298};
299
300/// Notates an object to be exported from wasm
301/// to the host.
302pub const Export = struct {
303 name: []const u8,
304 kind: ExternalKind,
305 index: u32,
306};
307
308/// Element describes the layout of the table that can
309/// be found at `table_index`
310pub const Element = struct {
311 table_index: u32,
312 offset: InitExpression,
313 func_indexes: []const u32,
314};
315
316/// Imports are used to import objects from the host
317pub const Import = struct {
318 module_name: []const u8,
319 name: []const u8,
320 kind: Kind,
321
322 pub const Kind = union(ExternalKind) {
323 function: u32,
324 table: Table,
325 memory: Limits,
326 global: GlobalType,
327 };
328};
329
330/// `Type` represents a function signature type containing both
331/// a slice of parameters as well as a slice of return values.
332pub const Type = struct {
333 params: []const Valtype,
334 returns: []const Valtype,
335
336 pub fn format(self: Type, comptime fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void {
337 _ = fmt;
338 _ = opt;
339 try writer.writeByte('(');
340 for (self.params) |param, i| {
341 try writer.print("{s}", .{@tagName(param)});
342 if (i + 1 != self.params.len) {
343 try writer.writeAll(", ");
344 }
345 }
346 try writer.writeAll(") -> ");
347 if (self.returns.len == 0) {
348 try writer.writeAll("nil");
349 } else {
350 for (self.returns) |return_ty, i| {
351 try writer.print("{s}", .{@tagName(return_ty)});
352 if (i + 1 != self.returns.len) {
353 try writer.writeAll(", ");
354 }
355 }
356 }
357 }
358
359 pub fn eql(self: Type, other: Type) bool {
360 return std.mem.eql(Valtype, self.params, other.params) and
361 std.mem.eql(Valtype, self.returns, other.returns);
362 }
363
364 pub fn deinit(self: *Type, gpa: *std.mem.Allocator) void {
365 gpa.free(self.params);
366 gpa.free(self.returns);
367 self.* = undefined;
368 }
369};
370
237/// Wasm module sections as per spec:371/// Wasm module sections as per spec:
238/// https://webassembly.github.io/spec/core/binary/modules.html372/// https://webassembly.github.io/spec/core/binary/modules.html
239pub const Section = enum(u8) {373pub const Section = enum(u8) {
...@@ -249,6 +383,8 @@ pub const Section = enum(u8) {...@@ -249,6 +383,8 @@ pub const Section = enum(u8) {
249 element,383 element,
250 code,384 code,
251 data,385 data,
386 data_count,
387 _,
252};388};
253389
254/// Returns the integer value of a given `Section`390/// Returns the integer value of a given `Section`
...@@ -270,7 +406,7 @@ pub fn externalKind(val: ExternalKind) u8 {...@@ -270,7 +406,7 @@ pub fn externalKind(val: ExternalKind) u8 {
270 return @enumToInt(val);406 return @enumToInt(val);
271}407}
272408
273// types409// type constants
274pub const element_type: u8 = 0x70;410pub const element_type: u8 = 0x70;
275pub const function_type: u8 = 0x60;411pub const function_type: u8 = 0x60;
276pub const result_type: u8 = 0x40;412pub const result_type: u8 = 0x40;
...@@ -280,7 +416,7 @@ pub const block_empty: u8 = 0x40;...@@ -280,7 +416,7 @@ pub const block_empty: u8 = 0x40;
280416
281// binary constants417// binary constants
282pub const magic = [_]u8{ 0x00, 0x61, 0x73, 0x6D }; // \0asm418pub const magic = [_]u8{ 0x00, 0x61, 0x73, 0x6D }; // \0asm
283pub const version = [_]u8{ 0x01, 0x00, 0x00, 0x00 }; // version 1419pub const version = [_]u8{ 0x01, 0x00, 0x00, 0x00 }; // version 1 (MVP)
284420
285// Each wasm page size is 64kB421// Each wasm page size is 64kB
286pub const page_size = 64 * 1024;422pub const page_size = 64 * 1024;
src/arch/wasm/CodeGen.zig+35-33
...@@ -518,9 +518,6 @@ blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, struct {...@@ -518,9 +518,6 @@ blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, struct {
518}) = .{},518}) = .{},
519/// `bytes` contains the wasm bytecode belonging to the 'code' section.519/// `bytes` contains the wasm bytecode belonging to the 'code' section.
520code: ArrayList(u8),520code: ArrayList(u8),
521/// Contains the generated function type bytecode for the current function
522/// found in `decl`
523func_type_data: ArrayList(u8),
524/// The index the next local generated will have521/// The index the next local generated will have
525/// NOTE: arguments share the index with locals therefore the first variable522/// NOTE: arguments share the index with locals therefore the first variable
526/// will have the index that comes after the last argument's index523/// will have the index that comes after the last argument's index
...@@ -539,7 +536,7 @@ locals: std.ArrayListUnmanaged(u8),...@@ -539,7 +536,7 @@ locals: std.ArrayListUnmanaged(u8),
539/// The Target we're emitting (used to call intInfo)536/// The Target we're emitting (used to call intInfo)
540target: std.Target,537target: std.Target,
541/// Represents the wasm binary file that is being linked.538/// Represents the wasm binary file that is being linked.
542bin_file: *link.File,539bin_file: *link.File.Wasm,
543/// Table with the global error set. Consists of every error found in540/// Table with the global error set. Consists of every error found in
544/// the compiled code. Each error name maps to a `Module.ErrorInt` which is emitted541/// the compiled code. Each error name maps to a `Module.ErrorInt` which is emitted
545/// during codegen to determine the error value.542/// during codegen to determine the error value.
...@@ -577,6 +574,7 @@ pub fn deinit(self: *Self) void {...@@ -577,6 +574,7 @@ pub fn deinit(self: *Self) void {
577 self.locals.deinit(self.gpa);574 self.locals.deinit(self.gpa);
578 self.mir_instructions.deinit(self.gpa);575 self.mir_instructions.deinit(self.gpa);
579 self.mir_extra.deinit(self.gpa);576 self.mir_extra.deinit(self.gpa);
577 self.code.deinit();
580 self.* = undefined;578 self.* = undefined;
581}579}
582580
...@@ -734,43 +732,44 @@ fn allocLocal(self: *Self, ty: Type) InnerError!WValue {...@@ -734,43 +732,44 @@ fn allocLocal(self: *Self, ty: Type) InnerError!WValue {
734 return WValue{ .local = initial_index };732 return WValue{ .local = initial_index };
735}733}
736734
737fn genFunctype(self: *Self) InnerError!void {735/// Generates a `wasm.Type` from a given function type.
738 assert(self.decl.has_tv);736/// Memory is owned by the caller.
739 const ty = self.decl.ty;737fn genFunctype(self: *Self, fn_ty: Type) !wasm.Type {
740 const writer = self.func_type_data.writer();738 var params = std.ArrayList(wasm.Valtype).init(self.gpa);
741739 defer params.deinit();
742 try writer.writeByte(wasm.function_type);740 var returns = std.ArrayList(wasm.Valtype).init(self.gpa);
741 defer returns.deinit();
743742
744 // param types743 // param types
745 try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen()));744 if (fn_ty.fnParamLen() != 0) {
746 if (ty.fnParamLen() != 0) {745 const fn_params = try self.gpa.alloc(Type, fn_ty.fnParamLen());
747 const params = try self.gpa.alloc(Type, ty.fnParamLen());746 defer self.gpa.free(fn_params);
748 defer self.gpa.free(params);747 fn_ty.fnParamTypes(fn_params);
749 ty.fnParamTypes(params);748 for (fn_params) |param_type| {
750 for (params) |param_type| {749 if (!param_type.hasCodeGenBits()) continue;
751 // Can we maybe get the source index of each param?750 try params.append(try self.typeToValtype(param_type));
752 const val_type = try self.genValtype(param_type);
753 try writer.writeByte(val_type);
754 }751 }
755 }752 }
756753
757 // return type754 // return type
758 const return_type = ty.fnReturnType();755 const return_type = fn_ty.fnReturnType();
759 switch (return_type.zigTypeTag()) {756 switch (return_type.zigTypeTag()) {
760 .Void, .NoReturn => try leb.writeULEB128(writer, @as(u32, 0)),757 .Void, .NoReturn => {},
761 .Struct => return self.fail("TODO: Implement struct as return type for wasm", .{}),758 .Struct => return self.fail("TODO: Implement struct as return type for wasm", .{}),
762 .Optional => return self.fail("TODO: Implement optionals as return type for wasm", .{}),759 .Optional => return self.fail("TODO: Implement optionals as return type for wasm", .{}),
763 else => {760 else => try returns.append(try self.typeToValtype(return_type)),
764 try leb.writeULEB128(writer, @as(u32, 1));
765 const val_type = try self.genValtype(return_type);
766 try writer.writeByte(val_type);
767 },
768 }761 }
762
763 return wasm.Type{
764 .params = params.toOwnedSlice(),
765 .returns = returns.toOwnedSlice(),
766 };
769}767}
770768
771pub fn genFunc(self: *Self) InnerError!Result {769pub fn genFunc(self: *Self) InnerError!Result {
772 try self.genFunctype();770 var func_type = try self.genFunctype(self.decl.ty);
773 // TODO: check for and handle death of instructions771 defer func_type.deinit(self.gpa);
772 self.decl.fn_link.wasm.type_index = try self.bin_file.putOrGetFuncType(func_type);
774773
775 var cc_result = try self.resolveCallingConventionValues(self.decl.ty);774 var cc_result = try self.resolveCallingConventionValues(self.decl.ty);
776 defer cc_result.deinit(self.gpa);775 defer cc_result.deinit(self.gpa);
...@@ -791,7 +790,7 @@ pub fn genFunc(self: *Self) InnerError!Result {...@@ -791,7 +790,7 @@ pub fn genFunc(self: *Self) InnerError!Result {
791790
792 var emit: Emit = .{791 var emit: Emit = .{
793 .mir = mir,792 .mir = mir,
794 .bin_file = self.bin_file,793 .bin_file = &self.bin_file.base,
795 .code = &self.code,794 .code = &self.code,
796 .locals = self.locals.items,795 .locals = self.locals.items,
797 .decl = self.decl,796 .decl = self.decl,
...@@ -813,8 +812,10 @@ pub fn genFunc(self: *Self) InnerError!Result {...@@ -813,8 +812,10 @@ pub fn genFunc(self: *Self) InnerError!Result {
813pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result {812pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result {
814 switch (ty.zigTypeTag()) {813 switch (ty.zigTypeTag()) {
815 .Fn => {814 .Fn => {
816 try self.genFunctype();
817 if (val.tag() == .extern_fn) {815 if (val.tag() == .extern_fn) {
816 var func_type = try self.genFunctype(self.decl.ty);
817 defer func_type.deinit(self.gpa);
818 self.decl.fn_link.wasm.type_index = try self.bin_file.putOrGetFuncType(func_type);
818 return Result.appended; // don't need code body for extern functions819 return Result.appended; // don't need code body for extern functions
819 }820 }
820 return self.fail("TODO implement wasm codegen for function pointers", .{});821 return self.fail("TODO implement wasm codegen for function pointers", .{});
...@@ -1079,7 +1080,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1079,7 +1080,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1079 try self.emitWValue(arg_val);1080 try self.emitWValue(arg_val);
1080 }1081 }
10811082
1082 try self.addLabel(.call, target.link.wasm.symbol_index);1083 try self.addLabel(.call, target.link.wasm.sym_index);
10831084
1084 const ret_ty = target.ty.fnReturnType();1085 const ret_ty = target.ty.fnReturnType();
1085 switch (ret_ty.zigTypeTag()) {1086 switch (ret_ty.zigTypeTag()) {
...@@ -1364,13 +1365,14 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void {...@@ -1364,13 +1365,14 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void {
1364 decl.alive = true;1365 decl.alive = true;
13651366
1366 // offset into the offset table within the 'data' section1367 // offset into the offset table within the 'data' section
1367 const ptr_width = self.target.cpu.arch.ptrBitWidth() / 8;1368 // const ptr_width = self.target.cpu.arch.ptrBitWidth() / 8;
1368 try self.addImm32(@bitCast(i32, decl.link.wasm.offset_index * ptr_width));1369 // try self.addImm32(@bitCast(i32, decl.link.wasm.offset_index * ptr_width));
13691370
1370 // memory instruction followed by their memarg immediate1371 // memory instruction followed by their memarg immediate
1371 // memarg ::== x:u32, y:u32 => {align x, offset y}1372 // memarg ::== x:u32, y:u32 => {align x, offset y}
1372 const extra_index = try self.addExtra(Mir.MemArg{ .offset = 0, .alignment = 4 });1373 const extra_index = try self.addExtra(Mir.MemArg{ .offset = 0, .alignment = 4 });
1373 try self.addInst(.{ .tag = .i32_load, .data = .{ .payload = extra_index } });1374 try self.addInst(.{ .tag = .i32_load, .data = .{ .payload = extra_index } });
1375 @panic("REDO!\n");
1374 } else return self.fail("Wasm TODO: emitConstant for other const pointer tag {s}", .{val.tag()});1376 } else return self.fail("Wasm TODO: emitConstant for other const pointer tag {s}", .{val.tag()});
1375 },1377 },
1376 .Void => {},1378 .Void => {},
src/arch/wasm/Emit.zig+3-9
...@@ -29,8 +29,6 @@ const InnerError = error{...@@ -29,8 +29,6 @@ const InnerError = error{
2929
30pub fn emitMir(emit: *Emit) InnerError!void {30pub fn emitMir(emit: *Emit) InnerError!void {
31 const mir_tags = emit.mir.instructions.items(.tag);31 const mir_tags = emit.mir.instructions.items(.tag);
32 // Reserve space to write the size after generating the code.
33 try emit.code.resize(5);
34 // write the locals in the prologue of the function body32 // write the locals in the prologue of the function body
35 // before we emit the function body when lowering MIR33 // before we emit the function body when lowering MIR
36 try emit.emitLocals();34 try emit.emitLocals();
...@@ -157,11 +155,6 @@ pub fn emitMir(emit: *Emit) InnerError!void {...@@ -157,11 +155,6 @@ pub fn emitMir(emit: *Emit) InnerError!void {
157 .i64_extend32_s => try emit.emitTag(tag),155 .i64_extend32_s => try emit.emitTag(tag),
158 }156 }
159 }157 }
160
161 // Fill in the size of the generated code to the reserved space at the
162 // beginning of the buffer.
163 const size = emit.code.items.len - 5;
164 leb128.writeUnsignedFixed(5, emit.code.items[0..5], @intCast(u32, size));
165}158}
166159
167fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError {160fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError {
...@@ -269,8 +262,9 @@ fn emitCall(emit: *Emit, inst: Mir.Inst.Index) !void {...@@ -269,8 +262,9 @@ fn emitCall(emit: *Emit, inst: Mir.Inst.Index) !void {
269 // The function index immediate argument will be filled in using this data262 // The function index immediate argument will be filled in using this data
270 // in link.Wasm.flush().263 // in link.Wasm.flush().
271 // TODO: Replace this with proper relocations saved in the Atom.264 // TODO: Replace this with proper relocations saved in the Atom.
272 try emit.decl.fn_link.wasm.idx_refs.append(emit.bin_file.allocator, .{265 try emit.decl.link.wasm.relocs.append(emit.bin_file.allocator, .{
273 .offset = offset,266 .offset = offset,
274 .decl = label,267 .index = label,
268 .relocation_type = .R_WASM_FUNCTION_INDEX_LEB,
275 });269 });
276}270}
src/link/Wasm.zig+370-294
...@@ -10,6 +10,7 @@ const leb = std.leb;...@@ -10,6 +10,7 @@ const leb = std.leb;
10const log = std.log.scoped(.link);10const log = std.log.scoped(.link);
11const wasm = std.wasm;11const wasm = std.wasm;
1212
13const Atom = @import("Wasm/Atom.zig");
13const Module = @import("../Module.zig");14const Module = @import("../Module.zig");
14const Compilation = @import("../Compilation.zig");15const Compilation = @import("../Compilation.zig");
15const CodeGen = @import("../arch/wasm/CodeGen.zig");16const CodeGen = @import("../arch/wasm/CodeGen.zig");
...@@ -22,101 +23,80 @@ const TypedValue = @import("../TypedValue.zig");...@@ -22,101 +23,80 @@ const TypedValue = @import("../TypedValue.zig");
22const LlvmObject = @import("../codegen/llvm.zig").Object;23const LlvmObject = @import("../codegen/llvm.zig").Object;
23const Air = @import("../Air.zig");24const Air = @import("../Air.zig");
24const Liveness = @import("../Liveness.zig");25const Liveness = @import("../Liveness.zig");
26const Symbol = @import("Wasm/Symbol.zig");
27const types = @import("Wasm/types.zig");
2528
26pub const base_tag = link.File.Tag.wasm;29pub const base_tag = link.File.Tag.wasm;
2730
31/// deprecated: Use `@import("Wasm/Atom.zig");`
32pub const DeclBlock = Atom;
33
28base: link.File,34base: link.File,
29/// If this is not null, an object file is created by LLVM and linked with LLD afterwards.35/// If this is not null, an object file is created by LLVM and linked with LLD afterwards.
30llvm_object: ?*LlvmObject = null,36llvm_object: ?*LlvmObject = null,
31/// List of all function Decls to be written to the output file. The index of
32/// each Decl in this list at the time of writing the binary is used as the
33/// function index. In the event where ext_funcs' size is not 0, the index of
34/// each function is added on top of the ext_funcs' length.
35/// TODO: can/should we access some data structure in Module directly?
36funcs: std.ArrayListUnmanaged(*Module.Decl) = .{},
37/// List of all extern function Decls to be written to the `import` section of the
38/// wasm binary. The position in the list defines the function index
39ext_funcs: std.ArrayListUnmanaged(*Module.Decl) = .{},
40/// When importing objects from the host environment, a name must be supplied.37/// When importing objects from the host environment, a name must be supplied.
41/// LLVM uses "env" by default when none is given. This would be a good default for Zig38/// LLVM uses "env" by default when none is given. This would be a good default for Zig
42/// to support existing code.39/// to support existing code.
43/// TODO: Allow setting this through a flag?40/// TODO: Allow setting this through a flag?
44host_name: []const u8 = "env",41host_name: []const u8 = "env",
45/// The last `DeclBlock` that was initialized will be saved here.42/// The last `DeclBlock` that was initialized will be saved here.
46last_block: ?*DeclBlock = null,43last_atom: ?*Atom = null,
47/// Table with offsets, each element represents an offset with the value being
48/// the offset into the 'data' section where the data lives
49offset_table: std.ArrayListUnmanaged(u32) = .{},
50/// List of offset indexes which are free to be used for new decl's.
51/// Each element's value points to an index into the offset_table.
52offset_table_free_list: std.ArrayListUnmanaged(u32) = .{},
53/// List of all `Decl` that are currently alive.44/// List of all `Decl` that are currently alive.
54/// This is ment for bookkeeping so we can safely cleanup all codegen memory45/// This is ment for bookkeeping so we can safely cleanup all codegen memory
55/// when calling `deinit`46/// when calling `deinit`
56symbols: std.ArrayListUnmanaged(*Module.Decl) = .{},47decls: std.AutoHashMapUnmanaged(*Module.Decl, void) = .{},
48/// List of all symbols.
49symbols: std.ArrayListUnmanaged(Symbol) = .{},
57/// List of symbol indexes which are free to be used.50/// List of symbol indexes which are free to be used.
58symbols_free_list: std.ArrayListUnmanaged(u32) = .{},51symbols_free_list: std.ArrayListUnmanaged(u32) = .{},
52/// Maps atoms to their segment index
53atoms: std.AutoHashMapUnmanaged(u32, *Atom) = .{},
54/// Represents the index into `segments` where the 'code' section
55/// lives.
56code_section_index: ?u32 = null,
57/// The count of imported functions. This number will be appended
58/// to the function indexes as their index starts at the lowest non-extern function.
59imported_functions_count: u32 = 0,
60/// List of all 'extern' declarations
61imports: std.ArrayListUnmanaged(wasm.Import) = .{},
62/// List of indexes of symbols representing extern declarations.
63import_symbols: std.ArrayListUnmanaged(u32) = .{},
64/// Represents non-synthetic section entries.
65/// Used for code, data and custom sections.
66segments: std.ArrayListUnmanaged(Segment) = .{},
67/// Maps a data segment key (such as .rodata) to the index into `segments`.
68data_segments: std.StringArrayHashMapUnmanaged(u32) = .{},
69/// A list of `types.Segment` which provide meta data
70/// about a data symbol such as its name
71segment_info: std.ArrayListUnmanaged(types.Segment) = .{},
72
73// Output sections
74/// Output type section
75func_types: std.ArrayListUnmanaged(wasm.Type) = .{},
76/// Output function section
77functions: std.ArrayListUnmanaged(wasm.Func) = .{},
78/// Output global section
79globals: std.ArrayListUnmanaged(wasm.Global) = .{},
80
81/// Indirect function table, used to call function pointers
82/// When this is non-zero, we must emit a table entry,
83/// as well as an 'elements' section.
84function_table: std.ArrayListUnmanaged(Symbol) = .{},
85
86pub const Segment = struct {
87 alignment: u32,
88 size: u32,
89 offset: u32,
90};
5991
60pub const FnData = struct {92pub const FnData = struct {
61 /// Generated code for the type of the function93 type_index: u32,
62 functype: std.ArrayListUnmanaged(u8),
63 /// Generated code for the body of the function
64 code: std.ArrayListUnmanaged(u8),
65 /// Locations in the generated code where function indexes must be filled in.
66 /// This must be kept ordered by offset.
67 /// `decl` is the symbol_index of the target.
68 idx_refs: std.ArrayListUnmanaged(struct { offset: u32, decl: u32 }),
6994
70 pub const empty: FnData = .{95 pub const empty: FnData = .{
71 .functype = .{},96 .type_index = undefined,
72 .code = .{},
73 .idx_refs = .{},
74 };97 };
75};98};
7699
77pub const DeclBlock = struct {
78 /// Determines whether the `DeclBlock` has been initialized for codegen.
79 init: bool,
80 /// Index into the `symbols` list.
81 symbol_index: u32,
82 /// Index into the offset table
83 offset_index: u32,
84 /// The size of the block and how large part of the data section it occupies.
85 /// Will be 0 when the Decl will not live inside the data section and `data` will be undefined.
86 size: u32,
87 /// Points to the previous and next blocks.
88 /// Can be used to find the total size, and used to calculate the `offset` based on the previous block.
89 prev: ?*DeclBlock,
90 next: ?*DeclBlock,
91 /// Pointer to data that will be written to the 'data' section.
92 /// This data either lives in `FnData.code` or is externally managed.
93 /// For data that does not live inside the 'data' section, this field will be undefined. (size == 0).
94 data: [*]const u8,
95
96 pub const empty: DeclBlock = .{
97 .init = false,
98 .symbol_index = 0,
99 .offset_index = 0,
100 .size = 0,
101 .prev = null,
102 .next = null,
103 .data = undefined,
104 };
105
106 /// Unplugs the `DeclBlock` from the chain
107 fn unplug(self: *DeclBlock) void {
108 if (self.prev) |prev| {
109 prev.next = self.next;
110 }
111
112 if (self.next) |next| {
113 next.prev = self.prev;
114 }
115 self.next = null;
116 self.prev = null;
117 }
118};
119
120pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Wasm {100pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Wasm {
121 assert(options.object_format == .wasm);101 assert(options.object_format == .wasm);
122102
...@@ -139,6 +119,22 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio...@@ -139,6 +119,22 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
139119
140 try file.writeAll(&(wasm.magic ++ wasm.version));120 try file.writeAll(&(wasm.magic ++ wasm.version));
141121
122 // As sym_index '0' is reserved, we use it for our stack pointer symbol
123 const global = try wasm_bin.globals.addOne(allocator);
124 global.* = .{
125 .global_type = .{
126 .valtype = .i32,
127 .mutable = true,
128 },
129 .init = .{ .i32_const = 0 },
130 };
131 const symbol = try wasm_bin.symbols.addOne(allocator);
132 symbol.* = .{
133 .name = "__stack_pointer",
134 .tag = .global,
135 .flags = 0,
136 .index = 0,
137 };
142 return wasm_bin;138 return wasm_bin;
143}139}
144140
...@@ -160,63 +156,58 @@ pub fn deinit(self: *Wasm) void {...@@ -160,63 +156,58 @@ pub fn deinit(self: *Wasm) void {
160 if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator);156 if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator);
161 }157 }
162158
163 for (self.symbols.items) |decl, symbol_index| {159 var decl_it = self.decls.keyIterator();
164 // Check if we already freed all memory for the symbol160 while (decl_it.next()) |decl_ptr| {
165 // TODO: Audit this when we refactor the linker.161 const decl = decl_ptr.*;
166 var already_freed = false;162 decl.link.wasm.deinit(self.base.allocator);
167 for (self.symbols_free_list.items) |index| {163 }
168 if (symbol_index == index) {164
169 already_freed = true;165 for (self.func_types.items) |func_type| {
170 break;166 self.base.allocator.free(func_type.params);
171 }167 self.base.allocator.free(func_type.returns);
172 }168 }
173 if (already_freed) continue;169 for (self.segment_info.items) |segment_info| {
174 decl.fn_link.wasm.functype.deinit(self.base.allocator);170 self.base.allocator.free(segment_info.name);
175 decl.fn_link.wasm.code.deinit(self.base.allocator);
176 decl.fn_link.wasm.idx_refs.deinit(self.base.allocator);
177 }171 }
178172
179 self.funcs.deinit(self.base.allocator);173 self.decls.deinit(self.base.allocator);
180 self.ext_funcs.deinit(self.base.allocator);
181 self.offset_table.deinit(self.base.allocator);
182 self.offset_table_free_list.deinit(self.base.allocator);
183 self.symbols.deinit(self.base.allocator);174 self.symbols.deinit(self.base.allocator);
184 self.symbols_free_list.deinit(self.base.allocator);175 self.symbols_free_list.deinit(self.base.allocator);
176 self.atoms.deinit(self.base.allocator);
177 self.segments.deinit(self.base.allocator);
178 self.data_segments.deinit(self.base.allocator);
179 self.segment_info.deinit(self.base.allocator);
180
181 // free output sections
182 self.imports.deinit(self.base.allocator);
183 self.import_symbols.deinit(self.base.allocator);
184 self.func_types.deinit(self.base.allocator);
185 self.functions.deinit(self.base.allocator);
186 self.globals.deinit(self.base.allocator);
187 self.function_table.deinit(self.base.allocator);
185}188}
186189
187pub fn allocateDeclIndexes(self: *Wasm, decl: *Module.Decl) !void {190pub fn allocateDeclIndexes(self: *Wasm, decl: *Module.Decl) !void {
188 if (decl.link.wasm.init) return;191 if (decl.link.wasm.sym_index != 0) return;
189192
190 try self.offset_table.ensureUnusedCapacity(self.base.allocator, 1);
191 try self.symbols.ensureUnusedCapacity(self.base.allocator, 1);193 try self.symbols.ensureUnusedCapacity(self.base.allocator, 1);
194 try self.decls.putNoClobber(self.base.allocator, decl, {});
192195
193 const block = &decl.link.wasm;196 const atom = &decl.link.wasm;
194 block.init = true;
195197
196 if (self.offset_table_free_list.popOrNull()) |index| {198 var symbol: Symbol = .{
197 block.offset_index = index;199 .name = undefined, // will be set after updateDecl
198 } else {200 .flags = 0,
199 block.offset_index = @intCast(u32, self.offset_table.items.len);201 .tag = undefined, // will be set after updateDecl
200 _ = self.offset_table.addOneAssumeCapacity();202 .index = undefined, // will be set after updateDecl
201 }203 };
202204
203 if (self.symbols_free_list.popOrNull()) |index| {205 if (self.symbols_free_list.popOrNull()) |index| {
204 block.symbol_index = index;206 atom.sym_index = index;
205 self.symbols.items[block.symbol_index] = decl;207 self.symbols.items[index] = symbol;
206 } else {208 } else {
207 block.symbol_index = @intCast(u32, self.symbols.items.len);209 atom.sym_index = @intCast(u32, self.symbols.items.len);
208 self.symbols.appendAssumeCapacity(decl);210 self.symbols.appendAssumeCapacity(symbol);
209 }
210
211 self.offset_table.items[block.offset_index] = 0;
212
213 if (decl.ty.zigTypeTag() == .Fn) {
214 switch (decl.val.tag()) {
215 // dependent on function type, appends it to the correct list
216 .function => try self.funcs.append(self.base.allocator, decl),
217 .extern_fn => try self.ext_funcs.append(self.base.allocator, decl),
218 else => unreachable,
219 }
220 }211 }
221}212}
222213
...@@ -228,25 +219,19 @@ pub fn updateFunc(self: *Wasm, module: *Module, func: *Module.Fn, air: Air, live...@@ -228,25 +219,19 @@ pub fn updateFunc(self: *Wasm, module: *Module, func: *Module.Fn, air: Air, live
228 if (self.llvm_object) |llvm_object| return llvm_object.updateFunc(module, func, air, liveness);219 if (self.llvm_object) |llvm_object| return llvm_object.updateFunc(module, func, air, liveness);
229 }220 }
230 const decl = func.owner_decl;221 const decl = func.owner_decl;
231 assert(decl.link.wasm.init); // Must call allocateDeclIndexes()222 assert(decl.link.wasm.sym_index != 0); // Must call allocateDeclIndexes()
232
233 const fn_data = &decl.fn_link.wasm;
234 fn_data.functype.items.len = 0;
235 fn_data.code.items.len = 0;
236 fn_data.idx_refs.items.len = 0;
237223
238 var codegen: CodeGen = .{224 var codegen: CodeGen = .{
239 .gpa = self.base.allocator,225 .gpa = self.base.allocator,
240 .air = air,226 .air = air,
241 .liveness = liveness,227 .liveness = liveness,
242 .values = .{},228 .values = .{},
243 .code = fn_data.code.toManaged(self.base.allocator),229 .code = std.ArrayList(u8).init(self.base.allocator),
244 .func_type_data = fn_data.functype.toManaged(self.base.allocator),
245 .decl = decl,230 .decl = decl,
246 .err_msg = undefined,231 .err_msg = undefined,
247 .locals = .{},232 .locals = .{},
248 .target = self.base.options.target,233 .target = self.base.options.target,
249 .bin_file = &self.base,234 .bin_file = self,
250 .global_error_set = self.base.options.module.?.global_error_set,235 .global_error_set = self.base.options.module.?.global_error_set,
251 };236 };
252 defer codegen.deinit();237 defer codegen.deinit();
...@@ -272,26 +257,19 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {...@@ -272,26 +257,19 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
272 if (build_options.have_llvm) {257 if (build_options.have_llvm) {
273 if (self.llvm_object) |llvm_object| return llvm_object.updateDecl(module, decl);258 if (self.llvm_object) |llvm_object| return llvm_object.updateDecl(module, decl);
274 }259 }
275 assert(decl.link.wasm.init); // Must call allocateDeclIndexes()260 assert(decl.link.wasm.sym_index != 0); // Must call allocateDeclIndexes()
276
277 // TODO don't use this for non-functions
278 const fn_data = &decl.fn_link.wasm;
279 fn_data.functype.items.len = 0;
280 fn_data.code.items.len = 0;
281 fn_data.idx_refs.items.len = 0;
282261
283 var codegen: CodeGen = .{262 var codegen: CodeGen = .{
284 .gpa = self.base.allocator,263 .gpa = self.base.allocator,
285 .air = undefined,264 .air = undefined,
286 .liveness = undefined,265 .liveness = undefined,
287 .values = .{},266 .values = .{},
288 .code = fn_data.code.toManaged(self.base.allocator),267 .code = std.ArrayList(u8).init(self.base.allocator),
289 .func_type_data = fn_data.functype.toManaged(self.base.allocator),
290 .decl = decl,268 .decl = decl,
291 .err_msg = undefined,269 .err_msg = undefined,
292 .locals = .{},270 .locals = .{},
293 .target = self.base.options.target,271 .target = self.base.options.target,
294 .bin_file = &self.base,272 .bin_file = self,
295 .global_error_set = self.base.options.module.?.global_error_set,273 .global_error_set = self.base.options.module.?.global_error_set,
296 };274 };
297 defer codegen.deinit();275 defer codegen.deinit();
...@@ -310,33 +288,87 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {...@@ -310,33 +288,87 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
310}288}
311289
312fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, result: CodeGen.Result, codegen: *CodeGen) !void {290fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, result: CodeGen.Result, codegen: *CodeGen) !void {
313 const fn_data: *FnData = &decl.fn_link.wasm;
314
315 fn_data.code = codegen.code.toUnmanaged();
316 fn_data.functype = codegen.func_type_data.toUnmanaged();
317
318 const code: []const u8 = switch (result) {291 const code: []const u8 = switch (result) {
319 .appended => @as([]const u8, fn_data.code.items),292 .appended => @as([]const u8, codegen.code.items),
320 .externally_managed => |payload| payload,293 .externally_managed => |payload| payload,
321 };294 };
322295
323 const block = &decl.link.wasm;296 const atom: *Atom = &decl.link.wasm;
324 if (decl.ty.zigTypeTag() != .Fn) {297 atom.size = @intCast(u32, code.len);
325 block.size = @intCast(u32, code.len);298 try atom.code.appendSlice(self.base.allocator, code);
326 block.data = code.ptr;
327 }
328299
329 // If we're updating an existing decl, unplug it first300 // If we're updating an existing decl, unplug it first
330 // to avoid infinite loops due to earlier links301 // to avoid infinite loops due to earlier links
331 block.unplug();302 atom.unplug();
332303
333 if (self.last_block) |last| {304 const symbol: *Symbol = &self.symbols.items[atom.sym_index];
334 if (last != block) {305 if (decl.isExtern()) {
335 last.next = block;306 symbol.setUndefined(true);
336 block.prev = last;307 }
337 }308 symbol.name = decl.name;
309 const final_index = switch (decl.ty.zigTypeTag()) {
310 .Fn => result: {
311 const type_index = decl.fn_link.wasm.type_index;
312 const index = @intCast(u32, self.functions.items.len);
313 try self.functions.append(self.base.allocator, .{ .type_index = type_index });
314 symbol.tag = .function;
315 symbol.index = index;
316 atom.alignment = 1;
317
318 if (self.code_section_index == null) {
319 self.code_section_index = @intCast(u32, self.segments.items.len);
320 try self.segments.append(self.base.allocator, .{
321 .alignment = atom.alignment,
322 .size = atom.size,
323 .offset = atom.offset,
324 });
325 } else {
326 self.segments.items[self.code_section_index.?].size += atom.size;
327 }
328
329 break :result self.code_section_index.?;
330 },
331 else => result: {
332 const gop = try self.data_segments.getOrPut(self.base.allocator, ".rodata");
333 const atom_index = if (gop.found_existing) blk: {
334 self.segments.items[gop.value_ptr.*].size += atom.size;
335 break :blk gop.value_ptr.*;
336 } else blk: {
337 const index = @intCast(u32, self.segments.items.len) - @boolToInt(self.code_section_index != null);
338 try self.segments.append(self.base.allocator, .{
339 .alignment = atom.alignment,
340 .size = atom.size,
341 .offset = atom.offset,
342 });
343 gop.value_ptr.* = index;
344 break :blk index;
345 };
346 const info_index = @intCast(u32, self.segment_info.items.len);
347 const segment_name = try std.mem.concat(self.base.allocator, u8, &.{
348 ".rodata.",
349 std.mem.span(symbol.name),
350 });
351 errdefer self.base.allocator.free(segment_name);
352 try self.segment_info.append(self.base.allocator, .{
353 .name = segment_name,
354 .alignment = atom.alignment,
355 .flags = 0,
356 });
357 symbol.tag = .data;
358 symbol.index = info_index;
359 atom.alignment = decl.ty.abiAlignment(self.base.options.target);
360 break :result atom_index;
361 },
362 };
363
364 if (self.atoms.getPtr(final_index)) |last| {
365 last.*.next = atom;
366 atom.prev = last.*;
367 atom.offset = last.*.offset + last.*.size;
368 last.* = atom;
369 } else {
370 try self.atoms.putNoClobber(self.base.allocator, final_index, atom);
338 }371 }
339 self.last_block = block;
340}372}
341373
342pub fn updateDeclExports(374pub fn updateDeclExports(
...@@ -358,29 +390,28 @@ pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {...@@ -358,29 +390,28 @@ pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
358 if (self.llvm_object) |llvm_object| return llvm_object.freeDecl(decl);390 if (self.llvm_object) |llvm_object| return llvm_object.freeDecl(decl);
359 }391 }
360392
361 if (self.getFuncidx(decl)) |func_idx| {393 const atom = &decl.link.wasm;
362 switch (decl.val.tag()) {
363 .function => _ = self.funcs.swapRemove(func_idx),
364 .extern_fn => _ = self.ext_funcs.swapRemove(func_idx),
365 else => unreachable,
366 }
367 }
368 const block = &decl.link.wasm;
369394
370 if (self.last_block == block) {395 if (self.last_atom == atom) {
371 self.last_block = block.prev;396 self.last_atom = atom.prev;
372 }397 }
373398
374 block.unplug();399 atom.unplug();
375400 self.symbols_free_list.append(self.base.allocator, atom.sym_index) catch {};
376 self.offset_table_free_list.append(self.base.allocator, decl.link.wasm.offset_index) catch {};401 atom.deinit(self.base.allocator);
377 self.symbols_free_list.append(self.base.allocator, block.symbol_index) catch {};402 _ = self.decls.remove(decl);
378403}
379 block.init = false;
380404
381 decl.fn_link.wasm.functype.deinit(self.base.allocator);405fn createUndefinedSymbol(self: *Wasm, decl: *Module.Decl, symbol_index: u32) !void {
382 decl.fn_link.wasm.code.deinit(self.base.allocator);406 var symbol: *Symbol = &self.symbols.items[symbol_index];
383 decl.fn_link.wasm.idx_refs.deinit(self.base.allocator);407 symbol.setUndefined(true);
408 switch (decl.ty.zigTypeTag()) {
409 .Fn => {
410 symbol.setIndex(self.imported_functions_count);
411 self.imported_functions_count += 1;
412 },
413 else => @panic("TODO: Wasm implement extern non-function types"),
414 }
384}415}
385416
386pub fn flush(self: *Wasm, comp: *Compilation) !void {417pub fn flush(self: *Wasm, comp: *Compilation) !void {
...@@ -398,27 +429,20 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -398,27 +429,20 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
398429
399 const file = self.base.file.?;430 const file = self.base.file.?;
400 const header_size = 5 + 1;431 const header_size = 5 + 1;
401 // ptr_width in bytes
402 const ptr_width = self.base.options.target.cpu.arch.ptrBitWidth() / 8;
403 // The size of the offset table in bytes
404 // The table contains all decl's with its corresponding offset into
405 // the 'data' section
406 const offset_table_size = @intCast(u32, self.offset_table.items.len * ptr_width);
407 // The size of the emulated stack432 // The size of the emulated stack
408 const stack_size = @intCast(u32, self.base.options.stack_size_override orelse std.wasm.page_size);433 const stack_size = @intCast(u32, self.base.options.stack_size_override orelse std.wasm.page_size);
409434
410 // The size of the data, this together with `offset_table_size` amounts to the435 var data_size: u32 = 0;
411 // total size of the 'data' section436 for (self.segments.items) |segment, index| {
412 var first_decl: ?*DeclBlock = null;437 // skip 'code' segments as they do not count towards data section size
413 const data_size: u32 = if (self.last_block) |last| blk: {438 if (self.code_section_index) |code_index| {
414 var size = last.size;439 if (index == code_index) continue;
415 var cur = last;
416 while (cur.prev) |prev| : (cur = prev) {
417 size += prev.size;
418 }440 }
419 first_decl = cur;441 data_size += segment.size;
420 break :blk size;442 }
421 } else 0;443
444 // set the stack size on the global
445 self.globals.items[0].init = .{ .i32_const = @bitCast(i32, data_size + stack_size) };
422446
423 // No need to rewrite the magic/version header447 // No need to rewrite the magic/version header
424 try file.setEndPos(@sizeOf(@TypeOf(wasm.magic ++ wasm.version)));448 try file.setEndPos(@sizeOf(@TypeOf(wasm.magic ++ wasm.version)));
...@@ -427,46 +451,62 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -427,46 +451,62 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
427 // Type section451 // Type section
428 {452 {
429 const header_offset = try reserveVecSectionHeader(file);453 const header_offset = try reserveVecSectionHeader(file);
454 const writer = file.writer();
430455
431 // extern functions are defined in the wasm binary first through the `import`456 for (self.func_types.items) |func_type| {
432 // section, so define their func types first457 try leb.writeULEB128(writer, wasm.function_type);
433 for (self.ext_funcs.items) |decl| try file.writeAll(decl.fn_link.wasm.functype.items);458 try leb.writeULEB128(writer, @intCast(u32, func_type.params.len));
434 for (self.funcs.items) |decl| try file.writeAll(decl.fn_link.wasm.functype.items);459 for (func_type.params) |param_ty| try leb.writeULEB128(writer, wasm.valtype(param_ty));
460 try leb.writeULEB128(writer, @intCast(u32, func_type.returns.len));
461 for (func_type.returns) |ret_ty| try leb.writeULEB128(writer, wasm.valtype(ret_ty));
462 }
435463
436 try writeVecSectionHeader(464 try writeVecSectionHeader(
437 file,465 file,
438 header_offset,466 header_offset,
439 .type,467 .type,
440 @intCast(u32, (try file.getPos()) - header_offset - header_size),468 @intCast(u32, (try file.getPos()) - header_offset - header_size),
441 @intCast(u32, self.ext_funcs.items.len + self.funcs.items.len),469 @intCast(u32, self.func_types.items.len),
442 );470 );
443 }471 }
444472
445 // Import section473 // Import section
446 {474 if (self.import_symbols.items.len > 0) {
447 // TODO: implement non-functions imports
448 const header_offset = try reserveVecSectionHeader(file);475 const header_offset = try reserveVecSectionHeader(file);
449 const writer = file.writer();476 const writer = file.writer();
450 for (self.ext_funcs.items) |decl, typeidx| {477 for (self.import_symbols.items) |symbol_index| {
478 const import_symbol = self.symbols.items[symbol_index];
479 std.debug.assert(import_symbol.isUndefined());
451 try leb.writeULEB128(writer, @intCast(u32, self.host_name.len));480 try leb.writeULEB128(writer, @intCast(u32, self.host_name.len));
452 try writer.writeAll(self.host_name);481 try writer.writeAll(self.host_name);
453482
454 // wasm requires the length of the import name with no null-termination483 const name = std.mem.span(import_symbol.name);
455 const decl_len = mem.len(decl.name);484 try leb.writeULEB128(writer, @intCast(u32, name.len));
456 try leb.writeULEB128(writer, @intCast(u32, decl_len));485 try writer.writeAll(name);
457 try writer.writeAll(decl.name[0..decl_len]);486
458487 try writer.writeByte(wasm.externalKind(import_symbol.tag.externalType()));
459 // emit kind and the function type488 const import = self.findImport(import_symbol.index, import_symbol.tag.externalType()).?;
460 try writer.writeByte(wasm.externalKind(.function));489 switch (import.kind) {
461 try leb.writeULEB128(writer, @intCast(u32, typeidx));490 .function => |type_index| try leb.writeULEB128(writer, type_index),
491 .global => |global_type| {
492 try leb.writeULEB128(writer, wasm.valtype(global_type.valtype));
493 try writer.writeByte(@boolToInt(global_type.mutable));
494 },
495 .table => |table| {
496 try leb.writeULEB128(writer, wasm.reftype(table.reftype));
497 try emitLimits(writer, table.limits);
498 },
499 .memory => |limits| {
500 try emitLimits(writer, limits);
501 },
502 }
462 }503 }
463
464 try writeVecSectionHeader(504 try writeVecSectionHeader(
465 file,505 file,
466 header_offset,506 header_offset,
467 .import,507 .import,
468 @intCast(u32, (try file.getPos()) - header_offset - header_size),508 @intCast(u32, (try file.getPos()) - header_offset - header_size),
469 @intCast(u32, self.ext_funcs.items.len),509 @intCast(u32, self.imports.items.len),
470 );510 );
471 }511 }
472512
...@@ -474,9 +514,11 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -474,9 +514,11 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
474 {514 {
475 const header_offset = try reserveVecSectionHeader(file);515 const header_offset = try reserveVecSectionHeader(file);
476 const writer = file.writer();516 const writer = file.writer();
477 for (self.funcs.items) |_, typeidx| {517 for (self.functions.items) |function| {
478 const func_idx = @intCast(u32, self.getFuncIdxOffset() + typeidx);518 try leb.writeULEB128(
479 try leb.writeULEB128(writer, func_idx);519 writer,
520 @intCast(u32, function.type_index),
521 );
480 }522 }
481523
482 try writeVecSectionHeader(524 try writeVecSectionHeader(
...@@ -484,7 +526,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -484,7 +526,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
484 header_offset,526 header_offset,
485 .function,527 .function,
486 @intCast(u32, (try file.getPos()) - header_offset - header_size),528 @intCast(u32, (try file.getPos()) - header_offset - header_size),
487 @intCast(u32, self.funcs.items.len),529 @intCast(u32, self.functions.items.len),
488 );530 );
489 }531 }
490532
...@@ -500,7 +542,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -500,7 +542,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
500 writer,542 writer,
501 try std.math.divCeil(543 try std.math.divCeil(
502 u32,544 u32,
503 offset_table_size + data_size + stack_size,545 data_size + stack_size,
504 std.wasm.page_size,546 std.wasm.page_size,
505 ),547 ),
506 );548 );
...@@ -515,29 +557,21 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -515,29 +557,21 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
515557
516 // Global section (used to emit stack pointer)558 // Global section (used to emit stack pointer)
517 {559 {
518 // We emit the emulated stack at the end of the data section,
519 // 'growing' downwards towards the program memory.
520 // TODO: Have linker resolve the offset table, so we can emit the stack
521 // at the start so we can't overwrite program memory with the stack.
522 const sp_value = offset_table_size + data_size + std.wasm.page_size;
523 const mutable = true; // stack pointer MUST be mutable
524 const header_offset = try reserveVecSectionHeader(file);560 const header_offset = try reserveVecSectionHeader(file);
525 const writer = file.writer();561 const writer = file.writer();
526562
527 try writer.writeByte(wasm.valtype(.i32));563 for (self.globals.items) |global| {
528 try writer.writeByte(@boolToInt(mutable));564 try writer.writeByte(wasm.valtype(global.global_type.valtype));
529565 try writer.writeByte(@boolToInt(global.global_type.mutable));
530 // set the initial value of the stack pointer to the data size + stack size566 try emitInit(writer, global.init);
531 try writer.writeByte(wasm.opcode(.i32_const));567 }
532 try leb.writeILEB128(writer, @bitCast(i32, sp_value));
533 try writer.writeByte(wasm.opcode(.end));
534568
535 try writeVecSectionHeader(569 try writeVecSectionHeader(
536 file,570 file,
537 header_offset,571 header_offset,
538 .global,572 .global,
539 @intCast(u32, (try file.getPos()) - header_offset - header_size),573 @intCast(u32, (try file.getPos()) - header_offset - header_size),
540 @as(u32, 1),574 @intCast(u32, self.globals.items.len),
541 );575 );
542 }576 }
543577
...@@ -546,6 +580,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -546,6 +580,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
546 const header_offset = try reserveVecSectionHeader(file);580 const header_offset = try reserveVecSectionHeader(file);
547 const writer = file.writer();581 const writer = file.writer();
548 var count: u32 = 0;582 var count: u32 = 0;
583 var func_index: u32 = 0;
549 for (module.decl_exports.values()) |exports| {584 for (module.decl_exports.values()) |exports| {
550 for (exports) |exprt| {585 for (exports) |exprt| {
551 // Export name length + name586 // Export name length + name
...@@ -557,7 +592,8 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -557,7 +592,8 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
557 // Type of the export592 // Type of the export
558 try writer.writeByte(wasm.externalKind(.function));593 try writer.writeByte(wasm.externalKind(.function));
559 // Exported function index594 // Exported function index
560 try leb.writeULEB128(writer, self.getFuncidx(exprt.exported_decl).?);595 try leb.writeULEB128(writer, func_index);
596 func_index += 1;
561 },597 },
562 else => return error.TODOImplementNonFnDeclsForWasm,598 else => return error.TODOImplementNonFnDeclsForWasm,
563 }599 }
...@@ -585,75 +621,108 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {...@@ -585,75 +621,108 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
585 }621 }
586622
587 // Code section623 // Code section
588 {624 if (self.code_section_index) |code_index| {
589 const header_offset = try reserveVecSectionHeader(file);625 const header_offset = try reserveVecSectionHeader(file);
590 const writer = file.writer();626 const writer = file.writer();
591 for (self.funcs.items) |decl| {627 var atom = self.atoms.get(code_index).?.getFirst();
592 const fn_data = &decl.fn_link.wasm;628 while (true) {
593629 try leb.writeULEB128(writer, atom.size);
594 // Write the already generated code to the file, inserting630 try writer.writeAll(atom.code.items);
595 // function indexes where required.631
596 for (fn_data.idx_refs.items) |idx_ref| {632 atom = atom.next orelse break;
597 const relocatable_decl = self.symbols.items[idx_ref.decl];
598 const index = self.getFuncidx(relocatable_decl).?;
599 leb.writeUnsignedFixed(5, fn_data.code.items[idx_ref.offset..][0..5], index);
600 }
601 try writer.writeAll(fn_data.code.items);
602 }633 }
603 try writeVecSectionHeader(634 try writeVecSectionHeader(
604 file,635 file,
605 header_offset,636 header_offset,
606 .code,637 .code,
607 @intCast(u32, (try file.getPos()) - header_offset - header_size),638 @intCast(u32, (try file.getPos()) - header_offset - header_size),
608 @intCast(u32, self.funcs.items.len),639 @intCast(u32, self.functions.items.len),
609 );640 );
610 }641 }
611642
612 // Data section643 // Data section
613 if (data_size != 0) {644 if (self.data_segments.count() != 0) {
614 const header_offset = try reserveVecSectionHeader(file);645 const header_offset = try reserveVecSectionHeader(file);
615 const writer = file.writer();646 const writer = file.writer();
616 // index to memory section (currently, there can only be 1 memory section in wasm)
617 try leb.writeULEB128(writer, @as(u32, 0));
618
619 // offset into data section
620 try writer.writeByte(wasm.opcode(.i32_const));
621 try leb.writeILEB128(writer, @as(i32, 0));
622 try writer.writeByte(wasm.opcode(.end));
623
624 const total_size = offset_table_size + data_size;
625647
626 // offset table + data size648 var it = self.data_segments.iterator();
627 try leb.writeULEB128(writer, total_size);649 while (it.next()) |entry| {
628650 // do not output 'bss' section
629 // fill in the offset table and the data segments651 if (std.mem.eql(u8, entry.key_ptr.*, ".bss")) continue;
630 const file_offset = try file.getPos();652 const atom_index = entry.value_ptr.*;
631 var cur = first_decl;653 var atom = self.atoms.getPtr(atom_index).?.*.getFirst();
632 var data_offset = offset_table_size;654 var segment = self.segments.items[atom_index];
633 while (cur) |cur_block| : (cur = cur_block.next) {655
634 if (cur_block.size == 0) continue;656 // flag and index to memory section (currently, there can only be 1 memory section in wasm)
635 assert(cur_block.init);657 try leb.writeULEB128(writer, @as(u32, 0));
636658
637 const offset = (cur_block.offset_index) * ptr_width;659 // offset into data section
638 var buf: [4]u8 = undefined;660 try writer.writeByte(wasm.opcode(.i32_const));
639 std.mem.writeIntLittle(u32, &buf, data_offset);661 try leb.writeILEB128(writer, @as(i32, 0));
640662 try writer.writeByte(wasm.opcode(.end));
641 try file.pwriteAll(&buf, file_offset + offset);663
642 try file.pwriteAll(cur_block.data[0..cur_block.size], file_offset + data_offset);664 // offset table + data size
643 data_offset += cur_block.size;665 try leb.writeULEB128(writer, segment.size);
666
667 // fill in the offset table and the data segments
668 var current_offset: u32 = 0;
669 while (true) {
670 std.debug.assert(current_offset == atom.offset);
671 std.debug.assert(atom.code.items.len == atom.size);
672
673 try writer.writeAll(atom.code.items);
674
675 current_offset += atom.size;
676 if (atom.next) |next| {
677 atom = next;
678 } else break;
679 }
644 }680 }
645681
646 try file.seekTo(file_offset + data_offset);
647 try writeVecSectionHeader(682 try writeVecSectionHeader(
648 file,683 file,
649 header_offset,684 header_offset,
650 .data,685 .data,
651 @intCast(u32, (file_offset + data_offset) - header_offset - header_size),686 @intCast(u32, (try file.getPos()) - header_offset - header_size),
652 @intCast(u32, 1), // only 1 data section687 @intCast(u32, 1), // only 1 data section
653 );688 );
654 }689 }
655}690}
656691
692fn emitLimits(writer: anytype, limits: wasm.Limits) !void {
693 try leb.writeULEB128(writer, @boolToInt(limits.max != null));
694 try leb.writeULEB128(writer, limits.min);
695 if (limits.max) |max| {
696 try leb.writeULEB128(writer, max);
697 }
698}
699
700fn emitInit(writer: anytype, init_expr: wasm.InitExpression) !void {
701 switch (init_expr) {
702 .i32_const => |val| {
703 try writer.writeByte(wasm.opcode(.i32_const));
704 try leb.writeILEB128(writer, val);
705 },
706 .i64_const => |val| {
707 try writer.writeByte(wasm.opcode(.i64_const));
708 try leb.writeILEB128(writer, val);
709 },
710 .f32_const => |val| {
711 try writer.writeByte(wasm.opcode(.f32_const));
712 try writer.writeIntLittle(u32, @bitCast(u32, val));
713 },
714 .f64_const => |val| {
715 try writer.writeByte(wasm.opcode(.f64_const));
716 try writer.writeIntLittle(u64, @bitCast(u64, val));
717 },
718 .global_get => |val| {
719 try writer.writeByte(wasm.opcode(.global_get));
720 try leb.writeULEB128(writer, val);
721 },
722 }
723 try writer.writeByte(wasm.opcode(.end));
724}
725
657fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {726fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
658 const tracy = trace(@src());727 const tracy = trace(@src());
659 defer tracy.end();728 defer tracy.end();
...@@ -970,32 +1039,6 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {...@@ -970,32 +1039,6 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void {
970 }1039 }
971}1040}
9721041
973/// Get the current index of a given Decl in the function list
974/// This will correctly provide the index, regardless whether the function is extern or not
975/// TODO: we could maintain a hash map to potentially make this simpler
976fn getFuncidx(self: Wasm, decl: *Module.Decl) ?u32 {
977 var offset: u32 = 0;
978 const slice = switch (decl.val.tag()) {
979 .function => blk: {
980 // when the target is a regular function, we have to calculate
981 // the offset of where the index starts
982 offset += self.getFuncIdxOffset();
983 break :blk self.funcs.items;
984 },
985 .extern_fn => self.ext_funcs.items,
986 else => return null,
987 };
988 return for (slice) |func, idx| {
989 if (func == decl) break @intCast(u32, offset + idx);
990 } else null;
991}
992
993/// Based on the size of `ext_funcs` returns the
994/// offset of the function indices
995fn getFuncIdxOffset(self: Wasm) u32 {
996 return @intCast(u32, self.ext_funcs.items.len);
997}
998
999fn reserveVecSectionHeader(file: fs.File) !u64 {1042fn reserveVecSectionHeader(file: fs.File) !u64 {
1000 // section id + fixed leb contents size + fixed leb vector length1043 // section id + fixed leb contents size + fixed leb vector length
1001 const header_size = 1 + 5 + 5;1044 const header_size = 1 + 5 + 5;
...@@ -1012,3 +1055,36 @@ fn writeVecSectionHeader(file: fs.File, offset: u64, section: wasm.Section, size...@@ -1012,3 +1055,36 @@ fn writeVecSectionHeader(file: fs.File, offset: u64, section: wasm.Section, size
1012 leb.writeUnsignedFixed(5, buf[6..], items);1055 leb.writeUnsignedFixed(5, buf[6..], items);
1013 try file.pwriteAll(&buf, offset);1056 try file.pwriteAll(&buf, offset);
1014}1057}
1058
1059/// Searches for an a matching function signature, when not found
1060/// a new entry will be made. The index of the existing/new signature will be returned.
1061pub fn putOrGetFuncType(self: *Wasm, func_type: wasm.Type) !u32 {
1062 var index: u32 = 0;
1063 while (index < self.func_types.items.len) : (index += 1) {
1064 if (self.func_types.items[index].eql(func_type)) return index;
1065 }
1066
1067 // functype does not exist.
1068 const params = try self.base.allocator.dupe(wasm.Valtype, func_type.params);
1069 errdefer self.base.allocator.free(params);
1070 const returns = try self.base.allocator.dupe(wasm.Valtype, func_type.returns);
1071 errdefer self.base.allocator.free(returns);
1072 try self.func_types.append(self.base.allocator, .{
1073 .params = params,
1074 .returns = returns,
1075 });
1076 return index;
1077}
1078
1079/// From a given index and an `ExternalKind`, finds the corresponding Import.
1080/// This is due to indexes for imports being unique per type, rather than across all imports.
1081fn findImport(self: Wasm, index: u32, external_type: wasm.ExternalKind) ?*wasm.Import {
1082 var current_index: u32 = 0;
1083 for (self.imports.items) |*import| {
1084 if (import.kind == external_type) {
1085 if (current_index == index) return import;
1086 current_index += 1;
1087 }
1088 }
1089 return null;
1090}
src/link/Wasm/Atom.zig created+182
...@@ -0,0 +1,182 @@
1const Atom = @This();
2
3const std = @import("std");
4const types = @import("types.zig");
5const Wasm = @import("../Wasm.zig");
6const Symbol = @import("Symbol.zig");
7
8const leb = std.leb;
9const log = std.log.scoped(.zld);
10const mem = std.mem;
11const Allocator = mem.Allocator;
12
13/// symbol index of the symbol representing this atom
14sym_index: u32,
15/// Size of the atom, used to calculate section sizes in the final binary
16size: u32,
17/// List of relocations belonging to this atom
18relocs: std.ArrayListUnmanaged(types.Relocation) = .{},
19/// Contains the binary data of an atom, which can be non-relocated
20code: std.ArrayListUnmanaged(u8) = .{},
21/// For code this is 1, for data this is set to the highest value of all segments
22alignment: u32,
23/// Offset into the section where the atom lives, this already accounts
24/// for alignment.
25offset: u32,
26
27/// Next atom in relation to this atom.
28/// When null, this atom is the last atom
29next: ?*Atom,
30/// Previous atom in relation to this atom.
31/// is null when this atom is the first in its order
32prev: ?*Atom,
33
34/// Represents a default empty wasm `Atom`
35pub const empty: Atom = .{
36 .alignment = 0,
37 .next = null,
38 .offset = 0,
39 .prev = null,
40 .size = 0,
41 .sym_index = 0,
42};
43
44/// Frees all resources owned by this `Atom`.
45/// Also destroys itself, making any usage of this atom illegal.
46pub fn deinit(self: *Atom, gpa: *Allocator) void {
47 self.relocs.deinit(gpa);
48 self.code.deinit(gpa);
49}
50
51pub fn format(self: Atom, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
52 _ = fmt;
53 _ = options;
54 writer.print("Atom{{ .sym_index = {d}, .alignment = {d}, .size = {d}, .offset = 0x{x:0>8} }}", .{
55 self.sym_index,
56 self.alignment,
57 self.size,
58 self.offset,
59 });
60}
61
62/// Returns the first `Atom` from a given atom
63pub fn getFirst(self: *Atom) *Atom {
64 var tmp = self;
65 while (tmp.prev) |prev| tmp = prev;
66 return tmp;
67}
68
69/// Returns the last `Atom` from a given atom
70pub fn getLast(self: *Atom) *Atom {
71 var tmp = self;
72 while (tmp.next) |next| tmp = next;
73 return tmp;
74}
75/// Unplugs the `Atom` from the chain
76pub fn unplug(self: *Atom) void {
77 if (self.prev) |prev| {
78 prev.next = self.next;
79 }
80
81 if (self.next) |next| {
82 next.prev = self.prev;
83 }
84 self.next = null;
85 self.prev = null;
86}
87
88/// Resolves the relocations within the atom, writing the new value
89/// at the calculated offset.
90pub fn resolveRelocs(self: *Atom, wasm_bin: *const Wasm) !void {
91 const object = wasm_bin.objects.items[self.file];
92 const symbol: Symbol = object.symtable[self.sym_index];
93
94 log.debug("Resolving relocs in atom '{s}' count({d})", .{
95 symbol.name,
96 self.relocs.items.len,
97 });
98
99 for (self.relocs.items) |reloc| {
100 const value = self.relocationValue(reloc, wasm_bin);
101 log.debug("Relocating '{s}' referenced in '{s}' offset=0x{x:0>8} value={d}", .{
102 object.symtable[reloc.index].name,
103 symbol.name,
104 reloc.offset,
105 value,
106 });
107
108 switch (reloc.relocation_type) {
109 .R_WASM_TABLE_INDEX_I32,
110 .R_WASM_FUNCTION_OFFSET_I32,
111 .R_WASM_GLOBAL_INDEX_I32,
112 .R_WASM_MEMORY_ADDR_I32,
113 .R_WASM_SECTION_OFFSET_I32,
114 => std.mem.writeIntLittle(u32, self.code.items[reloc.offset..][0..4], @intCast(u32, value)),
115 .R_WASM_TABLE_INDEX_I64,
116 .R_WASM_MEMORY_ADDR_I64,
117 => std.mem.writeIntLittle(u64, self.code.items[reloc.offset..][0..8], value),
118 .R_WASM_GLOBAL_INDEX_LEB,
119 .R_WASM_EVENT_INDEX_LEB,
120 .R_WASM_FUNCTION_INDEX_LEB,
121 .R_WASM_MEMORY_ADDR_LEB,
122 .R_WASM_MEMORY_ADDR_SLEB,
123 .R_WASM_TABLE_INDEX_SLEB,
124 .R_WASM_TABLE_NUMBER_LEB,
125 .R_WASM_TYPE_INDEX_LEB,
126 => leb.writeUnsignedFixed(5, self.code.items[reloc.offset..][0..5], @intCast(u32, value)),
127 .R_WASM_MEMORY_ADDR_LEB64,
128 .R_WASM_MEMORY_ADDR_SLEB64,
129 .R_WASM_TABLE_INDEX_SLEB64,
130 => leb.writeUnsignedFixed(10, self.code.items[reloc.offset..][0..10], value),
131 }
132 }
133}
134
135/// From a given `relocation` will return the new value to be written.
136/// All values will be represented as a `u64` as all values can fit within it.
137/// The final value must be casted to the correct size.
138fn relocationValue(self: *Atom, relocation: types.Relocation, wasm_bin: *const Wasm) u64 {
139 const object = wasm_bin.objects.items[self.file];
140 const symbol: Symbol = object.symtable[relocation.index];
141 return switch (relocation.relocation_type) {
142 .R_WASM_FUNCTION_INDEX_LEB => symbol.kind.function.functionIndex(),
143 .R_WASM_TABLE_NUMBER_LEB => symbol.kind.table.table.table_idx,
144 .R_WASM_TABLE_INDEX_I32,
145 .R_WASM_TABLE_INDEX_I64,
146 .R_WASM_TABLE_INDEX_SLEB,
147 .R_WASM_TABLE_INDEX_SLEB64,
148 => symbol.getTableIndex() orelse 0,
149 .R_WASM_TYPE_INDEX_LEB => symbol.kind.function.func.type_idx,
150 .R_WASM_GLOBAL_INDEX_I32,
151 .R_WASM_GLOBAL_INDEX_LEB,
152 => symbol.kind.global.global.global_idx,
153 .R_WASM_MEMORY_ADDR_I32,
154 .R_WASM_MEMORY_ADDR_I64,
155 .R_WASM_MEMORY_ADDR_LEB,
156 .R_WASM_MEMORY_ADDR_LEB64,
157 .R_WASM_MEMORY_ADDR_SLEB,
158 .R_WASM_MEMORY_ADDR_SLEB64,
159 => blk: {
160 if (symbol.isUndefined() and (symbol.kind == .data or symbol.isWeak())) {
161 return 0;
162 }
163 const segment_name = object.segment_info[symbol.index().?].outputName();
164 const atom_index = wasm_bin.data_segments.get(segment_name).?;
165 var target_atom = wasm_bin.atoms.getPtr(atom_index).?.*.getFirst();
166 while (true) {
167 if (target_atom.sym_index == relocation.index) break;
168 if (target_atom.next) |next| {
169 target_atom = next;
170 } else break;
171 }
172 const segment = wasm_bin.segments.items[atom_index];
173 const base = wasm_bin.options.global_base orelse 1024;
174 const offset = target_atom.offset + segment.offset;
175 break :blk offset + base + (relocation.addend orelse 0);
176 },
177 .R_WASM_EVENT_INDEX_LEB => symbol.kind.event.index,
178 .R_WASM_SECTION_OFFSET_I32,
179 .R_WASM_FUNCTION_OFFSET_I32,
180 => relocation.offset,
181 };
182}
src/link/Wasm/Symbol.zig created+157
...@@ -0,0 +1,157 @@
1//! Wasm symbols describing its kind,
2//! name and its properties.
3const Symbol = @This();
4
5const std = @import("std");
6const types = @import("types.zig");
7
8/// Bitfield containings flags for a symbol
9/// Can contain any of the flags defined in `Flag`
10flags: u32,
11/// Symbol name, when undefined this will be taken from the import.
12name: [*:0]const u8,
13/// An union that represents both the type of symbol
14/// as well as the data it holds.
15tag: Tag,
16/// Index into the list of objects based on set `tag`
17/// NOTE: This will be set to `undefined` when `tag` is `data`
18/// and the symbol is undefined.
19index: u32,
20
21pub const Tag = enum {
22 function,
23 data,
24 global,
25 section,
26 event,
27 table,
28
29 /// From a given symbol tag, returns the `ExternalType`
30 /// Asserts the given tag can be represented as an external type.
31 pub fn externalType(self: Tag) std.wasm.ExternalKind {
32 return switch (self) {
33 .function => .function,
34 .global => .global,
35 .data => .memory,
36 .section => unreachable, // Not an external type
37 .event => unreachable, // Not an external type
38 .table => .table,
39 };
40 }
41};
42
43pub const Flag = enum(u32) {
44 /// Indicates a weak symbol.
45 /// When linking multiple modules defining the same symbol, all weak definitions are discarded
46 /// in favourite of the strong definition. When no strong definition exists, all weak but one definiton is discarded.
47 /// If multiple definitions remain, we get an error: symbol collision.
48 WASM_SYM_BINDING_WEAK = 0x1,
49 /// Indicates a local, non-exported, non-module-linked symbol.
50 /// The names of local symbols are not required to be unique, unlike non-local symbols.
51 WASM_SYM_BINDING_LOCAL = 0x2,
52 /// Represents the binding of a symbol, indicating if it's local or not, and weak or not.
53 WASM_SYM_BINDING_MASK = 0x3,
54 /// Indicates a hidden symbol. Hidden symbols will not be exported to the link result, but may
55 /// link to other modules.
56 WASM_SYM_VISIBILITY_HIDDEN = 0x4,
57 /// Indicates an undefined symbol. For non-data symbols, this must match whether the symbol is
58 /// an import or is defined. For data symbols however, determines whether a segment is specified.
59 WASM_SYM_UNDEFINED = 0x10,
60 /// Indicates a symbol of which its intention is to be exported from the wasm module to the host environment.
61 /// This differs from the visibility flag as this flag affects the static linker.
62 WASM_SYM_EXPORTED = 0x20,
63 /// Indicates the symbol uses an explicit symbol name, rather than reusing the name from a wasm import.
64 /// Allows remapping imports from foreign WASM modules into local symbols with a different name.
65 WASM_SYM_EXPLICIT_NAME = 0x40,
66 /// Indicates the symbol is to be included in the linker output, regardless of whether it is used or has any references to it.
67 WASM_SYM_NO_STRIP = 0x80,
68 /// Indicates a symbol is TLS
69 WASM_SYM_TLS = 0x100,
70};
71
72/// Verifies if the given symbol should be imported from the
73/// host environment or not
74pub fn requiresImport(self: Symbol) bool {
75 if (!self.isUndefined()) return false;
76 if (self.isWeak()) return false;
77 if (self.kind == .data) return false;
78 // if (self.isDefined() and self.isWeak()) return true; //TODO: Only when building shared lib
79
80 return true;
81}
82
83pub fn hasFlag(self: Symbol, flag: Flag) bool {
84 return self.flags & @enumToInt(flag) != 0;
85}
86
87pub fn setFlag(self: *Symbol, flag: Flag) void {
88 self.flags |= @enumToInt(flag);
89}
90
91pub fn isUndefined(self: Symbol) bool {
92 return self.flags & @enumToInt(Flag.WASM_SYM_UNDEFINED) != 0;
93}
94
95pub fn setUndefined(self: *Symbol, is_undefined: bool) void {
96 if (is_undefined) {
97 self.setFlag(.WASM_SYM_UNDEFINED);
98 } else {
99 self.flags &= ~@enumToInt(Flag.WASM_SYM_UNDEFINED);
100 }
101}
102
103pub fn isDefined(self: Symbol) bool {
104 return !self.isUndefined();
105}
106
107pub fn isVisible(self: Symbol) bool {
108 return self.flags & @enumToInt(Flag.WASM_SYM_VISIBILITY_HIDDEN) == 0;
109}
110
111pub fn isLocal(self: Symbol) bool {
112 return self.flags & @enumToInt(Flag.WASM_SYM_BINDING_LOCAL) != 0;
113}
114
115pub fn isGlobal(self: Symbol) bool {
116 return self.flags & @enumToInt(Flag.WASM_SYM_BINDING_LOCAL) == 0;
117}
118
119pub fn isHidden(self: Symbol) bool {
120 return self.flags & @enumToInt(Flag.WASM_SYM_VISIBILITY_HIDDEN) != 0;
121}
122
123pub fn isNoStrip(self: Symbol) bool {
124 return self.flags & @enumToInt(Flag.WASM_SYM_NO_STRIP) != 0;
125}
126
127pub fn isExported(self: Symbol) bool {
128 if (self.isUndefined() or self.isLocal()) return false;
129 if (self.isHidden()) return false;
130 return true;
131}
132
133pub fn isWeak(self: Symbol) bool {
134 return self.flags & @enumToInt(Flag.WASM_SYM_BINDING_WEAK) != 0;
135}
136
137/// Formats the symbol into human-readable text
138pub fn format(self: Symbol, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
139 _ = fmt;
140 _ = options;
141
142 const kind_fmt: u8 = switch (self.kind) {
143 .function => 'F',
144 .data => 'D',
145 .global => 'G',
146 .section => 'S',
147 .event => 'E',
148 .table => 'T',
149 };
150 const visible: []const u8 = if (self.isVisible()) "yes" else "no";
151 const binding: []const u8 = if (self.isLocal()) "local" else "global";
152
153 try writer.print(
154 "{c} binding={s} visible={s} id={d} name={s}",
155 .{ kind_fmt, binding, visible, self.index(), self.name },
156 );
157}
src/link/Wasm/types.zig created+199
...@@ -0,0 +1,199 @@
1//! This file contains all constants and related to wasm's object format.
2
3const std = @import("std");
4
5pub const Relocation = struct {
6 /// Represents the type of the `Relocation`
7 relocation_type: RelocationType,
8 /// Offset of the value to rewrite relative to the relevant section's contents.
9 /// When `offset` is zero, its position is immediately after the id and size of the section.
10 offset: u32,
11 /// The index of the symbol used.
12 /// When the type is `R_WASM_TYPE_INDEX_LEB`, it represents the index of the type.
13 index: u32,
14 /// Addend to add to the address.
15 /// This field is only non-null for `R_WASM_MEMORY_ADDR_*`, `R_WASM_FUNCTION_OFFSET_I32` and `R_WASM_SECTION_OFFSET_I32`.
16 addend: ?u32 = null,
17
18 /// All possible relocation types currently existing.
19 /// This enum is exhaustive as the spec is WIP and new types
20 /// can be added which means that a generated binary will be invalid,
21 /// so instead we will show an error in such cases.
22 pub const RelocationType = enum(u8) {
23 R_WASM_FUNCTION_INDEX_LEB = 0,
24 R_WASM_TABLE_INDEX_SLEB = 1,
25 R_WASM_TABLE_INDEX_I32 = 2,
26 R_WASM_MEMORY_ADDR_LEB = 3,
27 R_WASM_MEMORY_ADDR_SLEB = 4,
28 R_WASM_MEMORY_ADDR_I32 = 5,
29 R_WASM_TYPE_INDEX_LEB = 6,
30 R_WASM_GLOBAL_INDEX_LEB = 7,
31 R_WASM_FUNCTION_OFFSET_I32 = 8,
32 R_WASM_SECTION_OFFSET_I32 = 9,
33 R_WASM_EVENT_INDEX_LEB = 10,
34 R_WASM_GLOBAL_INDEX_I32 = 13,
35 R_WASM_MEMORY_ADDR_LEB64 = 14,
36 R_WASM_MEMORY_ADDR_SLEB64 = 15,
37 R_WASM_MEMORY_ADDR_I64 = 16,
38 R_WASM_TABLE_INDEX_SLEB64 = 18,
39 R_WASM_TABLE_INDEX_I64 = 19,
40 R_WASM_TABLE_NUMBER_LEB = 20,
41
42 /// Returns true for relocation types where the `addend` field is present.
43 pub fn addendIsPresent(self: RelocationType) bool {
44 return switch (self) {
45 .R_WASM_MEMORY_ADDR_LEB,
46 .R_WASM_MEMORY_ADDR_SLEB,
47 .R_WASM_MEMORY_ADDR_I32,
48 .R_WASM_MEMORY_ADDR_LEB64,
49 .R_WASM_MEMORY_ADDR_SLEB64,
50 .R_WASM_MEMORY_ADDR_I64,
51 .R_WASM_FUNCTION_OFFSET_I32,
52 .R_WASM_SECTION_OFFSET_I32,
53 => true,
54 else => false,
55 };
56 }
57 };
58
59 /// Verifies the relocation type of a given `Relocation` and returns
60 /// true when the relocation references a function call or address to a function.
61 pub fn isFunction(self: Relocation) bool {
62 return switch (self.relocation_type) {
63 .R_WASM_FUNCTION_INDEX_LEB,
64 .R_WASM_TABLE_INDEX_SLEB,
65 => true,
66 else => false,
67 };
68 }
69
70 pub fn format(self: Relocation, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
71 _ = fmt;
72 _ = options;
73 try writer.print("{s} offset=0x{x:0>6} symbol={d}", .{
74 @tagName(self.relocation_type),
75 self.offset,
76 self.index,
77 });
78 }
79};
80
81pub const SubsectionType = enum(u8) {
82 WASM_SEGMENT_INFO = 5,
83 WASM_INIT_FUNCS = 6,
84 WASM_COMDAT_INFO = 7,
85 WASM_SYMBOL_TABLE = 8,
86};
87
88pub const Segment = struct {
89 /// Segment's name, encoded as UTF-8 bytes.
90 name: []const u8,
91 /// The required alignment of the segment, encoded as a power of 2
92 alignment: u32,
93 /// Bitfield containing flags for a segment
94 flags: u32,
95
96 pub fn outputName(self: Segment) []const u8 {
97 if (std.mem.startsWith(u8, self.name, ".rodata.")) {
98 return ".rodata";
99 } else if (std.mem.startsWith(u8, self.name, ".text.")) {
100 return ".text";
101 } else if (std.mem.startsWith(u8, self.name, ".rodata.")) {
102 return ".rodata";
103 } else if (std.mem.startsWith(u8, self.name, ".data.")) {
104 return ".data";
105 } else if (std.mem.startsWith(u8, self.name, ".bss.")) {
106 return ".bss";
107 }
108 return self.name;
109 }
110};
111
112pub const InitFunc = struct {
113 /// Priority of the init function
114 priority: u32,
115 /// The symbol index of init function (not the function index).
116 symbol_index: u32,
117};
118
119pub const Comdat = struct {
120 name: []const u8,
121 /// Must be zero, no flags are currently defined by the tool-convention.
122 flags: u32,
123 symbols: []const ComdatSym,
124};
125
126pub const ComdatSym = struct {
127 kind: Type,
128 /// Index of the data segment/function/global/event/table within a WASM module.
129 /// The object must not be an import.
130 index: u32,
131
132 pub const Type = enum(u8) {
133 WASM_COMDAT_DATA = 0,
134 WASM_COMDAT_FUNCTION = 1,
135 WASM_COMDAT_GLOBAL = 2,
136 WASM_COMDAT_EVENT = 3,
137 WASM_COMDAT_TABLE = 4,
138 WASM_COMDAT_SECTION = 5,
139 };
140};
141
142pub const Feature = struct {
143 /// Provides information about the usage of the feature.
144 /// - '0x2b' (+): Object uses this feature, and the link fails if feature is not in the allowed set.
145 /// - '0x2d' (-): Object does not use this feature, and the link fails if this feature is in the allowed set.
146 /// - '0x3d' (=): Object uses this feature, and the link fails if this feature is not in the allowed set,
147 /// or if any object does not use this feature.
148 prefix: Prefix,
149 /// Type of the feature, must be unique in the sequence of features.
150 tag: Tag,
151
152 pub const Tag = enum {
153 atomics,
154 bulk_memory,
155 exception_handling,
156 multivalue,
157 mutable_globals,
158 nontrapping_fptoint,
159 sign_ext,
160 simd128,
161 tail_call,
162 };
163
164 pub const Prefix = enum(u8) {
165 used = '+',
166 disallowed = '-',
167 required = '=',
168 };
169
170 pub fn toString(self: Feature) []const u8 {
171 return switch (self.tag) {
172 .bulk_memory => "bulk-memory",
173 .exception_handling => "exception-handling",
174 .mutable_globals => "mutable-globals",
175 .nontrapping_fptoint => "nontrapping-fptoint",
176 .sign_ext => "sign-ext",
177 .tail_call => "tail-call",
178 else => @tagName(self),
179 };
180 }
181
182 pub fn format(self: Feature, comptime fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void {
183 _ = opt;
184 _ = fmt;
185 try writer.print("{c} {s}", .{ self.prefix, self.toString() });
186 }
187};
188
189pub const known_features = std.ComptimeStringMap(Feature.Tag, .{
190 .{ "atomics", .atomics },
191 .{ "bulk-memory", .bulk_memory },
192 .{ "exception-handling", .exception_handling },
193 .{ "multivalue", .multivalue },
194 .{ "mutable-globals", .mutable_globals },
195 .{ "nontrapping-fptoint", .nontrapping_fptoint },
196 .{ "sign-ext", .sign_ext },
197 .{ "simd128", .simd128 },
198 .{ "tail-call", .tail_call },
199});