From 07cdaf8175679319469919630255aa39a50dead4 Mon Sep 17 00:00:00 2001 From: Amir Date: Mon, 20 Apr 2026 19:15:57 -0500 Subject: [PATCH 01/20] Synth scheduling implementation PR --- analyze/aps-analyze.c | 92 +- analyze/aps-analyze.h | 1 + analyze/aps-dnc.c | 3 +- analyze/aps-dnc.h | 3 +- analyze/apsc.c | 3 + aps2scala/Makefile | 5 +- aps2scala/aps2scala.cc | 13 +- aps2scala/dump-scala.cc | 51 +- aps2scala/dump-scala.h | 1 + codegen/dump.h | 12 + codegen/implement.h | 16 + codegen/synth-impl.cc | 1804 +++++++++++++++++++++++++++++++++++++++ examples/Makefile | 3 + 13 files changed, 1959 insertions(+), 48 deletions(-) create mode 100644 codegen/synth-impl.cc diff --git a/analyze/aps-analyze.c b/analyze/aps-analyze.c index 63afa5af..10791186 100644 --- a/analyze/aps-analyze.c +++ b/analyze/aps-analyze.c @@ -17,6 +17,7 @@ Several phases: // Boolean indicating whether to use SCC chunk scheduling bool static_scc_schedule = false; +bool anc_analysis = false; static void *analyze_thing(void *ignore, void *node) { @@ -29,66 +30,75 @@ static void *analyze_thing(void *ignore, void *node) { default: break; case KEYmodule_decl: - s = compute_dnc(decl); - if (!(d = (s->original_state_dependency = analysis_state_cycle(s)))) - { - // Do nothing; no cycle to remove - s->loop_required = false; - } - else if (!(d & DEPENDENCY_MAYBE_SIMPLE) || !(d & DEPENDENCY_NOT_JUST_FIBER)) - { - printf("Fiber cycle detected (%d); cycle being removed\n", d); + s = compute_dnc(decl, anc_analysis); + if (anc_analysis) { if (cycle_debug & PRINT_CYCLE) { print_cycles(s, stdout); } - break_fiber_cycles(decl, s, d); + d = (s->original_state_dependency = analysis_state_cycle(s)); s->loop_required = !(d & DEPENDENCY_MAYBE_SIMPLE); - d = no_dependency; // clear dependency - } - else - { - aps_error(decl, "Unable to handle dependency (%d); Attribute grammar is not DNC", d); - return NULL; - } - - // If SCC scheduling is in-progress - if (static_scc_schedule) - { - // Pure fiber cycles should have been broken when reaching this step - // If there is a non-monotone cycle, then scheduling is no longer possible - if (d & DEPENDENCY_MAYBE_SIMPLE) + } else { + if (!(d = (s->original_state_dependency = analysis_state_cycle(s)))) + { + // Do nothing; no cycle to remove + s->loop_required = false; + } + else if (!(d & DEPENDENCY_MAYBE_SIMPLE) || !(d & DEPENDENCY_NOT_JUST_FIBER)) { + printf("Fiber cycle detected (%d); cycle being removed\n", d); if (cycle_debug & PRINT_CYCLE) { print_cycles(s, stdout); } - - aps_error(decl, "Non-monotone Cycle detected (%d); Attribute grammar is not COAG", d); + break_fiber_cycles(decl, s, d); + s->loop_required = !(d & DEPENDENCY_MAYBE_SIMPLE); + d = no_dependency; // clear dependency + } + else + { + aps_error(decl, "Unable to handle dependency (%d); Attribute grammar is not DNC", d); return NULL; } - // SCC chunk scheduling supports CRAG without conditional cycles - compute_static_schedule(s); - } - else - { - if (!d) + // If SCC scheduling is in-progress + if (static_scc_schedule) { - // RAG scheduling with conditional cycles - compute_oag(decl, s); // calculate OAG if grammar is DNC - d = analysis_state_cycle(s); // check again for type-3 errors - } + // Pure fiber cycles should have been broken when reaching this step + // If there is a non-monotone cycle, then scheduling is no longer possible + if (d & DEPENDENCY_MAYBE_SIMPLE) + { + if (cycle_debug & PRINT_CYCLE) + { + print_cycles(s, stdout); + } + + aps_error(decl, "Non-monotone Cycle detected (%d); Attribute grammar is not COAG", d); + return NULL; + } - if (d) + // SCC chunk scheduling supports CRAG without conditional cycles + compute_static_schedule(s); + } + else { - if (cycle_debug & PRINT_CYCLE) + if (!d) { - print_cycles(s, stdout); + // RAG scheduling with conditional cycles + compute_oag(decl, s); // calculate OAG if grammar is DNC + d = analysis_state_cycle(s); // check again for type-3 errors } - aps_error(decl, "Cycle detected (%d); Attribute grammar is not OAG", d); - return NULL; + if (d) + { + if (cycle_debug & PRINT_CYCLE) + { + print_cycles(s, stdout); + } + + aps_error(decl, "Cycle detected (%d); Attribute grammar is not OAG", d); + return NULL; + } } } diff --git a/analyze/aps-analyze.h b/analyze/aps-analyze.h index 6a407e8b..b1166f85 100644 --- a/analyze/aps-analyze.h +++ b/analyze/aps-analyze.h @@ -7,6 +7,7 @@ (STATE*)(Declaration_info(md)->analysis_state) extern bool static_scc_schedule; +extern bool anc_analysis; extern void analyze_Program(Program); /* decorate modules with STATE */ diff --git a/analyze/aps-dnc.c b/analyze/aps-dnc.c index 2d49eb81..ea03b2f5 100644 --- a/analyze/aps-dnc.c +++ b/analyze/aps-dnc.c @@ -2828,9 +2828,10 @@ void dnc_close(STATE*s) { } } -STATE *compute_dnc(Declaration module) { +STATE *compute_dnc(Declaration module, bool anc_analysis) { STATE *s=(STATE *)HALLOC(sizeof(STATE)); Declaration_info(module)->analysis_state = s; + s->anc_analysis = anc_analysis; init_analysis_state(s,module); dnc_close(s); if (analysis_debug & (DNC_ITERATE|DNC_FINAL)) { diff --git a/analyze/aps-dnc.h b/analyze/aps-dnc.h index c2699a04..8552667b 100644 --- a/analyze/aps-dnc.h +++ b/analyze/aps-dnc.h @@ -111,6 +111,7 @@ typedef struct analysis_state { VECTOR(FIBER) fibers; CYCLES cycles; BOOL loop_required; + BOOL anc_analysis; DEPENDENCY original_state_dependency; // This is value of analysis_state_cycle // before removing fiber cycle or // linearization of phases in summary graph @@ -129,7 +130,7 @@ extern INSTANCE *get_instance(Declaration attr, FIBER fiber, extern void assert_closed(AUG_GRAPH*); extern void dnc_close(STATE *); -extern STATE *compute_dnc(Declaration module); +extern STATE *compute_dnc(Declaration module, bool anc_analysis); /* Low level routines: use with caution */ extern void free_edge(EDGESET old, AUG_GRAPH *aug_graph); diff --git a/analyze/apsc.c b/analyze/apsc.c index 2e15e5ee..b775deec 100644 --- a/analyze/apsc.c +++ b/analyze/apsc.c @@ -9,6 +9,7 @@ void usage() { fprintf(stderr,"apsc: usage: %s [-DH] [-D...] [-p apspath] file...\n",argv0); fprintf(stderr," schedule APS files (omit '.aps' extension)\n"); fprintf(stderr," -C SCC chunk static scheduling\n"); + fprintf(stderr," -F ANC analysis\n"); fprintf(stderr," -DH print debug options\n"); exit(1); } @@ -26,6 +27,8 @@ int main(int argc,char **argv) { set_aps_path(argv[++i]); } else if (*options == 'C') { static_scc_schedule = true; + } else if (*options == 'F') { + anc_analysis = true; } else usage(); } else { Program p = find_Program(make_string(argv[i])); diff --git a/aps2scala/Makefile b/aps2scala/Makefile index a3d6f91a..f2b08a08 100644 --- a/aps2scala/Makefile +++ b/aps2scala/Makefile @@ -1,7 +1,7 @@ CPP=g++ CPPFLAGS=-Wall -g -Wno-unused-variable -DUSING_CXX -DAPS2SCALA -I../parse -I../analyze -I../codegen -I../utilities -APS2SCALAOBJS = aps2scala.o dump-scala.o implement.o dyn-impl.o static-impl.o static-scc-impl.o +APS2SCALAOBJS = aps2scala.o dump-scala.o implement.o dyn-impl.o static-impl.o static-scc-impl.o synth-impl.o APS2SCALALIBS = ../lib/aps-lib.o ../lib/aps-ag.a ../utilities/utilities.o aps2scala : ${APS2SCALAOBJS} ${APS2SCALALIBS} ${CPP} ${CPPFLAGS} ${APS2SCALAOBJS} ${APS2SCALALIBS} -o aps2scala @@ -11,6 +11,9 @@ ${APS2SCALAOBJS} : dump-scala.h install: aps2scala mv aps2scala ../bin/. +synth-impl.o : ../codegen/synth-impl.cc + ${CPP} -c ${CPPFLAGS} $< -o $@ + static-impl.o : ../codegen/static-impl.cc ${CPP} -c ${CPPFLAGS} $< -o $@ diff --git a/aps2scala/aps2scala.cc b/aps2scala/aps2scala.cc index 5b026603..1e4245c6 100644 --- a/aps2scala/aps2scala.cc +++ b/aps2scala/aps2scala.cc @@ -40,6 +40,7 @@ extern int aps_yyparse(void); Implementation* impl; bool static_schedule = false; bool is_tree_only_program = false; +bool synth_implementation = false; static void* program_is_tree_only(void *scope, void *node) { if (ABSTRACT_APS_tnode_phylum(node) == KEYDeclaration) { @@ -82,6 +83,10 @@ int main(int argc,char **argv) { static_schedule = true; static_scc_schedule = true; continue; + } else if (streq(argv[i],"-F") || streq(argv[i],"--synth")) { + synth_implementation = true; + anc_analysis = true; + continue; } else if (streq(argv[i],"-V") || streq(argv[i],"--verbose")) { ++verbose; continue; @@ -110,8 +115,12 @@ int main(int argc,char **argv) { type_Program(p); traverse_Program(program_is_tree_only, p, p); aps_check_error("type"); - if (static_schedule) { - impl = static_scc_schedule ? static_scc_impl : static_impl; + if (static_schedule || synth_implementation) { + if (static_schedule) { + impl = static_scc_schedule ? static_scc_impl : static_impl; + } else { + impl = synth_impl; + } analyze_Program(p); aps_check_error("analysis"); if (!impl) { diff --git a/aps2scala/dump-scala.cc b/aps2scala/dump-scala.cc index b3910da5..06331578 100644 --- a/aps2scala/dump-scala.cc +++ b/aps2scala/dump-scala.cc @@ -1681,7 +1681,7 @@ void dump_scala_Declaration(Declaration decl,ostream& oss) ++nesting_level; STATE *s = (STATE*)Declaration_info(decl)->analysis_state; - if (static_scc_schedule && s != NULL) + if ((static_scc_schedule || anc_analysis) && s != NULL) { activate_static_circular = s->loop_required; } @@ -2436,6 +2436,24 @@ void dump_collect_Actuals(Type ctype, Actuals as, ostream& o) } } +STATE* current_state = NULL; +AUG_GRAPH* current_aug_graph = NULL; +std::vector synth_functions_states; +SYNTH_FUNCTION_STATE* current_synth_functions_state = NULL; + +static bool find_instance(AUG_GRAPH* aug_graph, Declaration node, Declaration attr, INSTANCE** instance_out) { + int i; + for (i = 0; i < aug_graph->instances.length; i++) { + INSTANCE* instance = &aug_graph->instances.array[i]; + if (instance->node == node && instance->fibered_attr.attr == attr) { + *instance_out = instance; + return true; + } + } + + return false; +} + void dump_Expression(Expression e, ostream& o) { switch (Expression_KEY(e)) { @@ -2455,6 +2473,22 @@ void dump_Expression(Expression e, ostream& o) dump_collect_Actuals(infer_expr_type(e),funcall_actuals(e),o); return; } + + Declaration attr; + if (impl == synth_impl && (attr = attr_ref_p(e)) != nullptr) { + vector visited; + struct Expression_info * index = Expression_info(e); + Declaration node = USE_DECL(value_use_use(first_Actual(funcall_actuals(e)))); + INSTANCE* instance; + if (find_instance(current_aug_graph, node, attr, &instance)) { + impl->dump_synth_instance(instance, o); + return; + } else { + fatal_error("failed to find instance"); + return; + } + } + bool dump_anchor_actual = false; if (should_include_ast_for_objects()) { Expression fexpr = funcall_f(e); @@ -2689,7 +2723,7 @@ string operator+(string s, int i) string indent(int nl) { return string(indent_multiple*nl,' '); } bool check_surrounding_decl(void* node, KEYTYPE_Declaration decl_key, Declaration* result_decl) { - while (node != NULL) { + while (node != NULL && ABSTRACT_APS_tnode_phylum(node) != KEY_ABSTRACT_APS_None) { if (ABSTRACT_APS_tnode_phylum(node) == KEYDeclaration) { Declaration decl = (Declaration)node; if (Declaration_KEY(decl) == decl_key) { @@ -2703,3 +2737,16 @@ bool check_surrounding_decl(void* node, KEYTYPE_Declaration decl_key, Declaratio *result_decl = NULL; return false; } + +bool check_surrounding_node(void* node, KEYTYPE_ABSTRACT_APS_Phylum ast_key, void** result_node) { + while (node != NULL && ABSTRACT_APS_tnode_phylum(node) != KEY_ABSTRACT_APS_None) { + if (ABSTRACT_APS_tnode_phylum(node) == ast_key) { + *result_node = node; + return true; + } + node = tnode_parent(node); + } + + *result_node = NULL; + return false; +} diff --git a/aps2scala/dump-scala.h b/aps2scala/dump-scala.h index 5a17eb3c..7fdc8fd7 100644 --- a/aps2scala/dump-scala.h +++ b/aps2scala/dump-scala.h @@ -17,6 +17,7 @@ extern int verbose; extern int debug; extern bool include_comments; extern bool static_scc_schedule; +extern bool anc_analysis; class Implementation; diff --git a/codegen/dump.h b/codegen/dump.h index 7a024886..2cfc2cdd 100644 --- a/codegen/dump.h +++ b/codegen/dump.h @@ -1,6 +1,8 @@ #ifndef DUMP_H +#include "implement.h" #include #include +#include using std::ostream; using std::string; @@ -196,4 +198,14 @@ inline const output_streams& operator<< } #endif /* APS2SCALA */ +// The following are used for synth function implementation only +extern AUG_GRAPH* current_aug_graph; +extern STATE* current_state; +extern std::vector synth_functions_states; +extern SYNTH_FUNCTION_STATE* current_synth_functions_state; + +// Common code generation utility functions +extern bool check_surrounding_decl(void* node, KEYTYPE_Declaration decl_key, Declaration* result_decl); +extern bool check_surrounding_node(void* node, KEYTYPE_ABSTRACT_APS_Phylum ast_key, void** result_node); + #endif diff --git a/codegen/implement.h b/codegen/implement.h index 22314831..d7f8d3c0 100644 --- a/codegen/implement.h +++ b/codegen/implement.h @@ -26,6 +26,17 @@ struct output_streams; #define LOCAL_UNIQUE_PREFIX(ld) Def_info(value_decl_def(ld))->unique_prefix +typedef struct synth_function_state { + std::string fdecl_name; + INSTANCE* source; + PHY_GRAPH* source_phy_graph; + std::vector regular_dependencies; + std::vector fiber_dependents; + std::vector aug_graphs; + bool is_phylum_instance; + bool is_fiber_evaluation; +} SYNTH_FUNCTION_STATE; + // Abstract class: class Implementation { public: @@ -68,11 +79,16 @@ class Implementation { // if a Declaration has an implementation mark on it, // this function is called to implement its use: virtual void implement_value_use(Declaration vd, ostream&) = 0; + + virtual void dump_synth_instance(INSTANCE*, ostream&) { + fatal_error("Only implemented for synth function codegen"); + } }; extern Implementation *dynamic_impl; extern Implementation *static_impl; extern Implementation *static_scc_impl; +extern Implementation *synth_impl; #define IMPLEMENTATION_MARKS (127<<24) diff --git a/codegen/synth-impl.cc b/codegen/synth-impl.cc new file mode 100644 index 00000000..77efd7e3 --- /dev/null +++ b/codegen/synth-impl.cc @@ -0,0 +1,1804 @@ +#include +#include + +#include +#include +#include +extern "C" { +#include + +#include "aps-ag.h" +} +#include +#include +#include +#include +#include +#include + +#include "dump.h" +#include "implement.h" + +#define LOCAL_VALUE_FLAG (1 << 28) + +#ifdef APS2SCALA +#define DEREF "." +#else +#define DEREF "->" +#endif + +static const string LOOP_VAR = "isInsideFixedPoint"; +static const string PREV_LOOP_VAR = "prevIsInsideFixedPoint"; + +// visit procedures are called: +// visit_n_m +// where n is the number of the phy_graph and m is the phase. +// This does a dispatch to visit_n_m_p +// where p is the production number (0-based constructor index) +#define KEY_BLOCK_ITEM_CONDITION 1 +#define KEY_BLOCK_ITEM_INSTANCE 2 + +struct block_item_base { + int key; + INSTANCE* instance; + struct block_item_base* prev; +}; + +typedef struct block_item_base BlockItem; + +struct block_item_condition { + int key; /* KEY_BLOCK_ITEM_CONDITION */ + INSTANCE* instance; + BlockItem* prev; + Declaration condition; + BlockItem* next_positive; + BlockItem* next_negative; +}; + +struct block_item_instance { + int key; /* KEY_BLOCK_ITEM_INSTANCE */ + INSTANCE* instance; + BlockItem* prev; + BlockItem* next; +}; + +vector current_blocks; +BlockItem* current_scope_block; +vector dumped_conditional_block_items; +vector dumped_instances; +static bool tracking_fiber_convergence = false; + +// Given a block, it prints its linearized schedule as comments in the output stream. +static void print_linearized_block(BlockItem* block, ostream& os) { + if (block != NULL) { + os << indent() << block->instance << "\n"; + if (block->key == KEY_BLOCK_ITEM_CONDITION) { + struct block_item_condition* cond = (struct block_item_condition*)block; + + if (cond->prev != NULL && cond->prev->key != KEY_BLOCK_ITEM_CONDITION) { + os << indent() << cond->prev->instance << "\n"; + } + + os << indent() << "IF\n"; + nesting_level++; + print_linearized_block(cond->next_positive, os); + nesting_level--; + os << indent() << "ELSE\n"; + nesting_level++; + print_linearized_block(cond->next_negative, os); + nesting_level--; + } else { + print_linearized_block(((struct block_item_instance*)block)->next, os); + } + } +} + +static vector sort_instances(AUG_GRAPH* aug_graph) { + vector result; + + int n = aug_graph->instances.length; + int i; + for (i = 0; i < n; i++) { + INSTANCE* instance = &aug_graph->instances.array[i]; + if (!if_rule_p(instance->fibered_attr.attr)) { + result.push_back(instance); + } + } + + for (i = 0; i < n; i++) { + INSTANCE* instance = &aug_graph->instances.array[i]; + if (if_rule_p(instance->fibered_attr.attr)) { + result.push_back(instance); + } + } + + return result; +} + +// Given an augmented dependency graph, it linearize it recursively +static BlockItem* linearize_block_helper(AUG_GRAPH* aug_graph, + const vector& sorted_instances, + bool* scheduled, + CONDITION* cond, + BlockItem* prev, + int remaining, + INSTANCE* aug_graph_instance) { + // impossible merge condition + if (CONDITION_IS_IMPOSSIBLE(*cond)) { + return NULL; + } + + int j; + int n = aug_graph->instances.length; + + for (auto it = sorted_instances.begin(); it != sorted_instances.end(); it++) { + INSTANCE* instance = *it; + int i = instance->index; + + if (scheduled[i]) { + continue; + } + + // impossible merge condition, cannot schedule this instance + if (MERGED_CONDITION_IS_IMPOSSIBLE(*cond, instance_condition(instance))) { + scheduled[i] = true; + BlockItem* result = linearize_block_helper(aug_graph, sorted_instances, scheduled, cond, prev, remaining - 1, aug_graph_instance); + scheduled[i] = false; + return result; + } + + // if there is no dependency between this instance and the augmented dependency instance that we want to linearize for, + // then this instance should not be included in the linearization linked-list + if (aug_graph_instance != instance && !edgeset_kind(aug_graph->graph[instance->index * n + aug_graph_instance->index])) { + scheduled[i] = true; + BlockItem* result = linearize_block_helper(aug_graph, sorted_instances, scheduled, cond, prev, remaining - 1, aug_graph_instance); + scheduled[i] = false; + return result; + } + + // printf("trying to schedule: "); + // print_instance(instance, stdout); + // printf("\n"); + + bool ready_to_schedule = true; + for (j = 0; j < n && ready_to_schedule; j++) { + INSTANCE* other_instance = &aug_graph->instances.array[j]; + + // already scheduled dependency + if (scheduled[j]) { + continue; + } + + // impossible merge condition, ignore this dependency + if (MERGED_CONDITION_IS_IMPOSSIBLE(instance_condition(instance), instance_condition(other_instance))) { + continue; + } + + // not a direct dependency + if (!(edgeset_kind(aug_graph->graph[j * n + i]) & DEPENDENCY_MAYBE_DIRECT)) { + continue; + } + + ready_to_schedule = false; + break; + } + + // if all dependencies are ready to schedule + if (!ready_to_schedule) { + continue; + } + + BlockItem* item_base; + scheduled[i] = true; + + if (if_rule_p(instance->fibered_attr.attr)) { + struct block_item_condition* item = (struct block_item_condition*)malloc(sizeof(struct block_item_condition)); + item_base = (BlockItem*)item; + + item->key = KEY_BLOCK_ITEM_CONDITION; + item->instance = instance; + item->condition = instance->fibered_attr.attr; + item->prev = prev; + + int cmask = 1 << (if_rule_index(instance->fibered_attr.attr)); + cond->positive |= cmask; + item->next_positive = linearize_block_helper(aug_graph, sorted_instances, scheduled, cond, item_base, remaining - 1, aug_graph_instance); + cond->positive &= ~cmask; + cond->negative |= cmask; + item->next_negative = linearize_block_helper(aug_graph, sorted_instances, scheduled, cond, item_base, remaining - 1, aug_graph_instance); + cond->negative &= ~cmask; + } else { + struct block_item_instance* item = (struct block_item_instance*)malloc(sizeof(struct block_item_instance)); + item_base = (BlockItem*)item; + item->key = KEY_BLOCK_ITEM_INSTANCE; + item->instance = instance; + item->prev = prev; + item->next = linearize_block_helper(aug_graph, sorted_instances, scheduled, cond, item_base, remaining - 1, aug_graph_instance); + } + + scheduled[i] = false; + + return item_base; + } + + if (remaining != 0) { + fatal_error("failed to schedule some instances, remaining: %d", remaining); + } + + return NULL; +} + +// Given an augmented dependency graph, it linearizes +// the direct dependency schedule. +static BlockItem* linearize_block(AUG_GRAPH* aug_graph, INSTANCE* aug_graph_instance) { + int n = aug_graph->instances.length; + bool* scheduled = (bool*)alloca(sizeof(bool) * n); + memset(scheduled, 0, sizeof(bool) * n); + + CONDITION cond = {0, 0}; + vector sorted_instances = sort_instances(aug_graph); + + return linearize_block_helper(aug_graph, sorted_instances, scheduled, &cond, NULL, n, aug_graph_instance); +} + +// Given an instance it traverses the direct dependency schedule +// trying to find the instance and if it sees the condition +// along the way, it returns that condition. +static BlockItem* find_surrounding_block(BlockItem* block, INSTANCE* instance) { + while (block != NULL) { + if (block->key == KEY_BLOCK_ITEM_CONDITION) { + return block; + } else if (block->key == KEY_BLOCK_ITEM_INSTANCE) { + if (block->instance == instance) { + return block; + } else { + block = ((struct block_item_instance*)block)->next; + } + } + } + + return NULL; +} + +enum instance_direction custom_instance_direction(INSTANCE* i) { + enum instance_direction dir = fibered_attr_direction(&i->fibered_attr); + if (i->node == NULL) { + return dir; + } else if (DECL_IS_LHS(i->node)) { + return dir; + } else if (DECL_IS_RHS(i->node)) { + return dir; + } else if (DECL_IS_LOCAL(i->node)) { + return instance_local; + } else { + fatal_error("%d: unknown attributed node", tnode_line_number(i->node)); + return dir; /* keep CC happy */ + } +} + +static bool instance_is_local(INSTANCE* instance) { + if (instance->fibered_attr.fiber == NULL) { + return custom_instance_direction(instance) == instance_local; + } else { + return fibered_attr_direction(&instance->fibered_attr) == instance_local; + } +} + +static bool instance_is_synthesized(INSTANCE* instance) { + if (instance->fibered_attr.fiber == NULL) { + return custom_instance_direction(instance) == instance_outward; + } else { + return fibered_attr_direction(&instance->fibered_attr) == instance_outward; + } +} + +static bool instance_is_inherited(INSTANCE* instance) { + if (instance->fibered_attr.fiber == NULL) { + return custom_instance_direction(instance) == instance_inward; + } else { + return fibered_attr_direction(&instance->fibered_attr) == instance_inward; + } +} + +static bool instance_is_pure_shared_info(INSTANCE* instance) { + return instance->fibered_attr.fiber == NULL && ATTR_DECL_IS_SHARED_INFO(instance->fibered_attr.attr); +} + +static std::vector collect_phylum_graph_attr_dependencies(PHY_GRAPH* phylum_graph, + INSTANCE* sink_instance) { + std::vector result; + + int i; + int n = phylum_graph->instances.length; + + for (i = 0; i < n; i++) { + INSTANCE* source_instance = &phylum_graph->instances.array[i]; + if (!instance_is_pure_shared_info(source_instance) && source_instance->index != sink_instance->index && + phylum_graph->mingraph[source_instance->index * n + sink_instance->index]) { + result.push_back(source_instance); + } + } + + return result; +} + +static bool is_function_decl_attribute(INSTANCE* instance) { + if (instance->node != NULL && ABSTRACT_APS_tnode_phylum(instance->node) == KEYDeclaration) { + switch (Declaration_KEY(instance->node)) { + case KEYpragma_call: { + Declaration fdecl = Declaration_info(instance->node)->proxy_fdecl; + switch (Declaration_KEY(fdecl)) { + case KEYfunction_decl: + return true; + default: + break; + } + break; + } + default: + break; + } + } + + return false; +} + +static std::vector collect_aug_graph_attr_dependencies(AUG_GRAPH* aug_graph, + INSTANCE* sink_instance) { + std::vector result; + + int i; + int n = aug_graph->instances.length; + + for (i = 0; i < n; i++) { + INSTANCE* source_instance = &aug_graph->instances.array[i]; + if (!instance_is_pure_shared_info(source_instance) && source_instance->index != sink_instance->index && + !is_function_decl_attribute(source_instance) && + edgeset_kind(aug_graph->graph[source_instance->index * n + sink_instance->index])) { + result.push_back(source_instance); + } + } + + return result; +} + +static vector collect_lhs_aug_graphs(STATE* state, PHY_GRAPH* pgraph) { + vector result; + + int i; + int n = state->match_rules.length; + for (i = 0; i < n; i++) { + AUG_GRAPH* aug_graph = &state->aug_graphs[i]; + PHY_GRAPH* aug_graph_pgraph = Declaration_info(aug_graph->lhs_decl)->node_phy_graph; + + if (aug_graph_pgraph == pgraph) { + switch (Declaration_KEY(aug_graph->lhs_decl)) { + case KEYsome_function_decl: + continue; + default: + break; + } + + result.push_back(aug_graph); + } + } + + return result; +} + +static bool find_instance(AUG_GRAPH* aug_graph, + Declaration node, + FIBERED_ATTRIBUTE& fiber_attr, + INSTANCE** instance_out) { + int i; + for (i = 0; i < aug_graph->instances.length; i++) { + INSTANCE* instance = &aug_graph->instances.array[i]; + if (instance->node == node && instance->fibered_attr.attr == fiber_attr.attr) { + if (fibered_attr_equal(&instance->fibered_attr, &fiber_attr)) { + *instance_out = instance; + return true; + } + } + } + + return false; +} + +string attr_to_string(Declaration attr) { + if (ATTR_DECL_IS_SHARED_INFO(attr)) { + return "sharedinfo"; + } else { + return decl_name(attr); + } +} + +string fiber_to_string(FIBER fiber) { + std::stringstream ss; + + while (fiber != NULL && fiber->field != NULL) { + std::string field = decl_name(fiber->field); + field.erase(std::remove(field.begin(), field.end(), '!'), field.end()); + + ss << field; + fiber = fiber->shorter; + if (fiber->field != NULL) { + ss << "_"; + } + } + + return ss.str(); +} + +string instance_to_string(INSTANCE* in, bool force_include_node_type = false, bool trim_node = false) { + vector result; + + Declaration node = in->node; + if (force_include_node_type && node == NULL) { + node = current_aug_graph->lhs_decl; + } + + if (!trim_node && node != NULL) { + if (Declaration_KEY(node) == KEYpragma_call) { + result.push_back(symbol_name(pragma_call_name(node))); + } else { + result.push_back(decl_name(node)); + } + } + + if (in->fibered_attr.attr != NULL) { + result.push_back(attr_to_string(in->fibered_attr.attr)); + } + + if (in->fibered_attr.fiber != NULL) { + result.push_back(fiber_to_string(in->fibered_attr.fiber)); + } + + return std::accumulate(std::next(result.begin()), result.end(), result[0], + [](std::string a, std::string b) { return a + "_" + b; }); +} + +static string instance_to_string_with_nodetype(Declaration polymorphic, INSTANCE* in) { + Declaration attr = in->fibered_attr.attr; + std::stringstream ss; + + if (Declaration_KEY(attr) == KEYvalue_decl && LOCAL_UNIQUE_PREFIX(attr) != 0) { + ss << "a" << LOCAL_UNIQUE_PREFIX(attr) << "_"; + } + + ss << decl_name(polymorphic) << "_" << instance_to_string(in, false, false); + + return ss.str(); +} + +static string instance_to_attr(INSTANCE* in) { + Declaration attr = in->fibered_attr.attr; + Declaration field = in->fibered_attr.fiber != NULL ? in->fibered_attr.fiber->field : NULL; + std::stringstream ss; + + if (Declaration_KEY(attr) == KEYvalue_decl && LOCAL_UNIQUE_PREFIX(attr) != 0) { + ss << "a" << LOCAL_UNIQUE_PREFIX(attr); + } else { + ss << "a"; + } + + if (attr != NULL && !ATTR_DECL_IS_SHARED_INFO(attr)) { + ss << "_" << decl_name(attr); + } + + if (field != NULL) { + std::string field_str = decl_name(field); + field_str.erase(std::remove(field_str.begin(), field_str.end(), '!'), field_str.end()); + ss << "_" << field_str; + } + + return ss.str(); +} + +static bool check_is_match_formal(void* node) { + Declaration formal_decl = NULL; + bool is_formal = check_surrounding_decl(node, KEYnormal_formal, &formal_decl); + + void* match = NULL; + bool is_inside_match = check_surrounding_node(node, KEYMatch, &match); + + return is_formal && is_inside_match; +} + +// Unified filter for determining which dependencies should be included +// as function parameters and call arguments in synthesized eval functions. +static bool should_skip_synth_dependency(INSTANCE* source_instance) { + if (source_instance->fibered_attr.fiber != NULL) { + return true; + } + if (if_rule_p(source_instance->fibered_attr.attr)) { + return true; + } + if (check_is_match_formal(source_instance->fibered_attr.attr)) { + return true; + } + return false; +} + +static std::vector build_synth_functions_state(STATE* s) { + std::vector synth_function_states; + int i, j; + int aug_graph_count = s->match_rules.length; + + for (i = 0; i < s->phyla.length; i++) { + PHY_GRAPH* pg = &s->phy_graphs[i]; + if (Declaration_KEY(pg->phylum) != KEYphylum_decl) { + continue; + } + + for (j = 0; j < pg->instances.length; j++) { + INSTANCE* in = &pg->instances.array[j]; + bool is_fiber = in->fibered_attr.fiber != NULL; + bool is_shared_info = ATTR_DECL_IS_SHARED_INFO(in->fibered_attr.attr); + + if (!instance_is_synthesized(in)) { + continue; + } + + SYNTH_FUNCTION_STATE* state = new SYNTH_FUNCTION_STATE(); + state->fdecl_name = instance_to_string_with_nodetype(pg->phylum, in); + state->source = in; + state->is_phylum_instance = true; + state->source_phy_graph = pg; + state->is_fiber_evaluation = is_fiber || is_shared_info; + + if (state->is_fiber_evaluation && state->source->node != NULL) { + printf("Warning: Synthesizing fiber evaluation for instance with attributed node, which is not supported yet. Instance: "); + print_instance(in, stdout); + printf("\n"); + } + + state->regular_dependencies = collect_phylum_graph_attr_dependencies(pg, in); + state->aug_graphs = collect_lhs_aug_graphs(s, pg); + + synth_function_states.push_back(state); + } + } + + for (i = 0; i < aug_graph_count; i++) { + AUG_GRAPH* aug_graph = &s->aug_graphs[i]; + + switch (Declaration_KEY(aug_graph->lhs_decl)) { + case KEYsome_function_decl: + continue; + default: + break; + } + + for (j = 0; j < aug_graph->instances.length; j++) { + INSTANCE* instance = &aug_graph->instances.array[j]; + bool is_local = instance_direction(instance) == instance_local; + bool is_fiber = instance->fibered_attr.fiber != NULL; + bool is_shared_info = ATTR_DECL_IS_SHARED_INFO(instance->fibered_attr.attr); + + if (is_local && !is_fiber && !if_rule_p(instance->fibered_attr.attr)) { + switch (Declaration_KEY(instance->fibered_attr.attr)) { + case KEYformal: + continue; + default: + break; + } + + Declaration attr = instance->fibered_attr.attr; + PHY_GRAPH* pg = Declaration_info(aug_graph->lhs_decl)->node_phy_graph; + Declaration tdecl = canonical_type_decl(canonical_type(value_decl_type(attr))); + + SYNTH_FUNCTION_STATE* state = new SYNTH_FUNCTION_STATE(); + state->fdecl_name = instance_to_string_with_nodetype(tdecl, instance); + state->source = instance; + state->is_phylum_instance = false; + state->source_phy_graph = pg; + state->is_fiber_evaluation = is_fiber || is_shared_info; + state->regular_dependencies = collect_aug_graph_attr_dependencies(aug_graph, instance); + + vector aug_graphs; + aug_graphs.push_back(aug_graph); + state->aug_graphs = aug_graphs; + + synth_function_states.push_back(state); + } + } + } + + return synth_function_states; +} + +static void destroy_synth_function_states(const vector& states) { + for (auto it = states.begin(); it != states.end(); it++) { + delete (*it); + } +} + +static void dump_attribute_type(INSTANCE* in, ostream& os) { + CanonicalType* ctype = canonical_type(infer_some_value_decl_type(in->fibered_attr.attr)); + switch (ctype->key) { + case KEY_CANONICAL_USE: { + os << "T_" << decl_name(canonical_type_decl(ctype)); + break; + } + case KEY_CANONICAL_FUNC: { + struct Canonical_function_type* fdecl_ctype = (struct Canonical_function_type*)ctype; + os << "T_" << decl_name(canonical_type_decl(fdecl_ctype->return_type)); + break; + } + default: + break; + } +} + +class FiberDependencyDumper { +public: + static void dump(AUG_GRAPH* aug_graph, INSTANCE* sink, ostream& os) { + + int i, j; + int n = aug_graph->instances.length; + + vector relevant_instances; + + // collect relevant fiber dependencies + for (i = 0; i < n; i++) { + INSTANCE* in = &aug_graph->instances.array[i]; + if (in->node != NULL && Declaration_KEY(in->node) == KEYpragma_call) { + continue; + } + + if (in->index == sink->index) { + continue; + } + + if (edgeset_kind(aug_graph->graph[in->index * n + sink->index])) { + if (in->fibered_attr.fiber != NULL) { + if (instance_is_synthesized(in) || instance_is_local(in)) { + relevant_instances.push_back(in); + } + } + } + } + + if (relevant_instances.empty()) { + return; + } + + bool* scheduled = (bool*)alloca(sizeof(bool) * n); + memset(scheduled, 0, sizeof(bool) * n); + + SccGraph scc_graph; + scc_graph_initialize(&scc_graph, static_cast(relevant_instances.size())); + + // add vertices to the SCC graph + for (auto it = relevant_instances.begin(); it != relevant_instances.end(); it++) { + INSTANCE* in = *it; + scc_graph_add_vertex(&scc_graph, in); + } + + // add edges to the SCC graph + for (auto it1 = relevant_instances.begin(); it1 != relevant_instances.end(); it1++) { + INSTANCE* in1 = *it1; + for (auto it2 = relevant_instances.begin(); it2 != relevant_instances.end(); it2++) { + INSTANCE* in2 = *it2; + if (in1->index == in2->index) { + continue; + } + + if (edgeset_kind(aug_graph->graph[in1->index * n + in2->index])) { + scc_graph_add_edge(&scc_graph, in1, in2); + } + } + } + + SCC_COMPONENTS* components = scc_graph_components(&scc_graph); + + if (include_comments) { + os << indent() << "/* Fiber dependency SCC components:\n"; + nesting_level++; + for (i = 0; i < components->length; i++) { + SCC_COMPONENT* component = components->array[i]; + os << indent() << "Component " << i << ":\n"; + nesting_level++; + for (j = 0; j < component->length; j++) { + INSTANCE* in = (INSTANCE*)component->array[j]; + os << indent() << in << "\n"; + } + nesting_level--; + } + nesting_level--; + os << indent() << "*/\n"; + } + + dump_scc_helper(aug_graph, components, scheduled, os); + + scc_graph_destroy(&scc_graph); + } + +private: + static void dump_scc_helper(AUG_GRAPH* aug_graph, SCC_COMPONENTS* components, bool* scheduled, ostream& os) { + int component_count = components->length; + int i; + for (i = 0; i < component_count; i++) { + SCC_COMPONENT* component = find_next_ready_component(aug_graph, components, scheduled); + + dump_scc_helper_dump(aug_graph, component, scheduled, os); + } + + for (i = 0; i < component_count; i++) { + SCC_COMPONENT* component = components->array[i]; + if (!already_scheduled(aug_graph, component, scheduled)) { + fatal_error("some instances were not scheduled"); + } + } + } + + static void dump_scc_helper_dump(AUG_GRAPH* aug_graph, SCC_COMPONENT* component, bool* scheduled, ostream& os) { + int i; + + if (already_scheduled(aug_graph, component, scheduled)) { + return; + } + + int n = aug_graph->instances.length; + if (component->length == 0) { + return; + } + + for (i = 0; i < component->length; i++) { + INSTANCE* in = (INSTANCE*)component->array[i]; + + if (scheduled[in->index]) { + continue; + } + + bool dependency_ready = true; + for (int j = 0; j < component->length && dependency_ready; j++) { + INSTANCE* dependency_instance = (INSTANCE*)component->array[j]; + if (dependency_instance == in) { + continue; + } + + if (!scheduled[dependency_instance->index] && + edgeset_kind(aug_graph->graph[dependency_instance->index * n + in->index]) & DEPENDENCY_MAYBE_DIRECT) { + dependency_ready = false; + } + } + + if (!dependency_ready) { + continue; + } + + scheduled[in->index] = true; + os << indent(); + impl->dump_synth_instance(in, os); + dumped_conditional_block_items.clear(); + dumped_instances.clear(); + os << "\n"; + + dump_scc_helper_dump(aug_graph, component, scheduled, os); + } + } + + static bool already_scheduled(AUG_GRAPH* aug_graph, SCC_COMPONENT* component, bool* scheduled) { + for (int i = 0; i < component->length; i++) { + INSTANCE* in = (INSTANCE*)component->array[i]; + if (!scheduled[in->index]) { + return false; + } + } + return true; + } + + static SCC_COMPONENT* find_next_ready_component(AUG_GRAPH* aug_graph, + SCC_COMPONENTS* components, + bool* scheduled) { + + int n = aug_graph->instances.length; + + for (int i = 0; i < components->length; i++) { + SCC_COMPONENT* component = components->array[i]; + + // if all instances in the component are scheduled, skip it + if (already_scheduled(aug_graph, component, scheduled)) { + continue; + } + + bool component_ready = true; + for (int j = 0; j < component->length && component_ready; j++) { + INSTANCE* in = (INSTANCE*)component->array[j]; + + for (int k = 0; k < components->length && component_ready; k++) { + SCC_COMPONENT* other_component = components->array[k]; + if (other_component == component) { + continue; + } + + if (already_scheduled(aug_graph, other_component, scheduled)) { + continue; + } + + for (int l = 0; l < other_component->length; l++) { + INSTANCE* other_in = (INSTANCE*)other_component->array[l]; + if (edgeset_kind(aug_graph->graph[other_in->index * n + in->index])) { + component_ready = false; + break; + } + } + } + } + + if (component_ready) { + return component; + } + } + + fatal_error("no more components to schedule"); + return NULL; + } +}; + +#ifdef APS2SCALA +static void dump_synth_functions(STATE* s, ostream& os) +#else /* APS2SCALA */ +static void dump_synth_functions(STATE* s, output_streams& oss) +#endif /* APS2SCALA */ +{ +#ifdef APS2SCALA + ostream& oss = os; +#else /* !APS2SCALA */ + ostream& hs = oss.hs; + ostream& cpps = oss.cpps; + ostream& os = inline_definitions ? hs : cpps; + +#endif /* APS2SCALA */ + // first dump all visit functions for each phylum: + + os << "\n"; + + int i; + int aug_graph_count = s->match_rules.length; + current_state = s; + synth_functions_states = build_synth_functions_state(s); + bool needs_fixed_point = s->loop_required; + + for (auto state_it = synth_functions_states.begin(); state_it != synth_functions_states.end(); state_it++) { + SYNTH_FUNCTION_STATE* synth_functions_state = *state_it; + current_synth_functions_state = synth_functions_state; + + if (include_comments) { + os << indent() << "// " << synth_functions_state->source << " (" + << (synth_functions_state->is_phylum_instance ? "phylum" : "aug-graph") << ")\n"; + } + if (synth_functions_state->is_fiber_evaluation) { + os << indent() << "val evaluated_map_" << synth_functions_state->fdecl_name + // << " = scala.collection.mutable.Map[T_" << decl_name(synth_functions_state->source_phy_graph->phylum) + << " = scala.collection.mutable.Map[Int" + << ", Boolean]()" + << "\n\n"; + } + + os << indent() << "def eval_" << synth_functions_state->fdecl_name << "("; + os << "node: T_" << decl_name(synth_functions_state->source_phy_graph->phylum); + + for (auto it = synth_functions_state->regular_dependencies.begin(); + it != synth_functions_state->regular_dependencies.end(); it++) { + INSTANCE* source_instance = *it; + if (should_skip_synth_dependency(source_instance)) { + continue; + } + + // for locals, it needs prefix in formals, not for fibers or regular attributes + + os << ",\n"; + os << indent(nesting_level+1); + os << "v_"; + + if (!synth_functions_state->is_phylum_instance) { + os << instance_to_string(source_instance, false, false) << ": "; + } else { + os << instance_to_string(source_instance, false, true) << ": "; + } + + dump_attribute_type(source_instance, os); + } + + os << ")"; + + if (needs_fixed_point) { + os << "(implicit " << LOOP_VAR << ": Boolean, changed: AtomicBoolean)"; + } + + os << ": "; + + if (synth_functions_state->is_fiber_evaluation) { + os << "Unit"; + } else { + dump_attribute_type(synth_functions_state->source, os); + } + os << " = {\n"; + nesting_level++; + + // don't cache if we are in the loop. + if (needs_fixed_point) { + os << indent() << "if (!" << LOOP_VAR << ") {\n"; + nesting_level++; + } + + if (synth_functions_state->is_fiber_evaluation) { + os << indent() << "evaluated_map_" << synth_functions_state->fdecl_name + << ".getOrElse(node.nodeNumber, false) match {\n"; + os << indent(nesting_level + 1) << "case true => "; + os << "return ()\n"; + } else { + os << indent() << instance_to_attr(synth_functions_state->source) + << ".checkNode(node).status match {\n"; + os << indent(nesting_level + 1) << "case Evaluation.ASSIGNED => "; + if (include_comments) { + os << "{\n"; + nesting_level++; + os << indent(nesting_level + 1) << "Debug.out(\"cache hit for \" + node + \" with value \" + " << instance_to_attr(synth_functions_state->source) << ".get(node));\n"; + os << indent(nesting_level + 1); + } + + os << "return " << instance_to_attr(synth_functions_state->source) << ".get(node)\n"; + + if (include_comments) { + nesting_level--; + os << indent(nesting_level + 1) << "}\n"; + } + } + + os << indent(nesting_level + 1) << "case _ => ()\n"; + os << indent() << "};\n"; + + if (needs_fixed_point) { + nesting_level--; + os << indent() << "}\n"; + } + + if (synth_functions_state->is_fiber_evaluation) { + os << indent() << "node match {\n"; + } else { + os << indent() << "val result = node match {\n"; + } + nesting_level++; + + for (auto it = synth_functions_state->aug_graphs.begin(); it != synth_functions_state->aug_graphs.end(); it++) { + AUG_GRAPH* aug_graph = *it; + int n = aug_graph->instances.length; + + current_aug_graph = aug_graph; + current_blocks.push_back(matcher_body(top_level_match_m(aug_graph->match_rule))); + + os << indent() << "case " << matcher_pat(top_level_match_m(aug_graph->match_rule)) << " => {\n"; + nesting_level++; + + INSTANCE* aug_graph_instance = NULL; + if (synth_functions_state->is_phylum_instance) { + if (!find_instance(aug_graph, aug_graph->lhs_decl, synth_functions_state->source->fibered_attr, &aug_graph_instance)) { + fatal_error("something is wrong with instances in aug graph %s", aug_graph_name(aug_graph)); + } + } else { + aug_graph_instance = synth_functions_state->source; + } + + // Linearize the current scope block but make sure IF statements or conditional instances + // that have nothing to do with this instance don't appear in linearization + current_scope_block = linearize_block(aug_graph, aug_graph_instance); + + if (include_comments) { + os << indent() << "/* Linearized schedule:\n"; + nesting_level++; + print_linearized_block(current_scope_block, os); + nesting_level--; + os << indent() << "*/\n"; + } + + int src_idx = synth_functions_state->source->index; + string src_attr = instance_to_attr(synth_functions_state->source); + + bool declared_is_circular = instance_circular(aug_graph_instance); + bool depends_on_itself = edgeset_kind(aug_graph->graph[src_idx * n + src_idx]) != 0; + + if (!declared_is_circular && depends_on_itself) { + aps_warning(aug_graph_instance->node, "Instance %s depends on itself but is not declared circular", instance_to_string(aug_graph_instance).c_str()); + } + + bool dump_fixed_point_loop = declared_is_circular && !instance_is_pure_shared_info(synth_functions_state->source); + string node_get = ATTR_DECL_IS_SHARED_INFO(synth_functions_state->source->fibered_attr.attr) ? "" : "node"; + string node_assign = ATTR_DECL_IS_SHARED_INFO(synth_functions_state->source->fibered_attr.attr) ? "" : "node, "; + + // Open fixed-point loop using shared changed flag for convergence tracking + if (dump_fixed_point_loop) { + os << indent() << "{\n"; + nesting_level++; + os << indent() << "val " << PREV_LOOP_VAR << src_idx << " = " << LOOP_VAR << ";\n"; + os << indent() << "val prevChanged" << src_idx << " = changed;\n"; + os << indent() << "val newChanged" << src_idx << " = new AtomicBoolean(false);\n"; + if (include_comments) { + os << indent() << "var iterCount" << src_idx << " = 0;\n"; + } + os << indent() << "do {\n"; + nesting_level++; + os << indent() << "newChanged" << src_idx << ".set(false);\n"; + if (synth_functions_state->is_fiber_evaluation) { + tracking_fiber_convergence = true; + } + os << indent() << "implicit val " << LOOP_VAR << ": Boolean = true;\n"; + os << indent() << "implicit val changed: AtomicBoolean = newChanged" << src_idx << ";\n"; + } + + // Fiber dependencies (inside loop when looping) + if (synth_functions_state->is_fiber_evaluation) { + if (include_comments && !dump_fixed_point_loop) { + os << "\n"; + } + FiberDependencyDumper::dump(aug_graph, aug_graph_instance, os); + os << indent(); + impl->dump_synth_instance(aug_graph_instance, os); + os << "\n"; + dumped_conditional_block_items.clear(); + dumped_instances.clear(); + } + + // Non-fiber instance computation + if (!synth_functions_state->is_fiber_evaluation) { + if (dump_fixed_point_loop) { + os << indent() << src_attr << ".assign(" << node_assign; + impl->dump_synth_instance(aug_graph_instance, os); + os << ", changed);\n"; + } else { + os << indent(); + impl->dump_synth_instance(aug_graph_instance, os); + os << "\n"; + } + } + + // Close fixed-point loop + if (dump_fixed_point_loop) { + tracking_fiber_convergence = false; + if (include_comments) { + os << indent() << "iterCount" << src_idx << " += 1;\n"; + os << indent() << "Debug.out(\"fixed-point " << synth_functions_state->fdecl_name + << " node=\" + node + \" iteration=\" + iterCount" << src_idx << ");\n"; + } + nesting_level--; + os << indent() << "} while (newChanged" << src_idx << ".get && !" << PREV_LOOP_VAR << src_idx << ")\n"; + os << indent() << "prevChanged" << src_idx << ".compareAndSet(false, newChanged" << src_idx << ".get);\n"; + if (!synth_functions_state->is_fiber_evaluation) { + os << indent() << src_attr << ".get(" << node_get << ")\n"; + } + nesting_level--; + os << indent() << "}\n"; + } + + current_blocks.clear(); + dumped_conditional_block_items.clear(); + dumped_instances.clear(); + + nesting_level--; + os << indent() << "}\n"; + } + + os << indent() << "case _ => throw new RuntimeException(\"failed pattern matching: \" + node)\n"; + + nesting_level--; + os << indent() << "};\n"; + + if (synth_functions_state->is_fiber_evaluation) { + os << indent() << "evaluated_map_" << synth_functions_state->fdecl_name << ".update(node.nodeNumber, true);\n"; + } else { + os << indent() << instance_to_attr(synth_functions_state->source) << ".assign(node, result);\n"; + os << indent() << instance_to_attr(synth_functions_state->source) << ".get(node);\n"; + } + + if (!synth_functions_state->is_fiber_evaluation) { + os << indent() << "result\n"; + } + + nesting_level--; + os << indent() << "}\n\n"; + } + + destroy_synth_function_states(synth_functions_states); +} + +class SynthScc : public Implementation { + public: + typedef Implementation::ModuleInfo Super; + class ModuleInfo : public Super { + public: + ModuleInfo(Declaration mdecl) : Implementation::ModuleInfo(mdecl) {} + + void note_top_level_match(Declaration tlm, GEN_OUTPUT& oss) { Super::note_top_level_match(tlm, oss); } + + void note_local_attribute(Declaration ld, GEN_OUTPUT& oss) { + Super::note_local_attribute(ld, oss); + Declaration_info(ld)->decl_flags |= LOCAL_ATTRIBUTE_FLAG; + } + + void note_attribute_decl(Declaration ad, GEN_OUTPUT& oss) { + Declaration_info(ad)->decl_flags |= ATTRIBUTE_DECL_FLAG; + Super::note_attribute_decl(ad, oss); + } + + void note_var_value_decl(Declaration vd, GEN_OUTPUT& oss) { Super::note_var_value_decl(vd, oss); } + +#ifdef APS2SCALA + void implement(ostream& os){ +#else /* APS2SCALA */ + void implement(output_streams& oss) { +#endif /* APS2SCALA */ + STATE* s = (STATE*)Declaration_info(module_decl) -> analysis_state; + +#ifdef APS2SCALA + ostream& oss = os; +#else + ostream& hs = oss.hs; + ostream& cpps = oss.cpps; + ostream& os = inline_definitions ? hs : cpps; + // char *name = decl_name(module_decl); +#endif /* APS2SCALA */ + + dump_synth_functions(s, oss); + + bool needs_fixed_point = s->original_state_dependency != 0; + + // Implement finish routine: +#ifdef APS2SCALA + os << indent() << "override def finish() : Unit = {\n"; +#else /* APS2SCALA */ + hs << indent() << "void finish()"; + if (!inline_definitions) { + hs << ";\n"; + cpps << "void " << oss.prefix << "finish()"; + } + INDEFINITION; + os << " {\n"; +#endif /* APS2SCALA */ + ++nesting_level; + + PHY_GRAPH* start_phy_graph = summary_graph_for(s, s->start_phylum); + + if (needs_fixed_point) { + os << indent() << "implicit val " << LOOP_VAR << ": Boolean = false;\n"; + os << indent() << "implicit val changed: AtomicBoolean = new AtomicBoolean(false);\n"; + } + os << indent() << "for (root <- t_" << decl_name(s->start_phylum) << ".nodes) {\n"; + ++nesting_level; + int i; + for (i = 0; i < start_phy_graph->instances.length; i++) { + INSTANCE* in = &start_phy_graph->instances.array[i]; + + if (!instance_is_synthesized(in)) + continue; + + string eval_name = instance_to_string_with_nodetype(s->start_phylum, &start_phy_graph->instances.array[i]); + os << indent() << "eval_" << eval_name << "(root);\n"; + } + --nesting_level; + os << indent() << "}\n"; + +#ifdef APS2SCALA + os << indent() << "super.finish();\n"; +#endif /* ! APS2SCALA */ + --nesting_level; + os << indent() << "};\n"; + + clear_implementation_marks(module_decl); + } +}; + +Super* get_module_info(Declaration m) { + return new ModuleInfo(m); +} + +void implement_function_body(Declaration f, ostream& os) { + dynamic_impl->implement_function_body(f, os); +} + +void implement_value_use(Declaration vd, ostream& os) { + int flags = Declaration_info(vd)->decl_flags; + if (flags & LOCAL_ATTRIBUTE_FLAG) { + int instance_index = Declaration_info(vd)->instance_index; + INSTANCE* instance = ¤t_aug_graph->instances.array[instance_index]; + + Type ty = value_decl_type(vd); + Declaration ctype_decl = canonical_type_decl(canonical_type(ty)); + string target_name = instance_to_string_with_nodetype(ctype_decl, instance); + + os << "eval_" << target_name << "(\n"; + int saved_nesting = nesting_level; + nesting_level = std::max(nesting_level + 2, 1); + os << indent() << "node"; + + // Find matching synth function state and use its dependencies + // for consistent parameter passing with the function signature + for (auto state_it = synth_functions_states.begin(); state_it != synth_functions_states.end(); state_it++) { + SYNTH_FUNCTION_STATE* synth_function_state = *state_it; + if (synth_function_state->fdecl_name == target_name) { + for (auto dep_it = synth_function_state->regular_dependencies.begin(); + dep_it != synth_function_state->regular_dependencies.end(); dep_it++) { + INSTANCE* source_instance = *dep_it; + if (should_skip_synth_dependency(source_instance)) { + continue; + } + os << ",\n" << indent(); + impl->dump_synth_instance(source_instance, os); + } + break; + } + } + + nesting_level = saved_nesting; + os << "\n" << indent() << ")"; + } else if (flags & ATTRIBUTE_DECL_FLAG) { + if (ATTR_DECL_IS_INH(vd)) { + os << "v_" << decl_name(vd); + } else { + os << "a" << "_" << decl_name(vd) << DEREF << "get"; + } + + // os << "a" << "_" << decl_name(vd) << DEREF << "get"; + } else if (flags & LOCAL_VALUE_FLAG) { + os << "v" << LOCAL_UNIQUE_PREFIX(vd) << "_" << decl_name(vd); + } else { + aps_error(vd, "internal_error: What is special about this?"); + } +} + +static Expression default_init(Default def) { + switch (Default_KEY(def)) { + case KEYsimple: + return simple_value(def); + case KEYcomposite: + return composite_initial(def); + default: + return 0; + } +} + +/* Return new array with instance assignments for block. + * If "from" is not NULL, then initialize the new array + * with it. + */ +static vector > make_instance_assignment() { + int n = current_aug_graph->instances.length; + + vector > from(n); + + for (int i = 0; i < n; ++i) { + INSTANCE* in = ¤t_aug_graph->instances.array[i]; + Declaration ad = in->fibered_attr.attr; + if (ad != 0 && in->fibered_attr.fiber == 0 && ABSTRACT_APS_tnode_phylum(ad) == KEYDeclaration) { + // get default! + switch (Declaration_KEY(ad)) { + case KEYattribute_decl: + from[in->index].insert(default_init(attribute_decl_default(ad))); + break; + case KEYvalue_decl: + from[in->index].insert(default_init(value_decl_default(ad))); + break; + default: + break; + } + } + } + + // start from the outer-most and override it with the most inner scope + for (auto it = current_blocks.begin(); it != current_blocks.end(); it++) { + Block block = *it; + vector > array(from); + + // Step #1 clear any existing assignments and insert normal assignments + // Step #2 insert collection assignments + int step = 1; + while (step <= 2) { + Declarations ds = block_body(block); + for (Declaration d = first_Declaration(ds); d; d = DECL_NEXT(d)) { + switch (Declaration_KEY(d)) { + case KEYnormal_assign: { + if (INSTANCE* in = Expression_info(assign_rhs(d))->value_for) { + if (in->index >= n) { + fatal_error("bad index [normal_assign] for instance"); + } + array[in->index].clear(); + if (assign_rhs(d) == NULL) { + printf("Warning: assignment to %s is empty\n", instance_to_string(in).c_str()); + } + + array[in->index].insert(assign_rhs(d)); + } + break; + } + case KEYcollect_assign: { + if (INSTANCE* in = Expression_info(assign_rhs(d))->value_for) { + if (in->index >= n) { + fatal_error("bad index [collection_assign] for instance"); + } + + if (step == 1) { + array[in->index].clear(); + } else { + array[in->index].insert(assign_rhs(d)); + } + } + break; + } + default: + break; + } + } + + step++; + } + + // and repeat if any + from = array; + } + + return from; +} + +void dump_assignment(INSTANCE* in, Expression rhs, ostream& o) { + Declaration ad = in != NULL ? in->fibered_attr.attr : NULL; + Symbol asym = ad ? def_name(declaration_def(ad)) : 0; + bool node_is_syntax = in->node == current_aug_graph->lhs_decl; + + if (in->fibered_attr.fiber != NULL) { + if (rhs == NULL) { + if (include_comments) { + o << "// " << in << "\n"; + } + return; + } + + Declaration assign = (Declaration)tnode_parent(rhs); + Expression lhs = assign_lhs(assign); + Declaration field = 0; + // dump the object containing the field + switch (Expression_KEY(lhs)) { + case KEYvalue_use: + // shared global collection + field = USE_DECL(value_use_use(lhs)); +#ifdef APS2SCALA + o << "a_" << decl_name(field) << "."; + if (debug) + o << "assign"; + else + o << "set"; + o << "(" << rhs; + if (tracking_fiber_convergence) { + o << ", changed"; + } + o << ")"; +#else /* APS2SCALA */ + o << "v_" << decl_name(field) << "="; + switch (Default_KEY(value_decl_default(field))) { + case KEYcomposite: + o << composite_combiner(value_decl_default(field)); + break; + default: + o << as_val(value_decl_type(field)) << "->v_combine"; + break; + } + o << "(v_" << decl_name(field) << "," << rhs << ");\n"; +#endif /* APS2SCALA */ + break; + case KEYfuncall: + field = field_ref_p(lhs); + if (field == 0) + fatal_error("what sort of assignment lhs: %d", tnode_line_number(assign)); + o << "a_" << decl_name(field) << DEREF; + if (debug) + o << "assign"; + else + o << "set"; + o << "(" << field_ref_object(lhs) << "," << rhs; + if (tracking_fiber_convergence) { + o << ", changed"; + } + o << ");\n"; + break; + default: + fatal_error("what sort of assignment lhs: %d", tnode_line_number(assign)); + } + return; + } + + if (in->node == 0 && ad != NULL) { + if (rhs) { + if (Declaration_info(ad)->decl_flags & LOCAL_ATTRIBUTE_FLAG) { + o << "a" << LOCAL_UNIQUE_PREFIX(ad) << "_" << asym << DEREF; + if (debug) + o << "assign"; + else + o << "set"; + o << "(anchor," << rhs << ");\n"; + } else { + int i = LOCAL_UNIQUE_PREFIX(ad); + if (i == 0) { +#ifdef APS2SCALA + if (!def_is_constant(value_decl_def(ad))) { + if (include_comments) { + o << "// v_" << asym << " is assigned/initialized by default.\n"; + } + } else { + if (include_comments) { + o << "// v_" << asym << " is initialized in module.\n"; + } + } +#else + o << "v_" << asym << " = " << rhs << ";\n"; +#endif + } else { + o << "v" << i << "_" << asym << " = " << rhs << "; // local\n"; + } + } + } else { + if (Declaration_KEY(ad) == KEYvalue_decl && !direction_is_collection(value_decl_direction(ad))) { + aps_warning(ad, "Local attribute %s is apparently undefined", decl_name(ad)); + } + if (include_comments) { + o << "// " << in << " is ready now\n"; + } + } + return; + } else if (node_is_syntax) { + if (ATTR_DECL_IS_SHARED_INFO(ad)) { + if (include_comments) { + o << "// shared info for " << decl_name(in->node) << " is ready.\n"; + } + } else if (ATTR_DECL_IS_UP_DOWN(ad)) { + if (include_comments) { + o << "// " << decl_name(in->node) << "." << decl_name(ad) << " implicit.\n"; + } + } else if (rhs) { + if (Declaration_KEY(in->node) == KEYfunction_decl) { + if (direction_is_collection(value_decl_direction(ad))) { + std::cout << "Not expecting collection here!\n"; + o << "v_" << asym << " = somehow_combine(v_" << asym << "," << rhs << ");\n"; + } else { + int i = LOCAL_UNIQUE_PREFIX(ad); + if (i == 0) + o << "v_" << asym << " = " << rhs << "; // function\n"; + else + o << "v" << i << "_" << asym << " = " << rhs << ";\n"; + } + } else { + o << "a_" << asym << DEREF; + if (debug) + o << "assign"; + else + o << "set"; + o << "(v_" << decl_name(in->node) << "," << rhs << ");\n"; + } + } else { + aps_warning(in->node, "Attribute %s.%s is apparently undefined", decl_name(in->node), + symbol_name(asym)); + + if (include_comments) { + o << "// " << in << " is ready.\n"; + } + } + return; + } else if (Declaration_KEY(in->node) == KEYvalue_decl) { + if (rhs) { + // assigning field of object + o << "a_" << asym << DEREF; + if (debug) + o << "assign"; + else + o << "set"; + o << "(v_" << decl_name(in->node) << "," << rhs << ");\n"; + } else { + if (include_comments) { + o << "// " << in << " is ready now.\n"; + } + } + return; + } +} + +void dump_rhs_instance_helper(AUG_GRAPH* aug_graph, BlockItem* item, INSTANCE* instance, ostream& o) { + if (item == NULL) { + if (include_comments) { + o << "// " << instance << " is ready now.\n"; + } + return; + } + + if (item->key == KEY_BLOCK_ITEM_INSTANCE) { + struct block_item_instance* bi = (struct block_item_instance*)item; + vector > all_assignments = make_instance_assignment(); + std::set relevant_assignments = all_assignments[instance->index]; + bool any_assignment_dump = false; + + if (bi->instance == instance || bi->next == NULL) { + if (!relevant_assignments.empty()) { + for (auto it = relevant_assignments.begin(); it != relevant_assignments.end(); it++) { + Expression rhs = *it; + if (rhs == NULL) { + continue; + } + + any_assignment_dump = true; + + if (instance->fibered_attr.fiber != NULL) { + dump_assignment(instance, rhs, o); + } else { + // just dump RHS since synth functions are only interested in RHS, not side-effect + dump_Expression(rhs, o); + } + } + + if (!any_assignment_dump) { + fatal_error("should have dumped an assignment here"); + } + + return; + } + + if (instance->fibered_attr.fiber != NULL) { + // Shared info attribute wasn't assigned in this block, dump its default + auto direction = fibered_attr_direction(&instance->fibered_attr); + auto directionStr = ""; + switch (direction) + { + case instance_inward: + directionStr = "instance_inward"; + break; + case instance_outward: + directionStr = "instance_outward"; + break; + case instance_local: + directionStr = "instance_local"; + break; + default: + break; + } + + o << "/* did not find any assignment for this fiber attribute " << instance << " -> " << directionStr << " <-" <<" */"; + return; + } else { + print_instance(instance, stdout); + printf(" is a non-fiber instance, but no assignment found in this block. %d\n", if_rule_p(instance->fibered_attr.attr)); + fatal_error("crashed since non-fiber instance is missing an assignment"); + } + } else { + dump_rhs_instance_helper(aug_graph, bi->next, instance, o); + return; + } + } else if (item->key == KEY_BLOCK_ITEM_CONDITION) { + struct block_item_condition* cond = (struct block_item_condition*)item; + bool visited_if_stmt = std::find(dumped_conditional_block_items.begin(), dumped_conditional_block_items.end(), item) != dumped_conditional_block_items.end(); + dumped_conditional_block_items.push_back(item); + + switch (ABSTRACT_APS_tnode_phylum(cond->condition)) + { + case KEYDeclaration: + { + Declaration if_stmt = (Declaration)cond->condition; + if (Declaration_KEY(if_stmt) != KEYif_stmt) { + fatal_error("expected if statement, got %s %d", decl_name(if_stmt), Declaration_info(if_stmt)); + } + + if (!edgeset_kind(current_aug_graph->graph[cond->instance->index * current_aug_graph->instances.length + instance->index])) { + printf("\n"); + print_instance(cond->instance, stdout); + printf(" does not affect "); + print_instance(instance, stdout); + printf("\n"); + fatal_error("crashed since instance not affected by condition"); + } + + if (!visited_if_stmt) { + o << "if ("; + dump_Expression(if_stmt_cond(if_stmt), o); + o << ") {\n"; + nesting_level++; + } + current_blocks.push_back(if_stmt_if_true(if_stmt)); + if (!visited_if_stmt) { + o << indent(); + } + + vector dumped_instanced_positive(dumped_instances); + dump_rhs_instance_helper(aug_graph, cond->next_positive, instance, o); + dumped_instances = dumped_instanced_positive; + + if (!visited_if_stmt) { + current_blocks.pop_back(); + o << "\n"; + nesting_level--; + o << indent() << "} else {\n"; + nesting_level++; + } + current_blocks.push_back(if_stmt_if_false(if_stmt)); + if (!visited_if_stmt) { + o << indent(); + } + + vector dumped_instanced_negative(dumped_instances); + dump_rhs_instance_helper(aug_graph, cond->next_negative, instance, o); + dumped_instances = dumped_instanced_negative; + + current_blocks.pop_back(); + if (!visited_if_stmt) { + nesting_level--; + o << "\n"; + o << indent() << "}"; + } + break; + } + case KEYMatch: + { + Match m = (Match)cond->condition; + Pattern p = matcher_pat(m); + Declaration header = Match_info(m)->header; + // if first match in case, we evaluate variable: + if (m == first_Match(case_stmt_matchers(header))) { + Expression e = case_stmt_expr(header); +#ifdef APS2SCALA + // Type ty = infer_expr_type(e); + o << "{\n"; + nesting_level++; + o << indent() << "val node" << instance->index << " = " << e << ";\n"; +#else /* APS2SCALA */ + Type ty = infer_expr_type(e); + o << indent() << ty << " node" << instance->index << " = " << e << ";\n"; +#endif /* APS2SCALA */ + } +#ifdef APS2SCALA + o << indent() << "node" << instance->index << " match {\n"; + nesting_level++; + o << indent() << "case " << p << " => {\n"; +#else /* APS2SCALA */ + o << indent() << "if ("; + dump_Pattern_cond(p, "node" + std::to_string(instance->index), o); + o << ") {\n"; +#endif /* APS2SCALA */ + nesting_level += 1; +#ifndef APS2SCALA + dump_Pattern_bindings(p, o); +#endif /* APS2SCALA */ + Block if_true; + Block if_false; + if_true = matcher_body(m); + if (MATCH_NEXT(m)) { + if_false = 0; //? Why not the nxt match ? + } else { + if_false = case_stmt_default(header); + } + + current_blocks.push_back(if_true); + o << indent(); + dump_rhs_instance_helper(aug_graph, cond->next_positive, instance, o); + o << "\n"; + current_blocks.pop_back(); + +#ifdef APS2SCALA + nesting_level--; + o << indent() << "}\n"; + o << indent() << "case _ => {\n"; + nesting_level++; +#else /* APS2SCALA */ + o << "} else {\n"; +#endif /* APS2SCALA */ + current_blocks.push_back(if_false); + o << indent(); + dump_rhs_instance_helper(aug_graph, cond->next_negative, instance, o); + o << "\n"; + current_blocks.pop_back(); + + nesting_level--; +#ifdef APS2SCALA + o << indent() << "}\n"; + nesting_level--; + o << indent() << "}\n"; + if (m == first_Match(case_stmt_matchers(header))) { + nesting_level--; + o << indent() << "}"; + } +#else /* APS2SCALA */ + o << indent() << "}\n"; +#endif /* APS2SCALA */ + + break; + } + default: + fatal_error("unhandled if statement type"); + break; + } + } +} + +virtual void dump_synth_instance(INSTANCE* instance, ostream& o) override { + bool already_dumped = false; + if (std::find(dumped_instances.begin(), dumped_instances.end(), instance) != dumped_instances.end()) { + already_dumped = true; + } else { + dumped_instances.push_back(instance); + } + + AUG_GRAPH* aug_graph = current_aug_graph; + BlockItem* block = find_surrounding_block(current_scope_block, instance); + + Declaration node = instance->node; + bool is_parent_instance = current_aug_graph->lhs_decl == instance->node; + + bool is_synthesized = instance_is_synthesized(instance); + bool is_inherited = instance_is_inherited(instance); + bool is_circular = edgeset_kind(current_aug_graph->graph[instance->index * current_aug_graph->instances.length + instance->index]); + bool is_match_formal = check_is_match_formal(instance->fibered_attr.attr); + bool is_available = is_match_formal || is_inherited; + + if (is_circular && already_dumped && !is_available) { + o << "/* circular dependency detected for " << instance << ", dumping as attribute access */ "; + + o << instance_to_attr(instance) << ".get("; + if (instance->node == NULL) { + o << "node"; + } else { + o << "v_" << decl_name(instance->node); + } + + o << ")"; + return; + } else if (is_match_formal) { + o << "v_" << instance_to_string(instance, false, current_synth_functions_state->is_phylum_instance); + } else if (is_inherited) { + if (is_parent_instance) { + o << "v_" << instance_to_string(instance, false, current_synth_functions_state->is_phylum_instance); + } else { + // we need to find the assignment and dump the RHS recursive call + dump_rhs_instance_helper(aug_graph, block, instance, o); + } + } else if (is_synthesized) { + if (is_parent_instance) { + dump_rhs_instance_helper(aug_graph, block, instance, o); + } else { + for (auto it = synth_functions_states.begin(); it != synth_functions_states.end(); it++) { + SYNTH_FUNCTION_STATE* synth_function_state = *it; + if (fibered_attr_equal(&synth_function_state->source->fibered_attr, &instance->fibered_attr)) { + o << "eval_" << synth_function_state->fdecl_name << "(\n"; + int saved_nesting = nesting_level; + nesting_level = std::max(nesting_level + 2, 2); + o << indent() << "v_" << decl_name(node); + + std::vector dependencies = synth_function_state->regular_dependencies; + for (auto it = dependencies.begin(); it != dependencies.end(); it++) { + INSTANCE* source_instance = *it; + + if (should_skip_synth_dependency(source_instance)) { + continue; + } + + for (int i = 0; i < current_aug_graph->instances.length; i++) { + INSTANCE* in = ¤t_aug_graph->instances.array[i]; + if (in->node == node && fibered_attr_equal(&in->fibered_attr, &source_instance->fibered_attr)) { + o << ",\n" << indent(); + dump_synth_instance(in, o); + } + } + } + nesting_level = saved_nesting; + + o << "\n" << indent() << ")"; + return; + } + } + + printf("failed to find synth function for instance "); + print_instance(instance, stdout); + printf("\n"); + fatal_error("internal error: failed to find synth function for instance"); + } + } else { + dump_rhs_instance_helper(aug_graph, block, instance, o); + } +} +} +; + +Implementation* synth_impl = new SynthScc(); diff --git a/examples/Makefile b/examples/Makefile index e28dad0a..ed357827 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -15,6 +15,9 @@ default: %.scc.scala : %.aps ${APS2SCALA} -C ${DEBUG_FLAGS} -p ${BASE} $* +%.synth.scala : %.aps + ${APS2SCALA} -F ${DEBUG_FLAGS} -p ${BASE} $* + %.scala : %.aps ${APS2SCALA} ${DEBUG_FLAGS} -p ${BASE} $* From a6a637d0e689b2bf771ab84a977e18d409e3688f Mon Sep 17 00:00:00 2001 From: Amir Hesamian Date: Tue, 21 Apr 2026 01:42:11 +0000 Subject: [PATCH 02/20] simplified the analysis --- analyze/aps-analyze.c | 90 +++++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/analyze/aps-analyze.c b/analyze/aps-analyze.c index 10791186..998eaa5c 100644 --- a/analyze/aps-analyze.c +++ b/analyze/aps-analyze.c @@ -38,67 +38,67 @@ static void *analyze_thing(void *ignore, void *node) } d = (s->original_state_dependency = analysis_state_cycle(s)); s->loop_required = !(d & DEPENDENCY_MAYBE_SIMPLE); - } else { - if (!(d = (s->original_state_dependency = analysis_state_cycle(s)))) + break; + } + if (!(d = (s->original_state_dependency = analysis_state_cycle(s)))) + { + // Do nothing; no cycle to remove + s->loop_required = false; + } + else if (!(d & DEPENDENCY_MAYBE_SIMPLE) || !(d & DEPENDENCY_NOT_JUST_FIBER)) + { + printf("Fiber cycle detected (%d); cycle being removed\n", d); + if (cycle_debug & PRINT_CYCLE) { - // Do nothing; no cycle to remove - s->loop_required = false; + print_cycles(s, stdout); } - else if (!(d & DEPENDENCY_MAYBE_SIMPLE) || !(d & DEPENDENCY_NOT_JUST_FIBER)) + break_fiber_cycles(decl, s, d); + s->loop_required = !(d & DEPENDENCY_MAYBE_SIMPLE); + d = no_dependency; // clear dependency + } + else + { + aps_error(decl, "Unable to handle dependency (%d); Attribute grammar is not DNC", d); + return NULL; + } + + // If SCC scheduling is in-progress + if (static_scc_schedule) + { + // Pure fiber cycles should have been broken when reaching this step + // If there is a non-monotone cycle, then scheduling is no longer possible + if (d & DEPENDENCY_MAYBE_SIMPLE) { - printf("Fiber cycle detected (%d); cycle being removed\n", d); if (cycle_debug & PRINT_CYCLE) { print_cycles(s, stdout); } - break_fiber_cycles(decl, s, d); - s->loop_required = !(d & DEPENDENCY_MAYBE_SIMPLE); - d = no_dependency; // clear dependency - } - else - { - aps_error(decl, "Unable to handle dependency (%d); Attribute grammar is not DNC", d); + + aps_error(decl, "Non-monotone Cycle detected (%d); Attribute grammar is not COAG", d); return NULL; } - // If SCC scheduling is in-progress - if (static_scc_schedule) + // SCC chunk scheduling supports CRAG without conditional cycles + compute_static_schedule(s); + } + else + { + if (!d) { - // Pure fiber cycles should have been broken when reaching this step - // If there is a non-monotone cycle, then scheduling is no longer possible - if (d & DEPENDENCY_MAYBE_SIMPLE) - { - if (cycle_debug & PRINT_CYCLE) - { - print_cycles(s, stdout); - } - - aps_error(decl, "Non-monotone Cycle detected (%d); Attribute grammar is not COAG", d); - return NULL; - } - - // SCC chunk scheduling supports CRAG without conditional cycles - compute_static_schedule(s); + // RAG scheduling with conditional cycles + compute_oag(decl, s); // calculate OAG if grammar is DNC + d = analysis_state_cycle(s); // check again for type-3 errors } - else + + if (d) { - if (!d) + if (cycle_debug & PRINT_CYCLE) { - // RAG scheduling with conditional cycles - compute_oag(decl, s); // calculate OAG if grammar is DNC - d = analysis_state_cycle(s); // check again for type-3 errors + print_cycles(s, stdout); } - if (d) - { - if (cycle_debug & PRINT_CYCLE) - { - print_cycles(s, stdout); - } - - aps_error(decl, "Cycle detected (%d); Attribute grammar is not OAG", d); - return NULL; - } + aps_error(decl, "Cycle detected (%d); Attribute grammar is not OAG", d); + return NULL; } } From 07eb4a230dd1401e101cea9a6f81a6c4818319e3 Mon Sep 17 00:00:00 2001 From: Amir Hesamian Date: Tue, 21 Apr 2026 02:04:01 +0000 Subject: [PATCH 03/20] expose synth internals things to synth only --- aps2scala/dump-scala.cc | 33 ++-------------------------- codegen/dump.h | 6 ------ codegen/implement.h | 19 +++++----------- codegen/synth-impl.cc | 48 ++++++++++++++++++++++++++++++++++------- 4 files changed, 47 insertions(+), 59 deletions(-) diff --git a/aps2scala/dump-scala.cc b/aps2scala/dump-scala.cc index 06331578..4de4da0b 100644 --- a/aps2scala/dump-scala.cc +++ b/aps2scala/dump-scala.cc @@ -2436,24 +2436,6 @@ void dump_collect_Actuals(Type ctype, Actuals as, ostream& o) } } -STATE* current_state = NULL; -AUG_GRAPH* current_aug_graph = NULL; -std::vector synth_functions_states; -SYNTH_FUNCTION_STATE* current_synth_functions_state = NULL; - -static bool find_instance(AUG_GRAPH* aug_graph, Declaration node, Declaration attr, INSTANCE** instance_out) { - int i; - for (i = 0; i < aug_graph->instances.length; i++) { - INSTANCE* instance = &aug_graph->instances.array[i]; - if (instance->node == node && instance->fibered_attr.attr == attr) { - *instance_out = instance; - return true; - } - } - - return false; -} - void dump_Expression(Expression e, ostream& o) { switch (Expression_KEY(e)) { @@ -2474,19 +2456,8 @@ void dump_Expression(Expression e, ostream& o) return; } - Declaration attr; - if (impl == synth_impl && (attr = attr_ref_p(e)) != nullptr) { - vector visited; - struct Expression_info * index = Expression_info(e); - Declaration node = USE_DECL(value_use_use(first_Actual(funcall_actuals(e)))); - INSTANCE* instance; - if (find_instance(current_aug_graph, node, attr, &instance)) { - impl->dump_synth_instance(instance, o); - return; - } else { - fatal_error("failed to find instance"); - return; - } + if (auto* synth = dynamic_cast(impl)) { + if (synth->try_dump_funcall(e, o)) return; } bool dump_anchor_actual = false; diff --git a/codegen/dump.h b/codegen/dump.h index 2cfc2cdd..9a1f80c6 100644 --- a/codegen/dump.h +++ b/codegen/dump.h @@ -198,12 +198,6 @@ inline const output_streams& operator<< } #endif /* APS2SCALA */ -// The following are used for synth function implementation only -extern AUG_GRAPH* current_aug_graph; -extern STATE* current_state; -extern std::vector synth_functions_states; -extern SYNTH_FUNCTION_STATE* current_synth_functions_state; - // Common code generation utility functions extern bool check_surrounding_decl(void* node, KEYTYPE_Declaration decl_key, Declaration* result_decl); extern bool check_surrounding_node(void* node, KEYTYPE_ABSTRACT_APS_Phylum ast_key, void** result_node); diff --git a/codegen/implement.h b/codegen/implement.h index d7f8d3c0..f1b16a8c 100644 --- a/codegen/implement.h +++ b/codegen/implement.h @@ -26,17 +26,6 @@ struct output_streams; #define LOCAL_UNIQUE_PREFIX(ld) Def_info(value_decl_def(ld))->unique_prefix -typedef struct synth_function_state { - std::string fdecl_name; - INSTANCE* source; - PHY_GRAPH* source_phy_graph; - std::vector regular_dependencies; - std::vector fiber_dependents; - std::vector aug_graphs; - bool is_phylum_instance; - bool is_fiber_evaluation; -} SYNTH_FUNCTION_STATE; - // Abstract class: class Implementation { public: @@ -79,10 +68,12 @@ class Implementation { // if a Declaration has an implementation mark on it, // this function is called to implement its use: virtual void implement_value_use(Declaration vd, ostream&) = 0; +}; - virtual void dump_synth_instance(INSTANCE*, ostream&) { - fatal_error("Only implemented for synth function codegen"); - } +class SynthImplementation : public Implementation { + public: + virtual bool try_dump_funcall(Expression, ostream&) = 0; + virtual void dump_synth_instance(INSTANCE*, ostream&) = 0; }; extern Implementation *dynamic_impl; diff --git a/codegen/synth-impl.cc b/codegen/synth-impl.cc index 77efd7e3..53a9f3f5 100644 --- a/codegen/synth-impl.cc +++ b/codegen/synth-impl.cc @@ -19,6 +19,22 @@ extern "C" { #include "dump.h" #include "implement.h" +typedef struct synth_function_state { + std::string fdecl_name; + INSTANCE* source; + PHY_GRAPH* source_phy_graph; + std::vector regular_dependencies; + std::vector fiber_dependents; + std::vector aug_graphs; + bool is_phylum_instance; + bool is_fiber_evaluation; +} SYNTH_FUNCTION_STATE; + +static STATE* current_state = NULL; +static AUG_GRAPH* current_aug_graph = NULL; +static std::vector synth_functions_states; +static SYNTH_FUNCTION_STATE* current_synth_functions_state = NULL; + #define LOCAL_VALUE_FLAG (1 << 28) #ifdef APS2SCALA @@ -30,6 +46,8 @@ extern "C" { static const string LOOP_VAR = "isInsideFixedPoint"; static const string PREV_LOOP_VAR = "prevIsInsideFixedPoint"; +static SynthImplementation* synth_impl_ptr; + // visit procedures are called: // visit_n_m // where n is the number of the phy_graph and m is the phase. @@ -770,7 +788,7 @@ class FiberDependencyDumper { scheduled[in->index] = true; os << indent(); - impl->dump_synth_instance(in, os); + synth_impl_ptr->dump_synth_instance(in, os); dumped_conditional_block_items.clear(); dumped_instances.clear(); os << "\n"; @@ -1035,7 +1053,7 @@ static void dump_synth_functions(STATE* s, output_streams& oss) } FiberDependencyDumper::dump(aug_graph, aug_graph_instance, os); os << indent(); - impl->dump_synth_instance(aug_graph_instance, os); + synth_impl_ptr->dump_synth_instance(aug_graph_instance, os); os << "\n"; dumped_conditional_block_items.clear(); dumped_instances.clear(); @@ -1045,11 +1063,11 @@ static void dump_synth_functions(STATE* s, output_streams& oss) if (!synth_functions_state->is_fiber_evaluation) { if (dump_fixed_point_loop) { os << indent() << src_attr << ".assign(" << node_assign; - impl->dump_synth_instance(aug_graph_instance, os); + synth_impl_ptr->dump_synth_instance(aug_graph_instance, os); os << ", changed);\n"; } else { os << indent(); - impl->dump_synth_instance(aug_graph_instance, os); + synth_impl_ptr->dump_synth_instance(aug_graph_instance, os); os << "\n"; } } @@ -1103,7 +1121,7 @@ static void dump_synth_functions(STATE* s, output_streams& oss) destroy_synth_function_states(synth_functions_states); } -class SynthScc : public Implementation { +class SynthImpl : public SynthImplementation { public: typedef Implementation::ModuleInfo Super; class ModuleInfo : public Super { @@ -1224,7 +1242,7 @@ void implement_value_use(Declaration vd, ostream& os) { continue; } os << ",\n" << indent(); - impl->dump_synth_instance(source_instance, os); + synth_impl_ptr->dump_synth_instance(source_instance, os); } break; } @@ -1713,7 +1731,21 @@ void dump_rhs_instance_helper(AUG_GRAPH* aug_graph, BlockItem* item, INSTANCE* i } } -virtual void dump_synth_instance(INSTANCE* instance, ostream& o) override { +bool try_dump_funcall(Expression e, ostream& o) override { + Declaration attr = attr_ref_p(e); + if (attr == nullptr) return false; + Declaration node = USE_DECL(value_use_use(first_Actual(funcall_actuals(e)))); + FIBERED_ATTRIBUTE fiber_attr = {attr, NULL}; + INSTANCE* instance; + if (find_instance(current_aug_graph, node, fiber_attr, &instance)) { + dump_synth_instance(instance, o); + return true; + } + fatal_error("failed to find instance"); + return false; // unreachable +} + +void dump_synth_instance(INSTANCE* instance, ostream& o) { bool already_dumped = false; if (std::find(dumped_instances.begin(), dumped_instances.end(), instance) != dumped_instances.end()) { already_dumped = true; @@ -1801,4 +1833,4 @@ virtual void dump_synth_instance(INSTANCE* instance, ostream& o) override { } ; -Implementation* synth_impl = new SynthScc(); +Implementation* synth_impl = synth_impl_ptr = new SynthImpl(); From 67f5570a10554720e44a86901d16413332a43862 Mon Sep 17 00:00:00 2001 From: Amir Hesamian Date: Tue, 21 Apr 2026 02:16:08 +0000 Subject: [PATCH 04/20] code improvements --- codegen/synth-impl.cc | 38 +++++++++----------------------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/codegen/synth-impl.cc b/codegen/synth-impl.cc index 53a9f3f5..6b0d99f4 100644 --- a/codegen/synth-impl.cc +++ b/codegen/synth-impl.cc @@ -24,13 +24,11 @@ typedef struct synth_function_state { INSTANCE* source; PHY_GRAPH* source_phy_graph; std::vector regular_dependencies; - std::vector fiber_dependents; std::vector aug_graphs; bool is_phylum_instance; bool is_fiber_evaluation; } SYNTH_FUNCTION_STATE; -static STATE* current_state = NULL; static AUG_GRAPH* current_aug_graph = NULL; static std::vector synth_functions_states; static SYNTH_FUNCTION_STATE* current_synth_functions_state = NULL; @@ -48,11 +46,6 @@ static const string PREV_LOOP_VAR = "prevIsInsideFixedPoint"; static SynthImplementation* synth_impl_ptr; -// visit procedures are called: -// visit_n_m -// where n is the number of the phy_graph and m is the phase. -// This does a dispatch to visit_n_m_p -// where p is the production number (0-based constructor index) #define KEY_BLOCK_ITEM_CONDITION 1 #define KEY_BLOCK_ITEM_INSTANCE 2 @@ -80,10 +73,10 @@ struct block_item_instance { BlockItem* next; }; -vector current_blocks; -BlockItem* current_scope_block; -vector dumped_conditional_block_items; -vector dumped_instances; +static vector current_blocks; +static BlockItem* current_scope_block; +static vector dumped_conditional_block_items; +static vector dumped_instances; static bool tracking_fiber_convergence = false; // Given a block, it prints its linearized schedule as comments in the output stream. @@ -174,10 +167,6 @@ static BlockItem* linearize_block_helper(AUG_GRAPH* aug_graph, return result; } - // printf("trying to schedule: "); - // print_instance(instance, stdout); - // printf("\n"); - bool ready_to_schedule = true; for (j = 0; j < n && ready_to_schedule; j++) { INSTANCE* other_instance = &aug_graph->instances.array[j]; @@ -278,7 +267,7 @@ static BlockItem* find_surrounding_block(BlockItem* block, INSTANCE* instance) { return NULL; } -enum instance_direction custom_instance_direction(INSTANCE* i) { +static enum instance_direction custom_instance_direction(INSTANCE* i) { enum instance_direction dir = fibered_attr_direction(&i->fibered_attr); if (i->node == NULL) { return dir; @@ -422,7 +411,7 @@ static bool find_instance(AUG_GRAPH* aug_graph, return false; } -string attr_to_string(Declaration attr) { +static string attr_to_string(Declaration attr) { if (ATTR_DECL_IS_SHARED_INFO(attr)) { return "sharedinfo"; } else { @@ -430,7 +419,7 @@ string attr_to_string(Declaration attr) { } } -string fiber_to_string(FIBER fiber) { +static string fiber_to_string(FIBER fiber) { std::stringstream ss; while (fiber != NULL && fiber->field != NULL) { @@ -447,7 +436,7 @@ string fiber_to_string(FIBER fiber) { return ss.str(); } -string instance_to_string(INSTANCE* in, bool force_include_node_type = false, bool trim_node = false) { +static string instance_to_string(INSTANCE* in, bool force_include_node_type = false, bool trim_node = false) { vector result; Declaration node = in->node; @@ -875,7 +864,6 @@ static void dump_synth_functions(STATE* s, output_streams& oss) int i; int aug_graph_count = s->match_rules.length; - current_state = s; synth_functions_states = build_synth_functions_state(s); bool needs_fixed_point = s->loop_required; @@ -889,7 +877,6 @@ static void dump_synth_functions(STATE* s, output_streams& oss) } if (synth_functions_state->is_fiber_evaluation) { os << indent() << "val evaluated_map_" << synth_functions_state->fdecl_name - // << " = scala.collection.mutable.Map[T_" << decl_name(synth_functions_state->source_phy_graph->phylum) << " = scala.collection.mutable.Map[Int" << ", Boolean]()" << "\n\n"; @@ -1155,7 +1142,6 @@ class SynthImpl : public SynthImplementation { ostream& hs = oss.hs; ostream& cpps = oss.cpps; ostream& os = inline_definitions ? hs : cpps; - // char *name = decl_name(module_decl); #endif /* APS2SCALA */ dump_synth_functions(s, oss); @@ -1256,8 +1242,6 @@ void implement_value_use(Declaration vd, ostream& os) { } else { os << "a" << "_" << decl_name(vd) << DEREF << "get"; } - - // os << "a" << "_" << decl_name(vd) << DEREF << "get"; } else if (flags & LOCAL_VALUE_FLAG) { os << "v" << LOCAL_UNIQUE_PREFIX(vd) << "_" << decl_name(vd); } else { @@ -1276,10 +1260,7 @@ static Expression default_init(Default def) { } } -/* Return new array with instance assignments for block. - * If "from" is not NULL, then initialize the new array - * with it. - */ +// Collects instance assignments from the current block scope. static vector > make_instance_assignment() { int n = current_aug_graph->instances.length; @@ -1658,7 +1639,6 @@ void dump_rhs_instance_helper(AUG_GRAPH* aug_graph, BlockItem* item, INSTANCE* i if (m == first_Match(case_stmt_matchers(header))) { Expression e = case_stmt_expr(header); #ifdef APS2SCALA - // Type ty = infer_expr_type(e); o << "{\n"; nesting_level++; o << indent() << "val node" << instance->index << " = " << e << ";\n"; From 8351c280781e19afc979c5af77f2c6b0666025a2 Mon Sep 17 00:00:00 2001 From: Amir Hesamian Date: Tue, 21 Apr 2026 05:47:36 +0000 Subject: [PATCH 05/20] just code improvement to avoid re-calculation of assignments --- codegen/synth-impl.cc | 91 ++++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 45 deletions(-) diff --git a/codegen/synth-impl.cc b/codegen/synth-impl.cc index 6b0d99f4..969a8e17 100644 --- a/codegen/synth-impl.cc +++ b/codegen/synth-impl.cc @@ -1509,64 +1509,65 @@ void dump_rhs_instance_helper(AUG_GRAPH* aug_graph, BlockItem* item, INSTANCE* i if (item->key == KEY_BLOCK_ITEM_INSTANCE) { struct block_item_instance* bi = (struct block_item_instance*)item; + + if (bi->instance != instance && bi->next != NULL) { + dump_rhs_instance_helper(aug_graph, bi->next, instance, o); + return; + } + vector > all_assignments = make_instance_assignment(); std::set relevant_assignments = all_assignments[instance->index]; bool any_assignment_dump = false; - if (bi->instance == instance || bi->next == NULL) { - if (!relevant_assignments.empty()) { - for (auto it = relevant_assignments.begin(); it != relevant_assignments.end(); it++) { - Expression rhs = *it; - if (rhs == NULL) { - continue; - } - - any_assignment_dump = true; - - if (instance->fibered_attr.fiber != NULL) { - dump_assignment(instance, rhs, o); - } else { - // just dump RHS since synth functions are only interested in RHS, not side-effect - dump_Expression(rhs, o); - } + if (!relevant_assignments.empty()) { + for (auto it = relevant_assignments.begin(); it != relevant_assignments.end(); it++) { + Expression rhs = *it; + if (rhs == NULL) { + continue; } - if (!any_assignment_dump) { - fatal_error("should have dumped an assignment here"); + any_assignment_dump = true; + + if (instance->fibered_attr.fiber != NULL) { + dump_assignment(instance, rhs, o); + } else { + // just dump RHS since synth functions are only interested in RHS, not side-effect + dump_Expression(rhs, o); } + } - return; + if (!any_assignment_dump) { + fatal_error("should have dumped an assignment here"); } - if (instance->fibered_attr.fiber != NULL) { - // Shared info attribute wasn't assigned in this block, dump its default - auto direction = fibered_attr_direction(&instance->fibered_attr); - auto directionStr = ""; - switch (direction) - { - case instance_inward: - directionStr = "instance_inward"; - break; - case instance_outward: - directionStr = "instance_outward"; - break; - case instance_local: - directionStr = "instance_local"; - break; - default: - break; - } + return; + } - o << "/* did not find any assignment for this fiber attribute " << instance << " -> " << directionStr << " <-" <<" */"; - return; - } else { - print_instance(instance, stdout); - printf(" is a non-fiber instance, but no assignment found in this block. %d\n", if_rule_p(instance->fibered_attr.attr)); - fatal_error("crashed since non-fiber instance is missing an assignment"); + if (instance->fibered_attr.fiber != NULL) { + // Shared info attribute wasn't assigned in this block, dump its default + auto direction = fibered_attr_direction(&instance->fibered_attr); + auto directionStr = ""; + switch (direction) + { + case instance_inward: + directionStr = "instance_inward"; + break; + case instance_outward: + directionStr = "instance_outward"; + break; + case instance_local: + directionStr = "instance_local"; + break; + default: + break; } - } else { - dump_rhs_instance_helper(aug_graph, bi->next, instance, o); + + o << "/* did not find any assignment for this fiber attribute " << instance << " -> " << directionStr << " <-" <<" */"; return; + } else { + print_instance(instance, stdout); + printf(" is a non-fiber instance, but no assignment found in this block. %d\n", if_rule_p(instance->fibered_attr.attr)); + fatal_error("crashed since non-fiber instance is missing an assignment"); } } else if (item->key == KEY_BLOCK_ITEM_CONDITION) { struct block_item_condition* cond = (struct block_item_condition*)item; From ab3e1c0d9869052f34366e4e08d4f695e1100978 Mon Sep 17 00:00:00 2001 From: Amir Hesamian Date: Tue, 21 Apr 2026 05:56:13 +0000 Subject: [PATCH 06/20] basic code improvements --- codegen/synth-impl.cc | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/codegen/synth-impl.cc b/codegen/synth-impl.cc index 969a8e17..b6d36a38 100644 --- a/codegen/synth-impl.cc +++ b/codegen/synth-impl.cc @@ -1,5 +1,4 @@ #include -#include #include #include @@ -9,11 +8,8 @@ extern "C" { #include "aps-ag.h" } -#include -#include #include #include -#include #include #include "dump.h" @@ -862,8 +858,6 @@ static void dump_synth_functions(STATE* s, output_streams& oss) os << "\n"; - int i; - int aug_graph_count = s->match_rules.length; synth_functions_states = build_synth_functions_state(s); bool needs_fixed_point = s->loop_required; @@ -1106,6 +1100,7 @@ static void dump_synth_functions(STATE* s, output_streams& oss) } destroy_synth_function_states(synth_functions_states); + synth_functions_states.clear(); } class SynthImpl : public SynthImplementation { @@ -1779,7 +1774,7 @@ void dump_synth_instance(INSTANCE* instance, ostream& o) { nesting_level = std::max(nesting_level + 2, 2); o << indent() << "v_" << decl_name(node); - std::vector dependencies = synth_function_state->regular_dependencies; + const std::vector& dependencies = synth_function_state->regular_dependencies; for (auto it = dependencies.begin(); it != dependencies.end(); it++) { INSTANCE* source_instance = *it; From c75b89da431b403473f63d1d760d37fb2a0b47ed Mon Sep 17 00:00:00 2001 From: Amir Hesamian Date: Fri, 24 Apr 2026 20:24:13 +0000 Subject: [PATCH 07/20] fixed issue with codegen Co-authored-by: Copilot --- codegen/static-scc-impl.cc | 4 ++-- codegen/synth-impl.cc | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/codegen/static-scc-impl.cc b/codegen/static-scc-impl.cc index d31227a5..82248a67 100644 --- a/codegen/static-scc-impl.cc +++ b/codegen/static-scc-impl.cc @@ -162,9 +162,9 @@ static vector > make_instance_assignment( if (in->index >= n) fatal_error("bad index [collection_assign] for instance"); - if (step == 1) + if (step == 1 && include_initial_defaults) array[in->index].clear(); - else + else if (step == 2) array[in->index].insert(assign_rhs(d)); } break; diff --git a/codegen/synth-impl.cc b/codegen/synth-impl.cc index b6d36a38..3227b55a 100644 --- a/codegen/synth-impl.cc +++ b/codegen/synth-impl.cc @@ -1282,6 +1282,7 @@ static vector > make_instance_assignment() { // start from the outer-most and override it with the most inner scope for (auto it = current_blocks.begin(); it != current_blocks.end(); it++) { Block block = *it; + bool is_outermost = (it == current_blocks.begin()); vector > array(from); // Step #1 clear any existing assignments and insert normal assignments @@ -1311,9 +1312,9 @@ static vector > make_instance_assignment() { fatal_error("bad index [collection_assign] for instance"); } - if (step == 1) { + if (step == 1 && is_outermost) { array[in->index].clear(); - } else { + } else if (step == 2) { array[in->index].insert(assign_rhs(d)); } } From 1e2d31cca89b34b37824c112e967171ddca9712a Mon Sep 17 00:00:00 2001 From: Amir Hesamian Date: Fri, 24 Apr 2026 20:42:16 +0000 Subject: [PATCH 08/20] Testing Co-authored-by: Copilot --- codegen/synth-impl.cc | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/codegen/synth-impl.cc b/codegen/synth-impl.cc index 3227b55a..764db48e 100644 --- a/codegen/synth-impl.cc +++ b/codegen/synth-impl.cc @@ -1516,19 +1516,39 @@ void dump_rhs_instance_helper(AUG_GRAPH* aug_graph, BlockItem* item, INSTANCE* i bool any_assignment_dump = false; if (!relevant_assignments.empty()) { + // Filter out NULLs first + vector valid_rhs; for (auto it = relevant_assignments.begin(); it != relevant_assignments.end(); it++) { - Expression rhs = *it; - if (rhs == NULL) { - continue; - } + if (*it != NULL) valid_rhs.push_back(*it); + } + if (!valid_rhs.empty()) { any_assignment_dump = true; if (instance->fibered_attr.fiber != NULL) { - dump_assignment(instance, rhs, o); + for (auto it = valid_rhs.begin(); it != valid_rhs.end(); it++) { + dump_assignment(instance, *it, o); + } + } else if (valid_rhs.size() == 1) { + dump_Expression(valid_rhs[0], o); } else { - // just dump RHS since synth functions are only interested in RHS, not side-effect - dump_Expression(rhs, o); + // Multiple RHS entries only occur from collect_assign (:>) contributions. + // Combine them pairwise using the type's v_combine. + Declaration attr = instance->fibered_attr.attr; + if (!direction_is_collection(value_decl_direction(attr))) { + fatal_error("Multiple RHS for non-collection attribute %s", decl_name(attr)); + } + Type vt = value_decl_type(attr); + // Nest v_combine calls for all contributions + for (size_t i = 0; i < valid_rhs.size() - 1; i++) { + o << as_val(vt) << ".v_combine("; + } + dump_Expression(valid_rhs[0], o); + for (size_t i = 1; i < valid_rhs.size(); i++) { + o << ", "; + dump_Expression(valid_rhs[i], o); + o << ")"; + } } } From 6f5421c0d6bbe95fc82ae610f2359a9b731df11b Mon Sep 17 00:00:00 2001 From: Amir Date: Fri, 24 Apr 2026 17:46:40 -0500 Subject: [PATCH 09/20] revert unnessary change --- codegen/static-scc-impl.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codegen/static-scc-impl.cc b/codegen/static-scc-impl.cc index 82248a67..d31227a5 100644 --- a/codegen/static-scc-impl.cc +++ b/codegen/static-scc-impl.cc @@ -162,9 +162,9 @@ static vector > make_instance_assignment( if (in->index >= n) fatal_error("bad index [collection_assign] for instance"); - if (step == 1 && include_initial_defaults) + if (step == 1) array[in->index].clear(); - else if (step == 2) + else array[in->index].insert(assign_rhs(d)); } break; From 2d6e1657fdbbfb353b8b1a827e0b1dc01985f7ba Mon Sep 17 00:00:00 2001 From: Amir Date: Tue, 12 May 2026 11:14:37 -0500 Subject: [PATCH 10/20] smaller fixes --- codegen/synth-impl.cc | 12 +++++++----- parse/aps-util.c | 9 +++++++++ parse/aps-util.h | 1 + 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/codegen/synth-impl.cc b/codegen/synth-impl.cc index 764db48e..bb15eb0f 100644 --- a/codegen/synth-impl.cc +++ b/codegen/synth-impl.cc @@ -776,7 +776,7 @@ class FiberDependencyDumper { synth_impl_ptr->dump_synth_instance(in, os); dumped_conditional_block_items.clear(); dumped_instances.clear(); - os << "\n"; + os << ";\n"; dump_scc_helper_dump(aug_graph, component, scheduled, os); } @@ -1431,7 +1431,7 @@ void dump_assignment(INSTANCE* in, Expression rhs, ostream& o) { } } } else { - if (Declaration_KEY(ad) == KEYvalue_decl && !direction_is_collection(value_decl_direction(ad))) { + if (!direction_is_collection(some_value_decl_direction(ad))) { aps_warning(ad, "Local attribute %s is apparently undefined", decl_name(ad)); } if (include_comments) { @@ -1450,7 +1450,8 @@ void dump_assignment(INSTANCE* in, Expression rhs, ostream& o) { } } else if (rhs) { if (Declaration_KEY(in->node) == KEYfunction_decl) { - if (direction_is_collection(value_decl_direction(ad))) { + Direction ad_dir = some_value_decl_direction(ad); + if (direction_is_collection(ad_dir)) { std::cout << "Not expecting collection here!\n"; o << "v_" << asym << " = somehow_combine(v_" << asym << "," << rhs << ");\n"; } else { @@ -1535,10 +1536,11 @@ void dump_rhs_instance_helper(AUG_GRAPH* aug_graph, BlockItem* item, INSTANCE* i // Multiple RHS entries only occur from collect_assign (:>) contributions. // Combine them pairwise using the type's v_combine. Declaration attr = instance->fibered_attr.attr; - if (!direction_is_collection(value_decl_direction(attr))) { + Direction attr_dir = some_value_decl_direction(attr); + if (!direction_is_collection(attr_dir)) { fatal_error("Multiple RHS for non-collection attribute %s", decl_name(attr)); } - Type vt = value_decl_type(attr); + Type vt = Declaration_KEY(attr) == KEYattribute_decl ? function_type_return_type(attribute_decl_type(attr)) : value_decl_type(attr); // Nest v_combine calls for all contributions for (size_t i = 0; i < valid_rhs.size() - 1; i++) { o << as_val(vt) << ".v_combine("; diff --git a/parse/aps-util.c b/parse/aps-util.c index c9a8c059..8c89b4fc 100644 --- a/parse/aps-util.c +++ b/parse/aps-util.c @@ -216,6 +216,15 @@ Type some_value_decl_type(Declaration _node) { } } +Direction some_value_decl_direction(Declaration _node) { + switch (Declaration_KEY(_node)) { + default: fatal_error("some_value_decl_direction: called with %s", + Declaration_constructors[Declaration_KEY(_node)]); + case KEYvalue_decl: return value_decl_direction(_node); + case KEYattribute_decl: return attribute_decl_direction(_node); + } +} + Expression assign_lhs(Declaration _node) { switch (Declaration_KEY(_node)) { default: fatal_error("assign_lhs: called with %s", diff --git a/parse/aps-util.h b/parse/aps-util.h index c9b22239..8b6edcf0 100644 --- a/parse/aps-util.h +++ b/parse/aps-util.h @@ -76,6 +76,7 @@ extern Type some_type_decl_type(Declaration); case KEYconstructor_decl: \ case KEYformal extern Type some_value_decl_type(Declaration); +extern Direction some_value_decl_direction(Declaration); #define KEYassign KEYnormal_assign: case KEYcollect_assign extern Expression assign_lhs(Declaration); From 02c406aa30e252e952e2eb6ab7452425eaa8f064 Mon Sep 17 00:00:00 2001 From: Amir Date: Fri, 15 May 2026 13:51:44 -0500 Subject: [PATCH 11/20] handle objects --- codegen/synth-impl.cc | 72 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/codegen/synth-impl.cc b/codegen/synth-impl.cc index bb15eb0f..db7eea8a 100644 --- a/codegen/synth-impl.cc +++ b/codegen/synth-impl.cc @@ -1552,13 +1552,9 @@ void dump_rhs_instance_helper(AUG_GRAPH* aug_graph, BlockItem* item, INSTANCE* i o << ")"; } } + return; } - - if (!any_assignment_dump) { - fatal_error("should have dumped an assignment here"); - } - - return; + // valid_rhs is empty (all NULL defaults) — fall through to default handling } if (instance->fibered_attr.fiber != NULL) { @@ -1583,6 +1579,42 @@ void dump_rhs_instance_helper(AUG_GRAPH* aug_graph, BlockItem* item, INSTANCE* i o << "/* did not find any assignment for this fiber attribute " << instance << " -> " << directionStr << " <-" <<" */"; return; } else { + // Check if this is a local collection attribute (value_decl with collection direction) + // whose type is also COMBINABLE per its canonical signature. + // Such attributes may have conditional assignments (inside case/match blocks) that are + // not found by make_instance_assignment() at this level - they are handled elsewhere. + Declaration attr = instance->fibered_attr.attr; + bool is_local_collection = direction_is_collection(some_value_decl_direction(attr)); + if (is_local_collection) { + // Verify via canonical signature that the type implements COMBINABLE - + // i.e., some source_class in its signature set has a "combine" declaration + // in its body (handles COMBINABLE directly, MAKE_LATTICE, and any other + // combinable module, without needing to walk parent signature chains). + Type vt = infer_some_value_decl_type(attr); + CanonicalType* ctype = canonical_type(vt); + CanonicalSignatureSet csig_set = infer_canonical_signatures(ctype); + bool is_combinable = false; + for (int ci = 0; ci < csig_set->num_elements && !is_combinable; ci++) { + CanonicalSignature* csig = (CanonicalSignature*)csig_set->elements[ci]; + Block body = some_class_decl_contents(csig->source_class); + for (Declaration bd = first_Declaration(block_body(body)); bd; bd = DECL_NEXT(bd)) { + if (strcmp(decl_name(bd), "combine") == 0) { + is_combinable = true; + break; + } + } + } + if (is_combinable) { + // A local collection attribute with no direct assignment in this block + // (e.g., assigned only inside a conditional/case block) should return + // the type's initial/bottom value for this branch. + o << as_val(vt) << ".v_initial"; + if (include_comments) { + o << " /* local collection " << decl_name(attr) << ": no direct assignment, using initial */"; + } + return; + } + } print_instance(instance, stdout); printf(" is a non-fiber instance, but no assignment found in this block. %d\n", if_rule_p(instance->fibered_attr.attr)); fatal_error("crashed since non-fiber instance is missing an assignment"); @@ -1731,6 +1763,34 @@ void dump_rhs_instance_helper(AUG_GRAPH* aug_graph, BlockItem* item, INSTANCE* i } bool try_dump_funcall(Expression e, ostream& o) override { + // In synth mode, phylum constructor calls use "node" as the AST anchor + // (the eval function parameter), not "anchor" (which is the Evaluation class field). + // Replicate the logic from dump-scala.cc's dump_anchor_actual check but emit "node". + { + Expression fexpr = funcall_f(e); + if (Expression_KEY(fexpr) == KEYvalue_use) { + Declaration udecl = USE_DECL(value_use_use(fexpr)); + if (Declaration_KEY(udecl) == KEYconstructor_decl) { + // Inline constructor_decl_base_type_decl to get the base type decl + Type ct = constructor_decl_type(udecl); + Declaration retdecl = first_Declaration(function_type_return_values(ct)); + Type return_type = value_decl_type(retdecl); + Declaration tdecl = USE_DECL(type_use_use(return_type)); + if (Declaration_KEY(tdecl) == KEYphylum_decl) { + dump_Expression(fexpr, o); + o << "("; + bool start = true; + FOR_SEQUENCE(Expression, arg, Actuals, funcall_actuals(e), + if (start) start = false; else o << ",\n"; + dump_Expression(arg, o)); + if (first_Actual(funcall_actuals(e)) != NULL) o << ", "; + o << "node)"; + return true; + } + } + } + } + Declaration attr = attr_ref_p(e); if (attr == nullptr) return false; Declaration node = USE_DECL(value_use_use(first_Actual(funcall_actuals(e)))); From 91cabc9b7656569e35f060724ae208fa58916514 Mon Sep 17 00:00:00 2001 From: Amir Date: Fri, 15 May 2026 14:27:53 -0500 Subject: [PATCH 12/20] improved the Makefile --- codegen/synth-impl.cc | 1 + examples/scala/Makefile | 44 ++++++++++++-------- examples/scala/farrow-ubd-driver.scala | 4 +- examples/scala/farrow-ubd-fiber-driver.scala | 3 +- examples/scala/nested-ubd-driver.scala | 2 +- examples/scala/nested-ubd-fiber-driver.scala | 4 +- 6 files changed, 35 insertions(+), 23 deletions(-) diff --git a/codegen/synth-impl.cc b/codegen/synth-impl.cc index db7eea8a..f137af0f 100644 --- a/codegen/synth-impl.cc +++ b/codegen/synth-impl.cc @@ -10,6 +10,7 @@ extern "C" { } #include #include +#include #include #include "dump.h" diff --git a/examples/scala/Makefile b/examples/scala/Makefile index 23faaf80..8b4ba482 100644 --- a/examples/scala/Makefile +++ b/examples/scala/Makefile @@ -12,27 +12,35 @@ EVALUATOR ?= DYNAMIC ifeq (${EVALUATOR},STATIC) IMPL_SUFFIX = .static.scala +else ifeq (${EVALUATOR},SYNTH) +IMPL_SUFFIX = .synth.scala else IMPL_SUFFIX = .scala endif -SCALAGEN = simple.scala classic-binding.scala tiny.scala broad-fiber-cycle.scala \ - test-coll.scala test-use-coll.scala test-cycle.scala use-global.scala \ - grammar.scala first.scala follow.scala nullable.scala \ - farrow-lv-tree.scala farrow-lv.static.scala \ - farrow-ubd-tree.scala farrow-ubd.static.scala farrow-ubd-fiber.static.scala \ - broad-fiber-cycle.static.scala classic-binding.static.scala \ - grammar.scala first.static.scala follow.static.scala nullable.static.scala \ - nested-cycles.static.scala \ - test-coll.static.scala test-use-coll.static.scala test-cycle.static.scala use-global.static.scala - -MISCGEN = nested-cycles.scala SimpleParser.scala SimpleScanner.scala \ +SCALAGEN = simple.scala tiny.scala \ + classic-binding.scala classic-binding.static.scala classic-binding.synth.scala \ + grammar.scala \ + first.scala first.static.scala first.synth.scala \ + follow.scala follow.static.scala follow.synth.scala \ + nullable.scala nullable.static.scala nullable.synth.scala \ + test-coll.scala test-coll.static.scala test-coll.synth.scala \ + test-use-coll.scala test-use-coll.static.scala test-use-coll.synth.scala \ + test-cycle.scala test-cycle.static.scala test-cycle.synth.scala \ + use-global.scala use-global.static.scala use-global.synth.scala \ + broad-fiber-cycle.scala broad-fiber-cycle.static.scala broad-fiber-cycle.synth.scala \ + nested-cycles.scala nested-cycles.static.scala nested-cycles.synth.scala \ + farrow-lv-tree.scala farrow-lv.scala farrow-lv.static.scala farrow-lv.synth.scala \ + farrow-ubd-tree.scala farrow-ubd.scala farrow-ubd.static.scala farrow-ubd.synth.scala \ + farrow-ubd-fiber.scala farrow-ubd-fiber.static.scala farrow-ubd-fiber.synth.scala \ + nested-ubd-tree.scala nested-ubd.scala nested-ubd.static.scala nested-ubd.synth.scala \ + nested-ubd-fiber.scala nested-ubd-fiber.static.scala nested-ubd-fiber.synth.scala + +MISCGEN = SimpleParser.scala SimpleScanner.scala \ GrammarTokens.scala SimpleTokens.scala GrammarScanner.scala GrammarParser.scala \ - FarrowLvTokens.scala FarrowLvTokens.scala FarrowLvScanner.scala FarrowLvParser.scala farrow-lv.scala \ - FarrowUbdTokens.scala FarrowUbdTokens.scala FarrowUbdScanner.scala FarrowUbdParser.scala farrow-ubd.scala \ - farrow-ubd-fiber.scala \ - NestedUbdTokens.scala NestedUbdScanner.scala NestedUbdParser.scala nested-ubd.scala \ - nested-ubd-fiber.scala \ + FarrowLvTokens.scala FarrowLvScanner.scala FarrowLvParser.scala \ + FarrowUbdTokens.scala FarrowUbdScanner.scala FarrowUbdParser.scala \ + NestedUbdTokens.scala NestedUbdScanner.scala NestedUbdParser.scala \ *.output *.lex *.y *.scala~ .PHONY: all clean phony_explicit @@ -137,13 +145,13 @@ NestedUbdParserBase.class : NestedUbdParserBase.scala NestedUbd_implicit.class NestedUbdTokens.class : NestedUbdTokens.scala ${SCALAC} ${SCALACFLAGS} $< -NestedUbd_implicit.class : nested-ubd-tree.scala nested-ubd.static.scala +NestedUbd_implicit.class : nested-ubd-tree.scala nested-ubd${IMPL_SUFFIX} ${SCALAC} ${SCALACFLAGS} $^ NestedUbdDriver.class : NestedUbd_implicit.class NestedUbdParser.class ${SCALAC} ${SCALACFLAGS} nested-ubd-driver.scala -NestedUbdFiber_implicit.class : nested-ubd-tree.scala nested-ubd-fiber.static.scala +NestedUbdFiber_implicit.class : nested-ubd-tree.scala nested-ubd-fiber${IMPL_SUFFIX} ${SCALAC} ${SCALACFLAGS} $^ NestedUbdFiberDriver.class : NestedUbdFiber_implicit.class NestedUbdParser.class diff --git a/examples/scala/farrow-ubd-driver.scala b/examples/scala/farrow-ubd-driver.scala index bb055eb6..c60a663f 100644 --- a/examples/scala/farrow-ubd-driver.scala +++ b/examples/scala/farrow-ubd-driver.scala @@ -16,9 +16,11 @@ object FarrowUbdDriver extends App { val m_farrow_ubd_tree = farrow_ubd_tree; val m_farrow_ubd = new M_FARROW_UBD[m_farrow_ubd_tree.T_Result]("FarrowUbd", m_farrow_ubd_tree.t_Result); - Debug.activate(); + // Debug.activate(); m_farrow_ubd_tree.finish(); m_farrow_ubd.finish(); + m_farrow_ubd.v_program_errs(m_farrow_ubd.t_Program.nodes(0)).foreach(println(_)) + } diff --git a/examples/scala/farrow-ubd-fiber-driver.scala b/examples/scala/farrow-ubd-fiber-driver.scala index 0f29de7f..1247bc6b 100644 --- a/examples/scala/farrow-ubd-fiber-driver.scala +++ b/examples/scala/farrow-ubd-fiber-driver.scala @@ -16,9 +16,10 @@ object FarrowUbdFiberDriver extends App { val m_farrow_ubd_tree = farrow_ubd_tree; val m_farrow_ubd = new M_FARROW_UBD_FIBER[m_farrow_ubd_tree.T_Result]("FarrowUbdFiber", m_farrow_ubd_tree.t_Result); - Debug.activate(); + // Debug.activate(); m_farrow_ubd_tree.finish(); m_farrow_ubd.finish(); + m_farrow_ubd.v_program_errs(m_farrow_ubd.t_Program.nodes(0)).foreach(println(_)) } diff --git a/examples/scala/nested-ubd-driver.scala b/examples/scala/nested-ubd-driver.scala index cbd62110..db5f713b 100644 --- a/examples/scala/nested-ubd-driver.scala +++ b/examples/scala/nested-ubd-driver.scala @@ -21,5 +21,5 @@ object NestedUbdDriver extends App { m_nested_ubd_tree.finish(); m_nested_ubd.finish(); - println(m_nested_ubd.v_program_errs(m_nested_ubd.t_Program.nodes(0))); + m_nested_ubd.v_program_errs(m_nested_ubd.t_Program.nodes(0)).foreach(println(_)); } diff --git a/examples/scala/nested-ubd-fiber-driver.scala b/examples/scala/nested-ubd-fiber-driver.scala index 41d7c5ff..5b881589 100644 --- a/examples/scala/nested-ubd-fiber-driver.scala +++ b/examples/scala/nested-ubd-fiber-driver.scala @@ -16,10 +16,10 @@ object NestedUbdFiberDriver extends App { val m_nested_ubd_tree = nested_ubd_tree; val m_nested_ubd = new M_NESTED_UBD_FIBER[m_nested_ubd_tree.T_Result]("NestedUbdFiber", m_nested_ubd_tree.t_Result); - Debug.activate(); + // Debug.activate(); m_nested_ubd_tree.finish(); m_nested_ubd.finish(); - println(m_nested_ubd.v_messages); + m_nested_ubd.v_messages.foreach(println(_)); } From ce5f91f90306a36de30da641a5e7759e53253b55 Mon Sep 17 00:00:00 2001 From: Amir Date: Fri, 15 May 2026 21:47:33 -0500 Subject: [PATCH 13/20] tiny change --- examples/nested-ubd.aps | 2 +- examples/scala/nested-ubd-driver.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/nested-ubd.aps b/examples/nested-ubd.aps index a2cb7dda..b626aac0 100644 --- a/examples/nested-ubd.aps +++ b/examples/nested-ubd.aps @@ -25,7 +25,7 @@ module NESTED_UBD[T :: var NESTED_UBD_TREE[]] extends T begin pragma inherited(decls_env); pragma synthesized(decls_defs, decls_errs); - attribute Block.block_env : DeclPairLattice; + circular attribute Block.block_env : DeclPairLattice; attribute Block.block_errs : Messages; pragma inherited (block_env); pragma synthesized (block_errs); diff --git a/examples/scala/nested-ubd-driver.scala b/examples/scala/nested-ubd-driver.scala index db5f713b..b541e317 100644 --- a/examples/scala/nested-ubd-driver.scala +++ b/examples/scala/nested-ubd-driver.scala @@ -16,7 +16,7 @@ object NestedUbdDriver extends App { val m_nested_ubd_tree = nested_ubd_tree; val m_nested_ubd = new M_NESTED_UBD[m_nested_ubd_tree.T_Result]("NestedUbd", m_nested_ubd_tree.t_Result); - Debug.activate(); + // Debug.activate(); m_nested_ubd_tree.finish(); m_nested_ubd.finish(); From cf786ee8f17fcafd6ff535407f3171bc4d208ca2 Mon Sep 17 00:00:00 2001 From: Amir Hesamian Date: Tue, 16 Jun 2026 15:52:49 +0000 Subject: [PATCH 14/20] Revert unnessary changes --- examples/scala/farrow-ubd-driver.scala | 4 +--- examples/scala/farrow-ubd-fiber-driver.scala | 3 +-- examples/scala/nested-ubd-driver.scala | 4 ++-- examples/scala/nested-ubd-fiber-driver.scala | 4 ++-- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/examples/scala/farrow-ubd-driver.scala b/examples/scala/farrow-ubd-driver.scala index c60a663f..bb055eb6 100644 --- a/examples/scala/farrow-ubd-driver.scala +++ b/examples/scala/farrow-ubd-driver.scala @@ -16,11 +16,9 @@ object FarrowUbdDriver extends App { val m_farrow_ubd_tree = farrow_ubd_tree; val m_farrow_ubd = new M_FARROW_UBD[m_farrow_ubd_tree.T_Result]("FarrowUbd", m_farrow_ubd_tree.t_Result); - // Debug.activate(); + Debug.activate(); m_farrow_ubd_tree.finish(); m_farrow_ubd.finish(); - m_farrow_ubd.v_program_errs(m_farrow_ubd.t_Program.nodes(0)).foreach(println(_)) - } diff --git a/examples/scala/farrow-ubd-fiber-driver.scala b/examples/scala/farrow-ubd-fiber-driver.scala index 1247bc6b..0f29de7f 100644 --- a/examples/scala/farrow-ubd-fiber-driver.scala +++ b/examples/scala/farrow-ubd-fiber-driver.scala @@ -16,10 +16,9 @@ object FarrowUbdFiberDriver extends App { val m_farrow_ubd_tree = farrow_ubd_tree; val m_farrow_ubd = new M_FARROW_UBD_FIBER[m_farrow_ubd_tree.T_Result]("FarrowUbdFiber", m_farrow_ubd_tree.t_Result); - // Debug.activate(); + Debug.activate(); m_farrow_ubd_tree.finish(); m_farrow_ubd.finish(); - m_farrow_ubd.v_program_errs(m_farrow_ubd.t_Program.nodes(0)).foreach(println(_)) } diff --git a/examples/scala/nested-ubd-driver.scala b/examples/scala/nested-ubd-driver.scala index b541e317..cbd62110 100644 --- a/examples/scala/nested-ubd-driver.scala +++ b/examples/scala/nested-ubd-driver.scala @@ -16,10 +16,10 @@ object NestedUbdDriver extends App { val m_nested_ubd_tree = nested_ubd_tree; val m_nested_ubd = new M_NESTED_UBD[m_nested_ubd_tree.T_Result]("NestedUbd", m_nested_ubd_tree.t_Result); - // Debug.activate(); + Debug.activate(); m_nested_ubd_tree.finish(); m_nested_ubd.finish(); - m_nested_ubd.v_program_errs(m_nested_ubd.t_Program.nodes(0)).foreach(println(_)); + println(m_nested_ubd.v_program_errs(m_nested_ubd.t_Program.nodes(0))); } diff --git a/examples/scala/nested-ubd-fiber-driver.scala b/examples/scala/nested-ubd-fiber-driver.scala index 5b881589..41d7c5ff 100644 --- a/examples/scala/nested-ubd-fiber-driver.scala +++ b/examples/scala/nested-ubd-fiber-driver.scala @@ -16,10 +16,10 @@ object NestedUbdFiberDriver extends App { val m_nested_ubd_tree = nested_ubd_tree; val m_nested_ubd = new M_NESTED_UBD_FIBER[m_nested_ubd_tree.T_Result]("NestedUbdFiber", m_nested_ubd_tree.t_Result); - // Debug.activate(); + Debug.activate(); m_nested_ubd_tree.finish(); m_nested_ubd.finish(); - m_nested_ubd.v_messages.foreach(println(_)); + println(m_nested_ubd.v_messages); } From 84a511ed77066a0727f5632d718209b6534e40b7 Mon Sep 17 00:00:00 2001 From: Amir Hesamian Date: Tue, 16 Jun 2026 21:30:48 +0000 Subject: [PATCH 15/20] add bigger outer fixed point loop --- codegen/synth-impl.cc | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/codegen/synth-impl.cc b/codegen/synth-impl.cc index f137af0f..cc8dd08d 100644 --- a/codegen/synth-impl.cc +++ b/codegen/synth-impl.cc @@ -1161,8 +1161,11 @@ class SynthImpl : public SynthImplementation { PHY_GRAPH* start_phy_graph = summary_graph_for(s, s->start_phylum); if (needs_fixed_point) { - os << indent() << "implicit val " << LOOP_VAR << ": Boolean = false;\n"; os << indent() << "implicit val changed: AtomicBoolean = new AtomicBoolean(false);\n"; + os << indent() << "implicit val " << LOOP_VAR << ": Boolean = true;\n"; + os << indent() << "do {\n"; + ++nesting_level; + os << indent() << "changed.set(false);\n"; } os << indent() << "for (root <- t_" << decl_name(s->start_phylum) << ".nodes) {\n"; ++nesting_level; @@ -1179,6 +1182,11 @@ class SynthImpl : public SynthImplementation { --nesting_level; os << indent() << "}\n"; + if (needs_fixed_point) { + --nesting_level; + os << indent() << "} while (changed.get)\n"; + } + #ifdef APS2SCALA os << indent() << "super.finish();\n"; #endif /* ! APS2SCALA */ From f623302672a584368452e88e940fd0949f12850e Mon Sep 17 00:00:00 2001 From: Amir Hesamian Date: Thu, 18 Jun 2026 17:14:24 +0000 Subject: [PATCH 16/20] more progress --- codegen/synth-impl.cc | 238 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 214 insertions(+), 24 deletions(-) diff --git a/codegen/synth-impl.cc b/codegen/synth-impl.cc index cc8dd08d..a2a80f60 100644 --- a/codegen/synth-impl.cc +++ b/codegen/synth-impl.cc @@ -1,6 +1,7 @@ #include #include +#include #include #include extern "C" { @@ -29,6 +30,9 @@ typedef struct synth_function_state { static AUG_GRAPH* current_aug_graph = NULL; static std::vector synth_functions_states; static SYNTH_FUNCTION_STATE* current_synth_functions_state = NULL; +// Set while dumping; read in finish(). True if a cycle closes through a +// fiber/shared-global collection. Currently informational only (see finish()). +static bool module_needs_global_loop = false; #define LOCAL_VALUE_FLAG (1 << 28) @@ -841,6 +845,150 @@ class FiberDependencyDumper { } }; +// True if this synth function computes a circular attribute. Such attributes +// assign through the change-tracking overload so the fixed-point loop can detect +// when they stop changing. +static bool synth_function_is_circular(SYNTH_FUNCTION_STATE* st) { + if (st->is_fiber_evaluation) { + return false; + } + if (instance_is_pure_shared_info(st->source)) { + return false; + } + for (auto it = st->aug_graphs.begin(); it != st->aug_graphs.end(); it++) { + AUG_GRAPH* aug_graph = *it; + INSTANCE* inst = NULL; + if (st->is_phylum_instance) { + if (!find_instance(aug_graph, aug_graph->lhs_decl, st->source->fibered_attr, &inst)) { + continue; + } + } else { + inst = st->source; + } + if (instance_circular(inst)) { + return true; + } + } + return false; +} + +// A synthesized, circular, non-fiber attribute on a child node -- a candidate +// whose local iteration can converge a cycle. +static bool is_cycle_closing_candidate(INSTANCE* instance, AUG_GRAPH* aug_graph) { + bool on_child_node = instance->node != NULL && instance->node != aug_graph->lhs_decl; + return on_child_node && + instance->fibered_attr.fiber == NULL && + !if_rule_p(instance->fibered_attr.attr) && + instance_is_synthesized(instance) && + instance_circular(instance); +} + +// True if `inherited` is the inherited circular attribute on the same child node +// that `instance` directly feeds -- the edge that closes the cycle here. +static bool closes_cycle_with(INSTANCE* inherited, INSTANCE* instance, AUG_GRAPH* aug_graph) { + int n = aug_graph->instances.length; + bool instance_feeds_inherited = + edgeset_kind(aug_graph->graph[instance->index * n + inherited->index]) & DEPENDENCY_MAYBE_DIRECT; + return inherited != instance && + inherited->node == instance->node && + inherited->fibered_attr.fiber == NULL && + instance_is_inherited(inherited) && + instance_circular(inherited) && + instance_feeds_inherited; +} + +// Child attributes that close a cycle at this production. A synthesized circular +// child instance that directly feeds its own inherited circular attribute closes +// the cycle here, so we can iterate it to a fixed point locally instead of +// program-wide. Productions that only forward an inherited attribute are skipped. +static std::vector collect_child_cycle_instances(AUG_GRAPH* aug_graph) { + std::vector result; + int n = aug_graph->instances.length; + for (int index = 0; index < n; index++) { + INSTANCE* instance = &aug_graph->instances.array[index]; + if (!is_cycle_closing_candidate(instance, aug_graph)) continue; + + for (int inner_index = 0; inner_index < n; inner_index++) { + INSTANCE* inherited = &aug_graph->instances.array[inner_index]; + if (closes_cycle_with(inherited, instance, aug_graph)) { + result.push_back(instance); + break; + } + } + } + return result; +} + +// True if a cycle closes through a fiber/shared-global collection rather than a +// child's inherited attribute -- i.e. no local child instance can close it. +static bool module_has_fiber_cycle(const std::vector& states) { + for (auto it = states.begin(); it != states.end(); it++) { + SYNTH_FUNCTION_STATE* st = *it; + if (!st->is_fiber_evaluation) continue; + if (instance_is_pure_shared_info(st->source) && st->is_phylum_instance) { + // pure shared-info has no fixed-point loop of its own; skip + continue; + } + for (auto ag = st->aug_graphs.begin(); ag != st->aug_graphs.end(); ag++) { + AUG_GRAPH* aug_graph = *ag; + INSTANCE* inst = NULL; + if (st->is_phylum_instance) { + if (!find_instance(aug_graph, aug_graph->lhs_decl, st->source->fibered_attr, &inst)) { + continue; + } + } else { + inst = st->source; + } + if (!instance_circular(inst)) continue; + + // A local child instance closes this cycle at its production, so no + // module-wide loop is needed. Count it as a fiber cycle only when none does. + std::vector child_cycle_instances = collect_child_cycle_instances(aug_graph); + if (child_cycle_instances.empty()) { + return true; + } + } + } + return false; +} + +// Emits the two implicits every fixed-point loop body opens with: isInsideFixedPoint +// (disables caching) and `changed` bound to this loop's convergence flag. +static void emit_loop_implicits(ostream& os, const string& flag) { + os << indent() << "implicit val " << LOOP_VAR << ": Boolean = true;\n"; + os << indent() << "implicit val changed: AtomicBoolean = " << flag << ";\n"; +} + +// Emits a local successive-approximation loop, guarded so it is skipped when +// already inside an outer fixed point (that loop drives convergence instead): +// +// if (!isInsideFixedPoint) { +// val = new AtomicBoolean(true); +// while (.get) { +// .set(false); +// implicit val isInsideFixedPoint = true; implicit val changed = ; +// +// } +// } +// +// `emit_body` writes the body at the current indentation. +static void emit_local_fixed_point_loop(ostream& os, + const string& flag, + const std::function& emit_body) { + os << indent() << "if (!" << LOOP_VAR << ") {\n"; + ++nesting_level; + os << indent() << "val " << flag << " = new AtomicBoolean(true);\n"; + os << indent() << "while (" << flag << ".get) {\n"; + ++nesting_level; + os << indent() << flag << ".set(false);\n"; + emit_loop_implicits(os, flag); + emit_body(); + --nesting_level; + os << indent() << "}\n"; + --nesting_level; + os << indent() << "}\n"; +} + #ifdef APS2SCALA static void dump_synth_functions(STATE* s, ostream& os) #else /* APS2SCALA */ @@ -862,6 +1010,10 @@ static void dump_synth_functions(STATE* s, output_streams& oss) synth_functions_states = build_synth_functions_state(s); bool needs_fixed_point = s->loop_required; + // Module-wide loop needed only when a cycle closes through a fiber/shared + // collection; inherited cycles converge locally at their productions. + module_needs_global_loop = needs_fixed_point && module_has_fiber_cycle(synth_functions_states); + for (auto state_it = synth_functions_states.begin(); state_it != synth_functions_states.end(); state_it++) { SYNTH_FUNCTION_STATE* synth_functions_state = *state_it; current_synth_functions_state = synth_functions_state; @@ -953,7 +1105,8 @@ static void dump_synth_functions(STATE* s, output_streams& oss) if (needs_fixed_point) { nesting_level--; - os << indent() << "}\n"; + os << indent() << "}"; + os << "\n"; } if (synth_functions_state->is_fiber_evaluation) { @@ -963,6 +1116,11 @@ static void dump_synth_functions(STATE* s, output_streams& oss) } nesting_level++; + // True if this attribute is circular anywhere. Then every assignment uses the + // change-tracking overload so the closure-site loop can see it settle; only + // the closure site emits the do/while. + bool source_circular = needs_fixed_point && synth_function_is_circular(synth_functions_state); + for (auto it = synth_functions_state->aug_graphs.begin(); it != synth_functions_state->aug_graphs.end(); it++) { AUG_GRAPH* aug_graph = *it; int n = aug_graph->instances.length; @@ -1004,10 +1162,38 @@ static void dump_synth_functions(STATE* s, output_streams& oss) aps_warning(aug_graph_instance->node, "Instance %s depends on itself but is not declared circular", instance_to_string(aug_graph_instance).c_str()); } - bool dump_fixed_point_loop = declared_is_circular && !instance_is_pure_shared_info(synth_functions_state->source); + // Non-fiber cycles loop at the closing child (collect_child_cycle_instances, + // above); everywhere else we compute once and report changes via `changed`. + // We do NOT emit a per-attribute do/while for them: the LHS self-edge marks + // them circular at every production, so looping at each one nests loops down + // the tree -> exponential in depth (Farrow 1986 sec.4: loop at the closure + // only). Fiber/shared cycles aren't local to a production, so they loop here. + bool dump_fixed_point_loop = synth_functions_state->is_fiber_evaluation && declared_is_circular && !instance_is_pure_shared_info(synth_functions_state->source); string node_get = ATTR_DECL_IS_SHARED_INFO(synth_functions_state->source->fibered_attr.attr) ? "" : "node"; string node_assign = ATTR_DECL_IS_SHARED_INFO(synth_functions_state->source->fibered_attr.attr) ? "" : "node, "; + // Converge any child cycle that closes here before computing this value, so + // the cached body below reads settled values. Replaces a whole-program loop. + if (!synth_functions_state->is_fiber_evaluation) { + std::vector child_cycle_instances = collect_child_cycle_instances(aug_graph); + for (auto it2 = child_cycle_instances.begin(); it2 != child_cycle_instances.end(); it2++) { + INSTANCE* cycle_instance = *it2; + // Skipped when already inside an outer fixed point (see helper): nesting + // a do/while per scope would re-converge this child every outer pass, + // which is exponential in nesting depth. The outer loop revisits this + // production each round; here we just do one step and report via `changed`. + emit_local_fixed_point_loop(os, "localChanged", [&] { + dumped_conditional_block_items.clear(); + dumped_instances.clear(); + os << indent(); + synth_impl_ptr->dump_synth_instance(cycle_instance, os); + os << ";\n"; + }); + } + dumped_conditional_block_items.clear(); + dumped_instances.clear(); + } + // Open fixed-point loop using shared changed flag for convergence tracking if (dump_fixed_point_loop) { os << indent() << "{\n"; @@ -1024,8 +1210,7 @@ static void dump_synth_functions(STATE* s, output_streams& oss) if (synth_functions_state->is_fiber_evaluation) { tracking_fiber_convergence = true; } - os << indent() << "implicit val " << LOOP_VAR << ": Boolean = true;\n"; - os << indent() << "implicit val changed: AtomicBoolean = newChanged" << src_idx << ";\n"; + emit_loop_implicits(os, "newChanged" + std::to_string(src_idx)); } // Fiber dependencies (inside loop when looping) @@ -1088,7 +1273,11 @@ static void dump_synth_functions(STATE* s, output_streams& oss) if (synth_functions_state->is_fiber_evaluation) { os << indent() << "evaluated_map_" << synth_functions_state->fdecl_name << ".update(node.nodeNumber, true);\n"; } else { - os << indent() << instance_to_attr(synth_functions_state->source) << ".assign(node, result);\n"; + if (source_circular) { + os << indent() << instance_to_attr(synth_functions_state->source) << ".assign(node, result, changed);\n"; + } else { + os << indent() << instance_to_attr(synth_functions_state->source) << ".assign(node, result);\n"; + } os << indent() << instance_to_attr(synth_functions_state->source) << ".get(node);\n"; } @@ -1161,11 +1350,19 @@ class SynthImpl : public SynthImplementation { PHY_GRAPH* start_phy_graph = summary_graph_for(s, s->start_phylum); if (needs_fixed_point) { + // Demand each root once with caching on (isInsideFixedPoint = false). + // Inherited cycles converged at their productions; fiber/shared cycles + // converge via the per-production loops inside their *_sharedinfo_* evals, + // so one cached pass reaches the fixed point for every grammar we handle. + // + // A module-wide do/while (caching off, re-traverse until `changed` stays + // false) would cover cycles whose state is purely module-global, but it + // disables caching across the whole traversal -> re-descends the tree + // uncached every round -> super-linear, non-terminating at depth ~10. No + // grammar needs it. If one ever does, iterate only the circular collection, + // not the whole tree. os << indent() << "implicit val changed: AtomicBoolean = new AtomicBoolean(false);\n"; - os << indent() << "implicit val " << LOOP_VAR << ": Boolean = true;\n"; - os << indent() << "do {\n"; - ++nesting_level; - os << indent() << "changed.set(false);\n"; + os << indent() << "implicit val " << LOOP_VAR << ": Boolean = false;\n"; } os << indent() << "for (root <- t_" << decl_name(s->start_phylum) << ".nodes) {\n"; ++nesting_level; @@ -1182,10 +1379,7 @@ class SynthImpl : public SynthImplementation { --nesting_level; os << indent() << "}\n"; - if (needs_fixed_point) { - --nesting_level; - os << indent() << "} while (changed.get)\n"; - } + (void)module_needs_global_loop; #ifdef APS2SCALA os << indent() << "super.finish();\n"; @@ -1588,17 +1782,14 @@ void dump_rhs_instance_helper(AUG_GRAPH* aug_graph, BlockItem* item, INSTANCE* i o << "/* did not find any assignment for this fiber attribute " << instance << " -> " << directionStr << " <-" <<" */"; return; } else { - // Check if this is a local collection attribute (value_decl with collection direction) - // whose type is also COMBINABLE per its canonical signature. - // Such attributes may have conditional assignments (inside case/match blocks) that are - // not found by make_instance_assignment() at this level - they are handled elsewhere. + // A local collection attribute may be assigned only inside a case/match + // block, which make_instance_assignment() doesn't see at this level. Declaration attr = instance->fibered_attr.attr; bool is_local_collection = direction_is_collection(some_value_decl_direction(attr)); if (is_local_collection) { - // Verify via canonical signature that the type implements COMBINABLE - - // i.e., some source_class in its signature set has a "combine" declaration - // in its body (handles COMBINABLE directly, MAKE_LATTICE, and any other - // combinable module, without needing to walk parent signature chains). + // Check the type is combinable: look for a "combine" declaration in any + // class in its canonical signature set. This covers COMBINABLE, + // MAKE_LATTICE, etc. without walking parent signature chains. Type vt = infer_some_value_decl_type(attr); CanonicalType* ctype = canonical_type(vt); CanonicalSignatureSet csig_set = infer_canonical_signatures(ctype); @@ -1614,9 +1805,8 @@ void dump_rhs_instance_helper(AUG_GRAPH* aug_graph, BlockItem* item, INSTANCE* i } } if (is_combinable) { - // A local collection attribute with no direct assignment in this block - // (e.g., assigned only inside a conditional/case block) should return - // the type's initial/bottom value for this branch. + // No direct assignment in this block (it's inside a conditional/case), + // so this branch contributes the type's initial/bottom value. o << as_val(vt) << ".v_initial"; if (include_comments) { o << " /* local collection " << decl_name(attr) << ": no direct assignment, using initial */"; From ed8f8e217464c2041bc6b38624e35caf4cc8816f Mon Sep 17 00:00:00 2001 From: Amir Hesamian Date: Sat, 20 Jun 2026 02:41:50 +0000 Subject: [PATCH 17/20] working --- codegen/synth-impl.cc | 150 +++++++++++++++++++++++++-- examples/scala/compare-evaluators.sh | 6 +- 2 files changed, 147 insertions(+), 9 deletions(-) diff --git a/codegen/synth-impl.cc b/codegen/synth-impl.cc index a2a80f60..8b8f5c55 100644 --- a/codegen/synth-impl.cc +++ b/codegen/synth-impl.cc @@ -127,6 +127,43 @@ static vector sort_instances(AUG_GRAPH* aug_graph) { return result; } +// Strongly-connected components of the direct-edge subgraph. Each cycle becomes +// one group; everything else is a singleton. linearize_block uses these to break +// direct dependency cycles (e.g. c1.ptr2:=c2; c2.ptr1:=c1) that a plain topological +// sort cannot order. Only direct edges count here; indirect ones must not affect +// the schedule. +static std::vector > direct_cycle_groups(AUG_GRAPH* aug_graph) { + int n = aug_graph->instances.length; + + SccGraph scc_graph; + scc_graph_initialize(&scc_graph, n); + for (int i = 0; i < n; i++) { + scc_graph_add_vertex(&scc_graph, &aug_graph->instances.array[i]); + } + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + if (i != j && (edgeset_kind(aug_graph->graph[i * n + j]) & DEPENDENCY_MAYBE_DIRECT)) { + scc_graph_add_edge(&scc_graph, &aug_graph->instances.array[i], + &aug_graph->instances.array[j]); + } + } + } + + SCC_COMPONENTS* components = scc_graph_components(&scc_graph); + std::vector > groups; + for (int c = 0; c < components->length; c++) { + SCC_COMPONENT* comp = components->array[c]; + std::vector group; + for (int k = 0; k < comp->length; k++) { + group.push_back((INSTANCE*)comp->array[k]); + } + groups.push_back(group); + } + scc_graph_destroy(&scc_graph); + + return groups; +} + // Given an augmented dependency graph, it linearize it recursively static BlockItem* linearize_block_helper(AUG_GRAPH* aug_graph, const vector& sorted_instances, @@ -134,7 +171,8 @@ static BlockItem* linearize_block_helper(AUG_GRAPH* aug_graph, CONDITION* cond, BlockItem* prev, int remaining, - INSTANCE* aug_graph_instance) { + INSTANCE* aug_graph_instance, + const std::vector& component_of) { // impossible merge condition if (CONDITION_IS_IMPOSSIBLE(*cond)) { return NULL; @@ -154,7 +192,7 @@ static BlockItem* linearize_block_helper(AUG_GRAPH* aug_graph, // impossible merge condition, cannot schedule this instance if (MERGED_CONDITION_IS_IMPOSSIBLE(*cond, instance_condition(instance))) { scheduled[i] = true; - BlockItem* result = linearize_block_helper(aug_graph, sorted_instances, scheduled, cond, prev, remaining - 1, aug_graph_instance); + BlockItem* result = linearize_block_helper(aug_graph, sorted_instances, scheduled, cond, prev, remaining - 1, aug_graph_instance, component_of); scheduled[i] = false; return result; } @@ -163,7 +201,7 @@ static BlockItem* linearize_block_helper(AUG_GRAPH* aug_graph, // then this instance should not be included in the linearization linked-list if (aug_graph_instance != instance && !edgeset_kind(aug_graph->graph[instance->index * n + aug_graph_instance->index])) { scheduled[i] = true; - BlockItem* result = linearize_block_helper(aug_graph, sorted_instances, scheduled, cond, prev, remaining - 1, aug_graph_instance); + BlockItem* result = linearize_block_helper(aug_graph, sorted_instances, scheduled, cond, prev, remaining - 1, aug_graph_instance, component_of); scheduled[i] = false; return result; } @@ -187,6 +225,12 @@ static BlockItem* linearize_block_helper(AUG_GRAPH* aug_graph, continue; } + // don't wait on a dependency in the same cycle group, that's the cycle we + // break here; cross-group edges still enforce the order + if (component_of[j] == component_of[i]) { + continue; + } + ready_to_schedule = false; break; } @@ -210,10 +254,10 @@ static BlockItem* linearize_block_helper(AUG_GRAPH* aug_graph, int cmask = 1 << (if_rule_index(instance->fibered_attr.attr)); cond->positive |= cmask; - item->next_positive = linearize_block_helper(aug_graph, sorted_instances, scheduled, cond, item_base, remaining - 1, aug_graph_instance); + item->next_positive = linearize_block_helper(aug_graph, sorted_instances, scheduled, cond, item_base, remaining - 1, aug_graph_instance, component_of); cond->positive &= ~cmask; cond->negative |= cmask; - item->next_negative = linearize_block_helper(aug_graph, sorted_instances, scheduled, cond, item_base, remaining - 1, aug_graph_instance); + item->next_negative = linearize_block_helper(aug_graph, sorted_instances, scheduled, cond, item_base, remaining - 1, aug_graph_instance, component_of); cond->negative &= ~cmask; } else { struct block_item_instance* item = (struct block_item_instance*)malloc(sizeof(struct block_item_instance)); @@ -221,7 +265,7 @@ static BlockItem* linearize_block_helper(AUG_GRAPH* aug_graph, item->key = KEY_BLOCK_ITEM_INSTANCE; item->instance = instance; item->prev = prev; - item->next = linearize_block_helper(aug_graph, sorted_instances, scheduled, cond, item_base, remaining - 1, aug_graph_instance); + item->next = linearize_block_helper(aug_graph, sorted_instances, scheduled, cond, item_base, remaining - 1, aug_graph_instance, component_of); } scheduled[i] = false; @@ -246,7 +290,16 @@ static BlockItem* linearize_block(AUG_GRAPH* aug_graph, INSTANCE* aug_graph_inst CONDITION cond = {0, 0}; vector sorted_instances = sort_instances(aug_graph); - return linearize_block_helper(aug_graph, sorted_instances, scheduled, &cond, NULL, n, aug_graph_instance); + // map each instance to its cycle group so the schedule can skip same-group edges + std::vector > groups = direct_cycle_groups(aug_graph); + std::vector component_of(n, -1); + for (size_t g = 0; g < groups.size(); g++) { + for (auto it = groups[g].begin(); it != groups[g].end(); it++) { + component_of[(*it)->index] = (int)g; + } + } + + return linearize_block_helper(aug_graph, sorted_instances, scheduled, &cond, NULL, n, aug_graph_instance, component_of); } // Given an instance it traverses the direct dependency schedule @@ -989,6 +1042,39 @@ static void emit_local_fixed_point_loop(ostream& os, os << indent() << "}\n"; } +// Collects the field assignments of a locally-built object, e.g. for +// `c : Context := context(...)` it returns c.ptr1 := ..., c.ptr2 := ... . +// Each entry keeps the field, the rhs, and the instance the rhs computes (used to +// linearize the rhs). These fields are read directly via a_field.get by remote +// access (index_scope), so we have to assign them while evaluating the object. +struct ObjectFieldAssign { + Declaration field; + Expression rhs; + INSTANCE* instance; +}; + +static std::vector +collect_object_field_assignments(AUG_GRAPH* aug_graph, Declaration obj_decl) { + std::vector result; + Block body = matcher_body(top_level_match_m(aug_graph->match_rule)); + for (Declaration d = first_Declaration(block_body(body)); d; d = DECL_NEXT(d)) { + if (Declaration_KEY(d) != KEYnormal_assign) continue; + Expression lhs = assign_lhs(d); + if (Expression_KEY(lhs) != KEYfuncall) continue; + Declaration field = field_ref_p(lhs); + if (field == 0) continue; + Expression obj = field_ref_object(lhs); + if (Expression_KEY(obj) != KEYvalue_use) continue; + if (USE_DECL(value_use_use(obj)) != obj_decl) continue; + ObjectFieldAssign fa; + fa.field = field; + fa.rhs = assign_rhs(d); + fa.instance = Expression_info(assign_rhs(d))->value_for; + result.push_back(fa); + } + return result; +} + #ifdef APS2SCALA static void dump_synth_functions(STATE* s, ostream& os) #else /* APS2SCALA */ @@ -1281,6 +1367,52 @@ static void dump_synth_functions(STATE* s, output_streams& oss) os << indent() << instance_to_attr(synth_functions_state->source) << ".get(node);\n"; } + // Assign a locally-built object's fields (c.ptr1 := ..., c.ptr2 := ...) once + // the object itself is in `result`. Done after the cache write so a rhs that + // refers back to this object reuses the cached value. Re-assigning is fine: + // the attribute is ASSIGNED, not EVALUATED, so set() won't throw. + if (!synth_functions_state->is_fiber_evaluation && + !synth_functions_state->is_phylum_instance) { + Declaration obj_decl = synth_functions_state->source->fibered_attr.attr; + for (auto ag_it = synth_functions_state->aug_graphs.begin(); + ag_it != synth_functions_state->aug_graphs.end(); ag_it++) { + AUG_GRAPH* aug_graph = *ag_it; + std::vector field_assigns = + collect_object_field_assignments(aug_graph, obj_decl); + if (field_assigns.empty()) continue; + + current_aug_graph = aug_graph; + current_blocks.clear(); + current_blocks.push_back(matcher_body(top_level_match_m(aug_graph->match_rule))); + + os << indent() << "node match {\n"; + nesting_level++; + os << indent() << "case " << matcher_pat(top_level_match_m(aug_graph->match_rule)) << " => {\n"; + nesting_level++; + for (auto fa = field_assigns.begin(); fa != field_assigns.end(); fa++) { + // skip a bare alias of a parent attr (e.g. newEnv.outer := self.block_env): + // FiberDependencyDumper handles those and the parent attr isn't in scope here + if (fa->instance == NULL) continue; + // linearize for this field's instance so its rhs dependencies resolve + current_scope_block = linearize_block(aug_graph, fa->instance); + dumped_conditional_block_items.clear(); + dumped_instances.clear(); + os << indent() << "a_" << decl_name(fa->field) << DEREF; + if (debug) os << "assign"; else os << "set"; + os << "(result, " << fa->rhs << ");\n"; + } + nesting_level--; + os << indent() << "}\n"; + os << indent() << "case _ => ()\n"; + nesting_level--; + os << indent() << "};\n"; + + current_blocks.clear(); + dumped_conditional_block_items.clear(); + dumped_instances.clear(); + } + } + if (!synth_functions_state->is_fiber_evaluation) { os << indent() << "result\n"; } @@ -1331,7 +1463,9 @@ class SynthImpl : public SynthImplementation { dump_synth_functions(s, oss); - bool needs_fixed_point = s->original_state_dependency != 0; + // same flag dump_synth_functions uses to gate the implicit params, so finish() + // brings the matching implicits into scope for the eval calls below + bool needs_fixed_point = s->loop_required; // Implement finish routine: #ifdef APS2SCALA diff --git a/examples/scala/compare-evaluators.sh b/examples/scala/compare-evaluators.sh index 41120bd3..ad217297 100755 --- a/examples/scala/compare-evaluators.sh +++ b/examples/scala/compare-evaluators.sh @@ -3,7 +3,7 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" cd "$SCRIPT_DIR" -EVALUATORS=(DYNAMIC STATIC) +EVALUATORS=(DYNAMIC STATIC SYNTH) extract_results() { sed -n '/^Results:$/,$p' @@ -19,7 +19,11 @@ run_with_evaluator() { if ! make EVALUATOR="$evaluator" "$driver.class" > /dev/null 2>&1; then return 1 fi + local start end + start=$(date +%s.%N) make EVALUATOR="$evaluator" ARGS="$args" "$driver.run" 2>&1 | extract_results > "$outfile" + end=$(date +%s.%N) + printf " -> finished in %.2fs\n" "$(echo "$end - $start" | bc)" } run_driver() { From 43acc2e4b420dccf274f5fef5bef5ed375dfc3cc Mon Sep 17 00:00:00 2001 From: Amir Date: Sun, 21 Jun 2026 01:00:50 -0500 Subject: [PATCH 18/20] added some TODOs --- codegen/synth-impl.cc | 53 ++++++++++++----------------------------- examples/nested-ubd.aps | 2 +- 2 files changed, 16 insertions(+), 39 deletions(-) diff --git a/codegen/synth-impl.cc b/codegen/synth-impl.cc index 8b8f5c55..921e4b01 100644 --- a/codegen/synth-impl.cc +++ b/codegen/synth-impl.cc @@ -30,9 +30,6 @@ typedef struct synth_function_state { static AUG_GRAPH* current_aug_graph = NULL; static std::vector synth_functions_states; static SYNTH_FUNCTION_STATE* current_synth_functions_state = NULL; -// Set while dumping; read in finish(). True if a cycle closes through a -// fiber/shared-global collection. Currently informational only (see finish()). -static bool module_needs_global_loop = false; #define LOCAL_VALUE_FLAG (1 << 28) @@ -292,6 +289,8 @@ static BlockItem* linearize_block(AUG_GRAPH* aug_graph, INSTANCE* aug_graph_inst // map each instance to its cycle group so the schedule can skip same-group edges std::vector > groups = direct_cycle_groups(aug_graph); + + // TODO: std::vector component_of(n, -1) is hacky, do something cleaner here std::vector component_of(n, -1); for (size_t g = 0; g < groups.size(); g++) { for (auto it = groups[g].begin(); it != groups[g].end(); it++) { @@ -321,6 +320,8 @@ static BlockItem* find_surrounding_block(BlockItem* block, INSTANCE* instance) { return NULL; } +// Used only by instance_is_local, instance_is_synthesized and instance_is_inherited +// to determine the direction of an instance that does not have a fiber. static enum instance_direction custom_instance_direction(INSTANCE* i) { enum instance_direction dir = fibered_attr_direction(&i->fibered_attr); if (i->node == NULL) { @@ -928,6 +929,7 @@ static bool synth_function_is_circular(SYNTH_FUNCTION_STATE* st) { // A synthesized, circular, non-fiber attribute on a child node -- a candidate // whose local iteration can converge a cycle. static bool is_cycle_closing_candidate(INSTANCE* instance, AUG_GRAPH* aug_graph) { + // TODO: create a function instance_is_child and instance_is_parent and use it instead bool on_child_node = instance->node != NULL && instance->node != aug_graph->lhs_decl; return on_child_node && instance->fibered_attr.fiber == NULL && @@ -1012,19 +1014,7 @@ static void emit_loop_implicits(ostream& os, const string& flag) { os << indent() << "implicit val changed: AtomicBoolean = " << flag << ";\n"; } -// Emits a local successive-approximation loop, guarded so it is skipped when -// already inside an outer fixed point (that loop drives convergence instead): -// -// if (!isInsideFixedPoint) { -// val = new AtomicBoolean(true); -// while (.get) { -// .set(false); -// implicit val isInsideFixedPoint = true; implicit val changed = ; -// -// } -// } -// -// `emit_body` writes the body at the current indentation. +// Emits a local fixed-point loop static void emit_local_fixed_point_loop(ostream& os, const string& flag, const std::function& emit_body) { @@ -1058,6 +1048,7 @@ collect_object_field_assignments(AUG_GRAPH* aug_graph, Declaration obj_decl) { std::vector result; Block body = matcher_body(top_level_match_m(aug_graph->match_rule)); for (Declaration d = first_Declaration(block_body(body)); d; d = DECL_NEXT(d)) { + // TODO: make sure all IF statements have curly braces if (Declaration_KEY(d) != KEYnormal_assign) continue; Expression lhs = assign_lhs(d); if (Expression_KEY(lhs) != KEYfuncall) continue; @@ -1096,10 +1087,6 @@ static void dump_synth_functions(STATE* s, output_streams& oss) synth_functions_states = build_synth_functions_state(s); bool needs_fixed_point = s->loop_required; - // Module-wide loop needed only when a cycle closes through a fiber/shared - // collection; inherited cycles converge locally at their productions. - module_needs_global_loop = needs_fixed_point && module_has_fiber_cycle(synth_functions_states); - for (auto state_it = synth_functions_states.begin(); state_it != synth_functions_states.end(); state_it++) { SYNTH_FUNCTION_STATE* synth_functions_state = *state_it; current_synth_functions_state = synth_functions_state; @@ -1252,14 +1239,13 @@ static void dump_synth_functions(STATE* s, output_streams& oss) // above); everywhere else we compute once and report changes via `changed`. // We do NOT emit a per-attribute do/while for them: the LHS self-edge marks // them circular at every production, so looping at each one nests loops down - // the tree -> exponential in depth (Farrow 1986 sec.4: loop at the closure - // only). Fiber/shared cycles aren't local to a production, so they loop here. + // the tree -> exponential in depth. Fiber/shared cycles aren't local to a production, so they loop here. bool dump_fixed_point_loop = synth_functions_state->is_fiber_evaluation && declared_is_circular && !instance_is_pure_shared_info(synth_functions_state->source); string node_get = ATTR_DECL_IS_SHARED_INFO(synth_functions_state->source->fibered_attr.attr) ? "" : "node"; string node_assign = ATTR_DECL_IS_SHARED_INFO(synth_functions_state->source->fibered_attr.attr) ? "" : "node, "; // Converge any child cycle that closes here before computing this value, so - // the cached body below reads settled values. Replaces a whole-program loop. + // the cached body below reads settled values. This trick replaces a whole-program fixed-point loop. if (!synth_functions_state->is_fiber_evaluation) { std::vector child_cycle_instances = collect_child_cycle_instances(aug_graph); for (auto it2 = child_cycle_instances.begin(); it2 != child_cycle_instances.end(); it2++) { @@ -1413,6 +1399,7 @@ static void dump_synth_functions(STATE* s, output_streams& oss) } } + // TODO: "result" variable needs to be a constant string variable at the beginning of the file. if (!synth_functions_state->is_fiber_evaluation) { os << indent() << "result\n"; } @@ -1464,7 +1451,7 @@ class SynthImpl : public SynthImplementation { dump_synth_functions(s, oss); // same flag dump_synth_functions uses to gate the implicit params, so finish() - // brings the matching implicits into scope for the eval calls below + // brings the matching implicit into scope for the eval calls below bool needs_fixed_point = s->loop_required; // Implement finish routine: @@ -1484,17 +1471,6 @@ class SynthImpl : public SynthImplementation { PHY_GRAPH* start_phy_graph = summary_graph_for(s, s->start_phylum); if (needs_fixed_point) { - // Demand each root once with caching on (isInsideFixedPoint = false). - // Inherited cycles converged at their productions; fiber/shared cycles - // converge via the per-production loops inside their *_sharedinfo_* evals, - // so one cached pass reaches the fixed point for every grammar we handle. - // - // A module-wide do/while (caching off, re-traverse until `changed` stays - // false) would cover cycles whose state is purely module-global, but it - // disables caching across the whole traversal -> re-descends the tree - // uncached every round -> super-linear, non-terminating at depth ~10. No - // grammar needs it. If one ever does, iterate only the circular collection, - // not the whole tree. os << indent() << "implicit val changed: AtomicBoolean = new AtomicBoolean(false);\n"; os << indent() << "implicit val " << LOOP_VAR << ": Boolean = false;\n"; } @@ -1513,8 +1489,6 @@ class SynthImpl : public SynthImplementation { --nesting_level; os << indent() << "}\n"; - (void)module_needs_global_loop; - #ifdef APS2SCALA os << indent() << "super.finish();\n"; #endif /* ! APS2SCALA */ @@ -1649,6 +1623,8 @@ static vector > make_instance_assignment() { fatal_error("bad index [collection_assign] for instance"); } + // TODO: anything we can learn from this PR: + // https://github.com/boyland/aps/pull/166 if (step == 1 && is_outermost) { array[in->index].clear(); } else if (step == 2) { @@ -1891,6 +1867,7 @@ void dump_rhs_instance_helper(AUG_GRAPH* aug_graph, BlockItem* item, INSTANCE* i } return; } + // TODO: do not use non ASCII characters like — // valid_rhs is empty (all NULL defaults) — fall through to default handling } @@ -2134,7 +2111,7 @@ bool try_dump_funcall(Expression e, ostream& o) override { return true; } fatal_error("failed to find instance"); - return false; // unreachable + return false; } void dump_synth_instance(INSTANCE* instance, ostream& o) { diff --git a/examples/nested-ubd.aps b/examples/nested-ubd.aps index 17bf8b45..f0264cac 100644 --- a/examples/nested-ubd.aps +++ b/examples/nested-ubd.aps @@ -25,7 +25,7 @@ module NESTED_UBD[T :: var NESTED_UBD_TREE[]] extends T begin pragma inherited(decls_env); pragma synthesized(decls_defs, decls_errs); - circular attribute Block.block_env : DeclPairLattice; + attribute Block.block_env : DeclPairLattice; attribute Block.block_errs : Messages; pragma inherited (block_env); pragma synthesized (block_errs); From beee561d8abf01968b1847e58ad1d7509a6df89d Mon Sep 17 00:00:00 2001 From: Amir Date: Mon, 22 Jun 2026 11:47:03 -0500 Subject: [PATCH 19/20] WIP --- codegen/synth-impl.cc | 37 ++++------------------------------ examples/local-fiber-cycle.aps | 2 ++ 2 files changed, 6 insertions(+), 33 deletions(-) diff --git a/codegen/synth-impl.cc b/codegen/synth-impl.cc index 921e4b01..678b6c09 100644 --- a/codegen/synth-impl.cc +++ b/codegen/synth-impl.cc @@ -974,39 +974,6 @@ static std::vector collect_child_cycle_instances(AUG_GRAPH* aug_graph return result; } -// True if a cycle closes through a fiber/shared-global collection rather than a -// child's inherited attribute -- i.e. no local child instance can close it. -static bool module_has_fiber_cycle(const std::vector& states) { - for (auto it = states.begin(); it != states.end(); it++) { - SYNTH_FUNCTION_STATE* st = *it; - if (!st->is_fiber_evaluation) continue; - if (instance_is_pure_shared_info(st->source) && st->is_phylum_instance) { - // pure shared-info has no fixed-point loop of its own; skip - continue; - } - for (auto ag = st->aug_graphs.begin(); ag != st->aug_graphs.end(); ag++) { - AUG_GRAPH* aug_graph = *ag; - INSTANCE* inst = NULL; - if (st->is_phylum_instance) { - if (!find_instance(aug_graph, aug_graph->lhs_decl, st->source->fibered_attr, &inst)) { - continue; - } - } else { - inst = st->source; - } - if (!instance_circular(inst)) continue; - - // A local child instance closes this cycle at its production, so no - // module-wide loop is needed. Count it as a fiber cycle only when none does. - std::vector child_cycle_instances = collect_child_cycle_instances(aug_graph); - if (child_cycle_instances.empty()) { - return true; - } - } - } - return false; -} - // Emits the two implicits every fixed-point loop body opens with: isInsideFixedPoint // (disables caching) and `changed` bound to this loop's convergence flag. static void emit_loop_implicits(ostream& os, const string& flag) { @@ -1066,6 +1033,10 @@ collect_object_field_assignments(AUG_GRAPH* aug_graph, Declaration obj_decl) { return result; } +// TODO: if we are in our independent cycle, then we shouldn't blindly say don't run your own do-while +// loop because your parent is in a do-while loop. +// if you are in an independent cycle, then you should get your own do-while loop +// See, if it's related, local-cycle inside a local-cycle may be a proble. #ifdef APS2SCALA static void dump_synth_functions(STATE* s, ostream& os) #else /* APS2SCALA */ diff --git a/examples/local-fiber-cycle.aps b/examples/local-fiber-cycle.aps index 97f78271..3af92fb8 100644 --- a/examples/local-fiber-cycle.aps +++ b/examples/local-fiber-cycle.aps @@ -23,6 +23,8 @@ module LOCAL_FIBER_CYCLE[T :: var TINY[]] extends T begin w.scope := c; end; + -- TODO: create an example of local cycle, inside a local cycle + -- more complicated examples match ?this=branch(?w1,?w2) begin this.syn := w1.syn + w2.syn; c1 : Context := context(scope_depth(this.scope)); From 31d755bdea0ac03f81fcae2823a5b07f4ec547a9 Mon Sep 17 00:00:00 2001 From: Amir Hesamian Date: Mon, 29 Jun 2026 06:22:46 +0000 Subject: [PATCH 20/20] Updated the code to allow local cycles --- codegen/synth-impl.cc | 233 ++++++++++++++++++++++++++++-------------- 1 file changed, 158 insertions(+), 75 deletions(-) diff --git a/codegen/synth-impl.cc b/codegen/synth-impl.cc index 678b6c09..1e62db47 100644 --- a/codegen/synth-impl.cc +++ b/codegen/synth-impl.cc @@ -41,6 +41,7 @@ static SYNTH_FUNCTION_STATE* current_synth_functions_state = NULL; static const string LOOP_VAR = "isInsideFixedPoint"; static const string PREV_LOOP_VAR = "prevIsInsideFixedPoint"; +static const string RESULT_VAR_PREFIX = "result"; static SynthImplementation* synth_impl_ptr; @@ -290,7 +291,8 @@ static BlockItem* linearize_block(AUG_GRAPH* aug_graph, INSTANCE* aug_graph_inst // map each instance to its cycle group so the schedule can skip same-group edges std::vector > groups = direct_cycle_groups(aug_graph); - // TODO: std::vector component_of(n, -1) is hacky, do something cleaner here + // -1 = singleton (not part of any multi-node cycle), so the + // same-group check in the scheduler won't match anything. std::vector component_of(n, -1); for (size_t g = 0; g < groups.size(); g++) { for (auto it = groups[g].begin(); it != groups[g].end(); it++) { @@ -926,11 +928,21 @@ static bool synth_function_is_circular(SYNTH_FUNCTION_STATE* st) { return false; } -// A synthesized, circular, non-fiber attribute on a child node -- a candidate -// whose local iteration can converge a cycle. -static bool is_cycle_closing_candidate(INSTANCE* instance, AUG_GRAPH* aug_graph) { - // TODO: create a function instance_is_child and instance_is_parent and use it instead - bool on_child_node = instance->node != NULL && instance->node != aug_graph->lhs_decl; +static bool instance_is_child(INSTANCE* instance, AUG_GRAPH* aug_graph) { + return instance->node != NULL && instance->node != aug_graph->lhs_decl; +} + +static bool instance_is_parent(INSTANCE* instance, AUG_GRAPH* aug_graph) { + return instance->node != NULL && instance->node == aug_graph->lhs_decl; +} + +// Can this instance participate in a local cycle? +// Must be: on a child node, synthesized, circular, non-fiber, not an if-rule. +// +// Example: in `match ?self:Block = scope(?ds:Declarations)`, ds.decls_defs +// passes this check if it is declared circular. +static bool is_local_cycle_candidate(INSTANCE* instance, AUG_GRAPH* aug_graph) { + bool on_child_node = instance_is_child(instance, aug_graph); return on_child_node && instance->fibered_attr.fiber == NULL && !if_rule_p(instance->fibered_attr.attr) && @@ -938,9 +950,12 @@ static bool is_cycle_closing_candidate(INSTANCE* instance, AUG_GRAPH* aug_graph) instance_circular(instance); } -// True if `inherited` is the inherited circular attribute on the same child node -// that `instance` directly feeds -- the edge that closes the cycle here. -static bool closes_cycle_with(INSTANCE* inherited, INSTANCE* instance, AUG_GRAPH* aug_graph) { +// Is `inherited` in the same cycle as `instance`? +// Same child node, inherited direction, circular, and `instance` feeds it directly. +// +// Pattern: child.synth -> parent -> child.inherited -> child.synth (loop) +// If we find this pair we can iterate locally at this production. +static bool is_in_same_cycle(INSTANCE* inherited, INSTANCE* instance, AUG_GRAPH* aug_graph) { int n = aug_graph->instances.length; bool instance_feeds_inherited = edgeset_kind(aug_graph->graph[instance->index * n + inherited->index]) & DEPENDENCY_MAYBE_DIRECT; @@ -952,20 +967,59 @@ static bool closes_cycle_with(INSTANCE* inherited, INSTANCE* instance, AUG_GRAPH instance_feeds_inherited; } -// Child attributes that close a cycle at this production. A synthesized circular -// child instance that directly feeds its own inherited circular attribute closes -// the cycle here, so we can iterate it to a fixed point locally instead of -// program-wide. Productions that only forward an inherited attribute are skipped. +// Check if child cycle has no connection to any fiber/shared-info cycle. +// If no edge exists (both directions) between child cycle and fiber instances, +// then it is independent. aug_graph edges are transitive so one check is enough. +static bool is_child_cycle_independent(AUG_GRAPH* aug_graph, INSTANCE* cycle_instance) { + int n = aug_graph->instances.length; + + // collect instances in this child cycle (synth instance + inherited it feeds) + std::vector child_cycle_indices; + child_cycle_indices.push_back(cycle_instance->index); + for (int i = 0; i < n; i++) { + INSTANCE* inst = &aug_graph->instances.array[i]; + if (is_in_same_cycle(inst, cycle_instance, aug_graph)) { + child_cycle_indices.push_back(inst->index); + } + } + + // if any fiber/shared-info circular instance has edge to/from our cycle, + // then they are related -> not independent + for (int i = 0; i < n; i++) { + INSTANCE* inst = &aug_graph->instances.array[i]; + // Only consider fiber or shared-info instances that are circular + if (inst->fibered_attr.fiber == NULL && !ATTR_DECL_IS_SHARED_INFO(inst->fibered_attr.attr)) { + continue; + } + if (!instance_circular(inst)) { + continue; + } + for (size_t k = 0; k < child_cycle_indices.size(); k++) { + int idx = child_cycle_indices[k]; + if (edgeset_kind(aug_graph->graph[i * n + idx]) || + edgeset_kind(aug_graph->graph[idx * n + i])) { + return false; // related to a fiber/shared-info cycle + } + } + } + + return true; +} + +// Find child instances that have a cycle at this production. +// A synth circular child that feeds its own inherited circular attr = local cycle. static std::vector collect_child_cycle_instances(AUG_GRAPH* aug_graph) { std::vector result; int n = aug_graph->instances.length; - for (int index = 0; index < n; index++) { - INSTANCE* instance = &aug_graph->instances.array[index]; - if (!is_cycle_closing_candidate(instance, aug_graph)) continue; + for (int i = 0; i < n; i++) { + INSTANCE* instance = &aug_graph->instances.array[i]; + if (!is_local_cycle_candidate(instance, aug_graph)) { + continue; + } - for (int inner_index = 0; inner_index < n; inner_index++) { - INSTANCE* inherited = &aug_graph->instances.array[inner_index]; - if (closes_cycle_with(inherited, instance, aug_graph)) { + for (int j = 0; j < n; j++) { + INSTANCE* inherited = &aug_graph->instances.array[j]; + if (is_in_same_cycle(inherited, instance, aug_graph)) { result.push_back(instance); break; } @@ -981,12 +1035,18 @@ static void emit_loop_implicits(ostream& os, const string& flag) { os << indent() << "implicit val changed: AtomicBoolean = " << flag << ";\n"; } -// Emits a local fixed-point loop +// Emit a local while-loop. If is_independent, no isInsideFixedPoint guard +// because independent cycles need their own loop always. static void emit_local_fixed_point_loop(ostream& os, const string& flag, - const std::function& emit_body) { - os << indent() << "if (!" << LOOP_VAR << ") {\n"; - ++nesting_level; + const std::function& emit_body, + bool is_independent = false) { + if (is_independent) { + os << indent() << "// Independent cycle: always converge locally\n"; + } else { + os << indent() << "if (!" << LOOP_VAR << ") {\n"; + ++nesting_level; + } os << indent() << "val " << flag << " = new AtomicBoolean(true);\n"; os << indent() << "while (" << flag << ".get) {\n"; ++nesting_level; @@ -995,8 +1055,10 @@ static void emit_local_fixed_point_loop(ostream& os, emit_body(); --nesting_level; os << indent() << "}\n"; - --nesting_level; - os << indent() << "}\n"; + if (!is_independent) { + --nesting_level; + os << indent() << "}\n"; + } } // Collects the field assignments of a locally-built object, e.g. for @@ -1015,15 +1077,24 @@ collect_object_field_assignments(AUG_GRAPH* aug_graph, Declaration obj_decl) { std::vector result; Block body = matcher_body(top_level_match_m(aug_graph->match_rule)); for (Declaration d = first_Declaration(block_body(body)); d; d = DECL_NEXT(d)) { - // TODO: make sure all IF statements have curly braces - if (Declaration_KEY(d) != KEYnormal_assign) continue; + if (Declaration_KEY(d) != KEYnormal_assign) { + continue; + } Expression lhs = assign_lhs(d); - if (Expression_KEY(lhs) != KEYfuncall) continue; + if (Expression_KEY(lhs) != KEYfuncall) { + continue; + } Declaration field = field_ref_p(lhs); - if (field == 0) continue; + if (field == 0) { + continue; + } Expression obj = field_ref_object(lhs); - if (Expression_KEY(obj) != KEYvalue_use) continue; - if (USE_DECL(value_use_use(obj)) != obj_decl) continue; + if (Expression_KEY(obj) != KEYvalue_use) { + continue; + } + if (USE_DECL(value_use_use(obj)) != obj_decl) { + continue; + } ObjectFieldAssign fa; fa.field = field; fa.rhs = assign_rhs(d); @@ -1033,10 +1104,6 @@ collect_object_field_assignments(AUG_GRAPH* aug_graph, Declaration obj_decl) { return result; } -// TODO: if we are in our independent cycle, then we shouldn't blindly say don't run your own do-while -// loop because your parent is in a do-while loop. -// if you are in an independent cycle, then you should get your own do-while loop -// See, if it's related, local-cycle inside a local-cycle may be a proble. #ifdef APS2SCALA static void dump_synth_functions(STATE* s, ostream& os) #else /* APS2SCALA */ @@ -1062,6 +1129,9 @@ static void dump_synth_functions(STATE* s, output_streams& oss) SYNTH_FUNCTION_STATE* synth_functions_state = *state_it; current_synth_functions_state = synth_functions_state; + // result var has instance index suffix so each function has unique name + string result_var = RESULT_VAR_PREFIX + std::to_string(synth_functions_state->source->index); + if (include_comments) { os << indent() << "// " << synth_functions_state->source << " (" << (synth_functions_state->is_phylum_instance ? "phylum" : "aug-graph") << ")\n"; @@ -1156,7 +1226,7 @@ static void dump_synth_functions(STATE* s, output_streams& oss) if (synth_functions_state->is_fiber_evaluation) { os << indent() << "node match {\n"; } else { - os << indent() << "val result = node match {\n"; + os << indent() << "val " << result_var << " = node match {\n"; } nesting_level++; @@ -1206,32 +1276,27 @@ static void dump_synth_functions(STATE* s, output_streams& oss) aps_warning(aug_graph_instance->node, "Instance %s depends on itself but is not declared circular", instance_to_string(aug_graph_instance).c_str()); } - // Non-fiber cycles loop at the closing child (collect_child_cycle_instances, - // above); everywhere else we compute once and report changes via `changed`. - // We do NOT emit a per-attribute do/while for them: the LHS self-edge marks - // them circular at every production, so looping at each one nests loops down - // the tree -> exponential in depth. Fiber/shared cycles aren't local to a production, so they loop here. + // Non-fiber cycles: we iterate at the child level (collect_child_cycle_instances). + // We do NOT emit per-attribute do/while because that would nest loops + // down the tree and become exponential. Fiber cycles are different, they loop here. bool dump_fixed_point_loop = synth_functions_state->is_fiber_evaluation && declared_is_circular && !instance_is_pure_shared_info(synth_functions_state->source); string node_get = ATTR_DECL_IS_SHARED_INFO(synth_functions_state->source->fibered_attr.attr) ? "" : "node"; string node_assign = ATTR_DECL_IS_SHARED_INFO(synth_functions_state->source->fibered_attr.attr) ? "" : "node, "; - // Converge any child cycle that closes here before computing this value, so - // the cached body below reads settled values. This trick replaces a whole-program fixed-point loop. + // converge child cycles first, so the value below sees stable results if (!synth_functions_state->is_fiber_evaluation) { std::vector child_cycle_instances = collect_child_cycle_instances(aug_graph); for (auto it2 = child_cycle_instances.begin(); it2 != child_cycle_instances.end(); it2++) { INSTANCE* cycle_instance = *it2; - // Skipped when already inside an outer fixed point (see helper): nesting - // a do/while per scope would re-converge this child every outer pass, - // which is exponential in nesting depth. The outer loop revisits this - // production each round; here we just do one step and report via `changed`. + // independent = always loop, related = skip if already in outer loop + bool independent = is_child_cycle_independent(aug_graph, cycle_instance); emit_local_fixed_point_loop(os, "localChanged", [&] { dumped_conditional_block_items.clear(); dumped_instances.clear(); os << indent(); synth_impl_ptr->dump_synth_instance(cycle_instance, os); os << ";\n"; - }); + }, /* is_independent= */independent); } dumped_conditional_block_items.clear(); dumped_instances.clear(); @@ -1317,9 +1382,9 @@ static void dump_synth_functions(STATE* s, output_streams& oss) os << indent() << "evaluated_map_" << synth_functions_state->fdecl_name << ".update(node.nodeNumber, true);\n"; } else { if (source_circular) { - os << indent() << instance_to_attr(synth_functions_state->source) << ".assign(node, result, changed);\n"; + os << indent() << instance_to_attr(synth_functions_state->source) << ".assign(node, " << result_var << ", changed);\n"; } else { - os << indent() << instance_to_attr(synth_functions_state->source) << ".assign(node, result);\n"; + os << indent() << instance_to_attr(synth_functions_state->source) << ".assign(node, " << result_var << ");\n"; } os << indent() << instance_to_attr(synth_functions_state->source) << ".get(node);\n"; } @@ -1336,7 +1401,9 @@ static void dump_synth_functions(STATE* s, output_streams& oss) AUG_GRAPH* aug_graph = *ag_it; std::vector field_assigns = collect_object_field_assignments(aug_graph, obj_decl); - if (field_assigns.empty()) continue; + if (field_assigns.empty()) { + continue; + } current_aug_graph = aug_graph; current_blocks.clear(); @@ -1349,14 +1416,20 @@ static void dump_synth_functions(STATE* s, output_streams& oss) for (auto fa = field_assigns.begin(); fa != field_assigns.end(); fa++) { // skip a bare alias of a parent attr (e.g. newEnv.outer := self.block_env): // FiberDependencyDumper handles those and the parent attr isn't in scope here - if (fa->instance == NULL) continue; + if (fa->instance == NULL) { + continue; + } // linearize for this field's instance so its rhs dependencies resolve current_scope_block = linearize_block(aug_graph, fa->instance); dumped_conditional_block_items.clear(); dumped_instances.clear(); os << indent() << "a_" << decl_name(fa->field) << DEREF; - if (debug) os << "assign"; else os << "set"; - os << "(result, " << fa->rhs << ");\n"; + if (debug) { + os << "assign"; + } else { + os << "set"; + } + os << "(" << result_var << ", " << fa->rhs << ");\n"; } nesting_level--; os << indent() << "}\n"; @@ -1370,9 +1443,8 @@ static void dump_synth_functions(STATE* s, output_streams& oss) } } - // TODO: "result" variable needs to be a constant string variable at the beginning of the file. if (!synth_functions_state->is_fiber_evaluation) { - os << indent() << "result\n"; + os << indent() << result_var << "\n"; } nesting_level--; @@ -1451,8 +1523,9 @@ class SynthImpl : public SynthImplementation { for (i = 0; i < start_phy_graph->instances.length; i++) { INSTANCE* in = &start_phy_graph->instances.array[i]; - if (!instance_is_synthesized(in)) + if (!instance_is_synthesized(in)) { continue; + } string eval_name = instance_to_string_with_nodetype(s->start_phylum, &start_phy_graph->instances.array[i]); os << indent() << "eval_" << eval_name << "(root);\n"; @@ -1642,10 +1715,11 @@ void dump_assignment(INSTANCE* in, Expression rhs, ostream& o) { field = USE_DECL(value_use_use(lhs)); #ifdef APS2SCALA o << "a_" << decl_name(field) << "."; - if (debug) + if (debug) { o << "assign"; - else + } else { o << "set"; + } o << "(" << rhs; if (tracking_fiber_convergence) { o << ", changed"; @@ -1666,13 +1740,15 @@ void dump_assignment(INSTANCE* in, Expression rhs, ostream& o) { break; case KEYfuncall: field = field_ref_p(lhs); - if (field == 0) + if (field == 0) { fatal_error("what sort of assignment lhs: %d", tnode_line_number(assign)); + } o << "a_" << decl_name(field) << DEREF; - if (debug) + if (debug) { o << "assign"; - else + } else { o << "set"; + } o << "(" << field_ref_object(lhs) << "," << rhs; if (tracking_fiber_convergence) { o << ", changed"; @@ -1689,10 +1765,11 @@ void dump_assignment(INSTANCE* in, Expression rhs, ostream& o) { if (rhs) { if (Declaration_info(ad)->decl_flags & LOCAL_ATTRIBUTE_FLAG) { o << "a" << LOCAL_UNIQUE_PREFIX(ad) << "_" << asym << DEREF; - if (debug) + if (debug) { o << "assign"; - else + } else { o << "set"; + } o << "(anchor," << rhs << ");\n"; } else { int i = LOCAL_UNIQUE_PREFIX(ad); @@ -1740,17 +1817,19 @@ void dump_assignment(INSTANCE* in, Expression rhs, ostream& o) { o << "v_" << asym << " = somehow_combine(v_" << asym << "," << rhs << ");\n"; } else { int i = LOCAL_UNIQUE_PREFIX(ad); - if (i == 0) + if (i == 0) { o << "v_" << asym << " = " << rhs << "; // function\n"; - else + } else { o << "v" << i << "_" << asym << " = " << rhs << ";\n"; + } } } else { o << "a_" << asym << DEREF; - if (debug) + if (debug) { o << "assign"; - else + } else { o << "set"; + } o << "(v_" << decl_name(in->node) << "," << rhs << ");\n"; } } else { @@ -1766,10 +1845,11 @@ void dump_assignment(INSTANCE* in, Expression rhs, ostream& o) { if (rhs) { // assigning field of object o << "a_" << asym << DEREF; - if (debug) + if (debug) { o << "assign"; - else + } else { o << "set"; + } o << "(v_" << decl_name(in->node) << "," << rhs << ");\n"; } else { if (include_comments) { @@ -1804,7 +1884,9 @@ void dump_rhs_instance_helper(AUG_GRAPH* aug_graph, BlockItem* item, INSTANCE* i // Filter out NULLs first vector valid_rhs; for (auto it = relevant_assignments.begin(); it != relevant_assignments.end(); it++) { - if (*it != NULL) valid_rhs.push_back(*it); + if (*it != NULL) { + valid_rhs.push_back(*it); + } } if (!valid_rhs.empty()) { @@ -1838,8 +1920,7 @@ void dump_rhs_instance_helper(AUG_GRAPH* aug_graph, BlockItem* item, INSTANCE* i } return; } - // TODO: do not use non ASCII characters like — - // valid_rhs is empty (all NULL defaults) — fall through to default handling + // valid_rhs is empty (all NULL defaults) -- fall through to default handling } if (instance->fibered_attr.fiber != NULL) { @@ -2073,7 +2154,9 @@ bool try_dump_funcall(Expression e, ostream& o) override { } Declaration attr = attr_ref_p(e); - if (attr == nullptr) return false; + if (attr == nullptr) { + return false; + } Declaration node = USE_DECL(value_use_use(first_Actual(funcall_actuals(e)))); FIBERED_ATTRIBUTE fiber_attr = {attr, NULL}; INSTANCE* instance; @@ -2097,7 +2180,7 @@ void dump_synth_instance(INSTANCE* instance, ostream& o) { BlockItem* block = find_surrounding_block(current_scope_block, instance); Declaration node = instance->node; - bool is_parent_instance = current_aug_graph->lhs_decl == instance->node; + bool is_parent_instance = instance_is_parent(instance, current_aug_graph); bool is_synthesized = instance_is_synthesized(instance); bool is_inherited = instance_is_inherited(instance);