mirror of
https://github.com/ziglang/zig.git
synced 2025-12-28 09:03:21 +00:00
Merge pull request #10229 from Luukdegram/wasm-linker
stage2: Upstream zwld (partly) into wasm backend
This commit is contained in:
commit
aa61e03f24
142
lib/std/wasm.zig
142
lib/std/wasm.zig
@ -1,4 +1,8 @@
|
||||
const testing = @import("std.zig").testing;
|
||||
///! Contains all constants and types representing the wasm
|
||||
///! binary format, as specified by:
|
||||
///! https://webassembly.github.io/spec/core/
|
||||
const std = @import("std.zig");
|
||||
const testing = std.testing;
|
||||
|
||||
// TODO: Add support for multi-byte ops (e.g. table operations)
|
||||
|
||||
@ -222,6 +226,18 @@ pub fn valtype(value: Valtype) u8 {
|
||||
return @enumToInt(value);
|
||||
}
|
||||
|
||||
/// Reference types, where the funcref references to a function regardless of its type
|
||||
/// and ref references an object from the embedder.
|
||||
pub const RefType = enum(u8) {
|
||||
funcref = 0x70,
|
||||
externref = 0x6F,
|
||||
};
|
||||
|
||||
/// Returns the integer value of a `Reftype`
|
||||
pub fn reftype(value: RefType) u8 {
|
||||
return @enumToInt(value);
|
||||
}
|
||||
|
||||
test "Wasm - valtypes" {
|
||||
const _i32 = valtype(.i32);
|
||||
const _i64 = valtype(.i64);
|
||||
@ -234,6 +250,124 @@ test "Wasm - valtypes" {
|
||||
try testing.expectEqual(@as(u8, 0x7C), _f64);
|
||||
}
|
||||
|
||||
/// Limits classify the size range of resizeable storage associated with memory types and table types.
|
||||
pub const Limits = struct {
|
||||
min: u32,
|
||||
max: ?u32,
|
||||
};
|
||||
|
||||
/// Initialization expressions are used to set the initial value on an object
|
||||
/// when a wasm module is being loaded.
|
||||
pub const InitExpression = union(enum) {
|
||||
i32_const: i32,
|
||||
i64_const: i64,
|
||||
f32_const: f32,
|
||||
f64_const: f64,
|
||||
global_get: u32,
|
||||
};
|
||||
|
||||
///
|
||||
pub const Func = struct {
|
||||
type_index: u32,
|
||||
};
|
||||
|
||||
/// Tables are used to hold pointers to opaque objects.
|
||||
/// This can either by any function, or an object from the host.
|
||||
pub const Table = struct {
|
||||
limits: Limits,
|
||||
reftype: RefType,
|
||||
};
|
||||
|
||||
/// Describes the layout of the memory where `min` represents
|
||||
/// the minimal amount of pages, and the optional `max` represents
|
||||
/// the max pages. When `null` will allow the host to determine the
|
||||
/// amount of pages.
|
||||
pub const Memory = struct {
|
||||
limits: Limits,
|
||||
};
|
||||
|
||||
/// Represents the type of a `Global` or an imported global.
|
||||
pub const GlobalType = struct {
|
||||
valtype: Valtype,
|
||||
mutable: bool,
|
||||
};
|
||||
|
||||
pub const Global = struct {
|
||||
global_type: GlobalType,
|
||||
init: InitExpression,
|
||||
};
|
||||
|
||||
/// Notates an object to be exported from wasm
|
||||
/// to the host.
|
||||
pub const Export = struct {
|
||||
name: []const u8,
|
||||
kind: ExternalKind,
|
||||
index: u32,
|
||||
};
|
||||
|
||||
/// Element describes the layout of the table that can
|
||||
/// be found at `table_index`
|
||||
pub const Element = struct {
|
||||
table_index: u32,
|
||||
offset: InitExpression,
|
||||
func_indexes: []const u32,
|
||||
};
|
||||
|
||||
/// Imports are used to import objects from the host
|
||||
pub const Import = struct {
|
||||
module_name: []const u8,
|
||||
name: []const u8,
|
||||
kind: Kind,
|
||||
|
||||
pub const Kind = union(ExternalKind) {
|
||||
function: u32,
|
||||
table: Table,
|
||||
memory: Limits,
|
||||
global: GlobalType,
|
||||
};
|
||||
};
|
||||
|
||||
/// `Type` represents a function signature type containing both
|
||||
/// a slice of parameters as well as a slice of return values.
|
||||
pub const Type = struct {
|
||||
params: []const Valtype,
|
||||
returns: []const Valtype,
|
||||
|
||||
pub fn format(self: Type, comptime fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
_ = fmt;
|
||||
_ = opt;
|
||||
try writer.writeByte('(');
|
||||
for (self.params) |param, i| {
|
||||
try writer.print("{s}", .{@tagName(param)});
|
||||
if (i + 1 != self.params.len) {
|
||||
try writer.writeAll(", ");
|
||||
}
|
||||
}
|
||||
try writer.writeAll(") -> ");
|
||||
if (self.returns.len == 0) {
|
||||
try writer.writeAll("nil");
|
||||
} else {
|
||||
for (self.returns) |return_ty, i| {
|
||||
try writer.print("{s}", .{@tagName(return_ty)});
|
||||
if (i + 1 != self.returns.len) {
|
||||
try writer.writeAll(", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eql(self: Type, other: Type) bool {
|
||||
return std.mem.eql(Valtype, self.params, other.params) and
|
||||
std.mem.eql(Valtype, self.returns, other.returns);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Type, gpa: *std.mem.Allocator) void {
|
||||
gpa.free(self.params);
|
||||
gpa.free(self.returns);
|
||||
self.* = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
/// Wasm module sections as per spec:
|
||||
/// https://webassembly.github.io/spec/core/binary/modules.html
|
||||
pub const Section = enum(u8) {
|
||||
@ -249,6 +383,8 @@ pub const Section = enum(u8) {
|
||||
element,
|
||||
code,
|
||||
data,
|
||||
data_count,
|
||||
_,
|
||||
};
|
||||
|
||||
/// Returns the integer value of a given `Section`
|
||||
@ -270,7 +406,7 @@ pub fn externalKind(val: ExternalKind) u8 {
|
||||
return @enumToInt(val);
|
||||
}
|
||||
|
||||
// types
|
||||
// type constants
|
||||
pub const element_type: u8 = 0x70;
|
||||
pub const function_type: u8 = 0x60;
|
||||
pub const result_type: u8 = 0x40;
|
||||
@ -280,7 +416,7 @@ pub const block_empty: u8 = 0x40;
|
||||
|
||||
// binary constants
|
||||
pub const magic = [_]u8{ 0x00, 0x61, 0x73, 0x6D }; // \0asm
|
||||
pub const version = [_]u8{ 0x01, 0x00, 0x00, 0x00 }; // version 1
|
||||
pub const version = [_]u8{ 0x01, 0x00, 0x00, 0x00 }; // version 1 (MVP)
|
||||
|
||||
// Each wasm page size is 64kB
|
||||
pub const page_size = 64 * 1024;
|
||||
|
||||
@ -518,9 +518,6 @@ blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, struct {
|
||||
}) = .{},
|
||||
/// `bytes` contains the wasm bytecode belonging to the 'code' section.
|
||||
code: ArrayList(u8),
|
||||
/// Contains the generated function type bytecode for the current function
|
||||
/// found in `decl`
|
||||
func_type_data: ArrayList(u8),
|
||||
/// The index the next local generated will have
|
||||
/// NOTE: arguments share the index with locals therefore the first variable
|
||||
/// will have the index that comes after the last argument's index
|
||||
@ -539,7 +536,7 @@ locals: std.ArrayListUnmanaged(u8),
|
||||
/// The Target we're emitting (used to call intInfo)
|
||||
target: std.Target,
|
||||
/// Represents the wasm binary file that is being linked.
|
||||
bin_file: *link.File,
|
||||
bin_file: *link.File.Wasm,
|
||||
/// Table with the global error set. Consists of every error found in
|
||||
/// the compiled code. Each error name maps to a `Module.ErrorInt` which is emitted
|
||||
/// during codegen to determine the error value.
|
||||
@ -577,6 +574,7 @@ pub fn deinit(self: *Self) void {
|
||||
self.locals.deinit(self.gpa);
|
||||
self.mir_instructions.deinit(self.gpa);
|
||||
self.mir_extra.deinit(self.gpa);
|
||||
self.code.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
@ -734,43 +732,44 @@ fn allocLocal(self: *Self, ty: Type) InnerError!WValue {
|
||||
return WValue{ .local = initial_index };
|
||||
}
|
||||
|
||||
fn genFunctype(self: *Self) InnerError!void {
|
||||
assert(self.decl.has_tv);
|
||||
const ty = self.decl.ty;
|
||||
const writer = self.func_type_data.writer();
|
||||
|
||||
try writer.writeByte(wasm.function_type);
|
||||
/// Generates a `wasm.Type` from a given function type.
|
||||
/// Memory is owned by the caller.
|
||||
fn genFunctype(self: *Self, fn_ty: Type) !wasm.Type {
|
||||
var params = std.ArrayList(wasm.Valtype).init(self.gpa);
|
||||
defer params.deinit();
|
||||
var returns = std.ArrayList(wasm.Valtype).init(self.gpa);
|
||||
defer returns.deinit();
|
||||
|
||||
// param types
|
||||
try leb.writeULEB128(writer, @intCast(u32, ty.fnParamLen()));
|
||||
if (ty.fnParamLen() != 0) {
|
||||
const params = try self.gpa.alloc(Type, ty.fnParamLen());
|
||||
defer self.gpa.free(params);
|
||||
ty.fnParamTypes(params);
|
||||
for (params) |param_type| {
|
||||
// Can we maybe get the source index of each param?
|
||||
const val_type = try self.genValtype(param_type);
|
||||
try writer.writeByte(val_type);
|
||||
if (fn_ty.fnParamLen() != 0) {
|
||||
const fn_params = try self.gpa.alloc(Type, fn_ty.fnParamLen());
|
||||
defer self.gpa.free(fn_params);
|
||||
fn_ty.fnParamTypes(fn_params);
|
||||
for (fn_params) |param_type| {
|
||||
if (!param_type.hasCodeGenBits()) continue;
|
||||
try params.append(try self.typeToValtype(param_type));
|
||||
}
|
||||
}
|
||||
|
||||
// return type
|
||||
const return_type = ty.fnReturnType();
|
||||
const return_type = fn_ty.fnReturnType();
|
||||
switch (return_type.zigTypeTag()) {
|
||||
.Void, .NoReturn => try leb.writeULEB128(writer, @as(u32, 0)),
|
||||
.Void, .NoReturn => {},
|
||||
.Struct => return self.fail("TODO: Implement struct as return type for wasm", .{}),
|
||||
.Optional => return self.fail("TODO: Implement optionals as return type for wasm", .{}),
|
||||
else => {
|
||||
try leb.writeULEB128(writer, @as(u32, 1));
|
||||
const val_type = try self.genValtype(return_type);
|
||||
try writer.writeByte(val_type);
|
||||
},
|
||||
else => try returns.append(try self.typeToValtype(return_type)),
|
||||
}
|
||||
|
||||
return wasm.Type{
|
||||
.params = params.toOwnedSlice(),
|
||||
.returns = returns.toOwnedSlice(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn genFunc(self: *Self) InnerError!Result {
|
||||
try self.genFunctype();
|
||||
// TODO: check for and handle death of instructions
|
||||
var func_type = try self.genFunctype(self.decl.ty);
|
||||
defer func_type.deinit(self.gpa);
|
||||
self.decl.fn_link.wasm.type_index = try self.bin_file.putOrGetFuncType(func_type);
|
||||
|
||||
var cc_result = try self.resolveCallingConventionValues(self.decl.ty);
|
||||
defer cc_result.deinit(self.gpa);
|
||||
@ -791,7 +790,7 @@ pub fn genFunc(self: *Self) InnerError!Result {
|
||||
|
||||
var emit: Emit = .{
|
||||
.mir = mir,
|
||||
.bin_file = self.bin_file,
|
||||
.bin_file = &self.bin_file.base,
|
||||
.code = &self.code,
|
||||
.locals = self.locals.items,
|
||||
.decl = self.decl,
|
||||
@ -813,8 +812,10 @@ pub fn genFunc(self: *Self) InnerError!Result {
|
||||
pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result {
|
||||
switch (ty.zigTypeTag()) {
|
||||
.Fn => {
|
||||
try self.genFunctype();
|
||||
if (val.tag() == .extern_fn) {
|
||||
var func_type = try self.genFunctype(self.decl.ty);
|
||||
defer func_type.deinit(self.gpa);
|
||||
self.decl.fn_link.wasm.type_index = try self.bin_file.putOrGetFuncType(func_type);
|
||||
return Result.appended; // don't need code body for extern functions
|
||||
}
|
||||
return self.fail("TODO implement wasm codegen for function pointers", .{});
|
||||
@ -1079,7 +1080,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
|
||||
try self.emitWValue(arg_val);
|
||||
}
|
||||
|
||||
try self.addLabel(.call, target.link.wasm.symbol_index);
|
||||
try self.addLabel(.call, target.link.wasm.sym_index);
|
||||
|
||||
const ret_ty = target.ty.fnReturnType();
|
||||
switch (ret_ty.zigTypeTag()) {
|
||||
@ -1362,15 +1363,7 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void {
|
||||
if (val.castTag(.decl_ref)) |payload| {
|
||||
const decl = payload.data;
|
||||
decl.alive = true;
|
||||
|
||||
// offset into the offset table within the 'data' section
|
||||
const ptr_width = self.target.cpu.arch.ptrBitWidth() / 8;
|
||||
try self.addImm32(@bitCast(i32, decl.link.wasm.offset_index * ptr_width));
|
||||
|
||||
// memory instruction followed by their memarg immediate
|
||||
// memarg ::== x:u32, y:u32 => {align x, offset y}
|
||||
const extra_index = try self.addExtra(Mir.MemArg{ .offset = 0, .alignment = 4 });
|
||||
try self.addInst(.{ .tag = .i32_load, .data = .{ .payload = extra_index } });
|
||||
try self.addLabel(.memory_address, decl.link.wasm.sym_index);
|
||||
} else return self.fail("Wasm TODO: emitConstant for other const pointer tag {s}", .{val.tag()});
|
||||
},
|
||||
.Void => {},
|
||||
|
||||
@ -29,8 +29,6 @@ const InnerError = error{
|
||||
|
||||
pub fn emitMir(emit: *Emit) InnerError!void {
|
||||
const mir_tags = emit.mir.instructions.items(.tag);
|
||||
// Reserve space to write the size after generating the code.
|
||||
try emit.code.resize(5);
|
||||
// write the locals in the prologue of the function body
|
||||
// before we emit the function body when lowering MIR
|
||||
try emit.emitLocals();
|
||||
@ -51,6 +49,7 @@ pub fn emitMir(emit: *Emit) InnerError!void {
|
||||
.call => try emit.emitCall(inst),
|
||||
.global_get => try emit.emitGlobal(tag, inst),
|
||||
.global_set => try emit.emitGlobal(tag, inst),
|
||||
.memory_address => try emit.emitMemAddress(inst),
|
||||
|
||||
// immediates
|
||||
.f32_const => try emit.emitFloat32(inst),
|
||||
@ -157,11 +156,10 @@ pub fn emitMir(emit: *Emit) InnerError!void {
|
||||
.i64_extend32_s => try emit.emitTag(tag),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill in the size of the generated code to the reserved space at the
|
||||
// beginning of the buffer.
|
||||
const size = emit.code.items.len - 5;
|
||||
leb128.writeUnsignedFixed(5, emit.code.items[0..5], @intCast(u32, size));
|
||||
fn offset(self: Emit) u32 {
|
||||
return @intCast(u32, self.code.items.len);
|
||||
}
|
||||
|
||||
fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError {
|
||||
@ -216,9 +214,14 @@ fn emitGlobal(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void {
|
||||
try emit.code.append(@enumToInt(tag));
|
||||
var buf: [5]u8 = undefined;
|
||||
leb128.writeUnsignedFixed(5, &buf, label);
|
||||
const global_offset = emit.offset();
|
||||
try emit.code.appendSlice(&buf);
|
||||
|
||||
// TODO: Append label to the relocation list of this function
|
||||
try emit.decl.link.wasm.relocs.append(emit.bin_file.allocator, .{
|
||||
.index = label,
|
||||
.offset = global_offset,
|
||||
.relocation_type = .R_WASM_GLOBAL_INDEX_LEB,
|
||||
});
|
||||
}
|
||||
|
||||
fn emitImm32(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
@ -261,16 +264,29 @@ fn emitMemArg(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) !void {
|
||||
fn emitCall(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
const label = emit.mir.instructions.items(.data)[inst].label;
|
||||
try emit.code.append(std.wasm.opcode(.call));
|
||||
const offset = @intCast(u32, emit.code.items.len);
|
||||
const call_offset = emit.offset();
|
||||
var buf: [5]u8 = undefined;
|
||||
leb128.writeUnsignedFixed(5, &buf, label);
|
||||
try emit.code.appendSlice(&buf);
|
||||
|
||||
// The function index immediate argument will be filled in using this data
|
||||
// in link.Wasm.flush().
|
||||
// TODO: Replace this with proper relocations saved in the Atom.
|
||||
try emit.decl.fn_link.wasm.idx_refs.append(emit.bin_file.allocator, .{
|
||||
.offset = offset,
|
||||
.decl = label,
|
||||
try emit.decl.link.wasm.relocs.append(emit.bin_file.allocator, .{
|
||||
.offset = call_offset,
|
||||
.index = label,
|
||||
.relocation_type = .R_WASM_FUNCTION_INDEX_LEB,
|
||||
});
|
||||
}
|
||||
|
||||
fn emitMemAddress(emit: *Emit, inst: Mir.Inst.Index) !void {
|
||||
const symbol_index = emit.mir.instructions.items(.data)[inst].label;
|
||||
try emit.code.append(std.wasm.opcode(.i32_const));
|
||||
const mem_offset = emit.offset();
|
||||
var buf: [5]u8 = undefined;
|
||||
leb128.writeUnsignedFixed(5, &buf, symbol_index);
|
||||
try emit.code.appendSlice(&buf);
|
||||
|
||||
try emit.decl.link.wasm.relocs.append(emit.bin_file.allocator, .{
|
||||
.offset = mem_offset,
|
||||
.index = symbol_index,
|
||||
.relocation_type = .R_WASM_MEMORY_ADDR_LEB,
|
||||
});
|
||||
}
|
||||
|
||||
@ -358,6 +358,12 @@ pub const Inst = struct {
|
||||
i64_extend16_s = 0xC3,
|
||||
/// Uses `tag`
|
||||
i64_extend32_s = 0xC4,
|
||||
/// Contains a symbol to a memory address
|
||||
/// Uses `label`
|
||||
///
|
||||
/// Note: This uses `0xFF` as value as it is unused and not-reserved
|
||||
/// by the wasm specification, making it safe to use
|
||||
memory_address = 0xFF,
|
||||
|
||||
/// From a given wasm opcode, returns a MIR tag.
|
||||
pub fn fromOpcode(opcode: std.wasm.Opcode) Tag {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
164
src/link/Wasm/Atom.zig
Normal file
164
src/link/Wasm/Atom.zig
Normal file
@ -0,0 +1,164 @@
|
||||
const Atom = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const types = @import("types.zig");
|
||||
const Wasm = @import("../Wasm.zig");
|
||||
const Symbol = @import("Symbol.zig");
|
||||
|
||||
const leb = std.leb;
|
||||
const log = std.log.scoped(.link);
|
||||
const mem = std.mem;
|
||||
const Allocator = mem.Allocator;
|
||||
|
||||
/// symbol index of the symbol representing this atom
|
||||
sym_index: u32,
|
||||
/// Size of the atom, used to calculate section sizes in the final binary
|
||||
size: u32,
|
||||
/// List of relocations belonging to this atom
|
||||
relocs: std.ArrayListUnmanaged(types.Relocation) = .{},
|
||||
/// Contains the binary data of an atom, which can be non-relocated
|
||||
code: std.ArrayListUnmanaged(u8) = .{},
|
||||
/// For code this is 1, for data this is set to the highest value of all segments
|
||||
alignment: u32,
|
||||
/// Offset into the section where the atom lives, this already accounts
|
||||
/// for alignment.
|
||||
offset: u32,
|
||||
|
||||
/// Next atom in relation to this atom.
|
||||
/// When null, this atom is the last atom
|
||||
next: ?*Atom,
|
||||
/// Previous atom in relation to this atom.
|
||||
/// is null when this atom is the first in its order
|
||||
prev: ?*Atom,
|
||||
|
||||
/// Represents a default empty wasm `Atom`
|
||||
pub const empty: Atom = .{
|
||||
.alignment = 0,
|
||||
.next = null,
|
||||
.offset = 0,
|
||||
.prev = null,
|
||||
.size = 0,
|
||||
.sym_index = 0,
|
||||
};
|
||||
|
||||
/// Frees all resources owned by this `Atom`.
|
||||
pub fn deinit(self: *Atom, gpa: *Allocator) void {
|
||||
self.relocs.deinit(gpa);
|
||||
self.code.deinit(gpa);
|
||||
}
|
||||
|
||||
/// Sets the length of relocations and code to '0',
|
||||
/// effectively resetting them and allowing them to be re-populated.
|
||||
pub fn clear(self: *Atom) void {
|
||||
self.relocs.clearRetainingCapacity();
|
||||
self.code.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
pub fn format(self: Atom, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
_ = fmt;
|
||||
_ = options;
|
||||
writer.print("Atom{{ .sym_index = {d}, .alignment = {d}, .size = {d}, .offset = 0x{x:0>8} }}", .{
|
||||
self.sym_index,
|
||||
self.alignment,
|
||||
self.size,
|
||||
self.offset,
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns the first `Atom` from a given atom
|
||||
pub fn getFirst(self: *Atom) *Atom {
|
||||
var tmp = self;
|
||||
while (tmp.prev) |prev| tmp = prev;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/// Resolves the relocations within the atom, writing the new value
|
||||
/// at the calculated offset.
|
||||
pub fn resolveRelocs(self: *Atom, wasm_bin: *const Wasm) !void {
|
||||
const symbol: Symbol = wasm_bin.symbols.items[self.sym_index];
|
||||
log.debug("Resolving relocs in atom '{s}' count({d})", .{
|
||||
symbol.name,
|
||||
self.relocs.items.len,
|
||||
});
|
||||
|
||||
for (self.relocs.items) |reloc| {
|
||||
const value = try relocationValue(reloc, wasm_bin);
|
||||
log.debug("Relocating '{s}' referenced in '{s}' offset=0x{x:0>8} value={d}\n", .{
|
||||
wasm_bin.symbols.items[reloc.index].name,
|
||||
symbol.name,
|
||||
reloc.offset,
|
||||
value,
|
||||
});
|
||||
|
||||
switch (reloc.relocation_type) {
|
||||
.R_WASM_TABLE_INDEX_I32,
|
||||
.R_WASM_FUNCTION_OFFSET_I32,
|
||||
.R_WASM_GLOBAL_INDEX_I32,
|
||||
.R_WASM_MEMORY_ADDR_I32,
|
||||
.R_WASM_SECTION_OFFSET_I32,
|
||||
=> std.mem.writeIntLittle(u32, self.code.items[reloc.offset..][0..4], @intCast(u32, value)),
|
||||
.R_WASM_TABLE_INDEX_I64,
|
||||
.R_WASM_MEMORY_ADDR_I64,
|
||||
=> std.mem.writeIntLittle(u64, self.code.items[reloc.offset..][0..8], value),
|
||||
.R_WASM_GLOBAL_INDEX_LEB,
|
||||
.R_WASM_EVENT_INDEX_LEB,
|
||||
.R_WASM_FUNCTION_INDEX_LEB,
|
||||
.R_WASM_MEMORY_ADDR_LEB,
|
||||
.R_WASM_MEMORY_ADDR_SLEB,
|
||||
.R_WASM_TABLE_INDEX_SLEB,
|
||||
.R_WASM_TABLE_NUMBER_LEB,
|
||||
.R_WASM_TYPE_INDEX_LEB,
|
||||
=> leb.writeUnsignedFixed(5, self.code.items[reloc.offset..][0..5], @intCast(u32, value)),
|
||||
.R_WASM_MEMORY_ADDR_LEB64,
|
||||
.R_WASM_MEMORY_ADDR_SLEB64,
|
||||
.R_WASM_TABLE_INDEX_SLEB64,
|
||||
=> leb.writeUnsignedFixed(10, self.code.items[reloc.offset..][0..10], value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// From a given `relocation` will return the new value to be written.
|
||||
/// All values will be represented as a `u64` as all values can fit within it.
|
||||
/// The final value must be casted to the correct size.
|
||||
fn relocationValue(relocation: types.Relocation, wasm_bin: *const Wasm) !u64 {
|
||||
const symbol: Symbol = wasm_bin.symbols.items[relocation.index];
|
||||
return switch (relocation.relocation_type) {
|
||||
.R_WASM_FUNCTION_INDEX_LEB => symbol.index,
|
||||
.R_WASM_TABLE_NUMBER_LEB => symbol.index,
|
||||
.R_WASM_TABLE_INDEX_I32,
|
||||
.R_WASM_TABLE_INDEX_I64,
|
||||
.R_WASM_TABLE_INDEX_SLEB,
|
||||
.R_WASM_TABLE_INDEX_SLEB64,
|
||||
=> return error.TodoImplementTableIndex, // find table index from a function symbol
|
||||
.R_WASM_TYPE_INDEX_LEB => wasm_bin.functions.items[symbol.index].type_index,
|
||||
.R_WASM_GLOBAL_INDEX_I32,
|
||||
.R_WASM_GLOBAL_INDEX_LEB,
|
||||
=> symbol.index,
|
||||
.R_WASM_MEMORY_ADDR_I32,
|
||||
.R_WASM_MEMORY_ADDR_I64,
|
||||
.R_WASM_MEMORY_ADDR_LEB,
|
||||
.R_WASM_MEMORY_ADDR_LEB64,
|
||||
.R_WASM_MEMORY_ADDR_SLEB,
|
||||
.R_WASM_MEMORY_ADDR_SLEB64,
|
||||
=> blk: {
|
||||
if (symbol.isUndefined() and (symbol.tag == .data or symbol.isWeak())) {
|
||||
return 0;
|
||||
}
|
||||
const segment_name = wasm_bin.segment_info.items[symbol.index].outputName();
|
||||
const atom_index = wasm_bin.data_segments.get(segment_name).?;
|
||||
var target_atom = wasm_bin.atoms.getPtr(atom_index).?.*.getFirst();
|
||||
while (true) {
|
||||
if (target_atom.sym_index == relocation.index) break;
|
||||
target_atom = target_atom.next orelse break;
|
||||
}
|
||||
const segment = wasm_bin.segments.items[atom_index];
|
||||
const base = wasm_bin.base.options.global_base orelse 1024;
|
||||
const offset = target_atom.offset + segment.offset;
|
||||
break :blk offset + base + (relocation.addend orelse 0);
|
||||
},
|
||||
.R_WASM_EVENT_INDEX_LEB => symbol.index,
|
||||
.R_WASM_SECTION_OFFSET_I32,
|
||||
.R_WASM_FUNCTION_OFFSET_I32,
|
||||
=> relocation.offset,
|
||||
};
|
||||
}
|
||||
157
src/link/Wasm/Symbol.zig
Normal file
157
src/link/Wasm/Symbol.zig
Normal file
@ -0,0 +1,157 @@
|
||||
//! Wasm symbols describing its kind,
|
||||
//! name and its properties.
|
||||
const Symbol = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const types = @import("types.zig");
|
||||
|
||||
/// Bitfield containings flags for a symbol
|
||||
/// Can contain any of the flags defined in `Flag`
|
||||
flags: u32,
|
||||
/// Symbol name, when undefined this will be taken from the import.
|
||||
name: [*:0]const u8,
|
||||
/// An union that represents both the type of symbol
|
||||
/// as well as the data it holds.
|
||||
tag: Tag,
|
||||
/// Index into the list of objects based on set `tag`
|
||||
/// NOTE: This will be set to `undefined` when `tag` is `data`
|
||||
/// and the symbol is undefined.
|
||||
index: u32,
|
||||
|
||||
pub const Tag = enum {
|
||||
function,
|
||||
data,
|
||||
global,
|
||||
section,
|
||||
event,
|
||||
table,
|
||||
|
||||
/// From a given symbol tag, returns the `ExternalType`
|
||||
/// Asserts the given tag can be represented as an external type.
|
||||
pub fn externalType(self: Tag) std.wasm.ExternalKind {
|
||||
return switch (self) {
|
||||
.function => .function,
|
||||
.global => .global,
|
||||
.data => .memory,
|
||||
.section => unreachable, // Not an external type
|
||||
.event => unreachable, // Not an external type
|
||||
.table => .table,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Flag = enum(u32) {
|
||||
/// Indicates a weak symbol.
|
||||
/// When linking multiple modules defining the same symbol, all weak definitions are discarded
|
||||
/// in favourite of the strong definition. When no strong definition exists, all weak but one definiton is discarded.
|
||||
/// If multiple definitions remain, we get an error: symbol collision.
|
||||
WASM_SYM_BINDING_WEAK = 0x1,
|
||||
/// Indicates a local, non-exported, non-module-linked symbol.
|
||||
/// The names of local symbols are not required to be unique, unlike non-local symbols.
|
||||
WASM_SYM_BINDING_LOCAL = 0x2,
|
||||
/// Represents the binding of a symbol, indicating if it's local or not, and weak or not.
|
||||
WASM_SYM_BINDING_MASK = 0x3,
|
||||
/// Indicates a hidden symbol. Hidden symbols will not be exported to the link result, but may
|
||||
/// link to other modules.
|
||||
WASM_SYM_VISIBILITY_HIDDEN = 0x4,
|
||||
/// Indicates an undefined symbol. For non-data symbols, this must match whether the symbol is
|
||||
/// an import or is defined. For data symbols however, determines whether a segment is specified.
|
||||
WASM_SYM_UNDEFINED = 0x10,
|
||||
/// Indicates a symbol of which its intention is to be exported from the wasm module to the host environment.
|
||||
/// This differs from the visibility flag as this flag affects the static linker.
|
||||
WASM_SYM_EXPORTED = 0x20,
|
||||
/// Indicates the symbol uses an explicit symbol name, rather than reusing the name from a wasm import.
|
||||
/// Allows remapping imports from foreign WASM modules into local symbols with a different name.
|
||||
WASM_SYM_EXPLICIT_NAME = 0x40,
|
||||
/// Indicates the symbol is to be included in the linker output, regardless of whether it is used or has any references to it.
|
||||
WASM_SYM_NO_STRIP = 0x80,
|
||||
/// Indicates a symbol is TLS
|
||||
WASM_SYM_TLS = 0x100,
|
||||
};
|
||||
|
||||
/// Verifies if the given symbol should be imported from the
|
||||
/// host environment or not
|
||||
pub fn requiresImport(self: Symbol) bool {
|
||||
if (!self.isUndefined()) return false;
|
||||
if (self.isWeak()) return false;
|
||||
if (self.kind == .data) return false;
|
||||
// if (self.isDefined() and self.isWeak()) return true; //TODO: Only when building shared lib
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn hasFlag(self: Symbol, flag: Flag) bool {
|
||||
return self.flags & @enumToInt(flag) != 0;
|
||||
}
|
||||
|
||||
pub fn setFlag(self: *Symbol, flag: Flag) void {
|
||||
self.flags |= @enumToInt(flag);
|
||||
}
|
||||
|
||||
pub fn isUndefined(self: Symbol) bool {
|
||||
return self.flags & @enumToInt(Flag.WASM_SYM_UNDEFINED) != 0;
|
||||
}
|
||||
|
||||
pub fn setUndefined(self: *Symbol, is_undefined: bool) void {
|
||||
if (is_undefined) {
|
||||
self.setFlag(.WASM_SYM_UNDEFINED);
|
||||
} else {
|
||||
self.flags &= ~@enumToInt(Flag.WASM_SYM_UNDEFINED);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn isDefined(self: Symbol) bool {
|
||||
return !self.isUndefined();
|
||||
}
|
||||
|
||||
pub fn isVisible(self: Symbol) bool {
|
||||
return self.flags & @enumToInt(Flag.WASM_SYM_VISIBILITY_HIDDEN) == 0;
|
||||
}
|
||||
|
||||
pub fn isLocal(self: Symbol) bool {
|
||||
return self.flags & @enumToInt(Flag.WASM_SYM_BINDING_LOCAL) != 0;
|
||||
}
|
||||
|
||||
pub fn isGlobal(self: Symbol) bool {
|
||||
return self.flags & @enumToInt(Flag.WASM_SYM_BINDING_LOCAL) == 0;
|
||||
}
|
||||
|
||||
pub fn isHidden(self: Symbol) bool {
|
||||
return self.flags & @enumToInt(Flag.WASM_SYM_VISIBILITY_HIDDEN) != 0;
|
||||
}
|
||||
|
||||
pub fn isNoStrip(self: Symbol) bool {
|
||||
return self.flags & @enumToInt(Flag.WASM_SYM_NO_STRIP) != 0;
|
||||
}
|
||||
|
||||
pub fn isExported(self: Symbol) bool {
|
||||
if (self.isUndefined() or self.isLocal()) return false;
|
||||
if (self.isHidden()) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn isWeak(self: Symbol) bool {
|
||||
return self.flags & @enumToInt(Flag.WASM_SYM_BINDING_WEAK) != 0;
|
||||
}
|
||||
|
||||
/// Formats the symbol into human-readable text
|
||||
pub fn format(self: Symbol, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
_ = fmt;
|
||||
_ = options;
|
||||
|
||||
const kind_fmt: u8 = switch (self.kind) {
|
||||
.function => 'F',
|
||||
.data => 'D',
|
||||
.global => 'G',
|
||||
.section => 'S',
|
||||
.event => 'E',
|
||||
.table => 'T',
|
||||
};
|
||||
const visible: []const u8 = if (self.isVisible()) "yes" else "no";
|
||||
const binding: []const u8 = if (self.isLocal()) "local" else "global";
|
||||
|
||||
try writer.print(
|
||||
"{c} binding={s} visible={s} id={d} name={s}",
|
||||
.{ kind_fmt, binding, visible, self.index(), self.name },
|
||||
);
|
||||
}
|
||||
199
src/link/Wasm/types.zig
Normal file
199
src/link/Wasm/types.zig
Normal file
@ -0,0 +1,199 @@
|
||||
//! This file contains all constants and related to wasm's object format.
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
pub const Relocation = struct {
|
||||
/// Represents the type of the `Relocation`
|
||||
relocation_type: RelocationType,
|
||||
/// Offset of the value to rewrite relative to the relevant section's contents.
|
||||
/// When `offset` is zero, its position is immediately after the id and size of the section.
|
||||
offset: u32,
|
||||
/// The index of the symbol used.
|
||||
/// When the type is `R_WASM_TYPE_INDEX_LEB`, it represents the index of the type.
|
||||
index: u32,
|
||||
/// Addend to add to the address.
|
||||
/// This field is only non-null for `R_WASM_MEMORY_ADDR_*`, `R_WASM_FUNCTION_OFFSET_I32` and `R_WASM_SECTION_OFFSET_I32`.
|
||||
addend: ?u32 = null,
|
||||
|
||||
/// All possible relocation types currently existing.
|
||||
/// This enum is exhaustive as the spec is WIP and new types
|
||||
/// can be added which means that a generated binary will be invalid,
|
||||
/// so instead we will show an error in such cases.
|
||||
pub const RelocationType = enum(u8) {
|
||||
R_WASM_FUNCTION_INDEX_LEB = 0,
|
||||
R_WASM_TABLE_INDEX_SLEB = 1,
|
||||
R_WASM_TABLE_INDEX_I32 = 2,
|
||||
R_WASM_MEMORY_ADDR_LEB = 3,
|
||||
R_WASM_MEMORY_ADDR_SLEB = 4,
|
||||
R_WASM_MEMORY_ADDR_I32 = 5,
|
||||
R_WASM_TYPE_INDEX_LEB = 6,
|
||||
R_WASM_GLOBAL_INDEX_LEB = 7,
|
||||
R_WASM_FUNCTION_OFFSET_I32 = 8,
|
||||
R_WASM_SECTION_OFFSET_I32 = 9,
|
||||
R_WASM_EVENT_INDEX_LEB = 10,
|
||||
R_WASM_GLOBAL_INDEX_I32 = 13,
|
||||
R_WASM_MEMORY_ADDR_LEB64 = 14,
|
||||
R_WASM_MEMORY_ADDR_SLEB64 = 15,
|
||||
R_WASM_MEMORY_ADDR_I64 = 16,
|
||||
R_WASM_TABLE_INDEX_SLEB64 = 18,
|
||||
R_WASM_TABLE_INDEX_I64 = 19,
|
||||
R_WASM_TABLE_NUMBER_LEB = 20,
|
||||
|
||||
/// Returns true for relocation types where the `addend` field is present.
|
||||
pub fn addendIsPresent(self: RelocationType) bool {
|
||||
return switch (self) {
|
||||
.R_WASM_MEMORY_ADDR_LEB,
|
||||
.R_WASM_MEMORY_ADDR_SLEB,
|
||||
.R_WASM_MEMORY_ADDR_I32,
|
||||
.R_WASM_MEMORY_ADDR_LEB64,
|
||||
.R_WASM_MEMORY_ADDR_SLEB64,
|
||||
.R_WASM_MEMORY_ADDR_I64,
|
||||
.R_WASM_FUNCTION_OFFSET_I32,
|
||||
.R_WASM_SECTION_OFFSET_I32,
|
||||
=> true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// Verifies the relocation type of a given `Relocation` and returns
|
||||
/// true when the relocation references a function call or address to a function.
|
||||
pub fn isFunction(self: Relocation) bool {
|
||||
return switch (self.relocation_type) {
|
||||
.R_WASM_FUNCTION_INDEX_LEB,
|
||||
.R_WASM_TABLE_INDEX_SLEB,
|
||||
=> true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn format(self: Relocation, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
_ = fmt;
|
||||
_ = options;
|
||||
try writer.print("{s} offset=0x{x:0>6} symbol={d}", .{
|
||||
@tagName(self.relocation_type),
|
||||
self.offset,
|
||||
self.index,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
pub const SubsectionType = enum(u8) {
|
||||
WASM_SEGMENT_INFO = 5,
|
||||
WASM_INIT_FUNCS = 6,
|
||||
WASM_COMDAT_INFO = 7,
|
||||
WASM_SYMBOL_TABLE = 8,
|
||||
};
|
||||
|
||||
pub const Segment = struct {
|
||||
/// Segment's name, encoded as UTF-8 bytes.
|
||||
name: []const u8,
|
||||
/// The required alignment of the segment, encoded as a power of 2
|
||||
alignment: u32,
|
||||
/// Bitfield containing flags for a segment
|
||||
flags: u32,
|
||||
|
||||
pub fn outputName(self: Segment) []const u8 {
|
||||
if (std.mem.startsWith(u8, self.name, ".rodata.")) {
|
||||
return ".rodata";
|
||||
} else if (std.mem.startsWith(u8, self.name, ".text.")) {
|
||||
return ".text";
|
||||
} else if (std.mem.startsWith(u8, self.name, ".rodata.")) {
|
||||
return ".rodata";
|
||||
} else if (std.mem.startsWith(u8, self.name, ".data.")) {
|
||||
return ".data";
|
||||
} else if (std.mem.startsWith(u8, self.name, ".bss.")) {
|
||||
return ".bss";
|
||||
}
|
||||
return self.name;
|
||||
}
|
||||
};
|
||||
|
||||
pub const InitFunc = struct {
|
||||
/// Priority of the init function
|
||||
priority: u32,
|
||||
/// The symbol index of init function (not the function index).
|
||||
symbol_index: u32,
|
||||
};
|
||||
|
||||
pub const Comdat = struct {
|
||||
name: []const u8,
|
||||
/// Must be zero, no flags are currently defined by the tool-convention.
|
||||
flags: u32,
|
||||
symbols: []const ComdatSym,
|
||||
};
|
||||
|
||||
pub const ComdatSym = struct {
|
||||
kind: Type,
|
||||
/// Index of the data segment/function/global/event/table within a WASM module.
|
||||
/// The object must not be an import.
|
||||
index: u32,
|
||||
|
||||
pub const Type = enum(u8) {
|
||||
WASM_COMDAT_DATA = 0,
|
||||
WASM_COMDAT_FUNCTION = 1,
|
||||
WASM_COMDAT_GLOBAL = 2,
|
||||
WASM_COMDAT_EVENT = 3,
|
||||
WASM_COMDAT_TABLE = 4,
|
||||
WASM_COMDAT_SECTION = 5,
|
||||
};
|
||||
};
|
||||
|
||||
pub const Feature = struct {
|
||||
/// Provides information about the usage of the feature.
|
||||
/// - '0x2b' (+): Object uses this feature, and the link fails if feature is not in the allowed set.
|
||||
/// - '0x2d' (-): Object does not use this feature, and the link fails if this feature is in the allowed set.
|
||||
/// - '0x3d' (=): Object uses this feature, and the link fails if this feature is not in the allowed set,
|
||||
/// or if any object does not use this feature.
|
||||
prefix: Prefix,
|
||||
/// Type of the feature, must be unique in the sequence of features.
|
||||
tag: Tag,
|
||||
|
||||
pub const Tag = enum {
|
||||
atomics,
|
||||
bulk_memory,
|
||||
exception_handling,
|
||||
multivalue,
|
||||
mutable_globals,
|
||||
nontrapping_fptoint,
|
||||
sign_ext,
|
||||
simd128,
|
||||
tail_call,
|
||||
};
|
||||
|
||||
pub const Prefix = enum(u8) {
|
||||
used = '+',
|
||||
disallowed = '-',
|
||||
required = '=',
|
||||
};
|
||||
|
||||
pub fn toString(self: Feature) []const u8 {
|
||||
return switch (self.tag) {
|
||||
.bulk_memory => "bulk-memory",
|
||||
.exception_handling => "exception-handling",
|
||||
.mutable_globals => "mutable-globals",
|
||||
.nontrapping_fptoint => "nontrapping-fptoint",
|
||||
.sign_ext => "sign-ext",
|
||||
.tail_call => "tail-call",
|
||||
else => @tagName(self),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn format(self: Feature, comptime fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
_ = opt;
|
||||
_ = fmt;
|
||||
try writer.print("{c} {s}", .{ self.prefix, self.toString() });
|
||||
}
|
||||
};
|
||||
|
||||
pub const known_features = std.ComptimeStringMap(Feature.Tag, .{
|
||||
.{ "atomics", .atomics },
|
||||
.{ "bulk-memory", .bulk_memory },
|
||||
.{ "exception-handling", .exception_handling },
|
||||
.{ "multivalue", .multivalue },
|
||||
.{ "mutable-globals", .mutable_globals },
|
||||
.{ "nontrapping-fptoint", .nontrapping_fptoint },
|
||||
.{ "sign-ext", .sign_ext },
|
||||
.{ "simd128", .simd128 },
|
||||
.{ "tail-call", .tail_call },
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user