During codegen we do not yet know the indexes that will be used for called functions. Therefore, we store the offset into the in-memory code where the index is needed with a pointer to the Decl and use this data to insert the proper indexes while writing the binary in the flush function.
252 lines
8.6 KiB
Zig
252 lines
8.6 KiB
Zig
const Wasm = @This();
|
|
|
|
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
const assert = std.debug.assert;
|
|
const fs = std.fs;
|
|
const leb = std.debug.leb;
|
|
|
|
const Module = @import("../Module.zig");
|
|
const codegen = @import("../codegen/wasm.zig");
|
|
const link = @import("../link.zig");
|
|
|
|
/// Various magic numbers defined by the wasm spec
|
|
const spec = struct {
|
|
const magic = [_]u8{ 0x00, 0x61, 0x73, 0x6D }; // \0asm
|
|
const version = [_]u8{ 0x01, 0x00, 0x00, 0x00 }; // version 1
|
|
|
|
const custom_id = 0;
|
|
const types_id = 1;
|
|
const imports_id = 2;
|
|
const funcs_id = 3;
|
|
const tables_id = 4;
|
|
const memories_id = 5;
|
|
const globals_id = 6;
|
|
const exports_id = 7;
|
|
const start_id = 8;
|
|
const elements_id = 9;
|
|
const code_id = 10;
|
|
const data_id = 11;
|
|
};
|
|
|
|
pub const base_tag = link.File.Tag.wasm;
|
|
|
|
pub const FnData = struct {
|
|
/// Generated code for the type of the function
|
|
functype: std.ArrayListUnmanaged(u8) = .{},
|
|
/// Generated code for the body of the function
|
|
code: std.ArrayListUnmanaged(u8) = .{},
|
|
/// Locations in the generated code where function indexes must be filled in.
|
|
/// This must be kept ordered by offset.
|
|
idx_refs: std.ArrayListUnmanaged(struct { offset: u32, decl: *Module.Decl }) = .{},
|
|
};
|
|
|
|
base: link.File,
|
|
|
|
/// List of all function Decls to be written to the output file. The index of
|
|
/// each Decl in this list at the time of writing the binary is used as the
|
|
/// function index.
|
|
/// TODO: can/should we access some data structure in Module directly?
|
|
funcs: std.ArrayListUnmanaged(*Module.Decl) = .{},
|
|
|
|
pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: link.Options) !*link.File {
|
|
assert(options.object_format == .wasm);
|
|
|
|
// TODO: read the file and keep vaild parts instead of truncating
|
|
const file = try dir.createFile(sub_path, .{ .truncate = true, .read = true });
|
|
errdefer file.close();
|
|
|
|
const wasm = try allocator.create(Wasm);
|
|
errdefer allocator.destroy(wasm);
|
|
|
|
try file.writeAll(&(spec.magic ++ spec.version));
|
|
|
|
wasm.* = .{
|
|
.base = .{
|
|
.tag = .wasm,
|
|
.options = options,
|
|
.file = file,
|
|
.allocator = allocator,
|
|
},
|
|
};
|
|
|
|
return &wasm.base;
|
|
}
|
|
|
|
pub fn deinit(self: *Wasm) void {
|
|
for (self.funcs.items) |decl| {
|
|
decl.fn_link.wasm.?.functype.deinit(self.base.allocator);
|
|
decl.fn_link.wasm.?.code.deinit(self.base.allocator);
|
|
decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator);
|
|
}
|
|
self.funcs.deinit(self.base.allocator);
|
|
}
|
|
|
|
// Generate code for the Decl, storing it in memory to be later written to
|
|
// the file on flush().
|
|
pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
|
|
if (decl.typed_value.most_recent.typed_value.ty.zigTypeTag() != .Fn)
|
|
return error.TODOImplementNonFnDeclsForWasm;
|
|
|
|
if (decl.fn_link.wasm) |*fn_data| {
|
|
fn_data.functype.items.len = 0;
|
|
fn_data.code.items.len = 0;
|
|
fn_data.idx_refs.items.len = 0;
|
|
} else {
|
|
decl.fn_link.wasm = .{};
|
|
try self.funcs.append(self.base.allocator, decl);
|
|
}
|
|
const fn_data = &decl.fn_link.wasm.?;
|
|
|
|
var managed_functype = fn_data.functype.toManaged(self.base.allocator);
|
|
var managed_code = fn_data.code.toManaged(self.base.allocator);
|
|
try codegen.genFunctype(&managed_functype, decl);
|
|
try codegen.genCode(&managed_code, decl);
|
|
fn_data.functype = managed_functype.toUnmanaged();
|
|
fn_data.code = managed_code.toUnmanaged();
|
|
}
|
|
|
|
pub fn updateDeclExports(
|
|
self: *Wasm,
|
|
module: *Module,
|
|
decl: *const Module.Decl,
|
|
exports: []const *Module.Export,
|
|
) !void {}
|
|
|
|
pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
|
|
// TODO: remove this assert when non-function Decls are implemented
|
|
assert(decl.typed_value.most_recent.typed_value.ty.zigTypeTag() == .Fn);
|
|
_ = self.funcs.swapRemove(self.getFuncidx(decl).?);
|
|
decl.fn_link.wasm.?.functype.deinit(self.base.allocator);
|
|
decl.fn_link.wasm.?.code.deinit(self.base.allocator);
|
|
decl.fn_link.wasm.?.idx_refs.deinit(self.base.allocator);
|
|
decl.fn_link.wasm = null;
|
|
}
|
|
|
|
pub fn flush(self: *Wasm, module: *Module) !void {
|
|
const file = self.base.file.?;
|
|
const header_size = 5 + 1;
|
|
|
|
// No need to rewrite the magic/version header
|
|
try file.setEndPos(@sizeOf(@TypeOf(spec.magic ++ spec.version)));
|
|
try file.seekTo(@sizeOf(@TypeOf(spec.magic ++ spec.version)));
|
|
|
|
// Type section
|
|
{
|
|
const header_offset = try reserveVecSectionHeader(file);
|
|
for (self.funcs.items) |decl| {
|
|
try file.writeAll(decl.fn_link.wasm.?.functype.items);
|
|
}
|
|
try writeVecSectionHeader(
|
|
file,
|
|
header_offset,
|
|
spec.types_id,
|
|
@intCast(u32, (try file.getPos()) - header_offset - header_size),
|
|
@intCast(u32, self.funcs.items.len),
|
|
);
|
|
}
|
|
|
|
// Function section
|
|
{
|
|
const header_offset = try reserveVecSectionHeader(file);
|
|
const writer = file.writer();
|
|
for (self.funcs.items) |_, typeidx| try leb.writeULEB128(writer, @intCast(u32, typeidx));
|
|
try writeVecSectionHeader(
|
|
file,
|
|
header_offset,
|
|
spec.funcs_id,
|
|
@intCast(u32, (try file.getPos()) - header_offset - header_size),
|
|
@intCast(u32, self.funcs.items.len),
|
|
);
|
|
}
|
|
|
|
// Export section
|
|
{
|
|
const header_offset = try reserveVecSectionHeader(file);
|
|
const writer = file.writer();
|
|
var count: u32 = 0;
|
|
for (module.decl_exports.entries.items) |entry| {
|
|
for (entry.value) |exprt| {
|
|
// Export name length + name
|
|
try leb.writeULEB128(writer, @intCast(u32, exprt.options.name.len));
|
|
try writer.writeAll(exprt.options.name);
|
|
|
|
switch (exprt.exported_decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) {
|
|
.Fn => {
|
|
// Type of the export
|
|
try writer.writeByte(0x00);
|
|
// Exported function index
|
|
try leb.writeULEB128(writer, self.getFuncidx(exprt.exported_decl).?);
|
|
},
|
|
else => return error.TODOImplementNonFnDeclsForWasm,
|
|
}
|
|
|
|
count += 1;
|
|
}
|
|
}
|
|
try writeVecSectionHeader(
|
|
file,
|
|
header_offset,
|
|
spec.exports_id,
|
|
@intCast(u32, (try file.getPos()) - header_offset - header_size),
|
|
count,
|
|
);
|
|
}
|
|
|
|
// Code section
|
|
{
|
|
const header_offset = try reserveVecSectionHeader(file);
|
|
const writer = file.writer();
|
|
for (self.funcs.items) |decl| {
|
|
const fn_data = &decl.fn_link.wasm.?;
|
|
|
|
// Write the already generated code to the file, inserting
|
|
// function indexes where required.
|
|
var current: u32 = 0;
|
|
for (fn_data.idx_refs.items) |idx_ref| {
|
|
try writer.writeAll(fn_data.code.items[current..idx_ref.offset]);
|
|
current = idx_ref.offset;
|
|
// Use a fixed width here to make calculating the code size
|
|
// in codegen.wasm.genCode() simpler.
|
|
var buf: [5]u8 = undefined;
|
|
leb.writeUnsignedFixed(5, &buf, self.getFuncidx(idx_ref.decl).?);
|
|
try writer.writeAll(&buf);
|
|
}
|
|
|
|
try writer.writeAll(fn_data.code.items[current..]);
|
|
}
|
|
try writeVecSectionHeader(
|
|
file,
|
|
header_offset,
|
|
spec.code_id,
|
|
@intCast(u32, (try file.getPos()) - header_offset - header_size),
|
|
@intCast(u32, self.funcs.items.len),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Get the current index of a given Decl in the function list
|
|
/// TODO: we could maintain a hash map to potentially make this
|
|
fn getFuncidx(self: Wasm, decl: *Module.Decl) ?u32 {
|
|
return for (self.funcs.items) |func, idx| {
|
|
if (func == decl) break @intCast(u32, idx);
|
|
} else null;
|
|
}
|
|
|
|
fn reserveVecSectionHeader(file: fs.File) !u64 {
|
|
// section id + fixed leb contents size + fixed leb vector length
|
|
const header_size = 1 + 5 + 5;
|
|
// TODO: this should be a single lseek(2) call, but fs.File does not
|
|
// currently provide a way to do this.
|
|
try file.seekBy(header_size);
|
|
return (try file.getPos()) - header_size;
|
|
}
|
|
|
|
fn writeVecSectionHeader(file: fs.File, offset: u64, section: u8, size: u32, items: u32) !void {
|
|
var buf: [1 + 5 + 5]u8 = undefined;
|
|
buf[0] = section;
|
|
leb.writeUnsignedFixed(5, buf[1..6], size);
|
|
leb.writeUnsignedFixed(5, buf[6..], items);
|
|
try file.pwriteAll(&buf, offset);
|
|
}
|