mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 17:47:41 +02:00
verification/rvgen: Add support for linear temporal logic
Add support for generating RV monitors from linear temporal logic, similar to the generation of deterministic automaton monitors. Cc: Masami Hiramatsu <mhiramat@kernel.org> Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com> Cc: Gabriele Monaco <gmonaco@redhat.com> Link: https://lore.kernel.org/f3c63b363ff9c5af3302ba2b5d92a26a98700eaf.1751634289.git.namcao@linutronix.de Signed-off-by: Nam Cao <namcao@linutronix.de> Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
This commit is contained in:
parent
cce86e03a2
commit
97ffa4ce6a
3
tools/verification/rvgen/.gitignore
vendored
Normal file
3
tools/verification/rvgen/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
__pycache__/
|
||||||
|
parser.out
|
||||||
|
parsetab.py
|
||||||
|
|
@ -21,5 +21,7 @@ install:
|
||||||
$(INSTALL) rvgen/dot2k.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/dot2k.py
|
$(INSTALL) rvgen/dot2k.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/dot2k.py
|
||||||
$(INSTALL) rvgen/container.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/container.py
|
$(INSTALL) rvgen/container.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/container.py
|
||||||
$(INSTALL) rvgen/generator.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/generator.py
|
$(INSTALL) rvgen/generator.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/generator.py
|
||||||
|
$(INSTALL) rvgen/ltl2ba.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/ltl2ba.py
|
||||||
|
$(INSTALL) rvgen/ltl2k.py -D -m 644 $(DESTDIR)$(PYLIB)/rvgen/ltl2k.py
|
||||||
$(INSTALL) __main__.py -D -m 755 $(DESTDIR)$(bindir)/rvgen
|
$(INSTALL) __main__.py -D -m 755 $(DESTDIR)$(bindir)/rvgen
|
||||||
cp -rp rvgen/templates $(DESTDIR)$(PYLIB)/rvgen/
|
cp -rp rvgen/templates $(DESTDIR)$(PYLIB)/rvgen/
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ if __name__ == '__main__':
|
||||||
from rvgen.dot2k import dot2k
|
from rvgen.dot2k import dot2k
|
||||||
from rvgen.generator import Monitor
|
from rvgen.generator import Monitor
|
||||||
from rvgen.container import Container
|
from rvgen.container import Container
|
||||||
|
from rvgen.ltl2k import ltl2k
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
@ -44,7 +45,7 @@ if __name__ == '__main__':
|
||||||
if params.monitor_class == "da":
|
if params.monitor_class == "da":
|
||||||
monitor = dot2k(params.spec, params.monitor_type, vars(params))
|
monitor = dot2k(params.spec, params.monitor_type, vars(params))
|
||||||
elif params.monitor_class == "ltl":
|
elif params.monitor_class == "ltl":
|
||||||
raise NotImplementedError
|
monitor = ltl2k(params.spec, params.monitor_type, vars(params))
|
||||||
else:
|
else:
|
||||||
print("Unknown monitor class:", params.monitor_class)
|
print("Unknown monitor class:", params.monitor_class)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
|
||||||
540
tools/verification/rvgen/rvgen/ltl2ba.py
Normal file
540
tools/verification/rvgen/rvgen/ltl2ba.py
Normal file
|
|
@ -0,0 +1,540 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
#
|
||||||
|
# Implementation based on
|
||||||
|
# Gerth, R., Peled, D., Vardi, M.Y., Wolper, P. (1996).
|
||||||
|
# Simple On-the-fly Automatic Verification of Linear Temporal Logic.
|
||||||
|
# https://doi.org/10.1007/978-0-387-34892-6_1
|
||||||
|
# With extra optimizations
|
||||||
|
|
||||||
|
from ply.lex import lex
|
||||||
|
from ply.yacc import yacc
|
||||||
|
|
||||||
|
# Grammar:
|
||||||
|
# ltl ::= opd | ( ltl ) | ltl binop ltl | unop ltl
|
||||||
|
#
|
||||||
|
# Operands (opd):
|
||||||
|
# true, false, user-defined names
|
||||||
|
#
|
||||||
|
# Unary Operators (unop):
|
||||||
|
# always
|
||||||
|
# eventually
|
||||||
|
# not
|
||||||
|
#
|
||||||
|
# Binary Operators (binop):
|
||||||
|
# until
|
||||||
|
# and
|
||||||
|
# or
|
||||||
|
# imply
|
||||||
|
# equivalent
|
||||||
|
|
||||||
|
tokens = (
|
||||||
|
'AND',
|
||||||
|
'OR',
|
||||||
|
'IMPLY',
|
||||||
|
'UNTIL',
|
||||||
|
'ALWAYS',
|
||||||
|
'EVENTUALLY',
|
||||||
|
'VARIABLE',
|
||||||
|
'LITERAL',
|
||||||
|
'NOT',
|
||||||
|
'LPAREN',
|
||||||
|
'RPAREN',
|
||||||
|
'ASSIGN',
|
||||||
|
)
|
||||||
|
|
||||||
|
t_AND = r'and'
|
||||||
|
t_OR = r'or'
|
||||||
|
t_IMPLY = r'imply'
|
||||||
|
t_UNTIL = r'until'
|
||||||
|
t_ALWAYS = r'always'
|
||||||
|
t_EVENTUALLY = r'eventually'
|
||||||
|
t_VARIABLE = r'[A-Z_0-9]+'
|
||||||
|
t_LITERAL = r'true|false'
|
||||||
|
t_NOT = r'not'
|
||||||
|
t_LPAREN = r'\('
|
||||||
|
t_RPAREN = r'\)'
|
||||||
|
t_ASSIGN = r'='
|
||||||
|
t_ignore_COMMENT = r'\#.*'
|
||||||
|
t_ignore = ' \t\n'
|
||||||
|
|
||||||
|
def t_error(t):
|
||||||
|
raise ValueError(f"Illegal character '{t.value[0]}'")
|
||||||
|
|
||||||
|
lexer = lex()
|
||||||
|
|
||||||
|
class GraphNode:
|
||||||
|
uid = 0
|
||||||
|
|
||||||
|
def __init__(self, incoming: set['GraphNode'], new, old, _next):
|
||||||
|
self.init = False
|
||||||
|
self.outgoing = set()
|
||||||
|
self.labels = set()
|
||||||
|
self.incoming = incoming.copy()
|
||||||
|
self.new = new.copy()
|
||||||
|
self.old = old.copy()
|
||||||
|
self.next = _next.copy()
|
||||||
|
self.id = GraphNode.uid
|
||||||
|
GraphNode.uid += 1
|
||||||
|
|
||||||
|
def expand(self, node_set):
|
||||||
|
if not self.new:
|
||||||
|
for nd in node_set:
|
||||||
|
if nd.old == self.old and nd.next == self.next:
|
||||||
|
nd.incoming |= self.incoming
|
||||||
|
return node_set
|
||||||
|
|
||||||
|
new_current_node = GraphNode({self}, self.next, set(), set())
|
||||||
|
return new_current_node.expand({self} | node_set)
|
||||||
|
n = self.new.pop()
|
||||||
|
return n.expand(self, node_set)
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return self.id < other.id
|
||||||
|
|
||||||
|
class ASTNode:
|
||||||
|
uid = 1
|
||||||
|
|
||||||
|
def __init__(self, op):
|
||||||
|
self.op = op
|
||||||
|
self.id = ASTNode.uid
|
||||||
|
ASTNode.uid += 1
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.op)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self is other
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
yield self
|
||||||
|
yield from self.op
|
||||||
|
|
||||||
|
def negate(self):
|
||||||
|
self.op = self.op.negate()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def expand(self, node, node_set):
|
||||||
|
return self.op.expand(self, node, node_set)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if isinstance(self.op, Literal):
|
||||||
|
return str(self.op.value)
|
||||||
|
if isinstance(self.op, Variable):
|
||||||
|
return self.op.name.lower()
|
||||||
|
return "val" + str(self.id)
|
||||||
|
|
||||||
|
def normalize(self):
|
||||||
|
# Get rid of:
|
||||||
|
# - ALWAYS
|
||||||
|
# - EVENTUALLY
|
||||||
|
# - IMPLY
|
||||||
|
# And move all the NOT to be inside
|
||||||
|
self.op = self.op.normalize()
|
||||||
|
return self
|
||||||
|
|
||||||
|
class BinaryOp:
|
||||||
|
op_str = "not_supported"
|
||||||
|
|
||||||
|
def __init__(self, left: ASTNode, right: ASTNode):
|
||||||
|
self.left = left
|
||||||
|
self.right = right
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((self.left, self.right))
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
yield from self.left
|
||||||
|
yield from self.right
|
||||||
|
|
||||||
|
def normalize(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def negate(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _is_temporal(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def is_temporal(self):
|
||||||
|
if self.left.op.is_temporal():
|
||||||
|
return True
|
||||||
|
if self.right.op.is_temporal():
|
||||||
|
return True
|
||||||
|
return self._is_temporal()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def expand(n: ASTNode, node: GraphNode, node_set) -> set[GraphNode]:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
class AndOp(BinaryOp):
|
||||||
|
op_str = '&&'
|
||||||
|
|
||||||
|
def normalize(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def negate(self):
|
||||||
|
return OrOp(self.left.negate(), self.right.negate())
|
||||||
|
|
||||||
|
def _is_temporal(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def expand(n: ASTNode, node: GraphNode, node_set) -> set[GraphNode]:
|
||||||
|
if not n.op.is_temporal():
|
||||||
|
node.old.add(n)
|
||||||
|
return node.expand(node_set)
|
||||||
|
|
||||||
|
tmp = GraphNode(node.incoming,
|
||||||
|
node.new | ({n.op.left, n.op.right} - node.old),
|
||||||
|
node.old | {n},
|
||||||
|
node.next)
|
||||||
|
return tmp.expand(node_set)
|
||||||
|
|
||||||
|
class OrOp(BinaryOp):
|
||||||
|
op_str = '||'
|
||||||
|
|
||||||
|
def normalize(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def negate(self):
|
||||||
|
return AndOp(self.left.negate(), self.right.negate())
|
||||||
|
|
||||||
|
def _is_temporal(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def expand(n: ASTNode, node: GraphNode, node_set) -> set[GraphNode]:
|
||||||
|
if not n.op.is_temporal():
|
||||||
|
node.old |= {n}
|
||||||
|
return node.expand(node_set)
|
||||||
|
|
||||||
|
node1 = GraphNode(node.incoming,
|
||||||
|
node.new | ({n.op.left} - node.old),
|
||||||
|
node.old | {n},
|
||||||
|
node.next)
|
||||||
|
node2 = GraphNode(node.incoming,
|
||||||
|
node.new | ({n.op.right} - node.old),
|
||||||
|
node.old | {n},
|
||||||
|
node.next)
|
||||||
|
return node2.expand(node1.expand(node_set))
|
||||||
|
|
||||||
|
class UntilOp(BinaryOp):
|
||||||
|
def normalize(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def negate(self):
|
||||||
|
return VOp(self.left.negate(), self.right.negate())
|
||||||
|
|
||||||
|
def _is_temporal(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def expand(n: ASTNode, node: GraphNode, node_set) -> set[GraphNode]:
|
||||||
|
node1 = GraphNode(node.incoming,
|
||||||
|
node.new | ({n.op.left} - node.old),
|
||||||
|
node.old | {n},
|
||||||
|
node.next | {n})
|
||||||
|
node2 = GraphNode(node.incoming,
|
||||||
|
node.new | ({n.op.right} - node.old),
|
||||||
|
node.old | {n},
|
||||||
|
node.next)
|
||||||
|
return node2.expand(node1.expand(node_set))
|
||||||
|
|
||||||
|
class VOp(BinaryOp):
|
||||||
|
def normalize(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def negate(self):
|
||||||
|
return UntilOp(self.left.negate(), self.right.negate())
|
||||||
|
|
||||||
|
def _is_temporal(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def expand(n: ASTNode, node: GraphNode, node_set) -> set[GraphNode]:
|
||||||
|
node1 = GraphNode(node.incoming,
|
||||||
|
node.new | ({n.op.right} - node.old),
|
||||||
|
node.old | {n},
|
||||||
|
node.next | {n})
|
||||||
|
node2 = GraphNode(node.incoming,
|
||||||
|
node.new | ({n.op.left, n.op.right} - node.old),
|
||||||
|
node.old | {n},
|
||||||
|
node.next)
|
||||||
|
return node2.expand(node1.expand(node_set))
|
||||||
|
|
||||||
|
class ImplyOp(BinaryOp):
|
||||||
|
def normalize(self):
|
||||||
|
# P -> Q === !P | Q
|
||||||
|
return OrOp(self.left.negate(), self.right)
|
||||||
|
|
||||||
|
def _is_temporal(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def negate(self):
|
||||||
|
# !(P -> Q) === !(!P | Q) === P & !Q
|
||||||
|
return AndOp(self.left, self.right.negate())
|
||||||
|
|
||||||
|
class UnaryOp:
|
||||||
|
def __init__(self, child: ASTNode):
|
||||||
|
self.child = child
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
yield from self.child
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.child)
|
||||||
|
|
||||||
|
def normalize(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _is_temporal(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def is_temporal(self):
|
||||||
|
if self.child.op.is_temporal():
|
||||||
|
return True
|
||||||
|
return self._is_temporal()
|
||||||
|
|
||||||
|
def negate(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
class EventuallyOp(UnaryOp):
|
||||||
|
def __str__(self):
|
||||||
|
return "eventually " + str(self.child)
|
||||||
|
|
||||||
|
def normalize(self):
|
||||||
|
# <>F == true U F
|
||||||
|
return UntilOp(ASTNode(Literal(True)), self.child)
|
||||||
|
|
||||||
|
def _is_temporal(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def negate(self):
|
||||||
|
# !<>F == [](!F)
|
||||||
|
return AlwaysOp(self.child.negate()).normalize()
|
||||||
|
|
||||||
|
class AlwaysOp(UnaryOp):
|
||||||
|
def normalize(self):
|
||||||
|
# []F === !(true U !F) == false V F
|
||||||
|
new = ASTNode(Literal(False))
|
||||||
|
return VOp(new, self.child)
|
||||||
|
|
||||||
|
def _is_temporal(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def negate(self):
|
||||||
|
# ![]F == <>(!F)
|
||||||
|
return EventuallyOp(self.child.negate()).normalize()
|
||||||
|
|
||||||
|
class NotOp(UnaryOp):
|
||||||
|
def __str__(self):
|
||||||
|
return "!" + str(self.child)
|
||||||
|
|
||||||
|
def normalize(self):
|
||||||
|
return self.child.op.negate()
|
||||||
|
|
||||||
|
def negate(self):
|
||||||
|
return self.child.op
|
||||||
|
|
||||||
|
def _is_temporal(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def expand(n: ASTNode, node: GraphNode, node_set) -> set[GraphNode]:
|
||||||
|
for f in node.old:
|
||||||
|
if n.op.child is f:
|
||||||
|
return node_set
|
||||||
|
node.old |= {n}
|
||||||
|
return node.expand(node_set)
|
||||||
|
|
||||||
|
class Variable:
|
||||||
|
def __init__(self, name: str):
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.name)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
yield from ()
|
||||||
|
|
||||||
|
def negate(self):
|
||||||
|
new = ASTNode(self)
|
||||||
|
return NotOp(new)
|
||||||
|
|
||||||
|
def normalize(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def is_temporal(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def expand(n: ASTNode, node: GraphNode, node_set) -> set[GraphNode]:
|
||||||
|
for f in node.old:
|
||||||
|
if isinstance(f, NotOp) and f.op.child is n:
|
||||||
|
return node_set
|
||||||
|
node.old |= {n}
|
||||||
|
return node.expand(node_set)
|
||||||
|
|
||||||
|
class Literal:
|
||||||
|
def __init__(self, value: bool):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
yield from ()
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.value)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.value:
|
||||||
|
return "true"
|
||||||
|
return "false"
|
||||||
|
|
||||||
|
def negate(self):
|
||||||
|
self.value = not self.value
|
||||||
|
return self
|
||||||
|
|
||||||
|
def normalize(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def is_temporal(self):
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def expand(n: ASTNode, node: GraphNode, node_set) -> set[GraphNode]:
|
||||||
|
if not n.op.value:
|
||||||
|
return node_set
|
||||||
|
node.old |= {n}
|
||||||
|
return node.expand(node_set)
|
||||||
|
|
||||||
|
def p_spec(p):
|
||||||
|
'''
|
||||||
|
spec : assign
|
||||||
|
| assign spec
|
||||||
|
'''
|
||||||
|
if len(p) == 3:
|
||||||
|
p[2].append(p[1])
|
||||||
|
p[0] = p[2]
|
||||||
|
else:
|
||||||
|
p[0] = [p[1]]
|
||||||
|
|
||||||
|
def p_assign(p):
|
||||||
|
'''
|
||||||
|
assign : VARIABLE ASSIGN ltl
|
||||||
|
'''
|
||||||
|
p[0] = (p[1], p[3])
|
||||||
|
|
||||||
|
def p_ltl(p):
|
||||||
|
'''
|
||||||
|
ltl : opd
|
||||||
|
| binop
|
||||||
|
| unop
|
||||||
|
'''
|
||||||
|
p[0] = p[1]
|
||||||
|
|
||||||
|
def p_opd(p):
|
||||||
|
'''
|
||||||
|
opd : VARIABLE
|
||||||
|
| LITERAL
|
||||||
|
| LPAREN ltl RPAREN
|
||||||
|
'''
|
||||||
|
if p[1] == "true":
|
||||||
|
p[0] = ASTNode(Literal(True))
|
||||||
|
elif p[1] == "false":
|
||||||
|
p[0] = ASTNode(Literal(False))
|
||||||
|
elif p[1] == '(':
|
||||||
|
p[0] = p[2]
|
||||||
|
else:
|
||||||
|
p[0] = ASTNode(Variable(p[1]))
|
||||||
|
|
||||||
|
def p_unop(p):
|
||||||
|
'''
|
||||||
|
unop : ALWAYS ltl
|
||||||
|
| EVENTUALLY ltl
|
||||||
|
| NOT ltl
|
||||||
|
'''
|
||||||
|
if p[1] == "always":
|
||||||
|
op = AlwaysOp(p[2])
|
||||||
|
elif p[1] == "eventually":
|
||||||
|
op = EventuallyOp(p[2])
|
||||||
|
elif p[1] == "not":
|
||||||
|
op = NotOp(p[2])
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Invalid unary operator {p[1]}")
|
||||||
|
|
||||||
|
p[0] = ASTNode(op)
|
||||||
|
|
||||||
|
def p_binop(p):
|
||||||
|
'''
|
||||||
|
binop : opd UNTIL ltl
|
||||||
|
| opd AND ltl
|
||||||
|
| opd OR ltl
|
||||||
|
| opd IMPLY ltl
|
||||||
|
'''
|
||||||
|
if p[2] == "and":
|
||||||
|
op = AndOp(p[1], p[3])
|
||||||
|
elif p[2] == "until":
|
||||||
|
op = UntilOp(p[1], p[3])
|
||||||
|
elif p[2] == "or":
|
||||||
|
op = OrOp(p[1], p[3])
|
||||||
|
elif p[2] == "imply":
|
||||||
|
op = ImplyOp(p[1], p[3])
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Invalid binary operator {p[2]}")
|
||||||
|
|
||||||
|
p[0] = ASTNode(op)
|
||||||
|
|
||||||
|
parser = yacc()
|
||||||
|
|
||||||
|
def parse_ltl(s: str) -> ASTNode:
|
||||||
|
spec = parser.parse(s)
|
||||||
|
|
||||||
|
rule = None
|
||||||
|
subexpr = {}
|
||||||
|
|
||||||
|
for assign in spec:
|
||||||
|
if assign[0] == "RULE":
|
||||||
|
rule = assign[1]
|
||||||
|
else:
|
||||||
|
subexpr[assign[0]] = assign[1]
|
||||||
|
|
||||||
|
if rule is None:
|
||||||
|
raise ValueError("Please define your specification in the \"RULE = <LTL spec>\" format")
|
||||||
|
|
||||||
|
for node in rule:
|
||||||
|
if not isinstance(node.op, Variable):
|
||||||
|
continue
|
||||||
|
replace = subexpr.get(node.op.name)
|
||||||
|
if replace is not None:
|
||||||
|
node.op = replace.op
|
||||||
|
|
||||||
|
return rule
|
||||||
|
|
||||||
|
def create_graph(s: str):
|
||||||
|
atoms = set()
|
||||||
|
|
||||||
|
ltl = parse_ltl(s)
|
||||||
|
for c in ltl:
|
||||||
|
c.normalize()
|
||||||
|
if isinstance(c.op, Variable):
|
||||||
|
atoms.add(c.op.name)
|
||||||
|
|
||||||
|
init = GraphNode(set(), set(), set(), set())
|
||||||
|
head = GraphNode({init}, {ltl}, set(), set())
|
||||||
|
graph = sorted(head.expand(set()))
|
||||||
|
|
||||||
|
for i, node in enumerate(graph):
|
||||||
|
# The id assignment during graph generation has gaps. Reassign them
|
||||||
|
node.id = i
|
||||||
|
|
||||||
|
for incoming in node.incoming:
|
||||||
|
if incoming is init:
|
||||||
|
node.init = True
|
||||||
|
else:
|
||||||
|
incoming.outgoing.add(node)
|
||||||
|
for o in node.old:
|
||||||
|
if not o.op.is_temporal():
|
||||||
|
node.labels.add(str(o))
|
||||||
|
|
||||||
|
return sorted(atoms), graph, ltl
|
||||||
252
tools/verification/rvgen/rvgen/ltl2k.py
Normal file
252
tools/verification/rvgen/rvgen/ltl2k.py
Normal file
|
|
@ -0,0 +1,252 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from . import generator
|
||||||
|
from . import ltl2ba
|
||||||
|
|
||||||
|
COLUMN_LIMIT = 100
|
||||||
|
|
||||||
|
def line_len(line: str) -> int:
|
||||||
|
tabs = line.count('\t')
|
||||||
|
return tabs * 7 + len(line)
|
||||||
|
|
||||||
|
def break_long_line(line: str, indent='') -> list[str]:
|
||||||
|
result = []
|
||||||
|
while line_len(line) > COLUMN_LIMIT:
|
||||||
|
i = line[:COLUMN_LIMIT - line_len(line)].rfind(' ')
|
||||||
|
result.append(line[:i])
|
||||||
|
line = indent + line[i + 1:]
|
||||||
|
if line:
|
||||||
|
result.append(line)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def build_condition_string(node: ltl2ba.GraphNode):
|
||||||
|
if not node.labels:
|
||||||
|
return "(true)"
|
||||||
|
|
||||||
|
result = "("
|
||||||
|
|
||||||
|
first = True
|
||||||
|
for label in sorted(node.labels):
|
||||||
|
if not first:
|
||||||
|
result += " && "
|
||||||
|
result += label
|
||||||
|
first = False
|
||||||
|
|
||||||
|
result += ")"
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def abbreviate_atoms(atoms: list[str]) -> list[str]:
|
||||||
|
def shorten(s: str) -> str:
|
||||||
|
skip = ["is", "by", "or", "and"]
|
||||||
|
return '_'.join([x[:2] for x in s.lower().split('_') if x not in skip])
|
||||||
|
|
||||||
|
abbrs = []
|
||||||
|
for atom in atoms:
|
||||||
|
for i in range(len(atom), -1, -1):
|
||||||
|
if sum(a.startswith(atom[:i]) for a in atoms) > 1:
|
||||||
|
break
|
||||||
|
share = atom[:i]
|
||||||
|
unique = atom[i:]
|
||||||
|
abbrs.append((shorten(share) + shorten(unique)))
|
||||||
|
return abbrs
|
||||||
|
|
||||||
|
class ltl2k(generator.Monitor):
|
||||||
|
template_dir = "ltl2k"
|
||||||
|
|
||||||
|
def __init__(self, file_path, MonitorType, extra_params={}):
|
||||||
|
if MonitorType != "per_task":
|
||||||
|
raise NotImplementedError("Only per_task monitor is supported for LTL")
|
||||||
|
super().__init__(extra_params)
|
||||||
|
with open(file_path) as f:
|
||||||
|
self.atoms, self.ba, self.ltl = ltl2ba.create_graph(f.read())
|
||||||
|
self.atoms_abbr = abbreviate_atoms(self.atoms)
|
||||||
|
self.name = extra_params.get("model_name")
|
||||||
|
if not self.name:
|
||||||
|
self.name = Path(file_path).stem
|
||||||
|
|
||||||
|
def _fill_states(self) -> str:
|
||||||
|
buf = [
|
||||||
|
"enum ltl_buchi_state {",
|
||||||
|
]
|
||||||
|
|
||||||
|
for node in self.ba:
|
||||||
|
buf.append("\tS%i," % node.id)
|
||||||
|
buf.append("\tRV_NUM_BA_STATES")
|
||||||
|
buf.append("};")
|
||||||
|
buf.append("static_assert(RV_NUM_BA_STATES <= RV_MAX_BA_STATES);")
|
||||||
|
return buf
|
||||||
|
|
||||||
|
def _fill_atoms(self):
|
||||||
|
buf = ["enum ltl_atom {"]
|
||||||
|
for a in sorted(self.atoms):
|
||||||
|
buf.append("\tLTL_%s," % a)
|
||||||
|
buf.append("\tLTL_NUM_ATOM")
|
||||||
|
buf.append("};")
|
||||||
|
buf.append("static_assert(LTL_NUM_ATOM <= RV_MAX_LTL_ATOM);")
|
||||||
|
return buf
|
||||||
|
|
||||||
|
def _fill_atoms_to_string(self):
|
||||||
|
buf = [
|
||||||
|
"static const char *ltl_atom_str(enum ltl_atom atom)",
|
||||||
|
"{",
|
||||||
|
"\tstatic const char *const names[] = {"
|
||||||
|
]
|
||||||
|
|
||||||
|
for name in self.atoms_abbr:
|
||||||
|
buf.append("\t\t\"%s\"," % name)
|
||||||
|
|
||||||
|
buf.extend([
|
||||||
|
"\t};",
|
||||||
|
"",
|
||||||
|
"\treturn names[atom];",
|
||||||
|
"}"
|
||||||
|
])
|
||||||
|
return buf
|
||||||
|
|
||||||
|
def _fill_atom_values(self):
|
||||||
|
buf = []
|
||||||
|
for node in self.ltl:
|
||||||
|
if node.op.is_temporal():
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(node.op, ltl2ba.Variable):
|
||||||
|
buf.append("\tbool %s = test_bit(LTL_%s, mon->atoms);" % (node, node.op.name))
|
||||||
|
elif isinstance(node.op, ltl2ba.AndOp):
|
||||||
|
buf.append("\tbool %s = %s && %s;" % (node, node.op.left, node.op.right))
|
||||||
|
elif isinstance(node.op, ltl2ba.OrOp):
|
||||||
|
buf.append("\tbool %s = %s || %s;" % (node, node.op.left, node.op.right))
|
||||||
|
elif isinstance(node.op, ltl2ba.NotOp):
|
||||||
|
buf.append("\tbool %s = !%s;" % (node, node.op.child))
|
||||||
|
buf.reverse()
|
||||||
|
|
||||||
|
buf2 = []
|
||||||
|
for line in buf:
|
||||||
|
buf2.extend(break_long_line(line, "\t "))
|
||||||
|
return buf2
|
||||||
|
|
||||||
|
def _fill_transitions(self):
|
||||||
|
buf = [
|
||||||
|
"static void",
|
||||||
|
"ltl_possible_next_states(struct ltl_monitor *mon, unsigned int state, unsigned long *next)",
|
||||||
|
"{"
|
||||||
|
]
|
||||||
|
buf.extend(self._fill_atom_values())
|
||||||
|
buf.extend([
|
||||||
|
"",
|
||||||
|
"\tswitch (state) {"
|
||||||
|
])
|
||||||
|
|
||||||
|
for node in self.ba:
|
||||||
|
buf.append("\tcase S%i:" % node.id)
|
||||||
|
|
||||||
|
for o in sorted(node.outgoing):
|
||||||
|
line = "\t\tif "
|
||||||
|
indent = "\t\t "
|
||||||
|
|
||||||
|
line += build_condition_string(o)
|
||||||
|
lines = break_long_line(line, indent)
|
||||||
|
buf.extend(lines)
|
||||||
|
|
||||||
|
buf.append("\t\t\t__set_bit(S%i, next);" % o.id)
|
||||||
|
buf.append("\t\tbreak;")
|
||||||
|
buf.extend([
|
||||||
|
"\t}",
|
||||||
|
"}"
|
||||||
|
])
|
||||||
|
|
||||||
|
return buf
|
||||||
|
|
||||||
|
def _fill_start(self):
|
||||||
|
buf = [
|
||||||
|
"static void ltl_start(struct task_struct *task, struct ltl_monitor *mon)",
|
||||||
|
"{"
|
||||||
|
]
|
||||||
|
buf.extend(self._fill_atom_values())
|
||||||
|
buf.append("")
|
||||||
|
|
||||||
|
for node in self.ba:
|
||||||
|
if not node.init:
|
||||||
|
continue
|
||||||
|
|
||||||
|
line = "\tif "
|
||||||
|
indent = "\t "
|
||||||
|
|
||||||
|
line += build_condition_string(node)
|
||||||
|
lines = break_long_line(line, indent)
|
||||||
|
buf.extend(lines)
|
||||||
|
|
||||||
|
buf.append("\t\t__set_bit(S%i, mon->states);" % node.id)
|
||||||
|
buf.append("}")
|
||||||
|
return buf
|
||||||
|
|
||||||
|
def fill_tracepoint_handlers_skel(self):
|
||||||
|
buff = []
|
||||||
|
buff.append("static void handle_example_event(void *data, /* XXX: fill header */)")
|
||||||
|
buff.append("{")
|
||||||
|
buff.append("\tltl_atom_update(task, LTL_%s, true/false);" % self.atoms[0])
|
||||||
|
buff.append("}")
|
||||||
|
buff.append("")
|
||||||
|
return '\n'.join(buff)
|
||||||
|
|
||||||
|
def fill_tracepoint_attach_probe(self):
|
||||||
|
return "\trv_attach_trace_probe(\"%s\", /* XXX: tracepoint */, handle_example_event);" \
|
||||||
|
% self.name
|
||||||
|
|
||||||
|
def fill_tracepoint_detach_helper(self):
|
||||||
|
return "\trv_detach_trace_probe(\"%s\", /* XXX: tracepoint */, handle_sample_event);" \
|
||||||
|
% self.name
|
||||||
|
|
||||||
|
def fill_atoms_init(self):
|
||||||
|
buff = []
|
||||||
|
for a in self.atoms:
|
||||||
|
buff.append("\tltl_atom_set(mon, LTL_%s, true/false);" % a)
|
||||||
|
return '\n'.join(buff)
|
||||||
|
|
||||||
|
def fill_model_h(self):
|
||||||
|
buf = [
|
||||||
|
"/* SPDX-License-Identifier: GPL-2.0 */",
|
||||||
|
"",
|
||||||
|
"/*",
|
||||||
|
" * C implementation of Buchi automaton, automatically generated by",
|
||||||
|
" * tools/verification/rvgen from the linear temporal logic specification.",
|
||||||
|
" * For further information, see kernel documentation:",
|
||||||
|
" * Documentation/trace/rv/linear_temporal_logic.rst",
|
||||||
|
" */",
|
||||||
|
"",
|
||||||
|
"#include <linux/rv.h>",
|
||||||
|
"",
|
||||||
|
"#define MONITOR_NAME " + self.name,
|
||||||
|
""
|
||||||
|
]
|
||||||
|
|
||||||
|
buf.extend(self._fill_atoms())
|
||||||
|
buf.append('')
|
||||||
|
|
||||||
|
buf.extend(self._fill_atoms_to_string())
|
||||||
|
buf.append('')
|
||||||
|
|
||||||
|
buf.extend(self._fill_states())
|
||||||
|
buf.append('')
|
||||||
|
|
||||||
|
buf.extend(self._fill_start())
|
||||||
|
buf.append('')
|
||||||
|
|
||||||
|
buf.extend(self._fill_transitions())
|
||||||
|
buf.append('')
|
||||||
|
|
||||||
|
return '\n'.join(buf)
|
||||||
|
|
||||||
|
def fill_monitor_class_type(self):
|
||||||
|
return "LTL_MON_EVENTS_ID"
|
||||||
|
|
||||||
|
def fill_monitor_class(self):
|
||||||
|
return "ltl_monitor_id"
|
||||||
|
|
||||||
|
def fill_main_c(self):
|
||||||
|
main_c = super().fill_main_c()
|
||||||
|
main_c = main_c.replace("%%ATOMS_INIT%%", self.fill_atoms_init())
|
||||||
|
|
||||||
|
return main_c
|
||||||
102
tools/verification/rvgen/rvgen/templates/ltl2k/main.c
Normal file
102
tools/verification/rvgen/rvgen/templates/ltl2k/main.c
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
#include <linux/ftrace.h>
|
||||||
|
#include <linux/tracepoint.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/rv.h>
|
||||||
|
#include <rv/instrumentation.h>
|
||||||
|
|
||||||
|
#define MODULE_NAME "%%MODEL_NAME%%"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* XXX: include required tracepoint headers, e.g.,
|
||||||
|
* #include <trace/events/sched.h>
|
||||||
|
*/
|
||||||
|
#include <rv_trace.h>
|
||||||
|
%%INCLUDE_PARENT%%
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is the self-generated part of the monitor. Generally, there is no need
|
||||||
|
* to touch this section.
|
||||||
|
*/
|
||||||
|
#include "%%MODEL_NAME%%.h"
|
||||||
|
#include <rv/ltl_monitor.h>
|
||||||
|
|
||||||
|
static void ltl_atoms_fetch(struct task_struct *task, struct ltl_monitor *mon)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This is called everytime the Buchi automaton is triggered.
|
||||||
|
*
|
||||||
|
* This function could be used to fetch the atomic propositions which
|
||||||
|
* are expensive to trace. It is possible only if the atomic proposition
|
||||||
|
* does not need to be updated at precise time.
|
||||||
|
*
|
||||||
|
* It is recommended to use tracepoints and ltl_atom_update() instead.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ltl_atoms_init(struct task_struct *task, struct ltl_monitor *mon, bool task_creation)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* This should initialize as many atomic propositions as possible.
|
||||||
|
*
|
||||||
|
* @task_creation indicates whether the task is being created. This is
|
||||||
|
* false if the task is already running before the monitor is enabled.
|
||||||
|
*/
|
||||||
|
%%ATOMS_INIT%%
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is the instrumentation part of the monitor.
|
||||||
|
*
|
||||||
|
* This is the section where manual work is required. Here the kernel events
|
||||||
|
* are translated into model's event.
|
||||||
|
*/
|
||||||
|
%%TRACEPOINT_HANDLERS_SKEL%%
|
||||||
|
static int enable_%%MODEL_NAME%%(void)
|
||||||
|
{
|
||||||
|
int retval;
|
||||||
|
|
||||||
|
retval = ltl_monitor_init();
|
||||||
|
if (retval)
|
||||||
|
return retval;
|
||||||
|
|
||||||
|
%%TRACEPOINT_ATTACH%%
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void disable_%%MODEL_NAME%%(void)
|
||||||
|
{
|
||||||
|
%%TRACEPOINT_DETACH%%
|
||||||
|
|
||||||
|
ltl_monitor_destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is the monitor register section.
|
||||||
|
*/
|
||||||
|
static struct rv_monitor rv_%%MODEL_NAME%% = {
|
||||||
|
.name = "%%MODEL_NAME%%",
|
||||||
|
.description = "%%DESCRIPTION%%",
|
||||||
|
.enable = enable_%%MODEL_NAME%%,
|
||||||
|
.disable = disable_%%MODEL_NAME%%,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init register_%%MODEL_NAME%%(void)
|
||||||
|
{
|
||||||
|
return rv_register_monitor(&rv_%%MODEL_NAME%%, %%PARENT%%);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __exit unregister_%%MODEL_NAME%%(void)
|
||||||
|
{
|
||||||
|
rv_unregister_monitor(&rv_%%MODEL_NAME%%);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(register_%%MODEL_NAME%%);
|
||||||
|
module_exit(unregister_%%MODEL_NAME%%);
|
||||||
|
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_AUTHOR(/* TODO */);
|
||||||
|
MODULE_DESCRIPTION("%%MODEL_NAME%%: %%DESCRIPTION%%");
|
||||||
14
tools/verification/rvgen/rvgen/templates/ltl2k/trace.h
Normal file
14
tools/verification/rvgen/rvgen/templates/ltl2k/trace.h
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Snippet to be included in rv_trace.h
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef CONFIG_RV_MON_%%MODEL_NAME_UP%%
|
||||||
|
DEFINE_EVENT(event_%%MONITOR_CLASS%%, event_%%MODEL_NAME%%,
|
||||||
|
TP_PROTO(struct task_struct *task, char *states, char *atoms, char *next),
|
||||||
|
TP_ARGS(task, states, atoms, next));
|
||||||
|
DEFINE_EVENT(error_%%MONITOR_CLASS%%, error_%%MODEL_NAME%%,
|
||||||
|
TP_PROTO(struct task_struct *task),
|
||||||
|
TP_ARGS(task));
|
||||||
|
#endif /* CONFIG_RV_MON_%%MODEL_NAME_UP%% */
|
||||||
Loading…
Reference in New Issue
Block a user