Commit 89b1fdc443
Changed files (5)
lib/std/wasm.zig
@@ -212,6 +212,28 @@ test "Wasm - opcodes" {
try testing.expectEqual(@as(u16, 0xC4), i64_extend32_s);
}
+/// Opcodes that require a prefix `0xFC`
+pub const PrefixedOpcode = enum(u8) {
+ i32_trunc_sat_f32_s = 0x00,
+ i32_trunc_sat_f32_u = 0x01,
+ i32_trunc_sat_f64_s = 0x02,
+ i32_trunc_sat_f64_u = 0x03,
+ i64_trunc_sat_f32_s = 0x04,
+ i64_trunc_sat_f32_u = 0x05,
+ i64_trunc_sat_f64_s = 0x06,
+ i64_trunc_sat_f64_u = 0x07,
+ memory_init = 0x08,
+ data_drop = 0x09,
+ memory_copy = 0x0A,
+ memory_fill = 0x0B,
+ table_init = 0x0C,
+ elem_drop = 0x0D,
+ table_copy = 0x0E,
+ table_grow = 0x0F,
+ table_size = 0x10,
+ table_fill = 0x11,
+};
+
/// Enum representing all Wasm value types as per spec:
/// https://webassembly.github.io/spec/core/binary/types.html
pub const Valtype = enum(u8) {
@@ -266,7 +288,7 @@ pub const InitExpression = union(enum) {
global_get: u32,
};
-///
+/// Represents a function entry, holding the index to its type
pub const Func = struct {
type_index: u32,
};
src/arch/wasm/CodeGen.zig
@@ -623,6 +623,10 @@ fn addTag(self: *Self, tag: Mir.Inst.Tag) error{OutOfMemory}!void {
try self.addInst(.{ .tag = tag, .data = .{ .tag = {} } });
}
+fn addExtended(self: *Self, opcode: wasm.PrefixedOpcode) error{OutOfMemory}!void {
+ try self.addInst(.{ .tag = .extended, .secondary = @enumToInt(opcode), .data = .{ .tag = {} } });
+}
+
fn addLabel(self: *Self, tag: Mir.Inst.Tag, label: u32) error{OutOfMemory}!void {
try self.addInst(.{ .tag = tag, .data = .{ .label = label } });
}
@@ -746,6 +750,13 @@ fn genFunctype(self: *Self, fn_ty: Type) !wasm.Type {
defer params.deinit();
var returns = std.ArrayList(wasm.Valtype).init(self.gpa);
defer returns.deinit();
+ const return_type = fn_ty.fnReturnType();
+
+ const want_sret = isByRef(return_type);
+
+ if (want_sret) {
+ try params.append(try self.typeToValtype(Type.usize));
+ }
// param types
if (fn_ty.fnParamLen() != 0) {
@@ -759,11 +770,8 @@ fn genFunctype(self: *Self, fn_ty: Type) !wasm.Type {
}
// return type
- const return_type = fn_ty.fnReturnType();
- switch (return_type.zigTypeTag()) {
- .Void, .NoReturn => {},
- .Struct => return self.fail("TODO: Implement struct as return type for wasm", .{}),
- else => try returns.append(try self.typeToValtype(return_type)),
+ if (!want_sret and return_type.hasCodeGenBits()) {
+ try returns.append(try self.typeToValtype(return_type));
}
return wasm.Type{
@@ -785,6 +793,15 @@ pub fn genFunc(self: *Self) InnerError!Result {
// Generate MIR for function body
try self.genBody(self.air.getMainBody());
+ // In case we have a return value, but the last instruction is a noreturn (such as a while loop)
+ // we emit an unreachable instruction to tell the stack validator that part will never be reached.
+ if (func_type.returns.len != 0 and self.air.instructions.len > 0) {
+ const inst = @intCast(u32, self.air.instructions.len - 1);
+ if (self.air.typeOfIndex(inst).isNoReturn()) {
+ try self.addTag(.@"unreachable");
+ }
+ }
+
// End of function body
try self.addTag(.end);
@@ -1074,6 +1091,15 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu
.return_value = .none,
};
errdefer self.gpa.free(result.args);
+ const ret_ty = fn_ty.fnReturnType();
+ // Check if we store the result as a pointer to the stack rather than
+ // by value
+ if (isByRef(ret_ty)) {
+ // 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 };
+ self.local_index += 1;
+ }
switch (cc) {
.Naked => return result,
.Unspecified, .C => {
@@ -1086,19 +1112,6 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu
result.args[ty_index] = .{ .local = self.local_index };
self.local_index += 1;
}
-
- const ret_ty = fn_ty.fnReturnType();
- // Check if we store the result as a pointer to the stack rather than
- // by value
- if (isByRef(ret_ty)) {
- if (self.initial_stack_value == .none) try self.initializeStack();
- result.return_value = try self.allocStack(ret_ty);
-
- // We want to make sure the return value's stack value doesn't get overwritten,
- // so set initial stack value to current's position instead.
- try self.addLabel(.global_get, 0);
- try self.addLabel(.local_set, self.initial_stack_value.local);
- }
},
else => return self.fail("TODO implement function parameters for cc '{}' on wasm", .{cc}),
}
@@ -1323,6 +1336,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
.load => self.airLoad(inst),
.loop => self.airLoop(inst),
+ .memset => self.airMemset(inst),
.not => self.airNot(inst),
.optional_payload => self.airOptionalPayload(inst),
.optional_payload_ptr => self.airOptionalPayloadPtr(inst),
@@ -1335,18 +1349,21 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
.ret => self.airRet(inst),
.ret_ptr => self.airRetPtr(inst),
.ret_load => self.airRetLoad(inst),
+
.slice => self.airSlice(inst),
.slice_len => self.airSliceLen(inst),
.slice_elem_val => self.airSliceElemVal(inst),
.slice_elem_ptr => self.airSliceElemPtr(inst),
.slice_ptr => self.airSlicePtr(inst),
.store => self.airStore(inst),
+
.struct_field_ptr => self.airStructFieldPtr(inst),
.struct_field_ptr_index_0 => self.airStructFieldPtrIndex(inst, 0),
.struct_field_ptr_index_1 => self.airStructFieldPtrIndex(inst, 1),
.struct_field_ptr_index_2 => self.airStructFieldPtrIndex(inst, 2),
.struct_field_ptr_index_3 => self.airStructFieldPtrIndex(inst, 3),
.struct_field_val => self.airStructFieldVal(inst),
+
.switch_br => self.airSwitchBr(inst),
.trunc => self.airTrunc(inst),
.unreach => self.airUnreachable(inst),
@@ -1374,7 +1391,6 @@ fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
// to the stack instead
if (self.return_value != .none) {
try self.store(self.return_value, operand, self.decl.ty.fnReturnType(), 0);
- try self.emitWValue(self.return_value);
} else {
try self.emitWValue(operand);
}
@@ -1393,6 +1409,9 @@ fn airRetPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
if (child_type.abiSize(self.target) == 0) return WValue{ .none = {} };
+ if (isByRef(child_type)) {
+ return self.return_value;
+ }
return self.allocStack(child_type);
}
@@ -1402,9 +1421,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 (isByRef(ret_ty)) {
- try self.emitWValue(operand);
- } else {
+ if (!isByRef(ret_ty)) {
const result = try self.load(operand, ret_ty, 0);
try self.emitWValue(result);
}
@@ -1425,6 +1442,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
.Pointer => ty.childType(),
else => unreachable,
};
+ const ret_ty = fn_ty.fnReturnType();
+ const first_param_sret = isByRef(ret_ty);
const target: ?*Decl = blk: {
const func_val = self.air.value(pl_op.operand) orelse break :blk null;
@@ -1437,6 +1456,12 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
return self.fail("Expected a function, but instead found type '{s}'", .{func_val.tag()});
};
+ const sret = if (first_param_sret) blk: {
+ const sret_local = try self.allocStack(ret_ty);
+ try self.emitWValue(sret_local);
+ break :blk sret_local;
+ } else WValue{ .none = {} };
+
for (args) |arg| {
const arg_ref = @intToEnum(Air.Inst.Ref, arg);
const arg_val = self.resolveInst(arg_ref);
@@ -1475,31 +1500,18 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
try self.addLabel(.call_indirect, fn_type_index);
}
- const ret_ty = fn_ty.fnReturnType();
- if (!ret_ty.hasCodeGenBits()) return WValue.none;
-
- // TODO: Implement this for all aggregate types
- if (ret_ty.isSlice()) {
- // first load the values onto the regular stack, before we move the stack pointer
- // to prevent overwriting the return value.
- const tmp = try self.allocLocal(ret_ty);
- try self.addLabel(.local_set, tmp.local);
- const field_ty = Type.@"usize";
- const offset = @intCast(u32, field_ty.abiSize(self.target));
- const ptr_local = try self.load(tmp, field_ty, 0);
- const len_local = try self.load(tmp, field_ty, offset);
-
- // As our values are now safe, we reserve space on the virtual stack and
- // store the values there.
- const result = try self.allocStack(ret_ty);
- try self.store(result, ptr_local, field_ty, 0);
- try self.store(result, len_local, field_ty, offset);
- return result;
+ if (self.liveness.isUnused(inst) or !ret_ty.hasCodeGenBits()) {
+ return WValue.none;
+ } else if (ret_ty.isNoReturn()) {
+ try self.addTag(.@"unreachable");
+ return WValue.none;
+ } else if (first_param_sret) {
+ return sret;
+ } else {
+ const result_local = try self.allocLocal(ret_ty);
+ try self.addLabel(.local_set, result_local.local);
+ return result_local;
}
-
- const result_local = try self.allocLocal(ret_ty);
- try self.addLabel(.local_set, result_local.local);
- return result_local;
}
fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
@@ -1989,7 +2001,7 @@ fn emitUndefined(self: *Self, ty: Type) InnerError!void {
// validator will not accept it due to out-of-bounds memory access);
.Array => try self.addImm32(@bitCast(i32, @as(u32, 0xaa))),
.Struct => {
- // TODO: Write 0xaa to each field
+ // TODO: Write 0xaa struct's memory
const result = try self.allocStack(ty);
try self.addLabel(.local_get, result.local);
},
@@ -2943,3 +2955,71 @@ fn airPtrBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
try self.addLabel(.local_set, result.local);
return result;
}
+
+fn airMemset(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
+ const pl_op = self.air.instructions.items(.data)[inst].pl_op;
+ const bin_op = self.air.extraData(Air.Bin, pl_op.payload).data;
+
+ const ptr = self.resolveInst(pl_op.operand);
+ const value = self.resolveInst(bin_op.lhs);
+ const len = self.resolveInst(bin_op.rhs);
+ try self.memSet(ptr, len, value);
+
+ return WValue.none;
+}
+
+/// Sets a region of memory at `ptr` to the value of `value`
+/// When the user has enabled the bulk_memory feature, we lower
+/// this to wasm's memset instruction. When the feature is not present,
+/// we implement it manually.
+fn memSet(self: *Self, ptr: WValue, len: WValue, value: WValue) InnerError!void {
+ // When bulk_memory is enabled, we lower it to wasm's memset instruction.
+ // If not, we lower it ourselves
+ if (std.Target.wasm.featureSetHas(self.target.cpu.features, .bulk_memory)) {
+ try self.emitWValue(ptr);
+ try self.emitWValue(value);
+ try self.emitWValue(len);
+ try self.addExtended(.memory_fill);
+ return;
+ }
+
+ // TODO: We should probably lower this to a call to compiler_rt
+ // But for now, we implement it manually
+ const offset = try self.allocLocal(Type.usize); // local for counter
+ // outer block to jump to when loop is done
+ try self.startBlock(.block, wasm.block_empty);
+ try self.startBlock(.loop, wasm.block_empty);
+ try self.emitWValue(offset);
+ try self.emitWValue(len);
+ switch (self.ptrSize()) {
+ 4 => try self.addTag(.i32_eq),
+ 8 => try self.addTag(.i64_eq),
+ else => unreachable,
+ }
+ try self.addLabel(.br_if, 1); // jump out of loop into outer block (finished)
+ try self.emitWValue(ptr);
+ try self.emitWValue(offset);
+ switch (self.ptrSize()) {
+ 4 => try self.addTag(.i32_add),
+ 8 => try self.addTag(.i64_add),
+ else => unreachable,
+ }
+ try self.emitWValue(value);
+ const mem_store_op: Mir.Inst.Tag = switch (self.ptrSize()) {
+ 4 => .i32_store8,
+ 8 => .i64_store8,
+ else => unreachable,
+ };
+ try self.addMemArg(mem_store_op, .{ .offset = 0, .alignment = 1 });
+ try self.emitWValue(offset);
+ try self.addImm32(1);
+ switch (self.ptrSize()) {
+ 4 => try self.addTag(.i32_add),
+ 8 => try self.addTag(.i64_add),
+ else => unreachable,
+ }
+ try self.addLabel(.local_set, offset.local);
+ try self.addLabel(.br, 0); // jump to start of loop
+ try self.endBlock();
+ try self.endBlock();
+}
src/arch/wasm/Emit.zig
@@ -161,6 +161,8 @@ pub fn emitMir(emit: *Emit) InnerError!void {
.i64_extend8_s => try emit.emitTag(tag),
.i64_extend16_s => try emit.emitTag(tag),
.i64_extend32_s => try emit.emitTag(tag),
+
+ .extended => try emit.emitExtended(inst),
}
}
}
@@ -321,3 +323,20 @@ fn emitMemAddress(emit: *Emit, inst: Mir.Inst.Index) !void {
.relocation_type = .R_WASM_MEMORY_ADDR_LEB,
});
}
+
+fn emitExtended(emit: *Emit, inst: Mir.Inst.Index) !void {
+ const opcode = emit.mir.instructions.items(.secondary)[inst];
+ switch (@intToEnum(std.wasm.PrefixedOpcode, opcode)) {
+ .memory_fill => try emit.emitMemFill(),
+ else => |tag| return emit.fail("TODO: Implement extension instruction: {s}\n", .{@tagName(tag)}),
+ }
+}
+
+fn emitMemFill(emit: *Emit) !void {
+ try emit.code.append(0xFC);
+ try emit.code.append(0x0B);
+ // When multi-memory proposal reaches phase 4, we
+ // can emit a different memory index here.
+ // For now we will always emit index 0.
+ try leb128.writeULEB128(emit.code.writer(), @as(u32, 0));
+}
src/arch/wasm/Mir.zig
@@ -19,6 +19,9 @@ extra: []const u32,
pub const Inst = struct {
/// The opcode that represents this instruction
tag: Tag,
+ /// This opcode will be set when `tag` represents an extended
+ /// instruction with prefix 0xFC, or a simd instruction with prefix 0xFD.
+ secondary: u8 = 0,
/// Data is determined by the set `tag`.
/// For example, `data` will be an i32 for when `tag` is 'i32_const'.
data: Data,
@@ -373,6 +376,11 @@ pub const Inst = struct {
i64_extend16_s = 0xC3,
/// Uses `tag`
i64_extend32_s = 0xC4,
+ /// The instruction consists of an extension opcode
+ /// set in `secondary`
+ ///
+ /// The `data` field depends on the extension instruction
+ extended = 0xFC,
/// Contains a symbol to a function pointer
/// uses `label`
///
test/behavior.zig
@@ -39,16 +39,23 @@ test {
_ = @import("behavior/defer.zig");
_ = @import("behavior/enum.zig");
_ = @import("behavior/error.zig");
+ _ = @import("behavior/generics.zig");
_ = @import("behavior/if.zig");
_ = @import("behavior/import.zig");
_ = @import("behavior/incomplete_struct_param_tld.zig");
_ = @import("behavior/inttoptr.zig");
+ _ = @import("behavior/member_func.zig");
+ _ = @import("behavior/null.zig");
_ = @import("behavior/pointers.zig");
_ = @import("behavior/ptrcast.zig");
_ = @import("behavior/ref_var_in_if_after_if_2nd_switch_prong.zig");
+ _ = @import("behavior/struct.zig");
+ _ = @import("behavior/this.zig");
_ = @import("behavior/truncate.zig");
- _ = @import("behavior/usingnamespace.zig");
_ = @import("behavior/underscore.zig");
+ _ = @import("behavior/usingnamespace.zig");
+ _ = @import("behavior/void.zig");
+ _ = @import("behavior/while.zig");
if (!builtin.zig_is_stage2 or builtin.stage2_arch != .wasm32) {
// Tests that pass for stage1, llvm backend, C backend
@@ -56,16 +63,9 @@ test {
_ = @import("behavior/array.zig");
_ = @import("behavior/cast.zig");
_ = @import("behavior/for.zig");
- _ = @import("behavior/generics.zig");
_ = @import("behavior/int128.zig");
- _ = @import("behavior/member_func.zig");
- _ = @import("behavior/null.zig");
_ = @import("behavior/optional.zig");
- _ = @import("behavior/struct.zig");
- _ = @import("behavior/this.zig");
_ = @import("behavior/translate_c_macros.zig");
- _ = @import("behavior/while.zig");
- _ = @import("behavior/void.zig");
if (builtin.object_format != .c) {
// Tests that pass for stage1 and the llvm backend.