--- a/ipc/ipdl/ipdl/ast.py
+++ b/ipc/ipdl/ipdl/ast.py
@@ -63,18 +63,16 @@ class Visitor:
for opens in p.opensStmts:
opens.accept(self)
for mgr in p.managers:
mgr.accept(self)
for managed in p.managesStmts:
managed.accept(self)
for msgDecl in p.messageDecls:
msgDecl.accept(self)
- for transitionStmt in p.transitionStmts:
- transitionStmt.accept(self)
def visitNamespace(self, ns):
pass
def visitSpawnsStmt(self, spawns):
pass
def visitBridgesStmt(self, bridges):
@@ -90,28 +88,16 @@ class Visitor:
pass
def visitMessageDecl(self, md):
for inParam in md.inParams:
inParam.accept(self)
for outParam in md.outParams:
outParam.accept(self)
- def visitTransitionStmt(self, ts):
- ts.state.accept(self)
- for trans in ts.transitions:
- trans.accept(self)
-
- def visitTransition(self, t):
- for toState in t.toStates:
- toState.accept(self)
-
- def visitState(self, s):
- pass
-
def visitParam(self, decl):
pass
def visitTypeSpec(self, ts):
pass
def visitDecl(self, d):
pass
@@ -241,18 +227,16 @@ class Protocol(NamespacedNode):
self.sendSemantics = ASYNC
self.nested = NOT_NESTED
self.spawnsStmts = [ ]
self.bridgesStmts = [ ]
self.opensStmts = [ ]
self.managers = [ ]
self.managesStmts = [ ]
self.messageDecls = [ ]
- self.transitionStmts = [ ]
- self.startStates = [ ]
class StructField(Node):
def __init__(self, loc, type, name):
Node.__init__(self, loc)
self.typespec = type
self.name = name
class StructDecl(NamespacedNode):
@@ -317,93 +301,16 @@ class MessageDecl(Node):
for modifier in modifiers:
if modifier.startswith('compress'):
self.compress = modifier
elif modifier == 'verify':
self.verify = modifier
elif modifier != '':
raise Exception, "Unexpected message modifier `%s'"% modifier
-class Transition(Node):
- def __init__(self, loc, trigger, msg, toStates):
- Node.__init__(self, loc)
- self.trigger = trigger
- self.msg = msg
- self.toStates = toStates
-
- def __cmp__(self, o):
- c = cmp(self.msg, o.msg)
- if c: return c
- c = cmp(self.trigger, o.trigger)
- if c: return c
-
- def __hash__(self): return hash(str(self))
- def __str__(self): return '%s %s'% (self.trigger, self.msg)
-
- @staticmethod
- def nameToTrigger(name):
- return { 'send': SEND, 'recv': RECV, 'call': CALL, 'answer': ANSWER }[name]
-
-Transition.NULL = Transition(Loc.NONE, None, None, [ ])
-
-class TransitionStmt(Node):
- def __init__(self, loc, state, transitions):
- Node.__init__(self, loc)
- self.state = state
- self.transitions = transitions
-
- @staticmethod
- def makeNullStmt(state):
- return TransitionStmt(Loc.NONE, state, [ Transition.NULL ])
-
-class SEND:
- pretty = 'send'
- @classmethod
- def __hash__(cls): return hash(cls.pretty)
- @classmethod
- def direction(cls): return OUT
-class RECV:
- pretty = 'recv'
- @classmethod
- def __hash__(cls): return hash(cls.pretty)
- @classmethod
- def direction(cls): return IN
-class CALL:
- pretty = 'call'
- @classmethod
- def __hash__(cls): return hash(cls.pretty)
- @classmethod
- def direction(cls): return OUT
-class ANSWER:
- pretty = 'answer'
- @classmethod
- def __hash__(cls): return hash(cls.pretty)
- @classmethod
- def direction(cls): return IN
-
-class State(Node):
- def __init__(self, loc, name, start=False):
- Node.__init__(self, loc)
- self.name = name
- self.start = start
- def __eq__(self, o):
- return (isinstance(o, State)
- and o.name == self.name
- and o.start == self.start)
- def __hash__(self):
- return hash(repr(self))
- def __ne__(self, o):
- return not (self == o)
- def __repr__(self): return '<State %r start=%r>'% (self.name, self.start)
- def __str__(self): return '<State %s start=%s>'% (self.name, self.start)
-
-State.ANY = State(Loc.NONE, '[any]', start=True)
-State.DEAD = State(Loc.NONE, '[dead]', start=False)
-State.DYING = State(Loc.NONE, '[dying]', start=False)
-
class Param(Node):
def __init__(self, loc, typespec, name):
Node.__init__(self, loc)
self.name = name
self.typespec = typespec
class TypeSpec(Node):
def __init__(self, loc, spec):
--- a/ipc/ipdl/ipdl/lower.py
+++ b/ipc/ipdl/ipdl/lower.py
@@ -1335,20 +1335,16 @@ with some new IPDL/C++ nodes that are tu
return _HybridDecl(decl.type, decl.progname)
def visitMessageDecl(self, md):
md.namespace = self.protocolName
md.params = [ param.accept(self) for param in md.inParams ]
md.returns = [ ret.accept(self) for ret in md.outParams ]
MessageDecl.upgrade(md)
- def visitTransitionStmt(self, ts):
- name = ts.state.decl.progname
- ts.state.decl.cxxname = name
- ts.state.decl.cxxenum = ExprVar(self.protocolName +'::'+ name)
##-----------------------------------------------------------------------------
def msgenums(protocol, pretty=False):
msgenum = TypeEnum('MessageType')
msgstart = _messageStartName(protocol.decl.type) +' << 16'
msgenum.addId(protocol.name + 'Start', msgstart)
@@ -1545,23 +1541,17 @@ class _GenerateProtocolCode(ipdl.ast.Vis
stateenum = TypeEnum('State')
# NB: __Dead is the first state on purpose, so that it has
# value '0'
stateenum.addId(_deadState().name)
stateenum.addId(_nullState().name)
stateenum.addId(_errorState().name)
if self.protocol.decl.type.hasReentrantDelete:
stateenum.addId(_dyingState().name)
- for ts in p.transitionStmts:
- stateenum.addId(ts.state.decl.cxxname)
- if len(p.transitionStmts):
- startstate = p.transitionStmts[0].state.decl.cxxname
- else:
- startstate = _nullState().name
- stateenum.addId(_startState().name, startstate)
+ stateenum.addId(_startState().name, _nullState().name)
ns.addstmts([ StmtDecl(Decl(stateenum,'')), Whitespace.NL ])
# spit out message type enum and classes
msgenum = msgenums(self.protocol)
ns.addstmts([ StmtDecl(Decl(msgenum, '')), Whitespace.NL ])
tfDecl, tfDefn = _splitFuncDeclDefn(self.genTransitionFunc())
@@ -1676,80 +1666,31 @@ class _GenerateProtocolCode(ipdl.ast.Vis
return openfunc
def genTransitionFunc(self):
ptype = self.protocol.decl.type
usesend, sendvar = set(), ExprVar('Send__')
userecv, recvvar = set(), ExprVar('Recv__')
- def sameTrigger(trigger, actionexpr):
- if trigger is ipdl.ast.SEND or trigger is ipdl.ast.CALL:
- usesend.add('yes')
- return ExprBinary(sendvar, '==', actionexpr)
- else:
- userecv.add('yes')
- return ExprBinary(recvvar, '==',
- actionexpr)
-
- def stateEnum(s):
- if s is ipdl.ast.State.DEAD:
- return _deadState()
- else:
- return ExprVar(s.decl.cxxname)
-
# bool Transition(Trigger trigger, State* next)
# The state we are transitioning from is stored in *next.
fromvar = ExprVar('from')
triggervar = ExprVar('trigger')
nextvar = ExprVar('next')
msgexpr = ExprSelect(triggervar, '.', 'mMessage')
- actionexpr = ExprSelect(triggervar, '.', 'mAction')
transitionfunc = FunctionDefn(FunctionDecl(
'Transition',
params=[ Decl(Type('mozilla::ipc::Trigger'), triggervar.name),
Decl(Type('State', ptr=1), nextvar.name) ],
ret=Type.BOOL))
fromswitch = StmtSwitch(fromvar)
- for ts in self.protocol.transitionStmts:
- msgswitch = StmtSwitch(msgexpr)
-
- msgToTransitions = { }
-
- for t in ts.transitions:
- msgid = t.msg._md.msgId()
-
- ifsametrigger = StmtIf(sameTrigger(t.trigger, actionexpr))
- # FIXME multi-out states
- for nextstate in t.toStates: break
- ifsametrigger.addifstmts([
- StmtExpr(ExprAssn(ExprDeref(nextvar),
- stateEnum(nextstate))),
- StmtReturn(ExprLiteral.TRUE)
- ])
-
- transitions = msgToTransitions.get(msgid, [ ])
- transitions.append(ifsametrigger)
- msgToTransitions[msgid] = transitions
-
- for msgid, transitions in msgToTransitions.iteritems():
- block = Block()
- block.addstmts(transitions +[ StmtBreak() ])
- msgswitch.addcase(CaseLabel(msgid), block)
-
- msgblock = Block()
- msgblock.addstmts([
- msgswitch,
- StmtBreak()
- ])
- fromswitch.addcase(CaseLabel(ts.state.decl.cxxname), msgblock)
-
# special cases for Null and Error
nullerrorblock = Block()
if ptype.hasDelete:
ifdelete = StmtIf(ExprBinary(_deleteId(), '==', msgexpr))
if ptype.hasReentrantDelete:
nextState = _dyingState()
else:
nextState = _deadState()
@@ -1795,23 +1736,16 @@ class _GenerateProtocolCode(ipdl.ast.Vis
StmtDecl(Decl(Type('int32_t', const=1), recvvar.name),
init=ExprVar('mozilla::ipc::Trigger::Recv')))
if usesend or userecv:
transitionfunc.addstmt(Whitespace.NL)
transitionfunc.addstmt(StmtDecl(Decl(Type('State'), fromvar.name),
init=ExprDeref(nextvar)))
transitionfunc.addstmt(fromswitch)
- # all --> Error transitions break to here. But only insert this
- # block if there is any possibility of such transitions.
- if self.protocol.transitionStmts:
- transitionfunc.addstmts([
- StmtExpr(ExprAssn(ExprDeref(nextvar), _errorState())),
- StmtReturn(ExprLiteral.FALSE),
- ])
return transitionfunc
##--------------------------------------------------
def _generateMessageConstructor(clsname, msgid, nested, prio, prettyName, compress):
routingId = ExprVar('routingId')
--- a/ipc/ipdl/ipdl/parser.py
+++ b/ipc/ipdl/ipdl/parser.py
@@ -100,48 +100,41 @@ class Parser:
def locFromTok(p, num):
return Loc(Parser.current.filename, p.lineno(num))
##-----------------------------------------------------------------------------
reserved = set((
- 'answer',
'as',
'async',
'both',
'bridges',
- 'call',
'child',
'class',
'compress',
'compressall',
'__delete__',
'delete', # reserve 'delete' to prevent its use
'from',
- 'goto',
'include',
'intr',
'manager',
'manages',
'namespace',
'nested',
'nullable',
'opens',
'or',
'parent',
'prio',
'protocol',
- 'recv',
'returns',
- 'send',
'spawns',
- 'start',
- 'state',
'struct',
'sync',
'union',
'upto',
'using',
'verify'))
tokens = [
'COLONCOLON', 'ID', 'STRING',
@@ -455,19 +448,20 @@ def p_ManagesStmt(p):
p[0] = ManagesStmt(locFromTok(p, 1), p[2])
##--------------------
## Message decls
def p_MessageDeclsOpt(p):
"""MessageDeclsOpt : MessageDeclThing MessageDeclsOpt
- | TransitionStmtsOpt"""
- if 2 == len(p):
- p[0] = p[1]
+ | """
+ if 1 == len(p):
+ # we fill in |loc| in the Protocol rule
+ p[0] = Protocol(None)
else:
p[2].messageDecls.insert(0, p[1])
p[0] = p[2]
def p_MessageDeclThing(p):
"""MessageDeclThing : MessageDirectionLabel ':' MessageDecl ';'
| MessageDecl ';'"""
if 3 == len(p):
@@ -555,81 +549,16 @@ def p_MessageVerify(p):
"""MessageVerify : VERIFY"""
p[0] = p[1]
def p_MessageCompress(p):
"""MessageCompress : COMPRESS
| COMPRESSALL"""
p[0] = p[1]
-##--------------------
-## State machine
-
-def p_TransitionStmtsOpt(p):
- """TransitionStmtsOpt : TransitionStmt TransitionStmtsOpt
- |"""
- if 1 == len(p):
- # we fill in |loc| in the Protocol rule
- p[0] = Protocol(None)
- else:
- p[2].transitionStmts.insert(0, p[1])
- p[0] = p[2]
-
-def p_TransitionStmt(p):
- """TransitionStmt : OptionalStart STATE State ':' Transitions"""
- p[3].start = p[1]
- p[0] = TransitionStmt(locFromTok(p, 2), p[3], p[5])
-
-def p_OptionalStart(p):
- """OptionalStart : START
- | """
- p[0] = (len(p) == 2) # True iff 'start' specified
-
-def p_Transitions(p):
- """Transitions : Transitions Transition
- | Transition"""
- if 3 == len(p):
- p[1].append(p[2])
- p[0] = p[1]
- else:
- p[0] = [ p[1] ]
-
-def p_Transition(p):
- """Transition : Trigger ID GOTO StateList ';'
- | Trigger __DELETE__ ';'
- | Trigger DELETE ';'"""
- if 'delete' == p[2]:
- _error(locFromTok(p, 1), "`delete' is a reserved identifier")
-
- loc, trigger = p[1]
- if 6 == len(p):
- nextstates = p[4]
- else:
- nextstates = [ State.DEAD ]
- p[0] = Transition(loc, trigger, p[2], nextstates)
-
-def p_Trigger(p):
- """Trigger : SEND
- | RECV
- | CALL
- | ANSWER"""
- p[0] = [ locFromTok(p, 1), Transition.nameToTrigger(p[1]) ]
-
-def p_StateList(p):
- """StateList : StateList OR State
- | State"""
- if 2 == len(p):
- p[0] = [ p[1] ]
- else:
- p[1].append(p[3])
- p[0] = p[1]
-
-def p_State(p):
- """State : ID"""
- p[0] = State(locFromTok(p, 1), p[1])
##--------------------
## Minor stuff
def p_Nested(p):
"""Nested : ID"""
kinds = {'not': 1,
'inside_sync': 2,
'inside_cpow': 3}
--- a/ipc/ipdl/ipdl/type.py
+++ b/ipc/ipdl/ipdl/type.py
@@ -1,36 +1,30 @@
# vim: set ts=4 sw=4 tw=99 et:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import os, sys
-from ipdl.ast import CxxInclude, Decl, Loc, QualifiedId, State, StructDecl, TransitionStmt
+from ipdl.ast import CxxInclude, Decl, Loc, QualifiedId, StructDecl
from ipdl.ast import TypeSpec, UnionDecl, UsingStmt, Visitor
from ipdl.ast import ASYNC, SYNC, INTR
-from ipdl.ast import IN, OUT, INOUT, ANSWER, CALL, RECV, SEND
+from ipdl.ast import IN, OUT, INOUT
from ipdl.ast import NOT_NESTED, INSIDE_SYNC_NESTED, INSIDE_CPOW_NESTED
import ipdl.builtin as builtin
_DELETE_MSG = '__delete__'
def _otherside(side):
if side == 'parent': return 'child'
elif side == 'child': return 'parent'
else: assert 0 and 'unknown side "%s"'% (side)
-def unique_pairs(s):
- n = len(s)
- for i, e1 in enumerate(s):
- for j in xrange(i+1, n):
- yield (e1, s[j])
-
def cartesian_product(s1, s2):
for e1 in s1:
for e2 in s2:
yield (e1, e2)
class TypeVisitor:
def __init__(self):
@@ -44,35 +38,31 @@ class TypeVisitor:
pass
def visitBuiltinCxxType(self, t, *args):
pass
def visitImportedCxxType(self, t, *args):
pass
- def visitStateType(self, s, *args):
- pass
-
def visitMessageType(self, m, *args):
for param in m.params:
param.accept(self, *args)
for ret in m.returns:
ret.accept(self, *args)
if m.cdtype is not None:
m.cdtype.accept(self, *args)
def visitProtocolType(self, p, *args):
# NB: don't visit manager and manages. a naive default impl
# could result in an infinite loop
pass
def visitActorType(self, a, *args):
a.protocol.accept(self, *args)
- a.state.accept(self, *args)
def visitStructType(self, s, *args):
if s in self.visited:
return
self.visited.add(s)
for field in s.fields:
field.accept(self, *args)
@@ -190,17 +180,16 @@ class ImportedCxxType(CxxType):
return self.qname.baseid
def fullname(self):
return str(self.qname)
##--------------------
class IPDLType(Type):
def isIPDL(self): return True
def isVisible(self): return True
- def isState(self): return False
def isMessage(self): return False
def isProtocol(self): return False
def isActor(self): return False
def isStruct(self): return False
def isUnion(self): return False
def isArray(self): return False
def isAtom(self): return True
def isCompound(self): return False
@@ -233,27 +222,16 @@ class IPDLType(Type):
elif greater.isInterrupt():
return True
return False
def needsMoreJuiceThan(self, o):
return not IPDLType.convertsTo(self, o)
-class StateType(IPDLType):
- def __init__(self, protocol, name, start=False):
- self.protocol = protocol
- self.name = name
- self.start = start
- def isState(self): return True
- def name(self):
- return self.name
- def fullname(self):
- return self.name()
-
class MessageType(IPDLType):
def __init__(self, nested, prio, sendSemantics, direction,
ctor=False, dtor=False, cdtype=None, compress=False,
verify=False):
assert not (ctor and dtor)
assert not (ctor or dtor) or type is not None
self.nested = nested
@@ -290,25 +268,24 @@ class Bridge:
def __cmp__(self, o):
return cmp(self.parent, o.parent) or cmp(self.child, o.child)
def __eq__(self, o):
return self.parent == o.parent and self.child == o.child
def __hash__(self):
return hash(self.parent) + hash(self.child)
class ProtocolType(IPDLType):
- def __init__(self, qname, nestedRange, sendSemantics, stateless=False):
+ def __init__(self, qname, nestedRange, sendSemantics):
self.qname = qname
self.nestedRange = nestedRange
self.sendSemantics = sendSemantics
self.spawns = set() # ProtocolType
self.opens = set() # ProtocolType
self.managers = [] # ProtocolType
self.manages = [ ]
- self.stateless = stateless
self.hasDelete = False
self.hasReentrantDelete = False
def isProtocol(self): return True
def name(self):
return self.qname.baseid
def fullname(self):
return str(self.qname)
@@ -359,19 +336,18 @@ class ProtocolType(IPDLType):
def isToplevel(self):
return not self.isManaged()
def manager(self):
assert 1 == len(self.managers)
for mgr in self.managers: return mgr
class ActorType(IPDLType):
- def __init__(self, protocol, state=None, nullable=0):
+ def __init__(self, protocol, nullable=0):
self.protocol = protocol
- self.state = state
self.nullable = nullable
def isActor(self): return True
def name(self):
return self.protocol.name()
def fullname(self):
return self.protocol.fullname()
@@ -634,20 +610,16 @@ With this information, it finally type c
# now that the nodes have decls, type checking is much easier.
if not runpass(CheckTypes(self.errors)):
return False
if not (runpass(BuildProcessGraph(self.errors))
and runpass(CheckProcessGraph(self.errors))):
return False
- if (tu.protocol
- and len(tu.protocol.startStates)
- and not runpass(CheckStateMachine(self.errors))):
- return False
return True
def reportErrors(self, errout):
for error in self.errors:
print >>errout, error
class TcheckVisitor(Visitor):
@@ -707,18 +679,17 @@ class GatherDecls(TcheckVisitor):
# to put both the namespace and non-namespaced name in the
# global scope. try to figure out something better; maybe
# a type-neutral |using| that works for C++ and protocol
# types?
qname = p.qname()
fullname = str(qname)
p.decl = self.declare(
loc=p.loc,
- type=ProtocolType(qname, p.nestedRange, p.sendSemantics,
- stateless=(0 == len(p.transitionStmts))),
+ type=ProtocolType(qname, p.nestedRange, p.sendSemantics),
shortname=p.name,
fullname=None if 0 == len(qname.quals) else fullname)
p.parentEndpointDecl = self.declare(
loc=p.loc,
type=EndpointType(QualifiedId(p.loc, 'Endpoint<' + fullname + 'Parent>', ['mozilla', 'ipc'])),
shortname='Endpoint<' + p.name + 'Parent>')
p.childEndpointDecl = self.declare(
@@ -921,104 +892,16 @@ class GatherDecls(TcheckVisitor):
ctordecl = self.symtab.lookup(mgdname +'Constructor')
if not (ctordecl and ctordecl.type.isCtor()):
self.error(
managed.loc,
"constructor declaration required for managed protocol `%s' (managed by protocol `%s')",
mgdname, p.name)
- p.states = { }
-
- if len(p.transitionStmts):
- p.startStates = [ ts for ts in p.transitionStmts
- if ts.state.start ]
- if 0 == len(p.startStates):
- p.startStates = [ p.transitionStmts[0] ]
-
- # declare implicit "any", "dead", and "dying" states
- self.declare(loc=State.ANY.loc,
- type=StateType(p.decl.type, State.ANY.name, start=False),
- progname=State.ANY.name)
- self.declare(loc=State.DEAD.loc,
- type=StateType(p.decl.type, State.DEAD.name, start=False),
- progname=State.DEAD.name)
- if p.decl.type.hasReentrantDelete:
- self.declare(loc=State.DYING.loc,
- type=StateType(p.decl.type, State.DYING.name, start=False),
- progname=State.DYING.name)
-
- # declare each state before decorating their mention
- for trans in p.transitionStmts:
- p.states[trans.state] = trans
- trans.state.decl = self.declare(
- loc=trans.state.loc,
- type=StateType(p.decl.type, trans.state, trans.state.start),
- progname=trans.state.name)
-
- for trans in p.transitionStmts:
- self.seentriggers = set()
- trans.accept(self)
-
- if not (p.decl.type.stateless
- or (p.decl.type.isToplevel()
- and None is self.symtab.lookup(_DELETE_MSG))):
- # add a special state |state DEAD: null goto DEAD;|
- deadtrans = TransitionStmt.makeNullStmt(State.DEAD)
- p.states[State.DEAD] = deadtrans
- if p.decl.type.hasReentrantDelete:
- dyingtrans = TransitionStmt.makeNullStmt(State.DYING)
- p.states[State.DYING] = dyingtrans
-
- # visit the message decls once more and resolve the state names
- # attached to actor params and returns
- def resolvestate(loc, actortype):
- assert actortype.isIPDL() and actortype.isActor()
-
- # already resolved this guy's state
- if isinstance(actortype.state, Decl):
- return
-
- if actortype.state is None:
- # we thought this was a C++ type until type checking,
- # when we realized it was an IPDL actor type. But
- # that means that the actor wasn't specified to be in
- # any particular state
- actortype.state = State.ANY
-
- statename = actortype.state.name
- # FIXME/cjones: this is just wrong. we need the symbol table
- # of the protocol this actor refers to. low priority bug
- # since nobody's using this feature yet
- statedecl = self.symtab.lookup(statename)
- if statedecl is None:
- self.error(
- loc,
- "protocol `%s' does not have the state `%s'",
- actortype.protocol.name(),
- statename)
- elif not statedecl.type.isState():
- self.error(
- loc,
- "tag `%s' is supposed to be of state type, but is instead of type `%s'",
- statename,
- statedecl.type.typename())
- else:
- actortype.state = statedecl.type
-
- for msg in p.messageDecls:
- for iparam in msg.inParams:
- loc = iparam.loc
- for actortype in iteractortypes(iparam.type):
- resolvestate(loc, actortype)
- for oparam in msg.outParams:
- loc = oparam.loc
- for actortype in iteractortypes(oparam.type):
- resolvestate(loc, actortype)
-
# FIXME/cjones declare all the little C++ thingies that will
# be generated. they're not relevant to IPDL itself, but
# those ("invisible") symbols can clash with others in the
# IPDL spec, and we'd like to catch those before C++ compilers
# are allowed to obfuscate the error
self.symtab.exitScope(p)
@@ -1158,66 +1041,16 @@ class GatherDecls(TcheckVisitor):
md.decl = self.declare(
loc=loc,
type=msgtype,
progname=msgname)
md.protocolDecl = self.currentProtocolDecl
md.decl._md = md
- def visitTransitionStmt(self, ts):
- self.seentriggers = set()
- TcheckVisitor.visitTransitionStmt(self, ts)
-
- def visitTransition(self, t):
- loc = t.loc
-
- # check the trigger message
- mname = t.msg
- if t in self.seentriggers:
- self.error(loc, "trigger `%s' appears multiple times", t.msg)
- self.seentriggers.add(t)
-
- mdecl = self.symtab.lookup(mname)
- if mdecl is not None and mdecl.type.isIPDL() and mdecl.type.isProtocol():
- mdecl = self.symtab.lookup(mname +'Constructor')
-
- if mdecl is None:
- self.error(loc, "message `%s' has not been declared", mname)
- elif not mdecl.type.isMessage():
- self.error(
- loc,
- "`%s' should have message type, but instead has type `%s'",
- mname, mdecl.type.typename())
- else:
- t.msg = mdecl
-
- # check the to-states
- seenstates = set()
- for toState in t.toStates:
- sname = toState.name
- sdecl = self.symtab.lookup(sname)
-
- if sname in seenstates:
- self.error(loc, "to-state `%s' appears multiple times", sname)
- seenstates.add(sname)
-
- if sdecl is None:
- self.error(loc, "state `%s' has not been declared", sname)
- elif not sdecl.type.isState():
- self.error(
- loc, "`%s' should have state type, but instead has type `%s'",
- sname, sdecl.type.typename())
- else:
- toState.decl = sdecl
- toState.start = sdecl.type.start
-
- t.toStates = set(t.toStates)
-
-
def _canonicalType(self, itype, typespec):
loc = typespec.loc
if itype.isIPDL():
if itype.isProtocol():
itype = ActorType(itype,
nullable=typespec.nullable)
if typespec.nullable and not (itype.isIPDL() and itype.isActor()):
@@ -1517,37 +1350,16 @@ class CheckTypes(TcheckVisitor):
if mtype.isCtor() and not ptype.isManagerOf(mtype.constructedType()):
self.error(
loc,
"ctor for protocol `%s', which is not managed by protocol `%s'",
mname[:-len('constructor')], pname)
- def visitTransition(self, t):
- _YNC = [ ASYNC, SYNC ]
-
- loc = t.loc
- impliedDirection, impliedSems = {
- SEND: [ OUT, _YNC ], RECV: [ IN, _YNC ],
- CALL: [ OUT, INTR ], ANSWER: [ IN, INTR ],
- } [t.trigger]
-
- if (OUT is impliedDirection and t.msg.type.isIn()
- or IN is impliedDirection and t.msg.type.isOut()
- or _YNC is impliedSems and t.msg.type.isInterrupt()
- or INTR is impliedSems and (not t.msg.type.isInterrupt())):
- mtype = t.msg.type
-
- self.error(
- loc, "%s %s message `%s' is not `%s'd",
- mtype.sendSemantics.pretty, mtype.direction.pretty,
- t.msg.progname,
- t.trigger.pretty)
-
##-----------------------------------------------------------------------------
class Process:
def __init__(self):
self.actors = set() # set(Actor)
self.edges = { } # Actor -> [ SpawnsEdge ]
self.spawn = set() # set(Actor)
@@ -1863,313 +1675,8 @@ class CheckProcessGraph(TcheckVisitor):
print 'Bridges'
for bridgeList in ProcessGraph.bridges.itervalues():
for bridge in bridgeList:
print ' ', bridge
print 'Opens'
for opensList in ProcessGraph.opens.itervalues():
for opens in opensList:
print ' ', opens
-
-##-----------------------------------------------------------------------------
-
-class CheckStateMachine(TcheckVisitor):
- def __init__(self, errors):
- # don't need the symbol table, we just want the error reporting
- TcheckVisitor.__init__(self, None, errors)
- self.p = None
-
- def visitProtocol(self, p):
- self.p = p
- self.checkReachability(p)
- for ts in p.transitionStmts:
- ts.accept(self)
-
- def visitTransitionStmt(self, ts):
- # We want to disallow "race conditions" in protocols. These
- # can occur when a protocol state machine has a state that
- # allows triggers of opposite direction. That declaration
- # allows the parent to send the child a message at the
- # exact instance the child sends the parent a message. One of
- # those messages would (probably) violate the state machine
- # and cause the child to be terminated. It's obviously very
- # nice if we can forbid this at the level of IPDL state
- # machines, rather than resorting to static or dynamic
- # checking of C++ implementation code.
- #
- # An easy way to avoid this problem in IPDL is to only allow
- # "unidirectional" protocol states; that is, from each state,
- # only send or only recv triggers are allowed. This approach
- # is taken by the Singularity project's "contract-based
- # message channels." However, this can be something of a
- # notational burden for stateful protocols.
- #
- # If two messages race, the effect is that the parent's and
- # child's states get temporarily out of sync. Informally,
- # IPDL allows this *only if* the state machines get out of
- # sync for only *one* step (state machine transition), then
- # sync back up. This is a design decision: the states could
- # be allowd to get out of sync for any constant k number of
- # steps. (If k is unbounded, there's no point in presenting
- # the abstraction of parent and child actor states being
- # "entangled".) The working hypothesis is that the more steps
- # the states are allowed to be out of sync, the harder it is
- # to reason about the protocol.
- #
- # Slightly less informally, two messages are allowed to race
- # only if processing them in either order leaves the protocol
- # in the same state. That is, messages A and B are allowed to
- # race only if processing A then B leaves the protocol in
- # state S, *and* processing B then A also leaves the protocol
- # in state S. Technically, if this holds, then messages A and
- # B could be called "commutative" wrt to actor state.
- #
- # "Formally", state machine definitions must adhere to two
- # rules.
- #
- # *Rule 1*: from a state S, all sync triggers must be of the same
- # "direction," i.e. only |send| or only |recv|
- #
- # (Pairs of sync messages can't commute, because otherwise
- # deadlock can occur from simultaneously in-flight sync
- # requests.)
- #
- # *Rule 2*: the "Diamond Rule".
- # from a state S,
- # for any pair of triggers t1 and t2,
- # where t1 and t2 have opposite direction,
- # and t1 transitions to state T1 and t2 to T2,
- # then the following must be true:
- # (T2 allows the trigger t1, transitioning to state U)
- # and
- # (T1 allows the trigger t2, transitioning to state U)
- # and
- # (
- # (
- # (all of T1's triggers have the same direction as t2)
- # and
- # (all of T2's triggers have the same direction as t1)
- # )
- # or
- # (T1, T2, and U are the same "terminal state")
- # )
- #
- # A "terminal state" S is one from which all triggers
- # transition back to S itself.
- #
- # The presence of triggers with multiple out states complicates
- # this check slightly, but doesn't fundamentally change it.
- #
- # from a state S,
- # for any pair of triggers t1 and t2,
- # where t1 and t2 have opposite direction,
- # for each pair of states (T1, T2) \in t1_out x t2_out,
- # where t1_out is the set of outstates from t1
- # t2_out is the set of outstates from t2
- # t1_out x t2_out is their Cartesian product
- # and t1 transitions to state T1 and t2 to T2,
- # then the following must be true:
- # (T2 allows the trigger t1, with out-state set { U })
- # and
- # (T1 allows the trigger t2, with out-state set { U })
- # and
- # (
- # (
- # (all of T1's triggers have the same direction as t2)
- # and
- # (all of T2's triggers have the same direction as t1)
- # )
- # or
- # (T1, T2, and U are the same "terminal state")
- # )
-
- # check Rule 1
- syncdirection = None
- syncok = True
- for trans in ts.transitions:
- if not trans.msg.type.isSync(): continue
- if syncdirection is None:
- syncdirection = trans.trigger.direction()
- elif syncdirection is not trans.trigger.direction():
- self.error(
- trans.loc,
- "sync trigger at state `%s' in protocol `%s' has different direction from earlier sync trigger at same state",
- ts.state.name, self.p.name)
- syncok = False
- # don't check the Diamond Rule if Rule 1 doesn't hold
- if not syncok:
- return
-
- # helper functions
- def triggerTargets(S, t):
- '''Return the set of states transitioned to from state |S|
-upon trigger |t|, or { } if |t| is not a trigger in |S|.'''
- for trans in self.p.states[S].transitions:
- if t.trigger is trans.trigger and t.msg is trans.msg:
- return trans.toStates
- return set()
-
- def allTriggersSameDirectionAs(S, t):
- '''Return true iff all the triggers from state |S| have the same
-direction as trigger |t|'''
- direction = t.direction()
- for trans in self.p.states[S].transitions:
- if direction != trans.trigger.direction():
- return False
- return True
-
- def terminalState(S):
- '''Return true iff |S| is a "terminal state".'''
- for trans in self.p.states[S].transitions:
- for S_ in trans.toStates:
- if S_ != S: return False
- return True
-
- def sameTerminalState(S1, S2, S3):
- '''Return true iff states |S1|, |S2|, and |S3| are all the same
-"terminal state".'''
- if isinstance(S3, set):
- assert len(S3) == 1
- for S3_ in S3: pass
- S3 = S3_
-
- return (S1 == S2 == S3) and terminalState(S1)
-
- S = ts.state.name
-
- # check the Diamond Rule
- for (t1, t2) in unique_pairs(ts.transitions):
- # if the triggers have the same direction, they can't race,
- # since only one endpoint can initiate either (and delivery
- # is in-order)
- if t1.trigger.direction() == t2.trigger.direction():
- continue
-
- loc = t1.loc
- t1_out = t1.toStates
- t2_out = t2.toStates
-
- for (T1, T2) in cartesian_product(t1_out, t2_out):
- # U1 <- { u | T1 --t2--> u }
- U1 = triggerTargets(T1, t2)
- # U2 <- { u | T2 --t1--> u }
- U2 = triggerTargets(T2, t1)
-
- # don't report more than one Diamond Rule violation
- # per state. there may be O(n^4) total, way too many
- # for a human to parse
- #
- # XXX/cjones: could set a limit on #printed and stop
- # after that limit ...
- raceError = False
- errT1 = None
- errT2 = None
-
- if 0 == len(U1) or 0 == len(U2):
- print "******* case 1"
- raceError = True
- elif 1 < len(U1) or 1 < len(U2):
- raceError = True
- # there are potentially many unpaired states; just
- # pick two
- print "******* case 2"
- for u1, u2 in cartesian_product(U1, U2):
- if u1 != u2:
- errT1, errT2 = u1, u2
- break
- elif U1 != U2:
- print "******* case 3"
- raceError = True
- for errT1 in U1: pass
- for errT2 in U2: pass
-
- if raceError:
- self.reportRaceError(loc, S,
- [ T1, t1, errT1 ],
- [ T2, t2, errT2 ])
- return
-
- if not ((allTriggersSameDirectionAs(T1, t2.trigger)
- and allTriggersSameDirectionAs(T2, t1.trigger))
- or sameTerminalState(T1, T2, U1)):
- self.reportRunawayError(loc, S, [ T1, t1, None ], [ T2, t2, None ])
- return
-
- def checkReachability(self, p):
- def explore(ts, visited):
- if ts.state in visited:
- return
- visited.add(ts.state)
- for outedge in ts.transitions:
- for toState in outedge.toStates:
- explore(p.states[toState], visited)
-
- checkfordelete = (State.DEAD in p.states)
-
- allvisited = set() # set(State)
- for root in p.startStates:
- visited = set()
-
- explore(root, visited)
- allvisited.update(visited)
-
- if checkfordelete and State.DEAD not in visited:
- self.error(
- root.loc,
- "when starting from state `%s', actors of protocol `%s' cannot be deleted", root.state.name, p.name)
-
- for ts in p.states.itervalues():
- if ts.state is not State.DEAD and ts.state not in allvisited:
- self.error(ts.loc,
- "unreachable state `%s' in protocol `%s'",
- ts.state.name, p.name)
-
-
- def _normalizeTransitionSequences(self, t1Seq, t2Seq):
- T1, M1, U1 = t1Seq
- T2, M2, U2 = t2Seq
- assert M1 is not None and M2 is not None
-
- # make sure that T1/M1/U1 is the parent side of the race
- if M1.trigger is RECV or M1.trigger is ANSWER:
- T1, M1, U1, T2, M2, U2 = T2, M2, U2, T1, M1, U1
-
- def stateName(S):
- if S: return S.name
- return '[error]'
-
- T1 = stateName(T1)
- T2 = stateName(T2)
- U1 = stateName(U1)
- U2 = stateName(U2)
-
- return T1, M1.msg.progname, U1, T2, M2.msg.progname, U2
-
-
- def reportRaceError(self, loc, S, t1Seq, t2Seq):
- T1, M1, U1, T2, M2, U2 = self._normalizeTransitionSequences(t1Seq, t2Seq)
- self.error(
- loc,
-"""in protocol `%(P)s', the sequence of events
- parent: +--`send %(M1)s'-->( state `%(T1)s' )--`recv %(M2)s'-->( state %(U1)s )
- /
- ( state `%(S)s' )
- \\
- child: +--`send %(M2)s'-->( state `%(T2)s' )--`recv %(M1)s'-->( state %(U2)s )
-results in error(s) or leaves parent/child state out of sync for more than one step and is thus a race hazard; i.e., triggers `%(M1)s' and `%(M2)s' fail to commute in state `%(S)s'"""% {
- 'P': self.p.name, 'S': S, 'M1': M1, 'M2': M2,
- 'T1': T1, 'T2': T2, 'U1': U1, 'U2': U2
- })
-
-
- def reportRunawayError(self, loc, S, t1Seq, t2Seq):
- T1, M1, _, T2, M2, __ = self._normalizeTransitionSequences(t1Seq, t2Seq)
- self.error(
- loc,
- """in protocol `%(P)s', the sequence of events
- parent: +--`send %(M1)s'-->( state `%(T1)s' )
- /
- ( state `%(S)s' )
- \\
- child: +--`send %(M2)s'-->( state `%(T2)s' )
-lead to parent/child states in which parent/child state can become more than one step out of sync (though this divergence might not lead to error conditions)"""% {
- 'P': self.p.name, 'S': S, 'M1': M1, 'M2': M2, 'T1': T1, 'T2': T2
- })