authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-11-26 16:21:59+01:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-11-30 17:56:02+01:00
loga314e86772c539e803dfd51c34de52675189b34a
tree561398e2941c16a2dee739d597e78cf18f4f0958
parent4af5bbde53da7f6ac8acc630135e569885bd94c4
signaturelock-open Commit is signed but in an unrecognized format.

wasm: support passing packed struct over C-ABI

This also adds support loading a runtime pointer from a packed struct. Also, this commit improves many utility functions such as `trunc` and `intcast` to also support non-integer types such as booleans.

2 files changed, 80 insertions(+), 40 deletions(-)

src/arch/wasm/CodeGen.zig+64-34
...@@ -483,7 +483,12 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {...@@ -483,7 +483,12 @@ fn buildOpcode(args: OpcodeBuildArguments) wasm.Opcode {
483 .f32 => if (args.signedness.? == .signed) return .i32_trunc_f32_s else return .i32_trunc_f32_u,483 .f32 => if (args.signedness.? == .signed) return .i32_trunc_f32_s else return .i32_trunc_f32_u,
484 .f64 => if (args.signedness.? == .signed) return .i32_trunc_f64_s else return .i32_trunc_f64_u,484 .f64 => if (args.signedness.? == .signed) return .i32_trunc_f64_s else return .i32_trunc_f64_u,
485 } else return .f32_trunc, // when no valtype2, it's an f16 instead which is stored in an i32.485 } else return .f32_trunc, // when no valtype2, it's an f16 instead which is stored in an i32.
486 .i64 => unreachable,486 .i64 => switch (args.valtype2.?) {
487 .i32 => unreachable,
488 .i64 => unreachable,
489 .f32 => if (args.signedness.? == .signed) return .i64_trunc_f32_s else return .i64_trunc_f32_u,
490 .f64 => if (args.signedness.? == .signed) return .i64_trunc_f64_s else return .i64_trunc_f64_u,
491 },
487 .f32 => return .f32_trunc,492 .f32 => return .f32_trunc,
488 .f64 => return .f64_trunc,493 .f64 => return .f64_trunc,
489 },494 },
...@@ -687,7 +692,10 @@ const InnerError = error{...@@ -687,7 +692,10 @@ const InnerError = error{
687};692};
688693
689pub fn deinit(func: *CodeGen) void {694pub fn deinit(func: *CodeGen) void {
690 assert(func.branches.items.len == 0); // we should end with no branches left. Forgot a call to `branches.pop()`?695 // in case of an error and we still have branches
696 for (func.branches.items) |*branch| {
697 branch.deinit(func.gpa);
698 }
691 func.branches.deinit(func.gpa);699 func.branches.deinit(func.gpa);
692 func.blocks.deinit(func.gpa);700 func.blocks.deinit(func.gpa);
693 func.locals.deinit(func.gpa);701 func.locals.deinit(func.gpa);
...@@ -1317,17 +1325,21 @@ fn lowerArg(func: *CodeGen, cc: std.builtin.CallingConvention, ty: Type, value:...@@ -1317,17 +1325,21 @@ fn lowerArg(func: *CodeGen, cc: std.builtin.CallingConvention, ty: Type, value:
1317 assert(ty_classes[0] == .direct);1325 assert(ty_classes[0] == .direct);
1318 const scalar_type = abi.scalarType(ty, func.target);1326 const scalar_type = abi.scalarType(ty, func.target);
1319 const abi_size = scalar_type.abiSize(func.target);1327 const abi_size = scalar_type.abiSize(func.target);
1320 const opcode = buildOpcode(.{
1321 .op = .load,
1322 .width = @intCast(u8, abi_size),
1323 .signedness = if (scalar_type.isSignedInt()) .signed else .unsigned,
1324 .valtype1 = typeToValtype(scalar_type, func.target),
1325 });
1326 try func.emitWValue(value);1328 try func.emitWValue(value);
1327 try func.addMemArg(Mir.Inst.Tag.fromOpcode(opcode), .{1329
1328 .offset = value.offset(),1330 // When the value lives in the virtual stack, we must load it onto the actual stack
1329 .alignment = scalar_type.abiAlignment(func.target),1331 if (value != .imm32 and value != .imm64) {
1330 });1332 const opcode = buildOpcode(.{
1333 .op = .load,
1334 .width = @intCast(u8, abi_size),
1335 .signedness = if (scalar_type.isSignedInt()) .signed else .unsigned,
1336 .valtype1 = typeToValtype(scalar_type, func.target),
1337 });
1338 try func.addMemArg(Mir.Inst.Tag.fromOpcode(opcode), .{
1339 .offset = value.offset(),
1340 .alignment = scalar_type.abiAlignment(func.target),
1341 });
1342 }
1331 },1343 },
1332 .Int, .Float => {1344 .Int, .Float => {
1333 if (ty_classes[1] == .none) {1345 if (ty_classes[1] == .none) {
...@@ -2478,18 +2490,21 @@ fn lowerParentPtr(func: *CodeGen, ptr_val: Value, ptr_child_ty: Type) InnerError...@@ -2478,18 +2490,21 @@ fn lowerParentPtr(func: *CodeGen, ptr_val: Value, ptr_child_ty: Type) InnerError
2478 const parent_ptr = try func.lowerParentPtr(field_ptr.container_ptr, parent_ty);2490 const parent_ptr = try func.lowerParentPtr(field_ptr.container_ptr, parent_ty);
24792491
2480 const offset = switch (parent_ty.zigTypeTag()) {2492 const offset = switch (parent_ty.zigTypeTag()) {
2481 .Struct => blk: {2493 .Struct => switch (parent_ty.containerLayout()) {
2482 const offset = parent_ty.structFieldOffset(field_ptr.field_index, func.target);2494 .Packed => parent_ty.packedStructFieldByteOffset(field_ptr.field_index, func.target),
2483 break :blk offset;2495 else => parent_ty.structFieldOffset(field_ptr.field_index, func.target),
2484 },2496 },
2485 .Union => blk: {2497 .Union => switch (parent_ty.containerLayout()) {
2486 const layout: Module.Union.Layout = parent_ty.unionGetLayout(func.target);2498 .Packed => 0,
2487 if (layout.payload_size == 0) break :blk 0;2499 else => blk: {
2488 if (layout.payload_align > layout.tag_align) break :blk 0;2500 const layout: Module.Union.Layout = parent_ty.unionGetLayout(func.target);
24892501 if (layout.payload_size == 0) break :blk 0;
2490 // tag is stored first so calculate offset from where payload starts2502 if (layout.payload_align > layout.tag_align) break :blk 0;
2491 const offset = @intCast(u32, std.mem.alignForwardGeneric(u64, layout.tag_size, layout.tag_align));2503
2492 break :blk offset;2504 // tag is stored first so calculate offset from where payload starts
2505 const offset = @intCast(u32, std.mem.alignForwardGeneric(u64, layout.tag_size, layout.tag_align));
2506 break :blk offset;
2507 },
2493 },2508 },
2494 .Pointer => switch (parent_ty.ptrSize()) {2509 .Pointer => switch (parent_ty.ptrSize()) {
2495 .Slice => switch (field_ptr.field_index) {2510 .Slice => switch (field_ptr.field_index) {
...@@ -2751,6 +2766,11 @@ fn emitUndefined(func: *CodeGen, ty: Type) InnerError!WValue {...@@ -2751,6 +2766,11 @@ fn emitUndefined(func: *CodeGen, ty: Type) InnerError!WValue {
2751 .ErrorUnion => {2766 .ErrorUnion => {
2752 return WValue{ .imm32 = 0xaaaaaaaa };2767 return WValue{ .imm32 = 0xaaaaaaaa };
2753 },2768 },
2769 .Struct => {
2770 const struct_obj = ty.castTag(.@"struct").?.data;
2771 assert(struct_obj.layout == .Packed);
2772 return func.emitUndefined(struct_obj.backing_int_ty);
2773 },
2754 else => return func.fail("Wasm TODO: emitUndefined for type: {}\n", .{ty.zigTypeTag()}),2774 else => return func.fail("Wasm TODO: emitUndefined for type: {}\n", .{ty.zigTypeTag()}),
2755 }2775 }
2756}2776}
...@@ -3201,6 +3221,18 @@ fn airStructFieldVal(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -3201,6 +3221,18 @@ fn airStructFieldVal(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
3201 const truncated = try func.trunc(shifted_value, int_type, backing_ty);3221 const truncated = try func.trunc(shifted_value, int_type, backing_ty);
3202 const bitcasted = try func.bitcast(field_ty, int_type, truncated);3222 const bitcasted = try func.bitcast(field_ty, int_type, truncated);
3203 break :result try bitcasted.toLocal(func, field_ty);3223 break :result try bitcasted.toLocal(func, field_ty);
3224 } else if (field_ty.isPtrAtRuntime() and struct_obj.fields.count() == 1) {
3225 // In this case we do not have to perform any transformations,
3226 // we can simply reuse the operand.
3227 break :result func.reuseOperand(struct_field.struct_operand, operand);
3228 } else if (field_ty.isPtrAtRuntime()) {
3229 var payload: Type.Payload.Bits = .{
3230 .base = .{ .tag = .int_unsigned },
3231 .data = @intCast(u16, field_ty.bitSize(func.target)),
3232 };
3233 const int_type = Type.initPayload(&payload.base);
3234 const truncated = try func.trunc(shifted_value, int_type, backing_ty);
3235 break :result try truncated.toLocal(func, field_ty);
3204 }3236 }
3205 const truncated = try func.trunc(shifted_value, field_ty, backing_ty);3237 const truncated = try func.trunc(shifted_value, field_ty, backing_ty);
3206 break :result try truncated.toLocal(func, field_ty);3238 break :result try truncated.toLocal(func, field_ty);
...@@ -3225,6 +3257,7 @@ fn airStructFieldVal(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -3225,6 +3257,7 @@ fn airStructFieldVal(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
3225 break :result try field.toLocal(func, field_ty);3257 break :result try field.toLocal(func, field_ty);
3226 },3258 },
3227 };3259 };
3260
3228 func.finishAir(inst, result, &.{struct_field.struct_operand});3261 func.finishAir(inst, result, &.{struct_field.struct_operand});
3229}3262}
32303263
...@@ -3571,13 +3604,13 @@ fn airIntcast(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -3571,13 +3604,13 @@ fn airIntcast(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
3571/// Asserts type's bitsize <= 1283604/// Asserts type's bitsize <= 128
3572/// NOTE: May leave the result on the top of the stack.3605/// NOTE: May leave the result on the top of the stack.
3573fn intcast(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerError!WValue {3606fn intcast(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerError!WValue {
3574 const given_info = given.intInfo(func.target);3607 const given_bitsize = @intCast(u16, given.bitSize(func.target));
3575 const wanted_info = wanted.intInfo(func.target);3608 const wanted_bitsize = @intCast(u16, wanted.bitSize(func.target));
3576 assert(given_info.bits <= 128);3609 assert(given_bitsize <= 128);
3577 assert(wanted_info.bits <= 128);3610 assert(wanted_bitsize <= 128);
35783611
3579 const op_bits = toWasmBits(given_info.bits).?;3612 const op_bits = toWasmBits(given_bitsize).?;
3580 const wanted_bits = toWasmBits(wanted_info.bits).?;3613 const wanted_bits = toWasmBits(wanted_bitsize).?;
3581 if (op_bits == wanted_bits) return operand;3614 if (op_bits == wanted_bits) return operand;
35823615
3583 if (op_bits > 32 and op_bits <= 64 and wanted_bits == 32) {3616 if (op_bits > 32 and op_bits <= 64 and wanted_bits == 32) {
...@@ -3585,10 +3618,7 @@ fn intcast(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerErro...@@ -3585,10 +3618,7 @@ fn intcast(func: *CodeGen, operand: WValue, given: Type, wanted: Type) InnerErro
3585 try func.addTag(.i32_wrap_i64);3618 try func.addTag(.i32_wrap_i64);
3586 } else if (op_bits == 32 and wanted_bits > 32 and wanted_bits <= 64) {3619 } else if (op_bits == 32 and wanted_bits > 32 and wanted_bits <= 64) {
3587 try func.emitWValue(operand);3620 try func.emitWValue(operand);
3588 try func.addTag(switch (wanted_info.signedness) {3621 try func.addTag(if (wanted.isSignedInt()) .i64_extend_i32_s else .i64_extend_i32_u);
3589 .signed => .i64_extend_i32_s,
3590 .unsigned => .i64_extend_i32_u,
3591 });
3592 } else if (wanted_bits == 128) {3622 } else if (wanted_bits == 128) {
3593 // for 128bit integers we store the integer in the virtual stack, rather than a local3623 // for 128bit integers we store the integer in the virtual stack, rather than a local
3594 const stack_ptr = try func.allocStack(wanted);3624 const stack_ptr = try func.allocStack(wanted);
...@@ -3869,7 +3899,7 @@ fn trunc(func: *CodeGen, operand: WValue, wanted_ty: Type, given_ty: Type) Inner...@@ -3869,7 +3899,7 @@ fn trunc(func: *CodeGen, operand: WValue, wanted_ty: Type, given_ty: Type) Inner
3869 }3899 }
38703900
3871 var result = try func.intcast(operand, given_ty, wanted_ty);3901 var result = try func.intcast(operand, given_ty, wanted_ty);
3872 const wanted_bits = wanted_ty.intInfo(func.target).bits;3902 const wanted_bits = @intCast(u16, wanted_ty.bitSize(func.target));
3873 const wasm_bits = toWasmBits(wanted_bits).?;3903 const wasm_bits = toWasmBits(wanted_bits).?;
3874 if (wasm_bits != wanted_bits) {3904 if (wasm_bits != wanted_bits) {
3875 result = try func.wrapOperand(result, wanted_ty);3905 result = try func.wrapOperand(result, wanted_ty);
src/arch/wasm/abi.zig+16-6
...@@ -94,15 +94,25 @@ pub fn classifyType(ty: Type, target: Target) [2]Class {...@@ -94,15 +94,25 @@ pub fn classifyType(ty: Type, target: Target) [2]Class {
94pub fn scalarType(ty: Type, target: std.Target) Type {94pub fn scalarType(ty: Type, target: std.Target) Type {
95 switch (ty.zigTypeTag()) {95 switch (ty.zigTypeTag()) {
96 .Struct => {96 .Struct => {
97 std.debug.assert(ty.structFieldCount() == 1);97 switch (ty.containerLayout()) {
98 return scalarType(ty.structFieldType(0), target);98 .Packed => {
99 const struct_obj = ty.castTag(.@"struct").?.data;
100 return scalarType(struct_obj.backing_int_ty, target);
101 },
102 else => {
103 std.debug.assert(ty.structFieldCount() == 1);
104 return scalarType(ty.structFieldType(0), target);
105 },
106 }
99 },107 },
100 .Union => {108 .Union => {
101 const layout = ty.unionGetLayout(target);109 if (ty.containerLayout() != .Packed) {
102 if (layout.payload_size == 0 and layout.tag_size != 0) {110 const layout = ty.unionGetLayout(target);
103 return scalarType(ty.unionTagTypeSafety().?, target);111 if (layout.payload_size == 0 and layout.tag_size != 0) {
112 return scalarType(ty.unionTagTypeSafety().?, target);
113 }
114 std.debug.assert(ty.unionFields().count() == 1);
104 }115 }
105 std.debug.assert(ty.unionFields().count() == 1);
106 return scalarType(ty.unionFields().values()[0].ty, target);116 return scalarType(ty.unionFields().values()[0].ty, target);
107 },117 },
108 else => return ty,118 else => return ty,