| ... | ... | @@ -21,6 +21,7 @@ const Air = @import("../../Air.zig"); |
| 21 | 21 | const Liveness = @import("../../Liveness.zig"); |
| 22 | 22 | const Mir = @import("Mir.zig"); |
| 23 | 23 | const Emit = @import("Emit.zig"); |
| 24 | const abi = @import("abi.zig"); |
| 24 | 25 | |
| 25 | 26 | /// Wasm Value, created when generating an instruction |
| 26 | 27 | const WValue = union(enum) { |
| ... | ... | @@ -722,18 +723,15 @@ fn typeToValtype(ty: Type, target: std.Target) wasm.Valtype { |
| 722 | 723 | const bits = ty.floatBits(target); |
| 723 | 724 | if (bits == 16 or bits == 32) break :blk wasm.Valtype.f32; |
| 724 | 725 | if (bits == 64) break :blk wasm.Valtype.f64; |
| 726 | if (bits == 128) break :blk wasm.Valtype.i64; |
| 725 | 727 | return wasm.Valtype.i32; // represented as pointer to stack |
| 726 | 728 | }, |
| 727 | | .Int => blk: { |
| 729 | .Int, .Enum => blk: { |
| 728 | 730 | const info = ty.intInfo(target); |
| 729 | 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 | 733 | 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 | 735 | 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 | 785 | |
| 788 | 786 | /// Generates a `wasm.Type` from a given function type. |
| 789 | 787 | /// Memory is owned by the caller. |
| 790 | | fn genFunctype(gpa: Allocator, fn_ty: Type, target: std.Target) !wasm.Type { |
| 788 | fn genFunctype(gpa: Allocator, fn_info: Type.Payload.Function.Data, target: std.Target) !wasm.Type { |
| 791 | 789 | var params = std.ArrayList(wasm.Valtype).init(gpa); |
| 792 | 790 | defer params.deinit(); |
| 793 | 791 | var returns = std.ArrayList(wasm.Valtype).init(gpa); |
| 794 | 792 | defer returns.deinit(); |
| 795 | | const return_type = fn_ty.fnReturnType(); |
| 796 | | |
| 797 | | const want_sret = isByRef(return_type, target); |
| 798 | 793 | |
| 799 | | if (want_sret) { |
| 800 | | try params.append(typeToValtype(return_type, target)); |
| 794 | if (firstParamSRet(fn_info, 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 | } |
| 802 | 806 | |
| 803 | 807 | // param types |
| 804 | | if (fn_ty.fnParamLen() != 0) { |
| 805 | | const fn_params = try gpa.alloc(Type, fn_ty.fnParamLen()); |
| 806 | | defer gpa.free(fn_params); |
| 807 | | fn_ty.fnParamTypes(fn_params); |
| 808 | | for (fn_params) |param_type| { |
| 808 | if (fn_info.param_types.len != 0) { |
| 809 | for (fn_info.param_types) |param_type| { |
| 809 | 810 | if (!param_type.hasRuntimeBitsIgnoreComptime()) continue; |
| 810 | | try params.append(typeToValtype(param_type, target)); |
| 811 | | } |
| 812 | | } |
| 813 | 811 | |
| 814 | | // return type |
| 815 | | if (!want_sret and return_type.hasRuntimeBitsIgnoreComptime()) { |
| 816 | | try returns.append(typeToValtype(return_type, target)); |
| 812 | switch (fn_info.cc) { |
| 813 | .C => { |
| 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 | } |
| 818 | 829 | |
| 819 | 830 | return wasm.Type{ |
| ... | ... | @@ -857,7 +868,7 @@ pub fn generate( |
| 857 | 868 | } |
| 858 | 869 | |
| 859 | 870 | fn 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 | 872 | defer func_type.deinit(self.gpa); |
| 862 | 873 | self.decl.fn_link.wasm.type_index = try self.bin_file.putOrGetFuncType(func_type); |
| 863 | 874 | |
| ... | ... | @@ -957,21 +968,22 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu |
| 957 | 968 | .args = &.{}, |
| 958 | 969 | .return_value = .none, |
| 959 | 970 | }; |
| 971 | if (cc == .Naked) return result; |
| 972 | |
| 960 | 973 | var args = std.ArrayList(WValue).init(self.gpa); |
| 961 | 974 | defer args.deinit(); |
| 962 | 975 | |
| 963 | | const ret_ty = fn_ty.fnReturnType(); |
| 964 | 976 | // Check if we store the result as a pointer to the stack rather than |
| 965 | 977 | // by value |
| 966 | | if (isByRef(ret_ty, self.target)) { |
| 978 | if (firstParamSRet(fn_ty.fnInfo(), self.target)) { |
| 967 | 979 | // the sret arg will be passed as first argument, therefore we |
| 968 | 980 | // set the `return_value` before allocating locals for regular args. |
| 969 | 981 | result.return_value = .{ .local = self.local_index }; |
| 970 | 982 | self.local_index += 1; |
| 971 | 983 | } |
| 984 | |
| 972 | 985 | switch (cc) { |
| 973 | | .Naked => return result, |
| 974 | | .Unspecified, .C => { |
| 986 | .Unspecified => { |
| 975 | 987 | for (param_types) |ty| { |
| 976 | 988 | if (!ty.hasRuntimeBitsIgnoreComptime()) { |
| 977 | 989 | continue; |
| ... | ... | @@ -981,12 +993,105 @@ fn resolveCallingConventionValues(self: *Self, fn_ty: Type) InnerError!CallWValu |
| 981 | 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 | 1008 | result.args = args.toOwnedSlice(); |
| 987 | 1009 | return result; |
| 988 | 1010 | } |
| 989 | 1011 | |
| 1012 | fn 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. |
| 1027 | fn 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. |
| 1073 | fn 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 | 1095 | /// Creates a local for the initial stack value |
| 991 | 1096 | /// Asserts `initial_stack_value` is `.none` |
| 992 | 1097 | fn initializeStack(self: *Self) !void { |
| ... | ... | @@ -1489,11 +1594,31 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 1489 | 1594 | fn airRet(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1490 | 1595 | const un_op = self.air.instructions.items(.data)[inst].un_op; |
| 1491 | 1596 | const operand = try self.resolveInst(un_op); |
| 1597 | const ret_ty = self.decl.ty.fnReturnType(); |
| 1492 | 1598 | |
| 1493 | 1599 | // result must be stored in the stack and we return a pointer |
| 1494 | 1600 | // to the stack instead |
| 1495 | 1601 | if (self.return_value != .none) { |
| 1496 | 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 | 1622 | } else { |
| 1498 | 1623 | try self.emitWValue(operand); |
| 1499 | 1624 | } |
| ... | ... | @@ -1509,9 +1634,10 @@ fn airRetPtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1509 | 1634 | return self.allocStack(Type.usize); // create pointer to void |
| 1510 | 1635 | } |
| 1511 | 1636 | |
| 1512 | | if (isByRef(child_type, self.target)) { |
| 1637 | if (firstParamSRet(self.decl.ty.fnInfo(), self.target)) { |
| 1513 | 1638 | return self.return_value; |
| 1514 | 1639 | } |
| 1640 | |
| 1515 | 1641 | return self.allocStackPtr(inst); |
| 1516 | 1642 | } |
| 1517 | 1643 | |
| ... | ... | @@ -1521,7 +1647,7 @@ fn airRetLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1521 | 1647 | const ret_ty = self.air.typeOf(un_op).childType(); |
| 1522 | 1648 | if (!ret_ty.hasRuntimeBitsIgnoreComptime()) return WValue.none; |
| 1523 | 1649 | |
| 1524 | | if (!isByRef(ret_ty, self.target)) { |
| 1650 | if (!firstParamSRet(self.decl.ty.fnInfo(), self.target)) { |
| 1525 | 1651 | const result = try self.load(operand, ret_ty, 0); |
| 1526 | 1652 | try self.emitWValue(result); |
| 1527 | 1653 | } |
| ... | ... | @@ -1544,7 +1670,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. |
| 1544 | 1670 | else => unreachable, |
| 1545 | 1671 | }; |
| 1546 | 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); |
| 1548 | 1674 | |
| 1549 | 1675 | const callee: ?*Decl = blk: { |
| 1550 | 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 | 1680 | break :blk module.declPtr(func.data.owner_decl); |
| 1555 | 1681 | } else if (func_val.castTag(.extern_fn)) |extern_fn| { |
| 1556 | 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 | 1684 | defer func_type.deinit(self.gpa); |
| 1559 | 1685 | ext_decl.fn_link.wasm.type_index = try self.bin_file.putOrGetFuncType(func_type); |
| 1560 | 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 | 1705 | const arg_ty = self.air.typeOf(arg_ref); |
| 1580 | 1706 | if (!arg_ty.hasRuntimeBitsIgnoreComptime()) continue; |
| 1581 | 1707 | |
| 1582 | | switch (arg_val) { |
| 1583 | | .stack_offset => try self.emitWValue(try self.buildPointerOffset(arg_val, 0, .new)), |
| 1584 | | else => try self.emitWValue(arg_val), |
| 1585 | | } |
| 1708 | try self.lowerArg(fn_ty.fnInfo().cc, arg_ty, arg_val); |
| 1586 | 1709 | } |
| 1587 | 1710 | |
| 1588 | 1711 | if (callee) |direct| { |
| ... | ... | @@ -1594,7 +1717,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallOptions. |
| 1594 | 1717 | const operand = try self.resolveInst(pl_op.operand); |
| 1595 | 1718 | try self.emitWValue(operand); |
| 1596 | 1719 | |
| 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 | 1721 | defer fn_type.deinit(self.gpa); |
| 1599 | 1722 | |
| 1600 | 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 | 1731 | return WValue.none; |
| 1609 | 1732 | } else if (first_param_sret) { |
| 1610 | 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 | 1742 | } else { |
| 1612 | 1743 | const result_local = try self.allocLocal(ret_ty); |
| 1613 | 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 | 1880 | } |
| 1750 | 1881 | |
| 1751 | 1882 | fn airArg(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 1752 | | _ = inst; |
| 1753 | | defer self.arg_index += 1; |
| 1754 | | return self.args[self.arg_index]; |
| 1883 | const arg = self.args[self.arg_index]; |
| 1884 | const cc = self.decl.ty.fnInfo().cc; |
| 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 | } |
| 1756 | 1898 | |
| 1757 | 1899 | fn airBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue { |