| author | |
| committer | |
| log | f78f9388fe79f084d5ea028e6270a410eacfc316 |
| tree | 40b3eeb986f9fdc8866a9d41134b41801a5adc84 |
| parent | 15fe99957226aa27da838812ec891f11b299453d |
Resolves: #224744 files changed, 110 insertions(+), 74 deletions(-)
src/Sema.zig+7-1| ... | ... | @@ -7983,7 +7983,13 @@ fn analyzeCall( |
| 7983 | 7983 | } |
| 7984 | 7984 | |
| 7985 | 7985 | if (call_tag == .call_always_tail) { |
| 7986 | return sema.handleTailCall(block, call_src, sema.typeOf(runtime_func), result); | |
| 7986 | const func_or_ptr_ty = sema.typeOf(runtime_func); | |
| 7987 | const runtime_func_ty = switch (func_or_ptr_ty.zigTypeTag(zcu)) { | |
| 7988 | .@"fn" => func_or_ptr_ty, | |
| 7989 | .pointer => func_or_ptr_ty.childType(zcu), | |
| 7990 | else => unreachable, | |
| 7991 | }; | |
| 7992 | return sema.handleTailCall(block, call_src, runtime_func_ty, result); | |
| 7987 | 7993 | } |
| 7988 | 7994 | |
| 7989 | 7995 | if (resolved_ret_ty.toIntern() == .noreturn_type) { |
test/behavior.zig-1| ... | ... | @@ -17,7 +17,6 @@ test { |
| 17 | 17 | _ = @import("behavior/byteswap.zig"); |
| 18 | 18 | _ = @import("behavior/byval_arg_var.zig"); |
| 19 | 19 | _ = @import("behavior/call.zig"); |
| 20 | _ = @import("behavior/call_tail.zig"); | |
| 21 | 20 | _ = @import("behavior/cast.zig"); |
| 22 | 21 | _ = @import("behavior/cast_int.zig"); |
| 23 | 22 | _ = @import("behavior/comptime_memory.zig"); |
test/behavior/call.zig+103| ... | ... | @@ -651,3 +651,106 @@ test "function call with cast to anyopaque pointer" { |
| 651 | 651 | }; |
| 652 | 652 | Foo.bar(Foo.t); |
| 653 | 653 | } |
| 654 | ||
| 655 | test "arguments pointed to on stack into tailcall" { | |
| 656 | if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; | |
| 657 | if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; | |
| 658 | if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; | |
| 659 | if (builtin.zig_backend == .stage2_c and builtin.os.tag == .windows) return error.SkipZigTest; // MSVC doesn't support always tail calls | |
| 660 | ||
| 661 | switch (builtin.cpu.arch) { | |
| 662 | .wasm32, | |
| 663 | .mips, | |
| 664 | .mipsel, | |
| 665 | .mips64, | |
| 666 | .mips64el, | |
| 667 | .powerpc, | |
| 668 | .powerpcle, | |
| 669 | .powerpc64, | |
| 670 | .powerpc64le, | |
| 671 | => return error.SkipZigTest, | |
| 672 | else => {}, | |
| 673 | } | |
| 674 | ||
| 675 | const S = struct { | |
| 676 | var base: usize = undefined; | |
| 677 | var result_off: [7]usize = undefined; | |
| 678 | var result_len: [7]usize = undefined; | |
| 679 | var result_index: usize = 0; | |
| 680 | ||
| 681 | noinline fn insertionSort(data: []u64) void { | |
| 682 | result_off[result_index] = @intFromPtr(data.ptr) - base; | |
| 683 | result_len[result_index] = data.len; | |
| 684 | result_index += 1; | |
| 685 | if (data.len > 1) { | |
| 686 | var least_i: usize = 0; | |
| 687 | var i: usize = 1; | |
| 688 | while (i < data.len) : (i += 1) { | |
| 689 | if (data[i] < data[least_i]) | |
| 690 | least_i = i; | |
| 691 | } | |
| 692 | std.mem.swap(u64, &data[0], &data[least_i]); | |
| 693 | ||
| 694 | // there used to be a bug where | |
| 695 | // `data[1..]` is created on the stack | |
| 696 | // and pointed to by the first argument register | |
| 697 | // then stack is invalidated by the tailcall and | |
| 698 | // overwritten by callee | |
| 699 | // https://github.com/ziglang/zig/issues/9703 | |
| 700 | return @call(.always_tail, insertionSort, .{data[1..]}); | |
| 701 | } | |
| 702 | } | |
| 703 | }; | |
| 704 | ||
| 705 | var data = [_]u64{ 1, 6, 2, 7, 1, 9, 3 }; | |
| 706 | S.base = @intFromPtr(&data); | |
| 707 | S.insertionSort(data[0..]); | |
| 708 | try expect(S.result_len[0] == 7); | |
| 709 | try expect(S.result_len[1] == 6); | |
| 710 | try expect(S.result_len[2] == 5); | |
| 711 | try expect(S.result_len[3] == 4); | |
| 712 | try expect(S.result_len[4] == 3); | |
| 713 | try expect(S.result_len[5] == 2); | |
| 714 | try expect(S.result_len[6] == 1); | |
| 715 | ||
| 716 | try expect(S.result_off[0] == 0); | |
| 717 | try expect(S.result_off[1] == 8); | |
| 718 | try expect(S.result_off[2] == 16); | |
| 719 | try expect(S.result_off[3] == 24); | |
| 720 | try expect(S.result_off[4] == 32); | |
| 721 | try expect(S.result_off[5] == 40); | |
| 722 | try expect(S.result_off[6] == 48); | |
| 723 | } | |
| 724 | ||
| 725 | test "tail call function pointer" { | |
| 726 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO | |
| 727 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO | |
| 728 | if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO | |
| 729 | if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO | |
| 730 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO | |
| 731 | if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; | |
| 732 | if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; | |
| 733 | ||
| 734 | if (builtin.zig_backend == .stage2_llvm) { | |
| 735 | if (builtin.cpu.arch.isMIPS() or builtin.cpu.arch.isPowerPC() or builtin.cpu.arch.isWasm()) { | |
| 736 | return error.SkipZigTest; | |
| 737 | } | |
| 738 | } | |
| 739 | ||
| 740 | if (builtin.zig_backend == .stage2_c and builtin.os.tag == .windows) return error.SkipZigTest; // MSVC doesn't support always tail calls | |
| 741 | ||
| 742 | const S = struct { | |
| 743 | fn foo(n: u8) void { | |
| 744 | if (n == 0) return; | |
| 745 | const other: *const fn (u8) void = &bar; | |
| 746 | return @call(.always_tail, other, .{n - 1}); | |
| 747 | } | |
| 748 | fn bar(n: u8) void { | |
| 749 | var other: *const fn (u8) void = undefined; | |
| 750 | other = &foo; // runtime-known pointer | |
| 751 | return @call(.always_tail, other, .{n}); | |
| 752 | } | |
| 753 | }; | |
| 754 | ||
| 755 | S.foo(100); | |
| 756 | } |
test/behavior/call_tail.zig deleted-72| ... | ... | @@ -1,72 +0,0 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | const std = @import("std"); | |
| 3 | const expect = std.testing.expect; | |
| 4 | ||
| 5 | var base: usize = undefined; | |
| 6 | var result_off: [7]usize = undefined; | |
| 7 | var result_len: [7]usize = undefined; | |
| 8 | var result_index: usize = 0; | |
| 9 | ||
| 10 | noinline fn insertionSort(data: []u64) void { | |
| 11 | result_off[result_index] = @intFromPtr(data.ptr) - base; | |
| 12 | result_len[result_index] = data.len; | |
| 13 | result_index += 1; | |
| 14 | if (data.len > 1) { | |
| 15 | var least_i: usize = 0; | |
| 16 | var i: usize = 1; | |
| 17 | while (i < data.len) : (i += 1) { | |
| 18 | if (data[i] < data[least_i]) | |
| 19 | least_i = i; | |
| 20 | } | |
| 21 | std.mem.swap(u64, &data[0], &data[least_i]); | |
| 22 | ||
| 23 | // there used to be a bug where | |
| 24 | // `data[1..]` is created on the stack | |
| 25 | // and pointed to by the first argument register | |
| 26 | // then stack is invalidated by the tailcall and | |
| 27 | // overwritten by callee | |
| 28 | // https://github.com/ziglang/zig/issues/9703 | |
| 29 | return @call(.always_tail, insertionSort, .{data[1..]}); | |
| 30 | } | |
| 31 | } | |
| 32 | ||
| 33 | test "arguments pointed to on stack into tailcall" { | |
| 34 | if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; | |
| 35 | ||
| 36 | switch (builtin.cpu.arch) { | |
| 37 | .wasm32, | |
| 38 | .mips, | |
| 39 | .mipsel, | |
| 40 | .mips64, | |
| 41 | .mips64el, | |
| 42 | .powerpc, | |
| 43 | .powerpcle, | |
| 44 | .powerpc64, | |
| 45 | .powerpc64le, | |
| 46 | => return error.SkipZigTest, | |
| 47 | else => {}, | |
| 48 | } | |
| 49 | if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; | |
| 50 | if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; | |
| 51 | ||
| 52 | if (builtin.zig_backend == .stage2_c and builtin.os.tag == .windows) return error.SkipZigTest; // MSVC doesn't support always tail calls | |
| 53 | ||
| 54 | var data = [_]u64{ 1, 6, 2, 7, 1, 9, 3 }; | |
| 55 | base = @intFromPtr(&data); | |
| 56 | insertionSort(data[0..]); | |
| 57 | try expect(result_len[0] == 7); | |
| 58 | try expect(result_len[1] == 6); | |
| 59 | try expect(result_len[2] == 5); | |
| 60 | try expect(result_len[3] == 4); | |
| 61 | try expect(result_len[4] == 3); | |
| 62 | try expect(result_len[5] == 2); | |
| 63 | try expect(result_len[6] == 1); | |
| 64 | ||
| 65 | try expect(result_off[0] == 0); | |
| 66 | try expect(result_off[1] == 8); | |
| 67 | try expect(result_off[2] == 16); | |
| 68 | try expect(result_off[3] == 24); | |
| 69 | try expect(result_off[4] == 32); | |
| 70 | try expect(result_off[5] == 40); | |
| 71 | try expect(result_off[6] == 48); | |
| 72 | } |