// OpenSTA, Static Timing Analyzer // Copyright (c) 2025, Parallax Software, Inc. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. // // Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // // This notice may not be removed or altered from any source distribution. #include "FilterObjects.hh" #include #include #include #include #include "Property.hh" #include "PatternMatch.hh" #include "Sta.hh" namespace sta { class FilterExpr { public: struct Token { enum class Kind { skip = 0, predicate, op_lparen, op_rparen, op_or, op_and, op_inv, defined, undefined }; Token(std::string text, Kind kind); std::string text; Kind kind; }; struct PredicateToken : public Token { PredicateToken(std::string property, std::string op, std::string arg); std::string property; std::string op; std::string arg; }; FilterExpr(std::string_view expression, Report *report); std::vector> postfix(bool bool_props_as_int); private: std::vector> lex(bool bool_props_as_int); std::vector> shuntingYard(std::vector> &infix); std::string raw_; Report *report_; }; FilterExpr::Token::Token(std::string text, Token::Kind kind) : text (text), kind(kind) { } FilterExpr::PredicateToken::PredicateToken(std::string property, std::string op, std::string arg) : Token(property + " " + op + " " + arg, Token::Kind::predicate), property(property), op(op), arg(arg) { } FilterExpr::FilterExpr(std::string_view expression, Report *report) : raw_(expression), report_(report) { } std::vector> FilterExpr::postfix(bool bool_props_as_int) { auto infix = lex(bool_props_as_int); return shuntingYard(infix); } std::vector> FilterExpr::lex(bool bool_props_as_int) { std::vector> token_regexes = { {std::regex("^\\s+"), Token::Kind::skip}, {std::regex("^defined\\(([a-zA-Z_]+)\\)"), Token::Kind::defined}, {std::regex("^undefined\\(([a-zA-Z_]+)\\)"), Token::Kind::undefined}, {std::regex("^@?([a-zA-Z_]+) *((==|!=|=~|!~) *([0-9a-zA-Z_\\/$\\[\\]*?]+))?"), Token::Kind::predicate}, {std::regex("^(&&)"), Token::Kind::op_and}, {std::regex("^(\\|\\|)"), Token::Kind::op_or}, {std::regex("^(!)"), Token::Kind::op_inv}, {std::regex("^(\\()"), Token::Kind::op_lparen}, {std::regex("^(\\))"), Token::Kind::op_rparen}, }; std::vector> result; const char* ptr = &raw_[0]; bool match = false; while (*ptr != '\0') { match = false; for (auto& [regex, kind]: token_regexes) { std::cmatch token_match; if (std::regex_search(ptr, token_match, regex)) { if (kind == Token::Kind::predicate) { std::string property = token_match[1].str(); // The default operation on a predicate if an op and arg are // omitted is 'arg == 1' / 'arg == true'. std::string op = "=="; std::string arg = (bool_props_as_int ? "1" : "true"); if (token_match[2].length() != 0) { op = token_match[3].str(); arg = token_match[4].str(); } result.push_back(std::make_unique(property, op, arg)); } else if (kind == Token::Kind::defined) result.push_back(std::make_unique(token_match[1].str(), kind)); else if (kind == Token::Kind::undefined) result.push_back(std::make_unique(token_match[1].str(), kind)); else if (kind != Token::Kind::skip) result.push_back(std::make_unique(std::string(ptr,token_match.length()), kind)); ptr += token_match.length(); match = true; break; }; } if (!match) report_->error(2600, "-filter parsing failed at '{}'.", ptr); } return result; } std::vector> FilterExpr::shuntingYard(std::vector> &infix) { std::vector> output; std::stack> operator_stack; for (auto &token : infix) { switch (token->kind) { case Token::Kind::predicate: output.push_back(std::move(token)); break; case Token::Kind::op_or: [[fallthrough]]; case Token::Kind::op_and: // The operators' enum values are ascending by precedence: // inv > and > or while (operator_stack.size() && operator_stack.top()->kind > token->kind) { output.push_back(std::move(operator_stack.top())); operator_stack.pop(); } operator_stack.push(std::move(token)); break; case Token::Kind::op_inv: // Unary with highest precedence, no need for the while loop. operator_stack.push(std::move(token)); break; case Token::Kind::defined: operator_stack.push(std::move(token)); break; case Token::Kind::undefined: operator_stack.push(std::move(token)); break; case Token::Kind::op_lparen: operator_stack.push(std::move(token)); break; case Token::Kind::op_rparen: if (operator_stack.empty()) report_->error(2601, "-filter extraneous )."); while (operator_stack.size() && operator_stack.top()->kind != Token::Kind::op_lparen) { output.push_back(std::move(operator_stack.top())); operator_stack.pop(); if (operator_stack.empty()) report_->error(2602, "-filter extraneous )."); } // Guaranteed to be lparen at this point. operator_stack.pop(); break; default: // Unhandled/skip. break; } } while (operator_stack.size()) { if (operator_stack.top()->kind == Token::Kind::op_lparen) report_->error(2603, "-filter unmatched (."); output.push_back(std::move(operator_stack.top())); operator_stack.pop(); } return output; } //////////////////////////////////////////////////////////////// template std::set filterObjects(const char *property, const char *op, const char *pattern, std::set &all, Sta *sta) { Properties &properties = sta->properties(); Network *network = sta->network(); auto filtered_objects = std::set(); bool exact_match = stringEq(op, "=="); bool pattern_match = stringEq(op, "=~"); bool not_match = stringEq(op, "!="); bool not_pattern_match = stringEq(op, "!~"); for (T *object : all) { PropertyValue value = properties.getProperty(object, property); std::string prop_str = value.to_string(network); const char *prop = prop_str.c_str(); if (prop && ((exact_match && stringEq(prop, pattern)) || (not_match && !stringEq(prop, pattern)) || (pattern_match && patternMatch(pattern, prop)) || (not_pattern_match && !patternMatch(pattern, prop)))) filtered_objects.insert(object); } return filtered_objects; } template std::vector filterObjects(std::string_view filter_expression, std::vector *objects, bool bool_props_as_int, Sta *sta) { Report *report = sta->report(); Network *network = sta->network(); Properties &properties = sta->properties(); std::vector result; if (objects) { std::set all; for (auto object: *objects) all.insert(object); FilterExpr filter(filter_expression, report); auto postfix = filter.postfix(bool_props_as_int); std::stack> eval_stack; for (auto &token : postfix) { if (token->kind == FilterExpr::Token::Kind::op_or) { if (eval_stack.size() < 2) report->error(2604, "-filter logical OR requires at least two operands."); auto arg0 = eval_stack.top(); eval_stack.pop(); auto arg1 = eval_stack.top(); eval_stack.pop(); auto union_result = std::set(); std::set_union(arg0.cbegin(), arg0.cend(), arg1.cbegin(), arg1.cend(), std::inserter(union_result, union_result.begin())); eval_stack.push(union_result); } else if (token->kind == FilterExpr::Token::Kind::op_and) { if (eval_stack.size() < 2) { report->error(2605, "-filter logical AND requires two operands."); } auto arg0 = eval_stack.top(); eval_stack.pop(); auto arg1 = eval_stack.top(); eval_stack.pop(); auto intersection_result = std::set(); std::set_intersection(arg0.cbegin(), arg0.cend(), arg1.cbegin(), arg1.cend(), std::inserter(intersection_result, intersection_result.begin())); eval_stack.push(intersection_result); } else if (token->kind == FilterExpr::Token::Kind::op_inv) { if (eval_stack.size() < 1) { report->error(2606, "-filter NOT missing operand."); } auto arg0 = eval_stack.top(); eval_stack.pop(); auto difference_result = std::set(); std::set_difference(all.cbegin(), all.cend(), arg0.cbegin(), arg0.cend(), std::inserter(difference_result, difference_result.begin())); eval_stack.push(difference_result); } else if (token->kind == FilterExpr::Token::Kind::defined || token->kind == FilterExpr::Token::Kind::undefined) { bool should_be_defined = (token->kind == FilterExpr::Token::Kind::defined); auto result = std::set(); for (auto object : all) { PropertyValue value = properties.getProperty(object, token->text); bool is_defined = false; switch (value.type()) { case PropertyValue::Type::float_: is_defined = value.floatValue() != 0; break; case PropertyValue::Type::bool_: is_defined = value.boolValue(); break; case PropertyValue::Type::string: case PropertyValue::Type::liberty_library: case PropertyValue::Type::liberty_cell: case PropertyValue::Type::liberty_port: case PropertyValue::Type::library: case PropertyValue::Type::cell: case PropertyValue::Type::port: case PropertyValue::Type::instance: case PropertyValue::Type::pin: case PropertyValue::Type::net: case PropertyValue::Type::clk: is_defined = value.to_string(network) != ""; break; case PropertyValue::Type::none: is_defined = false; break; case PropertyValue::Type::pins: is_defined = value.pins()->size() > 0; break; case PropertyValue::Type::clks: is_defined = value.clocks()->size() > 0; break; case PropertyValue::Type::paths: is_defined = value.paths()->size() > 0; break; case PropertyValue::Type::pwr_activity: is_defined = value.pwrActivity().isSet(); break; } if (is_defined == should_be_defined) { result.insert(object); } } eval_stack.push(result); } else if (token->kind == FilterExpr::Token::Kind::predicate) { auto *predicate_token = static_cast(token.get()); auto result = filterObjects(predicate_token->property.c_str(), predicate_token->op.c_str(), predicate_token->arg.c_str(), all, sta); eval_stack.push(result); } } if (eval_stack.size() == 0) report->error(2607, "-filter expression is empty."); if (eval_stack.size() > 1) // huh? report->error(2608, "-filter expression evaluated to multiple sets."); auto result_set = eval_stack.top(); result.resize(result_set.size()); std::copy(result_set.begin(), result_set.end(), result.begin()); std::map objects_i; for (size_t i = 0; i < objects->size(); ++i) objects_i[objects->at(i)] = i; std::sort(result.begin(), result.end(), [&](T* a, T* b) { return objects_i[a] < objects_i[b]; }); delete objects; } return result; } PortSeq filterPorts(std::string_view filter_expression, PortSeq *objects, bool bool_props_as_int, Sta *sta) { return filterObjects(filter_expression, objects, bool_props_as_int, sta); } InstanceSeq filterInstances(std::string_view filter_expression, InstanceSeq *objects, bool bool_props_as_int, Sta *sta) { return filterObjects(filter_expression, objects, bool_props_as_int, sta); } PinSeq filterPins(std::string_view filter_expression, PinSeq *objects, bool bool_props_as_int, Sta *sta) { return filterObjects(filter_expression, objects, bool_props_as_int, sta); } NetSeq filterNets(std::string_view filter_expression, NetSeq *objects, bool bool_props_as_int, Sta *sta) { return filterObjects(filter_expression, objects, bool_props_as_int, sta); } ClockSeq filterClocks(std::string_view filter_expression, ClockSeq *objects, bool bool_props_as_int, Sta *sta) { return filterObjects(filter_expression, objects, bool_props_as_int, sta); } LibertyCellSeq filterLibCells(std::string_view filter_expression, LibertyCellSeq *objects, bool bool_props_as_int, Sta *sta) { return filterObjects(filter_expression, objects, bool_props_as_int, sta); } LibertyPortSeq filterLibPins(std::string_view filter_expression, LibertyPortSeq *objects, bool bool_props_as_int, Sta *sta) { return filterObjects(filter_expression, objects, bool_props_as_int, sta); } LibertyLibrarySeq filterLibertyLibraries(std::string_view filter_expression, LibertyLibrarySeq *objects, bool bool_props_as_int, Sta *sta) { return filterObjects(filter_expression, objects, bool_props_as_int, sta); } EdgeSeq filterTimingArcs(std::string_view filter_expression, EdgeSeq *objects, bool bool_props_as_int, Sta *sta) { return filterObjects(filter_expression, objects, bool_props_as_int, sta); } PathEndSeq filterPathEnds(std::string_view filter_expression, PathEndSeq *objects, bool bool_props_as_int, Sta *sta) { return filterObjects(filter_expression, objects, bool_props_as_int, sta); } StringSeq filterExprToPostfix(std::string_view expr, bool bool_props_as_int, Report *report) { FilterExpr filter(expr, report); auto postfix = filter.postfix(bool_props_as_int); StringSeq result; for (auto &token : postfix) result.push_back(token->text); return result; } } // namespace sta