aboutsummaryrefslogtreecommitdiff
path: root/src/arch/wasm/CodeGen.zig
diff options
context:
space:
mode:
authorLuuk de Gram <luuk@degram.dev>2021-11-28 20:25:33 +0100
committerLuuk de Gram <luuk@degram.dev>2021-11-28 20:25:33 +0100
commitdd49eca34274cd2396cbe06a199fe8db9e8faf79 (patch)
tree928ab49cdd2021db17a83f7bb5c5f3cb19e10bef /src/arch/wasm/CodeGen.zig
parent7226ad2670f267b4d90b84d0e104fbb1fa41fe49 (diff)
downloadzig-dd49eca34274cd2396cbe06a199fe8db9e8faf79.tar.gz
zig-dd49eca34274cd2396cbe06a199fe8db9e8faf79.zip
wasm: Implement 'zig test'
- This implements the required codegen for decl types such as pointers, arrays, structs and more. - Wasm's start function can now use both a 'u8' and 'void' as return type. This will help us with writing tests using the stage2 testing backend. (Until all tests of behavioural tests pass). - Now correctly generates relocations for function pointers. - Also implements unwrapping error union error, as well as return pointers.
Diffstat (limited to 'src/arch/wasm/CodeGen.zig')
-rw-r--r--src/arch/wasm/CodeGen.zig210
1 files changed, 186 insertions, 24 deletions
diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig
index cbda80aee3..9e4315ead9 100644
--- a/src/arch/wasm/CodeGen.zig
+++ b/src/arch/wasm/CodeGen.zig
@@ -692,6 +692,7 @@ fn typeToValtype(self: *Self, ty: Type) InnerError!wasm.Valtype {
.Struct,
.ErrorUnion,
.Optional,
+ .Fn,
=> wasm.Valtype.i32,
else => self.fail("TODO - Wasm valtype for type '{}'", .{ty}),
};
@@ -809,23 +810,52 @@ pub fn genFunc(self: *Self) InnerError!Result {
}
/// Generates the wasm bytecode for the declaration belonging to `Context`
-pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result {
+pub fn genDecl(self: *Self, ty: Type, val: Value) InnerError!Result {
+ if (val.isUndef()) {
+ try self.code.appendNTimes(0xaa, ty.abiSize(self.target));
+ return Result.appended;
+ }
switch (ty.zigTypeTag()) {
.Fn => {
- 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
+ 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(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.genDecl(payload_type, payload.data);
+ } else if (!val.isNull()) {
+ return try self.genDecl(payload_type, val);
+ } else {
+ try self.code.appendNTimes(0, ty.abiSize(self.target));
+ return Result.appended;
+ }
+ }
+ // `null-tag` byte
+ try self.code.appendNTimes(@boolToInt(!val.isNull()), 4);
+ const pl_result = try self.genDecl(
+ 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),
}
- return self.fail("TODO implement wasm codegen for function pointers", .{});
+ return Result.appended;
},
- .Array => {
- if (val.castTag(.bytes)) |payload| {
+ .Array => switch (val.tag()) {
+ .bytes => {
+ const payload = val.castTag(.bytes).?;
if (ty.sentinel()) |sentinel| {
try self.code.appendSlice(payload.data);
- switch (try self.gen(ty.childType(), sentinel)) {
+ switch (try self.genDecl(ty.childType(), sentinel)) {
.appended => return Result.appended,
.externally_managed => |data| {
try self.code.appendSlice(data);
@@ -834,16 +864,33 @@ pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result {
}
}
return Result{ .externally_managed = payload.data };
- } else return self.fail("TODO implement gen for more kinds of arrays", .{});
+ },
+ .array => {
+ const elem_vals = val.castTag(.array).?.data;
+ const elem_ty = ty.elemType();
+ for (elem_vals) |elem_val| {
+ switch (try self.genDecl(elem_ty, elem_val)) {
+ .appended => {},
+ .externally_managed => |data| {
+ try self.code.appendSlice(data);
+ },
+ }
+ }
+ return Result.appended;
+ },
+ else => return self.fail("TODO implement genDecl for array type value: {s}", .{@tagName(val.tag())}),
},
.Int => {
const info = ty.intInfo(self.target);
- if (info.bits == 8 and info.signedness == .unsigned) {
- const int_byte = val.toUnsignedInt();
- try self.code.append(@intCast(u8, int_byte));
- return Result.appended;
- }
- return self.fail("TODO: Implement codegen for int type: '{}'", .{ty});
+ const abi_size = ty.abiSize(self.target);
+ // todo: Implement integer sizes larger than 64bits
+ if (info.bits > 64) return self.fail("TODO: Implement genDecl 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 => {
try self.emitConstant(val, ty);
@@ -855,15 +902,83 @@ pub fn gen(self: *Self, ty: Type, val: Value) InnerError!Result {
return Result.appended;
},
.Struct => {
- // TODO write the fields for real
- const abi_size = try std.math.cast(usize, ty.abiSize(self.target));
+ 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.genDecl(field_ty, field_val)) {
+ .appended => {},
+ .externally_managed => |payload| try self.code.appendSlice(payload),
+ }
+ }
+ return Result.appended;
+ },
+ .Union => {
+ // TODO: Implement Union declarations
+ const abi_size = ty.abiSize(self.target);
try self.code.writer().writeByteNTimes(0xaa, abi_size);
- return Result{ .appended = {} };
+ return Result.appended;
+ },
+ .Pointer => switch (val.tag()) {
+ .variable => {
+ const decl = val.castTag(.variable).?.data.owner_decl;
+ return try self.lowerDeclRef(decl);
+ },
+ .decl_ref => {
+ const decl = val.castTag(.decl_ref).?.data;
+ return try self.lowerDeclRef(decl);
+ },
+ .slice => {
+ const slice = val.castTag(.slice).?.data;
+ var buf: Type.SlicePtrFieldTypeBuffer = undefined;
+ const ptr_ty = ty.slicePtrFieldType(&buf);
+ switch (try self.genDecl(ptr_ty, slice.ptr)) {
+ .externally_managed => |data| try self.code.appendSlice(data),
+ .appended => {},
+ }
+ switch (try self.genDecl(Type.usize, slice.len)) {
+ .externally_managed => |data| try self.code.appendSlice(data),
+ .appended => {},
+ }
+ return Result.appended;
+ },
+ else => return self.fail("TODO: Implement zig decl gen for pointer type value: '{s}'", .{@tagName(val.tag())}),
},
else => |tag| return self.fail("TODO: Implement zig type codegen for type: '{s}'", .{tag}),
}
}
+fn lowerDeclRef(self: *Self, decl: *Module.Decl) InnerError!Result {
+ decl.alive = true;
+
+ const offset = @intCast(u32, self.code.items.len);
+ const atom = &self.decl.link.wasm;
+ const target_sym_index = decl.link.wasm.sym_index;
+
+ 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 = self.target.cpu.arch.ptrBitWidth() / 8;
+ try self.code.appendNTimes(0xaa, ptr_width);
+
+ return Result.appended;
+}
+
const CallWValues = struct {
args: []WValue,
return_value: WValue,
@@ -1015,6 +1130,8 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
.loop => self.airLoop(inst),
.not => self.airNot(inst),
.ret => self.airRet(inst),
+ .ret_ptr => self.airRetPtr(inst),
+ .ret_load => self.airRetLoad(inst),
.slice_len => self.airSliceLen(inst),
.slice_elem_val => self.airSliceElemVal(inst),
.store => self.airStore(inst),
@@ -1029,6 +1146,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
.wrap_optional => self.airWrapOptional(inst),
.unwrap_errunion_payload => self.airUnwrapErrUnionPayload(inst),
+ .unwrap_errunion_err => self.airUnwrapErrUnionError(inst),
.wrap_errunion_payload => self.airWrapErrUnionPayload(inst),
.optional_payload => self.airOptionalPayload(inst),
@@ -1061,6 +1179,34 @@ fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
return .none;
}
+fn airRetPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ const child_type = self.air.typeOfIndex(inst).childType();
+
+ // Initialize the stack
+ if (self.initial_stack_value == .none) {
+ try self.initializeStack();
+ }
+
+ const abi_size = child_type.abiSize(self.target);
+ if (abi_size == 0) return WValue{ .none = {} };
+
+ // local, containing the offset to the stack position
+ const local = try self.allocLocal(Type.initTag(.i32)); // always pointer therefore i32
+ try self.moveStack(@intCast(u32, abi_size), local.local);
+
+ return local;
+}
+
+fn airRetLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ const un_op = self.air.instructions.items(.data)[inst].un_op;
+ const operand = self.resolveInst(un_op);
+ const result = try self.load(operand, self.air.typeOf(un_op), 0);
+ try self.addLabel(.local_get, result.local);
+ try self.restoreStackPointer();
+ try self.addTag(.@"return");
+ return .none;
+}
+
fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
const extra = self.air.extraData(Air.Call, pl_op.payload);
@@ -1096,6 +1242,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
// so load its value onto the stack
std.debug.assert(ty.zigTypeTag() == .Pointer);
const operand = self.resolveInst(pl_op.operand);
+ try self.emitWValue(operand);
const result = try self.load(operand, fn_ty, operand.local_with_offset.offset);
try self.addLabel(.local_get, result.local);
@@ -1229,6 +1376,8 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro
// that is portable across the backend, rather than copying logic.
const abi_size = if ((ty.isInt() or ty.isAnyFloat()) and ty.abiSize(self.target) <= 8)
@intCast(u8, ty.abiSize(self.target))
+ else if (ty.zigTypeTag() == .ErrorSet or ty.zigTypeTag() == .Enum)
+ @intCast(u8, ty.abiSize(self.target))
else
@as(u8, 4);
const opcode = buildOpcode(.{
@@ -1272,6 +1421,8 @@ fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue {
// that is portable across the backend, rather than copying logic.
const abi_size = if ((ty.isInt() or ty.isAnyFloat()) and ty.abiSize(self.target) <= 8)
@intCast(u8, ty.abiSize(self.target))
+ else if (ty.zigTypeTag() == .ErrorSet or ty.zigTypeTag() == .Enum)
+ @intCast(u8, ty.abiSize(self.target))
else
@as(u8, 4);
@@ -1920,6 +2071,15 @@ fn airUnwrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue
return try self.load(operand, payload_ty, offset);
}
+fn airUnwrapErrUnionError(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ if (self.liveness.isUnused(inst)) return WValue.none;
+
+ const ty_op = self.air.instructions.items(.data)[inst].ty_op;
+ const operand = self.resolveInst(ty_op.operand);
+ const err_ty = self.air.typeOf(ty_op.operand);
+ return try self.load(operand, err_ty.errorUnionSet(), 0);
+}
+
fn airWrapErrUnionPayload(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
_ = ty_op;
@@ -1935,18 +2095,20 @@ fn airIntcast(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
const op_bits = ref_info.bits;
const wanted_bits = ty.intInfo(self.target).bits;
- try self.emitWValue(operand);
if (op_bits > 32 and wanted_bits <= 32) {
+ try self.emitWValue(operand);
try self.addTag(.i32_wrap_i64);
} else if (op_bits <= 32 and wanted_bits > 32) {
+ try self.emitWValue(operand);
try self.addTag(switch (ref_info.signedness) {
.signed => .i64_extend_i32_s,
.unsigned => .i64_extend_i32_u,
});
- }
+ } else return operand;
- // other cases are no-op
- return .none;
+ const result = try self.allocLocal(ty);
+ try self.addLabel(.local_set, result.local);
+ return result;
}
fn airIsNull(self: *Self, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {