From da26dc89f678656665b477601cb43934da060319 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 18 Jul 2026 15:23:59 -0700 Subject: [PATCH] Support shadowing type identifiers in procedural labels SystemVerilog procedural labels use ordinary identifiers. A visible type identifier can therefore also be used as a label: typedef int T; module test; initial begin T value; T: assert (1); end endmodule The parser currently handles procedural declarations and statements as separate lists. After `T value;`, it must decide whether the next `TYPE_IDENTIFIER` starts another declaration before it can see that the following `:` makes it an assertion label. Extending the label rule alone therefore introduces parser conflicts. Replace the separate lists with a mixed procedural item accumulator so the parser can keep the declaration-or-statement decision open. Track whether declarations have been seen, use allocation of the statement vector to record whether a statement has been seen, collect concrete statements and old-style task/function ports, and reject a declaration after a statement. A null statement allocates an empty statement vector and therefore also starts the statement section, preserving the existing declaration ordering rule. Own the accumulated statement and port vectors with `unique_ptr` and release old-style port vectors only when transferring them to a task or function. Parse assertion labels as `identifier_name ':'` and use the accumulator for constructors, functions, tasks, and sequential and parallel blocks. Keep the temporary scope for an unnamed block until its body has been classified. If it has no declarations, move nested named scopes into the enclosing scope and reparent them before discarding the temporary scope. Signed-off-by: Lars-Peter Clausen --- PScope.h | 1 + parse.y | 315 ++++++++++++++++++++++++++++++------------------------- pform.cc | 23 ++++ pform.h | 1 + 4 files changed, 200 insertions(+), 140 deletions(-) diff --git a/PScope.h b/PScope.h index 2d7a7b9d7..b4f1b2029 100644 --- a/PScope.h +++ b/PScope.h @@ -166,6 +166,7 @@ class LexicalScope { unsigned generate_counter; LexicalScope* parent_scope() const { return parent_; } + void set_parent_scope(LexicalScope *parent) { parent_ = parent; } virtual bool var_init_needs_explicit_lifetime() const; diff --git a/parse.y b/parse.y index f049652cd..6a78b6eb8 100644 --- a/parse.y +++ b/parse.y @@ -35,6 +35,7 @@ # include # include # include +# include using namespace std; @@ -539,6 +540,60 @@ static void current_function_set_statement(const YYLTYPE&loc, std::vectorset_statement(tmp); } +struct procedural_item_list_t { + bool has_decls = false; + YYLTYPE statements_loc = {}; + std::unique_ptr > statements; + std::unique_ptr > ports; +}; + +static procedural_item_list_t *make_procedural_item_list() +{ + return new procedural_item_list_t; +} + +static void procedural_item_list_add_statement(const YYLTYPE&loc, + procedural_item_list_t *list, + Statement *statement) +{ + assert(list); + if (!list->statements) { + list->statements.reset(new std::vector); + list->statements_loc = loc; + } else { + list->statements_loc.last_line = loc.last_line; + list->statements_loc.last_column = loc.last_column; + } + if (statement) + list->statements->push_back(statement); +} + +static void procedural_item_list_add_ports(procedural_item_list_t *list, + std::vector *ports) +{ + assert(list); + std::unique_ptr > new_ports(ports); + if (!new_ports) + return; + if (!list->ports) { + list->ports = std::move(new_ports); + return; + } + list->ports->insert(list->ports->end(), new_ports->begin(), + new_ports->end()); +} + +static void procedural_item_list_add_declaration(const YYLTYPE&loc, + procedural_item_list_t *list, + std::vector *ports) +{ + assert(list); + if (list->statements) + yyerror(loc, "error: Declarations must precede statements."); + list->has_decls = true; + procedural_item_list_add_ports(list, ports); +} + static void port_declaration_context_init(void) { port_declaration_context.port_type = NetNet::PINOUT; @@ -699,6 +754,7 @@ Module::port_t *module_declare_interface_port(const YYLTYPE&loc, char *type, PEventStatement*event_statement; Statement*statement; std::vector*statement_list; + struct procedural_item_list_t *procedural_item_list; decl_assignment_t*decl_assignment; std::list*decl_assignments; @@ -863,7 +919,7 @@ Module::port_t *module_declare_interface_port(const YYLTYPE&loc, char *type, %token K_timer K_transition K_units K_white_noise K_wreal %token K_zi_nd K_zi_np K_zi_zd K_zi_zp -%type from_exclude block_item_decls_opt +%type from_exclude %type number pos_neg_number %type signing unsigned_signed_opt signed_unsigned_opt %type import_export union_soft_opt @@ -897,7 +953,7 @@ Module::port_t *module_declare_interface_port(const YYLTYPE&loc, char *type, %type enum_name_list enum_name %type enum_data_type enum_base_type -%type tf_item_declaration tf_item_list tf_item_list_opt +%type tf_item_declaration %type tf_port_declaration tf_port_item tf_port_item_list %type tf_port_list tf_port_list_opt tf_port_list_parens_opt @@ -986,7 +1042,10 @@ Module::port_t *module_declare_interface_port(const YYLTYPE&loc, char *type, %type deferred_immediate_assertion_statement %type simple_immediate_assertion_statement %type procedural_assertion_statement -%type statement_or_null_list statement_or_null_list_opt +%type block_item_or_statement_list +%type block_item_or_statement_list_opt +%type tf_item_or_statement_list +%type tf_item_or_statement_list_opt %type analog_statement @@ -1106,6 +1165,12 @@ block_identifier_opt /* */ { $$ = 0; } ; +assertion_item_label_opt + : identifier_name ':' + { delete[]$1; } + | + ; + class_declaration /* IEEE1800-2005: A.1.2 */ : K_virtual_opt K_class lifetime_opt identifier_name class_declaration_extends_opt ';' { /* Up to 1800-2017 the grammar in the LRM allowed an optional lifetime @@ -1187,13 +1252,13 @@ class_item /* IEEE1800-2005: A.1.8 */ current_function = pform_push_constructor_scope(@3); } tf_port_list_parens_opt ';' - block_item_decls_opt - statement_or_null_list_opt + block_item_or_statement_list_opt K_endfunction endnew_opt - { current_function->set_ports($5); + { std::unique_ptr items($7); + current_function->set_ports($5); pform_set_constructor_return(current_function); pform_set_this_class(@3, current_function); - current_function_set_statement(@3, $8); + current_function_set_statement(@3, items->statements.get()); pform_pop_scope(); current_function = 0; } @@ -1820,19 +1885,20 @@ function_declaration /* IEEE1800-2005: A.2.6 */ { assert(current_function == 0); current_function = pform_push_function_scope(@1, $3.id, $2); } - tf_item_list_opt - statement_or_null_list_opt + tf_item_or_statement_list_opt K_endfunction - { current_function->set_ports($6); + { std::unique_ptr items($6); + current_function->set_ports(items->ports.release()); current_function->set_return($3.type); - current_function_set_statement($7 ? @7 : @3, $7); + current_function_set_statement(items->statements ? items->statements_loc : @3, + items->statements.get()); pform_set_this_class(@3, current_function); pform_pop_scope(); current_function = 0; } label_opt { // Last step: check any closing name. - check_end_label(@10, "function", $3.id, $10); + check_end_label(@9, "function", $3.id, $9); delete[]$3.id; } @@ -1841,12 +1907,13 @@ function_declaration /* IEEE1800-2005: A.2.6 */ current_function = pform_push_function_scope(@1, $3.id, $2); } '(' tf_port_list_opt ')' ';' - block_item_decls_opt - statement_or_null_list_opt + block_item_or_statement_list_opt K_endfunction - { current_function->set_ports($6); + { std::unique_ptr items($9); + current_function->set_ports($6); current_function->set_return($3.type); - current_function_set_statement($10 ? @10 : @3, $10); + current_function_set_statement(items->statements ? items->statements_loc : @3, + items->statements.get()); pform_set_this_class(@3, current_function); pform_pop_scope(); current_function = 0; @@ -1856,7 +1923,7 @@ function_declaration /* IEEE1800-2005: A.2.6 */ } label_opt { // Last step: check any closing name. - check_end_label(@13, "function", $3.id, $13); + check_end_label(@12, "function", $3.id, $12); delete[]$3.id; } @@ -2473,11 +2540,11 @@ port_direction_opt ; procedural_assertion_statement /* IEEE1800-2012 A.6.10 */ - : block_identifier_opt concurrent_assertion_statement + : assertion_item_label_opt concurrent_assertion_statement { $$ = $2; } - | block_identifier_opt simple_immediate_assertion_statement + | assertion_item_label_opt simple_immediate_assertion_statement { $$ = $2; } - | block_identifier_opt deferred_immediate_assertion_statement + | assertion_item_label_opt deferred_immediate_assertion_statement { $$ = $2; } ; @@ -2682,18 +2749,18 @@ task_declaration /* IEEE1800-2005: A.2.7 */ { assert(current_task == 0); current_task = pform_push_task_scope(@1, $3, $2); } - tf_item_list_opt - statement_or_null_list_opt + tf_item_or_statement_list_opt K_endtask - { current_task->set_ports($6); - current_task_set_statement(@3, $7); + { std::unique_ptr items($6); + current_task->set_ports(items->ports.release()); + current_task_set_statement(@3, items->statements.get()); pform_set_this_class(@3, current_task); pform_pop_scope(); current_task = 0; - if ($7 && $7->size() > 1) { - pform_requires_sv(@7, "Task body with multiple statements"); + if (items->statements && items->statements->size() > 1) { + pform_requires_sv(items->statements_loc, + "Task body with multiple statements"); } - delete $7; } label_opt { // Last step: check any closing name. This is done late so @@ -2701,7 +2768,7 @@ task_declaration /* IEEE1800-2005: A.2.7 */ // label_opt but still have the pform_endmodule() called // early enough that the lexor can know we are outside the // module. - check_end_label(@10, "task", $3, $10); + check_end_label(@9, "task", $3, $9); delete[]$3; } @@ -2710,11 +2777,11 @@ task_declaration /* IEEE1800-2005: A.2.7 */ current_task = pform_push_task_scope(@1, $3, $2); } tf_port_list_opt ')' ';' - block_item_decls_opt - statement_or_null_list_opt + block_item_or_statement_list_opt K_endtask - { current_task->set_ports($6); - current_task_set_statement(@3, $10); + { std::unique_ptr items($9); + current_task->set_ports($6); + current_task_set_statement(@3, items->statements.get()); pform_set_this_class(@3, current_task); pform_pop_scope(); if (generation_flag < GN_VER2005 && $6 == 0) { @@ -2722,7 +2789,6 @@ task_declaration /* IEEE1800-2005: A.2.7 */ << "\" has an empty port declaration list!" << endl; } current_task = 0; - if ($10) delete $10; } label_opt { // Last step: check any closing name. This is done late so @@ -2730,7 +2796,7 @@ task_declaration /* IEEE1800-2005: A.2.7 */ // label_opt but still have the pform_endmodule() called // early enough that the lexor can know we are outside the // module. - check_end_label(@13, "task", $3, $13); + check_end_label(@12, "task", $3, $12); delete[]$3; } @@ -3248,16 +3314,6 @@ block_item_decl } ; -block_item_decls - : block_item_decl - | block_item_decls block_item_decl - ; - -block_item_decls_opt - : block_item_decls { $$ = true; } - | { $$ = false; } - ; - /* We need to handle K_enum separately because * `typedef enum ` can either be the start of a enum forward * declaration or a enum type declaration with a type identifier as its base @@ -4746,36 +4802,6 @@ expr_primary } ; - /* A tf_item_list is shared between functions and tasks to match - declarations of ports. We check later to make sure there are no - output or inout ports actually used for functions. */ -tf_item_list_opt /* IEEE1800-2017: A.2.7 */ - : tf_item_list - { $$ = $1; } - | - { $$ = 0; } - ; - -tf_item_list /* IEEE1800-2017: A.2.7 */ - : tf_item_declaration - { $$ = $1; } - | tf_item_list tf_item_declaration - { if ($1 && $2) { - std::vector*tmp = $1; - size_t s1 = tmp->size(); - tmp->resize(s1 + $2->size()); - for (size_t idx = 0 ; idx < $2->size() ; idx += 1) - tmp->at(s1+idx) = $2->at(idx); - delete $2; - $$ = tmp; - } else if ($1) { - $$ = $1; - } else { - $$ = $2; - } - } - ; - tf_item_declaration /* IEEE1800-2017: A.2.7 */ : tf_port_declaration { $$ = $1; } | block_item_decl { $$ = 0; } @@ -7292,35 +7318,23 @@ statement_item /* This is roughly statement_item in the LRM */ { PBlock*tmp = pform_push_block_scope(@1, $2, PBlock::BL_SEQ); current_block_stack.push(tmp); } - block_item_decls_opt - { - if (!$2) { - if ($4) { - pform_block_decls_requires_sv(); - } else { - /* If there are no declarations in the scope then just delete it. */ - pform_pop_scope(); - assert(! current_block_stack.empty()); - PBlock*tmp = current_block_stack.top(); - current_block_stack.pop(); - delete tmp; - } + block_item_or_statement_list_opt K_end label_opt + { std::unique_ptr items($4); + if (!$2 && items->has_decls) { + pform_block_decls_requires_sv(); } - } - statement_or_null_list_opt K_end label_opt - { PBlock*tmp; - if ($2 || $4) { - pform_pop_scope(); - assert(! current_block_stack.empty()); - tmp = current_block_stack.top(); - current_block_stack.pop(); - } else { + bool keep_scope = $2 || items->has_decls; + pform_pop_block_scope(keep_scope); + assert(! current_block_stack.empty()); + auto tmp = current_block_stack.top(); + current_block_stack.pop(); + if (!keep_scope) { + delete tmp; tmp = new PBlock(PBlock::BL_SEQ); FILE_NAME(tmp, @1); } - if ($6) tmp->set_statement(*$6); - delete $6; - check_end_label(@8, "block", $2, $8); + if (items->statements) tmp->set_statement(*items->statements); + check_end_label(@6, "block", $2, $6); delete[]$2; $$ = tmp; } @@ -7335,36 +7349,25 @@ statement_item /* This is roughly statement_item in the LRM */ { PBlock*tmp = pform_push_block_scope(@1, $2, PBlock::BL_PAR); current_block_stack.push(tmp); } - block_item_decls_opt - { - if (!$2) { - if ($4) { - pform_requires_sv(@4, "Variable declaration in unnamed block"); - } else { - /* If there are no declarations in the scope then just delete it. */ - pform_pop_scope(); - assert(! current_block_stack.empty()); - PBlock*tmp = current_block_stack.top(); - current_block_stack.pop(); - delete tmp; - } + block_item_or_statement_list_opt join_keyword label_opt + { std::unique_ptr items($4); + if (!$2 && items->has_decls) { + pform_requires_sv(@4, "Variable declaration in unnamed block"); } - } - statement_or_null_list_opt join_keyword label_opt - { PBlock*tmp; - if ($2 || $4) { - pform_pop_scope(); - assert(! current_block_stack.empty()); - tmp = current_block_stack.top(); - current_block_stack.pop(); - tmp->set_join_type($7); + bool keep_scope = $2 || items->has_decls; + pform_pop_block_scope(keep_scope); + assert(! current_block_stack.empty()); + auto tmp = current_block_stack.top(); + current_block_stack.pop(); + if (keep_scope) { + tmp->set_join_type($5); } else { - tmp = new PBlock($7); + delete tmp; + tmp = new PBlock($5); FILE_NAME(tmp, @1); } - if ($6) tmp->set_statement(*$6); - delete $6; - check_end_label(@8, "fork", $2, $8); + if (items->statements) tmp->set_statement(*items->statements); + check_end_label(@6, "fork", $2, $6); delete[]$2; $$ = tmp; } @@ -7671,24 +7674,56 @@ compressed_statement } ; -statement_or_null_list_opt - : statement_or_null_list - { $$ = $1; } - | - { $$ = 0; } - ; - -statement_or_null_list - : statement_or_null_list statement_or_null - { std::vector*tmp = $1; - if ($2) tmp->push_back($2); - $$ = tmp; +block_item_or_statement_list + : block_item_decl + { $$ = make_procedural_item_list(); + procedural_item_list_add_declaration(@1, $$, nullptr); } | statement_or_null - { std::vector*tmp = new std::vector(0); - if ($1) tmp->push_back($1); - $$ = tmp; + { $$ = make_procedural_item_list(); + procedural_item_list_add_statement(@1, $$, $1); } + | block_item_or_statement_list block_item_decl + { procedural_item_list_add_declaration(@2, $1, nullptr); + $$ = $1; + } + | block_item_or_statement_list statement_or_null + { procedural_item_list_add_statement(@2, $1, $2); + $$ = $1; + } + ; + +block_item_or_statement_list_opt + : block_item_or_statement_list + { $$ = $1; } + | + { $$ = make_procedural_item_list(); } + ; + +tf_item_or_statement_list + : tf_item_declaration + { $$ = make_procedural_item_list(); + procedural_item_list_add_declaration(@1, $$, $1); + } + | statement_or_null + { $$ = make_procedural_item_list(); + procedural_item_list_add_statement(@1, $$, $1); + } + | tf_item_or_statement_list tf_item_declaration + { procedural_item_list_add_declaration(@2, $1, $2); + $$ = $1; + } + | tf_item_or_statement_list statement_or_null + { procedural_item_list_add_statement(@2, $1, $2); + $$ = $1; + } + ; + +tf_item_or_statement_list_opt + : tf_item_or_statement_list + { $$ = $1; } + | + { $$ = make_procedural_item_list(); } ; analog_statement diff --git a/pform.cc b/pform.cc index 41f093c06..1131de46a 100644 --- a/pform.cc +++ b/pform.cc @@ -487,6 +487,29 @@ static void add_local_symbol(LexicalScope*scope, perm_string name, PNamedItem*it scope->local_symbols[name] = item; } +/* + * Pop the scope used while parsing a block. If the block does not keep that + * scope, move any nested named scopes to the enclosing scope before the caller + * discards it. + */ +void pform_pop_block_scope(bool keep_scope) +{ + auto scope = lexical_scope; + assert(scope); + + pform_pop_scope(); + + if (keep_scope) + return; + + for (const auto& symbol : scope->local_symbols) { + auto nested_scope = dynamic_cast (symbol.second); + assert(nested_scope); + nested_scope->set_parent_scope(lexical_scope); + add_local_symbol(lexical_scope, symbol.first, symbol.second); + } +} + static void check_potential_imports(const struct vlltype&loc, perm_string name, bool tf_call) { LexicalScope*scope = lexical_scope; diff --git a/pform.h b/pform.h index 598eb3988..0e0988fc8 100644 --- a/pform.h +++ b/pform.h @@ -251,6 +251,7 @@ extern PNBTrigger* pform_new_nb_trigger(const struct vlltype&loc, * deletes it. Thus, the string pushed must be allocated. */ extern void pform_pop_scope(); +extern void pform_pop_block_scope(bool keep_scope); /* * Peek at the current (most recently active) scope.