wasm: Split funcgen and declgen

This allows us to get rid of unused fields when generating code for non-function decls.
We can now create seperate instances of `DeclGen` which in turn can then be used
to generate the code for a constant.
Besides those reasons, it will be much easier to switch to the generic purpose `codegen.zig` that any
backend should use. Allowing us to deduplicate this code.
This commit is contained in:
Luuk de Gram 2022-01-18 21:43:20 +01:00
parent 28acbdb02f
commit 1fe1e4d292
No known key found for this signature in database
GPG Key ID: A8CFE58E4DC7D664
3 changed files with 363 additions and 312 deletions

View File

@ -542,10 +542,9 @@ locals: std.ArrayListUnmanaged(u8),
target: std.Target,
/// Represents the wasm binary file that is being linked.
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.
global_error_set: std.StringHashMapUnmanaged(Module.ErrorInt),
/// Reference to the Module that this decl is part of.
/// Used to find the error value.
module: *Module,
/// List of MIR Instructions
mir_instructions: std.MultiArrayList(Mir.Inst) = .{},
/// Contains extra data for MIR
@ -581,7 +580,7 @@ pub fn deinit(self: *Self) void {
self.* = undefined;
}
/// Sets `err_msg` on `Context` and returns `error.CodegemFail` which is caught in link/Wasm.zig
/// Sets `err_msg` on `CodeGen` and returns `error.CodegenFail` which is caught in link/Wasm.zig
fn fail(self: *Self, comptime fmt: []const u8, args: anytype) InnerError {
const src: LazySrcLoc = .{ .node_offset = 0 };
const src_loc = src.toSrcLoc(self.decl);
@ -674,50 +673,41 @@ fn addExtraAssumeCapacity(self: *Self, extra: anytype) error{OutOfMemory}!u32 {
return result;
}
/// Using a given `Type`, returns the corresponding wasm Valtype
fn typeToValtype(self: *Self, ty: Type) InnerError!wasm.Valtype {
/// Using a given `Type`, returns the corresponding type
fn typeToValtype(ty: Type, target: std.Target) wasm.Valtype {
return switch (ty.zigTypeTag()) {
.Float => blk: {
const bits = ty.floatBits(self.target);
const bits = ty.floatBits(target);
if (bits == 16 or bits == 32) break :blk wasm.Valtype.f32;
if (bits == 64) break :blk wasm.Valtype.f64;
return self.fail("Float bit size not supported by wasm: '{d}'", .{bits});
return wasm.Valtype.i32; // represented as pointer to stack
},
.Int => blk: {
const info = ty.intInfo(self.target);
const info = ty.intInfo(target);
if (info.bits <= 32) break :blk wasm.Valtype.i32;
if (info.bits > 32 and info.bits <= 64) break :blk wasm.Valtype.i64;
break :blk wasm.Valtype.i32; // represented as pointer to stack
},
.Enum => switch (ty.tag()) {
.enum_simple => wasm.Valtype.i32,
else => self.typeToValtype(ty.cast(Type.Payload.EnumFull).?.data.tag_ty),
else => typeToValtype(ty.cast(Type.Payload.EnumFull).?.data.tag_ty, target),
},
.Bool,
.Pointer,
.ErrorSet,
.Struct,
.ErrorUnion,
.Optional,
.Fn,
.Array,
=> wasm.Valtype.i32,
else => self.fail("TODO - Wasm typeToValtype for type '{}'", .{ty}),
else => wasm.Valtype.i32, // all represented as reference/immediate
};
}
/// Using a given `Type`, returns the byte representation of its wasm value type
fn genValtype(self: *Self, ty: Type) InnerError!u8 {
return wasm.valtype(try self.typeToValtype(ty));
fn genValtype(ty: Type, target: std.Target) u8 {
return wasm.valtype(typeToValtype(ty, target));
}
/// Using a given `Type`, returns the corresponding wasm value type
/// Differently from `genValtype` this also allows `void` to create a block
/// with no return type
fn genBlockType(self: *Self, ty: Type) InnerError!u8 {
fn genBlockType(ty: Type, target: std.Target) u8 {
return switch (ty.tag()) {
.void, .noreturn => wasm.block_empty,
else => self.genValtype(ty),
else => genValtype(ty, target),
};
}
@ -739,7 +729,7 @@ fn emitWValue(self: *Self, value: WValue) InnerError!void {
/// Returns a corresponding `Wvalue` with `local` as active tag
fn allocLocal(self: *Self, ty: Type) InnerError!WValue {
const initial_index = self.local_index;
const valtype = try self.genValtype(ty);
const valtype = genValtype(ty, self.target);
try self.locals.append(self.gpa, valtype);
self.local_index += 1;
return WValue{ .local = initial_index };
@ -747,33 +737,33 @@ fn allocLocal(self: *Self, ty: Type) InnerError!WValue {
/// 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);
fn genFunctype(gpa: Allocator, fn_ty: Type, target: std.Target) !wasm.Type {
var params = std.ArrayList(wasm.Valtype).init(gpa);
defer params.deinit();
var returns = std.ArrayList(wasm.Valtype).init(self.gpa);
var returns = std.ArrayList(wasm.Valtype).init(gpa);
defer returns.deinit();
const return_type = fn_ty.fnReturnType();
const want_sret = self.isByRef(return_type);
const want_sret = isByRef(return_type, target);
if (want_sret) {
try params.append(try self.typeToValtype(Type.usize));
try params.append(typeToValtype(return_type, target));
}
// param types
if (fn_ty.fnParamLen() != 0) {
const fn_params = try self.gpa.alloc(Type, fn_ty.fnParamLen());
defer self.gpa.free(fn_params);
const fn_params = try gpa.alloc(Type, fn_ty.fnParamLen());
defer 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));
try params.append(typeToValtype(param_type, target));
}
}
// return type
if (!want_sret and return_type.hasCodeGenBits()) {
try returns.append(try self.typeToValtype(return_type));
try returns.append(typeToValtype(return_type, target));
}
return wasm.Type{
@ -782,8 +772,8 @@ fn genFunctype(self: *Self, fn_ty: Type) !wasm.Type {
};
}
pub fn genFunc(self: *Self) InnerError!Result {
var func_type = try self.genFunctype(self.decl.ty);
pub fn genFunc(self: *Self) InnerError!void {
var func_type = try genFunctype(self.gpa, self.decl.ty, self.target);
defer func_type.deinit(self.gpa);
self.decl.fn_link.wasm.type_index = try self.bin_file.putOrGetFuncType(func_type);
@ -828,240 +818,260 @@ pub fn genFunc(self: *Self) InnerError!Result {
},
else => |e| return e,
};
// codegen data has been appended to `code`
return Result.appended;
}
pub fn genDecl(self: *Self) InnerError!Result {
const decl = self.decl;
assert(decl.has_tv);
pub const DeclGen = struct {
/// The decl we are generating code for.
decl: *Decl,
/// The symbol we're generating code for.
/// This can either be the symbol of the Decl itself,
/// or one of its locals.
symbol_index: u32,
gpa: Allocator,
/// A reference to the linker, that will process the decl's
/// code and create any relocations it deems neccesary.
bin_file: *link.File.Wasm,
/// This will be set when `InnerError` has been returned.
/// In any other case, this will be 'undefined'.
err_msg: *Module.ErrorMsg,
/// Reference to the Module that is being compiled.
/// Used to find the error value of an error.
module: *Module,
/// The list of bytes that have been generated so far,
/// can be used to calculate the offset into a section.
code: *std.ArrayList(u8),
log.debug("gen: {s} type: {}, value: {}", .{ decl.name, decl.ty, decl.val });
if (decl.val.castTag(.function)) |func_payload| {
_ = func_payload;
return self.fail("TODO wasm backend genDecl function pointer", .{});
} else if (decl.val.castTag(.extern_fn)) |extern_fn| {
const ext_decl = extern_fn.data;
var func_type = try self.genFunctype(ext_decl.ty);
func_type.deinit(self.gpa);
ext_decl.fn_link.wasm.type_index = try self.bin_file.putOrGetFuncType(func_type);
return Result.appended;
} else {
const init_val = if (decl.val.castTag(.variable)) |payload| init_val: {
break :init_val payload.data.init;
} else decl.val;
if (init_val.tag() != .unreachable_value) {
return try self.genTypedValue(decl.ty, init_val);
}
return Result.appended;
/// Sets `err_msg` on `DeclGen` and returns `error.CodegenFail` which is caught in link/Wasm.zig
fn fail(self: *DeclGen, comptime fmt: []const u8, args: anytype) InnerError {
const src: LazySrcLoc = .{ .node_offset = 0 };
const src_loc = src.toSrcLoc(self.decl);
self.err_msg = try Module.ErrorMsg.create(self.gpa, src_loc, fmt, args);
return error.CodegenFail;
}
}
/// Generates the wasm bytecode for the declaration belonging to `Context`
fn genTypedValue(self: *Self, ty: Type, val: Value) InnerError!Result {
if (val.isUndef()) {
try self.code.appendNTimes(0xaa, @intCast(usize, ty.abiSize(self.target)));
return Result.appended;
fn target(self: *const DeclGen) std.Target {
return self.bin_file.base.options.target;
}
switch (ty.zigTypeTag()) {
.Fn => {
const fn_decl = switch (val.tag()) {
.extern_fn => val.castTag(.extern_fn).?.data,
.function => val.castTag(.function).?.data.owner_decl,
else => unreachable,
};
return try self.lowerDeclRef(ty, val, fn_decl);
},
.Optional => {
var opt_buf: Type.Payload.ElemType = undefined;
const payload_type = ty.optionalChild(&opt_buf);
if (ty.isPtrLikeOptional()) {
if (val.castTag(.opt_payload)) |payload| {
return try self.genTypedValue(payload_type, payload.data);
} else if (!val.isNull()) {
return try self.genTypedValue(payload_type, val);
} else {
try self.code.appendNTimes(0, @intCast(usize, ty.abiSize(self.target)));
return Result.appended;
}
}
// `null-tag` byte
try self.code.appendNTimes(@boolToInt(!val.isNull()), 4);
const pl_result = try self.genTypedValue(
payload_type,
if (val.castTag(.opt_payload)) |pl| pl.data else Value.initTag(.undef),
);
switch (pl_result) {
.appended => {},
.externally_managed => |payload| try self.code.appendSlice(payload),
pub fn genDecl(self: *DeclGen) InnerError!Result {
const decl = self.decl;
assert(decl.has_tv);
log.debug("gen: {s} type: {}, value: {}", .{ decl.name, decl.ty, decl.val });
if (decl.val.castTag(.function)) |func_payload| {
_ = func_payload;
return self.fail("TODO wasm backend genDecl function pointer", .{});
} else if (decl.val.castTag(.extern_fn)) |extern_fn| {
const ext_decl = extern_fn.data;
var func_type = try genFunctype(self.gpa, ext_decl.ty, self.target());
func_type.deinit(self.gpa);
ext_decl.fn_link.wasm.type_index = try self.bin_file.putOrGetFuncType(func_type);
return Result.appended;
} else {
const init_val = if (decl.val.castTag(.variable)) |payload| init_val: {
break :init_val payload.data.init;
} else decl.val;
if (init_val.tag() != .unreachable_value) {
return try self.genTypedValue(decl.ty, init_val, self.code.writer());
}
return Result.appended;
},
.Array => switch (val.tag()) {
.bytes => {
const payload = val.castTag(.bytes).?;
return Result{ .externally_managed = payload.data };
}
}
/// Generates the wasm bytecode for the declaration belonging to `Context`
fn genTypedValue(self: *DeclGen, ty: Type, val: Value, writer: anytype) InnerError!Result {
if (val.isUndef()) {
try writer.writeByteNTimes(0xaa, @intCast(usize, ty.abiSize(self.target())));
return Result.appended;
}
switch (ty.zigTypeTag()) {
.Fn => {
const fn_decl = switch (val.tag()) {
.extern_fn => val.castTag(.extern_fn).?.data,
.function => val.castTag(.function).?.data.owner_decl,
else => unreachable,
};
return try self.lowerDeclRef(ty, val, fn_decl, writer);
},
.array => {
const elem_vals = val.castTag(.array).?.data;
const elem_ty = ty.childType();
for (elem_vals) |elem_val| {
switch (try self.genTypedValue(elem_ty, elem_val)) {
.Optional => {
var opt_buf: Type.Payload.ElemType = undefined;
const payload_type = ty.optionalChild(&opt_buf);
const is_pl = !val.isNull();
if (!payload_type.hasCodeGenBits()) {
try writer.writeByteNTimes(@boolToInt(is_pl), 4);
return Result.appended;
}
if (ty.isPtrLikeOptional()) {
if (val.castTag(.opt_payload)) |payload| {
return try self.genTypedValue(payload_type, payload.data, writer);
} else if (!val.isNull()) {
return try self.genTypedValue(payload_type, val, writer);
} else {
try writer.writeByteNTimes(0, @intCast(usize, ty.abiSize(self.target())));
return Result.appended;
}
}
// `null-tag` bytes
try writer.writeByteNTimes(@boolToInt(is_pl), 4);
const pl_result = try self.genTypedValue(
payload_type,
if (val.castTag(.opt_payload)) |pl| pl.data else Value.initTag(.undef),
writer,
);
switch (pl_result) {
.appended => {},
.externally_managed => |payload| try writer.writeAll(payload),
}
return Result.appended;
},
.Array => switch (val.tag()) {
.bytes => {
const payload = val.castTag(.bytes).?;
return Result{ .externally_managed = payload.data };
},
.array => {
const elem_vals = val.castTag(.array).?.data;
const elem_ty = ty.childType();
for (elem_vals) |elem_val| {
switch (try self.genTypedValue(elem_ty, elem_val, writer)) {
.appended => {},
.externally_managed => |data| try writer.writeAll(data),
}
}
return Result.appended;
},
else => return self.fail("TODO implement genTypedValue for array type value: {s}", .{@tagName(val.tag())}),
},
.Int => {
const info = ty.intInfo(self.target());
const abi_size = @intCast(usize, ty.abiSize(self.target()));
// todo: Implement integer sizes larger than 64bits
if (info.bits > 64) return self.fail("TODO: Implement genTypedValue for integer bit size: {d}", .{info.bits});
var buf: [8]u8 = undefined;
if (info.signedness == .unsigned) {
std.mem.writeIntLittle(u64, &buf, val.toUnsignedInt());
} else std.mem.writeIntLittle(i64, &buf, val.toSignedInt());
try writer.writeAll(buf[0..abi_size]);
return Result.appended;
},
.Enum => {
try writer.writeByteNTimes(0xaa, @intCast(usize, ty.abiSize(self.target())));
return Result.appended;
},
.Bool => {
try writer.writeByte(@boolToInt(val.toBool()));
return Result.appended;
},
.Struct => {
const field_vals = val.castTag(.@"struct").?.data;
for (field_vals) |field_val, index| {
const field_ty = ty.structFieldType(index);
if (!field_ty.hasCodeGenBits()) continue;
switch (try self.genTypedValue(field_ty, field_val, writer)) {
.appended => {},
.externally_managed => |data| try self.code.appendSlice(data),
.externally_managed => |payload| try writer.writeAll(payload),
}
}
return Result.appended;
},
else => return self.fail("TODO implement genTypedValue for array type value: {s}", .{@tagName(val.tag())}),
},
.Int => {
const info = ty.intInfo(self.target);
const abi_size = @intCast(usize, ty.abiSize(self.target));
// todo: Implement integer sizes larger than 64bits
if (info.bits > 64) return self.fail("TODO: Implement genTypedValue for integer bit size: {d}", .{info.bits});
var buf: [8]u8 = undefined;
if (info.signedness == .unsigned) {
std.mem.writeIntLittle(u64, &buf, val.toUnsignedInt());
} else std.mem.writeIntLittle(i64, &buf, val.toSignedInt());
try self.code.appendSlice(buf[0..abi_size]);
return Result.appended;
},
.Enum => {
const size = @intCast(usize, ty.abiSize(self.target));
try self.code.appendNTimes(0xaa, size);
return Result.appended;
},
.Bool => {
const int_byte: u8 = @boolToInt(val.toBool());
try self.code.append(int_byte);
return Result.appended;
},
.Struct => {
const field_vals = val.castTag(.@"struct").?.data;
for (field_vals) |field_val, index| {
const field_ty = ty.structFieldType(index);
if (!field_ty.hasCodeGenBits()) continue;
switch (try self.genTypedValue(field_ty, field_val)) {
.appended => {},
.externally_managed => |payload| try self.code.appendSlice(payload),
}
}
return Result.appended;
},
.Union => {
// TODO: Implement Union declarations
const abi_size = @intCast(usize, ty.abiSize(self.target));
try self.code.appendNTimes(0xaa, abi_size);
return Result.appended;
},
.Pointer => switch (val.tag()) {
.variable => {
const decl = val.castTag(.variable).?.data.owner_decl;
return try self.lowerDeclRef(ty, val, decl);
.Union => {
// TODO: Implement Union declarations
try writer.writeByteNTimes(0xaa, @intCast(usize, ty.abiSize(self.target())));
return Result.appended;
},
.decl_ref => {
const decl = val.castTag(.decl_ref).?.data;
return try self.lowerDeclRef(ty, val, decl);
.Pointer => switch (val.tag()) {
.variable => {
const decl = val.castTag(.variable).?.data.owner_decl;
return try self.lowerDeclRef(ty, val, decl, writer);
},
.decl_ref => {
const decl = val.castTag(.decl_ref).?.data;
return try self.lowerDeclRef(ty, val, decl, writer);
},
.slice => {
const slice = val.castTag(.slice).?.data;
var buf: Type.SlicePtrFieldTypeBuffer = undefined;
const ptr_ty = ty.slicePtrFieldType(&buf);
switch (try self.genTypedValue(ptr_ty, slice.ptr, writer)) {
.externally_managed => |data| try writer.writeAll(data),
.appended => {},
}
switch (try self.genTypedValue(Type.usize, slice.len, writer)) {
.externally_managed => |data| try writer.writeAll(data),
.appended => {},
}
return Result.appended;
},
else => return self.fail("TODO: Implement zig decl gen for pointer type value: '{s}'", .{@tagName(val.tag())}),
},
.slice => {
const slice = val.castTag(.slice).?.data;
var buf: Type.SlicePtrFieldTypeBuffer = undefined;
const ptr_ty = ty.slicePtrFieldType(&buf);
switch (try self.genTypedValue(ptr_ty, slice.ptr)) {
.externally_managed => |data| try self.code.appendSlice(data),
.ErrorUnion => {
const error_ty = ty.errorUnionSet();
const payload_ty = ty.errorUnionPayload();
const is_pl = val.errorUnionIsPayload();
const err_val = if (!is_pl) val else Value.initTag(.zero);
switch (try self.genTypedValue(error_ty, err_val, writer)) {
.externally_managed => |data| try writer.writeAll(data),
.appended => {},
}
switch (try self.genTypedValue(Type.usize, slice.len)) {
.externally_managed => |data| try self.code.appendSlice(data),
.appended => {},
if (payload_ty.hasCodeGenBits()) {
const pl_val = if (val.castTag(.eu_payload)) |pl| pl.data else Value.initTag(.undef);
switch (try self.genTypedValue(payload_ty, pl_val, writer)) {
.externally_managed => |data| try writer.writeAll(data),
.appended => {},
}
}
return Result.appended;
},
.ErrorSet => {
switch (val.tag()) {
.@"error" => {
const name = val.castTag(.@"error").?.data.name;
const kv = try self.module.getErrorValue(name);
try writer.writeIntLittle(u32, kv.value);
},
else => {
try writer.writeByteNTimes(0, @intCast(usize, ty.abiSize(self.target())));
},
}
return Result.appended;
},
else => return self.fail("TODO: Implement zig decl gen for pointer type value: '{s}'", .{@tagName(val.tag())}),
},
.ErrorUnion => {
const error_ty = ty.errorUnionSet();
const payload_ty = ty.errorUnionPayload();
const is_pl = val.errorUnionIsPayload();
const err_val = if (!is_pl) val else Value.initTag(.zero);
switch (try self.genTypedValue(error_ty, err_val)) {
.externally_managed => |data| try self.code.appendSlice(data),
.appended => {},
}
if (payload_ty.hasCodeGenBits()) {
const pl_val = if (val.castTag(.eu_payload)) |pl| pl.data else Value.initTag(.undef);
switch (try self.genTypedValue(payload_ty, pl_val)) {
.externally_managed => |data| try self.code.appendSlice(data),
.appended => {},
}
}
return Result.appended;
},
.ErrorSet => {
switch (val.tag()) {
.@"error" => {
const name = val.castTag(.@"error").?.data.name;
const value = self.global_error_set.get(name).?;
try self.code.writer().writeIntLittle(u32, value);
},
else => {
const abi_size = @intCast(usize, ty.abiSize(self.target));
try self.code.appendNTimes(0, abi_size);
},
}
return Result.appended;
},
else => |tag| return self.fail("TODO: Implement zig type codegen for type: '{s}'", .{tag}),
}
}
fn lowerDeclRef(self: *Self, ty: Type, val: Value, decl: *Module.Decl) InnerError!Result {
if (ty.isSlice()) {
var buf: Type.SlicePtrFieldTypeBuffer = undefined;
const slice_ty = ty.slicePtrFieldType(&buf);
switch (try self.genTypedValue(slice_ty, val)) {
.appended => {},
.externally_managed => |payload| try self.code.appendSlice(payload),
else => |tag| return self.fail("TODO: Implement zig type codegen for type: '{s}'", .{tag}),
}
var slice_len: Value.Payload.U64 = .{
.base = .{ .tag = .int_u64 },
.data = val.sliceLen(),
};
return try self.genTypedValue(Type.usize, Value.initPayload(&slice_len.base));
}
const offset = @intCast(u32, self.code.items.len);
const atom = &self.decl.link.wasm;
const target_sym_index = decl.link.wasm.sym_index;
decl.markAlive();
if (decl.ty.zigTypeTag() == .Fn) {
// We found a function pointer, so add it to our table,
// as function pointers are not allowed to be stored inside the data section,
// but rather in a function table which are called by index
try self.bin_file.addTableFunction(target_sym_index);
try atom.relocs.append(self.gpa, .{
.index = target_sym_index,
.offset = offset,
.relocation_type = .R_WASM_TABLE_INDEX_I32,
});
} else {
try atom.relocs.append(self.gpa, .{
.index = target_sym_index,
.offset = offset,
.relocation_type = .R_WASM_MEMORY_ADDR_I32,
});
}
const ptr_width = @intCast(usize, self.target.cpu.arch.ptrBitWidth() / 8);
try self.code.appendNTimes(0xaa, ptr_width);
fn lowerDeclRef(self: *DeclGen, ty: Type, val: Value, decl: *Module.Decl, writer: anytype) InnerError!Result {
if (ty.isSlice()) {
var buf: Type.SlicePtrFieldTypeBuffer = undefined;
const slice_ty = ty.slicePtrFieldType(&buf);
switch (try self.genTypedValue(slice_ty, val, writer)) {
.appended => {},
.externally_managed => |payload| try writer.writeAll(payload),
}
var slice_len: Value.Payload.U64 = .{
.base = .{ .tag = .int_u64 },
.data = val.sliceLen(),
};
return try self.genTypedValue(Type.usize, Value.initPayload(&slice_len.base), writer);
}
return Result.appended;
}
decl.markAlive();
try writer.writeIntLittle(u32, try self.bin_file.getDeclVAddr(
self.decl, // The decl containing the source symbol index
decl.ty, // type we generate the address of
self.symbol_index, // source symbol index
decl.link.wasm.sym_index, // target symbol index
@intCast(u32, self.code.items.len), // offset
));
return Result.appended;
}
};
const CallWValues = struct {
args: []WValue,
@ -1086,7 +1096,7 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu
const ret_ty = fn_ty.fnReturnType();
// Check if we store the result as a pointer to the stack rather than
// by value
if (self.isByRef(ret_ty)) {
if (isByRef(ret_ty, self.target)) {
// the sret arg will be passed as first argument, therefore we
// set the `return_value` before allocating locals for regular args.
result.return_value = .{ .local = self.local_index };
@ -1213,8 +1223,8 @@ fn arch(self: *const Self) std.Target.Cpu.Arch {
}
/// For a given `Type`, will return true when the type will be passed
/// by reference, rather than by value.
fn isByRef(self: Self, ty: Type) bool {
/// by reference, rather than by value
fn isByRef(ty: Type, target: std.Target) bool {
switch (ty.zigTypeTag()) {
.Type,
.ComptimeInt,
@ -1242,7 +1252,7 @@ fn isByRef(self: Self, ty: Type) bool {
.Frame,
.Union,
=> return ty.hasCodeGenBits(),
.Int => return if (ty.intInfo(self.target).bits > 64) true else false,
.Int => return if (ty.intInfo(target).bits > 64) true else false,
.ErrorUnion => {
const has_tag = ty.errorUnionSet().hasCodeGenBits();
const has_pl = ty.errorUnionPayload().hasCodeGenBits();
@ -1470,7 +1480,7 @@ fn airRetPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
const child_type = self.air.typeOfIndex(inst).childType();
if (child_type.abiSize(self.target) == 0) return WValue{ .none = {} };
if (self.isByRef(child_type)) {
if (isByRef(child_type, self.target)) {
return self.return_value;
}
@ -1487,7 +1497,7 @@ fn airRetLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
const ret_ty = self.air.typeOf(un_op).childType();
if (!ret_ty.hasCodeGenBits()) return WValue.none;
if (!self.isByRef(ret_ty)) {
if (!isByRef(ret_ty, self.target)) {
const result = try self.load(operand, ret_ty, 0);
try self.emitWValue(result);
}
@ -1509,7 +1519,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
else => unreachable,
};
const ret_ty = fn_ty.fnReturnType();
const first_param_sret = self.isByRef(ret_ty);
const first_param_sret = isByRef(ret_ty, self.target);
const target: ?*Decl = blk: {
const func_val = self.air.value(pl_op.operand) orelse break :blk null;
@ -1546,7 +1556,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
const operand = try self.resolveInst(pl_op.operand);
try self.emitWValue(operand);
var fn_type = try self.genFunctype(fn_ty);
var fn_type = try genFunctype(self.gpa, fn_ty, self.target);
defer fn_type.deinit(self.gpa);
const fn_type_index = try self.bin_file.putOrGetFuncType(fn_type);
@ -1642,7 +1652,7 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro
}
try self.emitWValue(lhs);
try self.emitWValue(rhs);
const valtype = try self.typeToValtype(ty);
const valtype = typeToValtype(ty, self.target);
// check if we should pass by pointer or value based on ABI size
// TODO: Implement a way to get ABI values from a given type,
// that is portable across the backend, rather than copying logic.
@ -1675,7 +1685,7 @@ fn airLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
if (!ty.hasCodeGenBits()) return WValue{ .none = {} };
if (self.isByRef(ty)) {
if (isByRef(ty, self.target)) {
const new_local = try self.allocStack(ty);
try self.store(new_local, operand, ty, 0);
return new_local;
@ -1712,7 +1722,7 @@ fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue {
};
const opcode = buildOpcode(.{
.valtype1 = try self.typeToValtype(ty),
.valtype1 = typeToValtype(ty, self.target),
.width = abi_size * 8, // use bitsize instead of byte size
.op = .load,
.signedness = signedness,
@ -1743,7 +1753,7 @@ fn airBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
const rhs = try self.resolveInst(bin_op.rhs);
const operand_ty = self.air.typeOfIndex(inst);
if (self.isByRef(operand_ty)) {
if (isByRef(operand_ty, self.target)) {
return self.fail("TODO: Implement binary operation for type: {}", .{operand_ty});
}
@ -1753,7 +1763,7 @@ fn airBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
const bin_ty = self.air.typeOf(bin_op.lhs);
const opcode: wasm.Opcode = buildOpcode(.{
.op = op,
.valtype1 = try self.typeToValtype(bin_ty),
.valtype1 = typeToValtype(bin_ty, self.target),
.signedness = if (bin_ty.isSignedInt()) .signed else .unsigned,
});
try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
@ -1775,7 +1785,7 @@ fn airWrapBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
const bin_ty = self.air.typeOf(bin_op.lhs);
const opcode: wasm.Opcode = buildOpcode(.{
.op = op,
.valtype1 = try self.typeToValtype(bin_ty),
.valtype1 = typeToValtype(bin_ty, self.target),
.signedness = if (bin_ty.isSignedInt()) .signed else .unsigned,
});
try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
@ -1924,8 +1934,8 @@ fn lowerConstant(self: *Self, val: Value, ty: Type) InnerError!WValue {
},
.ErrorSet => switch (val.tag()) {
.@"error" => {
const error_index = self.global_error_set.get(val.getError().?).?;
return WValue{ .imm32 = error_index };
const kv = try self.module.getErrorValue(val.getError().?);
return WValue{ .imm32 = kv.value };
},
else => return WValue{ .imm32 = 0 },
},
@ -1945,7 +1955,7 @@ fn lowerConstant(self: *Self, val: Value, ty: Type) InnerError!WValue {
if (val.castTag(.eu_payload)) |pl| pl.data else Value.initTag(.undef),
payload_type,
);
const pl_ptr = if (self.isByRef(payload_type))
const pl_ptr = if (isByRef(payload_type, self.target))
try self.buildPointerOffset(result, error_type.abiSize(self.target), .new)
else
result;
@ -2159,8 +2169,8 @@ fn valueAsI32(self: Self, val: Value, ty: Type) i32 {
.unsigned => return @bitCast(i32, @truncate(u32, val.toUnsignedInt())),
},
.ErrorSet => {
const error_index = self.global_error_set.get(val.getError().?).?;
return @bitCast(i32, error_index);
const kv = self.module.getErrorValue(val.getError().?) catch unreachable; // passed invalid `Value` to function
return @bitCast(i32, kv.value);
},
else => unreachable, // Programmer called this function for an illegal type
}
@ -2168,7 +2178,7 @@ fn valueAsI32(self: Self, val: Value, ty: Type) i32 {
fn airBlock(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
const block_ty = try self.genBlockType(self.air.getRefType(ty_pl.ty));
const block_ty = genBlockType(self.air.getRefType(ty_pl.ty), self.target);
const extra = self.air.extraData(Air.Block, ty_pl.payload);
const body = self.air.extra[extra.end..][0..extra.data.body_len];
@ -2265,7 +2275,7 @@ fn airCmp(self: *Self, inst: Air.Inst.Index, op: std.math.CompareOperator) Inner
// both lhs and rhs, as well as checking the payload are matching of lhs and rhs
return self.cmpOptionals(lhs, rhs, operand_ty, op);
}
} else if (self.isByRef(operand_ty)) {
} else if (isByRef(operand_ty, self.target)) {
return self.cmpBigInt(lhs, rhs, operand_ty, op);
}
@ -2280,7 +2290,7 @@ fn airCmp(self: *Self, inst: Air.Inst.Index, op: std.math.CompareOperator) Inner
break :blk operand_ty.intInfo(self.target).signedness;
};
const opcode: wasm.Opcode = buildOpcode(.{
.valtype1 = try self.typeToValtype(operand_ty),
.valtype1 = typeToValtype(operand_ty, self.target),
.op = switch (op) {
.lt => .lt,
.lte => .le,
@ -2353,13 +2363,6 @@ fn airUnreachable(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
fn airBitcast(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const operand = try self.resolveInst(ty_op.operand);
// if (operand == .constant) {
// std.debug.print("Let's take a look at this!!!!!!\n--------------------\n", .{});
// const result = try self.allocLocal(self.air.typeOfIndex(inst));
// try self.emitWValue(operand);
// try self.addLabel(.local_set, result.local);
// return result;
// }
return operand;
}
@ -2407,7 +2410,7 @@ fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
return self.fail("Field type '{}' too big to fit into stack frame", .{field_ty});
};
if (self.isByRef(field_ty)) {
if (isByRef(field_ty, self.target)) {
return self.buildPointerOffset(operand, offset, .new);
}
@ -2527,7 +2530,7 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
const val = try self.lowerConstant(case.values[0].value, target_ty);
try self.emitWValue(val);
const opcode = buildOpcode(.{
.valtype1 = try self.typeToValtype(target_ty),
.valtype1 = typeToValtype(target_ty, self.target),
.op = .ne, // not equal, because we want to jump out of this block if it does not match the condition.
.signedness = signedness,
});
@ -2541,7 +2544,7 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
const val = try self.lowerConstant(value.value, target_ty);
try self.emitWValue(val);
const opcode = buildOpcode(.{
.valtype1 = try self.typeToValtype(target_ty),
.valtype1 = typeToValtype(target_ty, self.target),
.op = .eq,
.signedness = signedness,
});
@ -2596,7 +2599,7 @@ fn airUnwrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue
const payload_ty = err_ty.errorUnionPayload();
if (!payload_ty.hasCodeGenBits()) return WValue{ .none = {} };
const offset = @intCast(u32, err_ty.errorUnionSet().abiSize(self.target));
if (self.isByRef(payload_ty)) {
if (isByRef(payload_ty, self.target)) {
return self.buildPointerOffset(operand, offset, .new);
}
return try self.load(operand, payload_ty, offset);
@ -2725,7 +2728,7 @@ fn airOptionalPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
const offset = opt_ty.abiSize(self.target) - payload_ty.abiSize(self.target);
if (self.isByRef(payload_ty)) {
if (isByRef(payload_ty, self.target)) {
return self.buildPointerOffset(operand, offset, .new);
}
@ -2856,7 +2859,7 @@ fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
const result = try self.allocLocal(elem_ty);
try self.addLabel(.local_set, result.local);
if (self.isByRef(elem_ty)) {
if (isByRef(elem_ty, self.target)) {
return result;
}
return try self.load(result, elem_ty, 0);
@ -3017,7 +3020,7 @@ fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
const result = try self.allocLocal(elem_ty);
try self.addLabel(.local_set, result.local);
if (self.isByRef(elem_ty)) {
if (isByRef(elem_ty, self.target)) {
return result;
}
return try self.load(result, elem_ty, 0);
@ -3064,7 +3067,7 @@ fn airPtrBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
else => ptr_ty.childType(),
};
const valtype = try self.typeToValtype(Type.usize);
const valtype = typeToValtype(Type.usize, self.target);
const mul_opcode = buildOpcode(.{ .valtype1 = valtype, .op = .mul });
const bin_opcode = buildOpcode(.{ .valtype1 = valtype, .op = op });
@ -3167,7 +3170,7 @@ fn airArrayElemVal(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
const result = try self.allocLocal(elem_ty);
try self.addLabel(.local_set, result.local);
if (self.isByRef(elem_ty)) {
if (isByRef(elem_ty, self.target)) {
return result;
}
return try self.load(result, elem_ty, 0);
@ -3184,8 +3187,8 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
try self.emitWValue(operand);
const op = buildOpcode(.{
.op = .trunc,
.valtype1 = try self.typeToValtype(dest_ty),
.valtype2 = try self.typeToValtype(op_ty),
.valtype1 = typeToValtype(dest_ty, self.target),
.valtype2 = typeToValtype(op_ty, self.target),
.signedness = if (dest_ty.isSignedInt()) .signed else .unsigned,
});
try self.addTag(Mir.Inst.Tag.fromOpcode(op));
@ -3249,7 +3252,7 @@ fn cmpOptionals(self: *Self, lhs: WValue, rhs: WValue, operand_ty: Type, op: std
try self.emitWValue(lhs_pl);
try self.emitWValue(rhs_pl);
const opcode = buildOpcode(.{ .op = .ne, .valtype1 = try self.typeToValtype(payload_ty) });
const opcode = buildOpcode(.{ .op = .ne, .valtype1 = typeToValtype(payload_ty, self.target) });
try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
try self.addLabel(.br_if, 0);

View File

@ -234,12 +234,12 @@ pub fn updateFunc(self: *Wasm, module: *Module, func: *Module.Fn, air: Air, live
.locals = .{},
.target = self.base.options.target,
.bin_file = self,
.global_error_set = self.base.options.module.?.global_error_set,
.module = module,
};
defer codegen.deinit();
// generate the 'code' section for the function declaration
const result = codegen.genFunc() catch |err| switch (err) {
codegen.genFunc() catch |err| switch (err) {
error.CodegenFail => {
decl.analysis = .codegen_failure;
try module.failed_decls.put(module.gpa, decl, codegen.err_msg);
@ -247,7 +247,7 @@ pub fn updateFunc(self: *Wasm, module: *Module, func: *Module.Fn, air: Air, live
},
else => |e| return e,
};
return self.finishUpdateDecl(decl, result, &codegen);
return self.finishUpdateDecl(decl, codegen.code.items);
}
// Generate code for the Decl, storing it in memory to be later written to
@ -264,40 +264,37 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
decl.link.wasm.clear();
var codegen: CodeGen = .{
var code_writer = std.ArrayList(u8).init(self.base.allocator);
defer code_writer.deinit();
var decl_gen: CodeGen.DeclGen = .{
.gpa = self.base.allocator,
.air = undefined,
.liveness = undefined,
.values = .{},
.code = std.ArrayList(u8).init(self.base.allocator),
.decl = decl,
.err_msg = undefined,
.locals = .{},
.target = self.base.options.target,
.symbol_index = decl.link.wasm.sym_index,
.bin_file = self,
.global_error_set = self.base.options.module.?.global_error_set,
.err_msg = undefined,
.code = &code_writer,
.module = module,
};
defer codegen.deinit();
// generate the 'code' section for the function declaration
const result = codegen.genDecl() catch |err| switch (err) {
const result = decl_gen.genDecl() catch |err| switch (err) {
error.CodegenFail => {
decl.analysis = .codegen_failure;
try module.failed_decls.put(module.gpa, decl, codegen.err_msg);
try module.failed_decls.put(module.gpa, decl, decl_gen.err_msg);
return;
},
else => |e| return e,
};
return self.finishUpdateDecl(decl, result, &codegen);
}
fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, result: CodeGen.Result, codegen: *CodeGen) !void {
const code: []const u8 = switch (result) {
.appended => @as([]const u8, codegen.code.items),
.externally_managed => |payload| payload,
const code = switch (result) {
.externally_managed => |data| data,
.appended => code_writer.items,
};
return self.finishUpdateDecl(decl, code);
}
fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, code: []const u8) !void {
if (decl.isExtern()) {
try self.addOrUpdateImport(decl);
return;
@ -313,7 +310,7 @@ fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, result: CodeGen.Result, cod
/// Creates a new local symbol for a given type (and its bytes it's represented by)
/// and then append it as a 'contained' atom onto the Decl.
pub fn createLocalSymbol(self: *Wasm, decl: *Module.Decl, ty: Type, code: []const u8) !u32 {
pub fn createLocalSymbol(self: *Wasm, decl: *Module.Decl, ty: Type) !u32 {
assert(ty.zigTypeTag() != .Fn); // cannot create local symbols for functions
var symbol: Symbol = .{
.name = "unnamed_local",
@ -325,9 +322,7 @@ pub fn createLocalSymbol(self: *Wasm, decl: *Module.Decl, ty: Type, code: []cons
symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
var atom = Atom.empty;
atom.size = @intCast(u32, code.len);
atom.alignment = ty.abiAlignment(self.base.options.target);
try atom.code.appendSlice(self.base.allocator, code);
if (self.symbols_free_list.popOrNull()) |index| {
atom.sym_index = index;
@ -341,6 +336,41 @@ pub fn createLocalSymbol(self: *Wasm, decl: *Module.Decl, ty: Type, code: []cons
return atom.sym_index;
}
pub fn updateLocalSymbolCode(self: *Wasm, decl: *Module.Decl, symbol_index: u32, code: []const u8) !void {
const atom = decl.link.wasm.symbolAtom(symbol_index);
atom.size = @intCast(u32, code.len);
try atom.code.appendSlice(self.base.allocator, code);
}
/// For a given decl, find the given symbol index's atom, and create a relocation for the type.
/// Returns the given pointer address
pub fn getDeclVAddr(self: *Wasm, decl: *Module.Decl, ty: Type, symbol_index: u32, target_symbol_index: u32, offset: u32) !u32 {
const atom = decl.link.wasm.symbolAtom(symbol_index);
const is_wasm32 = self.base.options.target.cpu.arch == .wasm32;
if (ty.zigTypeTag() == .Fn) {
// We found a function pointer, so add it to our table,
// as function pointers are not allowed to be stored inside the data section.
// They are instead stored in a function table which are called by index.
try self.addTableFunction(target_symbol_index);
try atom.relocs.append(self.base.allocator, .{
.index = target_symbol_index,
.offset = offset,
.relocation_type = if (is_wasm32) .R_WASM_TABLE_INDEX_I32 else .R_WASM_TABLE_INDEX_I64,
});
} else {
try atom.relocs.append(self.base.allocator, .{
.index = target_symbol_index,
.offset = offset,
.relocation_type = if (is_wasm32) .R_WASM_MEMORY_ADDR_I32 else .R_WASM_MEMORY_ADDR_I64,
});
}
// we do not know the final address at this point,
// as atom allocation will determine the address and relocations
// will calculate and rewrite this. Therefore, we simply return the symbol index
// that was targeted.
return target_symbol_index;
}
pub fn updateDeclExports(
self: *Wasm,
module: *Module,

View File

@ -31,6 +31,10 @@ next: ?*Atom,
/// is null when this atom is the first in its order
prev: ?*Atom,
/// Contains atoms local to a decl, all managed by this `Atom`.
/// When the parent atom is being freed, it will also do so for all local atoms.
locals: std.ArrayListUnmanaged(Atom) = .{},
/// Represents a default empty wasm `Atom`
pub const empty: Atom = .{
.alignment = 0,
@ -45,6 +49,11 @@ pub const empty: Atom = .{
pub fn deinit(self: *Atom, gpa: Allocator) void {
self.relocs.deinit(gpa);
self.code.deinit(gpa);
while (self.locals.popOrNull()) |*local| {
local.deinit(gpa);
}
self.locals.deinit(gpa);
}
/// Sets the length of relocations and code to '0',
@ -72,6 +81,15 @@ pub fn getFirst(self: *Atom) *Atom {
return tmp;
}
/// Returns the atom for the given `symbol_index`.
/// This can be either the `Atom` itself, or one of its locals.
pub fn symbolAtom(self: *Atom, symbol_index: u32) *Atom {
if (self.sym_index == symbol_index) return self;
return for (self.locals.items) |*local_atom| {
if (local_atom.sym_index == symbol_index) break local_atom;
} else unreachable; // Used a symbol index not present in this atom or its children.
}
/// Resolves the relocations within the atom, writing the new value
/// at the calculated offset.
pub fn resolveRelocs(self: *Atom, wasm_bin: *const Wasm) !void {