Skip to content

Commit 27646d0

Browse files
committed
add coroutines
1 parent ab51302 commit 27646d0

13 files changed

Lines changed: 493 additions & 1 deletion

File tree

src/SimJulia.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,17 @@ module SimJulia
2020
export (&), (|)
2121
export Process, @process
2222
export yield, interrupt
23+
export FiniteStateMachine, @stateful, @yield, iscoroutinedone
24+
export Coroutine, @coroutine
2325

2426
include("base.jl")
2527
include("simulations.jl")
2628
include("events.jl")
2729
include("operators.jl")
2830
include("tasks/base.jl")
2931
include("processes.jl")
32+
include("finitestatemachines/utils.jl")
33+
include("finitestatemachines/transforms.jl")
34+
include("finitestatemachines/base.jl")
35+
include("coroutines.jl")
3036
end

src/coroutines.jl

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
type Coroutine <: AbstractProcess
2+
bev :: BaseEvent
3+
fsm :: FiniteStateMachine
4+
target :: AbstractEvent
5+
resume :: Function
6+
function Coroutine(func::Function, env::Environment, args::Any...)
7+
cor = new()
8+
cor.bev = BaseEvent(env)
9+
cor.fsm = func(env, args...)
10+
cor.target = Timeout(env)
11+
cor.resume = @callback execute(cor.target, cor)
12+
cor
13+
end
14+
end
15+
16+
macro coroutine(expr)
17+
expr.head != :call && error("Expression is not a function call!")
18+
func = esc(expr.args[1])
19+
args = [esc(expr.args[n]) for n in 2:length(expr.args)]
20+
:(Coroutine($(func), $(args...)))
21+
end
22+
23+
function execute(ev::AbstractEvent, proc::Coroutine)
24+
try
25+
env = environment(ev)
26+
set_active_process(env, proc)
27+
target = proc.fsm(value(ev))
28+
reset_active_process(env)
29+
if iscoroutinedone(proc.fsm)
30+
schedule(proc.bev, value=target)
31+
else
32+
proc.target = state(target) == triggered ? Timeout(env, value=value(target)) : target
33+
proc.resume = append_callback(execute, proc.target, proc)
34+
end
35+
catch exc
36+
rethrow(exc)
37+
end
38+
end
39+
40+
function interrupt(proc::Coroutine, cause::Any=nothing)
41+
if !iscoroutinedone(proc.fsm)
42+
remove_callback(proc.resume, proc.target)
43+
proc.target = Timeout(environment(proc), priority=typemax(Int8), value=InterruptException(proc, cause))
44+
proc.resume = append_callback(execute, proc.target, proc)
45+
end
46+
end

