def __init__(self, key_domain: Type[KeyWrapper], precursory: FularaState, scalar_vars: Set[VariableIdentifier] = None, map_vars: Set[VariableIdentifier] = None, k_pre_k_conv: Callable[[KeyWrapper], KeyWrapper] = lambda x: x): """ :param key_domain: domain for abstraction of dictionary keys, ranges over the scalar variables and the special key variable v_k, should support backward assignments with _substitute :param precursory: Forward analysis (Fulara analysis) result above the current statement :param scalar_vars: list of scalar variables, whose liveness should be abstracted :param map_vars: list of map variables, whose usage should be abstracted :param k_pre_k_conv: Conversion function to convert from key domain elements of the precursory analysis to key domain elements of this analysis (if the domains differ)""" arguments = { 'key_domain': key_domain, 'scalar_vars': scalar_vars, 'map_vars': map_vars } super().__init__(FularaUsageLattice, arguments) # Stack State.__init__(self, precursory) # self._s_vars = scalar_vars self._k_pre_k_conv = k_pre_k_conv
def range_call_semantics(self, stmt: Call, state: State) -> State: result = set() if len(stmt.arguments) == 1: start = Literal(IntegerLyraType(), "0") stops = self.semantics(stmt.arguments[0], state).result step = Literal(IntegerLyraType(), "1") for stop in stops: range = Range(stmt.typ, start, stop, step) result.add(range) state.result = result return state elif len(stmt.arguments) == 2: starts = self.semantics(stmt.arguments[0], state).result stops = self.semantics(stmt.arguments[1], state).result step = Literal(IntegerLyraType(), "1") for start in starts: for stop in stops: range = Range(stmt.typ, start, stop, step) result.add(range) state.result = result return state elif len(stmt.arguments) == 3: starts = self.semantics(stmt.arguments[0], state).result stops = self.semantics(stmt.arguments[1], state).result steps = self.semantics(stmt.arguments[2], state).result for start in starts: for stop in stops: for step in steps: range = Range(stmt.typ, start, stop, step) result.add(range) state.result = result return state error = f"Call to {stmt.name} with unexpected number of arguments!" raise ValueError(error)
def __init__(self, variables: Set[VariableIdentifier], precursory: State = None): """Map each program variable to the interval representing its value. :param variables: set of program variables """ lattices = defaultdict(lambda: IntervalLattice) super().__init__(variables, lattices) State.__init__(self, precursory)
def __init__(self, variables: Set[VariableIdentifier], precursory: State = None): """Map each program variable to its liveness status. :param variables: set of program variables """ lattices = defaultdict(lambda: LivenessLattice) super().__init__(variables, lattices) State.__init__(self, precursory)
def list_call_semantics(self, stmt: Call, state: State) -> State: """Semantics of a call to 'list'. :param stmt: call to 'list' to be executed :param state: state before executing the call statement :return: state modified by the call statement """ def do(variable): if isinstance(variable.typ, StringLyraType): typ = ListLyraType(variable.typ) state.result = {VariableIdentifier(typ, variable.name)} return state elif isinstance(variable.typ, ListLyraType): state.result = {variable} return state elif isinstance(variable.typ, TupleLyraType): raise NotImplementedError(f"Conversion to list of {variable.typ} is not yet implemented!") elif isinstance(variable.typ, SetLyraType): typ = ListLyraType(variable.typ.typ) state.result = {VariableIdentifier(typ, variable.name)} return state elif isinstance(variable.typ, DictLyraType): typ = ListLyraType(variable.typ.key_typ) state.result = {VariableIdentifier(typ, variable.name)} return state raise TypeError(f"Unexpected type {variable.typ} for list conversion!") if not stmt.arguments: state.result = {ListDisplay(stmt.typ, list())} return state assert len(stmt.arguments) == 1 # exactly one argument is expected argument = stmt.arguments[0] if isinstance(argument, VariableAccess): return do(argument.variable) # elif isinstance(argument, SubscriptionAccess): # target = argument.target # if isinstance(target, VariableAccess): # return do(target.variable) elif isinstance(argument, Call): if isinstance(argument.typ, StringLyraType): typ = ListLyraType(argument.typ) call = Call(argument.pp, argument.name, argument.arguments, typ) state.result = self.semantics(call, state).result return state elif isinstance(argument.typ, ListLyraType): state.result = self.semantics(argument, state).result return state elif isinstance(argument.typ, SetLyraType): typ = ListLyraType(argument.typ.typ) call = Call(argument.pp, argument.name, argument.arguments, typ) state.result = self.semantics(call, state).result return state elif isinstance(argument.typ, DictLyraType): typ = ListLyraType(argument.typ.key_typ) call = Call(argument.pp, argument.name, argument.arguments, typ) state.result = self.semantics(call, state).result return state raise NotImplementedError(f"Semantics for {stmt} is not yet implemented!")
def tuple_call_semantics(self, stmt: Call, state: State, interpreter: Interpreter) -> State: """Semantics of a call to 'tuple'. :param stmt: call to 'tuple' to be executed :param state: state before executing the call statement :return: state modified by the call statement """ if not stmt.arguments: state.result = {TupleDisplay(stmt.typ, list())} return state assert len(stmt.arguments) == 1 # exactly one argument is expected argument = self.semantics(stmt.arguments[0], state, interpreter).result result = set() for expression in argument: if isinstance(expression.typ, StringLyraType): if isinstance(expression, Literal): typs = [ deepcopy(expression.typ) for _ in range(len(expression.val)) ] typ = TupleLyraType(typs) result.add(CastOperation(typ, expression)) else: error = f"Cast to tuple of {expression} is not yet implemented!" raise NotImplementedError(error) elif isinstance(expression.typ, (ListLyraType, SetLyraType)): if isinstance(expression, ListDisplay): typs = [ deepcopy(expression.typ.typ) for _ in range(len(expression.items)) ] typ = TupleLyraType(typs) result.add(CastOperation(typ, expression)) else: error = f"Cast to tuple of {expression} is not yet implemented!" raise NotImplementedError(error) elif isinstance(expression.typ, TupleLyraType): result.add(expression) elif isinstance(expression.typ, DictLyraType): if isinstance(expression, DictDisplay): typs = [ deepcopy(expression.typ.key_typ) for _ in range(len(expression.keys)) ] typ = TupleLyraType(typs) result.add(CastOperation(typ, expression)) else: error = f"Cast to tuple of {expression} is not yet implemented!" raise NotImplementedError(error) else: error = f"Cast to list of expression {expression} with unexpected type!" raise ValueError(error) state.result = result return state
def user_defined_call_semantics(self, stmt: Call, state: State, interpreter: Interpreter): """Forward semantics of a user-defined function/method call. :param stmt: call statement to be executed :param state: state before executing the call statement :return: state modified by the call statement """ fname, fcfg = stmt.name, interpreter.cfgs[stmt.name] # add formal function parameters to the state and assign their actual values formal_args = interpreter.fargs[fname] for formal, actual in zip(formal_args, stmt.arguments): rhs = self.semantics(actual, state, interpreter).result state = state.add_variable(formal).forget_variable(formal) state = state.assign({formal}, rhs) if isinstance(actual, Call) and actual.name in interpreter.cfgs: _ret = VariableIdentifier(formal.typ, '{}#return'.format(actual.name)) state = state.remove_variable(_ret) # add local function variables to the state _formal_args = set() for formal in formal_args: _formal_args.add(formal) # if isinstance(formal.typ, (SequenceLyraType, ContainerLyraType)): # _formal_args.add(LengthIdentifier(formal)) # if isinstance(formal.typ, DictLyraType): # _formal_args.add(KeysIdentifier(formal)) # _formal_args.add(ValuesIdentifier(formal)) local_vars = set(fcfg.variables).difference(_formal_args) for local in local_vars: state = state.add_variable(local).forget_variable(local) fresult = interpreter.analyze(fcfg, state) # analyze the function fstate = fresult.get_node_result(fcfg.out_node)[state][-1] state = state.bottom().join(deepcopy(fstate)) # assign return variable if state.result: ret = VariableIdentifier(stmt.typ, '{}#return'.format(fname)) state = state.add_variable(ret).forget_variable(ret) state = state.assign({ret}, state.result) state.result = {ret} # remove local function variables and formal function parameters for local in local_vars: if not local.special: state = state.remove_variable(local) for formal in formal_args: state = state.remove_variable(formal) return state
def subscription_access_semantics(self, stmt: SubscriptionAccess, state: State) -> State: """Semantics of a subscription access. :param stmt: subscription access statement to be executed :param state: state before executing the subscription access :return: state modified by the subscription access """ target = self.semantics(stmt.target, state).result key = self.semantics(stmt.key, state).result result = set() for primary, index in itertools.product(target, key): if isinstance(primary.typ, StringLyraType): subscription = Subscription(primary.typ, primary, index) result.add(subscription) elif isinstance(primary.typ, (ListLyraType, SetLyraType)): subscription = Subscription(primary.typ.typ, primary, index) result.add(subscription) elif isinstance(primary.typ, DictLyraType): subscription = Subscription(primary.typ.key_typ, primary, index) result.add(subscription) else: error = f"Semantics for subscription of {primary} is not yet implemented!" raise NotImplementedError(error) state.result = result return state
def _binary_operation(self, stmt: Call, operator: BinaryOperation.Operator, state: State): """Semantics of a call to a binary operation. :param stmt: call to binary operation to be executed :param operator: binary operator :param state: state before executing the call statements :return: state modified by the call statement """ assert len(stmt.arguments ) == 2 # binary operations have exactly two arguments argument1 = self.semantics(stmt.arguments[0], state).result argument2 = self.semantics(stmt.arguments[1], state).result result = set() if isinstance(operator, BinaryArithmeticOperation.Operator): for left, right in itertools.product(argument1, argument2): operation = BinaryArithmeticOperation(stmt.typ, left, operator, right) result.add(operation) elif isinstance(operator, BinaryComparisonOperation.Operator): for left, right in itertools.product(argument1, argument2): operation = BinaryComparisonOperation(stmt.typ, left, operator, right) result.add(operation) elif isinstance(operator, BinaryBooleanOperation.Operator): for left, right in itertools.product(argument1, argument2): operation = BinaryBooleanOperation(stmt.typ, left, operator, right) result.add(operation) else: error = f"Semantics for binary operator {operator} is not yet implemented!" raise NotImplementedError(error) state.result = result return state
def dict_display_access_semantics(self, stmt: DictDisplayAccess, state: State) -> State: """Semantics of a list display access. :param stmt: dictionary display access statement to be executed :param state: state before executing the dictionary display access :return: state modified by the dictionary display access """ k_exprs = [self.semantics(k, state).result for k in stmt.keys] # List[Set[Expression]] v_exprs = [self.semantics(v, state).result for v in stmt.values] result = set() if k_exprs: # not empty # One "Set" of Tuples of possible key-value pairs per actual k-v-pair k_v_tuples = map(itertools.product, k_exprs, v_exprs) k_typ = next(iter(k_exprs[0]) ).typ # Is there a better way to retrieve the types? v_typ = next(iter(v_exprs[0])).typ for combination in itertools.product(*k_v_tuples): unzip = list( zip(*combination )) # to create two separate lists for keys and values display = DictDisplay(DictLyraType(k_typ, v_typ), list(unzip[0]), list(unzip[1])) result.add(display) else: # empty dict of generic type TODO: way to get specific type? result.add(DictDisplay(DictLyraType(LyraType(), LyraType()))) state.result = result return state
def _cast_call_semantics(self, stmt: Call, state: State, typ: LyraType) -> State: """Semantics of a call to 'int' or 'bool'. :param stmt: call to 'int' or 'bool' to be executed :param state: state before executing the call statement :return: state modified by the call statement """ if len(stmt.arguments) != 1: error = f"Semantics for multiple arguments of {stmt.name} is not yet implemented!" raise NotImplementedError(error) argument = self.semantics(stmt.arguments[0], state).result result = set() for expression in argument: if isinstance(expression, Input): result.add(Input(typ)) elif isinstance(expression, Literal): result.add(Literal(typ, expression.val)) elif isinstance(expression, VariableIdentifier): result.add(VariableIdentifier(typ, expression.name)) elif isinstance(expression, Subscription): pass # TODO else: error = f"Argument of type {expression.typ} of {stmt.name} is not yet supported!" raise NotImplementedError(error) state.result = result return state
def strip_call_semantics(self, stmt: Call, state: State, interpreter: Interpreter) -> State: if len(stmt.arguments) != 1: error = f"Semantics for multiple arguments of {stmt.name} is not yet implemented!" raise NotImplementedError(error) argument = self.semantics(stmt.arguments[0], state, interpreter).result result = set() for arg in argument: assert isinstance(arg, Expression) if not isinstance(arg.typ, StringLyraType): error = f"Call to {stmt.name} of argument with unexpected type!" raise ValueError(error) typ = StringLyraType() if isinstance(arg, Input): # input().strip() result.add(Input(typ)) continue elif isinstance(arg, VariableIdentifier): # x.strip() result.add(VariableIdentifier(typ, arg.name)) continue elif isinstance(arg, Subscription): # x[i].strip() result.add(Subscription(typ, arg.target, arg.key)) continue error = f"Call to {stmt.name} of unexpected argument {arg}!" raise ValueError(error) state.result = result return state
def split_call_semantics(self, stmt: Call, state: State) -> State: if len(stmt.arguments) != 1: error = f"Semantics for multiple arguments of {stmt.name} is not yet implemented!" raise NotImplementedError(error) argument = self.semantics(stmt.arguments[0], state).result result = set() for arg in argument: assert isinstance(arg, Expression) if not isinstance(arg.typ, StringLyraType): error = f"Call to {stmt.name} of argument with unexpected type!" raise ValueError(error) typ = ListLyraType(StringLyraType()) if isinstance(arg, Literal): # "a b c".split() -> ["a", "b", "c"] items = [ Literal(StringLyraType(), val) for val in arg.val.split() ] result.add(ListDisplay(typ, items)) continue elif isinstance(arg, VariableIdentifier): # x.split() result.add(VariableIdentifier(typ, arg.name)) continue elif isinstance(arg, Input): # input().split() result.add(Input(typ)) continue error = f"Call to {stmt.name} of unexpected argument!" raise ValueError(error) state.result = result return state
def _unary_operation(self, stmt: Call, operator: UnaryOperation.Operator, state: State): """Semantics of a call to a unary operation. :param stmt: call to unary operation to be executed :param operator: unary operator :param state: state before executing the call statements :return: state modified by the call statement """ assert len( stmt.arguments) == 1 # unary operations have exactly one argument argument = self.semantics(stmt.arguments[0], state).result result = set() if isinstance(operator, UnaryArithmeticOperation.Operator): for expression in argument: operation = UnaryArithmeticOperation(stmt.typ, operator, expression) result.add(operation) elif isinstance(operator, UnaryBooleanOperation.Operator): for expression in argument: operation = UnaryBooleanOperation(stmt.typ, operator, expression) result.add(operation) else: error = f"Semantics for unary operation {operator} is not yet implemented!" raise NotImplementedError(error) state.result = result return state
def raise_semantics(self, stmt: Raise, state: State) -> State: """Semantics of raising an Error. :param stmt: raise statement to be executed :param state: state before executing the raise Error :return: state modified by the raise """ return state.raise_error()
def raise_semantics(self, _, state: State, interpreter: Interpreter) -> State: """Semantics of raising an Error. :param _: raise statement to be executed :param state: state before executing the raise Error :return: state modified by the raise """ return state.raise_error()
def input_call_semantics(self, stmt: Call, state: State) -> State: """Semantics of a calls to 'input'. :param stmt: call to 'input' to be executed :param state: state before executing the call statement :return: state modified by the call statement """ state.result = {Input(stmt.typ)} return state
def literal_evaluation_semantics(self, stmt: LiteralEvaluation, state: State) -> State: """Semantics of a literal evaluation. :param stmt: literal evaluation statement to be executed :param state: state before executing the literal evaluation :return: stated modified by the literal evaluation """ state.result = {stmt.literal} return state
def variable_access_semantics(self, stmt: VariableAccess, state: State) -> State: """Semantics of a variable access. :param stmt: variable access statement to be executed :param state: state before executing the variable access :return: state modified by the variable access """ state.result = {stmt.variable} return state
def assignment_semantics(self, stmt: Assignment, state: State) -> State: """Backward semantics of an assignment. :param stmt: assignment statement to be executed :param state: state before executing the assignment :return: state modified by the assignment """ lhs = self.semantics(stmt.left, state).result # lhs evaluation rhs = self.semantics(stmt.right, state).result # rhs evaluation return state.substitute(lhs, rhs)
def update_call_semantics(self, stmt: Call, state: State, interpreter: Interpreter) -> State: assert len(stmt.arguments) == 2 targets = self.semantics(stmt.arguments[0], state, interpreter).result op = BinarySequenceOperation.Operator.Concat values = self.semantics(stmt.arguments[1], state, interpreter).result rhs = set() for target in targets: for value in values: display = SetDisplay(SetLyraType(value.typ), [value]) rhs.add(BinarySequenceOperation(target.typ, target, op, display)) return state.substitute(targets, rhs)
def dict_call_semantics(self, stmt: Call, state: State) -> State: """Semantics of a call to 'dict'. :param stmt: call to 'dict' to be executed :param state: state before executing the call statement :return: state modified by the call statement """ if not stmt.arguments: state.result = {SetDisplay(stmt.typ, list())} return state raise NotImplementedError(f"Semantics for {stmt} is not yet implemented!")
def print_call_semantics(self, stmt: Call, state: State) -> State: """Semantics of a call to 'print'. :param stmt: call to 'print' to be executed :param state: state before executing the call statement :return: state modified by the call statement """ if len(stmt.arguments) != 1: error = f"Semantics for multiple arguments of {stmt.name} is not yet implemented!" raise NotImplementedError(error) argument = self.semantics(stmt.arguments[0], state).result return state.output(argument)
def append_call_semantics(self, stmt: Call, state: State) -> State: assert len(stmt.arguments) == 2 targets = self.semantics(stmt.arguments[0], state).result op = BinarySequenceOperation.Operator.Concat values = self.semantics(stmt.arguments[1], state).result rhs = set() for target in targets: for value in values: display = ListDisplay(ListLyraType(value.typ), [value]) rhs.add( BinarySequenceOperation(target.typ, target, op, display)) return state.assign(targets, rhs)
def __init__(self, key_domain: Type[KeyWrapper], scalar_vars: Set[VariableIdentifier] = None, dict_vars: Set[VariableIdentifier] = None, k_k_pre_conv: Callable[[KeyWrapper], KeyWrapper] = lambda x: x, k_pre_k_conv: Callable[[KeyWrapper], KeyWrapper] = lambda x: x, precursory: FularaState = None): arguments = { 'key_domain': key_domain, 'scalar_vars': scalar_vars, 'dict_vars': dict_vars } super().__init__(FularaUsageLattice, arguments) # Stack State.__init__(self, precursory) # self._s_vars = scalar_vars self._k_k_pre_conv = k_k_pre_conv self._k_pre_k_conv = k_pre_k_conv self._loop_flag = False
def set_call_semantics(self, stmt: Call, state: State, interpreter: Interpreter) -> State: """Semantics of a call to 'set'. :param stmt: call to 'set' to be executed :param state: state before executing the call statement :return: state modified by the call statement """ if not stmt.arguments: state.result = {SetDisplay(stmt.typ, list())} return state assert len(stmt.arguments) == 1 # exactly one argument is expected argument = self.semantics(stmt.arguments[0], state, interpreter).result result = set() for expression in argument: if isinstance(expression.typ, StringLyraType): typ = SetLyraType(expression.typ) result.add(CastOperation(typ, expression)) elif isinstance(expression.typ, ListLyraType): typ = SetLyraType(expression.typ.typ) result.add(CastOperation(typ, expression)) elif isinstance(expression.typ, TupleLyraType): if all(typ == expression.typ.typs[0] for typ in expression.typ.typs): typ = SetLyraType(expression.typ.typs[0]) result.add(CastOperation(typ, expression)) else: error = f"Cast to list of {expression} is not yet implemented!" raise NotImplementedError(error) elif isinstance(expression.typ, SetLyraType): result.add(expression) elif isinstance(expression.typ, DictLyraType): typ = SetLyraType(expression.typ.key_typ) result.add(CastOperation(typ, expression)) else: error = f"Cast to list of expression {expression} with unexpected type!" raise ValueError(error) state.result = result return state
def user_defined_call_semantics(self, stmt: Call, state: State, interpreter: Interpreter): """Backward semantics of a user-defined function/method call. :param stmt: call statement to be executed :param state: state before executing the call statement :return: state modified by the call statement """ fname, fcfg, _ = stmt.name, interpreter.cfgs[stmt.name], deepcopy(state) # analyze the function fresult = interpreter.analyze(fcfg, state) fstate = fresult.get_node_result(fcfg.in_node)[state][-1] state = state.bottom().join(deepcopy(fstate)) # substitute function actual to formal parameters for formal, actual in zip(interpreter.fargs[fname], stmt.arguments): if isinstance(actual, Call) and actual.name in interpreter.cfgs: # TODO: right might not be a Call but just contain a Call state.result = {formal} state = self.semantics(actual, state, interpreter) else: rhs = self.semantics(actual, state, interpreter).result state = state.substitute({formal}, rhs) return state
def return_semantics(self, stmt: Return, state: State, interpreter: Interpreter): """Backward semantics of an return statement. :param stmt: return statement to be executed :param state: state before executing the return statement :return: state modified by the return statement """ if len(stmt.values) != 1: error = f"Semantics for multiple arguments of {stmt} is not yet implemented!" raise NotImplementedError(error) lhs = state.result rhs = self.semantics(stmt.values[0], state, interpreter).result return state.substitute(lhs, rhs)
def bitxor_call_semantics(self, stmt: Call, state: State, interpreter: Interpreter) -> State: """Semantics of calls to '^' (bitwise xor). :param stmt: call to '^' (bitwise xor) to be executed :param state: state before executing the call statement :return: state modified by the call statement """ argument = self.semantics(stmt.arguments[0], state, interpreter).result result = set() for arg in argument: result.add(VariableIdentifier(stmt.typ, arg.name)) state.result = result return state
def values_call_semantics(self, stmt: Call, state: State) -> State: """Semantics of calls to 'values'. :param stmt: call to 'values' to be executed :param state: state before executing the call statement :return: state modified by the call statement """ if isinstance(stmt.arguments[0], VariableAccess): # target state.result = {Values(stmt.typ, stmt.arguments[0].variable)} else: error = f"Semantics for values() call on non-variable {stmt.arguments[0]} is not yet "\ f"implemented!" raise NotImplementedError(error) return state