authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-04-24 21:49:12+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-04-26 12:20:27+02:00
log5f2d0d414dc44af7bda0e8d805d038e8f1a6f9d3
tree713e23b0b8d18270596ea043ac0a136a18561a98
parentcb49af6c9a6b790ec341cb081d1fec381d44e9a4

wasm: Implement codegen for C-ABI

This implements passing arguments and storing return values correctly for the C-ABI as specified by the tool-convention: https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md There's definitely room for better codegen in follow-up commits.

2 files changed, 207 insertions(+), 46 deletions(-)

src/arch/wasm/CodeGen.zig+183-41
...@@ -21,6 +21,7 @@ const Air = @import("../../Air.zig");...@@ -21,6 +21,7 @@ const Air = @import("../../Air.zig");
21const Liveness = @import("../../Liveness.zig");21const Liveness = @import("../../Liveness.zig");
22const Mir = @import("Mir.zig");22const Mir = @import("Mir.zig");
23const Emit = @import("Emit.zig");23const Emit = @import("Emit.zig");
24const abi = @import("abi.zig");
2425
25/// Wasm Value, created when generating an instruction26/// Wasm Value, created when generating an instruction
26const WValue = union(enum) {27const WValue = union(enum) {
...@@ -722,18 +723,15 @@ fn typeToValtype(ty: Type, target: std.Target) wasm.Valtype {...@@ -722,18 +723,15 @@ fn typeToValtype(ty: Type, target: std.Target) wasm.Valtype {
722 const bits = ty.floatBits(target);723 const bits = ty.floatBits(target);
723 if (bits == 16 or bits == 32) break :blk wasm.Valtype.f32;724 if (bits == 16 or bits == 32) break :blk wasm.Valtype.f32;
724 if (bits == 64) break :blk wasm.Valtype.f64;725 if (bits == 64) break :blk wasm.Valtype.f64;
726 if (bits == 128) break :blk wasm.Valtype.i64;
725 return wasm.Valtype.i32; // represented as pointer to stack727 return wasm.Valtype.i32; // represented as pointer to stack
726 },728 },
727 .Int => blk: {729 .Int, .Enum => blk: {
728 const info = ty.intInfo(target);730 const info = ty.intInfo(target);
729 if (info.bits <= 32) break :blk wasm.Valtype.i32;731 if (info.bits <= 32) break :blk wasm.Valtype.i32;
730 if (info.bits > 32 and info.bits <= 64) break :blk wasm.Valtype.i64;732 if (info.bits > 32 and info.bits <= 128) break :blk wasm.Valtype.i64;
731 break :blk wasm.Valtype.i32; // represented as pointer to stack733 break :blk wasm.Valtype.i32; // represented as pointer to stack
732 },734 },
733 .Enum => {
734 var buf: Type.Payload.Bits = undefined;
735 return typeToValtype(ty.intTagType(&buf), target);
736 },
737 else => wasm.Valtype.i32, // all represented as reference/immediate735 else => wasm.Valtype.i32, // all represented as reference/immediate
738 };736 };
739}737}
...@@ -787,33 +785,46 @@ fn allocLocal(self: *Self, ty: Type) InnerError!WValue {...@@ -787,33 +785,46 @@ fn allocLocal(self: *Self, ty: Type) InnerError!WValue {
787785
788/// Generates a `wasm.Type` from a given function type.786/// Generates a `wasm.Type` from a given function type.
789/// Memory is owned by the caller.787/// Memory is owned by the caller.
790fn genFunctype(gpa: Allocator, fn_ty: Type, target: std.Target) !wasm.Type {788fn genFunctype(gpa: Allocator, fn_info: Type.Payload.Function.Data, target: std.Target) !wasm.Type {
791 var params = std.ArrayList(wasm.Valtype).init(gpa);789 var params = std.ArrayList(wasm.Valtype).init(gpa);
792 defer params.deinit();790 defer params.deinit();
793 var returns = std.ArrayList(wasm.Valtype).init(gpa);791 var returns = std.ArrayList(wasm.Valtype).init(gpa);
794 defer returns.deinit();792 defer returns.deinit();
795 const return_type = fn_ty.fnReturnType();
796
797 const want_sret = isByRef(return_type, target);
798793
799 if (want_sret) {794 if (firstParamSRet(fn_info, target)) {
800 try params.append(typeToValtype(return_type, target));795 try params.append(typeToValtype(fn_info.return_type, target));
796 } else if (fn_info.return_type.hasRuntimeBitsIgnoreComptime()) {
797 if (fn_info.cc == .C) {
798 const res_classes = abi.classifyType(fn_info.return_type, target);
799 assert(res_classes[0] == .direct and res_classes[1] == .none);
800 const scalar_type = abi.scalarType(fn_info.return_type, target);
801 try returns.append(typeToValtype(scalar_type, target));
802 } else {
803 try returns.append(typeToValtype(fn_info.return_type, target));
804 }
801 }805 }
802806
803 // param types807 // param types
804 if (fn_ty.fnParamLen() != 0) {808 if (fn_info.param_types.len != 0) {
805 const fn_params = try gpa.alloc(Type, fn_ty.fnParamLen());809 for (fn_info.param_types) |param_type| {
806 defer gpa.free(fn_params);
807 fn_ty.fnParamTypes(fn_params);
808 for (fn_params) |param_type| {
809 if (!param_type.hasRuntimeBitsIgnoreComptime()) continue;810 if (!param_type.hasRuntimeBitsIgnoreComptime()) continue;
810 try params.append(typeToValtype(param_type, target));
811 }
812 }
813811
814 // return type812 switch (fn_info.cc) {
815 if (!want_sret and return_type.hasRuntimeBitsIgnoreComptime()) {813 .C => {
816 try returns.append(typeToValtype(return_type, target));814 const param_classes = abi.classifyType(param_type, target);
815 for (param_classes) |class| {
816 if (class == .none) continue;
817 if (class == .direct) {
818 const scalar_type = abi.scalarType(param_type, target);
819 try params.append(typeToValtype(scalar_type, target));
820 } else {
821 try params.append(typeToValtype(param_type, target));
822 }
823 }
824 },
825 else => try params.append(typeToValtype(param_type, target)),
826 }
827 }
817 }828 }
818829
819 return wasm.Type{830 return wasm.Type{
...@@ -857,7 +868,7 @@ pub fn generate(...@@ -857,7 +868,7 @@ pub fn generate(
857}868}
858869
859fn genFunc(self: *Self) InnerError!void {870fn genFunc(self: *Self) InnerError!void {
860 var func_type = try genFunctype(self.gpa, self.decl.ty, self.target);871 var func_type = try genFunctype(self.gpa, self.decl.ty.fnInfo(), self.target);
861 defer func_type.deinit(self.gpa);872 defer func_type.deinit(self.gpa);
862 self.decl.fn_link.wasm.type_index = try self.bin_file.putOrGetFuncType(func_type);873 self.decl.fn_link.wasm.type_index = try self.bin_file.putOrGetFuncType(func_type);
863874
...@@ -957,21 +968,22 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu...@@ -957,21 +968,22 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu
957 .args = &.{},968 .args = &.{},
958 .return_value = .none,969 .return_value = .none,
959 };970 };
971 if (cc == .Naked) return result;
972
960 var args = std.ArrayList(WValue).init(self.gpa);973 var args = std.ArrayList(WValue).init(self.gpa);
961 defer args.deinit();974 defer args.deinit();
962975
963 const ret_ty = fn_ty.fnReturnType();
964 // Check if we store the result as a pointer to the stack rather than976 // Check if we store the result as a pointer to the stack rather than
965 // by value977 // by value
966 if (isByRef(ret_ty, self.target)) {978 if (firstParamSRet(fn_ty.fnInfo(), self.target)) {
967 // the sret arg will be passed as first argument, therefore we979 // the sret arg will be passed as first argument, therefore we
968 // set the `return_value` before allocating locals for regular args.980 // set the `return_value` before allocating locals for regular args.
969 result.return_value = .{ .local = self.local_index };981 result.return_value = .{ .local = self.local_index };
970 self.local_index += 1;982 self.local_index += 1;
971 }983 }
984
972 switch (cc) {985 switch (cc) {
973 .Naked => return result,986 .Unspecified => {
974 .Unspecified, .C => {
975 for (param_types) |ty| {987 for (param_types) |ty| {
976 if (!ty.hasRuntimeBitsIgnoreComptime()) {988 if (!ty.hasRuntimeBitsIgnoreComptime()) {
977 continue;989 continue;
...@@ -981,12 +993,105 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu...@@ -981,12 +993,105 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu
981 self.local_index += 1;993 self.local_index += 1;
982 }994 }
983 },995 },
984 else => return self.fail("TODO implement function parameters for cc '{}' on wasm", .{cc}),996 .C => {
997 for (param_types) |ty| {
998 const ty_classes = abi.classifyType(ty, self.target);
999 for (ty_classes) |class| {
1000 if (class == .none) continue;
1001 try args.append(.{ .local = self.local_index });
1002 self.local_index += 1;
1003 }
1004 }
1005 },
1006 else => return self.fail("calling convention '{s}' not supported for Wasm", .{@tagName(cc)}),
985 }1007 }
986 result.args = args.toOwnedSlice();1008 result.args = args.toOwnedSlice();
987 return result;1009 return result;
988}1010}
9891011
1012fn firstParamSRet(fn_info: Type.Payload.Function.Data, target: std.Target) bool {
1013 switch (fn_info.cc) {
1014 .Unspecified, .Inline => return isByRef(fn_info.return_type, target),
1015 .C => {
1016 const ty_classes = abi.classifyType(fn_info.return_type, target);
1017 if (ty_classes[0] == .indirect) return true;
1018 if (ty_classes[0] == .direct and ty_classes[1] == .direct) return true;
1019 return false;
1020 },
1021 else => return false,
1022 }
1023}
1024
1025/// Lowers a Zig type and its value based on a given calling convention to ensure
1026/// it matches the ABI.
1027fn lowerArg(self: *Self, cc: std.builtin.CallingConvention, ty: Type, value: WValue) !void {
1028 if (cc != .C) {
1029 return self.lowerToStack(value);
1030 }
1031
1032 const ty_classes = abi.classifyType(ty, self.target);
1033 assert(ty_classes[0] != .none);
1034 switch (ty.zigTypeTag()) {
1035 .Struct, .Union => {
1036 if (ty_classes[0] == .indirect) {
1037 return self.lowerToStack(value);
1038 }
1039 assert(ty_classes[0] == .direct);
1040 const scalar_type = abi.scalarType(ty, self.target);
1041 const abi_size = scalar_type.abiSize(self.target);
1042 const opcode = buildOpcode(.{
1043 .op = .load,
1044 .width = @intCast(u8, abi_size),
1045 .signedness = if (scalar_type.isSignedInt()) .signed else .unsigned,
1046 .valtype1 = typeToValtype(scalar_type, self.target),
1047 });
1048 try self.emitWValue(value);
1049 try self.addMemArg(Mir.Inst.Tag.fromOpcode(opcode), .{
1050 .offset = value.offset(),
1051 .alignment = scalar_type.abiAlignment(self.target),
1052 });
1053 },
1054 .Int, .Float => {
1055 if (ty_classes[1] == .none) {
1056 return self.lowerToStack(value);
1057 }
1058 assert(ty_classes[0] == .direct and ty_classes[1] == .direct);
1059 assert(ty.abiSize(self.target) == 16);
1060 // in this case we have an integer or float that must be lowered as 2 i64's.
1061 try self.emitWValue(value);
1062 try self.addMemArg(.i64_load, .{ .offset = value.offset(), .alignment = 16 });
1063 try self.emitWValue(value);
1064 try self.addMemArg(.i64_load, .{ .offset = value.offset() + 8, .alignment = 16 });
1065 },
1066 else => return self.lowerToStack(value),
1067 }
1068}
1069
1070/// Lowers a `WValue` to the stack. This means when the `value` results in
1071/// `.stack_offset` we calculate the pointer of this offset and use that.
1072/// The value is left on the stack, and not stored in any temporary.
1073fn lowerToStack(self: *Self, value: WValue) !void {
1074 switch (value) {
1075 .stack_offset => |offset| {
1076 try self.emitWValue(value);
1077 if (offset > 0) {
1078 switch (self.arch()) {
1079 .wasm32 => {
1080 try self.addImm32(@bitCast(i32, offset));
1081 try self.addTag(.i32_add);
1082 },
1083 .wasm64 => {
1084 try self.addImm64(offset);
1085 try self.addTag(.i64_add);
1086 },
1087 else => unreachable,
1088 }
1089 }
1090 },
1091 else => try self.emitWValue(value),
1092 }
1093}
1094
990/// Creates a local for the initial stack value1095/// Creates a local for the initial stack value
991/// Asserts `initial_stack_value` is `.none`1096/// Asserts `initial_stack_value` is `.none`
992fn initializeStack(self: *Self) !void {1097fn initializeStack(self: *Self) !void {
...@@ -1489,11 +1594,31 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {...@@ -1489,11 +1594,31 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
1489fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {1594fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1490 const un_op = self.air.instructions.items(.data)[inst].un_op;1595 const un_op = self.air.instructions.items(.data)[inst].un_op;
1491 const operand = try self.resolveInst(un_op);1596 const operand = try self.resolveInst(un_op);
1597 const ret_ty = self.decl.ty.fnReturnType();
14921598
1493 // result must be stored in the stack and we return a pointer1599 // result must be stored in the stack and we return a pointer
1494 // to the stack instead1600 // to the stack instead
1495 if (self.return_value != .none) {1601 if (self.return_value != .none) {
1496 try self.store(self.return_value, operand, self.decl.ty.fnReturnType(), 0);1602 try self.store(self.return_value, operand, self.decl.ty.fnReturnType(), 0);
1603 } else if (self.decl.ty.fnInfo().cc == .C and ret_ty.hasRuntimeBitsIgnoreComptime()) {
1604 switch (ret_ty.zigTypeTag()) {
1605 // Aggregate types can be lowered as a singular value
1606 .Struct, .Union => {
1607 const scalar_type = abi.scalarType(ret_ty, self.target);
1608 try self.emitWValue(operand);
1609 const opcode = buildOpcode(.{
1610 .op = .load,
1611 .width = @intCast(u8, scalar_type.abiSize(self.target)),
1612 .signedness = if (scalar_type.isSignedInt()) .signed else .unsigned,
1613 .valtype1 = typeToValtype(scalar_type, self.target),
1614 });
1615 try self.addMemArg(Mir.Inst.Tag.fromOpcode(opcode), .{
1616 .offset = operand.offset(),
1617 .alignment = scalar_type.abiAlignment(self.target),
1618 });
1619 },
1620 else => try self.emitWValue(operand),
1621 }
1497 } else {1622 } else {
1498 try self.emitWValue(operand);1623 try self.emitWValue(operand);
1499 }1624 }
...@@ -1509,9 +1634,10 @@ fn airRetPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1509,9 +1634,10 @@ fn airRetPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1509 return self.allocStack(Type.usize); // create pointer to void1634 return self.allocStack(Type.usize); // create pointer to void
1510 }1635 }
15111636
1512 if (isByRef(child_type, self.target)) {1637 if (firstParamSRet(self.decl.ty.fnInfo(), self.target)) {
1513 return self.return_value;1638 return self.return_value;
1514 }1639 }
1640
1515 return self.allocStackPtr(inst);1641 return self.allocStackPtr(inst);
1516}1642}
15171643
...@@ -1521,7 +1647,7 @@ fn airRetLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1521,7 +1647,7 @@ fn airRetLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1521 const ret_ty = self.air.typeOf(un_op).childType();1647 const ret_ty = self.air.typeOf(un_op).childType();
1522 if (!ret_ty.hasRuntimeBitsIgnoreComptime()) return WValue.none;1648 if (!ret_ty.hasRuntimeBitsIgnoreComptime()) return WValue.none;
15231649
1524 if (!isByRef(ret_ty, self.target)) {1650 if (!firstParamSRet(self.decl.ty.fnInfo(), self.target)) {
1525 const result = try self.load(operand, ret_ty, 0);1651 const result = try self.load(operand, ret_ty, 0);
1526 try self.emitWValue(result);1652 try self.emitWValue(result);
1527 }1653 }
...@@ -1544,7 +1670,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions....@@ -1544,7 +1670,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
1544 else => unreachable,1670 else => unreachable,
1545 };1671 };
1546 const ret_ty = fn_ty.fnReturnType();1672 const ret_ty = fn_ty.fnReturnType();
1547 const first_param_sret = isByRef(ret_ty, self.target);1673 const first_param_sret = firstParamSRet(fn_ty.fnInfo(), self.target);
15481674
1549 const callee: ?*Decl = blk: {1675 const callee: ?*Decl = blk: {
1550 const func_val = self.air.value(pl_op.operand) orelse break :blk null;1676 const func_val = self.air.value(pl_op.operand) orelse break :blk null;
...@@ -1554,7 +1680,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions....@@ -1554,7 +1680,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
1554 break :blk module.declPtr(func.data.owner_decl);1680 break :blk module.declPtr(func.data.owner_decl);
1555 } else if (func_val.castTag(.extern_fn)) |extern_fn| {1681 } else if (func_val.castTag(.extern_fn)) |extern_fn| {
1556 const ext_decl = module.declPtr(extern_fn.data.owner_decl);1682 const ext_decl = module.declPtr(extern_fn.data.owner_decl);
1557 var func_type = try genFunctype(self.gpa, ext_decl.ty, self.target);1683 var func_type = try genFunctype(self.gpa, ext_decl.ty.fnInfo(), self.target);
1558 defer func_type.deinit(self.gpa);1684 defer func_type.deinit(self.gpa);
1559 ext_decl.fn_link.wasm.type_index = try self.bin_file.putOrGetFuncType(func_type);1685 ext_decl.fn_link.wasm.type_index = try self.bin_file.putOrGetFuncType(func_type);
1560 try self.bin_file.addOrUpdateImport(ext_decl);1686 try self.bin_file.addOrUpdateImport(ext_decl);
...@@ -1579,10 +1705,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions....@@ -1579,10 +1705,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
1579 const arg_ty = self.air.typeOf(arg_ref);1705 const arg_ty = self.air.typeOf(arg_ref);
1580 if (!arg_ty.hasRuntimeBitsIgnoreComptime()) continue;1706 if (!arg_ty.hasRuntimeBitsIgnoreComptime()) continue;
15811707
1582 switch (arg_val) {1708 try self.lowerArg(fn_ty.fnInfo().cc, arg_ty, arg_val);
1583 .stack_offset => try self.emitWValue(try self.buildPointerOffset(arg_val, 0, .new)),
1584 else => try self.emitWValue(arg_val),
1585 }
1586 }1709 }
15871710
1588 if (callee) |direct| {1711 if (callee) |direct| {
...@@ -1594,7 +1717,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions....@@ -1594,7 +1717,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
1594 const operand = try self.resolveInst(pl_op.operand);1717 const operand = try self.resolveInst(pl_op.operand);
1595 try self.emitWValue(operand);1718 try self.emitWValue(operand);
15961719
1597 var fn_type = try genFunctype(self.gpa, fn_ty, self.target);1720 var fn_type = try genFunctype(self.gpa, fn_ty.fnInfo(), self.target);
1598 defer fn_type.deinit(self.gpa);1721 defer fn_type.deinit(self.gpa);
15991722
1600 const fn_type_index = try self.bin_file.putOrGetFuncType(fn_type);1723 const fn_type_index = try self.bin_file.putOrGetFuncType(fn_type);
...@@ -1608,6 +1731,14 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions....@@ -1608,6 +1731,14 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.
1608 return WValue.none;1731 return WValue.none;
1609 } else if (first_param_sret) {1732 } else if (first_param_sret) {
1610 return sret;1733 return sret;
1734 // TODO: Make this less fragile and optimize
1735 } else if (fn_ty.fnInfo().cc == .C and ret_ty.zigTypeTag() == .Struct or ret_ty.zigTypeTag() == .Union) {
1736 const result_local = try self.allocLocal(ret_ty);
1737 try self.addLabel(.local_set, result_local.local);
1738 const scalar_type = abi.scalarType(ret_ty, self.target);
1739 const result = try self.allocStack(scalar_type);
1740 try self.store(result, result_local, scalar_type, 0);
1741 return result;
1611 } else {1742 } else {
1612 const result_local = try self.allocLocal(ret_ty);1743 const result_local = try self.allocLocal(ret_ty);
1613 try self.addLabel(.local_set, result_local.local);1744 try self.addLabel(.local_set, result_local.local);
...@@ -1749,9 +1880,20 @@ fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue {...@@ -1749,9 +1880,20 @@ fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue {
1749}1880}
17501881
1751fn airArg(self: *Self, inst: Air.Inst.Index) InnerError!WValue {1882fn airArg(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1752 _ = inst;1883 const arg = self.args[self.arg_index];
1753 defer self.arg_index += 1;1884 const cc = self.decl.ty.fnInfo().cc;
1754 return self.args[self.arg_index];1885 if (cc == .C) {
1886 const ty = self.air.typeOfIndex(inst);
1887 const arg_classes = abi.classifyType(ty, self.target);
1888 for (arg_classes) |class| {
1889 if (class != .none) {
1890 self.arg_index += 1;
1891 }
1892 }
1893 } else {
1894 self.arg_index += 1;
1895 }
1896 return arg;
1755}1897}
17561898
1757fn airBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {1899fn airBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
src/arch/wasm/abi.zig+24-5
...@@ -52,6 +52,7 @@ pub fn classifyType(ty: Type, target: Target) [2]Class {...@@ -52,6 +52,7 @@ pub fn classifyType(ty: Type, target: Target) [2]Class {
52 return memory;52 return memory;
53 },53 },
54 .Bool => return direct,54 .Bool => return direct,
55 .Array => return memory,
55 .ErrorUnion => {56 .ErrorUnion => {
56 const has_tag = ty.errorUnionSet().hasRuntimeBitsIgnoreComptime();57 const has_tag = ty.errorUnionSet().hasRuntimeBitsIgnoreComptime();
57 const has_pl = ty.errorUnionPayload().hasRuntimeBitsIgnoreComptime();58 const has_pl = ty.errorUnionPayload().hasRuntimeBitsIgnoreComptime();
...@@ -73,16 +74,13 @@ pub fn classifyType(ty: Type, target: Target) [2]Class {...@@ -73,16 +74,13 @@ pub fn classifyType(ty: Type, target: Target) [2]Class {
73 if (ty.isSlice()) return memory;74 if (ty.isSlice()) return memory;
74 return direct;75 return direct;
75 },76 },
76 .Array => {
77 if (ty.arrayLen() == 1) return direct;
78 return memory;
79 },
80 .Union => {77 .Union => {
81 const layout = ty.unionGetLayout(target);78 const layout = ty.unionGetLayout(target);
82 if (layout.payload_size == 0 and layout.tag_size != 0) {79 if (layout.payload_size == 0 and layout.tag_size != 0) {
83 return classifyType(ty.unionTagType().?, target);80 return classifyType(ty.unionTagType().?, target);
84 }81 }
85 return classifyType(ty.errorUnionPayload(), target);82 if (ty.unionFields().count() > 1) return memory;
83 return classifyType(ty.unionFields().values()[0].ty, target);
86 },84 },
87 .AnyFrame, .Frame => return direct,85 .AnyFrame, .Frame => return direct,
8886
...@@ -100,3 +98,24 @@ pub fn classifyType(ty: Type, target: Target) [2]Class {...@@ -100,3 +98,24 @@ pub fn classifyType(ty: Type, target: Target) [2]Class {
100 => unreachable,98 => unreachable,
101 }99 }
102}100}
101
102/// Returns the scalar type a given type can represent.
103/// Asserts given type can be represented as scalar, such as
104/// a struct with a single scalar field.
105pub fn scalarType(ty: Type, target: std.Target) Type {
106 switch (ty.zigTypeTag()) {
107 .Struct => {
108 std.debug.assert(ty.structFieldCount() == 1);
109 return scalarType(ty.structFieldType(0), target);
110 },
111 .Union => {
112 const layout = ty.unionGetLayout(target);
113 if (layout.payload_size == 0 and layout.tag_size != 0) {
114 return scalarType(ty.unionTagType().?, target);
115 }
116 std.debug.assert(ty.unionFields().count() == 1);
117 return scalarType(ty.unionFields().values()[0].ty, target);
118 },
119 else => return ty,
120 }
121}