// OpenSTA, Static Timing Analyzer // Copyright (c) 2026, 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 #include #include #include #include #include #include "Clock.hh" #include "ContainerHelpers.hh" #include "Format.hh" #include "GraphClass.hh" #include "GraphCmp.hh" #include "Liberty.hh" #include "LibertyClass.hh" #include "NetworkClass.hh" #include "Network.hh" #include "SearchClass.hh" #include "StringUtil.hh" #include "Property.hh" #include "PathEnd.hh" #include "PatternMatch.hh" #include "SdcClass.hh" #include "Sta.hh" namespace sta { class FilterExpr { public: struct Token { virtual ~Token() = default; enum class Kind { skip = 0, predicate, op_lparen, op_rparen, op_or, op_and, op_inv, defined, undefined }; Token(std::string_view text, Kind kind); const std::string &text() const { return text_; } Kind kind() const { return kind_; } std::string text_; Kind kind_; }; struct PredicateToken : public Token { PredicateToken(std::string_view property, std::string_view op, std::string_view arg); virtual ~PredicateToken() = default; const std::string &property() const { return property_; } const std::string &op() const { return op_; } const std::string &arg() const { return arg_; } std::string property_; std::string op_; std::string arg_; }; FilterExpr(std::string_view expression, Report *report); std::vector> postfix(); private: std::vector> lex(); std::vector> shuntingYard(std::vector> &infix); std::string raw_; Report *report_; }; FilterExpr::Token::Token(std::string_view text, Token::Kind kind) : text_(text), kind_(kind) { } FilterExpr::PredicateToken::PredicateToken(std::string_view property, std::string_view op, std::string_view arg) : Token(sta::format("{} {} {}", 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() { auto infix = lex(); return shuntingYard(infix); } std::vector> FilterExpr::lex() { 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_.data(); 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 'prop == 1'. std::string op = "=="; std::string arg = "1"; 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.empty() && 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.empty() && 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.empty()) { 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 static std::set filterObjects(std::string_view property, std::string_view op, std::string_view pattern, std::set &all, Sta *sta) { Properties &properties = sta->properties(); Network *network = sta->network(); auto filtered_objects = std::set(); bool exact_match = (op == "=="); bool pattern_match = (op == "=~"); bool not_match = (op == "!="); bool not_pattern_match = (op == "!~"); for (T *object : all) { PropertyValue value = properties.getProperty(object, property); std::string prop = value.to_string(network); if (value.type() == PropertyValue::Type::bool_) { // Canonicalize bool true/false to 1/0. if (stringEqual(pattern, "true")) pattern = "1"; else if (stringEqual(pattern, "false")) pattern = "0"; } if ((exact_match && prop == pattern) || (not_match && prop != pattern) || (pattern_match && patternMatch(pattern, prop)) || (not_pattern_match && !patternMatch(pattern, prop))) filtered_objects.insert(object); } return filtered_objects; } template static std::vector filterObjects(std::string_view filter_expression, const std::vector *objects, const std::function &object_less, 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); // Delete objects before parsing so errors to not leak them. delete objects; FilterExpr filter(filter_expression, report); auto postfix = filter.postfix(); 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).empty(); break; case PropertyValue::Type::none: is_defined = false; break; case PropertyValue::Type::pins: is_defined = !value.pins()->empty(); break; case PropertyValue::Type::clks: is_defined = !value.clocks()->empty(); break; case PropertyValue::Type::paths: is_defined = !value.paths()->empty(); 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(), predicate_token->op(), predicate_token->arg(), all, sta); eval_stack.push(result); } } if (eval_stack.empty()) 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()); sort(result, [object_less] (T *obj1, T *obj2) { return object_less(obj1, obj2); }); } return result; } PortSeq filterPorts(std::string_view filter_expression, PortSeq *ports, Sta *sta) { Network *network = sta->network(); return filterObjects(filter_expression, ports, [network] (const Port *port1, const Port *port2) { return network->name(port1) < network->name(port2); }, sta); } InstanceSeq filterInstances(std::string_view filter_expression, InstanceSeq *insts, Sta *sta) { Network *network = sta->network(); return filterObjects(filter_expression, insts, [network] (const Instance *inst1, const Instance *inst2) { return network->name(inst1) < network->name(inst2); }, sta); } PinSeq filterPins(std::string_view filter_expression, PinSeq *pins, Sta *sta) { Network *network = sta->network(); return filterObjects(filter_expression, pins, [network] (const Pin *pin1, const Pin *pin2) { return network->pathName(pin1) < network->pathName(pin2); }, sta); } NetSeq filterNets(std::string_view filter_expression, NetSeq *nets, Sta *sta) { Network *network = sta->network(); return filterObjects(filter_expression, nets, [network] (const Net *net1, const Net *net2) { return network->pathName(net1) < network->pathName(net2); }, sta); } ClockSeq filterClocks(std::string_view filter_expression, ClockSeq *clks, Sta *sta) { return filterObjects(filter_expression, clks, [] (const Clock *clk1, const Clock *clk2) { return clk1->name() < clk2->name(); }, sta); } LibertyCellSeq filterLibCells(std::string_view filter_expression, LibertyCellSeq *cells, Sta *sta) { return filterObjects(filter_expression, cells, [] (const LibertyCell *cell1, const LibertyCell *cell2) { return cell1->name() < cell2->name(); }, sta); } LibertyPortSeq filterLibPins(std::string_view filter_expression, LibertyPortSeq *ports, Sta *sta) { return filterObjects(filter_expression, ports, [] (const LibertyPort *port1, const LibertyPort *port2) { return port1->name() < port2->name(); }, sta); } LibertyLibrarySeq filterLibertyLibraries(std::string_view filter_expression, LibertyLibrarySeq *libs, Sta *sta) { return filterObjects(filter_expression, libs, [] (const LibertyLibrary *lib1, const LibertyLibrary *lib2) { return lib1->name() < lib2->name(); }, sta); } EdgeSeq filterTimingArcs(std::string_view filter_expression, EdgeSeq *edges, Sta *sta) { Network *network = sta->network(); Graph *graph = sta->graph(); EdgeLess edge_less(network, graph); return filterObjects(filter_expression, edges, [edge_less] (const Edge *edge1, const Edge *edge2) { return edge_less.operator()(edge1, edge2); }, sta); } PathEndSeq filterPathEnds(std::string_view filter_expression, PathEndSeq *ends, Sta *sta) { PathEndLess end_less(true, sta); return filterObjects(filter_expression, ends, [end_less] (const PathEnd *end1, const PathEnd *end2) { return end_less.operator()(end1, end2); }, sta); } StringSeq filterExprToPostfix(std::string_view expr, Report *report) { FilterExpr filter(expr, report); auto postfix = filter.postfix(); StringSeq result; for (auto &token : postfix) result.push_back(token->text()); return result; } } // namespace sta