|
742 | 742 | end for _ in 1:2]) |
743 | 743 | @test all(results) |
744 | 744 | end |
| 745 | + |
| 746 | +############################################################################################ |
| 747 | + |
| 748 | +# Device-side allocation: Julia objects that survive optimization are served by a |
| 749 | +# per-work-item heap in private memory (src/device/runtime.jl). Top-level definitions, as a |
| 750 | +# closure capturing test state would not be a bitstype. |
| 751 | + |
| 752 | +@noinline heap_consume(r::Base.RefValue{Float32}) = r[] + 1f0 |
| 753 | + |
| 754 | +struct HeapAnyBox |
| 755 | + x::Any |
| 756 | +end |
| 757 | +@noinline heap_consume(b::HeapAnyBox) = (b.x::Float32) * 2f0 |
| 758 | + |
| 759 | +@testset "device heap" begin |
| 760 | + # a Ref handed to a @noinline callee is heap-allocated |
| 761 | + function ref_kernel(a) |
| 762 | + i = get_global_id() |
| 763 | + @inbounds a[i] = heap_consume(Ref(a[i])) |
| 764 | + return |
| 765 | + end |
| 766 | + a = oneArray(Float32[41]) |
| 767 | + @oneapi ref_kernel(a) |
| 768 | + @test Array(a) == [42] |
| 769 | + |
| 770 | + # so is a struct whose `Any` field boxes its value |
| 771 | + function anybox_kernel(a) |
| 772 | + i = get_global_id() |
| 773 | + @inbounds a[i] = heap_consume(HeapAnyBox(a[i])) |
| 774 | + return |
| 775 | + end |
| 776 | + a = oneArray(Float32[21]) |
| 777 | + @oneapi anybox_kernel(a) |
| 778 | + @test Array(a) == [42] |
| 779 | + |
| 780 | + # every work-item has its own heap |
| 781 | + n = 4096 |
| 782 | + a = oneArray(Float32.(1:n)) |
| 783 | + @oneapi items=256 groups=n÷256 ref_kernel(a) |
| 784 | + @test Array(a) == Float32.(2:n+1) |
| 785 | + |
| 786 | + # objects stay valid across later allocations by the same work-item |
| 787 | + function select_kernel(a, idx) |
| 788 | + i = get_global_id() |
| 789 | + refs = ntuple(j -> Ref(a[i] * j), Val(4)) |
| 790 | + @inbounds a[i] = heap_consume(refs[idx]) |
| 791 | + return |
| 792 | + end |
| 793 | + a = oneArray(Float32[1, 2, 3, 4]) |
| 794 | + @oneapi items=4 select_kernel(a, 3) |
| 795 | + @test Array(a) == Float32[4, 7, 10, 13] |
| 796 | + |
| 797 | + # nothing is freed: a work-item that allocates more than the heap holds runs out of |
| 798 | + # memory, which is reported, and exits without writing its result |
| 799 | + function loop_kernel(a, n) |
| 800 | + i = get_global_id() |
| 801 | + @inbounds x = a[i] |
| 802 | + for _ in 1:n |
| 803 | + x = heap_consume(Ref(x)) |
| 804 | + end |
| 805 | + @inbounds a[i] = x |
| 806 | + return |
| 807 | + end |
| 808 | + fits = oneAPI.HEAP_SIZE ÷ oneAPI.HEAP_ALIGNMENT |
| 809 | + a = oneArray(Float32.(1:256)) |
| 810 | + @oneapi items=256 loop_kernel(a, fits) |
| 811 | + @test Array(a) == Float32.(1:256) .+ fits |
| 812 | + a = oneArray(Float32[1]) |
| 813 | + _, out = @grab_output begin |
| 814 | + @oneapi loop_kernel(a, fits + 1) |
| 815 | + synchronize() |
| 816 | + end |
| 817 | + @test occursin("Out of dynamic GPU memory", out) |
| 818 | + @test Array(a) == [1] |
| 819 | + |
| 820 | + # only kernels that allocate carry a heap |
| 821 | + function plain_kernel(a) |
| 822 | + i = get_global_id() |
| 823 | + @inbounds a[i] += 1f0 |
| 824 | + return |
| 825 | + end |
| 826 | + T = Tuple{oneDeviceVector{Float32, oneAPI.AS.CrossWorkgroup}} |
| 827 | + @test occursin(r"%heap\d* = alloca", sprint(io -> oneAPI.code_llvm(io, ref_kernel, T; kernel=true))) |
| 828 | + @test !occursin("alloca", sprint(io -> oneAPI.code_llvm(io, plain_kernel, T; kernel=true))) |
| 829 | +end |
0 commit comments