зеркало из https://github.com/microsoft/ivy.git
687 строки
17 KiB
XML
687 строки
17 KiB
XML
#lang ivy1.1
|
|
|
|
# Same as tilelink1.ivy, but with relase consistency rather than
|
|
# sequential consistency
|
|
|
|
# type of protocol time
|
|
type time
|
|
|
|
relation (T:time < U:time)
|
|
axiom (T < U & U < V) -> (T < V)
|
|
axiom ~(T < T)
|
|
axiom ~(T < U & U < T)
|
|
axiom T < U | T = U | U < T
|
|
|
|
# type of logical time
|
|
type ltime
|
|
|
|
relation lleq(T1:ltime,T2:ltime)
|
|
axiom (lleq(T,U) & lleq(U,V)) -> lleq(T,V)
|
|
axiom lleq(T,T)
|
|
axiom (lleq(T,U) & lleq(U,T)) -> U = T
|
|
axiom lleq(T,U) | lleq(U,T)
|
|
|
|
individual lzero : ltime
|
|
axiom lleq(lzero,T)
|
|
|
|
# type of memory data
|
|
type data
|
|
|
|
# type of memory addresses
|
|
type addr
|
|
|
|
# type of message types
|
|
type mtype = { grant, release, request, response}
|
|
|
|
# type of ownership state
|
|
type ownership = { none, shrd, excl }
|
|
|
|
# type of message ID
|
|
type msg_id
|
|
|
|
# describe sides of interface
|
|
type side = {client,manager}
|
|
|
|
# memory history
|
|
relation mem(A:addr, D:data)
|
|
|
|
# memory ops
|
|
|
|
type otype = {read, write, cas}
|
|
|
|
# memory events to serialize
|
|
|
|
module memev = {
|
|
individual type_ : otype
|
|
relation addr_(A:addr)
|
|
relation data_(D:data) # data for write and cas
|
|
relation result(D:data) # value for read and cas
|
|
individual side_ : side # node ID on client side?
|
|
relation serialized
|
|
relation fused
|
|
relation time_(T:time) # serialized time if any
|
|
|
|
axiom addr_(T) & addr_(U) -> T = U
|
|
axiom data_(T) & data_(U) -> T = U
|
|
axiom result(T) & result(U) -> T = U
|
|
axiom time_(T) & time_(U) -> T = U
|
|
|
|
init ~serialized & ~fused
|
|
}
|
|
|
|
instantiate evs(T:ltime) : memev # memory events
|
|
|
|
axiom (mem(A,D1) & mem(A,D2)) -> D1 = D2
|
|
|
|
# message records: set of messages sent
|
|
relation mrec(I:msg_id)
|
|
init ~mrec(I:msg_id)
|
|
|
|
module message = {
|
|
individual type_ : mtype
|
|
relation addr_(A : addr)
|
|
relation data_(D : data)
|
|
relation sent(T : time)
|
|
relation received(T : time)
|
|
individual own : ownership
|
|
relation ltime_(T : ltime)
|
|
individual op : otype
|
|
|
|
axiom addr_(T) & addr_(U) -> T = U
|
|
axiom data_(T) & data_(U) -> T = U
|
|
axiom sent(T) & sent(U) -> T = U
|
|
axiom received(T) & received(U) -> T = U
|
|
axiom sent(T) & received(U) -> T < U
|
|
axiom ltime_(T) & ltime_(U) -> T = U
|
|
}
|
|
|
|
instantiate msg(I:msg_id) : message
|
|
|
|
|
|
individual a : addr
|
|
individual lt : ltime
|
|
individual nlt : ltime
|
|
individual d : data
|
|
individual o : ownership
|
|
individual mid : msg_id
|
|
individual mid1 : msg_id
|
|
individual t : time
|
|
individual nt : time
|
|
individual gt : time
|
|
individual ngt : time
|
|
individual eside : side
|
|
individual mo : otype
|
|
individual e1 : ltime
|
|
individual e2 : ltime
|
|
individual st : time
|
|
individual st1 : time
|
|
|
|
# store buffer operations
|
|
|
|
macro send_msg(t,mt,a,d,lt,o) = {
|
|
mid := *;
|
|
assume ~mrec(mid) & msg(mid):sent(t);
|
|
assume msg(mid):type_ = mt;
|
|
assume msg(mid):addr_(a);
|
|
assume msg(mid):data_(d);
|
|
assume msg(mid):ltime_(lt);
|
|
assume msg(mid):own = o;
|
|
assume msg(mid):op = mo;
|
|
mrec(mid) := true
|
|
}
|
|
|
|
macro receive_msg(t,mt) = {
|
|
mid := *;
|
|
a := *;
|
|
d := *;
|
|
lt := *;
|
|
o := *;
|
|
mo := *;
|
|
assume mrec(mid) & msg(mid):received(t);
|
|
assume mt = msg(mid):type_;
|
|
assume msg(mid):addr_(a);
|
|
assume msg(mid):data_(d);
|
|
assume msg(mid):ltime_(lt);
|
|
assume msg(mid):own = o;
|
|
assume msg(mid):op = mo;
|
|
mrec(mid) := false
|
|
}
|
|
|
|
macro forward() = {
|
|
mid := *;
|
|
mid1 := *;
|
|
e1 := *;
|
|
e2 := *;
|
|
a := *;
|
|
d := *;
|
|
lt := *;
|
|
o := *;
|
|
mo := *;
|
|
st := *;
|
|
st1 := *;
|
|
assume mrec(mid) & mrec(mid1) & mid1 ~= mid;
|
|
assume msg(mid):type_ = request & msg(mid1):type_ = request;
|
|
assume msg(mid):addr_(a) & msg(mid1):addr_(a);
|
|
assume msg(mid):op = read;
|
|
assume msg(mid1):op = write;
|
|
assume msg(mid):sent(st) & msg(mid1):sent(st1);
|
|
assume st1 < st;
|
|
assume ~(mrec(M) & msg(M):sent(T) & st1 < T & T < st & (msg(M):addr_(a) | msg(M):op = cas));
|
|
|
|
# kill the request message
|
|
mrec(mid) := false;
|
|
|
|
# build a response message
|
|
assume msg(mid1):data_(d); # note we get data from previous write
|
|
assume msg(mid):ltime_(lt);
|
|
assume msg(mid):own = o;
|
|
assume msg(mid):op = mo;
|
|
|
|
# tell us what to fuse
|
|
assume msg(mid1):ltime_(e1) & msg(mid):ltime_(e2)
|
|
}
|
|
|
|
|
|
# no two messages received at same time
|
|
axiom msg(I):received(T) & msg(J):received(U) & T = U -> I = J
|
|
|
|
# messages are ordered
|
|
axiom msg(I):sent(T1) & msg(J):sent(U1) & T1 < U1 & msg(J):received(t) -> ~mrec(I)
|
|
|
|
derived prevents(T1,A1,T2,A2) =
|
|
~evs(T1):serialized
|
|
& T1 ~= T2 & lleq(T1,T2)
|
|
& evs(T1):side_ = evs(T2):side_
|
|
& evs(T1):addr_(A1) & evs(T2):addr_(A2)
|
|
& (A1 = A2 | evs(T1):type_ = cas | evs(T2):type_ = cas)
|
|
|
|
|
|
module interface = {
|
|
individual own(A:addr) : ownership # client side ownership state of address
|
|
relation to_ser(T:ltime) # client side events to serialize on manager side
|
|
relation cached(A:addr)
|
|
|
|
init own(A) = none & ~to_ser(T)
|
|
|
|
macro release() = {
|
|
a := *;
|
|
d := *;
|
|
assume cached(a);
|
|
assume own(a) ~= none;
|
|
if own(a) = excl {
|
|
assume mem(a,d)
|
|
};
|
|
own(a) := none;
|
|
o := none
|
|
}
|
|
|
|
macro release_check() = {
|
|
assert cached(a);
|
|
assert own(a) ~= none;
|
|
if own(a) = excl {
|
|
assert mem(a,d)
|
|
};
|
|
own(a) := none;
|
|
o := none
|
|
}
|
|
|
|
macro grant() = {
|
|
a := *;
|
|
d := *;
|
|
o := *;
|
|
assume cached(a);
|
|
assume o ~= none;
|
|
assume own(a) = none;
|
|
assume mem(a,d);
|
|
own(a) := o
|
|
}
|
|
|
|
macro grant_check() = {
|
|
assert cached(a);
|
|
assert o ~= none;
|
|
assert own(a) = none;
|
|
assert mem(a,d);
|
|
own(a) := o
|
|
}
|
|
|
|
macro request() = {
|
|
a := *;
|
|
d := *;
|
|
lt := *;
|
|
mo := *;
|
|
assume ~cached(a);
|
|
assume ~evs(lt):serialized & ~to_ser(lt);
|
|
assume evs(lt):side_ = client;
|
|
assume evs(lt):addr_(a);
|
|
assume evs(lt):data_(d);
|
|
assume mo = evs(lt):type_;
|
|
assume own(a) = none;
|
|
assume ~(prevents(T1,A1,lt,A2) & ~to_ser(T1));
|
|
to_ser(lt) := true
|
|
}
|
|
|
|
macro request_check() = {
|
|
assert ~cached(a);
|
|
assert ~evs(lt):serialized & ~to_ser(lt);
|
|
assert evs(lt):side_ = client;
|
|
assert evs(lt):addr_(a);
|
|
assert evs(lt):data_(d);
|
|
assert mo = evs(lt):type_;
|
|
assert own(a) = none;
|
|
assert ~(prevents(T1,A1,lt,A2) & ~to_ser(T1));
|
|
to_ser(lt) := true
|
|
}
|
|
|
|
macro response() = {
|
|
a := *;
|
|
d := *;
|
|
lt := *;
|
|
mo := *;
|
|
assume ~cached(a);
|
|
assume evs(lt):side_ = client;
|
|
assume evs(lt):type_ = write;
|
|
assume evs(lt):addr_(a);
|
|
assume evs(lt):data_(d);
|
|
assume mo = evs(lt):type_;
|
|
assume own(a) = none;
|
|
assume to_ser(lt);
|
|
assume evs(lt):serialized
|
|
}
|
|
|
|
macro response_check() = {
|
|
assert ~cached(a);
|
|
assert evs(lt):side_ = client;
|
|
assert evs(lt):type_ = write;
|
|
assert evs(lt):addr_(a);
|
|
assert evs(lt):data_(d);
|
|
assert mo = evs(lt):type_;
|
|
assert own(a) = none;
|
|
assert to_ser(lt);
|
|
assert evs(lt):serialized
|
|
}
|
|
|
|
# Describes the guarantees made about serialization events on
|
|
# client and manager side of the interface.
|
|
|
|
macro serialize(side_) = { # side_ = "serialize on client side"
|
|
lt := *; # local time of event
|
|
a := *; # event address
|
|
d := *; # event data
|
|
eside := evs(lt):side_; # eside = "event side"
|
|
assume evs(lt):addr_(a);
|
|
assume evs(lt):data_(d);
|
|
assume ~evs(lt):serialized;
|
|
mo := evs(lt):type_;
|
|
if eside = client {
|
|
if side_ = client {
|
|
assume mo = read -> own(a) ~= none;
|
|
assume mo = write -> own(a) = excl;
|
|
assume mo = cas -> own(a) = excl;
|
|
assume ~prevents(T1,A1,lt,A2)
|
|
}
|
|
else {
|
|
assume to_ser(lt);
|
|
assume ~(to_ser(T1) & prevents(T1,A1,lt,A2))
|
|
}
|
|
}
|
|
else {
|
|
assume side_ = manager; # can't serialize a manager side event on client side
|
|
assume mo = read -> own(a) ~= excl;
|
|
assume mo = write -> own(a) = none;
|
|
assume mo = cas -> own(a) = none;
|
|
assume ~prevents(T1,A1,lt,A2)
|
|
}
|
|
}
|
|
|
|
# Same as above, but asserts the guarantee rather than assuming
|
|
|
|
macro serialize_check(side_) = { # side_ = "serialize on client side"
|
|
eside := evs(lt):side_; # eside = "event on client side"
|
|
assert evs(lt):addr_(a);
|
|
assert evs(lt):data_(d);
|
|
assert ~evs(lt):serialized;
|
|
mo := evs(lt):type_;
|
|
if eside = client {
|
|
if side_ = client {
|
|
assert mo = read -> own(a) ~= none;
|
|
assert mo = write -> own(a) = excl;
|
|
assert mo = cas -> own(a) = excl;
|
|
assert ~prevents(T1,A1,lt,A2)
|
|
}
|
|
else {
|
|
assert to_ser(lt);
|
|
assert ~(to_ser(T1) & prevents(T1,A1,lt,A2))
|
|
}
|
|
}
|
|
else {
|
|
assert side_ = manager; # can't serialize a manager side event on client side
|
|
assert mo = read -> own(a) ~= excl;
|
|
assert mo = write -> own(a) = none;
|
|
assert mo = cas -> own(a) = none;
|
|
assert ~prevents(T1,A1,lt,A2)
|
|
}
|
|
}
|
|
|
|
macro coalesce(side_) = { # side_ = "side on which coalesce occurs"
|
|
e1 := *; # local time of later event
|
|
e2 := *; # local time of earlier event
|
|
a := *; # event address
|
|
d := *; # event data
|
|
|
|
assume evs(e1):side_ = client & evs(e2):side_ = client;
|
|
assume e1 ~= e2 & lleq(e1,e2) & evs(e1):type_ = write & evs(e2):type_ = read;
|
|
assume evs(e1):addr_(a);
|
|
assume evs(e2):addr_(a);
|
|
assume ~(T ~= e1 & T ~= e2 & lleq(e1,T) & lleq(T,e2) & evs(T):side_ = evs(e2):side_ &
|
|
evs(T):addr_(A) & (A = a | evs(T):type_ = cas) & ~evs(T):fused);
|
|
assume ~evs(e2):serialized;
|
|
assume side_ = manager <-> to_ser(e2)
|
|
}
|
|
|
|
macro coalesce_check(side_) = { # side_ = "side on which coalesce occurs"
|
|
assert evs(e1):side_ = client & evs(e2):side_ = client;
|
|
assert e1 ~= e2 & lleq(e1,e2) & evs(e1):type_ = write & evs(e2):type_ = read;
|
|
assert evs(e1):addr_(a);
|
|
assert evs(e2):addr_(a);
|
|
assert ~(T ~= e1 & T ~= e2 & lleq(e1,T) & lleq(T,e2) & evs(T):side_ = evs(e2):side_ &
|
|
evs(T):addr_(A) & (A = a | evs(T):type_ = cas) & ~evs(T):fused);
|
|
assert ~evs(e2):serialized;
|
|
assert side_ = manager <-> to_ser(e2)
|
|
}
|
|
|
|
|
|
}
|
|
|
|
# perform a serialization event at current global time
|
|
|
|
macro perform() = {
|
|
|
|
# this needed to prove release consistency, but we don't prove
|
|
# it here.
|
|
# assert ~prevents(T1,A1,lt,A2)
|
|
|
|
# serialize at current global time
|
|
evs(lt):serialized := true;
|
|
evs(lt):time_(X) := X = gt;
|
|
|
|
# perform memory operation, advancing global time
|
|
|
|
ngt := *;
|
|
assume gt < ngt; # advance global time
|
|
|
|
if mo = read {
|
|
evs(lt):result(D) := mem(a,D)
|
|
}
|
|
else {
|
|
if mo = write {
|
|
mem(a,D) := D = d
|
|
# to_ser(lt):= false # no response required
|
|
}
|
|
else { # mo = cas
|
|
evs(lt):result(D) := mem(a,D);
|
|
mem(a,D) := D = d
|
|
}
|
|
}
|
|
}
|
|
|
|
# this serializes an event by combining it with another event at the
|
|
# same location into an atomic event
|
|
|
|
macro fuse() = {
|
|
# these need to be proved saparately
|
|
# assert e1 ~= e2 & lleq(e1,e2) & evs(e1):type_ = write & evs(e2):type_ = read;
|
|
# assert evs(e1):addr_ = a;
|
|
# assert evs(e2):addr_(a);
|
|
# assert ~(T ~= e1 & T ~= e2 & lleq(e1,T) & lleq(T,e2) &
|
|
# (evs(T):addr_(a) | evs(T):type_ = cas));
|
|
# assert ~evs(e2):serialized;
|
|
evs(e2):serialized := true;
|
|
evs(e2):fused := true;
|
|
evs(e2):data_(X) := evs(e1):data_(X)
|
|
}
|
|
|
|
instantiate front : interface
|
|
instantiate back : interface
|
|
|
|
axiom front:cached(A) <-> back:cached(A)
|
|
|
|
macro front_client() = {
|
|
nt := *;
|
|
assume t < nt;
|
|
if * {
|
|
instantiate front:release();
|
|
instantiate send_msg(t,release,a,d,lt,o)
|
|
}
|
|
else {
|
|
instantiate front:request();
|
|
instantiate send_msg(t,request,a,d,lt,o)
|
|
};
|
|
t := nt
|
|
}
|
|
|
|
macro front_manager() = {
|
|
nt := *;
|
|
assume t < nt;
|
|
if * {
|
|
instantiate receive_msg(t,grant);
|
|
instantiate front:grant_check()
|
|
}
|
|
else {
|
|
instantiate receive_msg(t,response);
|
|
instantiate front:response_check()
|
|
};
|
|
t := nt
|
|
}
|
|
|
|
macro back_manager() = {
|
|
nt := *;
|
|
assume t < nt;
|
|
if * {
|
|
instantiate back:grant();
|
|
instantiate send_msg(t,grant,a,d,lt,o)
|
|
}
|
|
else {
|
|
instantiate back:response();
|
|
instantiate send_msg(t,response,a,d,lt,o)
|
|
};
|
|
t := nt
|
|
}
|
|
|
|
macro back_client() = {
|
|
nt := *;
|
|
assume t < nt;
|
|
if * {
|
|
instantiate receive_msg(t,release);
|
|
instantiate back:release_check()
|
|
}
|
|
else {
|
|
instantiate receive_msg(t,request);
|
|
instantiate back:request_check()
|
|
};
|
|
t := nt
|
|
}
|
|
|
|
action fc = {
|
|
instantiate front_client()
|
|
}
|
|
|
|
action fm = {
|
|
instantiate front_manager()
|
|
}
|
|
|
|
action bc = {
|
|
instantiate back_client()
|
|
}
|
|
|
|
action bm = {
|
|
instantiate back_manager()
|
|
}
|
|
|
|
# serialization happens on front or back side, but never in the channel
|
|
|
|
macro front_serialize = {
|
|
instantiate front:serialize(client);
|
|
instantiate back:serialize_check(client);
|
|
instantiate perform()
|
|
}
|
|
|
|
action fs = {
|
|
instantiate front_serialize
|
|
}
|
|
|
|
macro back_serialize = {
|
|
instantiate back:serialize(manager);
|
|
instantiate front:serialize_check(manager);
|
|
instantiate perform()
|
|
}
|
|
|
|
action bs = {
|
|
instantiate back_serialize
|
|
}
|
|
|
|
# coalescing happens on front or back side, but never in the channel
|
|
|
|
macro front_coalesce = {
|
|
instantiate front:coalesce(client);
|
|
instantiate back:coalesce_check(client);
|
|
instantiate fuse()
|
|
}
|
|
|
|
action ff = {
|
|
instantiate front_coalesce
|
|
}
|
|
|
|
macro back_coalesce = {
|
|
instantiate back:coalesce(manager);
|
|
instantiate front:coalesce_check(manager);
|
|
instantiate fuse()
|
|
}
|
|
|
|
action bf = {
|
|
instantiate back_coalesce
|
|
}
|
|
|
|
# this action cause a read in the chanel to have its value forwarded
|
|
# from a write. As a side effect it coalesces the write and the read
|
|
|
|
|
|
macro channel_internal() = {
|
|
nt := *;
|
|
assume t < nt;
|
|
instantiate forward();
|
|
instantiate front:coalesce_check(manager);
|
|
instantiate back:coalesce_check(client);
|
|
instantiate fuse()
|
|
}
|
|
|
|
action fw = {
|
|
instantiate channel_internal
|
|
}
|
|
|
|
|
|
action step = {
|
|
if * {
|
|
if * {
|
|
if * {
|
|
if * {
|
|
instantiate front_client()
|
|
}
|
|
else {
|
|
instantiate front_manager()
|
|
}
|
|
}
|
|
else {
|
|
if * {
|
|
instantiate back_client()
|
|
}
|
|
else {
|
|
instantiate back_manager()
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if * {
|
|
if * {
|
|
instantiate front_serialize()
|
|
}
|
|
else {
|
|
instantiate back_serialize()
|
|
}
|
|
}
|
|
else {
|
|
if * {
|
|
instantiate front_coalesce()
|
|
}
|
|
else {
|
|
instantiate back_coalesce()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
instantiate channel_internal()
|
|
}
|
|
}
|
|
|
|
# no two distinct active grant messages with same address
|
|
concept lone_grant(M1,M2,A) = msg(M1):type_ = grant * msg(M2):type_ = grant * M1 ~= M2 * mrec(M1) * mrec(M2) * msg(M1):addr_(A) * msg(M2):addr_(A)
|
|
|
|
# grant in channel means address owned at back side
|
|
concept grant_back_own(M,A) = msg(M):addr_(A) * mrec(M) * back:own(A) = none
|
|
|
|
# grant in channel means address not owned at front side
|
|
concept grant_front_own(M,A) = msg(M):addr_(A) * mrec(M) * front:own(A) ~= none
|
|
|
|
# no two distinct active release messages with same address
|
|
concept lone_release(M1,M2,A) = msg(M1):type_ = release * msg(M2):type_ = release * M1 ~= M2 * mrec(M1) * mrec(M2) * msg(M1):addr_(A) * msg(M2):addr_(A)
|
|
|
|
# release in channel means address owned at back side
|
|
concept release_back_own(M,A) = msg(M):addr_(A) * mrec(M) * back:own(A) = none
|
|
|
|
# release in channel means address not owned at front side
|
|
concept release_front_own(M,A) = msg(M):addr_(A) * mrec(M) * front:own(A) ~= none
|
|
|
|
# no grant and release messages with same address
|
|
concept grant_release(M1,M2,A) = msg(M1):type_ = grant * msg(M2):type_ = release * mrec(M1) * mrec(M2) * msg(M1):addr_(A) * msg(M2):addr_(A)
|
|
|
|
# exlusive owned on front side implies exclusive owned on back side
|
|
concept excl_front_back(A) = front:own(A) = excl * back:own(A) ~= excl
|
|
|
|
# shared owned on fron side implies shared owned on back side
|
|
concept shrd_front_back(A) = front:own(A) = shrd * back:own(A) ~= shrd
|
|
|
|
# grant messages have correct data
|
|
concept grant_data(A,M,D) = mrec(M) * msg(M):type_ = grant * msg(M):addr_(A) * msg(M):data_(D) * ~mem(A,D)
|
|
|
|
# release messages have correct data
|
|
concept release_data(A,M,D) = mrec(M) * msg(M):type_ = release * msg(M):addr_(A) * msg(M):data_(D) * ~mem(A,D) * back:own(A) = excl
|
|
|
|
# grant messages have correct ownership
|
|
concept grant_own(A,M) = mrec(M) * msg(M):type_ = grant * msg(M):addr_(A) * msg(M):own ~= back:own(A)
|
|
|
|
# on back interface, to_ser only if uncached
|
|
concept to_ser_uncached_back(T,A) = back:to_ser(T) * evs(T):addr_(A) * back:cached(A)
|
|
|
|
# on front interface, to_ser only if uncached
|
|
concept to_ser_uncached_front(T,A) = front:to_ser(T) * evs(T):addr_(A) * front:cached(A)
|
|
|
|
# on back interface, nothing to serialize that is prevented by something not to serialize
|
|
concept to_ser_back(T1,A1,T2,A2) = prevents(T1,A1,T2,A2) * ~back:to_ser(T1) * back:to_ser(T2)
|
|
|
|
# on front interface, nothing to serialize that is prevented by something not to serialize
|
|
concept to_ser_front(T1,A1,T2,A2) = prevents(T1,A1,T2,A2) * ~front:to_ser(T1) * front:to_ser(T2)
|
|
|
|
# request message only if to_ser on front
|
|
concept request_to_ser(M,T) = mrec(M) * msg(M):type_ = request * msg(M):ltime_(T) * ~front:to_ser(T)
|
|
|
|
# grant message only if cached on back
|
|
concept grant_cached(M,A) = mrec(M) * msg(M):type_ = grant * msg(M):addr_(A) * ~back:cached(A)
|
|
|
|
# release message only if cached on front
|
|
concept release_cached(M,A) = mrec(M) * msg(M):type_ = release * msg(M):addr_(A) * ~front:cached(A)
|
|
|
|
# only client side events are to_ser on back interface
|
|
concept client_side_to_ser_back(T) = back:to_ser(T) * evs(T):side_=manager
|
|
|
|
# only client side events are to_ser on front interface
|
|
concept client_side_to_ser_front(T) = front:to_ser(T) * evs(T):side_=manager |