diff --git a/include/utap/common.h b/include/utap/common.h index 1341e605..5954808b 100644 --- a/include/utap/common.h +++ b/include/utap/common.h @@ -205,7 +205,7 @@ enum kind_t { VAR_INDEX, IDENTIFIER, CONSTANT, - ARRAY, + SUBSCRIPT, POST_INCREMENT, PRE_INCREMENT, POST_DECREMENT, @@ -231,6 +231,7 @@ enum kind_t { STRING, BOOL, SCALAR, + ARRAY, LOCATION, LOCATION_EXPR, CHANNEL, diff --git a/include/utap/document.h b/include/utap/document.h index 06ad2fcb..adb56535 100644 --- a/include/utap/document.h +++ b/include/utap/document.h @@ -128,12 +128,21 @@ class BlockStatement; // Forward declaration */ struct function_t : stringify_t { - symbol_t uid; /**< The symbol of the function. */ - std::set changes{}; /**< Variables changed by this function. */ - std::set depends{}; /**< Variables the function depends on. */ - std::list variables{}; /**< Local variables. List is used for stable pointers. */ + symbol_t uid; ///< The symbol of the function. + std::set potential_reads{}; ///< May potentially read from + std::set potential_writes{}; ///< May potentially write to + std::set sure_reads{}; ///< Surely (always) reads from + std::set sure_writes{}; ///< Surely (always) writes to + std::list variables{}; ///< Local variables. List is used for stable pointers. std::unique_ptr body{nullptr}; /**< Pointer to the block. */ function_t() = default; + void remove_access(const symbol_t& symb) + { + potential_reads.erase(symb); + potential_writes.erase(symb); + sure_reads.erase(symb); + sure_writes.erase(symb); + } std::ostream& print(std::ostream& os) const; /**< textual representation, used to write the XML file */ }; diff --git a/include/utap/expression.h b/include/utap/expression.h index 2ad68386..68e7b594 100644 --- a/include/utap/expression.h +++ b/include/utap/expression.h @@ -72,6 +72,7 @@ class expression_t struct expression_data; std::shared_ptr data = nullptr; // PIMPL pattern with cheap/shallow copying expression_t(Constants::kind_t, const position_t&); + expression_t(std::unique_ptr data); public: /** Default constructor. Creates an empty expression. */ @@ -169,7 +170,7 @@ class expression_t symbol_t get_symbol(); /** - * Returns the set of symbols this expression might resolve + * Returns the set of potential symbols this expression might resolve * into. In case of inline if, both the 'true' and 'false' * branch is added. In case of dot-expressions, both the left * hand reference and the member field are returned. @@ -178,7 +179,8 @@ class expression_t * (s.f).get_symbol() returns 's,f' * (i<1?j:k).get_symbol() returns 'j,k' */ - void get_symbols(std::set& symbols) const; + void get_potential_symbols(std::set& symbols) const; + void get_sure_symbols(std::set& symbols) const; /** Returns the symbol this expression evaluates to. Notice that not all expression evaluate to a symbol. */ @@ -190,19 +192,24 @@ class expression_t /** Returns true if the expression contains deadlock expression */ bool contains_deadlock() const; - /** True if this expression can change any of the variables - identified by the given symbols. */ - bool changes_variable(const std::set&) const; + /// True if this expression potentially changes any of the symbols. */ + bool potentially_changes(const std::set& symbols) const; - /** True if this expression can change any variable at all. */ - bool changes_any_variable() const; + /// True if this expression can change any variable at all. */ + bool potentially_changes_some() const; - /** True if the evaluation of this expression depends on - any of the symbols in the given set. */ - bool depends_on(const std::set&) const; + /// True if the evaluation of this expression depends on any of the symbols + bool potentially_depends_on(const std::set& symbols) const; - void collect_possible_writes(std::set&) const; - void collect_possible_reads(std::set&, bool collectRandom = false) const; + /// collects symbols that may potentially be read by the expression + void collect_potential_reads(std::set&, bool collectRandom = false) const; + /// collects symbols are to be read by the expression for sure + void collect_sure_reads(std::set&, bool collectRandom = false) const; + + /// collects symbols that may potentially be written by the expression + void collect_potential_writes(std::set&) const; + /// collects symbols that are going to be written by the expression for sure + void collect_sure_writes(std::set&) const; /** Less-than operator. Makes it possible to put expression_t objects into an STL set. */ diff --git a/include/utap/statement.h b/include/utap/statement.h index cabd5173..00bb0404 100644 --- a/include/utap/statement.h +++ b/include/utap/statement.h @@ -143,8 +143,14 @@ class BlockStatement : public Statement, public declarations_t bool returns() override; frame_t get_frame() { return frame; } - void push_stat(std::unique_ptr stat); - std::unique_ptr pop_stat(); + void push(std::unique_ptr stat); + template + void emplace(Args&&... args) + { + static_assert(std::is_base_of_v, "Should implement Statement"); + push(std::make_unique(std::forward(args)...)); + } + std::unique_ptr pop(); Statement* back(); const_iterator begin() const; const_iterator end() const; @@ -283,51 +289,150 @@ class AbstractStatementVisitor : public StatementVisitor class ExpressionVisitor : public AbstractStatementVisitor { protected: - virtual void visitExpression(expression_t) = 0; + virtual void visitExpression(expression_t&) = 0; public: int32_t visitExprStatement(ExprStatement* stat) override; int32_t visitAssertStatement(AssertStatement* stat) override; + int32_t visitIfStatement(IfStatement* stat) override; int32_t visitForStatement(ForStatement* stat) override; int32_t visitWhileStatement(WhileStatement* stat) override; int32_t visitDoWhileStatement(DoWhileStatement* stat) override; - int32_t visitBlockStatement(BlockStatement* stat) override; int32_t visitSwitchStatement(SwitchStatement* stat) override; + int32_t visitBlockStatement(BlockStatement* stat) override; int32_t visitCaseStatement(CaseStatement* stat) override; int32_t visitDefaultStatement(DefaultStatement* stat) override; - int32_t visitIfStatement(IfStatement* stat) override; int32_t visitReturnStatement(ReturnStatement* stat) override; }; -class CollectChangesVisitor : public ExpressionVisitor +/// Collects all symbols that the visited statement may be potentially depend on (for reading) +class PotentialDependencyCollector : public ExpressionVisitor { protected: - void visitExpression(expression_t) override; + void visitExpression(expression_t& expr) override { expr.collect_potential_reads(dependencies); } + std::set& dependencies; + +public: + explicit PotentialDependencyCollector(std::set& deps): dependencies{deps} {} +}; + +inline std::set collect_potential_dependencies(Statement& statement) +{ + auto res = std::set{}; + auto visitor = PotentialDependencyCollector{res}; + statement.accept(&visitor); + return res; +} + +/// Collects all symbols that may be changed (written to) by the visited statement +class PotentialChangeCollector : public ExpressionVisitor +{ +protected: + void visitExpression(expression_t& expr) override { expr.collect_potential_writes(changes); } std::set& changes; public: - explicit CollectChangesVisitor(std::set& changes): changes{changes} {} + explicit PotentialChangeCollector(std::set& changes): changes{changes} {} }; -class CollectDependenciesVisitor : public ExpressionVisitor +inline std::set collect_potential_changes(Statement& statement) +{ + auto res = std::set{}; + auto visitor = PotentialChangeCollector{res}; + statement.accept(&visitor); + return res; +} + +/// Collects symbols that the visited statement surely (always) depend on (for reading) +class SureDependencyCollector : public ExpressionVisitor { protected: - void visitExpression(expression_t) override; + void visitExpression(expression_t& expr) override { expr.collect_sure_reads(dependencies); } std::set& dependencies; public: - explicit CollectDependenciesVisitor(std::set&); + explicit SureDependencyCollector(std::set& deps): dependencies{deps} {} }; -class CollectDynamicExpressions : public ExpressionVisitor +inline std::set collect_sure_dependencies(Statement& statement) +{ + auto res = std::set{}; + auto visitor = SureDependencyCollector{res}; + statement.accept(&visitor); + return res; +} + +/// Collects symbols that are surely (always) changed by the visited statements +class SureChangeCollector : public ExpressionVisitor +{ +protected: + void visitExpression(expression_t& expr) override { expr.collect_sure_writes(changes); } + std::set& changes; + +public: + explicit SureChangeCollector(std::set& changes): changes{changes} {} + int32_t visitIfStatement(IfStatement* stat) override + { + visitExpression(stat->cond); + // TODO: visit trueCase or falseCase if cond known at compile-time + return 0; + } + int32_t visitForStatement(ForStatement* stat) override + { + visitExpression(stat->init); + visitExpression(stat->cond); + // TODO: visit step and block if cond is known at compile-time + return 0; + } + int32_t visitWhileStatement(WhileStatement* stat) override + { + visitExpression(stat->cond); + // TODO: visit step and block if cond is known at compile-time + return 0; + } + int32_t visitDoWhileStatement(DoWhileStatement* stat) override + { + auto res = stat->stat->accept(this); + visitExpression(stat->cond); + return res; + } + int32_t visitSwitchStatement(SwitchStatement* stat) override + { + visitExpression(stat->cond); + // TODO: visit specific case if cond is known at compile-time + return 0; + } +}; + +inline std::set collect_sure_changes(Statement& statement) +{ + auto res = std::set{}; + auto visitor = SureChangeCollector{res}; + statement.accept(&visitor); + return res; +} + +class DynamicExprCollector final : public ExpressionVisitor { protected: - void visitExpression(expression_t) override; - std::list& expressions; + void visitExpression(expression_t& expr) override + { + if (expr.is_dynamic() || expr.has_dynamic_sub()) + expressions.push_back(expr); + } + std::vector& expressions; public: - explicit CollectDynamicExpressions(std::list& expressions): expressions{expressions} {} + explicit DynamicExprCollector(std::vector& expressions): expressions{expressions} {} }; +inline std::vector collect_dynamic_expressions(Statement& statement) +{ + auto res = std::vector{}; + auto visitor = DynamicExprCollector{res}; + statement.accept(&visitor); + return res; +} + } // namespace UTAP #endif /* UTAP_STATEMENT_H */ diff --git a/include/utap/symbols.h b/include/utap/symbols.h index e65b0445..2488d71f 100644 --- a/include/utap/symbols.h +++ b/include/utap/symbols.h @@ -65,7 +65,7 @@ class symbol_t protected: friend class frame_t; - symbol_t(frame_t* frame, type_t type, std::string name, position_t position, void* user); + symbol_t(frame_t* frame, type_t type, std::string name, position_t pos = {}, void* user = nullptr); public: /** Default constructor */ @@ -194,7 +194,7 @@ class frame_t bool empty() const; /** Adds a symbol of the given name and type to the frame */ - symbol_t add_symbol(const std::string& name, type_t, position_t position, void* user = nullptr); + symbol_t add_symbol(const std::string& name, type_t, position_t pos = {}, void* user = nullptr); /** Add all symbols from the given frame */ void add(symbol_t); diff --git a/include/utap/type.h b/include/utap/type.h index 505bc3c6..94f45b4e 100644 --- a/include/utap/type.h +++ b/include/utap/type.h @@ -351,44 +351,43 @@ class type_t * could be anything and it is the responsibility of the * caller to make sure that the given kind is a valid prefix. */ - type_t create_prefix(Constants::kind_t kind, position_t = position_t()) const; + type_t create_prefix(Constants::kind_t kind, position_t = {}) const; /** Creates a LABEL. */ - type_t create_label(std::string, position_t = position_t()) const; + type_t create_label(std::string, position_t = {}) const; /** */ - static type_t create_range(type_t, expression_t, expression_t, position_t = position_t()); + static type_t create_range(type_t, expression_t lower, expression_t upper, position_t = {}); /** Create a primitive type. */ - static type_t create_primitive(Constants::kind_t, position_t = position_t()); + static type_t create_primitive(Constants::kind_t, position_t = {}); /** Creates an array type. */ - static type_t create_array(type_t sub, type_t size, position_t = position_t()); + static type_t create_array(type_t sub, type_t size, position_t = {}); /** Creates a new type definition. */ - static type_t create_typedef(std::string, type_t, position_t = position_t()); + static type_t create_typedef(std::string, type_t, position_t = {}); /** Creates a new process type. */ - static type_t create_process(frame_t, position_t = position_t()); + static type_t create_process(frame_t, position_t = {}); /** Creates a new processset type. */ - static type_t create_process_set(type_t instance, position_t = position_t()); + static type_t create_process_set(type_t instance, position_t = {}); /** Creates a new record type */ - static type_t create_record(const std::vector&, const std::vector&, position_t = position_t()); + static type_t create_record(const std::vector&, const std::vector&, position_t = {}); /** Creates a new function type */ - static type_t create_function(type_t, const std::vector&, const std::vector&, - position_t = position_t()); + static type_t create_function(type_t, const std::vector&, const std::vector&, position_t = {}); static type_t create_external_function(type_t rt, const std::vector&, const std::vector&, - position_t = position_t()); + position_t = {}); /** Creates a new instance type */ - static type_t create_instance(frame_t, position_t = position_t()); + static type_t create_instance(frame_t, position_t = {}); /** Creates a new lsc instance type */ - static type_t create_LSC_instance(frame_t, position_t = position_t()); + static type_t create_LSC_instance(frame_t, position_t = {}); }; } // namespace UTAP diff --git a/src/ExpressionBuilder.cpp b/src/ExpressionBuilder.cpp index e07181a9..b7f63175 100644 --- a/src/ExpressionBuilder.cpp +++ b/src/ExpressionBuilder.cpp @@ -201,7 +201,7 @@ void ExpressionBuilder::type_void() static void collectDependencies(std::set& dependencies, expression_t expr) { std::set symbols; - expr.collect_possible_reads(symbols); + expr.collect_potential_reads(symbols); while (!symbols.empty()) { symbol_t s = *symbols.begin(); symbols.erase(s); @@ -209,7 +209,7 @@ static void collectDependencies(std::set& dependencies, expression_t e dependencies.insert(s); if (auto* data = s.get_data(); data) { variable_t* v = static_cast(data); - v->init.collect_possible_reads(symbols); + v->init.collect_potential_reads(symbols); } } } @@ -370,7 +370,7 @@ void ExpressionBuilder::expr_call_end(uint32_t n) e.set_type(type); for (size_t i = 1; i < expr.size(); i++) { type = type.get_sub(); - e = expression_t::create_binary(ARRAY, e, expr[i], position, type); + e = expression_t::create_binary(SUBSCRIPT, e, expr[i], position, type); } break; @@ -399,7 +399,7 @@ void ExpressionBuilder::expr_array() element = type_t(); } - fragments.push(expression_t::create_binary(ARRAY, var, index, position, element)); + fragments.push(expression_t::create_binary(SUBSCRIPT, var, index, position, element)); } // 1 expr diff --git a/src/StatementBuilder.cpp b/src/StatementBuilder.cpp index 87c80d91..9b295bd5 100644 --- a/src/StatementBuilder.cpp +++ b/src/StatementBuilder.cpp @@ -50,7 +50,7 @@ StatementBuilder::StatementBuilder(Document& doc, std::vector& dependencies, expression_t expr) { std::set symbols; - expr.collect_possible_reads(symbols); + expr.collect_potential_reads(symbols); while (!symbols.empty()) { symbol_t s = *symbols.begin(); symbols.erase(s); @@ -60,7 +60,7 @@ void StatementBuilder::collectDependencies(std::set& dependencies, exp if (auto t = s.get_type(); !(t.is_function() || t.is_function_external())) { // assume is its variable, which is not always true variable_t* v = static_cast(d); - v->init.collect_possible_reads(symbols); + v->init.collect_potential_reads(symbols); } else { // TODO; fixme. } @@ -194,9 +194,8 @@ static bool initialisable(type_t type) return true; case ARRAY: - if (type.get_array_size().is_scalar()) { + if (type.get_array_size().is_scalar()) return false; - } return initialisable(type.get_sub()); case STRING: return true; default: return type.is_integral() || type.is_clock() || type.is_double(); @@ -476,22 +475,19 @@ void StatementBuilder::block_end() // the containing block. std::unique_ptr block = std::move(blocks.back()); blocks.pop_back(); - get_block().push_stat(std::move(block)); + get_block().push(std::move(block)); // Restore containing frame popFrame(); } -void StatementBuilder::empty_statement() { get_block().push_stat(std::make_unique()); } +void StatementBuilder::empty_statement() { get_block().push(std::make_unique()); } void StatementBuilder::for_begin() {} void StatementBuilder::for_end() { // 3 expr, 1 stat - auto substat = get_block().pop_stat(); - auto forstat = std::make_unique(fragments[2], fragments[1], fragments[0], std::move(substat)); - get_block().push_stat(std::move(forstat)); - + get_block().emplace(fragments[2], fragments[1], fragments[0], get_block().pop()); fragments.pop(3); } @@ -518,14 +514,14 @@ void StatementBuilder::iteration_begin(const char* name) * this here as the statement is the only thing that can keep the * reference to the frame. */ - get_block().push_stat(std::make_unique(variable->uid, frames.top(), nullptr)); + get_block().emplace(variable->uid, frames.top(), nullptr); } void StatementBuilder::iteration_end(const char* name) { /* Retrieve the statement that we iterate over. */ - auto statement = get_block().pop_stat(); + auto statement = get_block().pop(); if (!get_block().empty()) { // If the syntax is wrong, we won't have anything in blocks.back() @@ -542,8 +538,8 @@ void StatementBuilder::while_begin() {} void StatementBuilder::while_end() { // 1 expr, 1 stat - auto whilestat = std::make_unique(fragments[0], get_block().pop_stat()); - get_block().push_stat(std::move(whilestat)); + auto whilestat = std::make_unique(fragments[0], get_block().pop()); + get_block().push(std::move(whilestat)); fragments.pop(); } @@ -551,8 +547,8 @@ void StatementBuilder::do_while_begin() {} void StatementBuilder::do_while_end() { // 1 stat, 1 expr - auto substat = get_block().pop_stat(); - get_block().push_stat(std::make_unique(std::move(substat), fragments[0])); + auto substat = get_block().pop(); + get_block().push(std::make_unique(std::move(substat), fragments[0])); fragments.pop(); } @@ -560,16 +556,16 @@ void StatementBuilder::if_end(bool elsePart) { // 1 expr, 1 or 2 statements std::unique_ptr falseCase; if (elsePart) - falseCase = get_block().pop_stat(); - auto trueCase = get_block().pop_stat(); + falseCase = get_block().pop(); + auto trueCase = get_block().pop(); auto ifstat = std::make_unique(fragments[0], std::move(trueCase), std::move(falseCase)); - get_block().push_stat(std::move(ifstat)); + get_block().push(std::move(ifstat)); fragments.pop(); } void StatementBuilder::expr_statement() { // 1 expr - get_block().push_stat(std::make_unique(fragments[0])); + get_block().push(std::make_unique(fragments[0])); fragments.pop(); } @@ -595,13 +591,13 @@ void StatementBuilder::return_statement(bool args) } else { stat = std::make_unique(); } - get_block().push_stat(std::move(stat)); + get_block().push(std::move(stat)); } } void StatementBuilder::assert_statement() { - get_block().push_stat(std::make_unique(fragments[0])); + get_block().push(std::make_unique(fragments[0])); fragments.pop(); } diff --git a/src/expression.cpp b/src/expression.cpp index af2e7499..4a22ab76 100644 --- a/src/expression.cpp +++ b/src/expression.cpp @@ -55,6 +55,66 @@ struct expression_t::expression_data : public std::enable_shared_from_this sub{}; /**< Subexpressions */ expression_data(const position_t& p, kind_t kind, int32_t value): position{p}, kind{kind}, value{value} {} + expression_data(const position_t& p, kind_t kind): position{p}, kind{kind} {} + std::unique_ptr clone() + { + auto res = std::make_unique(position, kind); + res->value = value; + res->symbol = symbol; + res->type = type; + res->sub.reserve(sub.size()); + res->sub.assign(sub.begin(), sub.end()); + return res; + } + std::unique_ptr clone_deeper() + { + auto res = std::make_unique(position, kind); + res->value = value; + res->type = type; + res->symbol = symbol; + if (!sub.empty()) { + res->sub.reserve(sub.size()); + for (const auto& s : sub) + res->sub.push_back(s.clone_deeper()); + } + return res; + } + std::unique_ptr clone_deeper(symbol_t from, symbol_t to) + { + auto res = std::make_unique(position, kind); + res->value = value; + res->type = type; + res->symbol = (symbol == from) ? to : symbol; + if (!sub.empty()) { + res->sub.reserve(sub.size()); + for (const auto& s : sub) + res->sub.push_back(s.clone_deeper(from, to)); + } + return res; + } + std::unique_ptr clone_deeper(frame_t frame, frame_t select) const + { + auto res = std::make_unique(position, kind); + res->value = value; + res->type = type; + auto uid = symbol_t{}; + if (symbol != symbol_t{}) { + bool found = frame.resolve(symbol.get_name(), uid); + if (!found && select != frame_t()) { + found = select.resolve(symbol.get_name(), uid); + } + assert(found); + res->symbol = uid; + } else { + res->symbol = symbol; + } + if (!sub.empty()) { + res->sub.reserve(sub.size()); + for (const auto& s : sub) + res->sub.push_back(s.clone_deeper(frame, select)); + } + return res; + } }; expression_t::expression_t(kind_t kind, const position_t& pos) @@ -62,69 +122,20 @@ expression_t::expression_t(kind_t kind, const position_t& pos) data = std::make_shared(pos, kind, 0); } -expression_t expression_t::clone() const -{ - auto expr = expression_t{data->kind, data->position}; - expr.data->value = data->value; - expr.data->type = data->type; - expr.data->symbol = data->symbol; - expr.data->sub.reserve(data->sub.size()); - expr.data->sub.assign(data->sub.begin(), data->sub.end()); - return expr; -} +expression_t::expression_t(std::unique_ptr data): data{std::move(data)} {} -expression_t expression_t::clone_deeper() const -{ - auto expr = expression_t{data->kind, data->position}; - expr.data->value = data->value; - expr.data->type = data->type; - expr.data->symbol = data->symbol; - if (!data->sub.empty()) { - expr.data->sub.reserve(data->sub.size()); - for (const auto& s : data->sub) - expr.data->sub.push_back(s.clone_deeper()); - } - return expr; -} +expression_t expression_t::clone() const { return expression_t{data->clone()}; } + +expression_t expression_t::clone_deeper() const { return expression_t{data->clone_deeper()}; } expression_t expression_t::clone_deeper(symbol_t from, symbol_t to) const { - auto expr = expression_t{data->kind, data->position}; - expr.data->value = data->value; - expr.data->type = data->type; - expr.data->symbol = (data->symbol == from) ? to : data->symbol; - - if (!data->sub.empty()) { - expr.data->sub.reserve(data->sub.size()); - for (const auto& s : data->sub) - expr.data->sub.push_back(s.clone_deeper(from, to)); - } - return expr; + return expression_t{data->clone_deeper(from, to)}; } expression_t expression_t::clone_deeper(frame_t frame, frame_t select) const { - auto expr = expression_t{data->kind, data->position}; - expr.data->value = data->value; - expr.data->type = data->type; - symbol_t uid; - if (data->symbol != symbol_t()) { - bool res = frame.resolve(data->symbol.get_name(), uid); - if (!res && select != frame_t()) { - res = select.resolve(data->symbol.get_name(), uid); - } - assert(res); - expr.data->symbol = uid; - } else { - expr.data->symbol = data->symbol; - } - - if (!data->sub.empty()) { - expr.data->sub.reserve(data->sub.size()); - for (const auto& s : data->sub) - expr.data->sub.push_back(s.clone_deeper(frame, select)); - } - return expr; + return expression_t{data->clone_deeper(frame, select)}; } expression_t expression_t::subst(symbol_t symbol, expression_t expr) const @@ -338,7 +349,7 @@ size_t expression_t::get_size() const case GT: case MIN: case MAX: - case ARRAY: + case SUBSCRIPT: case COMMA: case ASSIGN: case ASS_PLUS: @@ -646,7 +657,7 @@ const symbol_t expression_t::get_symbol() const case DOT: return get(0).get_symbol(); - case ARRAY: return get(0).get_symbol(); + case SUBSCRIPT: return get(0).get_symbol(); case PRE_INCREMENT: case PRE_DECREMENT: return get(0).get_symbol(); @@ -678,29 +689,61 @@ const symbol_t expression_t::get_symbol() const } } -void expression_t::get_symbols(std::set& symbols) const +void expression_t::get_potential_symbols(std::set& symbols) const { - if (empty()) { + if (empty()) return; - } switch (get_kind()) { case IDENTIFIER: symbols.insert(data->symbol); break; - case DOT: get(0).get_symbols(symbols); break; + case DOT: get(0).get_potential_symbols(symbols); break; - case ARRAY: get(0).get_symbols(symbols); break; + case SUBSCRIPT: get(0).get_potential_symbols(symbols); break; case PRE_INCREMENT: - case PRE_DECREMENT: get(0).get_symbols(symbols); break; + case PRE_DECREMENT: get(0).get_potential_symbols(symbols); break; case INLINE_IF: - get(1).get_symbols(symbols); - get(2).get_symbols(symbols); + get(1).get_potential_symbols(symbols); + get(2).get_potential_symbols(symbols); + break; + + case COMMA: get(1).get_potential_symbols(symbols); break; + + case ASSIGN: + case ASS_PLUS: + case ASS_MINUS: + case ASS_DIV: + case ASS_MOD: + case ASS_MULT: + case ASS_AND: + case ASS_OR: + case ASS_XOR: + case ASS_LSHIFT: + case ASS_RSHIFT: get(0).get_potential_symbols(symbols); break; + + case SYNC: get(0).get_potential_symbols(symbols); break; + + default: + // Do nothing break; + } +} + +void expression_t::get_sure_symbols(std::set& symbols) const +{ + if (empty()) + return; - case COMMA: get(1).get_symbols(symbols); break; + switch (get_kind()) { + case IDENTIFIER: symbols.insert(data->symbol); break; + case DOT: + case PRE_INCREMENT: + case PRE_DECREMENT: + case POST_INCREMENT: + case POST_DECREMENT: case ASSIGN: case ASS_PLUS: case ASS_MINUS: @@ -711,9 +754,25 @@ void expression_t::get_symbols(std::set& symbols) const case ASS_OR: case ASS_XOR: case ASS_LSHIFT: - case ASS_RSHIFT: get(0).get_symbols(symbols); break; + case ASS_RSHIFT: get(0).get_sure_symbols(symbols); break; + + case COMMA: + get(0).get_sure_symbols(symbols); + get(1).get_potential_symbols(symbols); + break; - case SYNC: get(0).get_symbols(symbols); break; + case SYNC: get(0).get_potential_symbols(symbols); break; + + case SUBSCRIPT: + // TODO: add if the index is compile-time computable + // get(0).get_sure_symbols(symbols); + break; + + case INLINE_IF: + // TODO: add if condition is compile-time computable + // get(1).get_sure_symbols(symbols); + // get(2).get_sure_symbols(symbols); + break; default: // Do nothing @@ -721,12 +780,11 @@ void expression_t::get_symbols(std::set& symbols) const } } -/** Returns true if expr might be a reference to a symbol in the - set. */ +/// Returns true if expr might be a reference to a symbol in the set. bool expression_t::is_reference_to(const std::set& symbols) const { std::set s; - get_symbols(s); + get_potential_symbols(s); return find_first_of(symbols.begin(), symbols.end(), s.begin(), s.end()) != symbols.end(); } @@ -741,24 +799,24 @@ bool expression_t::contains_deadlock() const return false; } -bool expression_t::changes_variable(const std::set& symbols) const +bool expression_t::potentially_changes(const std::set& symbols) const { auto changes = std::set{}; - collect_possible_writes(changes); + collect_potential_writes(changes); return find_first_of(symbols.begin(), symbols.end(), changes.begin(), changes.end()) != symbols.end(); } -bool expression_t::changes_any_variable() const +bool expression_t::potentially_changes_some() const { auto changes = std::set{}; - collect_possible_writes(changes); + collect_potential_writes(changes); return !changes.empty(); } -bool expression_t::depends_on(const std::set& symbols) const +bool expression_t::potentially_depends_on(const std::set& symbols) const { - std::set dependencies; - collect_possible_reads(dependencies); + auto dependencies = std::set{}; + collect_potential_reads(dependencies); return find_first_of(symbols.begin(), symbols.end(), dependencies.begin(), dependencies.end()) != symbols.end(); } @@ -809,7 +867,7 @@ int expression_t::get_precedence(kind_t kind) case FUN_CALL: case FUN_CALL_EXT: return 110; - case ARRAY: return 105; + case SUBSCRIPT: return 105; case DOT: case RATE: return 100; @@ -1081,7 +1139,7 @@ static const char* get_builtin_fun_name(kind_t kind) static inline std::ostream& embrace_strict(std::ostream& os, bool old, const expression_t& expr, int precedence) { - if (precedence > expr.get_precedence()) + if (expr.get_kind() == INLINE_IF or precedence > expr.get_precedence()) return expr.print(os << '(', old) << ')'; else return expr.print(os, old); @@ -1255,10 +1313,10 @@ std::ostream& expression_t::print(std::ostream& os, bool old) const break; case IDENTIFIER: os << data->symbol.get_name(); break; + case ARRAY: os << data->symbol.get_name(); break; case VAR_INDEX: case CONSTANT: - if (get_type().is(Constants::DOUBLE)) { os << get_double_value(); } else if (get_type().is_string()) { @@ -1271,7 +1329,7 @@ std::ostream& expression_t::print(std::ostream& os, bool old) const } break; - case ARRAY: + case SUBSCRIPT: embrace_strict(os, old, get(0), precedence); get(1).print(os << '[', old) << ']'; break; @@ -1730,7 +1788,7 @@ std::string expression_t::str(bool old) const return os.str(); } -void expression_t::collect_possible_writes(set& symbols) const +void expression_t::collect_potential_writes(set& symbols) const { function_t* fun; symbol_t symbol; @@ -1740,7 +1798,7 @@ void expression_t::collect_possible_writes(set& symbols) const return; for (uint32_t i = 0; i < get_size(); i++) { - get(i).collect_possible_writes(symbols); + get(i).collect_potential_writes(symbols); } switch (get_kind()) { @@ -1758,7 +1816,7 @@ void expression_t::collect_possible_writes(set& symbols) const case POST_INCREMENT: case POST_DECREMENT: case PRE_INCREMENT: - case PRE_DECREMENT: get(0).get_symbols(symbols); break; + case PRE_DECREMENT: get(0).get_potential_symbols(symbols); break; case FUN_CALL: case FUN_CALL_EXT: @@ -1766,14 +1824,13 @@ void expression_t::collect_possible_writes(set& symbols) const symbol = get(0).get_symbol(); if ((symbol.get_type().is_function() || symbol.get_type().is_function_external()) && symbol.get_data()) { fun = (function_t*)symbol.get_data(); - - symbols.insert(fun->changes.begin(), fun->changes.end()); + symbols.insert(fun->potential_writes.begin(), fun->potential_writes.end()); // Add arguments to non-constant reference parameters type = fun->uid.get_type(); for (uint32_t i = 1; i < min(get_size(), type.size()); i++) { if (type[i].is(REF) && !type[i].is_constant()) { - get(i).get_symbols(symbols); + get(i).get_potential_symbols(symbols); } } } @@ -1783,13 +1840,117 @@ void expression_t::collect_possible_writes(set& symbols) const } } -void expression_t::collect_possible_reads(set& symbols, bool collectRandom) const +void expression_t::collect_sure_writes(set& symbols) const +{ + function_t* fun; + symbol_t symbol; + type_t type; + + if (empty()) + return; + + switch (get_kind()) { + case ASSIGN: + case ASS_PLUS: + case ASS_MINUS: + case ASS_DIV: + case ASS_MOD: + case ASS_MULT: + case ASS_AND: + case ASS_OR: + case ASS_XOR: + case ASS_LSHIFT: + case ASS_RSHIFT: + case POST_INCREMENT: + case POST_DECREMENT: + case PRE_INCREMENT: + case PRE_DECREMENT: + get(0).get_sure_symbols(symbols); + for (uint32_t i = 0; i < get_size(); ++i) + get(i).collect_sure_writes(symbols); + break; + + case FUN_CALL: + case FUN_CALL_EXT: + // Add all symbols which are changed by the function + symbol = get(0).get_symbol(); + if ((symbol.get_type().is_function() || symbol.get_type().is_function_external()) && symbol.get_data()) { + fun = (function_t*)symbol.get_data(); + symbols.insert(fun->sure_writes.begin(), fun->sure_writes.end()); + // Add arguments to non-constant reference parameters + type = fun->uid.get_type(); + for (uint32_t i = 1; i < std::min(get_size(), type.size()); ++i) + if (type[i].is(REF) && !type[i].is_constant()) + get(i).get_sure_symbols(symbols); + } + break; + case INLINE_IF: + // TODO: add symbols if get(0) value is available at compile-time + // get(0).collect_sure_writes(symbols); + break; + default: + for (uint32_t i = 0; i < get_size(); ++i) + get(i).collect_sure_writes(symbols); + } +} + +void expression_t::collect_potential_reads(set& symbols, bool collectRandom) const +{ + if (empty()) + return; + + for (uint32_t i = 0; i < get_size(); i++) + get(i).collect_potential_reads(symbols); + + switch (get_kind()) { + case IDENTIFIER: symbols.insert(get_symbol()); break; + + case FUN_CALL: { + // Add all symbols which are used by the function + auto symbol = get(0).get_symbol(); + if (auto type = symbol.get_type(); type.is_function() || type.is_function_external()) { + if (auto* data = symbol.get_data(); data) { + auto fun = static_cast(data); + symbols.insert(fun->potential_reads.begin(), fun->potential_reads.end()); + } + } + break; + } + case RANDOM_F: + case RANDOM_POISSON_F: + if (collectRandom) { + symbols.insert(symbol_t()); // TODO: revisit, should register the argument? + } + break; + case RANDOM_ARCSINE_F: + case RANDOM_BETA_F: + case RANDOM_GAMMA_F: + case RANDOM_NORMAL_F: + case RANDOM_WEIBULL_F: + if (collectRandom) { + symbols.insert(symbol_t()); // TODO: revisit, should register the arguments? + symbols.insert(symbol_t()); + } + break; + + case RANDOM_TRI_F: + if (collectRandom) { + symbols.insert(symbol_t()); // TODO: revisit, should register the argument? + symbols.insert(symbol_t()); + symbols.insert(symbol_t()); + } + break; + default: break; + } +} + +void expression_t::collect_sure_reads(set& symbols, bool collectRandom) const { if (empty()) return; for (uint32_t i = 0; i < get_size(); i++) - get(i).collect_possible_reads(symbols); + get(i).collect_sure_reads(symbols); switch (get_kind()) { case IDENTIFIER: symbols.insert(get_symbol()); break; @@ -1800,7 +1961,7 @@ void expression_t::collect_possible_reads(set& symbols, bool collectRa if (auto type = symbol.get_type(); type.is_function() || type.is_function_external()) { if (auto* data = symbol.get_data(); data) { auto fun = static_cast(data); - symbols.insert(fun->depends.begin(), fun->depends.end()); + symbols.insert(fun->sure_reads.begin(), fun->sure_reads.end()); } } break; diff --git a/src/statement.cpp b/src/statement.cpp index 7e4b7e9d..5f29d566 100644 --- a/src/statement.cpp +++ b/src/statement.cpp @@ -102,7 +102,7 @@ string DoWhileStatement::str(const string& prefix) const return prefix + "do {\n" + stat->str(prefix + INDENT) + prefix + "}"; } -void BlockStatement::push_stat(std::unique_ptr stat) +void BlockStatement::push(std::unique_ptr stat) { assert(stat != nullptr); stats.push_back(std::move(stat)); @@ -122,7 +122,7 @@ Statement* BlockStatement::back() return stats.back().get(); } -std::unique_ptr BlockStatement::pop_stat() +std::unique_ptr BlockStatement::pop() { std::unique_ptr st = std::move(stats.back()); stats.pop_back(); @@ -288,8 +288,9 @@ int32_t ExpressionVisitor::visitWhileStatement(WhileStatement* stat) int32_t ExpressionVisitor::visitDoWhileStatement(DoWhileStatement* stat) { + auto res = stat->stat->accept(this); visitExpression(stat->cond); - return stat->stat->accept(this); + return res; } int32_t ExpressionVisitor::visitBlockStatement(BlockStatement* stat) @@ -338,15 +339,3 @@ int32_t ExpressionVisitor::visitReturnStatement(ReturnStatement* stat) visitExpression(stat->value); return 0; } - -void CollectChangesVisitor::visitExpression(expression_t expr) { expr.collect_possible_writes(changes); } - -CollectDependenciesVisitor::CollectDependenciesVisitor(std::set& dependencies): dependencies(dependencies) {} - -void CollectDependenciesVisitor::visitExpression(expression_t expr) { expr.collect_possible_reads(dependencies); } - -void CollectDynamicExpressions::visitExpression(expression_t expr) -{ - if (expr.is_dynamic() || expr.has_dynamic_sub()) - expressions.push_back(expr); -} diff --git a/src/typechecker.cpp b/src/typechecker.cpp index 553f3463..bf1d8e1c 100644 --- a/src/typechecker.cpp +++ b/src/typechecker.cpp @@ -167,7 +167,7 @@ static bool isAssignable(type_t type) case Constants::COST: case Constants::SCALAR: return true; - case ARRAY: return isAssignable(type[0]); + case Constants::ARRAY: return isAssignable(type[0]); case RECORD: for (size_t i = 0; i < type.size(); i++) { @@ -313,9 +313,10 @@ void TypeChecker::handleError(T expr, const std::string& msg) void TypeChecker::checkIgnoredValue(expression_t expr) { static const auto message = "$Expression_does_not_have_any_effect"; - if (!expr.changes_any_variable() && expr.get_kind() != FUN_CALL_EXT) { + if (!expr.potentially_changes_some() && expr.get_kind() != FUN_CALL_EXT) { handleWarning(expr, message); - } else if (expr.get_kind() == COMMA && !expr[1].changes_any_variable() && expr[1].get_kind() != FUN_CALL_EXT) { + } else if (expr.get_kind() == COMMA and not expr[1].potentially_changes_some() and + expr[1].get_kind() != FUN_CALL_EXT) { handleWarning(expr[1], message); } } @@ -332,7 +333,7 @@ bool TypeChecker::isCompileTimeComputable(expression_t expr) const * rid of the compileTimeComputableValues object. */ std::set reads; - expr.collect_possible_reads(reads, true); + expr.collect_potential_reads(reads, true); return std::all_of(reads.begin(), reads.end(), [this](const symbol_t& s) { return s != symbol_t{} && (s.get_type().is_function() || s.get_type().is_function_external() || compileTimeComputableValues.contains(s)); @@ -482,10 +483,10 @@ void TypeChecker::visitDocAfter(Document& doc) } // Check index expressions - while (expr.get_kind() == ARRAY) { + while (expr.get_kind() == SUBSCRIPT) { if (!isCompileTimeComputable(expr[1])) { handleError(expr[1], "$Must_be_computable_at_compile_time"); - } else if (i.head.changes_any_variable()) { + } else if (i.head.potentially_changes_some()) { handleError(expr[1], "$Index_must_be_side-effect_free"); } expr = expr[0]; @@ -507,10 +508,10 @@ void TypeChecker::visitDocAfter(Document& doc) } // Check index expressions - while (expr.get_kind() == ARRAY) { + while (expr.get_kind() == SUBSCRIPT) { if (!isCompileTimeComputable(expr[1])) { handleError(expr[1], "$Must_be_computable_at_compile_time"); - } else if (j.second.changes_any_variable()) { + } else if (j.second.potentially_changes_some()) { handleError(expr[1], "$Index_must_be_side-effect_free"); } expr = expr[0]; @@ -525,7 +526,7 @@ void TypeChecker::visitHybridClock(expression_t e) if (checkExpression(e)) { if (!is_clock(e)) { handleError(e, "$Clock_expected"); - } else if (e.changes_any_variable()) { + } else if (e.potentially_changes_some()) { handleError(e, "$Index_must_be_side-effect_free"); } // Should be a check to identify the clock at compile time. @@ -541,7 +542,7 @@ void TypeChecker::visitIODecl(iodecl_t& iodecl) handleError(e, "$Integer_expected"); } else if (!isCompileTimeComputable(e)) { handleError(e, "$Must_be_computable_at_compile_time"); - } else if (e.changes_any_variable()) { + } else if (e.potentially_changes_some()) { handleError(e, "$Index_must_be_side-effect_free"); } } @@ -582,10 +583,10 @@ void TypeChecker::visitIODecl(iodecl_t& iodecl) } // Check index expressions - while (expr.get_kind() == ARRAY) { + while (expr.get_kind() == SUBSCRIPT) { if (!isCompileTimeComputable(expr[1])) { handleError(expr[1], "$Must_be_computable_at_compile_time"); - } else if (expr.changes_any_variable()) { + } else if (expr.potentially_changes_some()) { handleError(expr[1], "$Index_must_be_side-effect_free"); } expr = expr[0]; @@ -606,10 +607,10 @@ void TypeChecker::visitIODecl(iodecl_t& iodecl) } // Check index expressions - while (expr.get_kind() == ARRAY) { + while (expr.get_kind() == SUBSCRIPT) { if (!isCompileTimeComputable(expr[1])) { handleError(expr[1], "$Must_be_computable_at_compile_time"); - } else if (expr.changes_any_variable()) { + } else if (expr.potentially_changes_some()) { handleError(expr[1], "$Index_must_be_side-effect_free"); } expr = expr[0]; @@ -656,7 +657,7 @@ void TypeChecker::visitVariable(variable_t& variable) } else if (!variable.init.empty() && checkExpression(variable.init)) { if (!isCompileTimeComputable(variable.init)) { handleError(variable.init, "$Must_be_computable_at_compile_time"); - } else if (variable.init.changes_any_variable()) { + } else if (variable.init.potentially_changes_some()) { handleError(variable.init, "$Initialiser_must_be_side-effect_free"); } else { variable.init = checkInitialiser(variable.uid.get_type(), variable.init); @@ -676,7 +677,7 @@ void TypeChecker::visitLocation(location_t& loc) s += inv.get_type().str(); s += " $cannot_be_used_as_an_invariant"; handleError(inv, s); - } else if (inv.changes_any_variable()) { + } else if (inv.potentially_changes_some()) { handleError(inv, "$Invariant_must_be_side-effect_free"); } else { RateDecomposer decomposer; @@ -729,7 +730,7 @@ void TypeChecker::visitEdge(edge_t& edge) s += edge.guard.get_type().str(); s += " $cannot_be_used_as_a_guard"; handleError(edge.guard, s); - } else if (edge.guard.changes_any_variable()) { + } else if (edge.guard.potentially_changes_some()) { handleError(edge.guard, "$Guard_must_be_side-effect_free"); } if (hasStrictLowerBound(edge.guard)) { @@ -750,7 +751,7 @@ void TypeChecker::visitEdge(edge_t& edge) type_t channel = edge.sync.get(0).get_type(); if (!channel.is_channel()) { handleError(edge.sync.get(0), "$Channel_expected"); - } else if (edge.sync.changes_any_variable()) { + } else if (edge.sync.potentially_changes_some()) { handleError(edge.sync, "$Synchronisation_must_be_side-effect_free"); } else { bool hasClockGuard = !edge.guard.empty() && !is_integral(edge.guard); @@ -855,7 +856,7 @@ void TypeChecker::visitEdge(edge_t& edge) s += edge.prob.get_type().str(); s += " $cannot_be_used_as_a_probability"; handleError(edge.prob, s); - } else if (edge.prob.changes_any_variable()) { + } else if (edge.prob.potentially_changes_some()) { handleError(edge.prob, "$Probability_must_be_side-effect_free"); } } @@ -873,7 +874,7 @@ void TypeChecker::visitMessage(message_t& message) type_t channel = message.label.get(0).get_type(); if (!channel.is_channel()) { handleError(message.label.get(0), "$Channel_expected"); - } else if (message.label.changes_any_variable()) { + } else if (message.label.potentially_changes_some()) { handleError(message.label, "$Message_must_be_side-effect_free"); } } @@ -889,7 +890,7 @@ void TypeChecker::visitCondition(condition_t& condition) s += condition.label.get_type().str(); s += " $cannot_be_used_as_a_condition"; handleError(condition.label, s); - } else if (condition.label.changes_any_variable()) { + } else if (condition.label.potentially_changes_some()) { handleError(condition.label, "$Condition_must_be_side-effect_free"); } } @@ -967,7 +968,7 @@ void TypeChecker::visitInstance(instance_t& instance) } // For template instantiation, the argument must be side-effect free - if (argument.changes_any_variable()) { + if (argument.potentially_changes_some()) { handleError(argument, "$Argument_must_be_side-effect_free"); continue; } @@ -1030,7 +1031,7 @@ static bool hasSpawnOrExit(expression_t expr) void TypeChecker::visitProperty(expression_t expr) { if (checkExpression(expr)) { - if (expr.changes_any_variable()) { + if (expr.potentially_changes_some()) { handleError(expr, "$Property_must_be_side-effect_free"); } if (expr.get_kind() == LOAD_STRAT || expr.get_kind() == SAVE_STRAT) { @@ -1202,50 +1203,43 @@ static bool validReturnType(type_t type) } } -void TypeChecker::visitFunction(function_t& fun) +void TypeChecker::visitFunction(function_t& fn) { - DocumentVisitor::visitFunction(fun); - /* Check that the return type is consistent and is a valid return - * type. - */ - type_t return_type = fun.uid.get_type()[0]; + DocumentVisitor::visitFunction(fn); + // Check that the return type is consistent and is a valid return type. + type_t return_type = fn.uid.get_type()[0]; checkType(return_type); - if (!return_type.is_void() && !validReturnType(return_type)) { + if (!return_type.is_void() && !validReturnType(return_type)) handleError(return_type, "$Invalid_return_type"); - } /* Type check the function body: Type checking return statements * requires access to the return type, hence we store a pointer to * the current function being type checked in the \a function * member. */ - function = &fun; - fun.body->accept(this); + function = &fn; + fn.body->accept(this); function = nullptr; - /* Check if there are dynamic expressions in the function body*/ - checkDynamicExpressions(fun.body.get()); + // Check if there are dynamic expressions in the function body + checkDynamicExpressions(fn.body.get()); /* Collect identifiers of things external to the function accessed * or changed by the function. Notice that neither local variables * nor parameters are considered to be changed or accessed by a - * function. - */ - CollectChangesVisitor visitor(fun.changes); - fun.body->accept(&visitor); - - CollectDependenciesVisitor visitor2(fun.depends); - fun.body->accept(&visitor2); + * function. */ + fn.potential_reads = collect_potential_dependencies(*fn.body); + fn.potential_writes = collect_potential_changes(*fn.body); + fn.sure_reads = collect_sure_dependencies(*fn.body); + fn.sure_writes = collect_sure_changes(*fn.body); - for (const auto& var : fun.variables) { - fun.changes.erase(var.uid); - fun.depends.erase(var.uid); - } - size_t parameters = fun.uid.get_type().size() - 1; - for (uint32_t i = 0; i < parameters; i++) { - fun.changes.erase(fun.body->get_frame()[i]); - fun.depends.erase(fun.body->get_frame()[i]); - } + // Remove local variables + for (const auto& var : fn.variables) + fn.remove_access(var.uid); + // Remove fn parameters + size_t parameters = fn.uid.get_type().size() - 1; + for (uint32_t i = 0; i < parameters; ++i) + fn.remove_access(fn.body->get_frame()[i]); } int32_t TypeChecker::visitEmptyStatement(EmptyStatement* stat) { return 0; } @@ -1258,7 +1252,7 @@ int32_t TypeChecker::visitExprStatement(ExprStatement* stat) int32_t TypeChecker::visitAssertStatement(AssertStatement* stat) { - if (checkExpression(stat->expr) && stat->expr.changes_any_variable()) { + if (checkExpression(stat->expr) && stat->expr.potentially_changes_some()) { handleError(stat->expr, "$Assertion_must_be_side-effect_free"); } return 0; @@ -1323,7 +1317,7 @@ int32_t TypeChecker::visitBlockStatement(BlockStatement* stat) if (auto* d = symbol.get_data(); d) { variable_t* var = static_cast(d); if (!var->init.empty() && checkExpression(var->init)) { - if (var->init.changes_any_variable()) { + if (var->init.potentially_changes_some()) { /* This is stronger than C. However side-effects in * initialisers are nasty: For records, the evaluation * order may be different from the order in the input @@ -2139,7 +2133,7 @@ bool TypeChecker::checkExpression(expression_t expr) return result; } - case ARRAY: + case SUBSCRIPT: arg1 = expr[0].get_type(); arg2 = expr[1].get_type(); @@ -2182,7 +2176,7 @@ bool TypeChecker::checkExpression(expression_t expr) handleError(expr[1], "$Boolean_expected"); } - if (expr[1].changes_any_variable()) { + if (expr[1].potentially_changes_some()) { handleError(expr[1], "$Expression_must_be_side-effect_free"); } break; @@ -2199,7 +2193,7 @@ bool TypeChecker::checkExpression(expression_t expr) handleError(expr[1], "$Boolean_expected"); } - if (expr[1].changes_any_variable()) { + if (expr[1].potentially_changes_some()) { handleError(expr[1], "$Expression_must_be_side-effect_free"); } break; @@ -2216,7 +2210,7 @@ bool TypeChecker::checkExpression(expression_t expr) handleError(expr[1], "$Number_expected"); } - if (expr[1].changes_any_variable()) { + if (expr[1].potentially_changes_some()) { handleError(expr[1], "$Expression_must_be_side-effect_free"); } break; @@ -2284,7 +2278,7 @@ bool TypeChecker::checkExpression(expression_t expr) handleError(expr[nb], "$Boolean_expected"); ok = false; } - if (expr[nb].changes_any_variable()) { // check reachability expression is side effect free + if (expr[nb].potentially_changes_some()) { // check reachability expression is side effect free handleError(expr[nb], "$Property_must_be_side-effect_free"); ok = false; } @@ -2298,7 +2292,7 @@ bool TypeChecker::checkExpression(expression_t expr) handleError(expr[i], "$Integer_or_clock_expected"); return false; } - if (expr[i].changes_any_variable()) { + if (expr[i].potentially_changes_some()) { handleError(expr[i], "$Property_must_be_side-effect_free"); return false; } @@ -2317,7 +2311,7 @@ bool TypeChecker::checkExpression(expression_t expr) if (expr[1].get_kind() == LIST) { for (uint32_t i = 0; i < expr[1].get_size(); ++i) { if (is_integral(expr[1][i])) { - if (expr[1][i].changes_any_variable()) { + if (expr[1][i].potentially_changes_some()) { handleError(expr[1][i], "$Expression_must_be_side-effect_free"); return false; } @@ -2358,7 +2352,7 @@ bool TypeChecker::checkExpression(expression_t expr) type = type_t::create_primitive(FORMULA); for (size_t i = 3; i < expr.get_size(); ++i) { - if (expr[i].changes_any_variable()) { + if (expr[i].potentially_changes_some()) { handleError(expr[i], "$Property_must_be_side-effect_free"); return false; } @@ -2488,7 +2482,7 @@ bool TypeChecker::isModifiableLValue(expression_t expr) const // REVISIT: Not correct if records contain constant fields. return isModifiableLValue(expr[0]); - case ARRAY: return isModifiableLValue(expr[0]); + case SUBSCRIPT: return isModifiableLValue(expr[0]); case PRE_INCREMENT: case PRE_DECREMENT: @@ -2541,7 +2535,7 @@ bool TypeChecker::isLValue(expression_t expr) const case ASS_RSHIFT: return true; case DOT: - case ARRAY: return isLValue(expr[0]); + case SUBSCRIPT: return isLValue(expr[0]); case INLINE_IF: return isLValue(expr[1]) && isLValue(expr[2]) && areEquivalent(expr[1].get_type(), expr[2].get_type()); @@ -2569,7 +2563,7 @@ bool TypeChecker::isUniqueReference(expression_t expr) const case DOT: return isUniqueReference(expr[0]); - case ARRAY: return isUniqueReference(expr[0]) && isCompileTimeComputable(expr[1]); + case SUBSCRIPT: return isUniqueReference(expr[0]) && isCompileTimeComputable(expr[1]); case PRE_INCREMENT: case PRE_DECREMENT: @@ -2695,10 +2689,8 @@ bool TypeChecker::checkSpawnParameterCompatible(type_t param, expression_t arg) bool TypeChecker::checkDynamicExpressions(Statement* stat) { - std::list expr_list; - CollectDynamicExpressions e(expr_list); - stat->accept(&e); bool ok = true; + auto expr_list = collect_dynamic_expressions(*stat); for (const auto& expr : expr_list) { ok = false; handleError(expr, "Dynamic constructs are only allowed on edges!"); @@ -2747,7 +2739,7 @@ bool TypeChecker::checkPredicate(const expression_t& predicate) handleError(predicate, "$Boolean_expected"); return false; } - if (predicate.changes_any_variable()) { // check reachability expression is side effect free + if (predicate.potentially_changes_some()) { // check reachability expression is side effect free handleError(predicate, "$Property_must_be_side-effect_free"); return false; } @@ -2784,7 +2776,7 @@ bool TypeChecker::checkMonitoredExpr(const expression_t& expr) handleError(expr, "$Integer_or_clock_expected"); return false; } - if (expr.changes_any_variable()) { + if (expr.potentially_changes_some()) { handleError(expr, "$Property_must_be_side-effect_free"); return false; } diff --git a/test/test_expression.cpp b/test/test_expression.cpp index 1ab244b2..afb16418 100644 --- a/test/test_expression.cpp +++ b/test/test_expression.cpp @@ -20,6 +20,7 @@ */ #include "utap/expression.h" +#include "utap/statement.h" #include @@ -73,9 +74,9 @@ TEST_CASE("Expression") REQUIRE(exp_t::get_precedence(POST_INCREMENT) == exp_t::get_precedence(POST_DECREMENT)); REQUIRE(exp_t::get_precedence(POST_INCREMENT) >= exp_t::get_precedence(FUN_CALL)); REQUIRE(exp_t::get_precedence(FUN_CALL) == exp_t::get_precedence(FUN_CALL_EXT)); - REQUIRE(exp_t::get_precedence(FUN_CALL) >= exp_t::get_precedence(ARRAY)); - REQUIRE(exp_t::get_precedence(ARRAY) >= exp_t::get_precedence(DOT)); - REQUIRE(exp_t::get_precedence(ARRAY) > exp_t::get_precedence(NOT)); + REQUIRE(exp_t::get_precedence(FUN_CALL) >= exp_t::get_precedence(SUBSCRIPT)); + REQUIRE(exp_t::get_precedence(SUBSCRIPT) >= exp_t::get_precedence(DOT)); + REQUIRE(exp_t::get_precedence(SUBSCRIPT) > exp_t::get_precedence(NOT)); REQUIRE(exp_t::get_precedence(DOT) >= exp_t::get_precedence(RATE)); REQUIRE(exp_t::get_precedence(PRE_INCREMENT) == exp_t::get_precedence(NOT)); REQUIRE(exp_t::get_precedence(PRE_INCREMENT) == exp_t::get_precedence(PRE_DECREMENT)); @@ -151,7 +152,7 @@ TEST_CASE("Expression") const auto ops = { // clang-format off MINUS, PLUS, MULT, DIV, MOD, BIT_AND, BIT_OR, BIT_XOR, BIT_LSHIFT, BIT_RSHIFT, - AND, OR, XOR, POW, LT, LE, EQ, NEQ, GE, GT, MIN, MAX, ARRAY, COMMA, + AND, OR, XOR, POW, LT, LE, EQ, NEQ, GE, GT, MIN, MAX, SUBSCRIPT, COMMA, ASSIGN, ASS_PLUS, ASS_MINUS, ASS_DIV, ASS_MOD, ASS_MULT, ASS_AND, ASS_OR, ASS_XOR, ASS_LSHIFT, ASS_RSHIFT, FORALL, EXISTS, SUM, SUP_VAR, INF_VAR, BOUNDS_VAR, FRACTION, FMOD_F, FMAX_F, FMIN_F, FDIM_F, POW_F, HYPOT_F, ATAN2_F, LDEXP_F, NEXT_AFTER_F, COPY_SIGN_F, @@ -199,3 +200,103 @@ TEST_CASE("Expression to string conversion") CHECK(p.str() == "(2 * 3) ** (5 * 7)"); } } + +template +std::ostream& operator<<(std::ostream& os, const std::set& s) +{ + os << '{'; + auto it = s.begin(); + if (it != s.end()) { + os << *it; + while (++it != s.end()) + os << ',' << *it; + } + return os << '}'; +} + +TEST_CASE("Expression dependencies and effects") +{ + using namespace UTAP; + // Analyse a small program: + // int i; + // int j; + // int a[int[0,2]]; + // if (j == 0) + // i = 0; + // j = i; + // a[2] = 2; + auto exp_0 = UTAP::expression_t::create_constant(0); + CHECK(exp_0.str() == "0"); + auto exp_2 = UTAP::expression_t::create_constant(2); + CHECK(exp_2.str() == "2"); + auto global = frame_t::create(); + auto type_int = type_t::create_primitive(Constants::INT); + auto type_int_0_2 = type_t::create_range(type_int, exp_0, exp_2); + auto type_arr = type_t::create_array(type_int, type_int_0_2); + auto symb_i = global.add_symbol("i", type_int); + auto symb_j = global.add_symbol("j", type_int); + auto symb_k = global.add_symbol("k", type_int); + auto symb_a = global.add_symbol("a", type_arr); + auto exp_i = expression_t::create_identifier(symb_i); + CHECK(exp_i.str() == "i"); + auto exp_j = expression_t::create_identifier(symb_j); + CHECK(exp_j.str() == "j"); + auto exp_k = expression_t::create_identifier(symb_k); + CHECK(exp_k.str() == "k"); + auto exp_a = expression_t::create_identifier(symb_a); + CHECK(exp_a.str() == "a"); + auto eq_j_0 = expression_t::create_binary(Constants::EQ, exp_j, exp_0); + CHECK(eq_j_0.str() == "j == 0"); + auto ass_i_0 = expression_t::create_binary(Constants::ASSIGN, exp_i, exp_0); + CHECK(ass_i_0.str() == "i = 0"); + auto ass_j_i = expression_t::create_binary(Constants::ASSIGN, exp_j, exp_i); + CHECK(ass_j_i.str() == "j = i"); + auto exp_a2 = expression_t::create_binary(Constants::SUBSCRIPT, exp_a, exp_2); + CHECK(exp_a2.str() == "a[2]"); + auto ass_a2_2 = expression_t::create_binary(Constants::ASSIGN, exp_a2, exp_2); + CHECK(ass_a2_2.str() == "a[2] = 2"); + auto block = BlockStatement{global}; + block.emplace(eq_j_0, std::make_unique(ass_i_0)); + block.emplace(ass_j_i); + block.emplace(ass_a2_2); + + auto potential_reads = collect_potential_dependencies(block); + CHECK(potential_reads == std::set{symb_i, symb_j, symb_a}); + + auto potential_writes = collect_potential_changes(block); + CHECK(potential_writes.size() == 3); + CHECK(potential_writes.find(symb_i) != potential_writes.end()); + CHECK(potential_writes.find(symb_j) != potential_writes.end()); + + auto sure_writes = collect_sure_changes(block); + CHECK(sure_writes.size() == 1); + CHECK(sure_writes.find(symb_i) == sure_writes.end()); + CHECK(sure_writes.find(symb_j) != sure_writes.end()); + + // inline-if + auto iif_eq_j_0_i_k = expression_t::create_nary(Constants::INLINE_IF, {eq_j_0, exp_i, exp_k}); + CHECK(iif_eq_j_0_i_k.str() == "j == 0 ? i : k"); + auto stat_iif1 = ExprStatement{iif_eq_j_0_i_k}; + potential_reads = collect_potential_dependencies(stat_iif1); + CHECK(potential_reads == std::set{symb_i, symb_j, symb_k}); + potential_writes = collect_potential_changes(stat_iif1); + CHECK(potential_writes.empty()); + sure_writes = collect_sure_changes(stat_iif1); + CHECK(sure_writes.empty()); + + // inline-if with assignment + auto iif_j_i_k_a2 = expression_t::create_binary(Constants::ASSIGN, iif_eq_j_0_i_k, exp_a2); + CHECK(iif_j_i_k_a2.str() == "(j == 0 ? i : k) = a[2]"); + + potential_reads.clear(); + iif_j_i_k_a2.collect_potential_reads(potential_reads); + CHECK(potential_reads == std::set{symb_i, symb_j, symb_k, symb_a}); + + potential_writes.clear(); + iif_j_i_k_a2.collect_potential_writes(potential_writes); + CHECK(potential_writes == std::set{symb_i, symb_k}); + + sure_writes.clear(); + iif_j_i_k_a2.collect_sure_writes(sure_writes); + CHECK(sure_writes.empty()); +} \ No newline at end of file