Skip to content

Commit 6a86ff2

Browse files
author
KDr2
committed
tape instruction support
1 parent d27401a commit 6a86ff2

File tree

4 files changed

+121
-16
lines changed

4 files changed

+121
-16
lines changed

src/tapedfunction.jl

Lines changed: 75 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ mutable struct Instruction{F} <: AbstractInstruction
1313
tape::Tape
1414
end
1515

16+
mutable struct TapeInstruction <: AbstractInstruction
17+
subtape::Tape
18+
tape::Tape
19+
end
20+
1621
Tape() = Tape(Vector{AbstractInstruction}(), 1, nothing)
1722
Tape(owner) = Tape(Vector{AbstractInstruction}(), 1, owner)
1823
MacroTools.@forward Tape.tape Base.iterate, Base.length
@@ -21,6 +26,9 @@ const NULL_TAPE = Tape()
2126

2227
function setowner!(tape::Tape, owner)
2328
tape.owner = owner
29+
for ins in tape
30+
isa(ins, TapeInstruction) && setowner!(ins.subtape, owner)
31+
end
2432
return tape
2533
end
2634

@@ -52,6 +60,12 @@ function Base.show(io::IO, instruction::Instruction)
5260
println(io, "Instruction($(fun)$(map(val, instruction.input)), tape=$(objectid(tape)))")
5361
end
5462

63+
function Base.show(io::IO, ti::TapeInstruction)
64+
subtape = ti.subtape
65+
tape = ti.tape
66+
println(io, "TapeInstruction($(subtape)), tape=$(objectid(tape)))")
67+
end
68+
5569
function Base.show(io::IO, tp::Tape)
5670
buf = IOBuffer()
5771
print(buf, "$(length(tp))-element Tape")
@@ -70,10 +84,19 @@ function (instr::Instruction{F})() where F
7084
instr.output.val = output
7185
end
7286

87+
function (instr::TapeInstruction)()
88+
run(instr.subtape)
89+
end
90+
7391
function increase_counter!(t::Tape)
7492
t.counter > length(t) && return
75-
# instr = t[t.counter]
76-
t.counter += 1
93+
instr = t[t.counter]
94+
if isa(instr, TapeInstruction)
95+
increase_counter!(instr.subtape)
96+
else
97+
# must be a produce instruction?
98+
t.counter += 1
99+
end
77100
return t
78101
end
79102

@@ -84,21 +107,62 @@ function run(tape::Tape, args...)
84107
end
85108
for instruction in tape
86109
instruction()
87-
increase_counter!(tape)
110+
tape.counter += 1
88111
end
89112
end
90113

114+
# if we should trace into a function
115+
# TODO:
116+
# overload (instr::Instruction{F})() to specify
117+
# which function to trace into
118+
function trace_into end
119+
trace_into(x) = false
120+
91121
function run_and_record!(tape::Tape, f, args...)
92122
f = val(f) # f maybe a Boxed closure
93-
output = try
94-
box(f(map(val, args)...))
95-
catch e
96-
@warn e
97-
Box{Any}(nothing)
123+
should_trace = trace_into(f)
124+
if !should_trace
125+
output = try
126+
box(f(map(val, args)...))
127+
catch e
128+
@warn e
129+
Box{Any}(nothing)
130+
end
131+
ins = Instruction(f, args, output, tape)
132+
push!(tape, ins)
133+
return output
134+
else
135+
real_args = map(val, args)
136+
ir = IRTools.@code_ir f(real_args...)
137+
ir = intercept(ir; recorder=:run_and_record!)
138+
# 1. we should distinguish fixed args and varargs here
139+
arg_len = ir |> IRTools.arguments |> length
140+
arg_len -= 2 # 1 for f, 1 for vargs
141+
p_args = real_args[1:arg_len]
142+
v_args = arg_len < 1 ? real_args : real_args[arg_len+1:end]
143+
# detect if there is an vararg for f
144+
if length(v_args) == 1
145+
m = which(f, typeof(real_args))
146+
no_vararg = @static if VERSION >= v"1.7"
147+
(m.sig <: Tuple) && hasproperty(m.sig, :types) &&
148+
!isa(m.sig.types[end], Core.TypeofVararg)
149+
else
150+
(m.sig <: Tuple) && hasproperty(m.sig, :types) &&
151+
!(m.sig.types[end] <: Vararg{Any})
152+
end
153+
if no_vararg
154+
v_args = v_args[1]
155+
end
156+
end
157+
subtape = IRTools.evalir(ir, f, p_args..., v_args)
158+
# 2. we should recover the args after getting the tape
159+
# to keep the chain complete
160+
subtape[1].input = args
161+
ins = TapeInstruction(subtape, tape)
162+
output = subtape[end].output
163+
push!(tape, ins)
164+
return output
98165
end
99-
ins = Instruction(f, args, output, tape)
100-
push!(tape, ins)
101-
return output
102166
end
103167

