aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2022-11-30 19:29:26 -0500
committerGitHub <noreply@github.com>2022-11-30 19:29:26 -0500
commit94e751ad013915b7b2d8dbfdd0d708fe9f56f087 (patch)
treecc9457e86fb969a9590e178ba865f46111c398fd /src
parent34fa6a1e0437ab7f08a2ccff2aff88aa77aeb037 (diff)
parent090deae41dbb79bcbe6a06d0bea0413f21576881 (diff)
downloadzig-94e751ad013915b7b2d8dbfdd0d708fe9f56f087.tar.gz
zig-94e751ad013915b7b2d8dbfdd0d708fe9f56f087.zip
Merge pull request #13718 from Luukdegram/wasm-packed
stage2: Wasm - Implement packed structs
Diffstat (limited to 'src')
-rw-r--r--src/arch/wasm/CodeGen.zig584
-rw-r--r--src/arch/wasm/abi.zig22
-rw-r--r--src/codegen.zig44
-rw-r--r--src/link/Wasm.zig5
4 files changed, 471 insertions, 184 deletions
diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig
index 7dca38b619..ebfff25b2f 100644
--- a/src/arch/wasm/CodeGen.zig
+++ b/src/arch/wasm/CodeGen.zig
@@ -483,7 +483,12 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {
.f32 => if (args.signedness.? == .signed) return .i32_trunc_f32_s else return .i32_trunc_f32_u,
.f64 => if (args.signedness.? == .signed) return .i32_trunc_f64_s else return .i32_trunc_f64_u,
} else return .f32_trunc, // when no valtype2, it's an f16 instead which is stored in an i32.
- .i64 => unreachable,
+ .i64 => switch (args.valtype2.?) {
+ .i32 => unreachable,
+ .i64 => unreachable,
+ .f32 => if (args.signedness.? == .signed) return .i64_trunc_f32_s else return .i64_trunc_f32_u,
+ .f64 => if (args.signedness.? == .signed) return .i64_trunc_f64_s else return .i64_trunc_f64_u,
+ },
.f32 => return .f32_trunc,
.f64 => return .f64_trunc,
},
@@ -687,7 +692,10 @@ const InnerError = error{
};
pub fn deinit(func: *CodeGen) void {
- assert(func.branches.items.len == 0); // we should end with no branches left. Forgot a call to `branches.pop()`?
+ // in case of an error and we still have branches
+ for (func.branches.items) |*branch| {
+ branch.deinit(func.gpa);
+ }
func.branches.deinit(func.gpa);
func.blocks.deinit(func.gpa);
func.locals.deinit(func.gpa);
@@ -909,6 +917,13 @@ fn typeToValtype(ty: Type, target: std.Target) wasm.Valtype {
if (info.bits > 32 and info.bits <= 128) break :blk wasm.Valtype.i64;
break :blk wasm.Valtype.i32; // represented as pointer to stack
},
+ .Struct => switch (ty.containerLayout()) {
+ .Packed => {
+ const struct_obj = ty.castTag(.@"struct").?.data;
+ return typeToValtype(struct_obj.backing_int_ty, target);
+ },
+ else => wasm.Valtype.i32,
+ },
else => wasm.Valtype.i32, // all represented as reference/immediate
};
}
@@ -1119,13 +1134,14 @@ fn genFunc(func: *CodeGen) InnerError!void {
try func.addTag(.dbg_prologue_end);
try func.branches.append(func.gpa, .{});
+ // clean up outer branch
+ defer {
+ var outer_branch = func.branches.pop();
+ outer_branch.deinit(func.gpa);
+ }
// Generate MIR for function body
try func.genBody(func.air.getMainBody());
- // clean up outer branch
- var outer_branch = func.branches.pop();
- outer_branch.deinit(func.gpa);
-
// 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 func.air.instructions.len > 0) {
@@ -1309,17 +1325,21 @@ fn lowerArg(func: *CodeGen, cc: std.builtin.CallingConvention, ty: Type, value:
assert(ty_classes[0] == .direct);
const scalar_type = abi.scalarType(ty, func.target);
const abi_size = scalar_type.abiSize(func.target);
- const opcode = buildOpcode(.{
- .op = .load,
- .width = @intCast(u8, abi_size),
- .signedness = if (scalar_type.isSignedInt()) .signed else .unsigned,
- .valtype1 = typeToValtype(scalar_type, func.target),
- });
try func.emitWValue(value);
- try func.addMemArg(Mir.Inst.Tag.fromOpcode(opcode), .{
- .offset = value.offset(),
- .alignment = scalar_type.abiAlignment(func.target),
- });
+
+ // When the value lives in the virtual stack, we must load it onto the actual stack
+ if (value != .imm32 and value != .imm64) {
+ const opcode = buildOpcode(.{
+ .op = .load,
+ .width = @intCast(u8, abi_size),
+ .signedness = if (scalar_type.isSignedInt()) .signed else .unsigned,
+ .valtype1 = typeToValtype(scalar_type, func.target),
+ });
+ try func.addMemArg(Mir.Inst.Tag.fromOpcode(opcode), .{
+ .offset = value.offset(),
+ .alignment = scalar_type.abiAlignment(func.target),
+ });
+ }
},
.Int, .Float => {
if (ty_classes[1] == .none) {
@@ -1472,12 +1492,15 @@ fn memcpy(func: *CodeGen, dst: WValue, src: WValue, len: WValue) !void {
// when the length is comptime-known, rather than a runtime value, we can optimize the generated code by having
// the loop during codegen, rather than inserting a runtime loop into the binary.
switch (len) {
- .imm32, .imm64 => {
+ .imm32, .imm64 => blk: {
const length = switch (len) {
.imm32 => |val| val,
.imm64 => |val| val,
else => unreachable,
};
+ // if the size (length) is more than 1024 bytes, we use a runtime loop instead to prevent
+ // binary size bloat.
+ if (length > 1024) break :blk;
var offset: u32 = 0;
const lhs_base = dst.offset();
const rhs_base = src.offset();
@@ -1498,80 +1521,81 @@ fn memcpy(func: *CodeGen, dst: WValue, src: WValue, len: WValue) !void {
else => unreachable,
}
}
+ return;
},
- else => {
- // TODO: We should probably lower this to a call to compiler_rt
- // But for now, we implement it manually
- var offset = try func.ensureAllocLocal(Type.usize); // local for counter
- defer offset.free(func);
+ else => {},
+ }
- // outer block to jump to when loop is done
- try func.startBlock(.block, wasm.block_empty);
- try func.startBlock(.loop, wasm.block_empty);
+ // TODO: We should probably lower this to a call to compiler_rt
+ // But for now, we implement it manually
+ var offset = try func.ensureAllocLocal(Type.usize); // local for counter
+ defer offset.free(func);
- // loop condition (offset == length -> break)
- {
- try func.emitWValue(offset);
- try func.emitWValue(len);
- switch (func.arch()) {
- .wasm32 => try func.addTag(.i32_eq),
- .wasm64 => try func.addTag(.i64_eq),
- else => unreachable,
- }
- try func.addLabel(.br_if, 1); // jump out of loop into outer block (finished)
- }
+ // outer block to jump to when loop is done
+ try func.startBlock(.block, wasm.block_empty);
+ try func.startBlock(.loop, wasm.block_empty);
- // get dst ptr
- {
- try func.emitWValue(dst);
- try func.emitWValue(offset);
- switch (func.arch()) {
- .wasm32 => try func.addTag(.i32_add),
- .wasm64 => try func.addTag(.i64_add),
- else => unreachable,
- }
- }
+ // loop condition (offset == length -> break)
+ {
+ try func.emitWValue(offset);
+ try func.emitWValue(len);
+ switch (func.arch()) {
+ .wasm32 => try func.addTag(.i32_eq),
+ .wasm64 => try func.addTag(.i64_eq),
+ else => unreachable,
+ }
+ try func.addLabel(.br_if, 1); // jump out of loop into outer block (finished)
+ }
- // get src value and also store in dst
- {
- try func.emitWValue(src);
- try func.emitWValue(offset);
- switch (func.arch()) {
- .wasm32 => {
- try func.addTag(.i32_add);
- try func.addMemArg(.i32_load8_u, .{ .offset = src.offset(), .alignment = 1 });
- try func.addMemArg(.i32_store8, .{ .offset = dst.offset(), .alignment = 1 });
- },
- .wasm64 => {
- try func.addTag(.i64_add);
- try func.addMemArg(.i64_load8_u, .{ .offset = src.offset(), .alignment = 1 });
- try func.addMemArg(.i64_store8, .{ .offset = dst.offset(), .alignment = 1 });
- },
- else => unreachable,
- }
- }
+ // get dst ptr
+ {
+ try func.emitWValue(dst);
+ try func.emitWValue(offset);
+ switch (func.arch()) {
+ .wasm32 => try func.addTag(.i32_add),
+ .wasm64 => try func.addTag(.i64_add),
+ else => unreachable,
+ }
+ }
- // increment loop counter
- {
- try func.emitWValue(offset);
- switch (func.arch()) {
- .wasm32 => {
- try func.addImm32(1);
- try func.addTag(.i32_add);
- },
- .wasm64 => {
- try func.addImm64(1);
- try func.addTag(.i64_add);
- },
- else => unreachable,
- }
- try func.addLabel(.local_set, offset.local.value);
- try func.addLabel(.br, 0); // jump to start of loop
- }
- try func.endBlock(); // close off loop block
- try func.endBlock(); // close off outer block
- },
+ // get src value and also store in dst
+ {
+ try func.emitWValue(src);
+ try func.emitWValue(offset);
+ switch (func.arch()) {
+ .wasm32 => {
+ try func.addTag(.i32_add);
+ try func.addMemArg(.i32_load8_u, .{ .offset = src.offset(), .alignment = 1 });
+ try func.addMemArg(.i32_store8, .{ .offset = dst.offset(), .alignment = 1 });
+ },
+ .wasm64 => {
+ try func.addTag(.i64_add);
+ try func.addMemArg(.i64_load8_u, .{ .offset = src.offset(), .alignment = 1 });
+ try func.addMemArg(.i64_store8, .{ .offset = dst.offset(), .alignment = 1 });
+ },
+ else => unreachable,
+ }
}
+
+ // increment loop counter
+ {
+ try func.emitWValue(offset);
+ switch (func.arch()) {
+ .wasm32 => {
+ try func.addImm32(1);
+ try func.addTag(.i32_add);
+ },
+ .wasm64 => {
+ try func.addImm64(1);
+ try func.addTag(.i64_add);
+ },
+ else => unreachable,
+ }
+ try func.addLabel(.local_set, offset.local.value);
+ try func.addLabel(.br, 0); // jump to start of loop
+ }
+ try func.endBlock(); // close off loop block
+ try func.endBlock(); // close off outer block
}
fn ptrSize(func: *const CodeGen) u16 {
@@ -1607,10 +1631,18 @@ fn isByRef(ty: Type, target: std.Target) bool {
.Array,
.Vector,
- .Struct,
.Frame,
.Union,
=> return ty.hasRuntimeBitsIgnoreComptime(),
+ .Struct => {
+ if (ty.castTag(.@"struct")) |struct_ty| {
+ const struct_obj = struct_ty.data;
+ if (struct_obj.layout == .Packed and struct_obj.haveFieldTypes()) {
+ return isByRef(struct_obj.backing_int_ty, target);
+ }
+ }
+ return ty.hasRuntimeBitsIgnoreComptime();
+ },
.Int => return ty.intInfo(target).bits > 64,
.Float => return ty.floatBits(target) > 64,
.ErrorUnion => {
@@ -2100,9 +2132,48 @@ fn airStore(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
const lhs = try func.resolveInst(bin_op.lhs);
const rhs = try func.resolveInst(bin_op.rhs);
- const ty = func.air.typeOf(bin_op.lhs).childType();
+ const ptr_ty = func.air.typeOf(bin_op.lhs);
+ const ptr_info = ptr_ty.ptrInfo().data;
+ const ty = ptr_ty.childType();
+ if (ptr_info.host_size == 0) {
+ try func.store(lhs, rhs, ty, 0);
+ } else {
+ // at this point we have a non-natural alignment, we must
+ // load the value, and then shift+or the rhs into the result location.
+ var int_ty_payload: Type.Payload.Bits = .{
+ .base = .{ .tag = .int_unsigned },
+ .data = ptr_info.host_size * 8,
+ };
+ const int_elem_ty = Type.initPayload(&int_ty_payload.base);
+
+ if (isByRef(int_elem_ty, func.target)) {
+ return func.fail("TODO: airStore for pointers to bitfields with backing type larger than 64bits", .{});
+ }
+
+ var mask = @intCast(u64, (@as(u65, 1) << @intCast(u7, ty.bitSize(func.target))) - 1);
+ mask <<= @intCast(u6, ptr_info.bit_offset);
+ mask ^= ~@as(u64, 0);
+ const shift_val = if (ptr_info.host_size <= 4)
+ WValue{ .imm32 = ptr_info.bit_offset }
+ else
+ WValue{ .imm64 = ptr_info.bit_offset };
+ const mask_val = if (ptr_info.host_size <= 4)
+ WValue{ .imm32 = @truncate(u32, mask) }
+ else
+ WValue{ .imm64 = mask };
+
+ try func.emitWValue(lhs);
+ const loaded = try func.load(lhs, int_elem_ty, 0);
+ const anded = try func.binOp(loaded, mask_val, int_elem_ty, .@"and");
+ const extended_value = try func.intcast(rhs, ty, int_elem_ty);
+ const shifted_value = if (ptr_info.bit_offset > 0) shifted: {
+ break :shifted try func.binOp(extended_value, shift_val, int_elem_ty, .shl);
+ } else extended_value;
+ const result = try func.binOp(anded, shifted_value, int_elem_ty, .@"or");
+ // lhs is still on the stack
+ try func.store(.stack, result, int_elem_ty, lhs.offset());
+ }
- try func.store(lhs, rhs, ty, 0);
func.finishAir(inst, .none, &.{ bin_op.lhs, bin_op.rhs });
}
@@ -2134,7 +2205,7 @@ fn store(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerE
const len = @intCast(u32, ty.abiSize(func.target));
return func.memcpy(lhs, rhs, .{ .imm32 = len });
},
- .Struct, .Array, .Union, .Vector => {
+ .Struct, .Array, .Union, .Vector => if (isByRef(ty, func.target)) {
const len = @intCast(u32, ty.abiSize(func.target));
return func.memcpy(lhs, rhs, .{ .imm32 = len });
},
@@ -2190,6 +2261,8 @@ fn airLoad(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
const ty_op = func.air.instructions.items(.data)[inst].ty_op;
const operand = try func.resolveInst(ty_op.operand);
const ty = func.air.getRefType(ty_op.ty);
+ const ptr_ty = func.air.typeOf(ty_op.operand);
+ const ptr_info = ptr_ty.ptrInfo().data;
if (!ty.hasRuntimeBitsIgnoreComptime()) return func.finishAir(inst, .none, &.{ty_op.operand});
@@ -2200,8 +2273,30 @@ fn airLoad(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
break :result new_local;
}
- const stack_loaded = try func.load(operand, ty, 0);
- break :result try stack_loaded.toLocal(func, ty);
+ if (ptr_info.host_size == 0) {
+ const stack_loaded = try func.load(operand, ty, 0);
+ break :result try stack_loaded.toLocal(func, ty);
+ }
+
+ // at this point we have a non-natural alignment, we must
+ // shift the value to obtain the correct bit.
+ var int_ty_payload: Type.Payload.Bits = .{
+ .base = .{ .tag = .int_unsigned },
+ .data = ptr_info.host_size * 8,
+ };
+ const int_elem_ty = Type.initPayload(&int_ty_payload.base);
+ const shift_val = if (ptr_info.host_size <= 4)
+ WValue{ .imm32 = ptr_info.bit_offset }
+ else if (ptr_info.host_size <= 8)
+ WValue{ .imm64 = ptr_info.bit_offset }
+ else
+ return func.fail("TODO: airLoad where ptr to bitfield exceeds 64 bits", .{});
+
+ const stack_loaded = try func.load(operand, int_elem_ty, 0);
+ const shifted = try func.binOp(stack_loaded, shift_val, int_elem_ty, .shr);
+ const result = try func.trunc(shifted, ty, int_elem_ty);
+ // const wrapped = try func.wrapOperand(shifted, ty);
+ break :result try result.toLocal(func, ty);
};
func.finishAir(inst, result, &.{ty_op.operand});
}
@@ -2406,7 +2501,7 @@ fn wrapBinOp(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerEr
/// NOTE: When the Type is <= 64 bits, leaves the value on top of the stack.
fn wrapOperand(func: *CodeGen, operand: WValue, ty: Type) InnerError!WValue {
assert(ty.abiSize(func.target) <= 16);
- const bitsize = ty.intInfo(func.target).bits;
+ const bitsize = @intCast(u16, ty.bitSize(func.target));
const wasm_bits = toWasmBits(bitsize) orelse {
return func.fail("TODO: Implement wrapOperand for bitsize '{d}'", .{bitsize});
};
@@ -2462,18 +2557,21 @@ fn lowerParentPtr(func: *CodeGen, ptr_val: Value, ptr_child_ty: Type) InnerError
const parent_ptr = try func.lowerParentPtr(field_ptr.container_ptr, parent_ty);
const offset = switch (parent_ty.zigTypeTag()) {
- .Struct => blk: {
- const offset = parent_ty.structFieldOffset(field_ptr.field_index, func.target);
- break :blk offset;
+ .Struct => switch (parent_ty.containerLayout()) {
+ .Packed => parent_ty.packedStructFieldByteOffset(field_ptr.field_index, func.target),
+ else => parent_ty.structFieldOffset(field_ptr.field_index, func.target),
},
- .Union => blk: {
- const layout: Module.Union.Layout = parent_ty.unionGetLayout(func.target);
- if (layout.payload_size == 0) break :blk 0;
- if (layout.payload_align > layout.tag_align) break :blk 0;
-
- // tag is stored first so calculate offset from where payload starts
- const offset = @intCast(u32, std.mem.alignForwardGeneric(u64, layout.tag_size, layout.tag_align));
- break :blk offset;
+ .Union => switch (parent_ty.containerLayout()) {
+ .Packed => 0,
+ else => blk: {
+ const layout: Module.Union.Layout = parent_ty.unionGetLayout(func.target);
+ if (layout.payload_size == 0) break :blk 0;
+ if (layout.payload_align > layout.tag_align) break :blk 0;
+
+ // tag is stored first so calculate offset from where payload starts
+ const offset = @intCast(u32, std.mem.alignForwardGeneric(u64, layout.tag_size, layout.tag_align));
+ break :blk offset;
+ },
},
.Pointer => switch (parent_ty.ptrSize()) {
.Slice => switch (field_ptr.field_index) {
@@ -2689,6 +2787,18 @@ fn lowerConstant(func: *CodeGen, arg_val: Value, ty: Type) InnerError!WValue {
const is_pl = val.tag() == .opt_payload;
return WValue{ .imm32 = if (is_pl) @as(u32, 1) else 0 };
},
+ .Struct => {
+ const struct_obj = ty.castTag(.@"struct").?.data;
+ assert(struct_obj.layout == .Packed);
+ var buf: [8]u8 = .{0} ** 8; // zero the buffer so we do not read 0xaa as integer
+ val.writeToPackedMemory(ty, func.bin_file.base.options.module.?, &buf, 0);
+ var payload: Value.Payload.U64 = .{
+ .base = .{ .tag = .int_u64 },
+ .data = std.mem.readIntLittle(u64, &buf),
+ };
+ const int_val = Value.initPayload(&payload.base);
+ return func.lowerConstant(int_val, struct_obj.backing_int_ty);
+ },
else => |zig_type| return func.fail("Wasm TODO: LowerConstant for zigTypeTag {}", .{zig_type}),
}
}
@@ -2723,6 +2833,11 @@ fn emitUndefined(func: *CodeGen, ty: Type) InnerError!WValue {
.ErrorUnion => {
return WValue{ .imm32 = 0xaaaaaaaa };
},
+ .Struct => {
+ const struct_obj = ty.castTag(.@"struct").?.data;
+ assert(struct_obj.layout == .Packed);
+ return func.emitUndefined(struct_obj.backing_int_ty);
+ },
else => return func.fail("Wasm TODO: emitUndefined for type: {}\n", .{ty.zigTypeTag()}),
}
}
@@ -3070,11 +3185,32 @@ fn airBitcast(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
const ty_op = func.air.instructions.items(.data)[inst].ty_op;
const result = if (!func.liveness.isUnused(inst)) result: {
const operand = try func.resolveInst(ty_op.operand);
+ const wanted_ty = func.air.typeOfIndex(inst);
+ const given_ty = func.air.typeOf(ty_op.operand);
+ if (given_ty.isAnyFloat() or wanted_ty.isAnyFloat()) {
+ const bitcast_result = try func.bitcast(wanted_ty, given_ty, operand);
+ break :result try bitcast_result.toLocal(func, wanted_ty);
+ }
break :result func.reuseOperand(ty_op.operand, operand);
} else WValue{ .none = {} };
func.finishAir(inst, result, &.{});
}
+fn bitcast(func: *CodeGen, wanted_ty: Type, given_ty: Type, operand: WValue) InnerError!WValue {
+ // if we bitcast a float to or from an integer we must use the 'reinterpret' instruction
+ if (!(wanted_ty.isAnyFloat() or given_ty.isAnyFloat())) return operand;
+ assert((wanted_ty.isInt() and given_ty.isAnyFloat()) or (wanted_ty.isAnyFloat() and given_ty.isInt()));
+
+ const opcode = buildOpcode(.{
+ .op = .reinterpret,
+ .valtype1 = typeToValtype(wanted_ty, func.target),
+ .valtype2 = typeToValtype(given_ty, func.target),
+ });
+ try func.emitWValue(operand);
+ try func.addTag(Mir.Inst.Tag.fromOpcode(opcode));
+ return WValue{ .stack = {} };
+}
+
fn airStructFieldPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
const ty_pl = func.air.instructions.items(.data)[inst].ty_pl;
const extra = func.air.extraData(Air.StructField, ty_pl.payload);
@@ -3082,13 +3218,7 @@ fn airStructFieldPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
const struct_ptr = try func.resolveInst(extra.data.struct_operand);
const struct_ty = func.air.typeOf(extra.data.struct_operand).childType();
- const offset = std.math.cast(u32, struct_ty.structFieldOffset(extra.data.field_index, func.target)) orelse {
- const module = func.bin_file.base.options.module.?;
- return func.fail("Field type '{}' too big to fit into stack frame", .{
- struct_ty.structFieldType(extra.data.field_index).fmt(module),
- });
- };
- const result = try func.structFieldPtr(struct_ptr, offset);
+ const result = try func.structFieldPtr(inst, extra.data.struct_operand, struct_ptr, struct_ty, extra.data.field_index);
func.finishAir(inst, result, &.{extra.data.struct_operand});
}
@@ -3097,21 +3227,40 @@ fn airStructFieldPtrIndex(func: *CodeGen, inst: Air.Inst.Index, index: u32) Inne
if (func.liveness.isUnused(inst)) return func.finishAir(inst, .none, &.{ty_op.operand});
const struct_ptr = try func.resolveInst(ty_op.operand);
const struct_ty = func.air.typeOf(ty_op.operand).childType();
- const field_ty = struct_ty.structFieldType(index);
- const offset = std.math.cast(u32, struct_ty.structFieldOffset(index, func.target)) orelse {
- const module = func.bin_file.base.options.module.?;
- return func.fail("Field type '{}' too big to fit into stack frame", .{
- field_ty.fmt(module),
- });
- };
- const result = try func.structFieldPtr(struct_ptr, offset);
+
+ const result = try func.structFieldPtr(inst, ty_op.operand, struct_ptr, struct_ty, index);
func.finishAir(inst, result, &.{ty_op.operand});
}
-fn structFieldPtr(func: *CodeGen, struct_ptr: WValue, offset: u32) InnerError!WValue {
+fn structFieldPtr(
+ func: *CodeGen,
+ inst: Air.Inst.Index,
+ ref: Air.Inst.Ref,
+ struct_ptr: WValue,
+ struct_ty: Type,
+ index: u32,
+) InnerError!WValue {
+ const result_ty = func.air.typeOfIndex(inst);
+ const offset = switch (struct_ty.containerLayout()) {
+ .Packed => switch (struct_ty.zigTypeTag()) {
+ .Struct => offset: {
+ if (result_ty.ptrInfo().data.host_size != 0) {
+ break :offset @as(u32, 0);
+ }
+ break :offset struct_ty.packedStructFieldByteOffset(index, func.target);
+ },
+ .Union => 0,
+ else => unreachable,
+ },
+ else => struct_ty.structFieldOffset(index, func.target),
+ };
+ // save a load and store when we can simply reuse the operand
+ if (offset == 0) {
+ return func.reuseOperand(ref, struct_ptr);
+ }
switch (struct_ptr) {
.stack_offset => |stack_offset| {
- return WValue{ .stack_offset = .{ .value = stack_offset.value + offset, .references = 1 } };
+ return WValue{ .stack_offset = .{ .value = stack_offset.value + @intCast(u32, offset), .references = 1 } };
},
else => return func.buildPointerOffset(struct_ptr, offset, .new),
}
@@ -3128,24 +3277,75 @@ fn airStructFieldVal(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
const field_ty = struct_ty.structFieldType(field_index);
if (!field_ty.hasRuntimeBitsIgnoreComptime()) return func.finishAir(inst, .none, &.{struct_field.struct_operand});
- const offset = std.math.cast(u32, struct_ty.structFieldOffset(field_index, func.target)) orelse {
- const module = func.bin_file.base.options.module.?;
- return func.fail("Field type '{}' too big to fit into stack frame", .{field_ty.fmt(module)});
- };
-
- const result = result: {
- if (isByRef(field_ty, func.target)) {
- switch (operand) {
- .stack_offset => |stack_offset| {
- break :result WValue{ .stack_offset = .{ .value = stack_offset.value + offset, .references = 1 } };
- },
- else => break :result try func.buildPointerOffset(operand, offset, .new),
+ const result = switch (struct_ty.containerLayout()) {
+ .Packed => switch (struct_ty.zigTypeTag()) {
+ .Struct => result: {
+ const struct_obj = struct_ty.castTag(.@"struct").?.data;
+ assert(struct_obj.layout == .Packed);
+ const offset = struct_obj.packedFieldBitOffset(func.target, field_index);
+ const backing_ty = struct_obj.backing_int_ty;
+ const wasm_bits = toWasmBits(backing_ty.intInfo(func.target).bits) orelse {
+ return func.fail("TODO: airStructFieldVal for packed structs larger than 128 bits", .{});
+ };
+ const const_wvalue = if (wasm_bits == 32)
+ WValue{ .imm32 = offset }
+ else if (wasm_bits == 64)
+ WValue{ .imm64 = offset }
+ else
+ return func.fail("TODO: airStructFieldVal for packed structs larger than 64 bits", .{});
+
+ // for first field we don't require any shifting
+ const shifted_value = if (offset == 0)
+ operand
+ else
+ try func.binOp(operand, const_wvalue, backing_ty, .shr);
+
+ if (field_ty.zigTypeTag() == .Float) {
+ var payload: Type.Payload.Bits = .{
+ .base = .{ .tag = .int_unsigned },
+ .data = @intCast(u16, field_ty.bitSize(func.target)),
+ };
+ const int_type = Type.initPayload(&payload.base);
+ const truncated = try func.trunc(shifted_value, int_type, backing_ty);
+ const bitcasted = try func.bitcast(field_ty, int_type, truncated);
+ break :result try bitcasted.toLocal(func, field_ty);
+ } else if (field_ty.isPtrAtRuntime() and struct_obj.fields.count() == 1) {
+ // In this case we do not have to perform any transformations,
+ // we can simply reuse the operand.
+ break :result func.reuseOperand(struct_field.struct_operand, operand);
+ } else if (field_ty.isPtrAtRuntime()) {
+ var payload: Type.Payload.Bits = .{
+ .base = .{ .tag = .int_unsigned },
+ .data = @intCast(u16, field_ty.bitSize(func.target)),
+ };
+ const int_type = Type.initPayload(&payload.base);
+ const truncated = try func.trunc(shifted_value, int_type, backing_ty);
+ break :result try truncated.toLocal(func, field_ty);
+ }
+ const truncated = try func.trunc(shifted_value, field_ty, backing_ty);
+ break :result try truncated.toLocal(func, field_ty);
+ },
+ .Union => return func.fail("TODO: airStructFieldVal for packed unions", .{}),
+ else => unreachable,
+ },
+ else => result: {
+ const offset = std.math.cast(u32, struct_ty.structFieldOffset(field_index, func.target)) orelse {
+ const module = func.bin_file.base.options.module.?;
+ return func.fail("Field type '{}' too big to fit into stack frame", .{field_ty.fmt(module)});
+ };
+ if (isByRef(field_ty, func.target)) {
+ switch (operand) {
+ .stack_offset => |stack_offset| {
+ break :result WValue{ .stack_offset = .{ .value = stack_offset.value + offset, .references = 1 } };
+ },
+ else => break :result try func.buildPointerOffset(operand, offset, .new),
+ }
}
- }
-
- const field = try func.load(operand, field_ty, offset);
- break :result try field.toLocal(func, field_ty);
+ const field = try func.load(operand, field_ty, offset);
+ break :result try field.toLocal(func, field_ty);
+ },
};
+
func.finishAir(inst, result, &.{struct_field.struct_operand});
}
@@ -3492,13 +3692,13 @@ fn airIntcast(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
/// Asserts type's bitsize <= 128
/// NOTE: May leave the result on the top of the stack.
fn intcast(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerError!WValue {
- const given_info = given.intInfo(func.target);
- const wanted_info = wanted.intInfo(func.target);
- assert(given_info.bits <= 128);
- assert(wanted_info.bits <= 128);
+ const given_bitsize = @intCast(u16, given.bitSize(func.target));
+ const wanted_bitsize = @intCast(u16, wanted.bitSize(func.target));
+ assert(given_bitsize <= 128);
+ assert(wanted_bitsize <= 128);
- const op_bits = toWasmBits(given_info.bits).?;
- const wanted_bits = toWasmBits(wanted_info.bits).?;
+ const op_bits = toWasmBits(given_bitsize).?;
+ const wanted_bits = toWasmBits(wanted_bitsize).?;
if (op_bits == wanted_bits) return operand;
if (op_bits > 32 and op_bits <= 64 and wanted_bits == 32) {
@@ -3506,10 +3706,7 @@ fn intcast(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerErro
try func.addTag(.i32_wrap_i64);
} else if (op_bits == 32 and wanted_bits > 32 and wanted_bits <= 64) {
try func.emitWValue(operand);
- try func.addTag(switch (wanted_info.signedness) {
- .signed => .i64_extend_i32_s,
- .unsigned => .i64_extend_i32_u,
- });
+ try func.addTag(if (wanted.isSignedInt()) .i64_extend_i32_s else .i64_extend_i32_u);
} else if (wanted_bits == 128) {
// for 128bit integers we store the integer in the virtual stack, rather than a local
const stack_ptr = try func.allocStack(wanted);
@@ -3725,7 +3922,7 @@ fn airSliceElemVal(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
try func.addTag(.i32_mul);
try func.addTag(.i32_add);
- const result_ptr = try func.allocLocal(elem_ty);
+ const result_ptr = try func.allocLocal(Type.usize);
try func.addLabel(.local_set, result_ptr.local.value);
const result = if (!isByRef(elem_ty, func.target)) result: {
@@ -3777,19 +3974,25 @@ fn airTrunc(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
const wanted_ty = func.air.getRefType(ty_op.ty);
const op_ty = func.air.typeOf(ty_op.operand);
- const int_info = op_ty.intInfo(func.target);
- if (toWasmBits(int_info.bits) == null) {
- return func.fail("TODO: Implement wasm integer truncation for integer bitsize: {d}", .{int_info.bits});
+ const result = try func.trunc(operand, wanted_ty, op_ty);
+ func.finishAir(inst, try result.toLocal(func, wanted_ty), &.{ty_op.operand});
+}
+
+/// Truncates a given operand to a given type, discarding any overflown bits.
+/// NOTE: Resulting value is left on the stack.
+fn trunc(func: *CodeGen, operand: WValue, wanted_ty: Type, given_ty: Type) InnerError!WValue {
+ const given_bits = @intCast(u16, given_ty.bitSize(func.target));
+ if (toWasmBits(given_bits) == null) {
+ return func.fail("TODO: Implement wasm integer truncation for integer bitsize: {d}", .{given_bits});
}
- var result = try func.intcast(operand, op_ty, wanted_ty);
- const wanted_bits = wanted_ty.intInfo(func.target).bits;
+ var result = try func.intcast(operand, given_ty, wanted_ty);
+ const wanted_bits = @intCast(u16, wanted_ty.bitSize(func.target));
const wasm_bits = toWasmBits(wanted_bits).?;
if (wasm_bits != wanted_bits) {
result = try func.wrapOperand(result, wanted_ty);
}
-
- func.finishAir(inst, try result.toLocal(func, wanted_ty), &.{ty_op.operand});
+ return result;
}
fn airBoolToInt(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
@@ -4186,23 +4389,68 @@ fn airAggregateInit(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
}
break :result_value result;
},
- .Struct => {
- const result = try func.allocStack(result_ty);
- const offset = try func.buildPointerOffset(result, 0, .new); // pointer to offset
- for (elements) |elem, elem_index| {
- if (result_ty.structFieldValueComptime(elem_index) != null) continue;
+ .Struct => switch (result_ty.containerLayout()) {
+ .Packed => {
+ if (isByRef(result_ty, func.target)) {
+ return func.fail("TODO: airAggregateInit for packed structs larger than 64 bits", .{});
+ }
+ const struct_obj = result_ty.castTag(.@"struct").?.data;
+ const fields = struct_obj.fields.values();
+ const backing_type = struct_obj.backing_int_ty;
+ // we ensure a new local is created so it's zero-initialized
+ const result = try func.ensureAllocLocal(backing_type);
+ var current_bit: u16 = 0;
+ for (elements) |elem, elem_index| {
+ const field = fields[elem_index];
+ if (!field.ty.hasRuntimeBitsIgnoreComptime()) continue;
+
+ const shift_val = if (struct_obj.backing_int_ty.bitSize(func.target) <= 32)
+ WValue{ .imm32 = current_bit }
+ else
+ WValue{ .imm64 = current_bit };
+
+ const value = try func.resolveInst(elem);
+ const value_bit_size = @intCast(u16, field.ty.bitSize(func.target));
+ var int_ty_payload: Type.Payload.Bits = .{
+ .base = .{ .tag = .int_unsigned },
+ .data = value_bit_size,
+ };
+ const int_ty = Type.initPayload(&int_ty_payload.base);
+
+ // load our current result on stack so we can perform all transformations
+ // using only stack values. Saving the cost of loads and stores.
+ try func.emitWValue(result);
+ const bitcasted = try func.bitcast(int_ty, field.ty, value);
+ const extended_val = try func.intcast(bitcasted, int_ty, backing_type);
+ // no need to shift any values when the current offset is 0
+ const shifted = if (current_bit != 0) shifted: {
+ break :shifted try func.binOp(extended_val, shift_val, backing_type, .shl);
+ } else extended_val;
+ // we ignore the result as we keep it on the stack to assign it directly to `result`
+ _ = try func.binOp(.stack, shifted, backing_type, .@"or");
+ try func.addLabel(.local_set, result.local.value);
+ current_bit += value_bit_size;
+ }
+ break :result_value result;
+ },
+ else => {
+ const result = try func.allocStack(result_ty);
+ const offset = try func.buildPointerOffset(result, 0, .new); // pointer to offset
+ for (elements) |elem, elem_index| {
+ if (result_ty.structFieldValueComptime(elem_index) != null) continue;
- const elem_ty = result_ty.structFieldType(elem_index);
- const elem_size = @intCast(u32, elem_ty.abiSize(func.target));
- const value = try func.resolveInst(elem);
- try func.store(offset, value, elem_ty, 0);
+ const elem_ty = result_ty.structFieldType(elem_index);
+ const elem_size = @intCast(u32, elem_ty.abiSize(func.target));
+ const value = try func.resolveInst(elem);
+ try func.store(offset, value, elem_ty, 0);
- if (elem_index < elements.len - 1) {
- _ = try func.buildPointerOffset(offset, elem_size, .modify);
+ if (elem_index < elements.len - 1) {
+ _ = try func.buildPointerOffset(offset, elem_size, .modify);
+ }
}
- }
- break :result_value result;
+ break :result_value result;
+ },
},
.Vector => return func.fail("TODO: Wasm backend: implement airAggregateInit for vectors", .{}),
else => unreachable,
@@ -4444,8 +4692,8 @@ fn airFptrunc(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
const dest_ty = func.air.typeOfIndex(inst);
const operand = try func.resolveInst(ty_op.operand);
- const trunc = try func.fptrunc(operand, func.air.typeOf(ty_op.operand), dest_ty);
- const result = try trunc.toLocal(func, dest_ty);
+ const truncated = try func.fptrunc(operand, func.air.typeOf(ty_op.operand), dest_ty);
+ const result = try truncated.toLocal(func, dest_ty);
func.finishAir(inst, result, &.{ty_op.operand});
}
diff --git a/src/arch/wasm/abi.zig b/src/arch/wasm/abi.zig
index d54965a50c..de3cf64ea3 100644
--- a/src/arch/wasm/abi.zig
+++ b/src/arch/wasm/abi.zig
@@ -94,15 +94,25 @@ pub fn classifyType(ty: Type, target: Target) [2]Class {
pub fn scalarType(ty: Type, target: std.Target) Type {
switch (ty.zigTypeTag()) {
.Struct => {
- std.debug.assert(ty.structFieldCount() == 1);
- return scalarType(ty.structFieldType(0), target);
+ switch (ty.containerLayout()) {
+ .Packed => {
+ const struct_obj = ty.castTag(.@"struct").?.data;
+ return scalarType(struct_obj.backing_int_ty, target);
+ },
+ else => {
+ std.debug.assert(ty.structFieldCount() == 1);
+ return scalarType(ty.structFieldType(0), target);
+ },
+ }
},
.Union => {
- const layout = ty.unionGetLayout(target);
- if (layout.payload_size == 0 and layout.tag_size != 0) {
- return scalarType(ty.unionTagTypeSafety().?, target);
+ if (ty.containerLayout() != .Packed) {
+ const layout = ty.unionGetLayout(target);
+ if (layout.payload_size == 0 and layout.tag_size != 0) {
+ return scalarType(ty.unionTagTypeSafety().?, target);
+ }
+ std.debug.assert(ty.unionFields().count() == 1);
}
- std.debug.assert(ty.unionFields().count() == 1);
return scalarType(ty.unionFields().values()[0].ty, target);
},
else => return ty,
diff --git a/src/codegen.zig b/src/codegen.zig
index 53a4f9a402..5a28ce1860 100644
--- a/src/codegen.zig
+++ b/src/codegen.zig
@@ -556,14 +556,42 @@ pub fn generateSymbol(
},
.Struct => {
if (typed_value.ty.containerLayout() == .Packed) {
- return Result{
- .fail = try ErrorMsg.create(
- bin_file.allocator,
- src_loc,
- "TODO implement generateSymbol for packed struct",
- .{},
- ),
- };
+ const struct_obj = typed_value.ty.castTag(.@"struct").?.data;
+ const fields = struct_obj.fields.values();
+ const field_vals = typed_value.val.castTag(.aggregate).?.data;
+ const abi_size = math.cast(usize, typed_value.ty.abiSize(target)) orelse return error.Overflow;
+ const current_pos = code.items.len;
+ const mod = bin_file.options.module.?;
+ try code.resize(current_pos + abi_size);
+ var bits: u16 = 0;
+
+ for (field_vals) |field_val, index| {
+ const field_ty = fields[index].ty;
+ // pointer may point to a decl which must be marked used
+ // but can also result in a relocation. Therefore we handle those seperately.
+ if (field_ty.zigTypeTag() == .Pointer) {
+ const field_size = math.cast(usize, field_ty.abiSize(target)) orelse return error.Overflow;
+ var tmp_list = try std.ArrayList(u8).initCapacity(code.allocator, field_size);
+ defer tmp_list.deinit();
+ switch (try generateSymbol(bin_file, src_loc, .{
+ .ty = field_ty,
+ .val = field_val,
+ }, &tmp_list, debug_output, reloc_info)) {
+ .appended => {
+ mem.copy(u8, code.items[current_pos..], tmp_list.items);
+ },
+ .externally_managed => |external_slice| {
+ mem.copy(u8, code.items[current_pos..], external_slice);
+ },
+ .fail => |em| return Result{ .fail = em },
+ }
+ } else {
+ field_val.writeToPackedMemory(field_ty, mod, code.items[current_pos..], bits);
+ }
+ bits += @intCast(u16, field_ty.bitSize(target));
+ }
+
+ return Result{ .appended = {} };
}
const struct_begin = code.items.len;
diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig
index 5d74338ebf..04b3e42db5 100644
--- a/src/link/Wasm.zig
+++ b/src/link/Wasm.zig
@@ -1841,8 +1841,9 @@ fn setupStart(wasm: *Wasm) !void {
/// Sets up the memory section of the wasm module, as well as the stack.
fn setupMemory(wasm: *Wasm) !void {
log.debug("Setting up memory layout", .{});
- const page_size = 64 * 1024;
- const stack_size = wasm.base.options.stack_size_override orelse page_size * 1;
+ const page_size = std.wasm.page_size; // 64kb
+ // Use the user-provided stack size or else we use 1MB by default
+ const stack_size = wasm.base.options.stack_size_override orelse page_size * 16;
const stack_alignment = 16; // wasm's stack alignment as specified by tool-convention
// Always place the stack at the start by default
// unless the user specified the global-base flag