From 2dddacda8e0a2a422a34fdd78e6b33fd05b40584 Mon Sep 17 00:00:00 2001 From: Ajay Brahmakshatriya Date: Wed, 23 Sep 2026 14:55:48 -0400 Subject: [PATCH] Normalize loop backedges across sibling scopes Add a normalization pass that copies the continuation of a loop label into out-of-scope backedges, restoring the structure expected by the loop finder. Hoist declarations copied during normalization, retain labels with non-local users, and add sample67 regression coverage. --- include/blocks/declaration_utils.h | 13 ++ include/blocks/loop_normalizer.h | 10 + samples/outputs.var_names/sample67 | 49 ++++ samples/outputs/sample67 | 49 ++++ samples/sample67.cpp | 66 ++++++ src/blocks/declaration_utils.cpp | 86 +++++++ src/blocks/for_loop_finder.cpp | 73 +----- src/blocks/loop_finder.cpp | 6 + src/blocks/loop_normalizer.cpp | 348 +++++++++++++++++++++++++++++ src/builder/builder_context.cpp | 2 + 10 files changed, 632 insertions(+), 70 deletions(-) create mode 100644 include/blocks/declaration_utils.h create mode 100644 include/blocks/loop_normalizer.h create mode 100644 samples/outputs.var_names/sample67 create mode 100644 samples/outputs/sample67 create mode 100644 samples/sample67.cpp create mode 100644 src/blocks/declaration_utils.cpp create mode 100644 src/blocks/loop_normalizer.cpp diff --git a/include/blocks/declaration_utils.h b/include/blocks/declaration_utils.h new file mode 100644 index 0000000..87d7c59 --- /dev/null +++ b/include/blocks/declaration_utils.h @@ -0,0 +1,13 @@ +#ifndef DECLARATION_UTILS_H +#define DECLARATION_UTILS_H + +#include "blocks/stmt.h" + +namespace block { + +bool is_splittable(decl_stmt::Ptr decl); +std::vector find_declarations(stmt::Ptr scope); +void split_declarations(stmt::Ptr scope, std::vector &declarations); + +} // namespace block +#endif diff --git a/include/blocks/loop_normalizer.h b/include/blocks/loop_normalizer.h new file mode 100644 index 0000000..6a794da --- /dev/null +++ b/include/blocks/loop_normalizer.h @@ -0,0 +1,10 @@ +#ifndef LOOP_NORMALIZER_H +#define LOOP_NORMALIZER_H + +#include "blocks/stmt.h" + +namespace block { +void normalize_loop_backedges(stmt::Ptr ast); +} + +#endif diff --git a/samples/outputs.var_names/sample67 b/samples/outputs.var_names/sample67 new file mode 100644 index 0000000..c1efe6b --- /dev/null +++ b/samples/outputs.var_names/sample67 @@ -0,0 +1,49 @@ +int generated_same_body (int arg0) { + int i_0 = 0; + int z_1 = 0; + if (i_0 < arg0) { + z_1 = z_1 + 1; + i_0 = i_0 + 1; + } + while (i_0 < arg0) { + z_1 = z_1 + 1; + i_0 = i_0 + 1; + } + return z_1; +} + +int generated_different_prefix (int arg0) { + int i_0 = 0; + int a_1 = 0; + int b_2 = 0; + if (i_0 < arg0) { + b_2 = b_2 + 1; + i_0 = i_0 + 1; + } + while (i_0 < arg0) { + a_1 = a_1 + 1; + i_0 = i_0 + 1; + } + int var3 = a_1 + b_2; + return var3; +} + +int generated_followed_by_loop (int arg0) { + int i_0 = 0; + int j_1 = 0; + int z_2 = 0; + if (i_0 < arg0) { + z_2 = z_2 + 1; + i_0 = i_0 + 1; + } + while (i_0 < arg0) { + z_2 = z_2 + 1; + i_0 = i_0 + 1; + } + while (j_1 < arg0) { + z_2 = z_2 + 1; + j_1 = j_1 + 1; + } + return z_2; +} + diff --git a/samples/outputs/sample67 b/samples/outputs/sample67 new file mode 100644 index 0000000..a8627bb --- /dev/null +++ b/samples/outputs/sample67 @@ -0,0 +1,49 @@ +int generated_same_body (int arg0) { + int var0 = 0; + int var1 = 0; + if (var0 < arg0) { + var1 = var1 + 1; + var0 = var0 + 1; + } + while (var0 < arg0) { + var1 = var1 + 1; + var0 = var0 + 1; + } + return var1; +} + +int generated_different_prefix (int arg0) { + int var0 = 0; + int var1 = 0; + int var2 = 0; + if (var0 < arg0) { + var2 = var2 + 1; + var0 = var0 + 1; + } + while (var0 < arg0) { + var1 = var1 + 1; + var0 = var0 + 1; + } + int var3 = var1 + var2; + return var3; +} + +int generated_followed_by_loop (int arg0) { + int var0 = 0; + int var1 = 0; + int var2 = 0; + if (var0 < arg0) { + var2 = var2 + 1; + var0 = var0 + 1; + } + while (var0 < arg0) { + var2 = var2 + 1; + var0 = var0 + 1; + } + while (var1 < arg0) { + var2 = var2 + 1; + var1 = var1 + 1; + } + return var2; +} + diff --git a/samples/sample67.cpp b/samples/sample67.cpp new file mode 100644 index 0000000..b8be689 --- /dev/null +++ b/samples/sample67.cpp @@ -0,0 +1,66 @@ +#include "blocks/c_code_generator.h" +#include "builder/builder_context.h" +#include "builder/dyn_var.h" +#include + +using builder::dyn_var; + +static dyn_var same_body(dyn_var n) { + dyn_var i(0); + dyn_var z(0); + if (i < n) { + z = z + 1; + i = i + 1; + } + while (i < n) { + z = z + 1; + i = i + 1; + } + return z; +} + +static dyn_var different_prefix(dyn_var n) { + dyn_var i(0); + dyn_var a(0); + dyn_var b(0); + if (i < n) { + b = b + 1; + i = i + 1; + } + while (i < n) { + a = a + 1; + i = i + 1; + } + return a + b; +} + +static dyn_var followed_by_loop(dyn_var n) { + dyn_var i(0); + dyn_var j(0); + dyn_var z(0); + if (i < n) { + z = z + 1; + i = i + 1; + } + while (i < n) { + z = z + 1; + i = i + 1; + } + while (j < n) { + z = z + 1; + j = j + 1; + } + return z; +} + +int main() { + builder::builder_context context; + auto same_body_ast = context.extract_function_ast(same_body, "generated_same_body"); + block::c_code_generator::generate_code(same_body_ast, std::cout, 0); + + auto different_prefix_ast = context.extract_function_ast(different_prefix, "generated_different_prefix"); + block::c_code_generator::generate_code(different_prefix_ast, std::cout, 0); + + auto followed_by_loop_ast = context.extract_function_ast(followed_by_loop, "generated_followed_by_loop"); + block::c_code_generator::generate_code(followed_by_loop_ast, std::cout, 0); +} diff --git a/src/blocks/declaration_utils.cpp b/src/blocks/declaration_utils.cpp new file mode 100644 index 0000000..274a15d --- /dev/null +++ b/src/blocks/declaration_utils.cpp @@ -0,0 +1,86 @@ +#include "blocks/declaration_utils.h" +#include "blocks/block_visitor.h" + +namespace block { +namespace { + +class declaration_finder : public block_visitor { +public: + using block_visitor::visit; + std::vector declarations; + + void visit(decl_stmt::Ptr declaration) override { + for (auto existing : declarations) + if (existing->decl_var == declaration->decl_var) + return; + declarations.push_back(declaration); + } +}; + +static bool contains_var(const std::vector &declarations, var::Ptr var) { + for (auto declaration : declarations) + if (declaration->decl_var == var) + return true; + return false; +} + +class declaration_splitter : public block_visitor { + std::vector &declarations; + +public: + using block_visitor::visit; + explicit declaration_splitter(std::vector &declarations) : declarations(declarations) {} + + void visit(stmt_block::Ptr block) override { + for (unsigned int i = 0; i < block->stmts.size();) { + if (!isa(block->stmts[i]) || + !contains_var(declarations, to(block->stmts[i])->decl_var)) { + block->stmts[i]->accept(this); + i++; + continue; + } + + auto declaration = to(block->stmts[i]); + if (!declaration->init_expr) { + block->stmts.erase(block->stmts.begin() + i); + continue; + } + + auto lhs = std::make_shared(); + lhs->static_offset = declaration->static_offset; + lhs->var1 = declaration->decl_var; + auto assignment = std::make_shared(); + assignment->static_offset = declaration->static_offset; + assignment->var1 = lhs; + assignment->expr1 = declaration->init_expr; + auto replacement = std::make_shared(); + replacement->static_offset = declaration->static_offset; + replacement->metadata_map = declaration->metadata_map; + replacement->annotation = declaration->annotation; + replacement->expr1 = assignment; + block->stmts[i] = replacement; + i++; + } + } +}; + +} // namespace + +bool is_splittable(decl_stmt::Ptr decl) { + type::Ptr type = decl->decl_var->var_type; + return !decl->is_typedef && !decl->is_extern && !decl->is_static && !type->is_const && + !isa(type) && !isa(type) && !isa(type); +} + +std::vector find_declarations(stmt::Ptr scope) { + declaration_finder finder; + scope->accept(&finder); + return finder.declarations; +} + +void split_declarations(stmt::Ptr scope, std::vector &declarations) { + declaration_splitter splitter(declarations); + scope->accept(&splitter); +} + +} // namespace block diff --git a/src/blocks/for_loop_finder.cpp b/src/blocks/for_loop_finder.cpp index 5f96dba..c8a6338 100644 --- a/src/blocks/for_loop_finder.cpp +++ b/src/blocks/for_loop_finder.cpp @@ -1,24 +1,8 @@ #include "blocks/for_loop_finder.h" +#include "blocks/declaration_utils.h" namespace block { -class declaration_finder : public block_visitor { -public: - using block_visitor::visit; - std::vector declarations; - - void visit(decl_stmt::Ptr stmt) override { - declarations.push_back(stmt); - block_visitor::visit(stmt); - } -}; - -static bool is_splittable(decl_stmt::Ptr decl) { - type::Ptr type = decl->decl_var->var_type; - return !decl->is_typedef && !decl->is_extern && !decl->is_static && !type->is_const && - !isa(type) && !isa(type) && !isa(type); -} - static bool contains_var(const std::vector &decls, var::Ptr var) { for (auto decl : decls) if (decl->decl_var == var) @@ -27,10 +11,7 @@ static bool contains_var(const std::vector &decls, var::Ptr var) } static bool find_declarations_to_hoist(stmt::Ptr body, expr::Ptr update, std::vector &result) { - declaration_finder declarations; - body->accept(&declarations); - - for (auto decl : declarations.declarations) { + for (auto decl : find_declarations(body)) { var_use_finder uses; uses.to_find = decl->decl_var; update->accept(&uses); @@ -44,53 +25,6 @@ static bool find_declarations_to_hoist(stmt::Ptr body, expr::Ptr update, std::ve return true; } -class declaration_splitter : public block_visitor { - std::vector &to_split; - - bool should_split(decl_stmt::Ptr decl) { - return contains_var(to_split, decl->decl_var); - } - -public: - using block_visitor::visit; - - declaration_splitter(std::vector &to_split) : to_split(to_split) {} - - void visit(stmt_block::Ptr block) override { - for (unsigned int i = 0; i < block->stmts.size();) { - if (!isa(block->stmts[i]) || !should_split(to(block->stmts[i]))) { - block->stmts[i]->accept(this); - i++; - continue; - } - - decl_stmt::Ptr decl = to(block->stmts[i]); - if (decl->init_expr == nullptr) { - block->stmts.erase(block->stmts.begin() + i); - continue; - } - - auto lhs = std::make_shared(); - lhs->static_offset = decl->static_offset; - lhs->var1 = decl->decl_var; - - auto assign = std::make_shared(); - assign->static_offset = decl->static_offset; - assign->var1 = lhs; - assign->expr1 = decl->init_expr; - - auto replacement = std::make_shared(); - replacement->static_offset = decl->static_offset; - replacement->metadata_map = decl->metadata_map; - replacement->annotation = decl->annotation; - replacement->expr1 = assign; - - block->stmts[i] = replacement; - i++; - } - } -}; - static bool use_update(expr::Ptr candidate, expr::Ptr &update) { if (update == nullptr) { update = candidate; @@ -303,8 +237,7 @@ void for_loop_finder::visit(stmt_block::Ptr a) { for_loop->update = ce; } - declaration_splitter splitter(declarations_to_hoist); - loop->body->accept(&splitter); + split_declarations(loop->body, declarations_to_hoist); for_loop->body = loop->body; new_stmts.push_back(for_loop); diff --git a/src/blocks/loop_finder.cpp b/src/blocks/loop_finder.cpp index 36ab18f..582559b 100644 --- a/src/blocks/loop_finder.cpp +++ b/src/blocks/loop_finder.cpp @@ -224,6 +224,12 @@ void loop_finder::visit_label(label_stmt::Ptr a, stmt_block::Ptr parent) { } if (last_stmt == nullptr) { + last_jump_finder global_jump_finder; + global_jump_finder.jump_label = a->label1; + ast->accept(&global_jump_finder); + if (global_jump_finder.has_jump_to) + return; + // This label was created but has no jump. // this currently happens when two statements have the same tag // For now we will just delete this label diff --git a/src/blocks/loop_normalizer.cpp b/src/blocks/loop_normalizer.cpp new file mode 100644 index 0000000..3df8920 --- /dev/null +++ b/src/blocks/loop_normalizer.cpp @@ -0,0 +1,348 @@ +#include "blocks/loop_normalizer.h" +#include "blocks/block_visitor.h" +#include "blocks/declaration_utils.h" +#include +#include +#include + +namespace block { +namespace { + +struct path_entry { + stmt_block::Ptr block; + unsigned int index; +}; + +class statement_path_finder : public block_visitor { + stmt::Ptr target; + std::vector current_path; + +public: + using block_visitor::visit; + std::vector result; + explicit statement_path_finder(stmt::Ptr target) : target(target) {} + + void visit(stmt_block::Ptr block) override { + if (!result.empty()) + return; + for (unsigned int i = 0; i < block->stmts.size(); i++) { + current_path.push_back({block, i}); + if (block->stmts[i] == target) { + result = current_path; + return; + } + block->stmts[i]->accept(this); + if (!result.empty()) + return; + current_path.pop_back(); + } + } +}; + +static std::vector find_statement_path(stmt::Ptr ast, stmt::Ptr target) { + statement_path_finder finder(target); + ast->accept(&finder); + return finder.result; +} + +class label_collector : public block_visitor { +public: + using block_visitor::visit; + std::vector labels; + void visit(label_stmt::Ptr stmt) override { + labels.push_back(stmt); + } +}; + +class target_goto_collector : public block_visitor { +public: + using block_visitor::visit; + label::Ptr target; + std::vector gotos; + void visit(goto_stmt::Ptr stmt) override { + if (stmt->label1 == target) + gotos.push_back(stmt); + } +}; + +static std::vector find_gotos(stmt::Ptr scope, label::Ptr target) { + target_goto_collector collector; + collector.target = target; + scope->accept(&collector); + return collector.gotos; +} + +struct normalization_candidate { + label_stmt::Ptr target; + std::vector path; + unsigned int common_parent_index = 0; + std::vector external_gotos; +}; + +static normalization_candidate find_candidate(stmt::Ptr ast, const std::unordered_set &skipped) { + label_collector collector; + ast->accept(&collector); + for (auto target : collector.labels) { + if (skipped.find(target->label1) != skipped.end()) + continue; + auto path = find_statement_path(ast, target); + assert(!path.empty()); + auto all_gotos = find_gotos(ast, target->label1); + if (all_gotos.empty()) + continue; + auto local_gotos = find_gotos(path.back().block, target->label1); + if (local_gotos.size() == all_gotos.size()) + continue; + + unsigned int common_parent_index = path.size() - 1; + while (common_parent_index > 0) { + common_parent_index--; + if (find_gotos(path[common_parent_index].block, target->label1).size() == all_gotos.size()) + break; + } + + std::vector external_gotos; + for (auto jump : all_gotos) { + if (std::find(local_gotos.begin(), local_gotos.end(), jump) == local_gotos.end()) + external_gotos.push_back(jump); + } + normalization_candidate result; + result.target = target; + result.path = path; + result.common_parent_index = common_parent_index; + result.external_gotos = external_gotos; + return result; + } + return {}; +} + +class declaration_collector : public block_visitor { +public: + using block_visitor::visit; + std::vector declarations; + void visit(decl_stmt::Ptr declaration) override { + for (auto existing : declarations) + if (existing->decl_var == declaration->decl_var) + return; + declarations.push_back(declaration); + } +}; + +static bool contains_var(const std::vector &declarations, var::Ptr var) { + for (auto declaration : declarations) + if (declaration->decl_var == var) + return true; + return false; +} + +class declaration_splitter : public block_visitor { + std::vector &declarations; + +public: + using block_visitor::visit; + explicit declaration_splitter(std::vector &declarations) : declarations(declarations) {} + + void visit(stmt_block::Ptr block) override { + for (unsigned int i = 0; i < block->stmts.size();) { + if (!isa(block->stmts[i]) || + !contains_var(declarations, to(block->stmts[i])->decl_var)) { + block->stmts[i]->accept(this); + i++; + continue; + } + auto declaration = to(block->stmts[i]); + if (!declaration->init_expr) { + block->stmts.erase(block->stmts.begin() + i); + continue; + } + auto lhs = std::make_shared(); + lhs->static_offset = declaration->static_offset; + lhs->var1 = declaration->decl_var; + auto assignment = std::make_shared(); + assignment->static_offset = declaration->static_offset; + assignment->var1 = lhs; + assignment->expr1 = declaration->init_expr; + auto replacement = std::make_shared(); + replacement->static_offset = declaration->static_offset; + replacement->metadata_map = declaration->metadata_map; + replacement->annotation = declaration->annotation; + replacement->expr1 = assignment; + block->stmts[i] = replacement; + i++; + } + } +}; + +class copied_label_rewriter : public block_visitor { + unsigned int &label_counter; + +public: + using block_visitor::visit; + std::unordered_map replacements; + explicit copied_label_rewriter(unsigned int &label_counter) : label_counter(label_counter) {} + + label::Ptr replacement_for(label::Ptr original) { + auto found = replacements.find(original); + if (found != replacements.end()) + return found->second; + auto replacement = std::make_shared