| ... | ... | @@ -29,6 +29,9 @@ const errUnionErrorOffset = codegen.errUnionErrorOffset; |
| 29 | 29 | |
| 30 | 30 | /// Wasm Value, created when generating an instruction |
| 31 | 31 | const WValue = union(enum) { |
| 32 | /// `WValue` which has been freed and may no longer hold |
| 33 | /// any references. |
| 34 | dead: void, |
| 32 | 35 | /// May be referenced but is unused |
| 33 | 36 | none: void, |
| 34 | 37 | /// The value lives on top of the stack |
| ... | ... | @@ -86,6 +89,7 @@ const WValue = union(enum) { |
| 86 | 89 | fn offset(value: WValue) u32 { |
| 87 | 90 | switch (value) { |
| 88 | 91 | .stack_offset => |stack_offset| return stack_offset.value, |
| 92 | .dead => unreachable, |
| 89 | 93 | else => return 0, |
| 90 | 94 | } |
| 91 | 95 | } |
| ... | ... | @@ -123,7 +127,8 @@ const WValue = union(enum) { |
| 123 | 127 | .f64 => gen.free_locals_f64.append(gen.gpa, local_value) catch return, |
| 124 | 128 | .v128 => gen.free_locals_v128.append(gen.gpa, local_value) catch return, |
| 125 | 129 | } |
| 126 | | value.* = undefined; |
| 130 | log.debug("freed local ({d}) of type {}", .{ local_value, valtype }); |
| 131 | value.* = .dead; |
| 127 | 132 | } |
| 128 | 133 | }; |
| 129 | 134 | |
| ... | ... | @@ -832,6 +837,7 @@ const Branch = struct { |
| 832 | 837 | |
| 833 | 838 | fn deinit(branch: *Branch, gpa: Allocator) void { |
| 834 | 839 | branch.values.deinit(gpa); |
| 840 | branch.* = undefined; |
| 835 | 841 | } |
| 836 | 842 | }; |
| 837 | 843 | |
| ... | ... | @@ -880,7 +886,11 @@ fn processDeath(func: *CodeGen, ref: Air.Inst.Ref) void { |
| 880 | 886 | // TODO: Upon branch consolidation free any locals if needed. |
| 881 | 887 | const value = func.currentBranch().values.getPtr(ref) orelse return; |
| 882 | 888 | if (value.* != .local) return; |
| 883 | | log.debug("Decreasing reference for ref: %{?d}\n", .{Air.refToIndex(ref)}); |
| 889 | const reserved_indexes = func.args.len + @boolToInt(func.return_value != .none); |
| 890 | if (value.local.value < reserved_indexes) { |
| 891 | return; // function arguments can never be re-used |
| 892 | } |
| 893 | log.debug("Decreasing reference for ref: %{?d}, using local '{d}'", .{ Air.refToIndex(ref), value.local.value }); |
| 884 | 894 | value.local.references -= 1; // if this panics, a call to `reuseOperand` was forgotten by the developer |
| 885 | 895 | if (value.local.references == 0) { |
| 886 | 896 | value.free(func); |
| ... | ... | @@ -1026,6 +1036,7 @@ fn genBlockType(ty: Type, target: std.Target) u8 { |
| 1026 | 1036 | /// Writes the bytecode depending on the given `WValue` in `val` |
| 1027 | 1037 | fn emitWValue(func: *CodeGen, value: WValue) InnerError!void { |
| 1028 | 1038 | switch (value) { |
| 1039 | .dead => unreachable, // reference to free'd `WValue` (missing reuseOperand?) |
| 1029 | 1040 | .none, .stack => {}, // no-op |
| 1030 | 1041 | .local => |idx| try func.addLabel(.local_get, idx.value), |
| 1031 | 1042 | .imm32 => |val| try func.addImm32(@bitCast(i32, val)), |
| ... | ... | @@ -1082,27 +1093,27 @@ fn allocLocal(func: *CodeGen, ty: Type) InnerError!WValue { |
| 1082 | 1093 | const valtype = typeToValtype(ty, func.target); |
| 1083 | 1094 | switch (valtype) { |
| 1084 | 1095 | .i32 => if (func.free_locals_i32.popOrNull()) |index| { |
| 1085 | | log.debug("reusing local ({d}) of type {}\n", .{ index, valtype }); |
| 1096 | log.debug("reusing local ({d}) of type {}", .{ index, valtype }); |
| 1086 | 1097 | return WValue{ .local = .{ .value = index, .references = 1 } }; |
| 1087 | 1098 | }, |
| 1088 | 1099 | .i64 => if (func.free_locals_i64.popOrNull()) |index| { |
| 1089 | | log.debug("reusing local ({d}) of type {}\n", .{ index, valtype }); |
| 1100 | log.debug("reusing local ({d}) of type {}", .{ index, valtype }); |
| 1090 | 1101 | return WValue{ .local = .{ .value = index, .references = 1 } }; |
| 1091 | 1102 | }, |
| 1092 | 1103 | .f32 => if (func.free_locals_f32.popOrNull()) |index| { |
| 1093 | | log.debug("reusing local ({d}) of type {}\n", .{ index, valtype }); |
| 1104 | log.debug("reusing local ({d}) of type {}", .{ index, valtype }); |
| 1094 | 1105 | return WValue{ .local = .{ .value = index, .references = 1 } }; |
| 1095 | 1106 | }, |
| 1096 | 1107 | .f64 => if (func.free_locals_f64.popOrNull()) |index| { |
| 1097 | | log.debug("reusing local ({d}) of type {}\n", .{ index, valtype }); |
| 1108 | log.debug("reusing local ({d}) of type {}", .{ index, valtype }); |
| 1098 | 1109 | return WValue{ .local = .{ .value = index, .references = 1 } }; |
| 1099 | 1110 | }, |
| 1100 | 1111 | .v128 => if (func.free_locals_v128.popOrNull()) |index| { |
| 1101 | | log.debug("reusing local ({d}) of type {}\n", .{ index, valtype }); |
| 1112 | log.debug("reusing local ({d}) of type {}", .{ index, valtype }); |
| 1102 | 1113 | return WValue{ .local = .{ .value = index, .references = 1 } }; |
| 1103 | 1114 | }, |
| 1104 | 1115 | } |
| 1105 | | log.debug("new local of type {}\n", .{valtype}); |
| 1116 | log.debug("new local of type {}", .{valtype}); |
| 1106 | 1117 | // no local was free to be re-used, so allocate a new local instead |
| 1107 | 1118 | return func.ensureAllocLocal(ty); |
| 1108 | 1119 | } |
| ... | ... | @@ -1222,6 +1233,7 @@ fn genFunc(func: *CodeGen) InnerError!void { |
| 1222 | 1233 | defer { |
| 1223 | 1234 | var outer_branch = func.branches.pop(); |
| 1224 | 1235 | outer_branch.deinit(func.gpa); |
| 1236 | assert(func.branches.items.len == 0); // missing branch merge |
| 1225 | 1237 | } |
| 1226 | 1238 | // Generate MIR for function body |
| 1227 | 1239 | try func.genBody(func.air.getMainBody()); |
| ... | ... | @@ -1242,7 +1254,7 @@ fn genFunc(func: *CodeGen) InnerError!void { |
| 1242 | 1254 | |
| 1243 | 1255 | // check if we have to initialize and allocate anything into the stack frame. |
| 1244 | 1256 | // If so, create enough stack space and insert the instructions at the front of the list. |
| 1245 | | if (func.stack_size > 0) { |
| 1257 | if (func.initial_stack_value != .none) { |
| 1246 | 1258 | var prologue = std.ArrayList(Mir.Inst).init(func.gpa); |
| 1247 | 1259 | defer prologue.deinit(); |
| 1248 | 1260 | |
| ... | ... | @@ -1593,10 +1605,16 @@ fn memcpy(func: *CodeGen, dst: WValue, src: WValue, len: WValue) !void { |
| 1593 | 1605 | else => {}, |
| 1594 | 1606 | } |
| 1595 | 1607 | |
| 1596 | | // TODO: We should probably lower this to a call to compiler_rt |
| 1597 | | // But for now, we implement it manually |
| 1598 | | var offset = try func.ensureAllocLocal(Type.usize); // local for counter |
| 1608 | // allocate a local for the offset, and set it to 0. |
| 1609 | // This to ensure that inside loops we correctly re-set the counter. |
| 1610 | var offset = try func.allocLocal(Type.usize); // local for counter |
| 1599 | 1611 | defer offset.free(func); |
| 1612 | switch (func.arch()) { |
| 1613 | .wasm32 => try func.addImm32(0), |
| 1614 | .wasm64 => try func.addImm64(0), |
| 1615 | else => unreachable, |
| 1616 | } |
| 1617 | try func.addLabel(.local_set, offset.local.value); |
| 1600 | 1618 | |
| 1601 | 1619 | // outer block to jump to when loop is done |
| 1602 | 1620 | try func.startBlock(.block, wasm.block_empty); |
| ... | ... | @@ -1796,10 +1814,8 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 1796 | 1814 | .subwrap => func.airWrapBinOp(inst, .sub), |
| 1797 | 1815 | .mul => func.airBinOp(inst, .mul), |
| 1798 | 1816 | .mulwrap => func.airWrapBinOp(inst, .mul), |
| 1799 | | .div_float, |
| 1800 | | .div_exact, |
| 1801 | | .div_trunc, |
| 1802 | | => func.airDiv(inst), |
| 1817 | .div_float, .div_exact => func.airDiv(inst), |
| 1818 | .div_trunc => func.airDivTrunc(inst), |
| 1803 | 1819 | .div_floor => func.airDivFloor(inst), |
| 1804 | 1820 | .bit_and => func.airBinOp(inst, .@"and"), |
| 1805 | 1821 | .bit_or => func.airBinOp(inst, .@"or"), |
| ... | ... | @@ -1963,11 +1979,11 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 1963 | 1979 | .tag_name => func.airTagName(inst), |
| 1964 | 1980 | |
| 1965 | 1981 | .error_set_has_value => func.airErrorSetHasValue(inst), |
| 1982 | .frame_addr => func.airFrameAddress(inst), |
| 1966 | 1983 | |
| 1967 | 1984 | .mul_sat, |
| 1968 | 1985 | .mod, |
| 1969 | 1986 | .assembly, |
| 1970 | | .frame_addr, |
| 1971 | 1987 | .bit_reverse, |
| 1972 | 1988 | .is_err_ptr, |
| 1973 | 1989 | .is_non_err_ptr, |
| ... | ... | @@ -2110,16 +2126,13 @@ fn airRetLoad(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 2110 | 2126 | const un_op = func.air.instructions.items(.data)[inst].un_op; |
| 2111 | 2127 | const operand = try func.resolveInst(un_op); |
| 2112 | 2128 | const ret_ty = func.air.typeOf(un_op).childType(); |
| 2129 | |
| 2130 | const fn_info = func.decl.ty.fnInfo(); |
| 2113 | 2131 | if (!ret_ty.hasRuntimeBitsIgnoreComptime()) { |
| 2114 | 2132 | if (ret_ty.isError()) { |
| 2115 | 2133 | try func.addImm32(0); |
| 2116 | | } else { |
| 2117 | | return func.finishAir(inst, .none, &.{}); |
| 2118 | 2134 | } |
| 2119 | | } |
| 2120 | | |
| 2121 | | const fn_info = func.decl.ty.fnInfo(); |
| 2122 | | if (!firstParamSRet(fn_info.cc, fn_info.return_type, func.target)) { |
| 2135 | } else if (!firstParamSRet(fn_info.cc, fn_info.return_type, func.target)) { |
| 2123 | 2136 | // leave on the stack |
| 2124 | 2137 | _ = try func.load(operand, ret_ty, 0); |
| 2125 | 2138 | } |
| ... | ... | @@ -2523,10 +2536,34 @@ fn airBinOp(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerError!void { |
| 2523 | 2536 | const bin_op = func.air.instructions.items(.data)[inst].bin_op; |
| 2524 | 2537 | const lhs = try func.resolveInst(bin_op.lhs); |
| 2525 | 2538 | const rhs = try func.resolveInst(bin_op.rhs); |
| 2526 | | const ty = func.air.typeOf(bin_op.lhs); |
| 2539 | const lhs_ty = func.air.typeOf(bin_op.lhs); |
| 2540 | const rhs_ty = func.air.typeOf(bin_op.rhs); |
| 2541 | |
| 2542 | // For certain operations, such as shifting, the types are different. |
| 2543 | // When converting this to a WebAssembly type, they *must* match to perform |
| 2544 | // an operation. For this reason we verify if the WebAssembly type is different, in which |
| 2545 | // case we first coerce the operands to the same type before performing the operation. |
| 2546 | // For big integers we can ignore this as we will call into compiler-rt which handles this. |
| 2547 | const result = switch (op) { |
| 2548 | .shr, .shl => res: { |
| 2549 | const lhs_wasm_bits = toWasmBits(@intCast(u16, lhs_ty.bitSize(func.target))) orelse { |
| 2550 | return func.fail("TODO: implement '{s}' for types larger than 128 bits", .{@tagName(op)}); |
| 2551 | }; |
| 2552 | const rhs_wasm_bits = toWasmBits(@intCast(u16, rhs_ty.bitSize(func.target))).?; |
| 2553 | const new_rhs = if (lhs_wasm_bits != rhs_wasm_bits and lhs_wasm_bits != 128) blk: { |
| 2554 | const tmp = try func.intcast(rhs, rhs_ty, lhs_ty); |
| 2555 | break :blk try tmp.toLocal(func, lhs_ty); |
| 2556 | } else rhs; |
| 2557 | const stack_result = try func.binOp(lhs, new_rhs, lhs_ty, op); |
| 2558 | break :res try stack_result.toLocal(func, lhs_ty); |
| 2559 | }, |
| 2560 | else => res: { |
| 2561 | const stack_result = try func.binOp(lhs, rhs, lhs_ty, op); |
| 2562 | break :res try stack_result.toLocal(func, lhs_ty); |
| 2563 | }, |
| 2564 | }; |
| 2527 | 2565 | |
| 2528 | | const stack_value = try func.binOp(lhs, rhs, ty, op); |
| 2529 | | func.finishAir(inst, try stack_value.toLocal(func, ty), &.{ bin_op.lhs, bin_op.rhs }); |
| 2566 | func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); |
| 2530 | 2567 | } |
| 2531 | 2568 | |
| 2532 | 2569 | /// Performs a binary operation on the given `WValue`'s |
| ... | ... | @@ -2565,37 +2602,56 @@ fn binOp(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError! |
| 2565 | 2602 | |
| 2566 | 2603 | fn binOpBigInt(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError!WValue { |
| 2567 | 2604 | if (ty.intInfo(func.target).bits > 128) { |
| 2568 | | return func.fail("TODO: Implement binary operation for big integer", .{}); |
| 2605 | return func.fail("TODO: Implement binary operation for big integers larger than 128 bits", .{}); |
| 2569 | 2606 | } |
| 2570 | 2607 | |
| 2571 | | if (op != .add and op != .sub) { |
| 2572 | | return func.fail("TODO: Implement binary operation for big integers", .{}); |
| 2573 | | } |
| 2574 | | |
| 2575 | | const result = try func.allocStack(ty); |
| 2576 | | var lhs_high_bit = try (try func.load(lhs, Type.u64, 0)).toLocal(func, Type.u64); |
| 2577 | | defer lhs_high_bit.free(func); |
| 2578 | | var rhs_high_bit = try (try func.load(rhs, Type.u64, 0)).toLocal(func, Type.u64); |
| 2579 | | defer rhs_high_bit.free(func); |
| 2580 | | var high_op_res = try (try func.binOp(lhs_high_bit, rhs_high_bit, Type.u64, op)).toLocal(func, Type.u64); |
| 2581 | | defer high_op_res.free(func); |
| 2582 | | |
| 2583 | | const lhs_low_bit = try func.load(lhs, Type.u64, 8); |
| 2584 | | const rhs_low_bit = try func.load(rhs, Type.u64, 8); |
| 2585 | | const low_op_res = try func.binOp(lhs_low_bit, rhs_low_bit, Type.u64, op); |
| 2608 | switch (op) { |
| 2609 | .mul => return func.callIntrinsic("__multi3", &.{ ty, ty }, ty, &.{ lhs, rhs }), |
| 2610 | .shr => return func.callIntrinsic("__lshrti3", &.{ ty, Type.i32 }, ty, &.{ lhs, rhs }), |
| 2611 | .shl => return func.callIntrinsic("__ashlti3", &.{ ty, Type.i32 }, ty, &.{ lhs, rhs }), |
| 2612 | .xor => { |
| 2613 | const result = try func.allocStack(ty); |
| 2614 | try func.emitWValue(result); |
| 2615 | const lhs_high_bit = try func.load(lhs, Type.u64, 0); |
| 2616 | const rhs_high_bit = try func.load(rhs, Type.u64, 0); |
| 2617 | const xor_high_bit = try func.binOp(lhs_high_bit, rhs_high_bit, Type.u64, .xor); |
| 2618 | try func.store(.stack, xor_high_bit, Type.u64, result.offset()); |
| 2586 | 2619 | |
| 2587 | | const lt = if (op == .add) blk: { |
| 2588 | | break :blk try func.cmp(high_op_res, rhs_high_bit, Type.u64, .lt); |
| 2589 | | } else if (op == .sub) blk: { |
| 2590 | | break :blk try func.cmp(lhs_high_bit, rhs_high_bit, Type.u64, .lt); |
| 2591 | | } else unreachable; |
| 2592 | | const tmp = try func.intcast(lt, Type.u32, Type.u64); |
| 2593 | | var tmp_op = try (try func.binOp(low_op_res, tmp, Type.u64, op)).toLocal(func, Type.u64); |
| 2594 | | defer tmp_op.free(func); |
| 2620 | try func.emitWValue(result); |
| 2621 | const lhs_low_bit = try func.load(lhs, Type.u64, 8); |
| 2622 | const rhs_low_bit = try func.load(rhs, Type.u64, 8); |
| 2623 | const xor_low_bit = try func.binOp(lhs_low_bit, rhs_low_bit, Type.u64, .xor); |
| 2624 | try func.store(.stack, xor_low_bit, Type.u64, result.offset() + 8); |
| 2625 | return result; |
| 2626 | }, |
| 2627 | .add, .sub => { |
| 2628 | const result = try func.allocStack(ty); |
| 2629 | var lhs_high_bit = try (try func.load(lhs, Type.u64, 0)).toLocal(func, Type.u64); |
| 2630 | defer lhs_high_bit.free(func); |
| 2631 | var rhs_high_bit = try (try func.load(rhs, Type.u64, 0)).toLocal(func, Type.u64); |
| 2632 | defer rhs_high_bit.free(func); |
| 2633 | var high_op_res = try (try func.binOp(lhs_high_bit, rhs_high_bit, Type.u64, op)).toLocal(func, Type.u64); |
| 2634 | defer high_op_res.free(func); |
| 2595 | 2635 | |
| 2596 | | try func.store(result, high_op_res, Type.u64, 0); |
| 2597 | | try func.store(result, tmp_op, Type.u64, 8); |
| 2598 | | return result; |
| 2636 | const lhs_low_bit = try func.load(lhs, Type.u64, 8); |
| 2637 | const rhs_low_bit = try func.load(rhs, Type.u64, 8); |
| 2638 | const low_op_res = try func.binOp(lhs_low_bit, rhs_low_bit, Type.u64, op); |
| 2639 | |
| 2640 | const lt = if (op == .add) blk: { |
| 2641 | break :blk try func.cmp(high_op_res, rhs_high_bit, Type.u64, .lt); |
| 2642 | } else if (op == .sub) blk: { |
| 2643 | break :blk try func.cmp(lhs_high_bit, rhs_high_bit, Type.u64, .lt); |
| 2644 | } else unreachable; |
| 2645 | const tmp = try func.intcast(lt, Type.u32, Type.u64); |
| 2646 | var tmp_op = try (try func.binOp(low_op_res, tmp, Type.u64, op)).toLocal(func, Type.u64); |
| 2647 | defer tmp_op.free(func); |
| 2648 | |
| 2649 | try func.store(result, high_op_res, Type.u64, 0); |
| 2650 | try func.store(result, tmp_op, Type.u64, 8); |
| 2651 | return result; |
| 2652 | }, |
| 2653 | else => return func.fail("TODO: Implement binary operation for big integers: '{s}'", .{@tagName(op)}), |
| 2654 | } |
| 2599 | 2655 | } |
| 2600 | 2656 | |
| 2601 | 2657 | const FloatOp = enum { |
| ... | ... | @@ -2751,14 +2807,38 @@ fn airWrapBinOp(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerError!void { |
| 2751 | 2807 | |
| 2752 | 2808 | const lhs = try func.resolveInst(bin_op.lhs); |
| 2753 | 2809 | const rhs = try func.resolveInst(bin_op.rhs); |
| 2754 | | const ty = func.air.typeOf(bin_op.lhs); |
| 2810 | const lhs_ty = func.air.typeOf(bin_op.lhs); |
| 2811 | const rhs_ty = func.air.typeOf(bin_op.rhs); |
| 2755 | 2812 | |
| 2756 | | if (ty.zigTypeTag() == .Vector) { |
| 2813 | if (lhs_ty.zigTypeTag() == .Vector or rhs_ty.zigTypeTag() == .Vector) { |
| 2757 | 2814 | return func.fail("TODO: Implement wrapping arithmetic for vectors", .{}); |
| 2758 | 2815 | } |
| 2759 | 2816 | |
| 2760 | | const result = try (try func.wrapBinOp(lhs, rhs, ty, op)).toLocal(func, ty); |
| 2761 | | func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); |
| 2817 | // For certain operations, such as shifting, the types are different. |
| 2818 | // When converting this to a WebAssembly type, they *must* match to perform |
| 2819 | // an operation. For this reason we verify if the WebAssembly type is different, in which |
| 2820 | // case we first coerce the operands to the same type before performing the operation. |
| 2821 | // For big integers we can ignore this as we will call into compiler-rt which handles this. |
| 2822 | const result = switch (op) { |
| 2823 | .shr, .shl => res: { |
| 2824 | const lhs_wasm_bits = toWasmBits(@intCast(u16, lhs_ty.bitSize(func.target))) orelse { |
| 2825 | return func.fail("TODO: implement '{s}' for types larger than 128 bits", .{@tagName(op)}); |
| 2826 | }; |
| 2827 | const rhs_wasm_bits = toWasmBits(@intCast(u16, rhs_ty.bitSize(func.target))).?; |
| 2828 | const new_rhs = if (lhs_wasm_bits != rhs_wasm_bits and lhs_wasm_bits != 128) blk: { |
| 2829 | const tmp = try func.intcast(rhs, rhs_ty, lhs_ty); |
| 2830 | break :blk try tmp.toLocal(func, lhs_ty); |
| 2831 | } else rhs; |
| 2832 | const stack_result = try func.wrapBinOp(lhs, new_rhs, lhs_ty, op); |
| 2833 | break :res try stack_result.toLocal(func, lhs_ty); |
| 2834 | }, |
| 2835 | else => res: { |
| 2836 | const stack_result = try func.wrapBinOp(lhs, rhs, lhs_ty, op); |
| 2837 | break :res try stack_result.toLocal(func, lhs_ty); |
| 2838 | }, |
| 2839 | }; |
| 2840 | |
| 2841 | return func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); |
| 2762 | 2842 | } |
| 2763 | 2843 | |
| 2764 | 2844 | /// Performs a wrapping binary operation. |
| ... | ... | @@ -2810,26 +2890,25 @@ fn wrapOperand(func: *CodeGen, operand: WValue, ty: Type) InnerError!WValue { |
| 2810 | 2890 | return WValue{ .stack = {} }; |
| 2811 | 2891 | } |
| 2812 | 2892 | |
| 2813 | | fn lowerParentPtr(func: *CodeGen, ptr_val: Value, ptr_child_ty: Type) InnerError!WValue { |
| 2893 | fn lowerParentPtr(func: *CodeGen, ptr_val: Value, offset: u32) InnerError!WValue { |
| 2814 | 2894 | switch (ptr_val.tag()) { |
| 2815 | 2895 | .decl_ref_mut => { |
| 2816 | 2896 | const decl_index = ptr_val.castTag(.decl_ref_mut).?.data.decl_index; |
| 2817 | | return func.lowerParentPtrDecl(ptr_val, decl_index); |
| 2897 | return func.lowerParentPtrDecl(ptr_val, decl_index, offset); |
| 2818 | 2898 | }, |
| 2819 | 2899 | .decl_ref => { |
| 2820 | 2900 | const decl_index = ptr_val.castTag(.decl_ref).?.data; |
| 2821 | | return func.lowerParentPtrDecl(ptr_val, decl_index); |
| 2901 | return func.lowerParentPtrDecl(ptr_val, decl_index, offset); |
| 2822 | 2902 | }, |
| 2823 | 2903 | .variable => { |
| 2824 | 2904 | const decl_index = ptr_val.castTag(.variable).?.data.owner_decl; |
| 2825 | | return func.lowerParentPtrDecl(ptr_val, decl_index); |
| 2905 | return func.lowerParentPtrDecl(ptr_val, decl_index, offset); |
| 2826 | 2906 | }, |
| 2827 | 2907 | .field_ptr => { |
| 2828 | 2908 | const field_ptr = ptr_val.castTag(.field_ptr).?.data; |
| 2829 | 2909 | const parent_ty = field_ptr.container_ty; |
| 2830 | | const parent_ptr = try func.lowerParentPtr(field_ptr.container_ptr, parent_ty); |
| 2831 | 2910 | |
| 2832 | | const offset = switch (parent_ty.zigTypeTag()) { |
| 2911 | const field_offset = switch (parent_ty.zigTypeTag()) { |
| 2833 | 2912 | .Struct => switch (parent_ty.containerLayout()) { |
| 2834 | 2913 | .Packed => parent_ty.packedStructFieldByteOffset(field_ptr.field_index, func.target), |
| 2835 | 2914 | else => parent_ty.structFieldOffset(field_ptr.field_index, func.target), |
| ... | ... | @@ -2842,8 +2921,8 @@ fn lowerParentPtr(func: *CodeGen, ptr_val: Value, ptr_child_ty: Type) InnerError |
| 2842 | 2921 | if (layout.payload_align > layout.tag_align) break :blk 0; |
| 2843 | 2922 | |
| 2844 | 2923 | // tag is stored first so calculate offset from where payload starts |
| 2845 | | const offset = @intCast(u32, std.mem.alignForwardGeneric(u64, layout.tag_size, layout.tag_align)); |
| 2846 | | break :blk offset; |
| 2924 | const field_offset = @intCast(u32, std.mem.alignForwardGeneric(u64, layout.tag_size, layout.tag_align)); |
| 2925 | break :blk field_offset; |
| 2847 | 2926 | }, |
| 2848 | 2927 | }, |
| 2849 | 2928 | .Pointer => switch (parent_ty.ptrSize()) { |
| ... | ... | @@ -2856,43 +2935,23 @@ fn lowerParentPtr(func: *CodeGen, ptr_val: Value, ptr_child_ty: Type) InnerError |
| 2856 | 2935 | }, |
| 2857 | 2936 | else => unreachable, |
| 2858 | 2937 | }; |
| 2859 | | |
| 2860 | | return switch (parent_ptr) { |
| 2861 | | .memory => |ptr| WValue{ |
| 2862 | | .memory_offset = .{ |
| 2863 | | .pointer = ptr, |
| 2864 | | .offset = @intCast(u32, offset), |
| 2865 | | }, |
| 2866 | | }, |
| 2867 | | .memory_offset => |mem_off| WValue{ |
| 2868 | | .memory_offset = .{ |
| 2869 | | .pointer = mem_off.pointer, |
| 2870 | | .offset = @intCast(u32, offset) + mem_off.offset, |
| 2871 | | }, |
| 2872 | | }, |
| 2873 | | else => unreachable, |
| 2874 | | }; |
| 2938 | return func.lowerParentPtr(field_ptr.container_ptr, offset + @intCast(u32, field_offset)); |
| 2875 | 2939 | }, |
| 2876 | 2940 | .elem_ptr => { |
| 2877 | 2941 | const elem_ptr = ptr_val.castTag(.elem_ptr).?.data; |
| 2878 | 2942 | const index = elem_ptr.index; |
| 2879 | | const offset = index * ptr_child_ty.abiSize(func.target); |
| 2880 | | const array_ptr = try func.lowerParentPtr(elem_ptr.array_ptr, elem_ptr.elem_ty); |
| 2881 | | |
| 2882 | | return WValue{ .memory_offset = .{ |
| 2883 | | .pointer = array_ptr.memory, |
| 2884 | | .offset = @intCast(u32, offset), |
| 2885 | | } }; |
| 2943 | const elem_offset = index * elem_ptr.elem_ty.abiSize(func.target); |
| 2944 | return func.lowerParentPtr(elem_ptr.array_ptr, offset + @intCast(u32, elem_offset)); |
| 2886 | 2945 | }, |
| 2887 | 2946 | .opt_payload_ptr => { |
| 2888 | 2947 | const payload_ptr = ptr_val.castTag(.opt_payload_ptr).?.data; |
| 2889 | | return func.lowerParentPtr(payload_ptr.container_ptr, payload_ptr.container_ty); |
| 2948 | return func.lowerParentPtr(payload_ptr.container_ptr, offset); |
| 2890 | 2949 | }, |
| 2891 | 2950 | else => |tag| return func.fail("TODO: Implement lowerParentPtr for tag: {}", .{tag}), |
| 2892 | 2951 | } |
| 2893 | 2952 | } |
| 2894 | 2953 | |
| 2895 | | fn lowerParentPtrDecl(func: *CodeGen, ptr_val: Value, decl_index: Module.Decl.Index) InnerError!WValue { |
| 2954 | fn lowerParentPtrDecl(func: *CodeGen, ptr_val: Value, decl_index: Module.Decl.Index, offset: u32) InnerError!WValue { |
| 2896 | 2955 | const module = func.bin_file.base.options.module.?; |
| 2897 | 2956 | const decl = module.declPtr(decl_index); |
| 2898 | 2957 | module.markDeclAlive(decl); |
| ... | ... | @@ -2901,10 +2960,10 @@ fn lowerParentPtrDecl(func: *CodeGen, ptr_val: Value, decl_index: Module.Decl.In |
| 2901 | 2960 | .data = decl.ty, |
| 2902 | 2961 | }; |
| 2903 | 2962 | const ptr_ty = Type.initPayload(&ptr_ty_payload.base); |
| 2904 | | return func.lowerDeclRefValue(.{ .ty = ptr_ty, .val = ptr_val }, decl_index); |
| 2963 | return func.lowerDeclRefValue(.{ .ty = ptr_ty, .val = ptr_val }, decl_index, offset); |
| 2905 | 2964 | } |
| 2906 | 2965 | |
| 2907 | | fn lowerDeclRefValue(func: *CodeGen, tv: TypedValue, decl_index: Module.Decl.Index) InnerError!WValue { |
| 2966 | fn lowerDeclRefValue(func: *CodeGen, tv: TypedValue, decl_index: Module.Decl.Index, offset: u32) InnerError!WValue { |
| 2908 | 2967 | if (tv.ty.isSlice()) { |
| 2909 | 2968 | return WValue{ .memory = try func.bin_file.lowerUnnamedConst(tv, decl_index) }; |
| 2910 | 2969 | } |
| ... | ... | @@ -2923,7 +2982,9 @@ fn lowerDeclRefValue(func: *CodeGen, tv: TypedValue, decl_index: Module.Decl.Ind |
| 2923 | 2982 | if (decl.ty.zigTypeTag() == .Fn) { |
| 2924 | 2983 | try func.bin_file.addTableFunction(target_sym_index); |
| 2925 | 2984 | return WValue{ .function_index = target_sym_index }; |
| 2926 | | } else return WValue{ .memory = target_sym_index }; |
| 2985 | } else if (offset == 0) { |
| 2986 | return WValue{ .memory = target_sym_index }; |
| 2987 | } else return WValue{ .memory_offset = .{ .pointer = target_sym_index, .offset = offset } }; |
| 2927 | 2988 | } |
| 2928 | 2989 | |
| 2929 | 2990 | /// Converts a signed integer to its 2's complement form and returns |
| ... | ... | @@ -2950,11 +3011,11 @@ fn lowerConstant(func: *CodeGen, arg_val: Value, ty: Type) InnerError!WValue { |
| 2950 | 3011 | if (val.isUndefDeep()) return func.emitUndefined(ty); |
| 2951 | 3012 | if (val.castTag(.decl_ref)) |decl_ref| { |
| 2952 | 3013 | const decl_index = decl_ref.data; |
| 2953 | | return func.lowerDeclRefValue(.{ .ty = ty, .val = val }, decl_index); |
| 3014 | return func.lowerDeclRefValue(.{ .ty = ty, .val = val }, decl_index, 0); |
| 2954 | 3015 | } |
| 2955 | 3016 | if (val.castTag(.decl_ref_mut)) |decl_ref_mut| { |
| 2956 | 3017 | const decl_index = decl_ref_mut.data.decl_index; |
| 2957 | | return func.lowerDeclRefValue(.{ .ty = ty, .val = val }, decl_index); |
| 3018 | return func.lowerDeclRefValue(.{ .ty = ty, .val = val }, decl_index, 0); |
| 2958 | 3019 | } |
| 2959 | 3020 | const target = func.target; |
| 2960 | 3021 | switch (ty.zigTypeTag()) { |
| ... | ... | @@ -2988,9 +3049,7 @@ fn lowerConstant(func: *CodeGen, arg_val: Value, ty: Type) InnerError!WValue { |
| 2988 | 3049 | else => unreachable, |
| 2989 | 3050 | }, |
| 2990 | 3051 | .Pointer => switch (val.tag()) { |
| 2991 | | .field_ptr, .elem_ptr, .opt_payload_ptr => { |
| 2992 | | return func.lowerParentPtr(val, ty.childType()); |
| 2993 | | }, |
| 3052 | .field_ptr, .elem_ptr, .opt_payload_ptr => return func.lowerParentPtr(val, 0), |
| 2994 | 3053 | .int_u64, .one => return WValue{ .imm32 = @intCast(u32, val.toUnsignedInt(target)) }, |
| 2995 | 3054 | .zero, .null_value => return WValue{ .imm32 = 0 }, |
| 2996 | 3055 | else => return func.fail("Wasm TODO: lowerConstant for other const pointer tag {}", .{val.tag()}), |
| ... | ... | @@ -3182,9 +3241,13 @@ fn airBlock(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3182 | 3241 | .label = func.block_depth, |
| 3183 | 3242 | .value = block_result, |
| 3184 | 3243 | }); |
| 3244 | |
| 3185 | 3245 | try func.genBody(body); |
| 3186 | 3246 | try func.endBlock(); |
| 3187 | 3247 | |
| 3248 | const liveness = func.liveness.getBlock(inst); |
| 3249 | try func.currentBranch().values.ensureUnusedCapacity(func.gpa, liveness.deaths.len); |
| 3250 | |
| 3188 | 3251 | func.finishAir(inst, block_result, &.{}); |
| 3189 | 3252 | } |
| 3190 | 3253 | |
| ... | ... | @@ -3239,41 +3302,31 @@ fn airCondBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3239 | 3302 | try func.addLabel(.br_if, 0); |
| 3240 | 3303 | |
| 3241 | 3304 | try func.branches.ensureUnusedCapacity(func.gpa, 2); |
| 3242 | | |
| 3243 | | func.branches.appendAssumeCapacity(.{}); |
| 3244 | | try func.currentBranch().values.ensureUnusedCapacity(func.gpa, @intCast(u32, liveness_condbr.else_deaths.len)); |
| 3245 | | try func.genBody(else_body); |
| 3246 | | try func.endBlock(); |
| 3247 | | var else_stack = func.branches.pop(); |
| 3248 | | defer else_stack.deinit(func.gpa); |
| 3305 | { |
| 3306 | func.branches.appendAssumeCapacity(.{}); |
| 3307 | try func.currentBranch().values.ensureUnusedCapacity(func.gpa, @intCast(u32, liveness_condbr.else_deaths.len)); |
| 3308 | defer { |
| 3309 | var else_stack = func.branches.pop(); |
| 3310 | else_stack.deinit(func.gpa); |
| 3311 | } |
| 3312 | try func.genBody(else_body); |
| 3313 | try func.endBlock(); |
| 3314 | } |
| 3249 | 3315 | |
| 3250 | 3316 | // Outer block that matches the condition |
| 3251 | | func.branches.appendAssumeCapacity(.{}); |
| 3252 | | try func.currentBranch().values.ensureUnusedCapacity(func.gpa, @intCast(u32, liveness_condbr.then_deaths.len)); |
| 3253 | | try func.genBody(then_body); |
| 3254 | | var then_stack = func.branches.pop(); |
| 3255 | | defer then_stack.deinit(func.gpa); |
| 3256 | | |
| 3257 | | try func.mergeBranch(&else_stack); |
| 3258 | | try func.mergeBranch(&then_stack); |
| 3317 | { |
| 3318 | func.branches.appendAssumeCapacity(.{}); |
| 3319 | try func.currentBranch().values.ensureUnusedCapacity(func.gpa, @intCast(u32, liveness_condbr.then_deaths.len)); |
| 3320 | defer { |
| 3321 | var then_stack = func.branches.pop(); |
| 3322 | then_stack.deinit(func.gpa); |
| 3323 | } |
| 3324 | try func.genBody(then_body); |
| 3325 | } |
| 3259 | 3326 | |
| 3260 | 3327 | func.finishAir(inst, .none, &.{}); |
| 3261 | 3328 | } |
| 3262 | 3329 | |
| 3263 | | fn mergeBranch(func: *CodeGen, branch: *const Branch) !void { |
| 3264 | | const parent = func.currentBranch(); |
| 3265 | | |
| 3266 | | const target_slice = branch.values.entries.slice(); |
| 3267 | | const target_keys = target_slice.items(.key); |
| 3268 | | const target_values = target_slice.items(.value); |
| 3269 | | |
| 3270 | | try parent.values.ensureTotalCapacity(func.gpa, parent.values.capacity() + branch.values.count()); |
| 3271 | | for (target_keys, 0..) |key, index| { |
| 3272 | | // TODO: process deaths from branches |
| 3273 | | parent.values.putAssumeCapacity(key, target_values[index]); |
| 3274 | | } |
| 3275 | | } |
| 3276 | | |
| 3277 | 3330 | fn airCmp(func: *CodeGen, inst: Air.Inst.Index, op: std.math.CompareOperator) InnerError!void { |
| 3278 | 3331 | const bin_op = func.air.instructions.items(.data)[inst].bin_op; |
| 3279 | 3332 | |
| ... | ... | @@ -3783,30 +3836,25 @@ fn airSwitchBr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3783 | 3836 | } |
| 3784 | 3837 | } |
| 3785 | 3838 | func.branches.appendAssumeCapacity(.{}); |
| 3786 | | |
| 3787 | 3839 | try func.currentBranch().values.ensureUnusedCapacity(func.gpa, liveness.deaths[index].len); |
| 3788 | | for (liveness.deaths[index]) |operand| { |
| 3789 | | func.processDeath(Air.indexToRef(operand)); |
| 3840 | defer { |
| 3841 | var case_branch = func.branches.pop(); |
| 3842 | case_branch.deinit(func.gpa); |
| 3790 | 3843 | } |
| 3791 | 3844 | try func.genBody(case.body); |
| 3792 | 3845 | try func.endBlock(); |
| 3793 | | var case_branch = func.branches.pop(); |
| 3794 | | defer case_branch.deinit(func.gpa); |
| 3795 | | try func.mergeBranch(&case_branch); |
| 3796 | 3846 | } |
| 3797 | 3847 | |
| 3798 | 3848 | if (has_else_body) { |
| 3799 | 3849 | func.branches.appendAssumeCapacity(.{}); |
| 3800 | 3850 | const else_deaths = liveness.deaths.len - 1; |
| 3801 | 3851 | try func.currentBranch().values.ensureUnusedCapacity(func.gpa, liveness.deaths[else_deaths].len); |
| 3802 | | for (liveness.deaths[else_deaths]) |operand| { |
| 3803 | | func.processDeath(Air.indexToRef(operand)); |
| 3852 | defer { |
| 3853 | var else_branch = func.branches.pop(); |
| 3854 | else_branch.deinit(func.gpa); |
| 3804 | 3855 | } |
| 3805 | 3856 | try func.genBody(else_body); |
| 3806 | 3857 | try func.endBlock(); |
| 3807 | | var else_branch = func.branches.pop(); |
| 3808 | | defer else_branch.deinit(func.gpa); |
| 3809 | | try func.mergeBranch(&else_branch); |
| 3810 | 3858 | } |
| 3811 | 3859 | func.finishAir(inst, .none, &.{}); |
| 3812 | 3860 | } |
| ... | ... | @@ -3935,7 +3983,7 @@ fn airWrapErrUnionErr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3935 | 3983 | // write 'undefined' to the payload |
| 3936 | 3984 | const payload_ptr = try func.buildPointerOffset(err_union, @intCast(u32, errUnionPayloadOffset(pl_ty, func.target)), .new); |
| 3937 | 3985 | const len = @intCast(u32, err_ty.errorUnionPayload().abiSize(func.target)); |
| 3938 | | try func.memset(payload_ptr, .{ .imm32 = len }, .{ .imm32 = 0xaaaaaaaa }); |
| 3986 | try func.memset(Type.u8, payload_ptr, .{ .imm32 = len }, .{ .imm32 = 0xaa }); |
| 3939 | 3987 | |
| 3940 | 3988 | break :result err_union; |
| 3941 | 3989 | }; |
| ... | ... | @@ -3955,7 +4003,13 @@ fn airIntcast(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 3955 | 4003 | return func.fail("todo Wasm intcast for bitsize > 128", .{}); |
| 3956 | 4004 | } |
| 3957 | 4005 | |
| 3958 | | const result = try (try func.intcast(operand, operand_ty, ty)).toLocal(func, ty); |
| 4006 | const op_bits = toWasmBits(@intCast(u16, operand_ty.bitSize(func.target))).?; |
| 4007 | const wanted_bits = toWasmBits(@intCast(u16, ty.bitSize(func.target))).?; |
| 4008 | const result = if (op_bits == wanted_bits) |
| 4009 | func.reuseOperand(ty_op.operand, operand) |
| 4010 | else |
| 4011 | try (try func.intcast(operand, operand_ty, ty)).toLocal(func, ty); |
| 4012 | |
| 3959 | 4013 | func.finishAir(inst, result, &.{}); |
| 3960 | 4014 | } |
| 3961 | 4015 | |
| ... | ... | @@ -4423,7 +4477,14 @@ fn airMemset(func: *CodeGen, inst: Air.Inst.Index, safety: bool) InnerError!void |
| 4423 | 4477 | .One => @as(WValue, .{ .imm32 = @intCast(u32, ptr_ty.childType().arrayLen()) }), |
| 4424 | 4478 | .C, .Many => unreachable, |
| 4425 | 4479 | }; |
| 4426 | | try func.memset(ptr, len, value); |
| 4480 | |
| 4481 | const elem_ty = if (ptr_ty.ptrSize() == .One) |
| 4482 | ptr_ty.childType().childType() |
| 4483 | else |
| 4484 | ptr_ty.childType(); |
| 4485 | |
| 4486 | const dst_ptr = try func.sliceOrArrayPtr(ptr, ptr_ty); |
| 4487 | try func.memset(elem_ty, dst_ptr, len, value); |
| 4427 | 4488 | |
| 4428 | 4489 | func.finishAir(inst, .none, &.{ bin_op.lhs, bin_op.rhs }); |
| 4429 | 4490 | } |
| ... | ... | @@ -4432,10 +4493,12 @@ fn airMemset(func: *CodeGen, inst: Air.Inst.Index, safety: bool) InnerError!void |
| 4432 | 4493 | /// When the user has enabled the bulk_memory feature, we lower |
| 4433 | 4494 | /// this to wasm's memset instruction. When the feature is not present, |
| 4434 | 4495 | /// we implement it manually. |
| 4435 | | fn memset(func: *CodeGen, ptr: WValue, len: WValue, value: WValue) InnerError!void { |
| 4496 | fn memset(func: *CodeGen, elem_ty: Type, ptr: WValue, len: WValue, value: WValue) InnerError!void { |
| 4497 | const abi_size = @intCast(u32, elem_ty.abiSize(func.target)); |
| 4498 | |
| 4436 | 4499 | // When bulk_memory is enabled, we lower it to wasm's memset instruction. |
| 4437 | | // If not, we lower it ourselves |
| 4438 | | if (std.Target.wasm.featureSetHas(func.target.cpu.features, .bulk_memory)) { |
| 4500 | // If not, we lower it ourselves. |
| 4501 | if (std.Target.wasm.featureSetHas(func.target.cpu.features, .bulk_memory) and abi_size == 1) { |
| 4439 | 4502 | try func.lowerToStack(ptr); |
| 4440 | 4503 | try func.emitWValue(value); |
| 4441 | 4504 | try func.emitWValue(len); |
| ... | ... | @@ -4443,74 +4506,79 @@ fn memset(func: *CodeGen, ptr: WValue, len: WValue, value: WValue) InnerError!vo |
| 4443 | 4506 | return; |
| 4444 | 4507 | } |
| 4445 | 4508 | |
| 4446 | | // When the length is comptime-known we do the loop at codegen, rather |
| 4447 | | // than emitting a runtime loop into the binary |
| 4448 | | switch (len) { |
| 4449 | | .imm32, .imm64 => { |
| 4450 | | const length = switch (len) { |
| 4451 | | .imm32 => |val| val, |
| 4452 | | .imm64 => |val| val, |
| 4453 | | else => unreachable, |
| 4454 | | }; |
| 4455 | | |
| 4456 | | var offset: u32 = 0; |
| 4457 | | const base = ptr.offset(); |
| 4458 | | while (offset < length) : (offset += 1) { |
| 4459 | | try func.emitWValue(ptr); |
| 4460 | | try func.emitWValue(value); |
| 4461 | | switch (func.arch()) { |
| 4462 | | .wasm32 => { |
| 4463 | | try func.addMemArg(.i32_store8, .{ .offset = base + offset, .alignment = 1 }); |
| 4464 | | }, |
| 4465 | | .wasm64 => { |
| 4466 | | try func.addMemArg(.i64_store8, .{ .offset = base + offset, .alignment = 1 }); |
| 4467 | | }, |
| 4468 | | else => unreachable, |
| 4469 | | } |
| 4470 | | } |
| 4471 | | }, |
| 4472 | | else => { |
| 4473 | | // TODO: We should probably lower this to a call to compiler_rt |
| 4474 | | // But for now, we implement it manually |
| 4475 | | const offset = try func.ensureAllocLocal(Type.usize); // local for counter |
| 4476 | | // outer block to jump to when loop is done |
| 4477 | | try func.startBlock(.block, wasm.block_empty); |
| 4478 | | try func.startBlock(.loop, wasm.block_empty); |
| 4479 | | try func.emitWValue(offset); |
| 4509 | const final_len = switch (len) { |
| 4510 | .imm32 => |val| WValue{ .imm32 = val * abi_size }, |
| 4511 | .imm64 => |val| WValue{ .imm64 = val * abi_size }, |
| 4512 | else => if (abi_size != 1) blk: { |
| 4513 | const new_len = try func.ensureAllocLocal(Type.usize); |
| 4480 | 4514 | try func.emitWValue(len); |
| 4481 | 4515 | switch (func.arch()) { |
| 4482 | | .wasm32 => try func.addTag(.i32_eq), |
| 4483 | | .wasm64 => try func.addTag(.i64_eq), |
| 4484 | | else => unreachable, |
| 4485 | | } |
| 4486 | | try func.addLabel(.br_if, 1); // jump out of loop into outer block (finished) |
| 4487 | | try func.emitWValue(ptr); |
| 4488 | | try func.emitWValue(offset); |
| 4489 | | switch (func.arch()) { |
| 4490 | | .wasm32 => try func.addTag(.i32_add), |
| 4491 | | .wasm64 => try func.addTag(.i64_add), |
| 4492 | | else => unreachable, |
| 4493 | | } |
| 4494 | | try func.emitWValue(value); |
| 4495 | | const mem_store_op: Mir.Inst.Tag = switch (func.arch()) { |
| 4496 | | .wasm32 => .i32_store8, |
| 4497 | | .wasm64 => .i64_store8, |
| 4498 | | else => unreachable, |
| 4499 | | }; |
| 4500 | | try func.addMemArg(mem_store_op, .{ .offset = ptr.offset(), .alignment = 1 }); |
| 4501 | | try func.emitWValue(offset); |
| 4502 | | try func.addImm32(1); |
| 4503 | | switch (func.arch()) { |
| 4504 | | .wasm32 => try func.addTag(.i32_add), |
| 4505 | | .wasm64 => try func.addTag(.i64_add), |
| 4516 | .wasm32 => { |
| 4517 | try func.emitWValue(.{ .imm32 = abi_size }); |
| 4518 | try func.addTag(.i32_mul); |
| 4519 | }, |
| 4520 | .wasm64 => { |
| 4521 | try func.emitWValue(.{ .imm64 = abi_size }); |
| 4522 | try func.addTag(.i64_mul); |
| 4523 | }, |
| 4506 | 4524 | else => unreachable, |
| 4507 | 4525 | } |
| 4508 | | try func.addLabel(.local_set, offset.local.value); |
| 4509 | | try func.addLabel(.br, 0); // jump to start of loop |
| 4510 | | try func.endBlock(); |
| 4511 | | try func.endBlock(); |
| 4526 | try func.addLabel(.local_set, new_len.local.value); |
| 4527 | break :blk new_len; |
| 4528 | } else len, |
| 4529 | }; |
| 4530 | |
| 4531 | var end_ptr = try func.allocLocal(Type.usize); |
| 4532 | defer end_ptr.free(func); |
| 4533 | var new_ptr = try func.buildPointerOffset(ptr, 0, .new); |
| 4534 | defer new_ptr.free(func); |
| 4535 | |
| 4536 | // get the loop conditional: if current pointer address equals final pointer's address |
| 4537 | try func.lowerToStack(ptr); |
| 4538 | try func.emitWValue(final_len); |
| 4539 | switch (func.arch()) { |
| 4540 | .wasm32 => try func.addTag(.i32_add), |
| 4541 | .wasm64 => try func.addTag(.i64_add), |
| 4542 | else => unreachable, |
| 4543 | } |
| 4544 | try func.addLabel(.local_set, end_ptr.local.value); |
| 4545 | |
| 4546 | // outer block to jump to when loop is done |
| 4547 | try func.startBlock(.block, wasm.block_empty); |
| 4548 | try func.startBlock(.loop, wasm.block_empty); |
| 4549 | |
| 4550 | // check for codition for loop end |
| 4551 | try func.emitWValue(new_ptr); |
| 4552 | try func.emitWValue(end_ptr); |
| 4553 | switch (func.arch()) { |
| 4554 | .wasm32 => try func.addTag(.i32_eq), |
| 4555 | .wasm64 => try func.addTag(.i64_eq), |
| 4556 | else => unreachable, |
| 4557 | } |
| 4558 | try func.addLabel(.br_if, 1); // jump out of loop into outer block (finished) |
| 4559 | |
| 4560 | // store the value at the current position of the pointer |
| 4561 | try func.store(new_ptr, value, elem_ty, 0); |
| 4562 | |
| 4563 | // move the pointer to the next element |
| 4564 | try func.emitWValue(new_ptr); |
| 4565 | switch (func.arch()) { |
| 4566 | .wasm32 => { |
| 4567 | try func.emitWValue(.{ .imm32 = abi_size }); |
| 4568 | try func.addTag(.i32_add); |
| 4512 | 4569 | }, |
| 4570 | .wasm64 => { |
| 4571 | try func.emitWValue(.{ .imm64 = abi_size }); |
| 4572 | try func.addTag(.i64_add); |
| 4573 | }, |
| 4574 | else => unreachable, |
| 4513 | 4575 | } |
| 4576 | try func.addLabel(.local_set, new_ptr.local.value); |
| 4577 | |
| 4578 | // end of loop |
| 4579 | try func.addLabel(.br, 0); // jump to start of loop |
| 4580 | try func.endBlock(); |
| 4581 | try func.endBlock(); |
| 4514 | 4582 | } |
| 4515 | 4583 | |
| 4516 | 4584 | fn airArrayElemVal(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| ... | ... | @@ -4825,8 +4893,15 @@ fn airAggregateInit(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 4825 | 4893 | const struct_obj = result_ty.castTag(.@"struct").?.data; |
| 4826 | 4894 | const fields = struct_obj.fields.values(); |
| 4827 | 4895 | const backing_type = struct_obj.backing_int_ty; |
| 4828 | | // we ensure a new local is created so it's zero-initialized |
| 4829 | | const result = try func.ensureAllocLocal(backing_type); |
| 4896 | |
| 4897 | // ensure the result is zero'd |
| 4898 | const result = try func.allocLocal(backing_type); |
| 4899 | if (struct_obj.backing_int_ty.bitSize(func.target) <= 32) |
| 4900 | try func.addImm32(0) |
| 4901 | else |
| 4902 | try func.addImm64(0); |
| 4903 | try func.addLabel(.local_set, result.local.value); |
| 4904 | |
| 4830 | 4905 | var current_bit: u16 = 0; |
| 4831 | 4906 | for (elements, 0..) |elem, elem_index| { |
| 4832 | 4907 | const field = fields[elem_index]; |
| ... | ... | @@ -4884,8 +4959,15 @@ fn airAggregateInit(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 4884 | 4959 | else => unreachable, |
| 4885 | 4960 | } |
| 4886 | 4961 | }; |
| 4887 | | // TODO: this is incorrect Liveness handling code |
| 4888 | | func.finishAir(inst, result, &.{}); |
| 4962 | |
| 4963 | if (elements.len <= Liveness.bpi - 1) { |
| 4964 | var buf = [1]Air.Inst.Ref{.none} ** (Liveness.bpi - 1); |
| 4965 | @memcpy(buf[0..elements.len], elements); |
| 4966 | return func.finishAir(inst, result, &buf); |
| 4967 | } |
| 4968 | var bt = try func.iterateBigTomb(inst, elements.len); |
| 4969 | for (elements) |arg| bt.feed(arg); |
| 4970 | return bt.finishAir(result); |
| 4889 | 4971 | } |
| 4890 | 4972 | |
| 4891 | 4973 | fn airUnionInit(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| ... | ... | @@ -5212,7 +5294,7 @@ fn airMemcpy(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 5212 | 5294 | const src_ty = func.air.typeOf(bin_op.rhs); |
| 5213 | 5295 | const len = switch (dst_ty.ptrSize()) { |
| 5214 | 5296 | .Slice => try func.sliceLen(dst), |
| 5215 | | .One => @as(WValue, .{ .imm64 = dst_ty.childType().arrayLen() }), |
| 5297 | .One => @as(WValue, .{ .imm32 = @intCast(u32, dst_ty.childType().arrayLen()) }), |
| 5216 | 5298 | .C, .Many => unreachable, |
| 5217 | 5299 | }; |
| 5218 | 5300 | const dst_ptr = try func.sliceOrArrayPtr(dst, dst_ty); |
| ... | ... | @@ -5372,11 +5454,10 @@ fn airAddSubWithOverflow(func: *CodeGen, inst: Air.Inst.Index, op: Op) InnerErro |
| 5372 | 5454 | }; |
| 5373 | 5455 | |
| 5374 | 5456 | var bin_op = try (try func.binOp(lhs, rhs, lhs_ty, op)).toLocal(func, lhs_ty); |
| 5375 | | defer bin_op.free(func); |
| 5376 | 5457 | var result = if (wasm_bits != int_info.bits) blk: { |
| 5377 | 5458 | break :blk try (try func.wrapOperand(bin_op, lhs_ty)).toLocal(func, lhs_ty); |
| 5378 | 5459 | } else bin_op; |
| 5379 | | defer result.free(func); // no-op when wasm_bits == int_info.bits |
| 5460 | defer result.free(func); |
| 5380 | 5461 | |
| 5381 | 5462 | const cmp_op: std.math.CompareOperator = if (op == .sub) .gt else .lt; |
| 5382 | 5463 | const overflow_bit: WValue = if (is_signed) blk: { |
| ... | ... | @@ -5532,16 +5613,12 @@ fn airMulWithOverflow(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 5532 | 5613 | |
| 5533 | 5614 | const int_info = lhs_ty.intInfo(func.target); |
| 5534 | 5615 | const wasm_bits = toWasmBits(int_info.bits) orelse { |
| 5535 | | return func.fail("TODO: Implement overflow arithmetic for integer bitsize: {d}", .{int_info.bits}); |
| 5536 | | }; |
| 5537 | | |
| 5538 | | if (wasm_bits > 32) { |
| 5539 | 5616 | return func.fail("TODO: Implement `@mulWithOverflow` for integer bitsize: {d}", .{int_info.bits}); |
| 5540 | | } |
| 5617 | }; |
| 5541 | 5618 | |
| 5542 | 5619 | const zero = switch (wasm_bits) { |
| 5543 | 5620 | 32 => WValue{ .imm32 = 0 }, |
| 5544 | | 64 => WValue{ .imm64 = 0 }, |
| 5621 | 64, 128 => WValue{ .imm64 = 0 }, |
| 5545 | 5622 | else => unreachable, |
| 5546 | 5623 | }; |
| 5547 | 5624 | |
| ... | ... | @@ -5568,7 +5645,7 @@ fn airMulWithOverflow(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 5568 | 5645 | try func.addLabel(.local_set, overflow_bit.local.value); |
| 5569 | 5646 | break :blk down_cast; |
| 5570 | 5647 | } |
| 5571 | | } else if (int_info.signedness == .signed) blk: { |
| 5648 | } else if (int_info.signedness == .signed and wasm_bits == 32) blk: { |
| 5572 | 5649 | const lhs_abs = try func.signAbsValue(lhs, lhs_ty); |
| 5573 | 5650 | const rhs_abs = try func.signAbsValue(rhs, lhs_ty); |
| 5574 | 5651 | const bin_op = try (try func.binOp(lhs_abs, rhs_abs, lhs_ty, .mul)).toLocal(func, lhs_ty); |
| ... | ... | @@ -5576,7 +5653,7 @@ fn airMulWithOverflow(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 5576 | 5653 | _ = try func.cmp(mul_abs, bin_op, lhs_ty, .neq); |
| 5577 | 5654 | try func.addLabel(.local_set, overflow_bit.local.value); |
| 5578 | 5655 | break :blk try func.wrapOperand(bin_op, lhs_ty); |
| 5579 | | } else blk: { |
| 5656 | } else if (wasm_bits == 32) blk: { |
| 5580 | 5657 | var bin_op = try (try func.binOp(lhs, rhs, lhs_ty, .mul)).toLocal(func, lhs_ty); |
| 5581 | 5658 | defer bin_op.free(func); |
| 5582 | 5659 | const shift_imm = if (wasm_bits == 32) |
| ... | ... | @@ -5587,7 +5664,99 @@ fn airMulWithOverflow(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 5587 | 5664 | _ = try func.cmp(shr, zero, lhs_ty, .neq); |
| 5588 | 5665 | try func.addLabel(.local_set, overflow_bit.local.value); |
| 5589 | 5666 | break :blk try func.wrapOperand(bin_op, lhs_ty); |
| 5590 | | }; |
| 5667 | } else if (int_info.bits == 64 and int_info.signedness == .unsigned) blk: { |
| 5668 | const new_ty = Type.initTag(.u128); |
| 5669 | var lhs_upcast = try (try func.intcast(lhs, lhs_ty, new_ty)).toLocal(func, lhs_ty); |
| 5670 | defer lhs_upcast.free(func); |
| 5671 | var rhs_upcast = try (try func.intcast(rhs, lhs_ty, new_ty)).toLocal(func, lhs_ty); |
| 5672 | defer rhs_upcast.free(func); |
| 5673 | const bin_op = try func.binOp(lhs_upcast, rhs_upcast, new_ty, .mul); |
| 5674 | const lsb = try func.load(bin_op, lhs_ty, 8); |
| 5675 | _ = try func.cmp(lsb, zero, lhs_ty, .neq); |
| 5676 | try func.addLabel(.local_set, overflow_bit.local.value); |
| 5677 | |
| 5678 | break :blk try func.load(bin_op, lhs_ty, 0); |
| 5679 | } else if (int_info.bits == 64 and int_info.signedness == .signed) blk: { |
| 5680 | const shift_val: WValue = .{ .imm64 = 63 }; |
| 5681 | var lhs_shifted = try (try func.binOp(lhs, shift_val, lhs_ty, .shr)).toLocal(func, lhs_ty); |
| 5682 | defer lhs_shifted.free(func); |
| 5683 | var rhs_shifted = try (try func.binOp(rhs, shift_val, lhs_ty, .shr)).toLocal(func, lhs_ty); |
| 5684 | defer rhs_shifted.free(func); |
| 5685 | |
| 5686 | const bin_op = try func.callIntrinsic( |
| 5687 | "__multi3", |
| 5688 | &[_]Type{Type.i64} ** 4, |
| 5689 | Type.initTag(.i128), |
| 5690 | &.{ lhs, lhs_shifted, rhs, rhs_shifted }, |
| 5691 | ); |
| 5692 | const res = try func.allocLocal(lhs_ty); |
| 5693 | const msb = try func.load(bin_op, lhs_ty, 0); |
| 5694 | try func.addLabel(.local_tee, res.local.value); |
| 5695 | const msb_shifted = try func.binOp(msb, shift_val, lhs_ty, .shr); |
| 5696 | const lsb = try func.load(bin_op, lhs_ty, 8); |
| 5697 | _ = try func.cmp(lsb, msb_shifted, lhs_ty, .neq); |
| 5698 | try func.addLabel(.local_set, overflow_bit.local.value); |
| 5699 | break :blk res; |
| 5700 | } else if (int_info.bits == 128 and int_info.signedness == .unsigned) blk: { |
| 5701 | var lhs_msb = try (try func.load(lhs, Type.u64, 0)).toLocal(func, Type.u64); |
| 5702 | defer lhs_msb.free(func); |
| 5703 | var lhs_lsb = try (try func.load(lhs, Type.u64, 8)).toLocal(func, Type.u64); |
| 5704 | defer lhs_lsb.free(func); |
| 5705 | var rhs_msb = try (try func.load(rhs, Type.u64, 0)).toLocal(func, Type.u64); |
| 5706 | defer rhs_msb.free(func); |
| 5707 | var rhs_lsb = try (try func.load(rhs, Type.u64, 8)).toLocal(func, Type.u64); |
| 5708 | defer rhs_lsb.free(func); |
| 5709 | |
| 5710 | const mul1 = try func.callIntrinsic( |
| 5711 | "__multi3", |
| 5712 | &[_]Type{Type.i64} ** 4, |
| 5713 | Type.initTag(.i128), |
| 5714 | &.{ lhs_lsb, zero, rhs_msb, zero }, |
| 5715 | ); |
| 5716 | const mul2 = try func.callIntrinsic( |
| 5717 | "__multi3", |
| 5718 | &[_]Type{Type.i64} ** 4, |
| 5719 | Type.initTag(.i128), |
| 5720 | &.{ rhs_lsb, zero, lhs_msb, zero }, |
| 5721 | ); |
| 5722 | const mul3 = try func.callIntrinsic( |
| 5723 | "__multi3", |
| 5724 | &[_]Type{Type.i64} ** 4, |
| 5725 | Type.initTag(.i128), |
| 5726 | &.{ lhs_msb, zero, rhs_msb, zero }, |
| 5727 | ); |
| 5728 | |
| 5729 | const rhs_lsb_not_zero = try func.cmp(rhs_lsb, zero, Type.u64, .neq); |
| 5730 | const lhs_lsb_not_zero = try func.cmp(lhs_lsb, zero, Type.u64, .neq); |
| 5731 | const lsb_and = try func.binOp(rhs_lsb_not_zero, lhs_lsb_not_zero, Type.bool, .@"and"); |
| 5732 | const mul1_lsb = try func.load(mul1, Type.u64, 8); |
| 5733 | const mul1_lsb_not_zero = try func.cmp(mul1_lsb, zero, Type.u64, .neq); |
| 5734 | const lsb_or1 = try func.binOp(lsb_and, mul1_lsb_not_zero, Type.bool, .@"or"); |
| 5735 | const mul2_lsb = try func.load(mul2, Type.u64, 8); |
| 5736 | const mul2_lsb_not_zero = try func.cmp(mul2_lsb, zero, Type.u64, .neq); |
| 5737 | const lsb_or = try func.binOp(lsb_or1, mul2_lsb_not_zero, Type.bool, .@"or"); |
| 5738 | |
| 5739 | const mul1_msb = try func.load(mul1, Type.u64, 0); |
| 5740 | const mul2_msb = try func.load(mul2, Type.u64, 0); |
| 5741 | const mul_add1 = try func.binOp(mul1_msb, mul2_msb, Type.u64, .add); |
| 5742 | |
| 5743 | var mul3_lsb = try (try func.load(mul3, Type.u64, 8)).toLocal(func, Type.u64); |
| 5744 | defer mul3_lsb.free(func); |
| 5745 | var mul_add2 = try (try func.binOp(mul_add1, mul3_lsb, Type.u64, .add)).toLocal(func, Type.u64); |
| 5746 | defer mul_add2.free(func); |
| 5747 | const mul_add_lt = try func.cmp(mul_add2, mul3_lsb, Type.u64, .lt); |
| 5748 | |
| 5749 | // result for overflow bit |
| 5750 | _ = try func.binOp(lsb_or, mul_add_lt, Type.bool, .@"or"); |
| 5751 | try func.addLabel(.local_set, overflow_bit.local.value); |
| 5752 | |
| 5753 | const tmp_result = try func.allocStack(Type.initTag(.u128)); |
| 5754 | try func.emitWValue(tmp_result); |
| 5755 | const mul3_msb = try func.load(mul3, Type.u64, 0); |
| 5756 | try func.store(.stack, mul3_msb, Type.u64, tmp_result.offset()); |
| 5757 | try func.store(tmp_result, mul_add2, Type.u64, 8); |
| 5758 | break :blk tmp_result; |
| 5759 | } else return func.fail("TODO: @mulWithOverflow for integers between 32 and 64 bits", .{}); |
| 5591 | 5760 | var bin_op_local = try bin_op.toLocal(func, lhs_ty); |
| 5592 | 5761 | defer bin_op_local.free(func); |
| 5593 | 5762 | |
| ... | ... | @@ -5821,7 +5990,7 @@ fn airTry(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 5821 | 5990 | const extra = func.air.extraData(Air.Try, pl_op.payload); |
| 5822 | 5991 | const body = func.air.extra[extra.end..][0..extra.data.body_len]; |
| 5823 | 5992 | const err_union_ty = func.air.typeOf(pl_op.operand); |
| 5824 | | const result = try lowerTry(func, err_union, body, err_union_ty, false); |
| 5993 | const result = try lowerTry(func, inst, err_union, body, err_union_ty, false); |
| 5825 | 5994 | func.finishAir(inst, result, &.{pl_op.operand}); |
| 5826 | 5995 | } |
| 5827 | 5996 | |
| ... | ... | @@ -5831,12 +6000,13 @@ fn airTryPtr(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 5831 | 6000 | const err_union_ptr = try func.resolveInst(extra.data.ptr); |
| 5832 | 6001 | const body = func.air.extra[extra.end..][0..extra.data.body_len]; |
| 5833 | 6002 | const err_union_ty = func.air.typeOf(extra.data.ptr).childType(); |
| 5834 | | const result = try lowerTry(func, err_union_ptr, body, err_union_ty, true); |
| 6003 | const result = try lowerTry(func, inst, err_union_ptr, body, err_union_ty, true); |
| 5835 | 6004 | func.finishAir(inst, result, &.{extra.data.ptr}); |
| 5836 | 6005 | } |
| 5837 | 6006 | |
| 5838 | 6007 | fn lowerTry( |
| 5839 | 6008 | func: *CodeGen, |
| 6009 | inst: Air.Inst.Index, |
| 5840 | 6010 | err_union: WValue, |
| 5841 | 6011 | body: []const Air.Inst.Index, |
| 5842 | 6012 | err_union_ty: Type, |
| ... | ... | @@ -5864,6 +6034,14 @@ fn lowerTry( |
| 5864 | 6034 | } |
| 5865 | 6035 | try func.addTag(.i32_eqz); |
| 5866 | 6036 | try func.addLabel(.br_if, 0); // jump out of block when error is '0' |
| 6037 | |
| 6038 | const liveness = func.liveness.getCondBr(inst); |
| 6039 | try func.branches.append(func.gpa, .{}); |
| 6040 | try func.currentBranch().values.ensureUnusedCapacity(func.gpa, liveness.else_deaths.len + liveness.then_deaths.len); |
| 6041 | defer { |
| 6042 | var branch = func.branches.pop(); |
| 6043 | branch.deinit(func.gpa); |
| 6044 | } |
| 5867 | 6045 | try func.genBody(body); |
| 5868 | 6046 | try func.endBlock(); |
| 5869 | 6047 | } |
| ... | ... | @@ -5965,6 +6143,26 @@ fn airDiv(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 5965 | 6143 | func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); |
| 5966 | 6144 | } |
| 5967 | 6145 | |
| 6146 | fn airDivTrunc(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 6147 | const bin_op = func.air.instructions.items(.data)[inst].bin_op; |
| 6148 | |
| 6149 | const ty = func.air.typeOfIndex(inst); |
| 6150 | const lhs = try func.resolveInst(bin_op.lhs); |
| 6151 | const rhs = try func.resolveInst(bin_op.rhs); |
| 6152 | |
| 6153 | const div_result = if (ty.isSignedInt()) |
| 6154 | try func.divSigned(lhs, rhs, ty) |
| 6155 | else |
| 6156 | try (try func.binOp(lhs, rhs, ty, .div)).toLocal(func, ty); |
| 6157 | |
| 6158 | if (ty.isAnyFloat()) { |
| 6159 | const trunc_result = try (try func.floatOp(.trunc, ty, &.{div_result})).toLocal(func, ty); |
| 6160 | return func.finishAir(inst, trunc_result, &.{ bin_op.lhs, bin_op.rhs }); |
| 6161 | } |
| 6162 | |
| 6163 | return func.finishAir(inst, div_result, &.{ bin_op.lhs, bin_op.rhs }); |
| 6164 | } |
| 6165 | |
| 5968 | 6166 | fn airDivFloor(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 5969 | 6167 | const bin_op = func.air.instructions.items(.data)[inst].bin_op; |
| 5970 | 6168 | |
| ... | ... | @@ -6969,3 +7167,12 @@ fn airAtomicStore(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 6969 | 7167 | |
| 6970 | 7168 | return func.finishAir(inst, .none, &.{ bin_op.lhs, bin_op.rhs }); |
| 6971 | 7169 | } |
| 7170 | |
| 7171 | fn airFrameAddress(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { |
| 7172 | if (func.initial_stack_value == .none) { |
| 7173 | try func.initializeStack(); |
| 7174 | } |
| 7175 | try func.emitWValue(func.bottom_stack_value); |
| 7176 | const result = try WValue.toLocal(.stack, func, Type.usize); |
| 7177 | return func.finishAir(inst, result, &.{}); |
| 7178 | } |