From 4322ecd7c55b19d25e91adb504266cc25ffb3dbb Mon Sep 17 00:00:00 2001 From: "Killian Q. Zhuo" Date: Wed, 22 Jun 2022 17:10:42 +0800 Subject: [PATCH] Deprecate `TArray` and `TRef`. (#152) * deprecate `TArray` and `TRef`. * remote TArray and TRef * docs update * Update Project.toml Co-authored-by: Hong Ge <3279477+yebai@users.noreply.github.com> --- Project.toml | 2 +- README.md | 33 +++--- src/Libtask.jl | 6 +- src/tapedfunction.jl | 104 +++++++++++-------- src/tapedtask.jl | 22 +++- src/tarray.jl | 238 ------------------------------------------- src/tref.jl | 66 ------------ test/tapedtask.jl | 31 +++++- test/tarray.jl | 89 +--------------- test/tref.jl | 15 ++- 10 files changed, 135 insertions(+), 471 deletions(-) delete mode 100644 src/tarray.jl delete mode 100644 src/tref.jl diff --git a/Project.toml b/Project.toml index d9050294..9dbb81c1 100644 --- a/Project.toml +++ b/Project.toml @@ -3,7 +3,7 @@ uuid = "6f1fad26-d15e-5dc8-ae53-837a1d7b8c9f" license = "MIT" desc = "Tape based task copying in Turing" repo = "https://github.com/TuringLang/Libtask.jl.git" -version = "0.7.5" +version = "0.8" [deps] FunctionWrappers = "069b7b12-0de2-55c6-9aab-29f3d0a68a2e" diff --git a/README.md b/README.md index 9175b1bb..2da3de66 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ a = copy(ttask) @show consume(ttask) # 3 ``` -Heap allocated objects are shallow copied: +Array and Ref objects are deep copied: ```julia using Libtask @@ -50,25 +50,22 @@ ttask = TapedTask(f) @show consume(ttask) # 0 @show consume(ttask) # 1 -a = copy(t) +a = copy(ttask) @show consume(a) # 2 @show consume(a) # 3 -@show consume(ttask) # 4 -@show consume(ttask) # 5 +@show consume(ttask) # 2 +@show consume(ttask) # 3 ``` -In constrast to standard arrays, which are only shallow copied during -task copying, `TArray`, an array data structure provided by Libtask, -is deep copied during the copying process of a task: +Others Heap allocated objects (e.g., `Dict`) are shallow copied: ```julia using Libtask function f() - t = TArray(Int, 1) - t[1] = 0 - for _ in 1:10 + t = Dict(1=>10, 2=>20) + while true produce(t[1]) t[1] = 1 + t[1] end @@ -76,20 +73,20 @@ end ttask = TapedTask(f) -@show consume(ttask) # 0 -@show consume(ttask) # 1 +@show consume(ttask) # 10 +@show consume(ttask) # 11 a = copy(ttask) -@show consume(a) # 2 -@show consume(a) # 3 +@show consume(a) # 12 +@show consume(a) # 13 -@show consume(ttask) # 2 -@show consume(ttask) # 3 +@show consume(ttask) # 14 +@show consume(ttask) # 15 ``` -Notes: +Notes: -- The [Turing](https://github.com/TuringLang/Turing.jl) probabilistic programming +- The [Turing](https://github.com/TuringLang/Turing.jl) probabilistic programming language uses this task copying feature in an efficient implementation of the [particle filtering](https://en.wikipedia.org/wiki/Particle_filter) sampling diff --git a/src/Libtask.jl b/src/Libtask.jl index 92275966..48a8e499 100644 --- a/src/Libtask.jl +++ b/src/Libtask.jl @@ -4,12 +4,10 @@ using FunctionWrappers: FunctionWrapper using LRUCache export TapedTask, consume, produce -export TArray, tzeros, tfill, TRef + +export TArray, tzeros, tfill, TRef # legacy types back compat include("tapedfunction.jl") include("tapedtask.jl") -include("tarray.jl") -include("tref.jl") - end diff --git a/src/tapedfunction.jl b/src/tapedfunction.jl index 656bff40..88a7b908 100644 --- a/src/tapedfunction.jl +++ b/src/tapedfunction.jl @@ -1,37 +1,37 @@ #= -`TapedFunction` converts a Julia function to a friendly tape for user-specified interpreters. -With this tape-like abstraction for functions, we gain some control over how the function is -executed, like capturing continuations, caching variables, injecting additional control flows -(i.e. produce/consume) between instructions on the tape, etc. - -Under the hood, we firstly used Julia's compiler API to get the IR code of the original function. -We use the unoptimised typed code in a non-strict SSA form. Then we convert each IR instruction -to a Julia data structure (an object of a subtype of AbstractInstruction). All the operands -(i.e., the variables) these instructions use are stored in a data structure called `Bindings`. -This conversion/binding process is performed at compile-time / tape-recording time and is only -done once for each function. - +`TapedFunction` converts a Julia function to a friendly tape for user-specified interpreters. +With this tape-like abstraction for functions, we gain some control over how the function is +executed, like capturing continuations, caching variables, injecting additional control flows +(i.e. produce/consume) between instructions on the tape, etc. + +Under the hood, we firstly used Julia's compiler API to get the IR code of the original function. +We use the unoptimised typed code in a non-strict SSA form. Then we convert each IR instruction +to a Julia data structure (an object of a subtype of AbstractInstruction). All the operands +(i.e., the variables) these instructions use are stored in a data structure called `Bindings`. +This conversion/binding process is performed at compile-time / tape-recording time and is only +done once for each function. + In a nutshell, there are two types of instructions (or primitives) on a tape: - Ordinary function call - Control-flow instruction: GotoInstruction and CondGotoInstruction, ReturnInstruction - -Once the tape is recorded, we can run the tape just like calling the original function. + +Once the tape is recorded, we can run the tape just like calling the original function. We first plugin the arguments, run each instruction on the tape, and stop after encountering -a ReturnInstruction. We also provide a mechanism to add a callback after each instruction. -This API allowed us to implement the `produce/consume` machanism in TapedTask. And exploiting +a ReturnInstruction. We also provide a mechanism to add a callback after each instruction. +This API allowed us to implement the `produce/consume` machanism in TapedTask. And exploiting these features, we implemented a fork mechanism for TapedTask. - + Some potentially sharp edges of this implementation: - 1. GlobalRef is evaluated at the tape-recording time (compile-time). Most times, - the value/object associated with a GlobalRef does not change at run time. - So this works well. But, if you do something like `module A v=1 end; make tapedfunction; A.eval(:(v=2)); run tf;`, + 1. GlobalRef is evaluated at the tape-recording time (compile-time). Most times, + the value/object associated with a GlobalRef does not change at run time. + So this works well. But, if you do something like `module A v=1 end; make tapedfunction; A.eval(:(v=2)); run tf;`, The assignment won't work. - 2. QuoteNode is also evaluated at the tape-recording time (compile-time). Primarily + 2. QuoteNode is also evaluated at the tape-recording time (compile-time). Primarily the result of evaluating a QuoteNode is a Symbol, which works well most of the time. - 3. Each Instruction execution contains one unnecessary allocation at the moment. - So writing a function with vectorised computation will be more performant, + 3. Each Instruction execution contains one unnecessary allocation at the moment. + So writing a function with vectorised computation will be more performant, for example, using broadcasting instead of a loop. =# @@ -58,8 +58,9 @@ mutable struct TapedFunction{F, TapeType} binding_values::Bindings arg_binding_slots::Vector{Int} # arg indices in binding_values retval_binding_slot::Int # 0 indicates the function has not returned + deepcopy_types::Vector{Any} - function TapedFunction{F, T}(f::F, args...; cache=false) where {F, T} + function TapedFunction{F, T}(f::F, args...; cache=false, deepcopy_types=[]) where {F, T} args_type = _accurate_typeof.(args) cache_key = (f, args_type...) @@ -72,17 +73,17 @@ mutable struct TapedFunction{F, TapeType} ir = _infer(f, args_type) binding_values, slots, tape = translate!(RawTape(), ir) - tf = new{F, T}(f, length(args), ir, tape, 1, binding_values, slots, 0) + tf = new{F, T}(f, length(args), ir, tape, 1, binding_values, slots, 0, deepcopy_types) TRCache[cache_key] = tf # set cache return tf end - TapedFunction(f, args...; cache=false) = - TapedFunction{typeof(f), RawTape}(f, args...; cache=cache) + TapedFunction(f, args...; cache=false, deepcopy_types=[]) = + TapedFunction{typeof(f), RawTape}(f, args...; cache=cache, deepcopy_types=deepcopy_types) function TapedFunction{F, T0}(tf::TapedFunction{F, T1}) where {F, T0, T1} new{F, T0}(tf.func, tf.arity, tf.ir, tf.tape, - tf.counter, tf.binding_values, tf.arg_binding_slots, 0) + tf.counter, tf.binding_values, tf.arg_binding_slots, 0, tf.deepcopy_types) end TapedFunction(tf::TapedFunction{F, T}) where {F, T} = TapedFunction{F, T}(tf) @@ -444,31 +445,50 @@ end ## copy Bindings, TapedFunction """ - tape_copy(x) + tape_shallowcopy(x) + tape_deepcopy(x) + +Function `tape_shallowcopy` and `tape_deepcopy` are used to copy data +while copying a TapedFunction. A value in the bindings of a +TapedFunction is either `tape_shallowcopy`ed or `tape_deepcopy`ed. For +TapedFunction, all types are shallow copied by default, and you can +specify some types to be deep copied by giving the `deepcopy_types` +kwyword argument while constructing a TapedFunction. + +The default behaviour of `tape_shallowcopy` is, we return its argument +untouched, like `identity` does, i.e., `tape_copy(x) = x`. The default +behaviour of `tape_deepcopy` is, we call `deepcopy` on its argument +and return the result, `tape_deepcopy(x) = deepcopy(x)`. If one wants +some kinds of data to be copied (shallowly or deeply) in a different +way, one can overload these functions. -Function `tape_copy` is used to copy data while copying a -TapedFunction, the default behaviour is: we perform share the data -between tasks, i.e., `tape_copy(x) = x`. If one wants some kinds of -data to be copied, or deeply copied, one can overload this function. """ -function tape_copy end -tape_copy(x) = x +function tape_shallowcopy end, function tape_deepcopy end + +tape_shallowcopy(x) = x +tape_deepcopy(x) = deepcopy(x) # Core.Box is used as closure captured variable container, so we should tape_copy its contents -tape_copy(x::Core.Box) = Core.Box(tape_copy(x.contents)) -# ?? should we deepcopy Array and Dict by default? -# tape_copy(x::Array) = deepcopy(x) -# tape_copy(x::Dict) = deepcopy(x) +tape_shallowcopy(x::Core.Box) = Core.Box(tape_shallowcopy(x.contents)) +tape_deepcopy(x::Core.Box) = Core.Box(tape_deepcopy(x.contents)) + +function _tape_copy(v, deepcopy_types) + if any(t -> isa(v, t), deepcopy_types) + tape_deepcopy(v) + else + tape_shallowcopy(v) + end +end -function copy_bindings(old::Bindings) +function copy_bindings(old::Bindings, deepcopy_types) newb = copy(old) for k in 1:length(old) - isassigned(old, k) && (newb[k] = tape_copy(old[k])) + newb[k] = _tape_copy(old[k], deepcopy_types) end return newb end function Base.copy(tf::TapedFunction) new_tf = TapedFunction(tf) - new_tf.binding_values = copy_bindings(tf.binding_values) + new_tf.binding_values = copy_bindings(tf.binding_values, tf.deepcopy_types) return new_tf end diff --git a/src/tapedtask.jl b/src/tapedtask.jl index f3ec4236..9a8680d7 100644 --- a/src/tapedtask.jl +++ b/src/tapedtask.jl @@ -65,8 +65,8 @@ end # NOTE: evaluating model without a trace, see # https://github.com/TuringLang/Turing.jl/pull/1757#diff-8d16dd13c316055e55f300cd24294bb2f73f46cbcb5a481f8936ff56939da7ceR329 -function TapedTask(f, args...) - tf = TapedFunction(f, args...; cache=true) +function TapedTask(f, args...; deepcopy_types=[Array, Ref]) # deepcoy Array and Ref by default. + tf = TapedFunction(f, args...; cache=true, deepcopy_types=deepcopy_types) TapedTask(tf, args...) end @@ -178,7 +178,7 @@ function Base.copy(t::TapedTask; args=()) end else # the task is not started yet, but no args is given - tape_copy.(t.args) + map(a -> _tape_copy(a, t.tf.deepcopy_types), t.args) end end new_t = TapedTask(tf, task_args...) @@ -187,3 +187,19 @@ function Base.copy(t::TapedTask; args=()) new_t.task.storage[:tapedtask] = new_t return new_t end + +# TArray and TRef back-compat +function TArray(args...) + Base.depwarn("`TArray` is deprecated, please use `Array` instead.", :TArray) + Array(args...) +end +function TArray(T::Type, dim) + Base.depwarn("`TArray` is deprecated, please use `Array` instead.", :TArray) + Array{T}(undef, dim) +end +tzeros, tfill = zeros, fill + +function TRef(x) + Base.depwarn("`TRef` is deprecated, please use `Ref` instead.", :TArray) + Ref(x) +end diff --git a/src/tarray.jl b/src/tarray.jl deleted file mode 100644 index 9c3c2fba..00000000 --- a/src/tarray.jl +++ /dev/null @@ -1,238 +0,0 @@ -########## -# TArray # -########## - -""" - TArray{T}(dims, ...) - -Implementation of data structures that automatically perform deepcopy during task copying. - -More specifically, we store the underlying arrays in the field -`TArray.data`, and this filed will be deepcopied while we are copying -a task who manipulates it. - -Usage: - -```julia -TArray(dim) -``` - -Example: - -```julia -ta = TArray(4) # init -for i in 1:4 ta[i] = i end # assign -Array(ta) # convert to 4-element Array{Int64,1}: [1, 2, 3, 4] -``` -""" -mutable struct TArray{T, N, A <: AbstractArray{T, N}} <: AbstractArray{T, N} - data::A -end - -TArray{T}(d::Integer...) where T = TArray(T, d) -TArray{T}(::UndefInitializer, d::Integer...) where T = TArray(T, d) -TArray{T}(::UndefInitializer, dim::NTuple{N,Int}) where {T,N} = TArray(T, dim) -TArray{T,N}(d::Vararg{<:Integer,N}) where {T,N} = TArray(T, d) -TArray{T,N}(::UndefInitializer, d::Vararg{<:Integer,N}) where {T,N} = TArray{T,N}(d) -TArray{T,N}(dim::NTuple{N,Int}) where {T,N} = TArray(T, dim) -TArray(T::Type, dim) = TArray(Array{T}(undef, dim)) - -localize(x) = x -localize(x::AbstractArray) = TArray(x) -getdata(x) = x -getdata(x::TArray) = x.data -tape_copy(x::TArray) = TArray(deepcopy(x.data)) - - -# Constructors -""" - tzeros(dims, ...) - -Construct a TArray of zeros. -Trailing arguments are the same as those accepted by `TArray`. - -```julia -tzeros(dim) -``` - -Example: - -```julia -tz = tzeros(4) # construct -Array(tz) # convert to 4-element Array{Int64,1}: [0, 0, 0, 0] -``` -""" -function tzeros(T::Type, dim) - d = zeros(T, dim) - TArray(d) -end - -tzeros(::Type{T}, d1::Integer, drest::Integer...) where T = tzeros(T, convert(Dims, tuple(d1, drest...))) -tzeros(d1::Integer, drest::Integer...) = tzeros(Float64, convert(Dims, tuple(d1, drest...))) -tzeros(d::Dims) = tzeros(Float64, d) - -""" - tfill(val, dim, ...) - -Construct a TArray of a specified value. - -```julia -tfill(val, dim) -``` - -Example: - -```julia -tz = tfill(9.0, 4) # construct -Array(tz) # convert to 4-element Array{Float64,1}: [9.0 9.0 9.0 9.0] -``` -""" -function tfill(val::Real, dim) - d = fill(val, dim) - TArray(d) -end - -# -# Conversion between TArray and Array -# - -function Base.convert(::Type{Array}, x::TArray) - convert(Array{eltype(x), ndims(x)}, x) -end -function Base.convert(::Type{Array{T, N}}, x::TArray{T, N}) where {T, N} - convert(Array{T, N}, getdata(x)) -end - -function Base.convert(::Type{TArray}, x::AbstractArray) - convert(TArray{eltype(x), ndims(x)}, x) -end -function Base.convert(::Type{TArray{T, N}}, x::AbstractArray{T, N}) where {T, N} - TArray(x) -end - -# -# Representation -# -function Base.show(io::IO, ::MIME"text/plain", x::TArray) - show(io, MIME("text/plain"), getdata(x)) -end - -Base.show(io::IO, x::TArray) = show(io, getdata(x)) - -function Base.summary(io::IO, x::TArray) - print(io, "TArray: ") - summary(io, getdata(x)) -end - -# -# Forward many methods to the underlying array -# -for F in (:size, - :iterate, - :firstindex, :lastindex, :axes) - @eval Base.$F(a::TArray, args...) = $F(getdata(a), args...) -end - -# -# Similarity implementation -# - -Base.similar(x::TArray, ::Type{T}, dims::Dims) where T = TArray(similar(getdata(x), T, dims)) - -for op in [:(==), :≈] - @eval Base.$op(x::TArray, y::AbstractArray) = Base.$op(getdata(x), y) - @eval Base.$op(x::AbstractArray, y::TArray) = Base.$op(x, getdata(y)) - @eval Base.$op(x::TArray, y::TArray) = Base.$op(getdata(x), getdata(y)) -end - -# -# Array Stdlib -# - -# Indexing Interface -Base.@propagate_inbounds function Base.getindex(x::TArray{T, N}, I::Vararg{Int,N}) where {T, N} - return getdata(x)[I...] -end - -Base.@propagate_inbounds function Base.setindex!(x::TArray{T, N}, e, I::Vararg{Int,N}) where {T, N} - getdata(x)[I...] = e -end - -function Base.push!(x::TArray, e) - push!(getdata(x), e) -end - -function Base.pop!(x::TArray) - pop!(getdata(x)) -end - -# Other methods from stdlib - -Base.view(x::TArray, inds...; kwargs...) = - Base.view(getdata(x), inds...; kwargs...) |> localize -Base.:-(x::TArray) = (-getdata(x)) |> localize -Base.transpose(x::TArray) = transpose(getdata(x)) |> localize -Base.adjoint(x::TArray) = adjoint(getdata(x)) |> localize -Base.repeat(x::TArray; kw...) = repeat(getdata(x); kw...) |> localize - -Base.hcat(xs::Union{TArray{T,1}, TArray{T,2}}...) where T = - hcat(getdata.(xs)...) |> localize -Base.vcat(xs::Union{TArray{T,1}, TArray{T,2}}...) where T = - vcat(getdata.(xs)...) |> localize -Base.cat(xs::Union{TArray{T,1}, TArray{T,2}}...; dims) where T = - cat(getdata.(xs)...; dims = dims) |> localize - - -Base.reshape(x::TArray, dims::Union{Colon,Int}...) = reshape(getdata(x), dims) |> localize -Base.reshape(x::TArray, dims::Tuple{Vararg{Union{Int,Colon}}}) = - reshape(getdata(x), Base._reshape_uncolon(getdata(x), dims)) |> localize -Base.reshape(x::TArray, dims::Tuple{Vararg{Int}}) = reshape(getdata(x), dims) |> localize - -Base.permutedims(x::TArray, perm) = permutedims(getdata(x), perm) |> localize -Base.PermutedDimsArray(x::TArray, perm) = PermutedDimsArray(getdata(x), perm) |> localize -Base.reverse(x::TArray; dims) = reverse(getdata(x), dims = dims) |> localize - -Base.sum(x::TArray; dims = :) = sum(getdata(x), dims = dims) |> localize -Base.sum(f::Union{Function,Type},x::TArray) = sum(f.(getdata(x))) |> localize -Base.prod(x::TArray; dims=:) = prod(getdata(x); dims=dims) |> localize -Base.prod(f::Union{Function, Type}, x::TArray) = prod(f.(getdata(x))) |> localize - -Base.findfirst(x::TArray, args...) = findfirst(getdata(x), args...) |> localize -Base.maximum(x::TArray; dims = :) = maximum(getdata(x), dims = dims) |> localize -Base.minimum(x::TArray; dims = :) = minimum(getdata(x), dims = dims) |> localize - -Base.:/(x::TArray, y::TArray) = getdata(x) / getdata(y) |> localize -Base.:/(x::AbstractArray, y::TArray) = x / getdata(y) |> localize -Base.:/(x::TArray, y::AbstractArray) = getdata(x) / y |> localize -Base.:\(x::TArray, y::TArray) = getdata(x) \ getdata(y) |> localize -Base.:\(x::AbstractArray, y::TArray) = x \ getdata(y) |> localize -Base.:\(x::TArray, y::AbstractArray) = getdata(x) \ y |> localize -Base.:*(x::TArray, y::TArray) = getdata(x) * getdata(y) |> localize -Base.:*(x::AbstractArray, y::TArray) = x * getdata(y) |> localize -Base.:*(x::TArray, y::AbstractArray) = getdata(x) * y |> localize - -# broadcast -Base.BroadcastStyle(::Type{<:TArray}) = Broadcast.ArrayStyle{TArray}() -Broadcast.broadcasted(::Broadcast.ArrayStyle{TArray}, f, args...) = f.(getdata.(args)...) |> localize - -import LinearAlgebra -import LinearAlgebra: \, /, inv, det, logdet, logabsdet, norm - -LinearAlgebra.inv(x::TArray) = inv(getdata(x)) |> localize -LinearAlgebra.det(x::TArray) = det(getdata(x)) |> localize -LinearAlgebra.logdet(x::TArray) = logdet(getdata(x)) |> localize -LinearAlgebra.logabsdet(x::TArray) = logabsdet(getdata(x)) |> localize -LinearAlgebra.norm(x::TArray, p::Real = 2) = - LinearAlgebra.norm(getdata(x), p) |> localize - -import LinearAlgebra: dot -dot(x::TArray, ys::TArray) = dot(getdata(x), getdata(ys)) |> localize -dot(x::AbstractArray, ys::TArray) = dot(x, getdata(ys)) |> localize -dot(x::TArray, ys::AbstractArray) = dot(getdata(x), ys) |> localize - -using Statistics -Statistics.mean(x::TArray; dims = :) = mean(getdata(x), dims = dims) |> localize -Statistics.std(x::TArray; kw...) = std(getdata(x), kw...) |> localize - -# TODO -# * NNlib diff --git a/src/tref.jl b/src/tref.jl deleted file mode 100644 index e6cbf876..00000000 --- a/src/tref.jl +++ /dev/null @@ -1,66 +0,0 @@ -########## -# TRef # -########## - -""" - TRef(x) - -Implementation of an abstract data structure that -automatically performs deepcopy during task copying. - -Atomic (single-valued) TRef objects must be set or updated -by indexing. For example, to access `val = TRef(1)`, you -must use `val[]`. - -Usage: - -```julia -TRef(x) -``` - -Example: - -```julia -# Initialize an atomic value -z = TRef(19.2) -z[] += 31 - -# Initialize a multi-index object -x = TRef([1 2 3; 4 5 6]) -x[1, 3] = 999 - -# Initialize a TRef holding a dictionary. -d = TRef(Dict("A" => 1, 5 => "B")) -d["A"] = 10 -``` -""" -mutable struct TRef{T} - val::T -end - -Base.get(r::TRef) = r.val -Base.show(io::IO, r::TRef) = Base.show(io::IO, get(r)) -Base.size(r::TRef) = Base.size(get(r)) -Base.ndims(r::TRef) = Base.ndims(get(r)) -tape_copy(r::TRef) = TRef(deepcopy(get(r))) - -function Base.getindex(r::TRef, I::Vararg{Any,N}) where {N} - return get(r)[I...] -end - -function Base.setindex!(r::TRef, x, I::Vararg{Any,N}) where {N} - d = get(r) - if isa(d, Real) - r.val = x - else - setindex!(d, x, I...) - end - return d -end - - -# Implements eltype, firstindex, lastindex, and iterate -# functions. -for F in (:eltype, :firstindex, :lastindex, :iterate) - @eval Base.$F(a::TRef, args...) = $F(get(a), args...) -end diff --git a/test/tapedtask.jl b/test/tapedtask.jl index 6256e6bf..20ad21ca 100644 --- a/test/tapedtask.jl +++ b/test/tapedtask.jl @@ -21,8 +21,8 @@ @inferred Libtask.TapedFunction(f) end - # Test case 2: heap allocated objects are shallowly copied. - @testset "heap allocated objects" begin + # Test case 2: Array objects are deeply copied. + @testset "Array objects" begin function f() t = [0 1 2] while true @@ -37,10 +37,33 @@ a = copy(ttask) @test consume(a) == 2 @test consume(a) == 3 + @test consume(ttask) == 2 + @test consume(ttask) == 3 @test consume(ttask) == 4 @test consume(ttask) == 5 - @test consume(ttask) == 6 - @test consume(ttask) == 7 + end + + # Test case 3: Dict objects are shallowly copied. + @testset "Dict objects" begin + function f() + t = Dict(1=>10, 2=>20) + while true + produce(t[1]) + t[1] = 1 + t[1] + end + end + + ttask = TapedTask(f) + + @test consume(ttask) == 10 + @test consume(ttask) == 11 + + a = copy(ttask) + @test consume(a) == 12 + @test consume(a) == 13 + + @test consume(ttask) == 14 + @test consume(ttask) == 15 end @testset "iteration" begin diff --git a/test/tarray.jl b/test/tarray.jl index ca99f46d..4d09610d 100644 --- a/test/tarray.jl +++ b/test/tarray.jl @@ -22,103 +22,18 @@ end @testset "push! and pop!" begin - ta1 = TArray{Int}(4) + ta1 = TArray(Int, 4) push!(ta1, 1) push!(ta1, 2) @test pop!(ta1) == 2 # another constructor - ta1_2 = TArray{Int, 1}(4) + ta1_2 = TArray(Int, 4) push!(ta1_2, 1) push!(ta1_2, 2) @test pop!(ta1_2) == 2 end - @testset "constructors and conversion" begin - ta2 = TArray{Int}(4, 4) - @test ta2 isa TArray{Int,2} - @test size(ta2) == (4, 4) - - ta2 = TArray{Int}(undef, 4, 4) - @test ta2 isa TArray{Int,2} - @test size(ta2) == (4, 4) - - ta2 = TArray{Int,2}(4, 4) - @test ta2 isa TArray{Int,2} - @test size(ta2) == (4, 4) - - ta2 = TArray{Int,2}(undef, 4, 4) - @test ta2 isa TArray{Int,2} - @test size(ta2) == (4, 4) - - @test_throws MethodError TArray{Int,2}(4) - @test_throws MethodError TArray{Int,2}(undef, 4) - - ta3 = TArray{Int, 4}(4, 3, 2, 1) - ta4 = Libtask.getdata(ta3) - @test ta3[3] == ta4[3] - - ta5 = TArray{Int}(4) - @test ta5 isa TArray{Int,1} - @test size(ta5) == (4,) - - ta5 = TArray{Int}(undef, 4) - @test ta5 isa TArray{Int,1} - @test size(ta5) == (4,) - - ta5 = TArray{Int,1}(4) - @test ta5 isa TArray{Int,1} - @test size(ta5) == (4,) - - ta5 = TArray{Int,1}(undef, 4) - @test ta5 isa TArray{Int,1} - @test size(ta5) == (4,) - - @test_throws MethodError TArray{Int,1}(4, 4) - @test_throws MethodError TArray{Int,1}(undef, 4, 4) - - for i in 1:4 - ta5[i] = i - end - @test Array(ta5) == [1, 2, 3, 4] - @test convert(Array, ta5) == [1, 2, 3, 4] - @test convert(Array{Int, 1}, ta5) == [1, 2, 3, 4] - @test ta5 == convert(TArray, [1, 2, 3, 4]) - @test ta5 == convert(TArray{Int, 1}, [1, 2, 3, 4]) - @test_throws MethodError convert(TArray{Int, 2}, [1, 2, 3, 4]) - @test_throws MethodError convert(Array{Int, 2}, ta5) - - @test Array(tzeros(4)) == zeros(4) - - ta6 = TArray{Float64}(4) - for i in 1:4 - ta6[i] = i / 10 - end - @test ta6[1] == 0.1 - @test Array(ta6) == [0.1, 0.2, 0.3, 0.4] - - # TODO: add test for use this multi-dim array - ta7 = TArray{Int, 2}((2, 2)) - end - - @testset "stdlib functions" begin - ta = TArray{Int}(4, 4) - - @test view(ta, 3:5) isa TArray{Int, 1} - @test view(ta, 3:5) == ta[3:5] - - @test -ta isa TArray{Int, 2} - @test -(-ta) == ta - - @test transpose(ta)[2, 3] == ta[3, 2] - - @test repeat(ta, 2) == vcat(ta, ta) - - @test repeat(ta, 1, 2) == hcat(ta, ta) - - @test ta .+ ta == Libtask.getdata(ta) .+ Libtask.getdata(ta) - end - @testset "task copy" begin function f() t = TArray(Int, 1) diff --git a/test/tref.jl b/test/tref.jl index b4f8fb1a..100f5e1a 100644 --- a/test/tref.jl +++ b/test/tref.jl @@ -28,11 +28,10 @@ @testset "dictionary" begin function f() t = TRef(Dict("A" => 1, 5 => "B")) - t["A"] = 0 + t[]["A"] = 0 for _ in 1:6 - produce(t["A"]) - t["A"] - t["A"] += 1 + produce(t[]["A"]) + t[]["A"] += 1 end end @@ -52,12 +51,12 @@ @testset "array" begin # Create a TRef storing a matrix. x = TRef([1 2 3; 4 5 6]) - x[1, 3] = 900 - @test x[1,3] == 900 + x[][1, 3] = 900 + @test x[][1,3] == 900 # TRef holding an array. y = TRef([1,2,3]) - y[2] = 19 - @test y[2] == 19 + y[][2] = 19 + @test y[][2] == 19 end end