src/finitestatemachines/base.jl

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
macro yield(val)
2+
:($(esc(val)))
3+
end
4+
5+
abstract type FiniteStateMachine end
6+
7+
iscoroutinedone(fsm::FiniteStateMachine) = fsm._state == 0xff
8+
9+
macro stateful(expr::Expr)
10+
expr.head != :function && error("Not a function definition!")
11+
args = getArguments(expr)
12+
func_name = shift!(args)
13+
type_name = gensym()
14+
slots = getSlots(expr, func_name)
15+
type_expr = :(
16+
type $type_name <: FiniteStateMachine
17+
_state :: UInt8
18+
$((:($slotname :: $(slottype == Union{} ? Any : :($slottype))) for (slotname, slottype) in slots)...)
19+
function $type_name($((:($arg::$(slots[:($arg)])) for arg in args)...))
20+
fsm = new()
21+
fsm._state = 0x00
22+
$((:(fsm.$arg = $arg) for arg in args)...)
23+
fsm
24+
end
25+
end
26+
)
27+
new_expr = deepcopy(expr.args[2])
28+
transformVars!(new_expr, keys(slots))
29+
n = transformYield!(new_expr)
30+
func_expr = :(
31+
function (_fsm::$type_name)(_ret::Any=nothing)
32+
_fsm._state == 0x00 && @goto _STATE_0
33+
$((:(_fsm._state == $i && @goto $(Symbol("_STATE_",:($i)))) for i in 0x01:n)...)
34+
error("Iterator has stopped!")
35+
@label _STATE_0
36+
_fsm._state = 0xff
37+
$((:($arg) for arg in new_expr.args)...)
38+
end
39+
)
40+
if expr.args[1].head == Symbol("::")
41+
func_expr.args[1] = Expr(Symbol("::"), func_expr.args[1], expr.args[1].args[2])
42+
end
43+
call_expr = deepcopy(expr)
44+
if call_expr.args[1].head == Symbol("::")
45+
call_expr.args[1] = call_expr.args[1].args[1]
46+
end
47+
call_expr.head = Symbol("=")
48+
call_expr.args[2] = :($type_name($((:($arg) for arg in args)...)))
49+
esc(quote
50+
$type_expr
51+
$func_expr
52+
$call_expr
53+
end)
54+
end
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
function transformVars!(expr::Expr, symbols::Base.KeyIterator{Dict{Symbol,Type}})
2+
for i in 1:length(expr.args)
3+
if expr.head == :kw && i == 1
4+
5+
elseif expr.head == Symbol(".") && i == 2
6+
7+
elseif isa(expr.args[i], Symbol) && in(expr.args[i], symbols)
8+
expr.args[i] = :(_fsm.$(expr.args[i]))
9+
elseif isa(expr.args[i], Expr)
10+
transformVars!(expr.args[i], symbols)
11+
end
12+
end
13+
end
14+
15+
function transformTry!(expr::Expr, n::UInt8=0x00, super::Expr=:(), line_no::Int=0, super_try::Expr=:(), line_try::Int=0) :: UInt8
16+
for (i, arg) in enumerate(expr.args)
17+
if isa(arg, Expr)
18+
if arg.head == :line
19+
line_no = i+1
20+
super = expr
21+
elseif arg.head == :macrocall && arg.args[1] == Symbol("@yield")
22+
n += one(UInt8)
23+
if expr == super
24+
expr.args[i] = :(isa(_ret, Exception) && throw(_ret))
25+
else
26+
expr.args[i] = :(_ret)
27+
insert!(super.args, line_no+1, :(isa(_ret, Exception) && throw(_ret)))
28+
end
29+
insert!(super.args, line_no, :(_fsm._state = 0xff))
30+
insert!(super.args, line_no, arg.args[2])
31+
insert!(super.args, line_no, :(_fsm._state = $n))
32+
insert!(super_try.args, line_try, :(@label $(Symbol("_STATE_",:($n)))))
33+
insert!(super_try.args, line_try, deepcopy(super_try.args[line_try+1]))
34+
for j in length(super_try.args[line_try].args[1].args):-1:line_no+2
35+
deleteat!(super_try.args[line_try].args[1].args, j)
36+
end
37+
for j in 1:line_no+1
38+
deleteat!(super_try.args[line_try+2].args[1].args, 1)
39+
end
40+
break
41+
else
42+
m = transformTry!(arg, n, super, line_no, super_try, line_try)
43+
if m > n
44+
n=m
45+
break
46+
end
47+
end
48+
end
49+
end
50+
n
51+
end
52+
53+
function transformYield!(expr::Expr, n::UInt8=0x00, super::Expr=:(), line_no::Int=0) :: UInt8
54+
for (i, arg) in enumerate(expr.args)
55+
if isa(arg, Expr)
56+
if arg.head == :try
57+
n = transformTry!(arg.args[1], n, super, line_no, expr, i)
58+
elseif arg.head == :line
59+
line_no = i+1
60+
super = expr
61+
elseif arg.head == :macrocall && arg.args[1] == Symbol("@yield")
62+
n += one(UInt8)
63+
if expr == super
64+
expr.args[i] = :(_fsm._state = 0xff)
65+
else
66+
expr.args[i] = :(_ret)
67+
insert!(super.args, line_no, :(_fsm._state = 0xff))
68+
end
69+
insert!(super.args, line_no, :(@label $(Symbol("_STATE_",:($n)))))
70+
insert!(super.args, line_no, arg.args[2])
71+
insert!(super.args, line_no, :(_fsm._state = $n))
72+
else
73+
n = transformYield!(arg, n, super, line_no)
74+
end
75+
end
76+
end
77+
n
78+
end