104168
function unbox_condition(ir)

src/tapedtask.jl

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,26 +52,31 @@ TapedTask(f, args...) = TapedTask(TapedFunction(f, arity=length(args)), args...)
5252
TapedTask(t::TapedTask, args...) = TapedTask(func(t), args...)
5353
func(t::TapedTask) = t.tf.func
5454

55-
5655
function step_in(t::Tape, args)
5756
len = length(t)
5857
if(t.counter <= 1 && length(args) > 0)
5958
input = map(box, args)
6059
t[1].input = input
6160
end
6261
while t.counter <= len
63-
t[t.counter]()
62+
ins = t[t.counter]
63+
if isa(ins, TapeInstruction)
64+
step_in(ins.subtape, ())
65+
else
66+
ins()
67+
end
6468
# produce and wait after an instruction is done
6569
ttask = t.owner.owner
6670
if length(ttask.produced_val) > 0
6771
val = pop!(ttask.produced_val)
6872
put!(ttask.produce_ch, val)
6973
take!(ttask.consume_ch) # wait for next consumer
7074
end
71-
increase_counter!(t)
75+
t.counter += 1
7276
end
7377
end
7478

79+
7580
function next_step!(t::TapedTask)
7681
increase_counter!(t.tf.tape)
7782
return t
@@ -99,14 +104,15 @@ function (instr::Instruction{typeof(produce)})()
99104
end
100105
=#
101106

102-
103107
# ** Approach (B) to implement `produce`:
104108
# This way has its caveat:
105109
# `produce` may deeply hide in an instruction, but not be an instruction
106110
# itself, and when we copy a task, the newly copied task will resume from
107111
# the instruction after the one which contains this `produce` call. If the
108112
# call to `produce` is not the last expression in the instuction, that
109113
# instruction will not be whole executed in the copied task.
114+
# With the abilty to trace into nested function call, we can minimize the
115+
# limitation of this caveat.
110116
@inline function is_in_tapedtask()
111117
ct = current_task()
112118
ct.storage === nothing && return false
@@ -119,6 +125,8 @@ end
119125
function produce(val)
120126
is_in_tapedtask() || return nothing
121127
ttask = current_task().storage[:tapedtask]
128+
# put!(ttask.produce_ch, val)
129+
# take!(ttask.consume_ch) # wait for next consumer
122130
length(ttask.produced_val) > 1 &&
123131
error("There is a produced value which is not consumed.")
124132
push!(ttask.produced_val, val)
@@ -199,6 +207,11 @@ function Base.copy(x::Instruction, on_tape::Tape, roster::Dict{UInt64, Any})
199207
Instruction(x.fun, input, output, on_tape)
200208
end
201209

210+
function Base.copy(x::TapeInstruction, on_tape::Tape, roster::Dict{UInt64, Any})
211+
subtape = copy(x.subtape, roster)
212+
TapeInstruction(subtape, on_tape)
213+
end
214+
202215
function Base.copy(t::Tape, roster::Dict{UInt64, Any})
203216
old_data = t.tape
204217
new_data = Vector{AbstractInstruction}()
@@ -223,7 +236,6 @@ function Base.copy(tf::TapedFunction)
223236
end
224237

225238
function Base.copy(t::TapedTask)
226-
# t.counter[] <= 1 && error("Can't copy a TapedTask which is not running.")
227239
tf = copy(t.tf)
228240
new_t = TapedTask(tf)
229241
new_t.task.storage = copy(t.task.storage)

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ using Libtask
22
using Test
33

44
include("ctask.jl")
5+
include("special-instuctions.jl")
56
include("tarray.jl")
67
include("tref.jl")
78

test/special-instuctions.jl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
@testset "Special Instructions" begin
2+
3+
@testset "TapeInstruction" begin
4+
i1(x) = i2(x)
5+
i2(x) = produce(x)
6+
7+
Libtask.trace_into(::typeof(i1)) = true
8+
Libtask.trace_into(::typeof(i2)) = true
9+
10+
function f()
11+
t = 0
12+
while t < 4
13+
i1(t)
14+
t = 1 + t
15+
end
16+
end
17+
18+
ctask = CTask(f)
19+
@test consume(ctask) == 0
20+
@test consume(ctask) == 1
21+
a = copy(ctask)
22+
@test consume(a) == 2
23+
@test consume(a) == 3
24+
@test consume(ctask) == 2
25+
@test consume(ctask) == 3
26+
end
27+
28+
end

0 commit comments

Comments
 (0)