mirror of
https://github.com/ziglang/zig.git
synced 2025-12-24 07:03:11 +00:00
wasm: Implement memset, and sret arguments.
We now detect if the return type will be set by passing the first argument as a pointer to stack memory from the callee's frame. This way, we do not have to worry about stack memory being overwritten. Besides this, we implement memset by either using wasm's memory.fill instruction when available, or lower it manually. In the future we can lower this to a compiler_rt call.
This commit is contained in:
parent
5c21a45cf0
commit
89b1fdc443
@ -212,6 +212,28 @@ test "Wasm - opcodes" {
|
|||||||
try testing.expectEqual(@as(u16, 0xC4), i64_extend32_s);
|
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:
|
/// Enum representing all Wasm value types as per spec:
|
||||||
/// https://webassembly.github.io/spec/core/binary/types.html
|
/// https://webassembly.github.io/spec/core/binary/types.html
|
||||||
pub const Valtype = enum(u8) {
|
pub const Valtype = enum(u8) {
|
||||||
@ -266,7 +288,7 @@ pub const InitExpression = union(enum) {
|
|||||||
global_get: u32,
|
global_get: u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
///
|
/// Represents a function entry, holding the index to its type
|
||||||
pub const Func = struct {
|
pub const Func = struct {
|
||||||
type_index: u32,
|
type_index: u32,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -623,6 +623,10 @@ fn addTag(self: *Self, tag: Mir.Inst.Tag) error{OutOfMemory}!void {
|
|||||||
try self.addInst(.{ .tag = tag, .data = .{ .tag = {} } });
|
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 {
|
fn addLabel(self: *Self, tag: Mir.Inst.Tag, label: u32) error{OutOfMemory}!void {
|
||||||
try self.addInst(.{ .tag = tag, .data = .{ .label = label } });
|
try self.addInst(.{ .tag = tag, .data = .{ .label = label } });
|
||||||
}
|
}
|
||||||
@ -746,6 +750,13 @@ fn genFunctype(self: *Self, fn_ty: Type) !wasm.Type {
|
|||||||
defer params.deinit();
|
defer params.deinit();
|
||||||
var returns = std.ArrayList(wasm.Valtype).init(self.gpa);
|
var returns = std.ArrayList(wasm.Valtype).init(self.gpa);
|
||||||
defer returns.deinit();
|
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
|
// param types
|
||||||
if (fn_ty.fnParamLen() != 0) {
|
if (fn_ty.fnParamLen() != 0) {
|
||||||
@ -759,11 +770,8 @@ fn genFunctype(self: *Self, fn_ty: Type) !wasm.Type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// return type
|
// return type
|
||||||
const return_type = fn_ty.fnReturnType();
|
if (!want_sret and return_type.hasCodeGenBits()) {
|
||||||
switch (return_type.zigTypeTag()) {
|
try returns.append(try self.typeToValtype(return_type));
|
||||||
.Void, .NoReturn => {},
|
|
||||||
.Struct => return self.fail("TODO: Implement struct as return type for wasm", .{}),
|
|
||||||
else => try returns.append(try self.typeToValtype(return_type)),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return wasm.Type{
|
return wasm.Type{
|
||||||
@ -785,6 +793,15 @@ pub fn genFunc(self: *Self) InnerError!Result {
|
|||||||
|
|
||||||
// Generate MIR for function body
|
// Generate MIR for function body
|
||||||
try self.genBody(self.air.getMainBody());
|
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
|
// End of function body
|
||||||
try self.addTag(.end);
|
try self.addTag(.end);
|
||||||
|
|
||||||
@ -1074,6 +1091,15 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu
|
|||||||
.return_value = .none,
|
.return_value = .none,
|
||||||
};
|
};
|
||||||
errdefer self.gpa.free(result.args);
|
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) {
|
switch (cc) {
|
||||||
.Naked => return result,
|
.Naked => return result,
|
||||||
.Unspecified, .C => {
|
.Unspecified, .C => {
|
||||||
@ -1086,19 +1112,6 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu
|
|||||||
result.args[ty_index] = .{ .local = self.local_index };
|
result.args[ty_index] = .{ .local = self.local_index };
|
||||||
self.local_index += 1;
|
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}),
|
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),
|
.load => self.airLoad(inst),
|
||||||
.loop => self.airLoop(inst),
|
.loop => self.airLoop(inst),
|
||||||
|
.memset => self.airMemset(inst),
|
||||||
.not => self.airNot(inst),
|
.not => self.airNot(inst),
|
||||||
.optional_payload => self.airOptionalPayload(inst),
|
.optional_payload => self.airOptionalPayload(inst),
|
||||||
.optional_payload_ptr => self.airOptionalPayloadPtr(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 => self.airRet(inst),
|
||||||
.ret_ptr => self.airRetPtr(inst),
|
.ret_ptr => self.airRetPtr(inst),
|
||||||
.ret_load => self.airRetLoad(inst),
|
.ret_load => self.airRetLoad(inst),
|
||||||
|
|
||||||
.slice => self.airSlice(inst),
|
.slice => self.airSlice(inst),
|
||||||
.slice_len => self.airSliceLen(inst),
|
.slice_len => self.airSliceLen(inst),
|
||||||
.slice_elem_val => self.airSliceElemVal(inst),
|
.slice_elem_val => self.airSliceElemVal(inst),
|
||||||
.slice_elem_ptr => self.airSliceElemPtr(inst),
|
.slice_elem_ptr => self.airSliceElemPtr(inst),
|
||||||
.slice_ptr => self.airSlicePtr(inst),
|
.slice_ptr => self.airSlicePtr(inst),
|
||||||
.store => self.airStore(inst),
|
.store => self.airStore(inst),
|
||||||
|
|
||||||
.struct_field_ptr => self.airStructFieldPtr(inst),
|
.struct_field_ptr => self.airStructFieldPtr(inst),
|
||||||
.struct_field_ptr_index_0 => self.airStructFieldPtrIndex(inst, 0),
|
.struct_field_ptr_index_0 => self.airStructFieldPtrIndex(inst, 0),
|
||||||
.struct_field_ptr_index_1 => self.airStructFieldPtrIndex(inst, 1),
|
.struct_field_ptr_index_1 => self.airStructFieldPtrIndex(inst, 1),
|
||||||
.struct_field_ptr_index_2 => self.airStructFieldPtrIndex(inst, 2),
|
.struct_field_ptr_index_2 => self.airStructFieldPtrIndex(inst, 2),
|
||||||
.struct_field_ptr_index_3 => self.airStructFieldPtrIndex(inst, 3),
|
.struct_field_ptr_index_3 => self.airStructFieldPtrIndex(inst, 3),
|
||||||
.struct_field_val => self.airStructFieldVal(inst),
|
.struct_field_val => self.airStructFieldVal(inst),
|
||||||
|
|
||||||
.switch_br => self.airSwitchBr(inst),
|
.switch_br => self.airSwitchBr(inst),
|
||||||
.trunc => self.airTrunc(inst),
|
.trunc => self.airTrunc(inst),
|
||||||
.unreach => self.airUnreachable(inst),
|
.unreach => self.airUnreachable(inst),
|
||||||
@ -1374,7 +1391,6 @@ fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
|
|||||||
// to the stack instead
|
// to the stack instead
|
||||||
if (self.return_value != .none) {
|
if (self.return_value != .none) {
|
||||||
try self.store(self.return_value, operand, self.decl.ty.fnReturnType(), 0);
|
try self.store(self.return_value, operand, self.decl.ty.fnReturnType(), 0);
|
||||||
try self.emitWValue(self.return_value);
|
|
||||||
} else {
|
} else {
|
||||||
try self.emitWValue(operand);
|
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 (child_type.abiSize(self.target) == 0) return WValue{ .none = {} };
|
||||||
|
|
||||||
|
if (isByRef(child_type)) {
|
||||||
|
return self.return_value;
|
||||||
|
}
|
||||||
return self.allocStack(child_type);
|
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();
|
const ret_ty = self.air.typeOf(un_op).childType();
|
||||||
if (!ret_ty.hasCodeGenBits()) return WValue.none;
|
if (!ret_ty.hasCodeGenBits()) return WValue.none;
|
||||||
|
|
||||||
if (isByRef(ret_ty)) {
|
if (!isByRef(ret_ty)) {
|
||||||
try self.emitWValue(operand);
|
|
||||||
} else {
|
|
||||||
const result = try self.load(operand, ret_ty, 0);
|
const result = try self.load(operand, ret_ty, 0);
|
||||||
try self.emitWValue(result);
|
try self.emitWValue(result);
|
||||||
}
|
}
|
||||||
@ -1425,6 +1442,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
|
|||||||
.Pointer => ty.childType(),
|
.Pointer => ty.childType(),
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
};
|
};
|
||||||
|
const ret_ty = fn_ty.fnReturnType();
|
||||||
|
const first_param_sret = isByRef(ret_ty);
|
||||||
|
|
||||||
const target: ?*Decl = blk: {
|
const target: ?*Decl = blk: {
|
||||||
const func_val = self.air.value(pl_op.operand) orelse break :blk null;
|
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()});
|
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| {
|
for (args) |arg| {
|
||||||
const arg_ref = @intToEnum(Air.Inst.Ref, arg);
|
const arg_ref = @intToEnum(Air.Inst.Ref, arg);
|
||||||
const arg_val = self.resolveInst(arg_ref);
|
const arg_val = self.resolveInst(arg_ref);
|
||||||
@ -1475,32 +1500,19 @@ fn airCall(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
|
|||||||
try self.addLabel(.call_indirect, fn_type_index);
|
try self.addLabel(.call_indirect, fn_type_index);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ret_ty = fn_ty.fnReturnType();
|
if (self.liveness.isUnused(inst) or !ret_ty.hasCodeGenBits()) {
|
||||||
if (!ret_ty.hasCodeGenBits()) return WValue.none;
|
return WValue.none;
|
||||||
|
} else if (ret_ty.isNoReturn()) {
|
||||||
// TODO: Implement this for all aggregate types
|
try self.addTag(.@"unreachable");
|
||||||
if (ret_ty.isSlice()) {
|
return WValue.none;
|
||||||
// first load the values onto the regular stack, before we move the stack pointer
|
} else if (first_param_sret) {
|
||||||
// to prevent overwriting the return value.
|
return sret;
|
||||||
const tmp = try self.allocLocal(ret_ty);
|
} else {
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result_local = try self.allocLocal(ret_ty);
|
const result_local = try self.allocLocal(ret_ty);
|
||||||
try self.addLabel(.local_set, result_local.local);
|
try self.addLabel(.local_set, result_local.local);
|
||||||
return result_local;
|
return result_local;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
|
fn airAlloc(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
|
||||||
const pointee_type = self.air.typeOfIndex(inst).childType();
|
const pointee_type = self.air.typeOfIndex(inst).childType();
|
||||||
@ -1989,7 +2001,7 @@ fn emitUndefined(self: *Self, ty: Type) InnerError!void {
|
|||||||
// validator will not accept it due to out-of-bounds memory access);
|
// validator will not accept it due to out-of-bounds memory access);
|
||||||
.Array => try self.addImm32(@bitCast(i32, @as(u32, 0xaa))),
|
.Array => try self.addImm32(@bitCast(i32, @as(u32, 0xaa))),
|
||||||
.Struct => {
|
.Struct => {
|
||||||
// TODO: Write 0xaa to each field
|
// TODO: Write 0xaa struct's memory
|
||||||
const result = try self.allocStack(ty);
|
const result = try self.allocStack(ty);
|
||||||
try self.addLabel(.local_get, result.local);
|
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);
|
try self.addLabel(.local_set, result.local);
|
||||||
return result;
|
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();
|
||||||
|
}
|
||||||
|
|||||||
@ -161,6 +161,8 @@ pub fn emitMir(emit: *Emit) InnerError!void {
|
|||||||
.i64_extend8_s => try emit.emitTag(tag),
|
.i64_extend8_s => try emit.emitTag(tag),
|
||||||
.i64_extend16_s => try emit.emitTag(tag),
|
.i64_extend16_s => try emit.emitTag(tag),
|
||||||
.i64_extend32_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,
|
.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));
|
||||||
|
}
|
||||||
|
|||||||
@ -19,6 +19,9 @@ extra: []const u32,
|
|||||||
pub const Inst = struct {
|
pub const Inst = struct {
|
||||||
/// The opcode that represents this instruction
|
/// The opcode that represents this instruction
|
||||||
tag: Tag,
|
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`.
|
/// Data is determined by the set `tag`.
|
||||||
/// For example, `data` will be an i32 for when `tag` is 'i32_const'.
|
/// For example, `data` will be an i32 for when `tag` is 'i32_const'.
|
||||||
data: Data,
|
data: Data,
|
||||||
@ -373,6 +376,11 @@ pub const Inst = struct {
|
|||||||
i64_extend16_s = 0xC3,
|
i64_extend16_s = 0xC3,
|
||||||
/// Uses `tag`
|
/// Uses `tag`
|
||||||
i64_extend32_s = 0xC4,
|
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
|
/// Contains a symbol to a function pointer
|
||||||
/// uses `label`
|
/// uses `label`
|
||||||
///
|
///
|
||||||
|
|||||||
@ -39,16 +39,23 @@ test {
|
|||||||
_ = @import("behavior/defer.zig");
|
_ = @import("behavior/defer.zig");
|
||||||
_ = @import("behavior/enum.zig");
|
_ = @import("behavior/enum.zig");
|
||||||
_ = @import("behavior/error.zig");
|
_ = @import("behavior/error.zig");
|
||||||
|
_ = @import("behavior/generics.zig");
|
||||||
_ = @import("behavior/if.zig");
|
_ = @import("behavior/if.zig");
|
||||||
_ = @import("behavior/import.zig");
|
_ = @import("behavior/import.zig");
|
||||||
_ = @import("behavior/incomplete_struct_param_tld.zig");
|
_ = @import("behavior/incomplete_struct_param_tld.zig");
|
||||||
_ = @import("behavior/inttoptr.zig");
|
_ = @import("behavior/inttoptr.zig");
|
||||||
|
_ = @import("behavior/member_func.zig");
|
||||||
|
_ = @import("behavior/null.zig");
|
||||||
_ = @import("behavior/pointers.zig");
|
_ = @import("behavior/pointers.zig");
|
||||||
_ = @import("behavior/ptrcast.zig");
|
_ = @import("behavior/ptrcast.zig");
|
||||||
_ = @import("behavior/ref_var_in_if_after_if_2nd_switch_prong.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/truncate.zig");
|
||||||
_ = @import("behavior/usingnamespace.zig");
|
|
||||||
_ = @import("behavior/underscore.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) {
|
if (!builtin.zig_is_stage2 or builtin.stage2_arch != .wasm32) {
|
||||||
// Tests that pass for stage1, llvm backend, C backend
|
// Tests that pass for stage1, llvm backend, C backend
|
||||||
@ -56,16 +63,9 @@ test {
|
|||||||
_ = @import("behavior/array.zig");
|
_ = @import("behavior/array.zig");
|
||||||
_ = @import("behavior/cast.zig");
|
_ = @import("behavior/cast.zig");
|
||||||
_ = @import("behavior/for.zig");
|
_ = @import("behavior/for.zig");
|
||||||
_ = @import("behavior/generics.zig");
|
|
||||||
_ = @import("behavior/int128.zig");
|
_ = @import("behavior/int128.zig");
|
||||||
_ = @import("behavior/member_func.zig");
|
|
||||||
_ = @import("behavior/null.zig");
|
|
||||||
_ = @import("behavior/optional.zig");
|
_ = @import("behavior/optional.zig");
|
||||||
_ = @import("behavior/struct.zig");
|
|
||||||
_ = @import("behavior/this.zig");
|
|
||||||
_ = @import("behavior/translate_c_macros.zig");
|
_ = @import("behavior/translate_c_macros.zig");
|
||||||
_ = @import("behavior/while.zig");
|
|
||||||
_ = @import("behavior/void.zig");
|
|
||||||
|
|
||||||
if (builtin.object_format != .c) {
|
if (builtin.object_format != .c) {
|
||||||
// Tests that pass for stage1 and the llvm backend.
|
// Tests that pass for stage1 and the llvm backend.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user