src/finitestatemachines/utils.jl

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
function getArguments(expr) :: Vector{Symbol}
2+
args = Symbol[]
3+
kws = Symbol[]
4+
params = Symbol[]
5+
expr_args = expr.args[1].head == :call ? expr.args[1].args : expr.args[1].args[1].args
6+
for arg in expr_args
7+
if isa(arg, Symbol)
8+
push!(args, arg)
9+
elseif arg.head == Symbol("::")
10+
push!(args, arg.args[1])
11+
elseif arg.head == :kw
12+
isa(arg.args[1], Symbol) ? push!(kws, arg.args[1]) : push!(kws, arg.args[1].args[1])
13+
elseif arg.head == :parameters
14+
for arg2 in arg.args
15+
isa(arg2.args[1], Symbol) ? push!(params, arg2.args[1]) : push!(params, arg2.args[1].args[1])
16+
end
17+
end
18+
end
19+
[args; kws; params]
20+
end
21+
22+
function getSlots(expr::Expr, name::Symbol) :: Dict{Symbol, Type}
23+
slots = Dict{Symbol, Type}()
24+
eval(expr)
25+
code_data_infos = @eval code_typed($name)
26+
for (code_info, data_type) in code_data_infos
27+
for i in 2:length(code_info.slotnames)
28+
slots[code_info.slotnames[i]] = code_info.slottypes[i]
29+
end
30+
end
31+
delete!(slots, Symbol("#temp#"))
32+
delete!(slots, Symbol("#unused#"))
33+
slots
34+
end
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using SimJulia, Distributions, BenchmarkTools
2+
3+
@stateful function exp_source(sim::Simulation{SimJulia.SimulationTime}, lambd::Float64, server::Resource, mu::Float64)
4+
while true
5+
dt = rand(Exponential(1/lambd))
6+
@yield return Timeout(sim, dt)
7+
@coroutine customer2(sim, server, mu)
8+
end
9+
end
10+
11+
@stateful function customer(sim::Simulation{SimJulia.SimulationTime}, server::Resource{Simulation{SimJulia.SimulationTime}}, mu::Float64)
12+
@yield return Request(server)
13+
dt = rand(Exponential(1/mu))
14+
@yield return Timeout(sim, dt)
15+
@yield return Release(server)
16+
end
17+
18+
@stateful function customer2(sim::Simulation{SimJulia.SimulationTime}, server::Resource{Simulation{SimJulia.SimulationTime}}, mu::Float64)
19+
@request server req begin
20+
dt = rand(Exponential(1/mu))
21+
@yield return Timeout(sim, dt)
22+
end
23+
end
24+
25+
function test_mm1(n::Float64)
26+
sim = Simulation()
27+
server = Resource(sim, 1)
28+
@coroutine exp_source(sim, 1.0, server, 1.1)
29+
run(sim, n)
30+
end
31+
32+
BenchmarkTools.DEFAULT_PARAMETERS.samples = 100
33+
println(mean(@benchmark test_mm1(100.0)))
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using SimJulia, BenchmarkTools
2+
3+
@stateful function fibonnaci(sim::Simulation)
4+
a = 0.0
5+
b = 1.0
6+
while true
7+
@yield return Timeout(sim, 1)
8+
a, b = b, a+b
9+
end
10+
end
11+
12+
function run_test()
13+
sim = Simulation()
14+
@coroutine fibonnaci(sim)
15+
run(sim, 10)
16+
end
17+
18+
run_test()
19+
BenchmarkTools.DEFAULT_PARAMETERS.samples = 100
20+
println(mean(@benchmark run_test()))
21+
#@profile for i in 1:100; run_test(); end
22+
#Profile.print(format=:flat)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using SimJulia, Distributions, BenchmarkTools
2+
3+
function exp_source(sim::Simulation, lambd::Float64, server::Resource, mu::Float64)
4+
while true
5+
dt = rand(Exponential(1/lambd))
6+
yield(Timeout(sim, dt))
7+
@process customer2(sim, server, mu)
8+
end
9+
end
10+
11+
function customer(sim::Simulation, server::Resource, mu::Float64)
12+
yield(Request(server))
13+
dt = rand(Exponential(1/mu))
14+
yield(Timeout(sim, dt))
15+
yield(Release(server))
16+
end
17+
18+
function customer2(sim::Simulation, server::Resource, mu::Float64)
19+
request(server) do req
20+
dt = rand(Exponential(1/mu))
21+
yield(Timeout(sim, dt))
22+
end
23+
end
24+
25+
function test_mm1(n::Float64)
26+
sim = Simulation()
27+
server = Resource(sim, 1)
28+
@process exp_source(sim, 1.0, server, 1.1)
29+
run(sim, n)
30+
end
31+
32+
BenchmarkTools.DEFAULT_PARAMETERS.samples = 100
33+
println(mean(@benchmark test_mm1(100.0)))
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using SimJulia, BenchmarkTools
2+
3+
function fibonnaci(sim::Simulation)
4+
a = 0.0
5+
b = 1.0
6+
while true
7+
yield(Timeout(sim, 1))
8+
a, b = b, a+b
9+
end
10+
end
11+
12+
function run_test()
13+
sim = Simulation()
14+
@process fibonnaci(sim)
15+
run(sim, 10)
16+
end
17+
18+
run_test()
19+
BenchmarkTools.DEFAULT_PARAMETERS.samples = 100
20+
println(mean(@benchmark run_test()))
21+
#@profile for i in 1:100; run_test(); end
22+
#Profile.print(format=:flat)

0 commit comments

Comments
 (0)