From de313229fa3251f44eeffd750edbfcab446f08cb Mon Sep 17 00:00:00 2001 From: Christopher Neal Date: Sun, 12 Apr 2026 00:39:29 -0400 Subject: [PATCH 1/4] tests for sched_db.cc and header documentation --- quickTest/Makefile | 13 +- quickTest/SystemTests/Makefile | 66 ++ quickTest/SystemTests/test_sched_db.cc | 814 +++++++++++++++ src/System/sched_db.cc | 348 +++---- src/include/sched_db.h | 1253 +++++++++++++++++++----- 5 files changed, 2095 insertions(+), 399 deletions(-) create mode 100644 quickTest/SystemTests/Makefile create mode 100644 quickTest/SystemTests/test_sched_db.cc diff --git a/quickTest/Makefile b/quickTest/Makefile index c1b6ecae..febd83e9 100644 --- a/quickTest/Makefile +++ b/quickTest/Makefile @@ -1,10 +1,10 @@ TEST_BASE = $(shell pwd) -.PHONY: FRC FVMModUnitTests FVMAdaptTest Containers Tools test default +.PHONY: FRC FVMModUnitTests FVMAdaptTest Containers Tools SystemTests test default -default: FVM FVMModUnitTests FVMAdaptTest Containers Tools - cat Containers/TestResults Tools/TestResults FVMModUnitTests/TestResults FVM/TestResults > TestResults - rm Containers/TestResults Tools/TestResults FVMModUnitTests/TestResults FVM/TestResults +default: FVM FVMModUnitTests FVMAdaptTest Containers Tools SystemTests + cat Containers/TestResults Tools/TestResults FVMModUnitTests/TestResults FVM/TestResults SystemTests/TestResults > TestResults + rm Containers/TestResults Tools/TestResults FVMModUnitTests/TestResults FVM/TestResults SystemTests/TestResults @grep PASS TestResults @grep FAIL TestResults || true @! grep -q FAIL TestResults @@ -26,6 +26,9 @@ Containers: FRC Tools: FRC $(MAKE) -C Tools LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) +SystemTests: FRC + $(MAKE) -C SystemTests LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) + clean: FRC rm -f TestResults $(MAKE) -C FVM LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) clean @@ -33,6 +36,7 @@ clean: FRC $(MAKE) -C Containers LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) clean $(MAKE) -C Tools LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) clean $(MAKE) -C FVMAdaptTest LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) clean + $(MAKE) -C SystemTests LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) clean distclean: rm -f TestResults @@ -41,3 +45,4 @@ distclean: $(MAKE) -C Containers LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) distclean $(MAKE) -C Tools LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) distclean $(MAKE) -C FVMAdaptTest LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) distclean + $(MAKE) -C SystemTests LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) distclean diff --git a/quickTest/SystemTests/Makefile b/quickTest/SystemTests/Makefile new file mode 100644 index 00000000..9ae62fd1 --- /dev/null +++ b/quickTest/SystemTests/Makefile @@ -0,0 +1,66 @@ +############################################################################### +# +# Copyright 2008-2025, Mississippi State University +# +# This file is part of the Loci Framework. +# +# The Loci Framework is free software: you can redistribute it and/or modify +# it under the terms of the Lesser GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# The Loci Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# Lesser GNU General Public License for more details. +# +# You should have received a copy of the Lesser GNU General Public License +# along with the Loci Framework. If not, see +# +############################################################################### +# LOCI_BASE should be set before the Makefile +# Set TARGET to the name of your program +# Set FILES to list '.loci' files that will be compiled into your module, or + +########################################################################### +# No changes required below this line +########################################################################### + +include $(LOCI_BASE)/Loci.conf +include $(LOCI_BASE)/version.conf + +INCLUDES = -I../contrib/doctest -I$(LOCI_BASE)/include + +AUTOMATIC_FILES = $(call loci_compile_files,) +AUTOMATIC_OBJS = $(call loci_file2objs,$(AUTOMATIC_FILES)) +AUTOMATIC_TESTS= $(subst .o,.test, $(AUTOMATIC_OBJS)) +TESTS = $(AUTOMATIC_TESTS) + +TestResults: $(TESTS) + cat $(TESTS) > TestResults; rm $(TESTS) + +%.test: %.o + $(LD) -o $*.exe $^ $(LOCAL_LIBS) $(LIBS) $(LDFLAGS) + @(LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) DYLD_LIBRARY_PATH=$(DYLD_LIBRARY_PATH) ./$*.exe > $*.log 2>&1 || true) + tail -n 1 $*.log > $*.log1 + echo -n SystemTests/$*":" > $@ + @if grep -q "SUCCESS!" $*.log1 ; then echo " PASSED!"; else echo " FAILED!"; fi >>$@ + rm $*.log1 $*.exe + +clean: + rm -fr *.o *.exe *.d *.log *.log1 + +# Junk files that are created while editing and running cases +JUNK = $(wildcard *~) $(wildcard crash_dump.*) core debug output $(wildcard *.o) +# ".cc" files created from .loci files +LOCI_LPP_FILES = $(LOCI_FILES:.loci=.$(LPP_I_SUFFIX)) + +distclean: clean + rm -fr TestResults $(JUNK) $(LOCI_LPP_FILES) $(DEPEND_FILES) $(TESTS) + +DEPEND_FILES=$(subst .o,.d,$(OBJS)) + +#include automatically generated dependencies +ifeq ($(filter $(MAKECMDGOALS),clean distclean ),) +-include $(DEPEND_FILES) +endif diff --git a/quickTest/SystemTests/test_sched_db.cc b/quickTest/SystemTests/test_sched_db.cc new file mode 100644 index 00000000..16fdc7c2 --- /dev/null +++ b/quickTest/SystemTests/test_sched_db.cc @@ -0,0 +1,814 @@ +#include + +#define DOCTEST_CONFIG_IMPLEMENT +#include + +#include +#include +#include +#include +#include +#include + +using namespace Loci; + +namespace { + +/// @brief Returns an inclusive `entitySet` interval `[first, last]`. +/// @param[in] first First entity in the interval. +/// @param[in] last Last entity in the interval. +/// @return An `entitySet` spanning the requested inclusive interval. +entitySet make_range(int first, int last) { + return entitySet(interval(first, last)); +} + +/// @brief Allocates `values` on `domain` and fills it from `entries`. +/// @param[in,out] values Store to allocate and populate. +/// @param[in] domain Domain to allocate in the store. +/// @param[in] entries Values copied into the store in domain iteration order. +/// @throws std::logic_error When `entries.size()` does not match `domain.size()`. +void assign_store_values(store &values, const entitySet &domain, + const std::vector &entries) { + if(static_cast(domain.size()) != entries.size()) { + throw std::logic_error("store seed data does not match the domain size"); + } + + values.allocate(domain); + size_t index = 0; + for(entitySet::const_iterator ei = domain.begin(); ei != domain.end(); + ++ei, ++index) { + values[*ei] = entries[index]; + } +} + +/// @brief Allocates `mapping` on `domain` and fills it from `entries`. +/// @param[in,out] mapping Map to allocate and populate. +/// @param[in] domain Domain to allocate in the map. +/// @param[in] entries Target entities copied into the map in domain iteration +/// order. +/// @throws std::logic_error When `entries.size()` does not match `domain.size()`. +void assign_map_values(Map &mapping, const entitySet &domain, + const std::vector &entries) { + if(static_cast(domain.size()) != entries.size()) { + throw std::logic_error("map seed data does not match the domain size"); + } + + mapping.allocate(domain); + size_t index = 0; + for(entitySet::const_iterator ei = domain.begin(); ei != domain.end(); + ++ei, ++index) { + mapping[*ei] = entries[index]; + } +} + +/// @brief Returns true when `rep` wraps a non-null representation pointer. +/// @param[in] rep Store representation handle to inspect. +/// @return True when `rep` is non-null. +bool has_rep(const storeRepP &rep) { + return rep != static_cast(0); +} + +/// @brief Returns true when both handles refer to the same store representation. +/// @param[in] lhs First store representation handle. +/// @param[in] rhs Second store representation handle. +/// @return True when both handles are non-null and share the same underlying +/// representation pointer. +bool same_rep(const storeRepP &lhs, const storeRepP &rhs) { + if(!has_rep(lhs) || !has_rep(rhs)) { + return false; + } + return lhs.operator->() == rhs.operator->(); +} + +/// @brief Builds a rule descriptor string that `rule(...)` can parse. +/// @param[in] source_expr Source portion of the rule signature. +/// @param[in] target_expr Target portion of the rule signature. +/// @param[in] qualifier Qualifier portion of the rule signature. +/// @return A `rule` constructed from the assembled signature. +rule make_rule(const std::string &source_expr, const std::string &target_expr, + const std::string &qualifier) { + std::ostringstream signature; + signature << "source(" << source_expr << "),target(" << target_expr + << "),qualifier(" << qualifier << ")"; + return rule(signature.str()); +} + +/// @brief Collects `vars` into a `variableSet`. +/// @param[in] vars Variables to insert. +/// @return A `variableSet` containing every variable from `vars`. +variableSet make_var_set(const std::vector &vars) { + variableSet set; + for(size_t i = 0; i < vars.size(); ++i) { + set += vars[i]; + } + return set; +} + +/// @brief Copies a `std::list` into an indexable vector. +/// @param[in] entries Communication records to copy. +/// @return A vector containing the same records in the same order. +std::vector to_vector(const std::list &entries) { + return std::vector(entries.begin(), entries.end()); +} + +} // namespace + +TEST_CASE("sched_db constructor mirrors fact_db variables and existence") { + fact_db facts; + store cells; + + // Seed one typed fact so the constructor has scheduler state to mirror. + assign_store_values(cells, make_range(4, 6), {10, 20, 30}); + facts.create_fact("cells", cells); + + sched_db sched(facts); + + // Verify the constructor registers the variable and its self-alias/synonym + // bookkeeping. + CHECK(sched.get_typed_variables().inSet(variable("cells"))); + CHECK(sched.get_aliases(variable("cells")).inSet(variable("cells"))); + CHECK(sched.get_synonyms(variable("cells")).inSet(variable("cells"))); + + // Verify the mirrored fact domain becomes known existence and leaves the + // remaining scheduler state empty/defaulted. + CHECK(sched.variable_existence(variable("cells")) == make_range(4, 6)); + CHECK_FALSE(sched.is_a_Map(variable("cells"))); + CHECK(sched.get_variable_requests(variable("cells")) == EMPTY); +} + +TEST_CASE("set_variable_type installs intensional facts and empty schedule state") { + fact_db facts; + sched_db sched(facts); + store derived; + + // Build a store representation that will be installed as an intensional fact. + assign_store_values(derived, make_range(10, 11), {3, 5}); + + sched.set_variable_type(variable("derived"), derived.Rep(), facts); + + // Verify the new variable is tracked by both sched_db and fact_db, and that + // sched_db starts it with no derived existence yet. + CHECK(sched.get_typed_variables().inSet(variable("derived"))); + CHECK(facts.get_intensional_facts().inSet(variable("derived"))); + CHECK_FALSE(facts.get_extensional_facts().inSet(variable("derived"))); + CHECK(sched.variable_existence(variable("derived")) == EMPTY); + + // Verify the installed fact points at the expected store contents. + storeRepP rep = facts.get_variable("derived"); + REQUIRE(has_rep(rep)); + store fetched(rep); + CHECK(fetched.domain() == make_range(10, 11)); + CHECK(fetched[10] == 3); + CHECK(fetched[11] == 5); +} + +TEST_CASE("alias_variable keeps alias bookkeeping in sync with fact_db") { + fact_db facts; + store cells; + assign_store_values(cells, make_range(1, 3), {7, 8, 9}); + facts.create_fact("cells", cells); + + sched_db sched(facts); + sched.alias_variable(variable("cells"), variable("cells_alias"), facts); + + variableSet aliases = sched.get_aliases(variable("cells")); + + // Verify sched_db records both names in the shared alias metadata. + CHECK(aliases.inSet(variable("cells"))); + CHECK(aliases.inSet(variable("cells_alias"))); + CHECK(sched.get_aliases(variable("cells_alias")) == aliases); + CHECK(sched.get_antialiases(variable("cells_alias")) + .inSet(variable("cells"))); + CHECK(sched.get_typed_variables().inSet(variable("cells_alias"))); + + // Verify fact_db mirrors the alias relationship onto the same store + // representation. + CHECK(same_rep(facts.get_variable("cells"), facts.get_variable("cells_alias"))); +} + +TEST_CASE("alias_variable accepts either argument order when one name already exists") { + fact_db facts; + store cells; + assign_store_values(cells, make_range(7, 9), {3, 4, 5}); + facts.create_fact("cells", cells); + + sched_db sched(facts); + sched.alias_variable(variable("cells_alias"), variable("cells")); + + variableSet aliases = sched.get_aliases(variable("cells")); + + // Verify the helper resolves the existing variable as the alias source rather + // than treating the reversed argument order as an error. + CHECK_FALSE(sched.errors_found()); + CHECK(aliases.inSet(variable("cells"))); + CHECK(aliases.inSet(variable("cells_alias"))); + CHECK(sched.get_aliases(variable("cells_alias")) == aliases); + CHECK(sched.get_antialiases(variable("cells_alias")) + .inSet(variable("cells"))); + CHECK(sched.get_typed_variables().inSet(variable("cells_alias"))); +} + +TEST_CASE("synonym_variable records canonical lookup behavior") { + fact_db facts; + store cells; + assign_store_values(cells, make_range(2, 4), {1, 2, 3}); + facts.create_fact("cells", cells); + + sched_db sched(facts); + sched.synonym_variable(variable("cells"), variable("cells_shadow"), facts); + + // Verify the synonym resolves back to the canonical scheduler name. + CHECK(sched.remove_synonym(variable("cells_shadow")) == variable("cells")); + + variableSet synonyms = sched.get_synonyms(variable("cells")); + + // Verify the synonym set and fact_db both treat the two names as one logical + // variable. + CHECK(synonyms.inSet(variable("cells"))); + CHECK(synonyms.inSet(variable("cells_shadow"))); + CHECK(sched.get_typed_variables().inSet(variable("cells_shadow"))); + CHECK( + same_rep(facts.get_variable("cells"), facts.get_variable("cells_shadow"))); +} + +TEST_CASE("synonyms resolve requests and direct existence through the canonical variable") { + fact_db facts; + store cells; + assign_store_values(cells, make_range(2, 4), {1, 2, 3}); + facts.create_fact("cells", cells); + + sched_db sched(facts); + sched.synonym_variable(variable("cells"), variable("cells_shadow"), facts); + sched.set_variable_existence(variable("cells_shadow"), make_range(20, 21)); + sched.variable_request(variable("cells_shadow"), make_range(21, 22)); + + entitySet expected_existence = make_range(2, 4); + expected_existence += make_range(20, 21); + + // Verify both names resolve through the same canonical scheduler entry. + CHECK(sched.remove_synonym(variable("cells_shadow")) == variable("cells")); + CHECK(sched.variable_existence(variable("cells")) == expected_existence); + CHECK(sched.variable_existence(variable("cells_shadow")) == expected_existence); + CHECK(sched.get_variable_requests(variable("cells")) == make_range(21, 22)); + CHECK(sched.get_variable_requests(variable("cells_shadow")) == + make_range(21, 22)); +} + +TEST_CASE("rotation groups are recorded and try_get helpers stay safe for unknown variables") { + fact_db facts; + store cells; + store faces; + assign_store_values(cells, make_range(1, 2), {10, 20}); + assign_store_values(faces, make_range(3, 4), {30, 40}); + facts.create_fact("cells", cells); + facts.create_fact("faces", faces); + + sched_db sched(facts); + variableSet rotations = make_var_set({variable("cells"), variable("faces")}); + sched.set_variable_rotations(rotations); + + // Verify every member of the rotation group sees the full recorded set. + CHECK(sched.get_rotations(variable("cells")) == rotations); + CHECK(sched.get_rotations(variable("faces")) == rotations); + + // Verify the unknown-safe accessors return empty results instead of aborting. + CHECK(sched.try_get_synonyms(variable("ghost")) == variableSet(EMPTY)); + CHECK(sched.try_get_aliases(variable("ghost")) == variableSet(EMPTY)); + CHECK(sched.try_get_antialiases(variable("ghost")) == variableSet(EMPTY)); + CHECK(sched.try_get_rotations(variable("ghost")) == variableSet(EMPTY)); +} + +TEST_CASE("aliases share scheduling data but keep separate existence and request state") { + fact_db facts; + store cells; + assign_store_values(cells, make_range(1, 2), {10, 20}); + facts.create_fact("cells", cells); + + sched_db sched(facts); + sched.alias_variable(variable("cells"), variable("cells_alias"), facts); + sched.set_variable_existence(variable("cells"), make_range(30, 31)); + sched.variable_request(variable("cells"), make_range(31, 32)); + + // Verify the alias still participates in the shared alias group. + CHECK(sched.get_aliases(variable("cells_alias")).inSet(variable("cells"))); + CHECK(sched.get_aliases(variable("cells_alias")) + .inSet(variable("cells_alias"))); + + // Verify existence and requests remain separate because aliases keep distinct + // sched_info records. + CHECK(sched.variable_existence(variable("cells_alias")) == EMPTY); + CHECK(sched.get_variable_requests(variable("cells_alias")) == EMPTY); +} + +TEST_CASE("aliases of map variables share map metadata and cached map queries") { + fact_db facts; + Map cell_to_parent; + assign_map_values(cell_to_parent, make_range(1, 4), {10, 10, 11, 13}); + facts.create_fact("cell_to_parent", cell_to_parent); + + sched_db sched(facts); + sched.alias_variable(variable("cell_to_parent"), + variable("cell_to_parent_alias"), facts); + + // Alias variables share sched_data, so map-ness and cached map queries should + // behave exactly like the canonical map variable. + CHECK(sched.is_a_Map(variable("cell_to_parent_alias"))); + CHECK(sched.image(variable("cell_to_parent_alias"), make_range(1, 3)) == + make_range(10, 11)); + + std::pair preimage = + sched.preimage(variable("cell_to_parent_alias"), make_range(10, 10)); + CHECK(preimage.first == make_range(1, 2)); + CHECK(preimage.second == make_range(1, 2)); +} + +TEST_CASE("requests are intersected with rule existence and can be cleared") { + fact_db facts; + store cells; + assign_store_values(cells, make_range(10, 12), {4, 5, 6}); + facts.create_fact("cells", cells); + + sched_db sched(facts); + rule producer = make_rule("upstream", "cells", "rule_one"); + + sched.set_existential_info(variable("cells"), producer, make_range(10, 12)); + sched.variable_request(variable("cells"), make_range(11, 13)); + sched.add_extra_unit_request(variable("cells"), make_range(20, 21)); + + // Verify the direct getters expose the stored existential and request state. + CHECK(sched.get_existential_info(variable("cells"), producer) == + make_range(10, 12)); + CHECK(sched.get_variable_requests(variable("cells")) == make_range(11, 13)); + + // Verify per-rule requests intersect the existential region, and that UNIT + // extras are tracked separately. + CHECK(sched.get_variable_request(producer, variable("cells")) == + make_range(11, 12)); + CHECK(sched.get_extra_unit_request(variable("cells")) == make_range(20, 21)); + + sched.clear_variable_request(); + + // Verify clearing requests also clears the extra UNIT-request bookkeeping. + CHECK(sched.get_variable_requests(variable("cells")) == EMPTY); + CHECK(sched.get_extra_unit_request(variable("cells")) == EMPTY); +} + +TEST_CASE("time variables exist everywhere and ignore request updates") { + sched_db sched; + variable time_step("$time_step"); + + // Sanity-check the fixture before probing sched_db behavior. + REQUIRE(time_step.get_info().tvar); + + // Verify time variables behave as globally existing scheduler variables and + // ignore request accumulation. + CHECK(sched.variable_existence(time_step) == ~EMPTY); + CHECK_NOTHROW(sched.variable_request(time_step, make_range(1, 3))); + CHECK_FALSE(sched.errors_found()); +} + +TEST_CASE("conflicting existential rules are reported as scheduler errors") { + fact_db facts; + store cells; + assign_store_values(cells, make_range(1, 3), {10, 11, 12}); + facts.create_fact("cells", cells); + + sched_db sched(facts); + rule first = make_rule("left", "cells", "rule_left"); + rule second = make_rule("right", "cells", "rule_right"); + + sched.clear_errors(); + sched.set_existential_info(variable("cells"), first, make_range(1, 2)); + + // The first producer establishes ownership without conflict. + CHECK_FALSE(sched.errors_found()); + + sched.set_existential_info(variable("cells"), second, make_range(2, 3)); + + // The overlapping second producer should trip the sticky error flag while + // leaving each rule's recorded contribution intact. + CHECK(sched.errors_found()); + CHECK(sched.get_existential_info(variable("cells"), first) == + make_range(1, 2)); + CHECK(sched.get_existential_info(variable("cells"), second) == + make_range(2, 3)); +} + +TEST_CASE("repeated existential writes from the same rule accumulate without conflicts") { + fact_db facts; + store cells; + assign_store_values(cells, make_range(1, 4), {10, 11, 12, 13}); + facts.create_fact("cells", cells); + + sched_db sched(facts); + rule producer = make_rule("left", "cells", "same_rule"); + rule unrelated = make_rule("right", "cells", "unrelated"); + + sched.clear_errors(); + sched.set_existential_info(variable("cells"), producer, make_range(1, 2)); + sched.set_existential_info(variable("cells"), producer, make_range(2, 4)); + sched.variable_request(variable("cells"), make_range(3, 5)); + + // Verify repeated writes from the same producer merge instead of conflicting. + CHECK_FALSE(sched.errors_found()); + CHECK(sched.get_existential_info(variable("cells"), producer) == + make_range(1, 4)); + + // Verify unrelated rules still report no request overlap. + CHECK(sched.get_variable_request(producer, variable("cells")) == + make_range(3, 4)); + CHECK(sched.get_variable_request(unrelated, variable("cells")) == EMPTY); +} + +TEST_CASE("map variables expose cached image and preimage queries") { + fact_db facts; + Map cell_to_parent; + store cells; + assign_map_values(cell_to_parent, make_range(1, 4), {10, 10, 11, 13}); + assign_store_values(cells, make_range(1, 4), {1, 2, 3, 4}); + facts.create_fact("cell_to_parent", cell_to_parent); + facts.create_fact("cells", cells); + + sched_db sched(facts); + + // Verify map-ness is detected from the shared scheduling data. + CHECK(sched.is_a_Map(variable("cell_to_parent"))); + CHECK_FALSE(sched.is_a_Map(variable("cells"))); + + // Verify forward image queries return the mapped codomain and remain stable + // across repeated lookups. + CHECK(sched.image(variable("cell_to_parent"), make_range(1, 3)) == + make_range(10, 11)); + CHECK(sched.image(variable("cell_to_parent"), make_range(1, 3)) == + make_range(10, 11)); + + // Verify preimage queries preserve the underlying map semantics. + std::pair preimage = + sched.preimage(variable("cell_to_parent"), make_range(10, 10)); + CHECK(preimage.first == make_range(1, 2)); + CHECK(preimage.second == make_range(1, 2)); + + // Non-map variables should return the empty fallbacks for both APIs. + CHECK(sched.image(variable("cells"), make_range(1, 3)) == EMPTY); + std::pair store_preimage = + sched.preimage(variable("cells"), make_range(10, 10)); + CHECK(store_preimage.first == EMPTY); + CHECK(store_preimage.second == EMPTY); +} + +TEST_CASE("direct existence and shadow helpers update bookkeeping without adding rules") { + fact_db facts; + store cells; + assign_store_values(cells, make_range(1, 2), {5, 6}); + facts.create_fact("cells", cells); + + sched_db sched(facts); + entitySet expected_existence = make_range(1, 2); + expected_existence += make_range(5, 6); + + // Start from a variable with no explicit existential producers. + CHECK(sched.get_existential_rules(variable("cells")).size() == 0); + + sched.set_variable_existence(variable("cells"), make_range(5, 6)); + sched.set_variable_shadow(variable("cells"), make_range(20, 20)); + sched.variable_shadow(variable("cells"), make_range(21, 22)); + + // Verify direct existence/shadow helpers update bookkeeping without + // synthesizing existential producer records. + CHECK(sched.variable_existence(variable("cells")) == expected_existence); + CHECK(sched.get_existential_rules(variable("cells")).size() == 0); + CHECK(sched.get_variable_shadow(variable("cells")) == make_range(20, 22)); +} + +TEST_CASE("print_summary reports the tracked container kind and scheduler state") { + fact_db facts; + store cells; + assign_store_values(cells, make_range(3, 4), {7, 8}); + facts.create_fact("cells", cells); + + sched_db sched(facts); + sched.synonym_variable(variable("cells"), variable("cells_shadow"), facts); + sched.variable_request(variable("cells"), make_range(4, 4)); + + std::ostringstream summary; + sched.print_summary(facts, summary); + const std::string text = summary.str(); + + // Keep this test resilient by checking for the key pieces of information the + // summary claims to expose rather than matching exact formatting. + CHECK(text.find("Summary of Existential deduction:") != std::string::npos); + CHECK(text.find("Container = STORE") != std::string::npos); + CHECK(text.find("cells") != std::string::npos); + CHECK(text.find("cells_shadow") != std::string::npos); + CHECK(text.find("request=") != std::string::npos); +} + +TEST_CASE("duplication policies and flags propagate across synonym sets") { + fact_db facts; + store cells; + assign_store_values(cells, make_range(5, 6), {8, 9}); + facts.create_fact("cells", cells); + + sched_db sched(facts); + sched.synonym_variable(variable("cells"), variable("cells_shadow"), facts); + + sched.add_policy(variable("cells"), sched_db::ALWAYS); + sched.add_policy(variable("cells"), sched_db::MODEL_BASED); + sched.set_duplicate_variable(variable("cells"), true); + + // Verify both the policy bitmask and the final duplication choice propagate + // across the synonym set. + CHECK(sched.is_policy(variable("cells"), sched_db::ALWAYS)); + CHECK(sched.is_policy(variable("cells_shadow"), sched_db::ALWAYS)); + CHECK(sched.is_policy(variable("cells"), sched_db::MODEL_BASED)); + CHECK(sched.is_policy(variable("cells_shadow"), sched_db::MODEL_BASED)); + CHECK(sched.is_duplicate_variable(variable("cells"))); + CHECK(sched.is_duplicate_variable(variable("cells_shadow"))); +} + +TEST_CASE("raw policy replacement affects synonyms but not aliases") { + fact_db facts; + store cells; + assign_store_values(cells, make_range(5, 6), {8, 9}); + facts.create_fact("cells", cells); + + sched_db sched(facts); + sched.alias_variable(variable("cells"), variable("cells_alias"), facts); + sched.synonym_variable(variable("cells"), variable("cells_shadow"), facts); + + sched.add_policy(variable("cells"), sched_db::ALWAYS); + sched.add_policy(variable("cells"), sched_db::MODEL_BASED); + sched.set_policy(variable("cells"), 1u); + sched.set_duplicate_variable(variable("cells"), true); + + // Replacing the raw mask should leave only the NEVER bit on the canonical + // scheduler record shared by synonyms. + CHECK(sched.get_policy(variable("cells")) == 1u); + CHECK(sched.get_policy(variable("cells_shadow")) == 1u); + CHECK(sched.is_policy(variable("cells"), sched_db::NEVER)); + CHECK(sched.is_policy(variable("cells_shadow"), sched_db::NEVER)); + CHECK_FALSE(sched.is_policy(variable("cells"), sched_db::ALWAYS)); + CHECK_FALSE(sched.is_policy(variable("cells_shadow"), sched_db::MODEL_BASED)); + + // Aliases keep their own sched_info, so neither the raw mask nor the final + // duplicate-variable flag propagates to them automatically. + CHECK(sched.get_policy(variable("cells_alias")) == 0u); + CHECK_FALSE(sched.is_duplicate_variable(variable("cells_alias"))); +} + +TEST_CASE("possible duplicate worklist expands synonym groups without pulling in aliases") { + fact_db facts; + store cells; + assign_store_values(cells, make_range(1, 2), {3, 4}); + facts.create_fact("cells", cells); + + sched_db sched(facts); + sched.alias_variable(variable("cells"), variable("cells_alias"), facts); + sched.synonym_variable(variable("cells"), variable("cells_shadow"), facts); + + sched.add_possible_duplicate_vars(make_var_set({variable("cells")})); + + variableSet possible = sched.get_possible_duplicate_vars(); + + // The worklist helper expands through scheduler synonyms because they share + // canonical state, but aliases remain separate scheduling entries. + CHECK(possible.inSet(variable("cells"))); + CHECK(possible.inSet(variable("cells_shadow"))); + CHECK_FALSE(possible.inSet(variable("cells_alias"))); +} + +TEST_CASE("duplication metadata and timing models are retained") { + fact_db facts; + store cells; + assign_store_values(cells, make_range(5, 6), {8, 9}); + facts.create_fact("cells", cells); + + sched_db sched(facts); + rule producer = make_rule("upstream", "cells", "model_rule"); + std::map > comp_info; + comp_info[producer] = std::make_pair(1.5, 0.25); + + sched.set_proc_able_entities(variable("cells"), producer, make_range(5, 5)); + sched.set_proc_able_entities(variable("cells"), producer, make_range(6, 6)); + sched.set_my_proc_able_entities(variable("cells"), producer, + make_range(5, 5)); + sched.set_reduce_proc_able_entities(variable("cells"), make_range(5, 6)); + sched.set_reduction_outputmap(variable("cells"), true); + sched.add_model_info(2.0, 0.5, comp_info); + sched.add_original_computation_time(variable("cells"), 4.0); + sched.add_original_computation_time(variable("cells"), 1.0); + sched.add_duplication_computation_time(variable("cells"), 2.5); + sched.add_original_communication_time(variable("cells"), 3.25); + sched.add_duplication_communication_time(variable("cells"), 1.75); + + // Verify the duplication bookkeeping helpers retain the stored entity sets + // and reduction-output-map flag. + CHECK(sched.get_proc_able_entities(variable("cells"), producer) == + make_range(5, 6)); + CHECK(sched.get_my_proc_able_entities(variable("cells"), producer) == + make_range(5, 5)); + CHECK(sched.get_reduce_proc_able_entities(variable("cells")) == + make_range(5, 6)); + CHECK(sched.is_reduction_outputmap(variable("cells"))); + + // Verify the timing accumulators preserve the totals written through the + // adder helpers. + CHECK(sched.get_precalculated_original_computation_time(variable("cells")) == + doctest::Approx(5.0)); + CHECK(sched.get_precalculated_duplication_computation_time(variable("cells")) == + doctest::Approx(2.5)); + CHECK(sched.get_precalculated_original_communication_time(variable("cells")) == + doctest::Approx(3.25)); + CHECK(sched.get_precalculated_duplication_communication_time(variable("cells")) == + doctest::Approx(1.75)); + + // Verify the installed communication and computation models can be read back. + auto comm_model = sched.get_comm_model(); + double ts = 0.0; + double tw = 0.0; + comm_model.get_parameters(ts, tw); + CHECK(ts == doctest::Approx(2.0)); + CHECK(tw == doctest::Approx(0.5)); + + auto computation_model = sched.get_comp_model(producer); + computation_model.get_parameters(ts, tw); + CHECK(ts == doctest::Approx(1.5)); + CHECK(tw == doctest::Approx(0.25)); + + // Missing rules should report the invalid sentinel model coefficients. + auto missing_model = + sched.get_comp_model(make_rule("upstream", "cells", "missing_rule")); + missing_model.get_parameters(ts, tw); + CHECK(ts < -99999.0); + CHECK(tw < -99999.0); +} + +TEST_CASE("reduction summaries replace prior values while per-rule sets accumulate") { + fact_db facts; + store cells; + assign_store_values(cells, make_range(1, 4), {10, 11, 12, 13}); + facts.create_fact("cells", cells); + + sched_db sched(facts); + rule producer = make_rule("upstream", "cells", "reduce_rule"); + + sched.set_proc_able_entities(variable("cells"), producer, make_range(1, 2)); + sched.set_proc_able_entities(variable("cells"), producer, make_range(3, 4)); + sched.set_my_proc_able_entities(variable("cells"), producer, make_range(2, 2)); + sched.set_my_proc_able_entities(variable("cells"), producer, make_range(4, 4)); + sched.set_reduce_proc_able_entities(variable("cells"), make_range(1, 2)); + sched.set_reduce_proc_able_entities(variable("cells"), make_range(4, 4)); + sched.set_reduction_outputmap(variable("cells"), true); + sched.set_reduction_outputmap(variable("cells"), false); + + // The per-rule caches accumulate across writes, while the reduction summary + // and output-map flag are simple replacement setters. + CHECK(sched.get_proc_able_entities(variable("cells"), producer) == + make_range(1, 4)); + CHECK(sched.get_my_proc_able_entities(variable("cells"), producer) == + make_range(2, 2) + make_range(4, 4)); + CHECK(sched.get_reduce_proc_able_entities(variable("cells")) == + make_range(4, 4)); + CHECK_FALSE(sched.is_reduction_outputmap(variable("cells"))); +} + +TEST_CASE("send entity caches only expose variables with mapped-output rules") { + fact_db facts; + store cells; + store plain_cells; + assign_store_values(cells, make_range(1, 2), {1, 2}); + assign_store_values(plain_cells, make_range(1, 2), {3, 4}); + facts.create_fact("cells", cells); + facts.create_fact("plain_cells", plain_cells); + + sched_db sched(facts); + // `parent_map->cells` gives the internal rule descriptor an output mapping. + rule mapped = make_rule("upstream", "parent_map->cells", "mapped_rule"); + rule plain = make_rule("upstream", "plain_cells", "plain_rule"); + + // Mark only one variable as coming from an output-mapped producer. + sched.set_existential_info(variable("cells"), mapped, make_range(1, 2)); + sched.set_existential_info(variable("plain_cells"), plain, make_range(1, 2)); + + // Seed both phase-specific send-entity caches with one mapped and one plain + // variable. + std::vector > barrier_updates; + barrier_updates.push_back( + std::make_pair(variable("cells"), make_range(10, 11))); + barrier_updates.push_back( + std::make_pair(variable("plain_cells"), make_range(20, 21))); + sched.update_send_entities(barrier_updates, sched_db::BARRIER); + + std::vector > recurse_updates; + recurse_updates.push_back( + std::make_pair(variable("cells"), make_range(30, 31))); + recurse_updates.push_back( + std::make_pair(variable("plain_cells"), make_range(40, 41))); + sched.update_send_entities(recurse_updates, sched_db::RECURSE_PRE); + + variableSet vars = + make_var_set({variable("cells"), variable("plain_cells")}); + + // Verify get_send_entities filters out variables whose producers do not map + // output entities. + std::vector > barrier = + sched.get_send_entities(vars, sched_db::BARRIER); + REQUIRE(barrier.size() == 1); + CHECK(barrier[0].first == variable("cells")); + CHECK(barrier[0].second == make_range(10, 11)); + + std::vector > recurse = + sched.get_send_entities(vars, sched_db::RECURSE_PRE); + REQUIRE(recurse.size() == 1); + CHECK(recurse[0].first == variable("cells")); + CHECK(recurse[0].second == make_range(30, 31)); + + // Rewriting the same cache entry should replace the stored entity set. + std::vector > overwrite; + overwrite.push_back(std::make_pair(variable("cells"), make_range(50, 50))); + sched.update_send_entities(overwrite, sched_db::BARRIER); + + barrier = sched.get_send_entities(vars, sched_db::BARRIER); + REQUIRE(barrier.size() == 1); + CHECK(barrier[0].second == make_range(50, 50)); +} + +TEST_CASE("communication list caches return sends before receives in processor order") { + fact_db facts; + sched_db sched(facts); + + // Build an intentionally unsorted mix of send and receive records. + comm_info recv_cells; + recv_cells.v = variable("cells"); + recv_cells.processor = 2; + recv_cells.recv_set = sequence(make_range(30, 31)); + + comm_info send_cells; + send_cells.v = variable("cells"); + send_cells.processor = 1; + send_cells.send_set = make_range(10, 11); + + comm_info recv_flux; + recv_flux.v = variable("flux"); + recv_flux.processor = 1; + recv_flux.recv_set = sequence(make_range(40, 40)); + + comm_info send_flux; + send_flux.v = variable("flux"); + send_flux.processor = 0; + send_flux.send_set = make_range(20, 20); + + std::list first_batch; + first_batch.push_back(recv_cells); + first_batch.push_back(send_cells); + sched.update_comm_info_list(first_batch, sched_db::BARRIER_CLIST); + + std::list second_batch; + second_batch.push_back(recv_flux); + second_batch.push_back(send_flux); + sched.update_comm_info_list(second_batch, sched_db::BARRIER_CLIST); + + variableSet vars = make_var_set({variable("cells"), variable("flux")}); + std::vector sorted = + to_vector(sched.get_comm_info_list(vars, facts, sched_db::BARRIER_CLIST)); + + // Verify repeated cache updates append records, and retrieval repacks the + // combined cache so sends come first in processor order, followed by receives + // in processor order. + REQUIRE(sorted.size() == 4); + CHECK(sorted[0].processor == 0); + CHECK(sorted[0].send_set == make_range(20, 20)); + CHECK(sorted[1].processor == 1); + CHECK(sorted[1].send_set == make_range(10, 11)); + CHECK(sorted[2].processor == 1); + CHECK(sorted[2].recv_set == sequence(make_range(40, 40))); + CHECK(sorted[3].processor == 2); + CHECK(sorted[3].recv_set == sequence(make_range(30, 31))); +} + +TEST_CASE("execution sequence cache keeps the first stored sequence") { + sched_db sched; + rule producer = make_rule("upstream", "cells", "exec_rule"); + + // Missing entries should read back as EMPTY. + CHECK(sched.get_exec_seq(producer) == EMPTY); + + // The first write populates the cache. + sched.update_exec_seq(producer, make_range(1, 2)); + CHECK(sched.get_exec_seq(producer) == make_range(1, 2)); + + // Later writes are ignored so the original execution sequence stays intact. + sched.update_exec_seq(producer, make_range(5, 6)); + CHECK(sched.get_exec_seq(producer) == make_range(1, 2)); +} + +int main(int argc, char **argv) { + Loci::Init(&argc, &argv); + + doctest::Context context; + context.applyCommandLine(argc, argv); + const int result = context.run(); + + Loci::Finalize(); + return result; +} diff --git a/src/System/sched_db.cc b/src/System/sched_db.cc index d610bcde..a9cd1d17 100644 --- a/src/System/sched_db.cc +++ b/src/System/sched_db.cc @@ -24,7 +24,7 @@ #include #include -using std::string ; +using std::string ; using std::map ; using std::make_pair ; using std::vector ; @@ -45,23 +45,24 @@ namespace Loci { extern int MPI_rank ; extern ofstream debugout ; - extern bool rule_has_mapping_in_output(rule r); + + extern bool rule_has_mapping_in_output(rule r) ; + sched_db::sched_db(fact_db &facts) { detected_errors = false ; init(facts) ; } - - sched_db::sched_db() {detected_errors = false ;} + + sched_db::sched_db() { detected_errors = false ; } sched_db::~sched_db() {} - void - sched_db::init(fact_db &facts) { + void sched_db::init(fact_db &facts) { variableSet tmp_all_vars = facts.get_typed_variables() ; for(size_t i = 0; i < tmp_all_vars.size(); i++) sched_infov.push_back(sched_data()) ; int sched_alloc = 0 ; for(variableSet::const_iterator vi = tmp_all_vars.begin(); vi != tmp_all_vars.end(); ++vi) { - sched_info si ; + sched_info si ; si.sched_info_ref = sched_alloc++ ; storeRepP rp = facts.get_variable(*vi) ; si.existence = rp->domain() ; @@ -80,12 +81,12 @@ namespace Loci { all_vars = tmp_all_vars ; } - + void sched_db::install_sched_info(variable v, sched_info info) { vmap_type::iterator mi = vmap.find(v) ; if(mi != vmap.end()) { cerr << "error: reinstalling fact in database for variable " - << v << endl ; + << v << endl ; abort() ; } info.synonyms += v ; @@ -93,15 +94,17 @@ namespace Loci { sched_infov[info.sched_info_ref].aliases += v ; all_vars += v ; } + /*! since free_set is never assigned, else block will never be executed, commented out here */ void sched_db::install_sched_data(variable v, sched_data data) { sched_infov.push_back(data) ; install_sched_info(v,sched_info(sched_infov.size()-1)) ; } + sched_db::sched_info &sched_db::get_sched_info(variable v) { vmap_type::iterator mi = vmap.find(remove_synonym(v)) ; - if(mi == vmap.end()) - return get_sched_info(variable("EMPTY")) ; /*! assume variable("EMPTY") will be definitely in vmap, other infinite loop*/ + if(mi == vmap.end()) + return get_sched_info(variable("EMPTY")) ; // assume variable("EMPTY") will be definitely in vmap, other infinite loop return mi->second ; } @@ -110,9 +113,8 @@ namespace Loci { get_sched_data(*vi).rotations += vars ; } } - - void sched_db::alias_variable(variable v, variable alias) { + void sched_db::alias_variable(variable v, variable alias) { if(all_vars.inSet(v)) { if(all_vars.inSet(alias)) { if(MPI_processes == 1) { @@ -122,33 +124,32 @@ namespace Loci { debugout << "alias already in fact_db!" << endl ; debugout << "error found in alias_variable("<::const_iterator mi ; sched_info &finfo = get_sched_info(v) ; @@ -314,13 +314,13 @@ namespace Loci { rules += mi->first ; return rules ; } - + void sched_db::variable_request(variable v, entitySet e) { if(v.get_info().tvar) return ; get_sched_info(v).requested += e ; } - + entitySet sched_db::get_variable_request(rule f, variable v) { sched_info &finfo = get_sched_info(v) ; map::iterator mi = finfo.exist_map.find(f) ; @@ -347,7 +347,7 @@ namespace Loci { else return fdata.imageMap[e] = get_sched_data(v).minfo->image(e) ; } - + pair sched_db::preimage(variable v, entitySet e) { sched_data &fdata = get_sched_data(v) ; if(!fdata.ismap) @@ -359,7 +359,7 @@ namespace Loci { else return fdata.preimageMap[e] = get_sched_data(v).minfo->preimage(e) ; } - + void sched_db::add_policy(variable v, duplicate_policy p) { unsigned int policy = get_policy(v); unsigned int temp = 1; @@ -399,11 +399,11 @@ namespace Loci { const map > &comp_info) { comm_model.ts = comm_ts; comm_model.tw = comm_tw; - + for(map >::const_iterator mi = comp_info.begin(); - mi != comp_info.end(); mi++) { - model tmpModel(mi->second.first, mi->second.second); - comp_model[mi->first] = tmpModel; + mi != comp_info.end(); mi++) { + model tmpModel(mi->second.first, mi->second.second) ; + comp_model[mi->first] = tmpModel ; } } @@ -413,16 +413,16 @@ namespace Loci { for(mi=vmap.begin();mi!=vmap.end();++mi) { storeRepP sp = facts.get_variable(mi->first) ; if(isMAP(sp)) - s << " Container = MAP " << endl ; + s << " Container = MAP " << endl ; else if(isSTORE(sp)) - s << " Container = STORE " << endl ; + s << " Container = STORE " << endl ; else if(isPARAMETER(sp)) - s << " Container = PARAMETER " << endl ; + s << " Container = PARAMETER " << endl ; else if(isBLACKBOX(sp)) - s << " Container = BLACKBOX " << endl; + s << " Container = BLACKBOX " << endl; else if(isCONSTRAINT(sp)) - s << " Container = CONSTRAINT " << endl ; - + s << " Container = CONSTRAINT " << endl ; + // double size = 0 ; // size = sp->pack_size(mi->second.requested) / 1000000 ; s << mi->first << " " <second.synonyms << " "<< mi->second.existence @@ -433,75 +433,82 @@ namespace Loci { std::vector > sched_db::get_send_entities(variableSet eset, send_entities_type e) { - std::vector > re; - variableSet::const_iterator vi; - std::map::const_iterator mi; - - variableSet send_vars; - for(vi = eset.begin(); vi !=eset.end(); vi++){ - variable v = variable(*vi); + std::vector > re ; + variableSet::const_iterator vi ; + std::map::const_iterator mi ; + + variableSet send_vars ; + for(vi = eset.begin(); vi !=eset.end(); vi++) { + variable v = variable(*vi) ; ruleSet rs = get_existential_rules(v) ; for(ruleSet::const_iterator rsi = rs.begin(); rsi != rs.end(); ++rsi) { if(rule_has_mapping_in_output(*rsi)) { - send_vars += v; - break; + send_vars += v ; + break ; } } } - if(e == BARRIER){ - for(vi = send_vars.begin(); vi != send_vars.end(); vi++){ - variable v = variable(*vi); - mi = barrier_send_entities_map.find(v); - if(mi != barrier_send_entities_map.end()){ - re.push_back(make_pair(v, mi->second)); - }else{ - debugout << "WARNING: for variable " << v << " barrier_send_entities_map is read before it's written" << endl; + if(e == BARRIER) { + for(vi = send_vars.begin(); vi != send_vars.end(); vi++) { + variable v = variable(*vi) ; + mi = barrier_send_entities_map.find(v) ; + if(mi != barrier_send_entities_map.end()) { + re.push_back(make_pair(v, mi->second)) ; + } else { + debugout << "WARNING: for variable " << v + << " barrier_send_entities_map is read before it's written" + << endl ; } } - }else if( e == RECURSE_PRE){ - for(vi = send_vars.begin(); vi !=send_vars.end(); vi++){ - variable v = variable(*vi); - mi = recurse_pre_send_entities_map.find(v); - if(mi != recurse_pre_send_entities_map.end()){ - re.push_back(make_pair(v, mi->second)); - }else{ - debugout << "WARNING: for variable " << v << " recurse_pre_send_entities_map is read before it's written" << endl; + } else if( e == RECURSE_PRE) { + for(vi = send_vars.begin(); vi !=send_vars.end(); vi++) { + variable v = variable(*vi) ; + mi = recurse_pre_send_entities_map.find(v) ; + if(mi != recurse_pre_send_entities_map.end()) { + re.push_back(make_pair(v, mi->second)) ; + } else { + debugout << "WARNING: for variable " << v + << " recurse_pre_send_entities_map is read before it's written" + << endl ; } } - }else{ - debugout<<"ERROR: unrecognized send_entities_type in get_send_entities() " << endl; + } else { + debugout <<"ERROR: unrecognized send_entities_type in get_send_entities() " + << endl ; } - return re; + return re ; } - - + void sched_db::update_send_entities( const std::vector >& evec, send_entities_type e){ - - if(e == BARRIER){ - for(unsigned int vi = 0; vi < evec.size(); vi++){ - variable v = evec[vi].first; - map::const_iterator mi = barrier_send_entities_map.find(v); - if(mi != barrier_send_entities_map.end()){ - debugout << "WARNING: for variable " << v << " barrier_send_entities_map is written more then once" << endl; + if(e == BARRIER) { + for(unsigned int vi = 0; vi < evec.size(); vi++) { + variable v = evec[vi].first ; + map::const_iterator mi = barrier_send_entities_map.find(v) ; + if(mi != barrier_send_entities_map.end()) { + debugout << "WARNING: for variable " << v + << " barrier_send_entities_map is written more than once" + << endl ; } - barrier_send_entities_map[v] = (evec[vi]).second; + barrier_send_entities_map[v] = (evec[vi]).second ; } - }else if(e == RECURSE_PRE){ - for(unsigned int vi = 0; vi < evec.size(); vi++){ - variable v = evec[vi].first; - map::const_iterator mi = recurse_pre_send_entities_map.find(v); - if(mi != recurse_pre_send_entities_map.end()){ - debugout << "WARNING: for variable " << v << " barrier_send_entities_map is written more then once" << endl; + } else if(e == RECURSE_PRE) { + for(unsigned int vi = 0; vi < evec.size(); vi++) { + variable v = evec[vi].first ; + map::const_iterator mi = recurse_pre_send_entities_map.find(v) ; + if(mi != recurse_pre_send_entities_map.end()) { + debugout << "WARNING: for variable " << v + << " recurse_pre_send_entities_map is written more than once" + << endl ; } - recurse_pre_send_entities_map[v] = (evec[vi]).second; + recurse_pre_send_entities_map[v] = (evec[vi]).second ; } - }else{ - debugout<<"ERROR: unrecognized send_entities_type in update_send_entities" << endl; + } else { + debugout<<"ERROR: unrecognized send_entities_type in update_send_entities" << endl ; } } - + /*!sort_comm_map() composes a list from the map so that in the list all send info is in front of recv info and the order of comm info is for(pi = procs.begin(); pi != procs.end(); pi++){ @@ -512,25 +519,23 @@ namespace Loci { std::list sort_comm_map(variableSet vset, const std::map >& clist_m, fact_db& facts, - sched_db::list_type e){ - - + sched_db::list_type e) { + vector > > send_info ; vector > > recv_info ; - // First collect information from slist HASH_MAP(int,vector) send_data ; HASH_MAP(int,vector) recv_data ; intervalSet send_procs, recv_procs ; - for(variableSet::const_iterator vi = vset.begin(); vi != vset.end(); vi++){ - variable va = variable(*vi); - list slist; - std::map >::const_iterator mi = clist_m.find(va); - if(mi != clist_m.end()){ + for(variableSet::const_iterator vi = vset.begin(); vi != vset.end(); vi++) { + variable va = variable(*vi) ; + list slist ; + std::map >::const_iterator mi = clist_m.find(va) ; + if(mi != clist_m.end()) { // debugout <<"get variable " << va <<" from list " << e << endl; - slist = mi->second; + slist = mi->second ; list::const_iterator cli ; for(cli=slist.begin();cli!=slist.end();++cli) { variable v = cli->v ; @@ -567,7 +572,7 @@ namespace Loci { list clist ; const int nrecv = recv_info.size() ; const int nsend = send_info.size() ; - + // Pack the buffer for sending for(int i=0;i sched_db::get_comm_info_list(variableSet eset, fact_db& facts, list_type e) const{ - list re; - if(e==BARRIER_CLIST){ - re = sort_comm_map(eset, barrier_clist_map, facts, e); - }else if(e==BARRIER_PLIST){ - re = sort_comm_map(eset, barrier_plist_map, facts, e); - }else if(e==REDUCE_RLIST){ - re = sort_comm_map(eset, reduce_rlist_map, facts, e); - }else if(e==REDUCE_CLIST){ - re = sort_comm_map(eset, reduce_clist_map, facts, e); - }else if(e==LOOP_ADVANCE_LIST){ - re = sort_comm_map(eset, loop_advance_list_map, facts, e); - }else if(e==RECURSE_CLIST){ - re = sort_comm_map(eset, recurse_clist_map, facts, e); - }else if(e==RECURSE_PRE_CLIST){ - re = sort_comm_map(eset, recurse_pre_clist_map, facts, e); - }else if(e==RECURSE_POST_CLIST){ - re = sort_comm_map(eset, recurse_post_clist_map, facts, e); - }else if(e==RECURSE_PRE_PLIST){ - re = sort_comm_map(eset, recurse_pre_plist_map, facts, e); - }else{ - debugout << "ERROR: unrecognized list_type " << e << " in get_comm_info_list" << endl; + list re ; + if(e==BARRIER_CLIST) { + re = sort_comm_map(eset, barrier_clist_map, facts, e) ; + } else if(e==BARRIER_PLIST) { + re = sort_comm_map(eset, barrier_plist_map, facts, e) ; + } else if(e==REDUCE_RLIST) { + re = sort_comm_map(eset, reduce_rlist_map, facts, e) ; + } else if(e==REDUCE_CLIST) { + re = sort_comm_map(eset, reduce_clist_map, facts, e) ; + } else if(e==LOOP_ADVANCE_LIST) { + re = sort_comm_map(eset, loop_advance_list_map, facts, e) ; + } else if(e==RECURSE_CLIST) { + re = sort_comm_map(eset, recurse_clist_map, facts, e) ; + } else if(e==RECURSE_PRE_CLIST) { + re = sort_comm_map(eset, recurse_pre_clist_map, facts, e) ; + } else if(e==RECURSE_POST_CLIST) { + re = sort_comm_map(eset, recurse_post_clist_map, facts, e) ; + } else if(e==RECURSE_PRE_PLIST) { + re = sort_comm_map(eset, recurse_pre_plist_map, facts, e) ; + } else { + debugout << "ERROR: unrecognized list_type " << e << " in get_comm_info_list" + << endl ; } - return re; + return re ; } - void sched_db::update_comm_info_list(const std::list& elist, list_type e){ + + void sched_db::update_comm_info_list(const std::list& elist, list_type e) { list::const_iterator cli ; - // map >::iterator mi; + // map >::iterator mi; for(cli=elist.begin();cli!=elist.end();++cli) { variable v = cli->v ; - if(e==BARRIER_CLIST){ - barrier_clist_map[v].push_back( *cli); - }else if(e==BARRIER_PLIST){ - barrier_plist_map[v].push_back( *cli); - }else if(e==REDUCE_RLIST){ - reduce_rlist_map[v].push_back( *cli); - }else if(e==REDUCE_CLIST){ - reduce_clist_map[v].push_back( *cli); - }else if(e==LOOP_ADVANCE_LIST){ - loop_advance_list_map[v].push_back( *cli); - }else if(e==RECURSE_CLIST){ - recurse_clist_map[v].push_back( *cli); - }else if(e==RECURSE_PRE_CLIST){ - recurse_pre_clist_map[v].push_back( *cli); - }else if(e==RECURSE_POST_CLIST){ - recurse_post_clist_map[v].push_back( *cli); - }else if(e==RECURSE_PRE_PLIST){ - recurse_pre_plist_map[v].push_back( *cli); - } else{ - debugout << "ERROR: unrecognized list_type " << e << " in update_comm_info_list" << endl; + if(e==BARRIER_CLIST) { + barrier_clist_map[v].push_back( *cli) ; + } else if(e==BARRIER_PLIST) { + barrier_plist_map[v].push_back( *cli) ; + } else if(e==REDUCE_RLIST) { + reduce_rlist_map[v].push_back( *cli) ; + } else if(e==REDUCE_CLIST) { + reduce_clist_map[v].push_back( *cli) ; + } else if(e==LOOP_ADVANCE_LIST) { + loop_advance_list_map[v].push_back( *cli) ; + } else if(e==RECURSE_CLIST) { + recurse_clist_map[v].push_back( *cli) ; + } else if(e==RECURSE_PRE_CLIST) { + recurse_pre_clist_map[v].push_back( *cli) ; + } else if(e==RECURSE_POST_CLIST) { + recurse_post_clist_map[v].push_back( *cli) ; + } else if(e==RECURSE_PRE_PLIST) { + recurse_pre_plist_map[v].push_back( *cli) ; + } else { + debugout << "ERROR: unrecognized list_type " << e + << " in update_comm_info_list" << endl ; } - - } + + } } } - + diff --git a/src/include/sched_db.h b/src/include/sched_db.h index 87563f95..660ed3ad 100644 --- a/src/include/sched_db.h +++ b/src/include/sched_db.h @@ -32,190 +32,476 @@ #include namespace Loci { + + /// @brief Describes one variable transfer between this processor and one peer. + /// + /// Each record names the variable being communicated and the peer involved. + /// `send_set` holds the local entities to pack and send to `processor`. + /// `recv_set` holds the local entities to fill from `processor`, in the order + /// required by unpacking. + /// + /// These records are produced during scheduler analysis, cached by `sched_db`, + /// and later assembled into communication blocks for barrier, reduction, loop, + /// and recursion schedules. struct comm_info { variable v ; + + /// @brief Peer processor for this transfer record. + /// For `send_set`, this is the destination rank. + /// For `recv_set`, this is the source rank. int processor ; + + /// @brief Local entities to pack and send to `processor`. entitySet send_set ; + + /// @brief Local entities to fill from `processor`, in unpack order. sequence recv_set ; } ; + + + /// @brief Send-side variable transfer grouped under a peer processor. + /// + /// The surrounding communication map or vector stores the peer rank; this + /// record only names the variable and the local entities to pack for that peer. struct send_var_info { variable v ; + + /// @brief Local entities to pack for the associated peer. entitySet set ; send_var_info(variable iv, const entitySet &iset) : v(iv),set(iset) {} } ; - + + + /// @brief Receive-side variable transfer grouped under a peer processor. + /// + /// The surrounding communication map or vector stores the peer rank; this + /// record only names the variable and the ordered local entities to fill from + /// that peer. struct recv_var_info { variable v ; + + /// @brief Local entities to unpack into, in message order. sequence seq ; recv_var_info(variable iv, const sequence &iseq) : v(iv),seq(iseq) {} } ; + + + /// @brief Compiler-side database of derived scheduling state. + /// + /// `sched_db` is initialized from a `fact_db` and then updated by scheduler + /// passes as they analyze rule existence, propagate variable requests, and + /// prepare distributed communication. + /// + /// It records per-variable scheduling metadata such as known existence, + /// requested entities, rule-by-rule existential contributions, alias and + /// synonym relationships, cached map-query results, communication records for + /// distributed scheduling phases, and duplication-analysis data. + /// + /// Other scheduler components use this database to answer questions about what + /// can be computed, what must be requested or communicated, and how a rule or + /// variable should be scheduled. + /// + /// `sched_db` owns scheduling metadata only; the underlying fact stores and + /// typed variables remain owned by `fact_db`. class sched_db { - + private: + + /// @brief Shared metadata for variables that point at the same + /// scheduling-data slot. + /// + /// Alias variables share one `sched_data` record so they can reuse map + /// classification, cached map queries, and loop-rotation bookkeeping. struct sched_data { - variableSet aliases,rotations,antialiases ; + /// @brief Variables that share this `sched_data` slot. + variableSet aliases ; + + /// @brief Variables linked by loop-rotation bookkeeping. + variableSet rotations ; + + /// @brief Original-side names recorded when aliases are introduced into + /// this slot. + variableSet antialiases ; + + /// @brief True when the backing store representation is a `MAP` or + /// `GPUMAP`. bool ismap ; + + /// @brief Cached `MapRep` used by `image()` and `preimage()` when + /// `ismap` is true. MapRepP minfo ; + + /// @brief Memoized `MapRep::image()` results keyed by the queried + /// domain. std::map imageMap ; - std::map > preimageMap - ; - sched_data() {} - sched_data(variable v, storeRepP &st) - { aliases += v ; - ismap = (st->RepType() == Loci::MAP || st->RepType() == Loci::GPUMAP) ; - if(ismap) minfo = MapRepP(st->getRep()) ; } + + /// @brief Memoized `MapRep::preimage()` results keyed by the queried + /// codomain. + /// The stored pair preserves both preimage variants returned by the map + /// representation. + std::map > preimageMap ; + + sched_data() {} + sched_data(variable v, storeRepP &st) { + aliases += v ; + ismap = (st->RepType() == Loci::MAP || st->RepType() == Loci::GPUMAP) ; + if(ismap) minfo = MapRepP(st->getRep()) ; + } } ; - + + + /// @brief One rule's contribution to the known existence of a variable. struct existential_info { + /// @brief Variable instance associated with this rule contribution. + /// Priority decoration on this name is used when overlapping producers + /// are compared. variable v ; + + /// @brief Entities that remain attributed to the rule after any + /// priority-based conflict handling. entitySet exists ; - existential_info() {} + + existential_info() {} existential_info(const variable &vin,const entitySet &e) : v(vin), exists(e) {} } ; - - struct sched_info ; - friend struct sched_info ; + /// @brief Canonical per-variable scheduler state. + /// + /// Each variable handled directly by `vmap` owns one of these records. + /// Shared alias/map metadata lives in `sched_infov`; this struct stores the + /// existential, request, reduction, and duplication state attached to the + /// variable itself. struct sched_info { + /// @brief Index into `sched_infov` for shared alias, rotation, and map + /// metadata. int sched_info_ref ; - /*! fact_installed is initialized in init(), and never used except for variable_is_fact_at(), comment out*/ + + // Legacy field kept commented out because it was initialized in `init()` + // but not otherwise used by the current scheduler implementation. // entitySet fact_installed ; + + /// @brief Names that resolve to this `sched_info` entry through synonym + /// lookup. variableSet synonyms ; - + + /// @brief Rule-keyed existential contributions merged into `existence`. std::map exist_map ; + + /// @brief Union of directly known and rule-derived entities for the + /// variable. entitySet existence ; + + /// @brief Union of entities requested by downstream scheduling passes. entitySet requested ; - entitySet shadow ; // Used by distributed memory apply rules - - //Apply rules may have target variables both on input and output side. - //Sometimes apply rules may need to access entities of target variable - //which are not requested. In that case, UNIT rule also allocates these - //entities in addition to entities being requested. + + /// @brief Distributed shadow-region entities for output-mapped apply or + /// reduction scheduling. + /// Later passes use this set to exchange partial results with owner + /// processors. + entitySet shadow ; + + /// @brief Additional UNIT-rule requests created when mapped apply logic + /// needs target entities beyond the normal request set. entitySet extra_unit_request; - //////////////////////////Duplication Related:////////////// - //Defines maximum which target variable entities a rule can compute + /// @brief Maps each rule to the entities it can compute under the broader + /// duplication-analysis context. std::map proc_able_map; - //Defines which target variable entities a rule can compute using context - //that is subset of my_entities + /// @brief Maps each rule to the entities it can compute using only + /// processor-local context. std::map my_proc_able_map; - unsigned int policy; //Each bit defines a policy - bool duplicate_variable; //Defines if a variable is duplicated - - //Applies only to reduce variable: considering all the rules of a variable, - //it defines which entities can definitely computed on a processor - entitySet reduce_proc_able_entities; + /// @brief Bitmask of enabled `duplicate_policy` selectors. + unsigned int policy; + + /// @brief True when duplication has been selected for this variable. + bool duplicate_variable; + + /// @brief Reduction-specific computable set used when deciding whether + /// owner communication can be avoided. + entitySet reduce_proc_able_entities; - bool reduction_outputmap; //If any of the apply or unit rule has mapping in output + /// @brief True when any producer rule for the variable maps its output + /// during reduction handling. + bool reduction_outputmap; + /// @brief Accumulated model-based computation-time estimates for the + /// original and duplicated schedules. double original_computation_time, duplication_computation_time; + + /// @brief Accumulated model-based communication-time estimates for the + /// original and duplicated schedules. double original_communication_time, duplication_communication_time; - ////////////////////////////////////////////////////////////// sched_info(int ref = -1) { - sched_info_ref = ref ; - policy = 0; - duplicate_variable = false; - reduce_proc_able_entities = ~EMPTY; - reduction_outputmap = false; - - original_computation_time = 0; - duplication_computation_time = 0; - original_communication_time = 0; - duplication_communication_time = 0; + sched_info_ref = ref ; + policy = 0; + duplicate_variable = false; + reduce_proc_able_entities = ~EMPTY; + reduction_outputmap = false; + + original_computation_time = 0; + duplication_computation_time = 0; + original_communication_time = 0; + duplication_communication_time = 0; } } ; - - - /*! - data structure used for duplication - */ + /// @brief Stores the two coefficients used by the scheduler's timing + /// models for duplication decisions. + /// + /// `ts` is the fixed-cost term and `tw` is the variable-cost term. + /// Communication costs are estimated as + /// `ts * processor_count + tw * packed_size`, while computation costs are + /// estimated as `ts + tw * context_size`. + /// + /// The units come from the external model data loaded by the scheduler, so + /// these values are treated as generic timing coefficients rather than + /// hard-coded seconds. struct model { - double ts, tw; - static bool is_valid_val(double val) { return (val > -99999); } + /// @brief Fixed-cost term in the timing model. + double ts ; + + /// @brief Variable-cost term in the timing model. + double tw ; + + /// @brief Returns true when `val` is a populated coefficient. + /// @param[in] val Coefficient value to validate. + /// @return True when `val` is not the invalid sentinel. + static bool is_valid_val(double val) { return (val > -99999) ; } + + /// @brief Copies the stored coefficients into output references. + /// @param[out] t0 Receives the fixed-cost term. + /// @param[out] tc Receives the variable-cost term. + void get_parameters(double &t0, double &tc) { t0 = ts; tc = tw ; } + + /// @brief Replaces the stored coefficients. + /// @param[in] t0 Fixed-cost term to store. + /// @param[in] tc Variable-cost term to store. + void set_parameters(double t0, double tc) { ts = t0; tw = tc ; } + model(double t0, double tc) { - ts = t0; - tw = tc; + ts = t0 ; + tw = tc ; } - model() { ts = -100000; tw = -100000;} - void get_parameters(double &t0, double &tc) { t0 = ts; tc = tw; } - void set_parameters(double t0, double tc) { ts = t0; tw = tc; } - }; + + model() { ts = -100000; tw = -100000 ; } + } ; + + /// @brief Variables already queued for later duplicate-work analysis. variableSet possible_duplicate_vars; + + /// @brief Per-rule computation timing models used by model-based + /// duplication decisions. std::map comp_model; + + /// @brief Communication timing model used by model-based duplication + /// decisions. model comm_model; - + /// @brief Registers a variable in the scheduler's canonical lookup tables. void register_variable(variable v) ; - + + /// @brief Every variable name currently known to the scheduler, including + /// aliases and synonyms added after initialization. variableSet all_vars ; + + /// @brief Maps non-canonical synonym names to the canonical variables + /// stored in `vmap`. std::map synonyms ; + + /// @brief Lookup-table type for variables that carry independent scheduler + /// state. typedef std::map vmap_type ; - /*! - vmap: the sched_info for each variable. - */ + /// @brief Per-variable scheduler state addressed after synonym resolution. vmap_type vmap ; + + /// @brief Shared `sched_data` table referenced by `sched_info_ref`. + /// Alias variables point into the same entry in this table. std::vector sched_infov ; - /*! free_set is never initialized or modified, seems no purpose to exists. comment out */ - // intervalSet free_set ; + // Legacy field kept commented out because it is neither initialized nor + // queried in the current scheduler implementation. + // intervalSet free_set ; + + /// @brief Sticky flag raised when scheduler analysis reports a conflict or + /// other invalid state. bool detected_errors ; - /*! the follow maps store variable-based comm_info and rule-based sequences for compilers - these data structures are modified and used by existential_analysis - */ + + /// @brief Phase-specific communication and execution caches populated + /// during request analysis. + /// Send-entity caches hold mapped-output send domains. Communication-list + /// caches hold variable-keyed `comm_info` records for barrier, reduction, + /// loop-advance, and recursion phases. `exec_seq_map` stores rule-keyed + /// execution domains. + + /// @brief Barrier-phase send sets used to build mapped-output + /// precommunication. std::map barrier_send_entities_map ; + + /// @brief Recursive pre-phase send sets used to build mapped-output + /// precommunication. std::map recurse_pre_send_entities_map ; + + /// @brief Barrier-phase clone/fill communication records derived from + /// variable requests. std::map > barrier_clist_map; + + /// @brief Barrier-phase precommunication records built for mapped outputs. std::map > barrier_plist_map; + + /// @brief Reduction-phase communication records that return shadow values + /// for owner-side joining. std::map > reduce_rlist_map; + + /// @brief Reduction-phase clone/fill communication records derived from + /// variable requests. std::map > reduce_clist_map; + + /// @brief Loop-advance communication records cached before time-level names + /// are restored. std::map > loop_advance_list_map; + + /// @brief General recursion communication cache keyed by variable. std::map > recurse_clist_map; + + /// @brief Recursive pre-phase request communication records. std::map > recurse_pre_clist_map; + + /// @brief Recursive post-phase communication records issued after recursive + /// iterations. std::map > recurse_post_clist_map; + + /// @brief Recursive pre-phase precommunication records for mapped outputs. std::map > recurse_pre_plist_map; + + /// @brief Rule-keyed execution domains cached during request processing. + /// The first write wins; later writes only warn. std::map exec_seq_map; - + public: + /// @brief Resolves a scheduler synonym to its canonical variable name. + /// + /// Synonyms share one canonical `sched_info` entry. Alias variables are not + /// resolved by this helper; aliases keep separate names and share + /// `sched_data`. + /// @param[in] v Variable name to canonicalize. + /// @return The canonical variable for `v`, or `v` if no synonym is + /// registered. variable remove_synonym(variable v) const { std::map::const_iterator mi ; - if((mi=synonyms.find(v)) != synonyms.end()) - return mi->second ; + if((mi=synonyms.find(v)) != synonyms.end()) + return mi->second ; return v ; } - public: - enum duplicate_policy{NEVER, ALWAYS, MODEL_BASED}; + /// @brief Selects a duplication-policy bit for a variable. + /// + /// `add_policy()` and `is_policy()` translate these selectors into bits in + /// `sched_info::policy`: `NEVER` -> 1, `ALWAYS` -> 2, `MODEL_BASED` -> 4. + enum duplicate_policy{NEVER, ALWAYS, MODEL_BASED}; + + /// @brief Selects one of the cached `comm_info` lists by scheduler phase. + /// + /// The chosen value determines which communication cache + /// `get_comm_info_list()` and `update_comm_info_list()` read or update for + /// barrier, reduction, loop-advance, or recursive scheduling. enum list_type{BARRIER_CLIST, BARRIER_PLIST, REDUCE_RLIST, REDUCE_CLIST, LOOP_ADVANCE_LIST,RECURSE_CLIST, RECURSE_PRE_CLIST, RECURSE_POST_CLIST, RECURSE_PRE_PLIST}; + + /// @brief Selects which cached mapped-output send-entity set to query or + /// update. + /// + /// These caches are populated during existence analysis and later used + /// while building precommunication for barrier or recursive pre phases. enum send_entities_type{BARRIER, RECURSE_PRE}; - + + /// @brief Creates an empty scheduler database with no registered + /// variables. + /// + /// Call `init()` before using variable-dependent scheduler state. sched_db() ; + + /// @brief Destroys the scheduler metadata owned by this database. ~sched_db() ; + + /// @brief Creates a scheduler database initialized from the typed facts in + /// `facts`. + /// + /// This convenience constructor clears the error flag and then seeds the + /// scheduler tables from the variables currently typed in `facts`. + /// @param[in] facts Fact database whose typed variables seed the initial + /// scheduler state. sched_db(fact_db &facts) ; - // this method is used to initialize a sched_db - // from a fact_db (it is needed in case of when - // the sched_db is not directly created from a fact_db) + + /// @brief Seeds scheduler state from the typed variables in `facts`. + /// + /// For each typed variable, this records its current store domain as known + /// existence, creates its canonical `sched_info`, initializes its shared + /// `sched_data`, and records the variable in `all_vars`. + /// + /// This routine is intended for a fresh `sched_db` or one that has been + /// explicitly prepared for reinitialization. + /// @param[in] facts Fact database whose typed variables seed scheduler + /// state. void init(fact_db &facts) ; + /// @brief Returns whether scheduler analysis has reported a sticky error. + /// + /// Top-level schedule builders check this flag after schedule construction + /// and abort if any MPI rank reports a failure. bool errors_found() {return detected_errors ;} + + /// @brief Clears the sticky scheduler error flag. + /// + /// This resets only the recorded flag; it does not repair the underlying + /// state that originally triggered the error. void clear_errors() {detected_errors = false ;} + + /// @brief Marks scheduler analysis as failed. + /// + /// Use this when a scheduler pass detects an invalid or unsupported state + /// but continues far enough for the caller to report diagnostics. void set_error() { detected_errors = true ; } - + + /// @brief Adds shared scheduling data for a new variable and registers `v`. + /// + /// The supplied `sched_data` is appended to `sched_infov`, and `v` then + /// receives a `sched_info` record pointing at that new shared-data slot. + /// @param[in] v Variable to register. + /// @param[in] data Shared scheduling data to append for `v`. void install_sched_data(variable v, sched_data data) ; + + /// @brief Installs scheduler state for `v` in the variable registry. + /// + /// Use this when `v` should reuse an existing `sched_data` slot described + /// by `info.sched_info_ref`, such as when registering an alias. + /// @param[in] v Variable to register. + /// @param[in] info Scheduler state to install for `v`. + /// @note Aborts if `v` is already registered. void install_sched_info(variable v, sched_info info) ; - - variableSet get_synonyms(variable v) const - { return get_sched_info(v).synonyms ; } - // this version tries to get the synonyms for - // the passed in variable, if no info, then returns EMPTY + /// @brief Returns the synonym set recorded for `v`'s canonical scheduler + /// entry. + /// + /// This covers names linked through `synonym_variable()`. It does not + /// include alias or rotation relationships. + /// @param[in] v Variable whose synonym set is requested. + /// @note `v` must already be known to the scheduler; use + /// `try_get_synonyms()` for unknown-safe lookup. + /// @return The synonym set for `v`'s canonical scheduler entry. + variableSet get_synonyms(variable v) const { + return get_sched_info(v).synonyms ; + } + + /// @brief Returns the synonym set recorded for `v`'s canonical scheduler + /// entry. + /// @param[in] v Variable whose synonym set is requested. + /// @return `EMPTY` when `v` is not known to the scheduler. variableSet try_get_synonyms(variable v) const { vmap_type::const_iterator mi = vmap.find(remove_synonym(v)) ; if(mi == vmap.end()) @@ -224,10 +510,22 @@ namespace Loci { return (mi->second).synonyms ; } - variableSet get_aliases(variable v) const - { return get_sched_data(v).aliases ; } + /// @brief Returns the alias group that shares `sched_data` with `v`. + /// + /// Alias relationships come from `alias_variable()` and are distinct from + /// synonym resolution. + /// @param[in] v Variable whose alias group is requested. + /// @note `v` must already be known to the scheduler; use + /// `try_get_aliases()` for unknown-safe lookup. + /// @return The alias group that shares `sched_data` with `v`. + variableSet get_aliases(variable v) const { + return get_sched_data(v).aliases ; + } - variableSet try_get_aliases(variable v) const { + /// @brief Returns the alias group that shares `sched_data` with `v`. + /// @param[in] v Variable whose alias group is requested. + /// @return `EMPTY` when `v` is not known to the scheduler. + variableSet try_get_aliases(variable v) const { vmap_type::const_iterator mi = vmap.find(remove_synonym(v)) ; if(mi == vmap.end()) return variableSet(EMPTY) ; @@ -235,9 +533,23 @@ namespace Loci { return sched_infov[(mi->second).sched_info_ref].aliases ; } - variableSet get_antialiases(variable v) const - { return get_sched_data(v).antialiases ; } + /// @brief Returns the anti-alias names recorded in `v`'s shared alias + /// metadata. + /// + /// These names are added when aliases are introduced and are stored in the + /// shared `sched_data` slot rather than in per-variable synonym state. + /// @param[in] v Variable whose anti-alias names are requested. + /// @note `v` must already be known to the scheduler; use + /// `try_get_antialiases()` for unknown-safe lookup. + /// @return The anti-alias names recorded for `v`. + variableSet get_antialiases(variable v) const { + return get_sched_data(v).antialiases ; + } + /// @brief Returns the anti-alias names recorded in `v`'s shared alias + /// metadata. + /// @param[in] v Variable whose anti-alias names are requested. + /// @return `EMPTY` when `v` is not known to the scheduler. variableSet try_get_antialiases(variable v) const { vmap_type::const_iterator mi = vmap.find(remove_synonym(v)) ; if(mi == vmap.end()) @@ -246,9 +558,21 @@ namespace Loci { return sched_infov[(mi->second).sched_info_ref].antialiases ; } - variableSet get_rotations(variable v) const - { return get_sched_data(v).rotations ; } + /// @brief Returns the loop-rotation group recorded for `v`. + /// + /// Rotation groups are registered by `set_variable_rotations()` so later + /// scheduling passes can treat those variables together. + /// @param[in] v Variable whose rotation group is requested. + /// @note `v` must already be known to the scheduler; use + /// `try_get_rotations()` for unknown-safe lookup. + /// @return The loop-rotation group recorded for `v`. + variableSet get_rotations(variable v) const { + return get_sched_data(v).rotations ; + } + /// @brief Returns the loop-rotation group recorded for `v`. + /// @param[in] v Variable whose rotation group is requested. + /// @return `EMPTY` when `v` is not known to the scheduler. variableSet try_get_rotations(variable v) const { vmap_type::const_iterator mi = vmap.find(remove_synonym(v)) ; if(mi == vmap.end()) @@ -256,54 +580,151 @@ namespace Loci { else return sched_infov[(mi->second).sched_info_ref].rotations ; } - + + /// @brief Ensures that `v` is typed in both the scheduler and `facts`. + /// + /// This creates an intensional fact for `v` in `facts`. If the scheduler + /// is not already tracking `v`, it also installs a fresh `sched_data` + /// record seeded from `st`. Existing scheduler state for `v` is left in + /// place. + /// @param[in] v Variable being typed. + /// @param[in] st Store representation to associate with `v`. + /// @param[in,out] facts Fact database updated with the corresponding + /// intensional fact. void set_variable_type(variable v, storeRepP st, fact_db &facts) ; - void set_variable_type(std::string vname,const storeRepP st, fact_db &facts) - { set_variable_type(variable(vname),st, facts) ; } - - void set_variable_type(variable v, store_instance &si, fact_db &facts) - { set_variable_type(v,si.Rep(), facts) ; } - void set_variable_type(std::string vname, store_instance &si, fact_db &facts) - { set_variable_type(variable(vname),si, facts) ; } - + void set_variable_type(std::string vname,const storeRepP st, + fact_db &facts) { + set_variable_type(variable(vname),st, facts) ; + } + + void set_variable_type(variable v, store_instance &si, fact_db &facts) { + set_variable_type(v,si.Rep(), facts) ; + } + void set_variable_type(std::string vname, store_instance &si, + fact_db &facts) { + set_variable_type(variable(vname),si, facts) ; + } + + /// @brief Alternate overload set for callers that do not pass a + /// `fact_db`. + /// @param[in] v Variable being typed. + /// @param[in] st Store representation to associate with `v`. void set_variable_type(variable v, storeRepP st) ; - void set_variable_type(std::string vname,const storeRepP st) - { set_variable_type(variable(vname),st) ; } - - void set_variable_type(variable v, store_instance &si) - { set_variable_type(v,si.Rep()) ; } - void set_variable_type(std::string vname, store_instance &si) - { set_variable_type(variable(vname),si) ; } - - /*! variable_is_fact_at() functions are never used, comment out*/ + void set_variable_type(std::string vname,const storeRepP st) { + set_variable_type(variable(vname),st) ; + } + + void set_variable_type(variable v, store_instance &si) { + set_variable_type(v,si.Rep()) ; + } + void set_variable_type(std::string vname, store_instance &si) { + set_variable_type(variable(vname),si) ; + } + + // Legacy `variable_is_fact_at()` overloads remain commented out because the + // scheduler no longer uses them. // void variable_is_fact_at(variable v,entitySet s, fact_db &facts) ; // void variable_is_fact_at(std::string vname, const entitySet s, fact_db &facts) // { variable_is_fact_at(variable(vname),s, facts) ; } - + // void variable_is_fact_at(variable v,entitySet s) ; // void variable_is_fact_at(std::string vname, const entitySet s) // { variable_is_fact_at(variable(vname),s) ; } - + + /// @brief Stores `rot` as the complete rotation group for every variable in + /// the set. + /// + /// Each member must already be known to the scheduler. The group is written + /// into shared scheduling data so later loop-scheduling passes can recover + /// the same rotation set from any member. Aliases that share the same + /// `sched_data` record observe the same rotation metadata. + /// @param[in] rot Complete rotation group to record for each member. void set_variable_rotations(variableSet rot) ; - + + /// @brief Registers `alias` as a new variable that shares `v`'s + /// `sched_data`, and mirrors the relationship into `facts`. + /// + /// Aliases share type- and map-level scheduling metadata through the same + /// `sched_data` slot, but each alias keeps its own `sched_info` fields such + /// as existence, requests, and rule ownership. + /// @param[in] v Existing scheduler variable that provides the shared + /// `sched_data`. + /// @param[in] alias New alias name to register. + /// @param[in,out] facts Fact database updated to mirror the alias + /// relationship. + /// @note If exactly one of `v` and `alias` already exists, the existing + /// variable is used as the source regardless of argument order. void alias_variable(variable v, variable alias, fact_db &facts) ; - void alias_variable(std::string vname, std::string alias, fact_db &facts) - { alias_variable(variable(vname),variable(alias), facts) ; } - + void alias_variable(std::string vname, std::string alias, fact_db &facts) { + alias_variable(variable(vname),variable(alias), facts) ; + } + + /// @brief Registers `alias` as a new variable that shares `v`'s + /// `sched_data`. + /// + /// This is the scheduler-only form of `alias_variable(..., fact_db&)`. + /// @param[in] v Existing scheduler variable that provides the shared + /// `sched_data`. + /// @param[in] alias New alias name to register. void alias_variable(variable v, variable alias) ; - void alias_variable(std::string vname, std::string alias) - { alias_variable(variable(vname),variable(alias)) ; } - + void alias_variable(std::string vname, std::string alias) { + alias_variable(variable(vname),variable(alias)) ; + } + + /// @brief Registers `synonym` as an alternate name for canonical variable + /// `v`, and mirrors the relationship into `facts`. + /// + /// After registration, lookups for `synonym` resolve through + /// `remove_synonym()` to `v`, so the synonym shares the same `sched_info` + /// state instead of getting a separate scheduler record. + /// @param[in] v Canonical variable name. + /// @param[in] synonym Alternate name to resolve through `v`. + /// @param[in,out] facts Fact database updated to mirror the synonym + /// relationship. void synonym_variable(variable v, variable synonym, fact_db &facts) ; - void synonym_variable(std::string vname, std::string synonym, fact_db &facts) - { synonym_variable(variable(vname),variable(synonym), facts) ; } - + void synonym_variable(std::string vname, std::string synonym, + fact_db &facts) { + synonym_variable(variable(vname),variable(synonym), facts) ; + } + + /// @brief Registers `synonym` as an alternate name for canonical variable + /// `v`. + /// + /// This is the scheduler-only form of `synonym_variable(..., fact_db&)`. + /// @param[in] v Canonical variable name. + /// @param[in] synonym Alternate name to resolve through `v`. void synonym_variable(variable v, variable synonym) ; - void synonym_variable(std::string vname, std::string synonym) - { synonym_variable(variable(vname),variable(synonym)) ; } - + void synonym_variable(std::string vname, std::string synonym) { + synonym_variable(variable(vname),variable(synonym)) ; + } + + /// @brief Records or extends the subset of `v` produced by rule `f`. + /// @param[in] v Variable instance, including any priority decoration. + /// @param[in] f Rule that produces the entities in `x`. + /// @param[in] x Produced entities to merge into `f`'s existential record. + /// + /// Repeated calls for the same rule accumulate into one entry. If the new + /// entities overlap entities already owned by a different rule, variable + /// priority annotations are used to resolve ownership when possible. + /// Unresolved overlaps are reported as conflicts and mark the scheduler as + /// having detected errors. Calls with `EMPTY` still register `f` in the + /// existential map. void set_existential_info(variable v,rule f,entitySet x) ; + + /// @brief Returns the rules currently present in `v`'s existential map. + /// + /// This reports registered rule entries, even if a rule's current entity + /// contribution is empty. + /// @param[in] v Variable whose existential producers are requested. + /// @return The rules currently present in `v`'s existential map. ruleSet get_existential_rules(variable v) ; + + /// @brief Returns `v`'s canonical `sched_info` after synonym resolution. + /// @param[in] v Variable whose scheduler state is requested. + /// @note Alias variables are not collapsed here; aliases keep separate + /// `sched_info` records. + /// @note Aborts if the canonical variable is not known to the scheduler. + /// @return The canonical read-only scheduler record for `v`. const sched_info & get_sched_info(variable v) const { vmap_type::const_iterator mi = vmap.find(remove_synonym(v)) ; if(mi == vmap.end()) { @@ -312,222 +733,604 @@ namespace Loci { } return mi->second ; } - const sched_data & get_sched_data(variable v) const - { return sched_infov[get_sched_info(v).sched_info_ref] ; } - + + /// @brief Returns the shared `sched_data` record referenced by `v`'s + /// canonical scheduler entry. + /// + /// Variables in the same alias group share this record. + /// @param[in] v Variable whose shared scheduling data is requested. + /// @return The shared read-only scheduling data for `v`. + const sched_data & get_sched_data(variable v) const { + return sched_infov[get_sched_info(v).sched_info_ref] ; + } + + /// @brief Returns `v`'s canonical `sched_info` after synonym resolution. + /// @param[in] v Variable whose scheduler state is requested. + /// @note Alias variables are not collapsed here; aliases keep separate + /// `sched_info` records. + /// @note Unknown variables are routed through the scheduler's `EMPTY` + /// placeholder record instead of aborting. + /// @return The canonical mutable scheduler record for `v`. sched_info & get_sched_info(variable v); - sched_data & get_sched_data(variable v) - { return sched_infov[get_sched_info(v).sched_info_ref] ; } + /// @brief Returns the shared `sched_data` record referenced by `v`'s + /// canonical scheduler entry. + /// + /// Variables in the same alias group share this record, so mutations affect + /// the whole alias group. + /// @param[in] v Variable whose shared scheduling data is requested. + /// @return The shared mutable scheduling data for `v`. + sched_data & get_sched_data(variable v) { + return sched_infov[get_sched_info(v).sched_info_ref] ; + } + + /// @brief Returns true when `v`'s shared scheduling data is backed by a map + /// representation. + /// + /// Alias variables report the same result because they share `sched_data`. + /// @param[in] v Variable to inspect. + /// @return True when `v` is backed by a map representation. bool is_a_Map(variable v) { return get_sched_data(v).ismap ; } + + /// @brief Returns the entities currently attributed to rule `f` for `v`. + /// + /// This queries `v`'s per-variable existential map, so synonyms resolve to + /// the same record while aliases keep separate rule-attribution state. + /// @param[in] v Variable whose existential map is queried. + /// @param[in] f Rule whose attributed entities are requested. + /// @return The stored existential subset for `f`, or `EMPTY` when `f` has + /// no entry for `v`. entitySet get_existential_info(variable v, rule f) { sched_info &finfo = get_sched_info(v) ; std::map::const_iterator mi ; mi = finfo.exist_map.find(f) ; if(mi!=finfo.exist_map.end()) { return mi->second.exists ; - } else + } else return EMPTY ; } + /// @brief Returns the accumulated existence currently known for `v`. + /// + /// This includes direct existence updates and entities recorded through + /// `set_existential_info()`. Because existence lives in `sched_info`, + /// synonyms share it while aliases keep separate existence sets. + /// @param[in] v Variable whose known existence is requested. + /// @return The accumulated existence set, or `~EMPTY` for time variables. entitySet variable_existence(variable v) ; + + /// @brief Adds `e` to the pending request set for `v`. + /// + /// Requests accumulate rather than replace the current set. Time variables + /// ignore request updates because they are treated as existing everywhere. + /// @param[in] v Variable whose request set is updated. + /// @param[in] e Requested entities to add. void variable_request(variable v, entitySet e) ; - void clear_variable_request(){ + /// @brief Clears pending requests and extra UNIT-rule requests for every + /// tracked `sched_info` entry. + /// + /// Synonyms reset together because they resolve to the same scheduler + /// entry, while aliases are cleared independently. + void clear_variable_request() { for(vmap_type::iterator mi = vmap.begin(); mi != vmap.end(); mi++){ mi->second.requested=EMPTY; mi->second.extra_unit_request = EMPTY; } } + // void reset_variable_request(variable v, entitySet e=EMPTY) ; - void set_variable_shadow(variable v, entitySet e) - { get_sched_info(v).shadow = e ; } - void variable_shadow(variable v, entitySet e) - { get_sched_info(v).shadow += e ; } - entitySet get_variable_shadow(variable v) const - { return get_sched_info(v).shadow ; } + + /// @brief Replaces the distributed shadow set tracked for `v`. + /// + /// This communication-oriented set is used by later distributed scheduling + /// passes, especially output-mapped and reduction cases. Because shadow + /// lives in `sched_info`, synonyms share it while aliases keep separate + /// shadow sets. + /// @param[in] v Variable whose shadow set is replaced. + /// @param[in] e New shadow set to store. + void set_variable_shadow(variable v, entitySet e) { + get_sched_info(v).shadow = e ; + } + + /// @brief Adds entities to the distributed shadow set tracked for `v`. + /// @param[in] v Variable whose shadow set is updated. + /// @param[in] e Shadow entities to add. + void variable_shadow(variable v, entitySet e) { + get_sched_info(v).shadow += e ; + } + + /// @brief Returns the distributed shadow set currently tracked for `v`. + /// + /// Shadow state lives in `sched_info`, so synonyms share it while aliases + /// keep separate shadow sets. + /// @param[in] v Variable whose shadow set is requested. + /// @return The distributed shadow set currently recorded for `v`. + entitySet get_variable_shadow(variable v) const { + return get_sched_info(v).shadow ; + } + + /// @brief Returns the requested portion of `v` that overlaps rule `f`'s + /// recorded existential contribution. + /// @param[in] f Rule whose requested overlap is requested. + /// @param[in] v Variable whose request set is queried. + /// @return The intersection of `f`'s existential contribution and the + /// current request set for `v`, or `EMPTY` when `f` has no existential + /// entry for `v`. entitySet get_variable_request(rule f, variable v) ; - entitySet get_variable_requests(variable v) const - { return get_sched_info(v).requested ; } + + /// @brief Returns the full pending request set currently stored for `v`. + /// + /// Requests live in `sched_info`, so synonyms share them while aliases keep + /// separate request sets. + /// @param[in] v Variable whose request set is requested. + /// @return The full pending request set for `v`. + entitySet get_variable_requests(variable v) const { + return get_sched_info(v).requested ; + } + + /// @brief Returns every variable name currently registered with the + /// scheduler. + /// + /// This is the scheduler's `all_vars` registry, so it includes canonical + /// variables plus any aliases or synonyms that were added later. + /// @return Every variable name currently registered with the scheduler. variableSet get_typed_variables() const { return all_vars ; } + + /// @brief Returns the memoized image of `e` through map variable `v`. + /// + /// The first lookup delegates to the stored `MapRep`; later lookups for + /// the same entity set reuse the cached result. Because this cache lives in + /// `sched_data`, aliases share it, and synonyms resolve to the same cache + /// through their canonical scheduler entry. + /// @param[in] v Map variable to query. + /// @param[in] e Domain entities whose image is requested. + /// @return `EMPTY` when `v` is not a map variable. entitySet image(variable v, entitySet e) ; + + /// @brief Returns the memoized preimage information of `e` through map + /// variable `v`. + /// + /// The stored pair is whatever the underlying `MapRep::preimage()` + /// produces for `e`: for simple maps the two sets match, while multi-valued + /// maps use the pair to distinguish fully-contained from merely touching + /// source entities. Because this cache lives in `sched_data`, aliases share + /// it, and synonyms resolve to the same cache through their canonical + /// scheduler entry. + /// @param[in] v Map variable to query. + /// @param[in] e Codomain entities whose preimage is requested. + /// @return The cached `MapRep::preimage()` result, or `(EMPTY, EMPTY)` when + /// `v` is not a map variable. std::pair preimage(variable v, entitySet e) ; + /// @brief Replaces the raw duplication-policy bitmask stored for `v`. + /// + /// This is the low-level setter behind `add_policy()`. Because policy + /// lives in `sched_info`, synonyms share it while aliases keep separate + /// policy state. The mask itself is only an input to later duplication + /// selection; it does not set `duplicate_variable`. + /// @param[in] v Variable whose raw policy mask is replaced. + /// @param[in] p New raw policy mask. void set_policy(variable v, unsigned int p) { get_sched_info(v).policy = p;} + + /// @brief Returns the raw duplication-policy bitmask stored for `v`. + /// + /// Use `duplicate_policy`, `add_policy()`, and `is_policy()` when you want + /// the symbolic interpretation of the bits. + /// @param[in] v Variable whose policy mask is requested. + /// @return The raw duplication-policy bitmask stored for `v`. unsigned int get_policy(variable v) { return get_sched_info(v).policy; } - - void add_policy(variable v, duplicate_policy p); - - bool is_policy(variable v, duplicate_policy p); - + + /// @brief Adds duplication policy `p` to `v`'s current policy mask. + /// + /// This accumulates policy bits rather than replacing the existing mask. + /// The update is applied across `v`'s synonym set, but not across aliases. + /// Later duplication analysis interprets that mask and records the final + /// choice with `set_duplicate_variable()`. + /// @param[in] v Variable whose policy mask is updated. + /// @param[in] p Policy bit to add. + void add_policy(variable v, duplicate_policy p) ; + + /// @brief Tests whether duplication policy `p` is active for `v`. + /// + /// This checks the symbolic `duplicate_policy` bit in `v`'s raw policy + /// mask. It does not mean duplication has already been selected. + /// @param[in] v Variable whose policy mask is queried. + /// @param[in] p Policy bit to test. + /// @return True when policy `p` is active for `v`. + bool is_policy(variable v, duplicate_policy p) ; + + /// @brief Returns true when duplication has been selected for `v`. + /// + /// This is the scheduler's stored duplication decision, not just a policy + /// recommendation. Later request-minimization and communication code uses + /// this flag to switch from owner-based requests to duplicated local + /// computation. + /// @param[in] v Variable whose duplication decision is queried. + /// @return True when duplication has been selected for `v`. bool is_duplicate_variable(variable v) { return get_sched_info(v).duplicate_variable; } - + + /// @brief Sets the duplicate-variable flag for `v`'s synonym set. + /// + /// This records the scheduler's final duplication choice for those names. + /// Aliases keep their own duplicate-variable flag because they have + /// separate `sched_info` entries. + /// @param[in] v Variable whose synonym set is updated. + /// @param[in] p New duplicate-variable flag. void set_duplicate_variable(variable v, bool p) { - variableSet syns = get_synonyms(v); + variableSet syns = get_synonyms(v) ; for(variableSet::const_iterator vi = syns.begin(); vi != syns.end(); vi++) - get_sched_info(*vi).duplicate_variable = p; - } + get_sched_info(*vi).duplicate_variable = p ; + } + /// @brief Returns the broader duplicate-work compute set recorded for rule + /// `f` and variable `v`. + /// + /// This per-rule set is used when `v` has been marked as a duplicate + /// variable. Unlike `get_my_proc_able_entities()`, it is recorded before + /// the `my_entities` restriction is applied, so it may include non-owned + /// entities this processor can recompute under work duplication. Because it + /// lives in `sched_info`, synonyms share it while aliases keep separate + /// entries. + /// @param[in] v Variable whose duplicate-work compute set is requested. + /// @param[in] f Rule whose compute set is requested. + /// @return The duplication-expanded compute set recorded for (`v`, `f`). entitySet get_proc_able_entities(variable v, rule f) { - sched_info &finfo = get_sched_info(v); - std::map::const_iterator mi; - mi = finfo.proc_able_map.find(f); + sched_info &finfo = get_sched_info(v) ; + std::map::const_iterator mi ; + mi = finfo.proc_able_map.find(f) ; if(mi != finfo.proc_able_map.end()) - return mi->second; + return mi->second ; else - return EMPTY; + return EMPTY ; } + /// @brief Adds entities to the duplicate-work compute set recorded for rule + /// `f` and variable `v`. + /// + /// Repeated calls accumulate into the cached set rather than replacing it. + /// @param[in] v Variable whose duplicate-work compute set is updated. + /// @param[in] f Rule whose compute set is updated. + /// @param[in] x Entities to add. void set_proc_able_entities(variable v, rule f, entitySet x) { - sched_info &finfo = get_sched_info(v); - finfo.proc_able_map[f] += x; + sched_info &finfo = get_sched_info(v) ; + finfo.proc_able_map[f] += x ; } + /// @brief Returns the processor-local compute set recorded for rule `f` + /// and variable `v`. + /// + /// This narrower per-rule set is recorded after restricting the rule's + /// context to this processor's local entities. Later request processing + /// uses it when `v` is not being duplicated. + /// @param[in] v Variable whose local compute set is requested. + /// @param[in] f Rule whose local compute set is requested. + /// @return The `my_entities`-restricted compute set for (`v`, `f`). entitySet get_my_proc_able_entities(variable v, rule f) { - sched_info &finfo = get_sched_info(v); - std::map::const_iterator mi; - mi = finfo.my_proc_able_map.find(f); + sched_info &finfo = get_sched_info(v) ; + std::map::const_iterator mi ; + mi = finfo.my_proc_able_map.find(f) ; if(mi != finfo.my_proc_able_map.end()) - return mi->second; + return mi->second ; else - return EMPTY; + return EMPTY ; } + /// @brief Adds entities to the processor-local compute set recorded for + /// rule `f` and variable `v`. + /// + /// Repeated calls accumulate into the cached set rather than replacing it. + /// @param[in] v Variable whose local compute set is updated. + /// @param[in] f Rule whose local compute set is updated. + /// @param[in] x Entities to add. void set_my_proc_able_entities(variable v, rule f, entitySet x) { - sched_info &finfo = get_sched_info(v); - finfo.my_proc_able_map[f] += x; + sched_info &finfo = get_sched_info(v) ; + finfo.my_proc_able_map[f] += x ; } + /// @brief Returns the reduction-specific computable summary recorded for + /// `v`. + /// + /// This is a later summary used by duplicated reduction handling, not a + /// per-rule map like `get_proc_able_entities()`. + /// @param[in] v Reduction variable whose summary is requested. + /// @return The reduction-specific computable summary for `v`. entitySet get_reduce_proc_able_entities(variable v) { - sched_info &finfo = get_sched_info(v); - return(finfo.reduce_proc_able_entities); + sched_info &finfo = get_sched_info(v) ; + return(finfo.reduce_proc_able_entities) ; } + /// @brief Replaces the reduction-specific computable summary recorded for + /// `v`. + /// @param[in] v Reduction variable whose summary is replaced. + /// @param[in] x New reduction-specific computable summary. void set_reduce_proc_able_entities(variable v, entitySet x) { - sched_info &finfo = get_sched_info(v); - finfo.reduce_proc_able_entities = x; + sched_info &finfo = get_sched_info(v) ; + finfo.reduce_proc_able_entities = x ; } + /// @brief Returns true when duplicated reduction handling for `v` must use + /// the output-mapped case. + /// + /// This flag is set during reduction analysis when at least one producer of + /// `v` maps its output. In that case later duplicated-reduction logic uses + /// the conservative `reduce_filter` path instead of widening by + /// `get_reduce_proc_able_entities(v)`. + /// @param[in] v Reduction variable to query. + /// @return True when `v` must use the output-mapped reduction path. bool is_reduction_outputmap(variable v) { - sched_info &finfo = get_sched_info(v); - return(finfo.reduction_outputmap); + sched_info &finfo = get_sched_info(v) ; + return(finfo.reduction_outputmap) ; } + /// @brief Records whether duplicated reduction handling for `v` should use + /// the output-mapped case. + /// + /// This value is computed by the reduction-analysis pass. + /// @param[in] v Reduction variable to update. + /// @param[in] x New reduction-output-mapping flag. void set_reduction_outputmap(variable v, bool x) { - sched_info &finfo = get_sched_info(v); - finfo.reduction_outputmap = x; + sched_info &finfo = get_sched_info(v) ; + finfo.reduction_outputmap = x ; } + /// @brief Adds extra UNIT-rule requests for `v`. + /// + /// This accumulates request entities that mapped apply/reduction handling + /// needs UNIT rules to cover in addition to the normal request set. These + /// extras are folded in only for UNIT-rule request processing and are + /// cleared by `clear_variable_request()`. + /// @param[in] v Variable whose extra UNIT requests are updated. + /// @param[in] x Extra UNIT-rule requests to add. void add_extra_unit_request(variable v, entitySet x) { - sched_info &finfo = get_sched_info(v); - finfo.extra_unit_request += x; + sched_info &finfo = get_sched_info(v) ; + finfo.extra_unit_request += x ; } // void reset_extra_unit_request(variable v, entitySet x=EMPTY) { -// sched_info &finfo = get_sched_info(v); -// finfo.extra_unit_request = x; +// sched_info &finfo = get_sched_info(v) ; +// finfo.extra_unit_request = x ; // } + /// @brief Returns the extra UNIT-rule requests currently recorded for `v`. + /// + /// These requests are only added into UNIT-rule processing, not every rule + /// request for `v`. + /// @param[in] v Variable whose extra UNIT requests are requested. + /// @return The extra UNIT-rule requests currently recorded for `v`. entitySet get_extra_unit_request(variable v) { - sched_info &finfo = get_sched_info(v); - return(finfo.extra_unit_request); + sched_info &finfo = get_sched_info(v) ; + return(finfo.extra_unit_request) ; } - - //For map variables, it is necessary to change existence without - //supplying any rules because Loci does not have rules - //that create maps + /// @brief Adds known existence for `v` without recording a producing rule. + /// + /// Despite the name, this extends the current existence set rather than + /// replacing it, and it does not create an `exist_map` producer entry. Use + /// it when existence is known directly, especially for map-related cases + /// that do not have a scheduler rule to attribute. + /// @param[in] v Variable whose known existence is extended. + /// @param[in] x Directly known existence to add. void set_variable_existence(variable v, entitySet x) { - sched_info &finfo = get_sched_info(v); - finfo.existence += x; + sched_info &finfo = get_sched_info(v) ; + finfo.existence += x ; } - void add_model_info(double comm_ts, double comm_tw, const std::map > &comp_info); + /// @brief Loads the timing models used by `MODEL_BASED` duplication. + /// + /// `comm_ts` and `comm_tw` populate the global communication model used to + /// estimate communication as startup plus size-based cost. `comp_info` + /// populates the per-rule computation models used to estimate rule work as + /// a fixed term plus a context-size term. + /// @param[in] comm_ts Fixed communication startup coefficient. + /// @param[in] comm_tw Variable communication coefficient. + /// @param[in] comp_info Per-rule computation-model coefficients. + void add_model_info(double comm_ts, double comm_tw, const std::map > &comp_info) ; + + // These helpers accumulate model estimates computed while comparing the + // original schedule against the duplicated-work alternative for `v`. + // "original" means the owner-communication schedule before duplication is + // selected, while "duplication" means the alternative that recomputes + // locally available entities and communicates only the remaining requests. + /// @brief Adds to `v`'s original-schedule computation estimate. + /// @param[in] v Variable whose estimate is updated. + /// @param[in] add Computation time to add. void add_original_computation_time(variable v, double add) { - sched_info &finfo = get_sched_info(v); - finfo.original_computation_time += add; + sched_info &finfo = get_sched_info(v) ; + finfo.original_computation_time += add ; } - + + /// @brief Adds to `v`'s duplicated-schedule computation estimate. + /// @param[in] v Variable whose estimate is updated. + /// @param[in] add Computation time to add. void add_duplication_computation_time(variable v, double add) { - sched_info &finfo = get_sched_info(v); - finfo.duplication_computation_time += add; + sched_info &finfo = get_sched_info(v) ; + finfo.duplication_computation_time += add ; } - + + /// @brief Adds to `v`'s original-schedule communication estimate. + /// @param[in] v Variable whose estimate is updated. + /// @param[in] add Communication time to add. void add_original_communication_time(variable v, double add) { - sched_info &finfo = get_sched_info(v); - finfo.original_communication_time += add; + sched_info &finfo = get_sched_info(v) ; + finfo.original_communication_time += add ; } + /// @brief Adds to `v`'s duplicated-schedule communication estimate. + /// @param[in] v Variable whose estimate is updated. + /// @param[in] add Communication time to add. void add_duplication_communication_time(variable v, double add) { - sched_info &finfo = get_sched_info(v); - finfo.duplication_communication_time += add; + sched_info &finfo = get_sched_info(v) ; + finfo.duplication_communication_time += add ; } + /// @brief Returns `v`'s accumulated original-schedule computation estimate. + /// @param[in] v Variable whose estimate is requested. + /// @return The accumulated original-schedule computation estimate for `v`. double get_precalculated_original_computation_time(variable v) { - sched_info &finfo = get_sched_info(v); - return(finfo.original_computation_time); + sched_info &finfo = get_sched_info(v) ; + return(finfo.original_computation_time) ; } + /// @brief Returns `v`'s accumulated duplicated-schedule computation + /// estimate. + /// @param[in] v Variable whose estimate is requested. + /// @return The accumulated duplicated-schedule computation estimate for `v`. double get_precalculated_duplication_computation_time(variable v) { - sched_info &finfo = get_sched_info(v); - return(finfo.duplication_computation_time); + sched_info &finfo = get_sched_info(v) ; + return(finfo.duplication_computation_time) ; } + /// @brief Returns `v`'s accumulated original-schedule communication + /// estimate. + /// @param[in] v Variable whose estimate is requested. + /// @return The accumulated original-schedule communication estimate for `v`. double get_precalculated_original_communication_time(variable v) { - sched_info &finfo = get_sched_info(v); - return(finfo.original_communication_time); + sched_info &finfo = get_sched_info(v) ; + return(finfo.original_communication_time) ; } + /// @brief Returns `v`'s accumulated duplicated-schedule communication + /// estimate. + /// @param[in] v Variable whose estimate is requested. + /// @return The accumulated duplicated-schedule communication estimate for `v`. double get_precalculated_duplication_communication_time(variable v) { - sched_info &finfo = get_sched_info(v); - return(finfo.duplication_communication_time); + sched_info &finfo = get_sched_info(v) ; + return(finfo.duplication_communication_time) ; } + /// @brief Returns the computation timing model for rule `r`, or an invalid + /// sentinel model. + /// + /// Missing rules return `model(-100000, -100000)`. Check the coefficients + /// with `model::is_valid_val()` before using them in timing math. + /// @param[in] r Rule whose computation model is requested. + /// @return The computation timing model for `r`, or an invalid sentinel. model get_comp_model(rule r) const { - std::map::const_iterator mi = comp_model.find(r); + std::map::const_iterator mi = comp_model.find(r) ; if(mi != comp_model.end()) - return mi->second; + return mi->second ; else { - return model(-100000, -100000); + return model(-100000, -100000) ; } } - - model get_comm_model() const { return comm_model; } - variableSet get_possible_duplicate_vars() const { return possible_duplicate_vars; } + /// @brief Returns the global communication timing model installed by + /// `add_model_info()`. + /// + /// Before model data is loaded, the coefficients remain invalid sentinel + /// values. + /// @return The global communication timing model. + model get_comm_model() const { return comm_model ; } + + /// @brief Returns the duplicate-analysis worklist for future target sets. + /// + /// This is not the final duplication decision; use + /// `is_duplicate_variable()` for that. The worklist is carried forward for + /// variables whose duplication should be reconsidered when they appear in a + /// later target set. + /// @return The current duplicate-analysis worklist. + variableSet get_possible_duplicate_vars() const { return possible_duplicate_vars ; } + + /// @brief Adds variables and their synonyms to the future duplicate-analysis + /// worklist. + /// + /// The set is append-only. Synonyms are inserted for each variable, while + /// aliases are not expanded here. + /// @param[in] vars Variables to add to the future duplicate-analysis + /// worklist. void add_possible_duplicate_vars(variableSet vars) { for(variableSet::const_iterator vi = vars.begin(); vi != vars.end(); vi++) { - variableSet syns = get_synonyms(*vi); - possible_duplicate_vars += syns; + variableSet syns = get_synonyms(*vi) ; + possible_duplicate_vars += syns ; } } - + + /// @brief Writes a readable summary of the scheduler's current existential + /// state. + /// + /// The summary includes each tracked variable's container kind, synonym + /// set, known existence, and current request set. + /// @param[in] facts Fact database used to identify each variable's + /// container kind. + /// @param[in,out] s Output stream that receives the summary text. + /// @return `s`. std::ostream &print_summary(fact_db &facts, std::ostream &s) ; - std::vector > get_send_entities(variableSet eset, send_entities_type e); - void update_send_entities( const std::vector >& evec, send_entities_type e); - std::list get_comm_info_list(variableSet eset, fact_db& facts, list_type e) const; - void update_comm_info_list(const std::list& elist, list_type e); + /// @brief Returns cached send-entity sets for variables in `eset` whose + /// existential producers map output entities. + /// + /// Only variables with at least one output-mapped producer are returned. + /// Missing cache entries are skipped with a warning to `debugout`. + /// @param[in] eset Variables whose send-entity caches are requested. + /// @param[in] e Phase-specific send-entity cache to query. + /// @return Cached send-entity sets for the applicable variables in `eset`. + std::vector > get_send_entities(variableSet eset, send_entities_type e) ; + + /// @brief Stores send-entity cache entries for a specific scheduler phase. + /// + /// Existing entries are overwritten, with a warning when the same variable + /// is written more than once for that phase. + /// @param[in] evec Send-entity entries to store. + /// @param[in] e Phase-specific send-entity cache to update. + void update_send_entities( const std::vector >& evec, send_entities_type e) ; + + /// @brief Returns one of the cached communication lists for variables in + /// `eset`. + /// + /// Retrieval filters the cache to the requested variables, then repacks the + /// result so all sends appear first in processor order, followed by all + /// receives in processor order. + /// @param[in] eset Variables whose cached communication records are + /// requested. + /// @param[in] facts Fact database associated with the schedule. + /// @param[in] e Phase-specific communication cache to query. + /// @return The filtered and reordered communication list. + std::list get_comm_info_list(variableSet eset, fact_db& facts, list_type e) const ; - entitySet get_exec_seq(rule r){ - entitySet re = EMPTY; - std::map::const_iterator mi = exec_seq_map.find(r); + /// @brief Appends communication records into one of the phase-specific + /// communication caches. + /// + /// Records are stored per variable and are not cleared or deduplicated by + /// this helper. + /// @param[in] elist Communication records to append. + /// @param[in] e Phase-specific communication cache to update. + void update_comm_info_list(const std::list& elist, list_type e) ; + + /// @brief Returns the cached execution sequence for `r`. + /// @param[in] r Rule whose cached execution sequence is requested. + /// @return The cached sequence, or `EMPTY` when no sequence has been + /// stored. + /// + /// Reading before the sequence is populated emits a warning to `debugout`. + entitySet get_exec_seq(rule r) { + entitySet re = EMPTY ; + std::map::const_iterator mi = exec_seq_map.find(r) ; if(mi != exec_seq_map.end()){ - re += mi->second; + re += mi->second ; }else{ - debugout<<"WARNING: exec_seq_map is read before write at rule " << r << endl; + debugout << "WARNING: exec_seq_map is read before write at rule " + << r << endl ; } - return re; + return re ; } - - void update_exec_seq(rule r, entitySet eset){ - std::map::const_iterator mi = exec_seq_map.find(r); - if(mi == exec_seq_map.end()){ - exec_seq_map[r] = eset; + + /// @brief Stores the execution sequence for `r` the first time it is seen. + /// + /// Later writes for the same rule are ignored and reported as warnings to + /// `debugout`. + /// @param[in] r Rule whose execution sequence is being cached. + /// @param[in] eset Execution sequence to cache for `r`. + void update_exec_seq(rule r, entitySet eset) { + std::map::const_iterator mi = exec_seq_map.find(r) ; + if(mi == exec_seq_map.end()) { + exec_seq_map[r] = eset ; }else{ - debugout<<"WARNING: exec_seq_map is written more than once at rule " << r << endl; + debugout << "WARNING: exec_seq_map is written more than once at rule " + << r << endl ; } } } ; From fc8ff4bf36e617514d1f712393636874a6403da7 Mon Sep 17 00:00:00 2001 From: Christopher Neal Date: Thu, 14 May 2026 00:04:15 -0400 Subject: [PATCH 2/4] remove sched_db source edits from test branch --- src/System/sched_db.cc | 348 ++++++----- src/include/sched_db.h | 1253 ++++++++-------------------------------- 2 files changed, 395 insertions(+), 1206 deletions(-) diff --git a/src/System/sched_db.cc b/src/System/sched_db.cc index a9cd1d17..d610bcde 100644 --- a/src/System/sched_db.cc +++ b/src/System/sched_db.cc @@ -24,7 +24,7 @@ #include #include -using std::string ; +using std::string ; using std::map ; using std::make_pair ; using std::vector ; @@ -45,24 +45,23 @@ namespace Loci { extern int MPI_rank ; extern ofstream debugout ; - - extern bool rule_has_mapping_in_output(rule r) ; - + extern bool rule_has_mapping_in_output(rule r); sched_db::sched_db(fact_db &facts) { detected_errors = false ; init(facts) ; } - - sched_db::sched_db() { detected_errors = false ; } + + sched_db::sched_db() {detected_errors = false ;} sched_db::~sched_db() {} - void sched_db::init(fact_db &facts) { + void + sched_db::init(fact_db &facts) { variableSet tmp_all_vars = facts.get_typed_variables() ; for(size_t i = 0; i < tmp_all_vars.size(); i++) sched_infov.push_back(sched_data()) ; int sched_alloc = 0 ; for(variableSet::const_iterator vi = tmp_all_vars.begin(); vi != tmp_all_vars.end(); ++vi) { - sched_info si ; + sched_info si ; si.sched_info_ref = sched_alloc++ ; storeRepP rp = facts.get_variable(*vi) ; si.existence = rp->domain() ; @@ -81,12 +80,12 @@ namespace Loci { all_vars = tmp_all_vars ; } - + void sched_db::install_sched_info(variable v, sched_info info) { vmap_type::iterator mi = vmap.find(v) ; if(mi != vmap.end()) { cerr << "error: reinstalling fact in database for variable " - << v << endl ; + << v << endl ; abort() ; } info.synonyms += v ; @@ -94,17 +93,15 @@ namespace Loci { sched_infov[info.sched_info_ref].aliases += v ; all_vars += v ; } - /*! since free_set is never assigned, else block will never be executed, commented out here */ void sched_db::install_sched_data(variable v, sched_data data) { sched_infov.push_back(data) ; install_sched_info(v,sched_info(sched_infov.size()-1)) ; } - sched_db::sched_info &sched_db::get_sched_info(variable v) { vmap_type::iterator mi = vmap.find(remove_synonym(v)) ; - if(mi == vmap.end()) - return get_sched_info(variable("EMPTY")) ; // assume variable("EMPTY") will be definitely in vmap, other infinite loop + if(mi == vmap.end()) + return get_sched_info(variable("EMPTY")) ; /*! assume variable("EMPTY") will be definitely in vmap, other infinite loop*/ return mi->second ; } @@ -113,8 +110,9 @@ namespace Loci { get_sched_data(*vi).rotations += vars ; } } - + void sched_db::alias_variable(variable v, variable alias) { + if(all_vars.inSet(v)) { if(all_vars.inSet(alias)) { if(MPI_processes == 1) { @@ -124,32 +122,33 @@ namespace Loci { debugout << "alias already in fact_db!" << endl ; debugout << "error found in alias_variable("<::const_iterator mi ; sched_info &finfo = get_sched_info(v) ; @@ -314,13 +314,13 @@ namespace Loci { rules += mi->first ; return rules ; } - + void sched_db::variable_request(variable v, entitySet e) { if(v.get_info().tvar) return ; get_sched_info(v).requested += e ; } - + entitySet sched_db::get_variable_request(rule f, variable v) { sched_info &finfo = get_sched_info(v) ; map::iterator mi = finfo.exist_map.find(f) ; @@ -347,7 +347,7 @@ namespace Loci { else return fdata.imageMap[e] = get_sched_data(v).minfo->image(e) ; } - + pair sched_db::preimage(variable v, entitySet e) { sched_data &fdata = get_sched_data(v) ; if(!fdata.ismap) @@ -359,7 +359,7 @@ namespace Loci { else return fdata.preimageMap[e] = get_sched_data(v).minfo->preimage(e) ; } - + void sched_db::add_policy(variable v, duplicate_policy p) { unsigned int policy = get_policy(v); unsigned int temp = 1; @@ -399,11 +399,11 @@ namespace Loci { const map > &comp_info) { comm_model.ts = comm_ts; comm_model.tw = comm_tw; - + for(map >::const_iterator mi = comp_info.begin(); - mi != comp_info.end(); mi++) { - model tmpModel(mi->second.first, mi->second.second) ; - comp_model[mi->first] = tmpModel ; + mi != comp_info.end(); mi++) { + model tmpModel(mi->second.first, mi->second.second); + comp_model[mi->first] = tmpModel; } } @@ -413,16 +413,16 @@ namespace Loci { for(mi=vmap.begin();mi!=vmap.end();++mi) { storeRepP sp = facts.get_variable(mi->first) ; if(isMAP(sp)) - s << " Container = MAP " << endl ; + s << " Container = MAP " << endl ; else if(isSTORE(sp)) - s << " Container = STORE " << endl ; + s << " Container = STORE " << endl ; else if(isPARAMETER(sp)) - s << " Container = PARAMETER " << endl ; + s << " Container = PARAMETER " << endl ; else if(isBLACKBOX(sp)) - s << " Container = BLACKBOX " << endl; + s << " Container = BLACKBOX " << endl; else if(isCONSTRAINT(sp)) - s << " Container = CONSTRAINT " << endl ; - + s << " Container = CONSTRAINT " << endl ; + // double size = 0 ; // size = sp->pack_size(mi->second.requested) / 1000000 ; s << mi->first << " " <second.synonyms << " "<< mi->second.existence @@ -433,82 +433,75 @@ namespace Loci { std::vector > sched_db::get_send_entities(variableSet eset, send_entities_type e) { - std::vector > re ; - variableSet::const_iterator vi ; - std::map::const_iterator mi ; - - variableSet send_vars ; - for(vi = eset.begin(); vi !=eset.end(); vi++) { - variable v = variable(*vi) ; + std::vector > re; + variableSet::const_iterator vi; + std::map::const_iterator mi; + + variableSet send_vars; + for(vi = eset.begin(); vi !=eset.end(); vi++){ + variable v = variable(*vi); ruleSet rs = get_existential_rules(v) ; for(ruleSet::const_iterator rsi = rs.begin(); rsi != rs.end(); ++rsi) { if(rule_has_mapping_in_output(*rsi)) { - send_vars += v ; - break ; + send_vars += v; + break; } } } - if(e == BARRIER) { - for(vi = send_vars.begin(); vi != send_vars.end(); vi++) { - variable v = variable(*vi) ; - mi = barrier_send_entities_map.find(v) ; - if(mi != barrier_send_entities_map.end()) { - re.push_back(make_pair(v, mi->second)) ; - } else { - debugout << "WARNING: for variable " << v - << " barrier_send_entities_map is read before it's written" - << endl ; + if(e == BARRIER){ + for(vi = send_vars.begin(); vi != send_vars.end(); vi++){ + variable v = variable(*vi); + mi = barrier_send_entities_map.find(v); + if(mi != barrier_send_entities_map.end()){ + re.push_back(make_pair(v, mi->second)); + }else{ + debugout << "WARNING: for variable " << v << " barrier_send_entities_map is read before it's written" << endl; } } - } else if( e == RECURSE_PRE) { - for(vi = send_vars.begin(); vi !=send_vars.end(); vi++) { - variable v = variable(*vi) ; - mi = recurse_pre_send_entities_map.find(v) ; - if(mi != recurse_pre_send_entities_map.end()) { - re.push_back(make_pair(v, mi->second)) ; - } else { - debugout << "WARNING: for variable " << v - << " recurse_pre_send_entities_map is read before it's written" - << endl ; + }else if( e == RECURSE_PRE){ + for(vi = send_vars.begin(); vi !=send_vars.end(); vi++){ + variable v = variable(*vi); + mi = recurse_pre_send_entities_map.find(v); + if(mi != recurse_pre_send_entities_map.end()){ + re.push_back(make_pair(v, mi->second)); + }else{ + debugout << "WARNING: for variable " << v << " recurse_pre_send_entities_map is read before it's written" << endl; } } - } else { - debugout <<"ERROR: unrecognized send_entities_type in get_send_entities() " - << endl ; + }else{ + debugout<<"ERROR: unrecognized send_entities_type in get_send_entities() " << endl; } - return re ; + return re; } + - + void sched_db::update_send_entities( const std::vector >& evec, send_entities_type e){ - if(e == BARRIER) { - for(unsigned int vi = 0; vi < evec.size(); vi++) { - variable v = evec[vi].first ; - map::const_iterator mi = barrier_send_entities_map.find(v) ; - if(mi != barrier_send_entities_map.end()) { - debugout << "WARNING: for variable " << v - << " barrier_send_entities_map is written more than once" - << endl ; + + if(e == BARRIER){ + for(unsigned int vi = 0; vi < evec.size(); vi++){ + variable v = evec[vi].first; + map::const_iterator mi = barrier_send_entities_map.find(v); + if(mi != barrier_send_entities_map.end()){ + debugout << "WARNING: for variable " << v << " barrier_send_entities_map is written more then once" << endl; } - barrier_send_entities_map[v] = (evec[vi]).second ; + barrier_send_entities_map[v] = (evec[vi]).second; } - } else if(e == RECURSE_PRE) { - for(unsigned int vi = 0; vi < evec.size(); vi++) { - variable v = evec[vi].first ; - map::const_iterator mi = recurse_pre_send_entities_map.find(v) ; - if(mi != recurse_pre_send_entities_map.end()) { - debugout << "WARNING: for variable " << v - << " recurse_pre_send_entities_map is written more than once" - << endl ; + }else if(e == RECURSE_PRE){ + for(unsigned int vi = 0; vi < evec.size(); vi++){ + variable v = evec[vi].first; + map::const_iterator mi = recurse_pre_send_entities_map.find(v); + if(mi != recurse_pre_send_entities_map.end()){ + debugout << "WARNING: for variable " << v << " barrier_send_entities_map is written more then once" << endl; } - recurse_pre_send_entities_map[v] = (evec[vi]).second ; + recurse_pre_send_entities_map[v] = (evec[vi]).second; } - } else { - debugout<<"ERROR: unrecognized send_entities_type in update_send_entities" << endl ; + }else{ + debugout<<"ERROR: unrecognized send_entities_type in update_send_entities" << endl; } } - + /*!sort_comm_map() composes a list from the map so that in the list all send info is in front of recv info and the order of comm info is for(pi = procs.begin(); pi != procs.end(); pi++){ @@ -519,23 +512,25 @@ namespace Loci { std::list sort_comm_map(variableSet vset, const std::map >& clist_m, fact_db& facts, - sched_db::list_type e) { - + sched_db::list_type e){ + + vector > > send_info ; vector > > recv_info ; + // First collect information from slist HASH_MAP(int,vector) send_data ; HASH_MAP(int,vector) recv_data ; intervalSet send_procs, recv_procs ; - for(variableSet::const_iterator vi = vset.begin(); vi != vset.end(); vi++) { - variable va = variable(*vi) ; - list slist ; - std::map >::const_iterator mi = clist_m.find(va) ; - if(mi != clist_m.end()) { + for(variableSet::const_iterator vi = vset.begin(); vi != vset.end(); vi++){ + variable va = variable(*vi); + list slist; + std::map >::const_iterator mi = clist_m.find(va); + if(mi != clist_m.end()){ // debugout <<"get variable " << va <<" from list " << e << endl; - slist = mi->second ; + slist = mi->second; list::const_iterator cli ; for(cli=slist.begin();cli!=slist.end();++cli) { variable v = cli->v ; @@ -572,7 +567,7 @@ namespace Loci { list clist ; const int nrecv = recv_info.size() ; const int nsend = send_info.size() ; - + // Pack the buffer for sending for(int i=0;i sched_db::get_comm_info_list(variableSet eset, fact_db& facts, list_type e) const{ - list re ; - if(e==BARRIER_CLIST) { - re = sort_comm_map(eset, barrier_clist_map, facts, e) ; - } else if(e==BARRIER_PLIST) { - re = sort_comm_map(eset, barrier_plist_map, facts, e) ; - } else if(e==REDUCE_RLIST) { - re = sort_comm_map(eset, reduce_rlist_map, facts, e) ; - } else if(e==REDUCE_CLIST) { - re = sort_comm_map(eset, reduce_clist_map, facts, e) ; - } else if(e==LOOP_ADVANCE_LIST) { - re = sort_comm_map(eset, loop_advance_list_map, facts, e) ; - } else if(e==RECURSE_CLIST) { - re = sort_comm_map(eset, recurse_clist_map, facts, e) ; - } else if(e==RECURSE_PRE_CLIST) { - re = sort_comm_map(eset, recurse_pre_clist_map, facts, e) ; - } else if(e==RECURSE_POST_CLIST) { - re = sort_comm_map(eset, recurse_post_clist_map, facts, e) ; - } else if(e==RECURSE_PRE_PLIST) { - re = sort_comm_map(eset, recurse_pre_plist_map, facts, e) ; - } else { - debugout << "ERROR: unrecognized list_type " << e << " in get_comm_info_list" - << endl ; + list re; + if(e==BARRIER_CLIST){ + re = sort_comm_map(eset, barrier_clist_map, facts, e); + }else if(e==BARRIER_PLIST){ + re = sort_comm_map(eset, barrier_plist_map, facts, e); + }else if(e==REDUCE_RLIST){ + re = sort_comm_map(eset, reduce_rlist_map, facts, e); + }else if(e==REDUCE_CLIST){ + re = sort_comm_map(eset, reduce_clist_map, facts, e); + }else if(e==LOOP_ADVANCE_LIST){ + re = sort_comm_map(eset, loop_advance_list_map, facts, e); + }else if(e==RECURSE_CLIST){ + re = sort_comm_map(eset, recurse_clist_map, facts, e); + }else if(e==RECURSE_PRE_CLIST){ + re = sort_comm_map(eset, recurse_pre_clist_map, facts, e); + }else if(e==RECURSE_POST_CLIST){ + re = sort_comm_map(eset, recurse_post_clist_map, facts, e); + }else if(e==RECURSE_PRE_PLIST){ + re = sort_comm_map(eset, recurse_pre_plist_map, facts, e); + }else{ + debugout << "ERROR: unrecognized list_type " << e << " in get_comm_info_list" << endl; } - return re ; + return re; } - - void sched_db::update_comm_info_list(const std::list& elist, list_type e) { + void sched_db::update_comm_info_list(const std::list& elist, list_type e){ list::const_iterator cli ; - // map >::iterator mi; + // map >::iterator mi; for(cli=elist.begin();cli!=elist.end();++cli) { variable v = cli->v ; - if(e==BARRIER_CLIST) { - barrier_clist_map[v].push_back( *cli) ; - } else if(e==BARRIER_PLIST) { - barrier_plist_map[v].push_back( *cli) ; - } else if(e==REDUCE_RLIST) { - reduce_rlist_map[v].push_back( *cli) ; - } else if(e==REDUCE_CLIST) { - reduce_clist_map[v].push_back( *cli) ; - } else if(e==LOOP_ADVANCE_LIST) { - loop_advance_list_map[v].push_back( *cli) ; - } else if(e==RECURSE_CLIST) { - recurse_clist_map[v].push_back( *cli) ; - } else if(e==RECURSE_PRE_CLIST) { - recurse_pre_clist_map[v].push_back( *cli) ; - } else if(e==RECURSE_POST_CLIST) { - recurse_post_clist_map[v].push_back( *cli) ; - } else if(e==RECURSE_PRE_PLIST) { - recurse_pre_plist_map[v].push_back( *cli) ; - } else { - debugout << "ERROR: unrecognized list_type " << e - << " in update_comm_info_list" << endl ; + if(e==BARRIER_CLIST){ + barrier_clist_map[v].push_back( *cli); + }else if(e==BARRIER_PLIST){ + barrier_plist_map[v].push_back( *cli); + }else if(e==REDUCE_RLIST){ + reduce_rlist_map[v].push_back( *cli); + }else if(e==REDUCE_CLIST){ + reduce_clist_map[v].push_back( *cli); + }else if(e==LOOP_ADVANCE_LIST){ + loop_advance_list_map[v].push_back( *cli); + }else if(e==RECURSE_CLIST){ + recurse_clist_map[v].push_back( *cli); + }else if(e==RECURSE_PRE_CLIST){ + recurse_pre_clist_map[v].push_back( *cli); + }else if(e==RECURSE_POST_CLIST){ + recurse_post_clist_map[v].push_back( *cli); + }else if(e==RECURSE_PRE_PLIST){ + recurse_pre_plist_map[v].push_back( *cli); + } else{ + debugout << "ERROR: unrecognized list_type " << e << " in update_comm_info_list" << endl; } - - } + + } } } - + diff --git a/src/include/sched_db.h b/src/include/sched_db.h index 660ed3ad..87563f95 100644 --- a/src/include/sched_db.h +++ b/src/include/sched_db.h @@ -32,476 +32,190 @@ #include namespace Loci { - - /// @brief Describes one variable transfer between this processor and one peer. - /// - /// Each record names the variable being communicated and the peer involved. - /// `send_set` holds the local entities to pack and send to `processor`. - /// `recv_set` holds the local entities to fill from `processor`, in the order - /// required by unpacking. - /// - /// These records are produced during scheduler analysis, cached by `sched_db`, - /// and later assembled into communication blocks for barrier, reduction, loop, - /// and recursion schedules. struct comm_info { variable v ; - - /// @brief Peer processor for this transfer record. - /// For `send_set`, this is the destination rank. - /// For `recv_set`, this is the source rank. int processor ; - - /// @brief Local entities to pack and send to `processor`. entitySet send_set ; - - /// @brief Local entities to fill from `processor`, in unpack order. sequence recv_set ; } ; - - - /// @brief Send-side variable transfer grouped under a peer processor. - /// - /// The surrounding communication map or vector stores the peer rank; this - /// record only names the variable and the local entities to pack for that peer. struct send_var_info { variable v ; - - /// @brief Local entities to pack for the associated peer. entitySet set ; send_var_info(variable iv, const entitySet &iset) : v(iv),set(iset) {} } ; - - - /// @brief Receive-side variable transfer grouped under a peer processor. - /// - /// The surrounding communication map or vector stores the peer rank; this - /// record only names the variable and the ordered local entities to fill from - /// that peer. + struct recv_var_info { variable v ; - - /// @brief Local entities to unpack into, in message order. sequence seq ; recv_var_info(variable iv, const sequence &iseq) : v(iv),seq(iseq) {} } ; - - - /// @brief Compiler-side database of derived scheduling state. - /// - /// `sched_db` is initialized from a `fact_db` and then updated by scheduler - /// passes as they analyze rule existence, propagate variable requests, and - /// prepare distributed communication. - /// - /// It records per-variable scheduling metadata such as known existence, - /// requested entities, rule-by-rule existential contributions, alias and - /// synonym relationships, cached map-query results, communication records for - /// distributed scheduling phases, and duplication-analysis data. - /// - /// Other scheduler components use this database to answer questions about what - /// can be computed, what must be requested or communicated, and how a rule or - /// variable should be scheduled. - /// - /// `sched_db` owns scheduling metadata only; the underlying fact stores and - /// typed variables remain owned by `fact_db`. class sched_db { - private: - - /// @brief Shared metadata for variables that point at the same - /// scheduling-data slot. - /// - /// Alias variables share one `sched_data` record so they can reuse map - /// classification, cached map queries, and loop-rotation bookkeeping. + struct sched_data { - /// @brief Variables that share this `sched_data` slot. - variableSet aliases ; - - /// @brief Variables linked by loop-rotation bookkeeping. - variableSet rotations ; - - /// @brief Original-side names recorded when aliases are introduced into - /// this slot. - variableSet antialiases ; - - /// @brief True when the backing store representation is a `MAP` or - /// `GPUMAP`. + variableSet aliases,rotations,antialiases ; bool ismap ; - - /// @brief Cached `MapRep` used by `image()` and `preimage()` when - /// `ismap` is true. MapRepP minfo ; - - /// @brief Memoized `MapRep::image()` results keyed by the queried - /// domain. std::map imageMap ; - - /// @brief Memoized `MapRep::preimage()` results keyed by the queried - /// codomain. - /// The stored pair preserves both preimage variants returned by the map - /// representation. - std::map > preimageMap ; - - sched_data() {} - sched_data(variable v, storeRepP &st) { - aliases += v ; - ismap = (st->RepType() == Loci::MAP || st->RepType() == Loci::GPUMAP) ; - if(ismap) minfo = MapRepP(st->getRep()) ; - } + std::map > preimageMap + ; + sched_data() {} + sched_data(variable v, storeRepP &st) + { aliases += v ; + ismap = (st->RepType() == Loci::MAP || st->RepType() == Loci::GPUMAP) ; + if(ismap) minfo = MapRepP(st->getRep()) ; } } ; - - - /// @brief One rule's contribution to the known existence of a variable. + struct existential_info { - /// @brief Variable instance associated with this rule contribution. - /// Priority decoration on this name is used when overlapping producers - /// are compared. variable v ; - - /// @brief Entities that remain attributed to the rule after any - /// priority-based conflict handling. entitySet exists ; - - existential_info() {} + existential_info() {} existential_info(const variable &vin,const entitySet &e) : v(vin), exists(e) {} } ; - /// @brief Canonical per-variable scheduler state. - /// - /// Each variable handled directly by `vmap` owns one of these records. - /// Shared alias/map metadata lives in `sched_infov`; this struct stores the - /// existential, request, reduction, and duplication state attached to the - /// variable itself. + + struct sched_info ; + friend struct sched_info ; struct sched_info { - /// @brief Index into `sched_infov` for shared alias, rotation, and map - /// metadata. int sched_info_ref ; - - // Legacy field kept commented out because it was initialized in `init()` - // but not otherwise used by the current scheduler implementation. + /*! fact_installed is initialized in init(), and never used except for variable_is_fact_at(), comment out*/ // entitySet fact_installed ; - - /// @brief Names that resolve to this `sched_info` entry through synonym - /// lookup. variableSet synonyms ; - - /// @brief Rule-keyed existential contributions merged into `existence`. + std::map exist_map ; - - /// @brief Union of directly known and rule-derived entities for the - /// variable. entitySet existence ; - - /// @brief Union of entities requested by downstream scheduling passes. entitySet requested ; - - /// @brief Distributed shadow-region entities for output-mapped apply or - /// reduction scheduling. - /// Later passes use this set to exchange partial results with owner - /// processors. - entitySet shadow ; - - /// @brief Additional UNIT-rule requests created when mapped apply logic - /// needs target entities beyond the normal request set. + entitySet shadow ; // Used by distributed memory apply rules + + //Apply rules may have target variables both on input and output side. + //Sometimes apply rules may need to access entities of target variable + //which are not requested. In that case, UNIT rule also allocates these + //entities in addition to entities being requested. entitySet extra_unit_request; - /// @brief Maps each rule to the entities it can compute under the broader - /// duplication-analysis context. + //////////////////////////Duplication Related:////////////// + //Defines maximum which target variable entities a rule can compute std::map proc_able_map; - /// @brief Maps each rule to the entities it can compute using only - /// processor-local context. + //Defines which target variable entities a rule can compute using context + //that is subset of my_entities std::map my_proc_able_map; - /// @brief Bitmask of enabled `duplicate_policy` selectors. - unsigned int policy; - - /// @brief True when duplication has been selected for this variable. - bool duplicate_variable; - - /// @brief Reduction-specific computable set used when deciding whether - /// owner communication can be avoided. - entitySet reduce_proc_able_entities; + unsigned int policy; //Each bit defines a policy + bool duplicate_variable; //Defines if a variable is duplicated + + //Applies only to reduce variable: considering all the rules of a variable, + //it defines which entities can definitely computed on a processor + entitySet reduce_proc_able_entities; - /// @brief True when any producer rule for the variable maps its output - /// during reduction handling. - bool reduction_outputmap; + bool reduction_outputmap; //If any of the apply or unit rule has mapping in output - /// @brief Accumulated model-based computation-time estimates for the - /// original and duplicated schedules. double original_computation_time, duplication_computation_time; - - /// @brief Accumulated model-based communication-time estimates for the - /// original and duplicated schedules. double original_communication_time, duplication_communication_time; + ////////////////////////////////////////////////////////////// sched_info(int ref = -1) { - sched_info_ref = ref ; - policy = 0; - duplicate_variable = false; - reduce_proc_able_entities = ~EMPTY; - reduction_outputmap = false; - - original_computation_time = 0; - duplication_computation_time = 0; - original_communication_time = 0; - duplication_communication_time = 0; + sched_info_ref = ref ; + policy = 0; + duplicate_variable = false; + reduce_proc_able_entities = ~EMPTY; + reduction_outputmap = false; + + original_computation_time = 0; + duplication_computation_time = 0; + original_communication_time = 0; + duplication_communication_time = 0; } } ; - /// @brief Stores the two coefficients used by the scheduler's timing - /// models for duplication decisions. - /// - /// `ts` is the fixed-cost term and `tw` is the variable-cost term. - /// Communication costs are estimated as - /// `ts * processor_count + tw * packed_size`, while computation costs are - /// estimated as `ts + tw * context_size`. - /// - /// The units come from the external model data loaded by the scheduler, so - /// these values are treated as generic timing coefficients rather than - /// hard-coded seconds. - struct model { - /// @brief Fixed-cost term in the timing model. - double ts ; - - /// @brief Variable-cost term in the timing model. - double tw ; - - /// @brief Returns true when `val` is a populated coefficient. - /// @param[in] val Coefficient value to validate. - /// @return True when `val` is not the invalid sentinel. - static bool is_valid_val(double val) { return (val > -99999) ; } - - /// @brief Copies the stored coefficients into output references. - /// @param[out] t0 Receives the fixed-cost term. - /// @param[out] tc Receives the variable-cost term. - void get_parameters(double &t0, double &tc) { t0 = ts; tc = tw ; } - - /// @brief Replaces the stored coefficients. - /// @param[in] t0 Fixed-cost term to store. - /// @param[in] tc Variable-cost term to store. - void set_parameters(double t0, double tc) { ts = t0; tw = tc ; } + + /*! + data structure used for duplication + */ + struct model { + double ts, tw; + static bool is_valid_val(double val) { return (val > -99999); } model(double t0, double tc) { - ts = t0 ; - tw = tc ; + ts = t0; + tw = tc; } - - model() { ts = -100000; tw = -100000 ; } - } ; - - /// @brief Variables already queued for later duplicate-work analysis. + model() { ts = -100000; tw = -100000;} + void get_parameters(double &t0, double &tc) { t0 = ts; tc = tw; } + void set_parameters(double t0, double tc) { ts = t0; tw = tc; } + }; variableSet possible_duplicate_vars; - - /// @brief Per-rule computation timing models used by model-based - /// duplication decisions. std::map comp_model; - - /// @brief Communication timing model used by model-based duplication - /// decisions. model comm_model; + - /// @brief Registers a variable in the scheduler's canonical lookup tables. void register_variable(variable v) ; - - /// @brief Every variable name currently known to the scheduler, including - /// aliases and synonyms added after initialization. + variableSet all_vars ; - - /// @brief Maps non-canonical synonym names to the canonical variables - /// stored in `vmap`. std::map synonyms ; - - /// @brief Lookup-table type for variables that carry independent scheduler - /// state. typedef std::map vmap_type ; - /// @brief Per-variable scheduler state addressed after synonym resolution. + /*! + vmap: the sched_info for each variable. + */ vmap_type vmap ; - - /// @brief Shared `sched_data` table referenced by `sched_info_ref`. - /// Alias variables point into the same entry in this table. std::vector sched_infov ; + /*! free_set is never initialized or modified, seems no purpose to exists. comment out */ + // intervalSet free_set ; - // Legacy field kept commented out because it is neither initialized nor - // queried in the current scheduler implementation. - // intervalSet free_set ; - - /// @brief Sticky flag raised when scheduler analysis reports a conflict or - /// other invalid state. bool detected_errors ; - - /// @brief Phase-specific communication and execution caches populated - /// during request analysis. - /// Send-entity caches hold mapped-output send domains. Communication-list - /// caches hold variable-keyed `comm_info` records for barrier, reduction, - /// loop-advance, and recursion phases. `exec_seq_map` stores rule-keyed - /// execution domains. - - /// @brief Barrier-phase send sets used to build mapped-output - /// precommunication. + /*! the follow maps store variable-based comm_info and rule-based sequences for compilers + these data structures are modified and used by existential_analysis + */ std::map barrier_send_entities_map ; - - /// @brief Recursive pre-phase send sets used to build mapped-output - /// precommunication. std::map recurse_pre_send_entities_map ; - - /// @brief Barrier-phase clone/fill communication records derived from - /// variable requests. std::map > barrier_clist_map; - - /// @brief Barrier-phase precommunication records built for mapped outputs. std::map > barrier_plist_map; - - /// @brief Reduction-phase communication records that return shadow values - /// for owner-side joining. std::map > reduce_rlist_map; - - /// @brief Reduction-phase clone/fill communication records derived from - /// variable requests. std::map > reduce_clist_map; - - /// @brief Loop-advance communication records cached before time-level names - /// are restored. std::map > loop_advance_list_map; - - /// @brief General recursion communication cache keyed by variable. std::map > recurse_clist_map; - - /// @brief Recursive pre-phase request communication records. std::map > recurse_pre_clist_map; - - /// @brief Recursive post-phase communication records issued after recursive - /// iterations. std::map > recurse_post_clist_map; - - /// @brief Recursive pre-phase precommunication records for mapped outputs. std::map > recurse_pre_plist_map; - - /// @brief Rule-keyed execution domains cached during request processing. - /// The first write wins; later writes only warn. std::map exec_seq_map; - + public: - /// @brief Resolves a scheduler synonym to its canonical variable name. - /// - /// Synonyms share one canonical `sched_info` entry. Alias variables are not - /// resolved by this helper; aliases keep separate names and share - /// `sched_data`. - /// @param[in] v Variable name to canonicalize. - /// @return The canonical variable for `v`, or `v` if no synonym is - /// registered. variable remove_synonym(variable v) const { std::map::const_iterator mi ; - if((mi=synonyms.find(v)) != synonyms.end()) - return mi->second ; + if((mi=synonyms.find(v)) != synonyms.end()) + return mi->second ; return v ; } - /// @brief Selects a duplication-policy bit for a variable. - /// - /// `add_policy()` and `is_policy()` translate these selectors into bits in - /// `sched_info::policy`: `NEVER` -> 1, `ALWAYS` -> 2, `MODEL_BASED` -> 4. - enum duplicate_policy{NEVER, ALWAYS, MODEL_BASED}; - - /// @brief Selects one of the cached `comm_info` lists by scheduler phase. - /// - /// The chosen value determines which communication cache - /// `get_comm_info_list()` and `update_comm_info_list()` read or update for - /// barrier, reduction, loop-advance, or recursive scheduling. + public: + enum duplicate_policy{NEVER, ALWAYS, MODEL_BASED}; enum list_type{BARRIER_CLIST, BARRIER_PLIST, REDUCE_RLIST, REDUCE_CLIST, LOOP_ADVANCE_LIST,RECURSE_CLIST, RECURSE_PRE_CLIST, RECURSE_POST_CLIST, RECURSE_PRE_PLIST}; - - /// @brief Selects which cached mapped-output send-entity set to query or - /// update. - /// - /// These caches are populated during existence analysis and later used - /// while building precommunication for barrier or recursive pre phases. enum send_entities_type{BARRIER, RECURSE_PRE}; - - /// @brief Creates an empty scheduler database with no registered - /// variables. - /// - /// Call `init()` before using variable-dependent scheduler state. + sched_db() ; - - /// @brief Destroys the scheduler metadata owned by this database. ~sched_db() ; - - /// @brief Creates a scheduler database initialized from the typed facts in - /// `facts`. - /// - /// This convenience constructor clears the error flag and then seeds the - /// scheduler tables from the variables currently typed in `facts`. - /// @param[in] facts Fact database whose typed variables seed the initial - /// scheduler state. sched_db(fact_db &facts) ; - - /// @brief Seeds scheduler state from the typed variables in `facts`. - /// - /// For each typed variable, this records its current store domain as known - /// existence, creates its canonical `sched_info`, initializes its shared - /// `sched_data`, and records the variable in `all_vars`. - /// - /// This routine is intended for a fresh `sched_db` or one that has been - /// explicitly prepared for reinitialization. - /// @param[in] facts Fact database whose typed variables seed scheduler - /// state. + // this method is used to initialize a sched_db + // from a fact_db (it is needed in case of when + // the sched_db is not directly created from a fact_db) void init(fact_db &facts) ; - /// @brief Returns whether scheduler analysis has reported a sticky error. - /// - /// Top-level schedule builders check this flag after schedule construction - /// and abort if any MPI rank reports a failure. bool errors_found() {return detected_errors ;} - - /// @brief Clears the sticky scheduler error flag. - /// - /// This resets only the recorded flag; it does not repair the underlying - /// state that originally triggered the error. void clear_errors() {detected_errors = false ;} - - /// @brief Marks scheduler analysis as failed. - /// - /// Use this when a scheduler pass detects an invalid or unsupported state - /// but continues far enough for the caller to report diagnostics. void set_error() { detected_errors = true ; } - - /// @brief Adds shared scheduling data for a new variable and registers `v`. - /// - /// The supplied `sched_data` is appended to `sched_infov`, and `v` then - /// receives a `sched_info` record pointing at that new shared-data slot. - /// @param[in] v Variable to register. - /// @param[in] data Shared scheduling data to append for `v`. + void install_sched_data(variable v, sched_data data) ; - - /// @brief Installs scheduler state for `v` in the variable registry. - /// - /// Use this when `v` should reuse an existing `sched_data` slot described - /// by `info.sched_info_ref`, such as when registering an alias. - /// @param[in] v Variable to register. - /// @param[in] info Scheduler state to install for `v`. - /// @note Aborts if `v` is already registered. void install_sched_info(variable v, sched_info info) ; + + variableSet get_synonyms(variable v) const + { return get_sched_info(v).synonyms ; } - /// @brief Returns the synonym set recorded for `v`'s canonical scheduler - /// entry. - /// - /// This covers names linked through `synonym_variable()`. It does not - /// include alias or rotation relationships. - /// @param[in] v Variable whose synonym set is requested. - /// @note `v` must already be known to the scheduler; use - /// `try_get_synonyms()` for unknown-safe lookup. - /// @return The synonym set for `v`'s canonical scheduler entry. - variableSet get_synonyms(variable v) const { - return get_sched_info(v).synonyms ; - } - - /// @brief Returns the synonym set recorded for `v`'s canonical scheduler - /// entry. - /// @param[in] v Variable whose synonym set is requested. - /// @return `EMPTY` when `v` is not known to the scheduler. + // this version tries to get the synonyms for + // the passed in variable, if no info, then returns EMPTY variableSet try_get_synonyms(variable v) const { vmap_type::const_iterator mi = vmap.find(remove_synonym(v)) ; if(mi == vmap.end()) @@ -510,22 +224,10 @@ namespace Loci { return (mi->second).synonyms ; } - /// @brief Returns the alias group that shares `sched_data` with `v`. - /// - /// Alias relationships come from `alias_variable()` and are distinct from - /// synonym resolution. - /// @param[in] v Variable whose alias group is requested. - /// @note `v` must already be known to the scheduler; use - /// `try_get_aliases()` for unknown-safe lookup. - /// @return The alias group that shares `sched_data` with `v`. - variableSet get_aliases(variable v) const { - return get_sched_data(v).aliases ; - } + variableSet get_aliases(variable v) const + { return get_sched_data(v).aliases ; } - /// @brief Returns the alias group that shares `sched_data` with `v`. - /// @param[in] v Variable whose alias group is requested. - /// @return `EMPTY` when `v` is not known to the scheduler. - variableSet try_get_aliases(variable v) const { + variableSet try_get_aliases(variable v) const { vmap_type::const_iterator mi = vmap.find(remove_synonym(v)) ; if(mi == vmap.end()) return variableSet(EMPTY) ; @@ -533,23 +235,9 @@ namespace Loci { return sched_infov[(mi->second).sched_info_ref].aliases ; } - /// @brief Returns the anti-alias names recorded in `v`'s shared alias - /// metadata. - /// - /// These names are added when aliases are introduced and are stored in the - /// shared `sched_data` slot rather than in per-variable synonym state. - /// @param[in] v Variable whose anti-alias names are requested. - /// @note `v` must already be known to the scheduler; use - /// `try_get_antialiases()` for unknown-safe lookup. - /// @return The anti-alias names recorded for `v`. - variableSet get_antialiases(variable v) const { - return get_sched_data(v).antialiases ; - } + variableSet get_antialiases(variable v) const + { return get_sched_data(v).antialiases ; } - /// @brief Returns the anti-alias names recorded in `v`'s shared alias - /// metadata. - /// @param[in] v Variable whose anti-alias names are requested. - /// @return `EMPTY` when `v` is not known to the scheduler. variableSet try_get_antialiases(variable v) const { vmap_type::const_iterator mi = vmap.find(remove_synonym(v)) ; if(mi == vmap.end()) @@ -558,21 +246,9 @@ namespace Loci { return sched_infov[(mi->second).sched_info_ref].antialiases ; } - /// @brief Returns the loop-rotation group recorded for `v`. - /// - /// Rotation groups are registered by `set_variable_rotations()` so later - /// scheduling passes can treat those variables together. - /// @param[in] v Variable whose rotation group is requested. - /// @note `v` must already be known to the scheduler; use - /// `try_get_rotations()` for unknown-safe lookup. - /// @return The loop-rotation group recorded for `v`. - variableSet get_rotations(variable v) const { - return get_sched_data(v).rotations ; - } + variableSet get_rotations(variable v) const + { return get_sched_data(v).rotations ; } - /// @brief Returns the loop-rotation group recorded for `v`. - /// @param[in] v Variable whose rotation group is requested. - /// @return `EMPTY` when `v` is not known to the scheduler. variableSet try_get_rotations(variable v) const { vmap_type::const_iterator mi = vmap.find(remove_synonym(v)) ; if(mi == vmap.end()) @@ -580,151 +256,54 @@ namespace Loci { else return sched_infov[(mi->second).sched_info_ref].rotations ; } - - /// @brief Ensures that `v` is typed in both the scheduler and `facts`. - /// - /// This creates an intensional fact for `v` in `facts`. If the scheduler - /// is not already tracking `v`, it also installs a fresh `sched_data` - /// record seeded from `st`. Existing scheduler state for `v` is left in - /// place. - /// @param[in] v Variable being typed. - /// @param[in] st Store representation to associate with `v`. - /// @param[in,out] facts Fact database updated with the corresponding - /// intensional fact. + void set_variable_type(variable v, storeRepP st, fact_db &facts) ; - void set_variable_type(std::string vname,const storeRepP st, - fact_db &facts) { - set_variable_type(variable(vname),st, facts) ; - } - - void set_variable_type(variable v, store_instance &si, fact_db &facts) { - set_variable_type(v,si.Rep(), facts) ; - } - void set_variable_type(std::string vname, store_instance &si, - fact_db &facts) { - set_variable_type(variable(vname),si, facts) ; - } - - /// @brief Alternate overload set for callers that do not pass a - /// `fact_db`. - /// @param[in] v Variable being typed. - /// @param[in] st Store representation to associate with `v`. + void set_variable_type(std::string vname,const storeRepP st, fact_db &facts) + { set_variable_type(variable(vname),st, facts) ; } + + void set_variable_type(variable v, store_instance &si, fact_db &facts) + { set_variable_type(v,si.Rep(), facts) ; } + void set_variable_type(std::string vname, store_instance &si, fact_db &facts) + { set_variable_type(variable(vname),si, facts) ; } + void set_variable_type(variable v, storeRepP st) ; - void set_variable_type(std::string vname,const storeRepP st) { - set_variable_type(variable(vname),st) ; - } - - void set_variable_type(variable v, store_instance &si) { - set_variable_type(v,si.Rep()) ; - } - void set_variable_type(std::string vname, store_instance &si) { - set_variable_type(variable(vname),si) ; - } - - // Legacy `variable_is_fact_at()` overloads remain commented out because the - // scheduler no longer uses them. + void set_variable_type(std::string vname,const storeRepP st) + { set_variable_type(variable(vname),st) ; } + + void set_variable_type(variable v, store_instance &si) + { set_variable_type(v,si.Rep()) ; } + void set_variable_type(std::string vname, store_instance &si) + { set_variable_type(variable(vname),si) ; } + + /*! variable_is_fact_at() functions are never used, comment out*/ // void variable_is_fact_at(variable v,entitySet s, fact_db &facts) ; // void variable_is_fact_at(std::string vname, const entitySet s, fact_db &facts) // { variable_is_fact_at(variable(vname),s, facts) ; } - + // void variable_is_fact_at(variable v,entitySet s) ; // void variable_is_fact_at(std::string vname, const entitySet s) // { variable_is_fact_at(variable(vname),s) ; } - - /// @brief Stores `rot` as the complete rotation group for every variable in - /// the set. - /// - /// Each member must already be known to the scheduler. The group is written - /// into shared scheduling data so later loop-scheduling passes can recover - /// the same rotation set from any member. Aliases that share the same - /// `sched_data` record observe the same rotation metadata. - /// @param[in] rot Complete rotation group to record for each member. + void set_variable_rotations(variableSet rot) ; - - /// @brief Registers `alias` as a new variable that shares `v`'s - /// `sched_data`, and mirrors the relationship into `facts`. - /// - /// Aliases share type- and map-level scheduling metadata through the same - /// `sched_data` slot, but each alias keeps its own `sched_info` fields such - /// as existence, requests, and rule ownership. - /// @param[in] v Existing scheduler variable that provides the shared - /// `sched_data`. - /// @param[in] alias New alias name to register. - /// @param[in,out] facts Fact database updated to mirror the alias - /// relationship. - /// @note If exactly one of `v` and `alias` already exists, the existing - /// variable is used as the source regardless of argument order. + void alias_variable(variable v, variable alias, fact_db &facts) ; - void alias_variable(std::string vname, std::string alias, fact_db &facts) { - alias_variable(variable(vname),variable(alias), facts) ; - } - - /// @brief Registers `alias` as a new variable that shares `v`'s - /// `sched_data`. - /// - /// This is the scheduler-only form of `alias_variable(..., fact_db&)`. - /// @param[in] v Existing scheduler variable that provides the shared - /// `sched_data`. - /// @param[in] alias New alias name to register. + void alias_variable(std::string vname, std::string alias, fact_db &facts) + { alias_variable(variable(vname),variable(alias), facts) ; } + void alias_variable(variable v, variable alias) ; - void alias_variable(std::string vname, std::string alias) { - alias_variable(variable(vname),variable(alias)) ; - } - - /// @brief Registers `synonym` as an alternate name for canonical variable - /// `v`, and mirrors the relationship into `facts`. - /// - /// After registration, lookups for `synonym` resolve through - /// `remove_synonym()` to `v`, so the synonym shares the same `sched_info` - /// state instead of getting a separate scheduler record. - /// @param[in] v Canonical variable name. - /// @param[in] synonym Alternate name to resolve through `v`. - /// @param[in,out] facts Fact database updated to mirror the synonym - /// relationship. + void alias_variable(std::string vname, std::string alias) + { alias_variable(variable(vname),variable(alias)) ; } + void synonym_variable(variable v, variable synonym, fact_db &facts) ; - void synonym_variable(std::string vname, std::string synonym, - fact_db &facts) { - synonym_variable(variable(vname),variable(synonym), facts) ; - } - - /// @brief Registers `synonym` as an alternate name for canonical variable - /// `v`. - /// - /// This is the scheduler-only form of `synonym_variable(..., fact_db&)`. - /// @param[in] v Canonical variable name. - /// @param[in] synonym Alternate name to resolve through `v`. + void synonym_variable(std::string vname, std::string synonym, fact_db &facts) + { synonym_variable(variable(vname),variable(synonym), facts) ; } + void synonym_variable(variable v, variable synonym) ; - void synonym_variable(std::string vname, std::string synonym) { - synonym_variable(variable(vname),variable(synonym)) ; - } - - /// @brief Records or extends the subset of `v` produced by rule `f`. - /// @param[in] v Variable instance, including any priority decoration. - /// @param[in] f Rule that produces the entities in `x`. - /// @param[in] x Produced entities to merge into `f`'s existential record. - /// - /// Repeated calls for the same rule accumulate into one entry. If the new - /// entities overlap entities already owned by a different rule, variable - /// priority annotations are used to resolve ownership when possible. - /// Unresolved overlaps are reported as conflicts and mark the scheduler as - /// having detected errors. Calls with `EMPTY` still register `f` in the - /// existential map. + void synonym_variable(std::string vname, std::string synonym) + { synonym_variable(variable(vname),variable(synonym)) ; } + void set_existential_info(variable v,rule f,entitySet x) ; - - /// @brief Returns the rules currently present in `v`'s existential map. - /// - /// This reports registered rule entries, even if a rule's current entity - /// contribution is empty. - /// @param[in] v Variable whose existential producers are requested. - /// @return The rules currently present in `v`'s existential map. ruleSet get_existential_rules(variable v) ; - - /// @brief Returns `v`'s canonical `sched_info` after synonym resolution. - /// @param[in] v Variable whose scheduler state is requested. - /// @note Alias variables are not collapsed here; aliases keep separate - /// `sched_info` records. - /// @note Aborts if the canonical variable is not known to the scheduler. - /// @return The canonical read-only scheduler record for `v`. const sched_info & get_sched_info(variable v) const { vmap_type::const_iterator mi = vmap.find(remove_synonym(v)) ; if(mi == vmap.end()) { @@ -733,604 +312,222 @@ namespace Loci { } return mi->second ; } - - /// @brief Returns the shared `sched_data` record referenced by `v`'s - /// canonical scheduler entry. - /// - /// Variables in the same alias group share this record. - /// @param[in] v Variable whose shared scheduling data is requested. - /// @return The shared read-only scheduling data for `v`. - const sched_data & get_sched_data(variable v) const { - return sched_infov[get_sched_info(v).sched_info_ref] ; - } - - /// @brief Returns `v`'s canonical `sched_info` after synonym resolution. - /// @param[in] v Variable whose scheduler state is requested. - /// @note Alias variables are not collapsed here; aliases keep separate - /// `sched_info` records. - /// @note Unknown variables are routed through the scheduler's `EMPTY` - /// placeholder record instead of aborting. - /// @return The canonical mutable scheduler record for `v`. + const sched_data & get_sched_data(variable v) const + { return sched_infov[get_sched_info(v).sched_info_ref] ; } + sched_info & get_sched_info(variable v); + sched_data & get_sched_data(variable v) + { return sched_infov[get_sched_info(v).sched_info_ref] ; } - /// @brief Returns the shared `sched_data` record referenced by `v`'s - /// canonical scheduler entry. - /// - /// Variables in the same alias group share this record, so mutations affect - /// the whole alias group. - /// @param[in] v Variable whose shared scheduling data is requested. - /// @return The shared mutable scheduling data for `v`. - sched_data & get_sched_data(variable v) { - return sched_infov[get_sched_info(v).sched_info_ref] ; - } - - /// @brief Returns true when `v`'s shared scheduling data is backed by a map - /// representation. - /// - /// Alias variables report the same result because they share `sched_data`. - /// @param[in] v Variable to inspect. - /// @return True when `v` is backed by a map representation. bool is_a_Map(variable v) { return get_sched_data(v).ismap ; } - - /// @brief Returns the entities currently attributed to rule `f` for `v`. - /// - /// This queries `v`'s per-variable existential map, so synonyms resolve to - /// the same record while aliases keep separate rule-attribution state. - /// @param[in] v Variable whose existential map is queried. - /// @param[in] f Rule whose attributed entities are requested. - /// @return The stored existential subset for `f`, or `EMPTY` when `f` has - /// no entry for `v`. entitySet get_existential_info(variable v, rule f) { sched_info &finfo = get_sched_info(v) ; std::map::const_iterator mi ; mi = finfo.exist_map.find(f) ; if(mi!=finfo.exist_map.end()) { return mi->second.exists ; - } else + } else return EMPTY ; } - /// @brief Returns the accumulated existence currently known for `v`. - /// - /// This includes direct existence updates and entities recorded through - /// `set_existential_info()`. Because existence lives in `sched_info`, - /// synonyms share it while aliases keep separate existence sets. - /// @param[in] v Variable whose known existence is requested. - /// @return The accumulated existence set, or `~EMPTY` for time variables. entitySet variable_existence(variable v) ; - - /// @brief Adds `e` to the pending request set for `v`. - /// - /// Requests accumulate rather than replace the current set. Time variables - /// ignore request updates because they are treated as existing everywhere. - /// @param[in] v Variable whose request set is updated. - /// @param[in] e Requested entities to add. void variable_request(variable v, entitySet e) ; - /// @brief Clears pending requests and extra UNIT-rule requests for every - /// tracked `sched_info` entry. - /// - /// Synonyms reset together because they resolve to the same scheduler - /// entry, while aliases are cleared independently. - void clear_variable_request() { + void clear_variable_request(){ for(vmap_type::iterator mi = vmap.begin(); mi != vmap.end(); mi++){ mi->second.requested=EMPTY; mi->second.extra_unit_request = EMPTY; } } - // void reset_variable_request(variable v, entitySet e=EMPTY) ; - - /// @brief Replaces the distributed shadow set tracked for `v`. - /// - /// This communication-oriented set is used by later distributed scheduling - /// passes, especially output-mapped and reduction cases. Because shadow - /// lives in `sched_info`, synonyms share it while aliases keep separate - /// shadow sets. - /// @param[in] v Variable whose shadow set is replaced. - /// @param[in] e New shadow set to store. - void set_variable_shadow(variable v, entitySet e) { - get_sched_info(v).shadow = e ; - } - - /// @brief Adds entities to the distributed shadow set tracked for `v`. - /// @param[in] v Variable whose shadow set is updated. - /// @param[in] e Shadow entities to add. - void variable_shadow(variable v, entitySet e) { - get_sched_info(v).shadow += e ; - } - - /// @brief Returns the distributed shadow set currently tracked for `v`. - /// - /// Shadow state lives in `sched_info`, so synonyms share it while aliases - /// keep separate shadow sets. - /// @param[in] v Variable whose shadow set is requested. - /// @return The distributed shadow set currently recorded for `v`. - entitySet get_variable_shadow(variable v) const { - return get_sched_info(v).shadow ; - } - - /// @brief Returns the requested portion of `v` that overlaps rule `f`'s - /// recorded existential contribution. - /// @param[in] f Rule whose requested overlap is requested. - /// @param[in] v Variable whose request set is queried. - /// @return The intersection of `f`'s existential contribution and the - /// current request set for `v`, or `EMPTY` when `f` has no existential - /// entry for `v`. + void set_variable_shadow(variable v, entitySet e) + { get_sched_info(v).shadow = e ; } + void variable_shadow(variable v, entitySet e) + { get_sched_info(v).shadow += e ; } + entitySet get_variable_shadow(variable v) const + { return get_sched_info(v).shadow ; } entitySet get_variable_request(rule f, variable v) ; - - /// @brief Returns the full pending request set currently stored for `v`. - /// - /// Requests live in `sched_info`, so synonyms share them while aliases keep - /// separate request sets. - /// @param[in] v Variable whose request set is requested. - /// @return The full pending request set for `v`. - entitySet get_variable_requests(variable v) const { - return get_sched_info(v).requested ; - } - - /// @brief Returns every variable name currently registered with the - /// scheduler. - /// - /// This is the scheduler's `all_vars` registry, so it includes canonical - /// variables plus any aliases or synonyms that were added later. - /// @return Every variable name currently registered with the scheduler. + entitySet get_variable_requests(variable v) const + { return get_sched_info(v).requested ; } variableSet get_typed_variables() const { return all_vars ; } - - /// @brief Returns the memoized image of `e` through map variable `v`. - /// - /// The first lookup delegates to the stored `MapRep`; later lookups for - /// the same entity set reuse the cached result. Because this cache lives in - /// `sched_data`, aliases share it, and synonyms resolve to the same cache - /// through their canonical scheduler entry. - /// @param[in] v Map variable to query. - /// @param[in] e Domain entities whose image is requested. - /// @return `EMPTY` when `v` is not a map variable. entitySet image(variable v, entitySet e) ; - - /// @brief Returns the memoized preimage information of `e` through map - /// variable `v`. - /// - /// The stored pair is whatever the underlying `MapRep::preimage()` - /// produces for `e`: for simple maps the two sets match, while multi-valued - /// maps use the pair to distinguish fully-contained from merely touching - /// source entities. Because this cache lives in `sched_data`, aliases share - /// it, and synonyms resolve to the same cache through their canonical - /// scheduler entry. - /// @param[in] v Map variable to query. - /// @param[in] e Codomain entities whose preimage is requested. - /// @return The cached `MapRep::preimage()` result, or `(EMPTY, EMPTY)` when - /// `v` is not a map variable. std::pair preimage(variable v, entitySet e) ; - /// @brief Replaces the raw duplication-policy bitmask stored for `v`. - /// - /// This is the low-level setter behind `add_policy()`. Because policy - /// lives in `sched_info`, synonyms share it while aliases keep separate - /// policy state. The mask itself is only an input to later duplication - /// selection; it does not set `duplicate_variable`. - /// @param[in] v Variable whose raw policy mask is replaced. - /// @param[in] p New raw policy mask. void set_policy(variable v, unsigned int p) { get_sched_info(v).policy = p;} - - /// @brief Returns the raw duplication-policy bitmask stored for `v`. - /// - /// Use `duplicate_policy`, `add_policy()`, and `is_policy()` when you want - /// the symbolic interpretation of the bits. - /// @param[in] v Variable whose policy mask is requested. - /// @return The raw duplication-policy bitmask stored for `v`. unsigned int get_policy(variable v) { return get_sched_info(v).policy; } - - /// @brief Adds duplication policy `p` to `v`'s current policy mask. - /// - /// This accumulates policy bits rather than replacing the existing mask. - /// The update is applied across `v`'s synonym set, but not across aliases. - /// Later duplication analysis interprets that mask and records the final - /// choice with `set_duplicate_variable()`. - /// @param[in] v Variable whose policy mask is updated. - /// @param[in] p Policy bit to add. - void add_policy(variable v, duplicate_policy p) ; - - /// @brief Tests whether duplication policy `p` is active for `v`. - /// - /// This checks the symbolic `duplicate_policy` bit in `v`'s raw policy - /// mask. It does not mean duplication has already been selected. - /// @param[in] v Variable whose policy mask is queried. - /// @param[in] p Policy bit to test. - /// @return True when policy `p` is active for `v`. - bool is_policy(variable v, duplicate_policy p) ; - - /// @brief Returns true when duplication has been selected for `v`. - /// - /// This is the scheduler's stored duplication decision, not just a policy - /// recommendation. Later request-minimization and communication code uses - /// this flag to switch from owner-based requests to duplicated local - /// computation. - /// @param[in] v Variable whose duplication decision is queried. - /// @return True when duplication has been selected for `v`. + + void add_policy(variable v, duplicate_policy p); + + bool is_policy(variable v, duplicate_policy p); + bool is_duplicate_variable(variable v) { return get_sched_info(v).duplicate_variable; } - - /// @brief Sets the duplicate-variable flag for `v`'s synonym set. - /// - /// This records the scheduler's final duplication choice for those names. - /// Aliases keep their own duplicate-variable flag because they have - /// separate `sched_info` entries. - /// @param[in] v Variable whose synonym set is updated. - /// @param[in] p New duplicate-variable flag. + void set_duplicate_variable(variable v, bool p) { - variableSet syns = get_synonyms(v) ; + variableSet syns = get_synonyms(v); for(variableSet::const_iterator vi = syns.begin(); vi != syns.end(); vi++) - get_sched_info(*vi).duplicate_variable = p ; - } + get_sched_info(*vi).duplicate_variable = p; + } - /// @brief Returns the broader duplicate-work compute set recorded for rule - /// `f` and variable `v`. - /// - /// This per-rule set is used when `v` has been marked as a duplicate - /// variable. Unlike `get_my_proc_able_entities()`, it is recorded before - /// the `my_entities` restriction is applied, so it may include non-owned - /// entities this processor can recompute under work duplication. Because it - /// lives in `sched_info`, synonyms share it while aliases keep separate - /// entries. - /// @param[in] v Variable whose duplicate-work compute set is requested. - /// @param[in] f Rule whose compute set is requested. - /// @return The duplication-expanded compute set recorded for (`v`, `f`). entitySet get_proc_able_entities(variable v, rule f) { - sched_info &finfo = get_sched_info(v) ; - std::map::const_iterator mi ; - mi = finfo.proc_able_map.find(f) ; + sched_info &finfo = get_sched_info(v); + std::map::const_iterator mi; + mi = finfo.proc_able_map.find(f); if(mi != finfo.proc_able_map.end()) - return mi->second ; + return mi->second; else - return EMPTY ; + return EMPTY; } - /// @brief Adds entities to the duplicate-work compute set recorded for rule - /// `f` and variable `v`. - /// - /// Repeated calls accumulate into the cached set rather than replacing it. - /// @param[in] v Variable whose duplicate-work compute set is updated. - /// @param[in] f Rule whose compute set is updated. - /// @param[in] x Entities to add. void set_proc_able_entities(variable v, rule f, entitySet x) { - sched_info &finfo = get_sched_info(v) ; - finfo.proc_able_map[f] += x ; + sched_info &finfo = get_sched_info(v); + finfo.proc_able_map[f] += x; } - /// @brief Returns the processor-local compute set recorded for rule `f` - /// and variable `v`. - /// - /// This narrower per-rule set is recorded after restricting the rule's - /// context to this processor's local entities. Later request processing - /// uses it when `v` is not being duplicated. - /// @param[in] v Variable whose local compute set is requested. - /// @param[in] f Rule whose local compute set is requested. - /// @return The `my_entities`-restricted compute set for (`v`, `f`). entitySet get_my_proc_able_entities(variable v, rule f) { - sched_info &finfo = get_sched_info(v) ; - std::map::const_iterator mi ; - mi = finfo.my_proc_able_map.find(f) ; + sched_info &finfo = get_sched_info(v); + std::map::const_iterator mi; + mi = finfo.my_proc_able_map.find(f); if(mi != finfo.my_proc_able_map.end()) - return mi->second ; + return mi->second; else - return EMPTY ; + return EMPTY; } - /// @brief Adds entities to the processor-local compute set recorded for - /// rule `f` and variable `v`. - /// - /// Repeated calls accumulate into the cached set rather than replacing it. - /// @param[in] v Variable whose local compute set is updated. - /// @param[in] f Rule whose local compute set is updated. - /// @param[in] x Entities to add. void set_my_proc_able_entities(variable v, rule f, entitySet x) { - sched_info &finfo = get_sched_info(v) ; - finfo.my_proc_able_map[f] += x ; + sched_info &finfo = get_sched_info(v); + finfo.my_proc_able_map[f] += x; } - /// @brief Returns the reduction-specific computable summary recorded for - /// `v`. - /// - /// This is a later summary used by duplicated reduction handling, not a - /// per-rule map like `get_proc_able_entities()`. - /// @param[in] v Reduction variable whose summary is requested. - /// @return The reduction-specific computable summary for `v`. entitySet get_reduce_proc_able_entities(variable v) { - sched_info &finfo = get_sched_info(v) ; - return(finfo.reduce_proc_able_entities) ; + sched_info &finfo = get_sched_info(v); + return(finfo.reduce_proc_able_entities); } - /// @brief Replaces the reduction-specific computable summary recorded for - /// `v`. - /// @param[in] v Reduction variable whose summary is replaced. - /// @param[in] x New reduction-specific computable summary. void set_reduce_proc_able_entities(variable v, entitySet x) { - sched_info &finfo = get_sched_info(v) ; - finfo.reduce_proc_able_entities = x ; + sched_info &finfo = get_sched_info(v); + finfo.reduce_proc_able_entities = x; } - /// @brief Returns true when duplicated reduction handling for `v` must use - /// the output-mapped case. - /// - /// This flag is set during reduction analysis when at least one producer of - /// `v` maps its output. In that case later duplicated-reduction logic uses - /// the conservative `reduce_filter` path instead of widening by - /// `get_reduce_proc_able_entities(v)`. - /// @param[in] v Reduction variable to query. - /// @return True when `v` must use the output-mapped reduction path. bool is_reduction_outputmap(variable v) { - sched_info &finfo = get_sched_info(v) ; - return(finfo.reduction_outputmap) ; + sched_info &finfo = get_sched_info(v); + return(finfo.reduction_outputmap); } - /// @brief Records whether duplicated reduction handling for `v` should use - /// the output-mapped case. - /// - /// This value is computed by the reduction-analysis pass. - /// @param[in] v Reduction variable to update. - /// @param[in] x New reduction-output-mapping flag. void set_reduction_outputmap(variable v, bool x) { - sched_info &finfo = get_sched_info(v) ; - finfo.reduction_outputmap = x ; + sched_info &finfo = get_sched_info(v); + finfo.reduction_outputmap = x; } - /// @brief Adds extra UNIT-rule requests for `v`. - /// - /// This accumulates request entities that mapped apply/reduction handling - /// needs UNIT rules to cover in addition to the normal request set. These - /// extras are folded in only for UNIT-rule request processing and are - /// cleared by `clear_variable_request()`. - /// @param[in] v Variable whose extra UNIT requests are updated. - /// @param[in] x Extra UNIT-rule requests to add. void add_extra_unit_request(variable v, entitySet x) { - sched_info &finfo = get_sched_info(v) ; - finfo.extra_unit_request += x ; + sched_info &finfo = get_sched_info(v); + finfo.extra_unit_request += x; } // void reset_extra_unit_request(variable v, entitySet x=EMPTY) { -// sched_info &finfo = get_sched_info(v) ; -// finfo.extra_unit_request = x ; +// sched_info &finfo = get_sched_info(v); +// finfo.extra_unit_request = x; // } - /// @brief Returns the extra UNIT-rule requests currently recorded for `v`. - /// - /// These requests are only added into UNIT-rule processing, not every rule - /// request for `v`. - /// @param[in] v Variable whose extra UNIT requests are requested. - /// @return The extra UNIT-rule requests currently recorded for `v`. entitySet get_extra_unit_request(variable v) { - sched_info &finfo = get_sched_info(v) ; - return(finfo.extra_unit_request) ; + sched_info &finfo = get_sched_info(v); + return(finfo.extra_unit_request); } + - /// @brief Adds known existence for `v` without recording a producing rule. - /// - /// Despite the name, this extends the current existence set rather than - /// replacing it, and it does not create an `exist_map` producer entry. Use - /// it when existence is known directly, especially for map-related cases - /// that do not have a scheduler rule to attribute. - /// @param[in] v Variable whose known existence is extended. - /// @param[in] x Directly known existence to add. + //For map variables, it is necessary to change existence without + //supplying any rules because Loci does not have rules + //that create maps void set_variable_existence(variable v, entitySet x) { - sched_info &finfo = get_sched_info(v) ; - finfo.existence += x ; + sched_info &finfo = get_sched_info(v); + finfo.existence += x; } + void add_model_info(double comm_ts, double comm_tw, const std::map > &comp_info); - /// @brief Loads the timing models used by `MODEL_BASED` duplication. - /// - /// `comm_ts` and `comm_tw` populate the global communication model used to - /// estimate communication as startup plus size-based cost. `comp_info` - /// populates the per-rule computation models used to estimate rule work as - /// a fixed term plus a context-size term. - /// @param[in] comm_ts Fixed communication startup coefficient. - /// @param[in] comm_tw Variable communication coefficient. - /// @param[in] comp_info Per-rule computation-model coefficients. - void add_model_info(double comm_ts, double comm_tw, const std::map > &comp_info) ; - - // These helpers accumulate model estimates computed while comparing the - // original schedule against the duplicated-work alternative for `v`. - // "original" means the owner-communication schedule before duplication is - // selected, while "duplication" means the alternative that recomputes - // locally available entities and communicates only the remaining requests. - /// @brief Adds to `v`'s original-schedule computation estimate. - /// @param[in] v Variable whose estimate is updated. - /// @param[in] add Computation time to add. void add_original_computation_time(variable v, double add) { - sched_info &finfo = get_sched_info(v) ; - finfo.original_computation_time += add ; + sched_info &finfo = get_sched_info(v); + finfo.original_computation_time += add; } - - /// @brief Adds to `v`'s duplicated-schedule computation estimate. - /// @param[in] v Variable whose estimate is updated. - /// @param[in] add Computation time to add. + void add_duplication_computation_time(variable v, double add) { - sched_info &finfo = get_sched_info(v) ; - finfo.duplication_computation_time += add ; + sched_info &finfo = get_sched_info(v); + finfo.duplication_computation_time += add; } - - /// @brief Adds to `v`'s original-schedule communication estimate. - /// @param[in] v Variable whose estimate is updated. - /// @param[in] add Communication time to add. + void add_original_communication_time(variable v, double add) { - sched_info &finfo = get_sched_info(v) ; - finfo.original_communication_time += add ; + sched_info &finfo = get_sched_info(v); + finfo.original_communication_time += add; } - /// @brief Adds to `v`'s duplicated-schedule communication estimate. - /// @param[in] v Variable whose estimate is updated. - /// @param[in] add Communication time to add. void add_duplication_communication_time(variable v, double add) { - sched_info &finfo = get_sched_info(v) ; - finfo.duplication_communication_time += add ; + sched_info &finfo = get_sched_info(v); + finfo.duplication_communication_time += add; } - /// @brief Returns `v`'s accumulated original-schedule computation estimate. - /// @param[in] v Variable whose estimate is requested. - /// @return The accumulated original-schedule computation estimate for `v`. double get_precalculated_original_computation_time(variable v) { - sched_info &finfo = get_sched_info(v) ; - return(finfo.original_computation_time) ; + sched_info &finfo = get_sched_info(v); + return(finfo.original_computation_time); } - /// @brief Returns `v`'s accumulated duplicated-schedule computation - /// estimate. - /// @param[in] v Variable whose estimate is requested. - /// @return The accumulated duplicated-schedule computation estimate for `v`. double get_precalculated_duplication_computation_time(variable v) { - sched_info &finfo = get_sched_info(v) ; - return(finfo.duplication_computation_time) ; + sched_info &finfo = get_sched_info(v); + return(finfo.duplication_computation_time); } - /// @brief Returns `v`'s accumulated original-schedule communication - /// estimate. - /// @param[in] v Variable whose estimate is requested. - /// @return The accumulated original-schedule communication estimate for `v`. double get_precalculated_original_communication_time(variable v) { - sched_info &finfo = get_sched_info(v) ; - return(finfo.original_communication_time) ; + sched_info &finfo = get_sched_info(v); + return(finfo.original_communication_time); } - /// @brief Returns `v`'s accumulated duplicated-schedule communication - /// estimate. - /// @param[in] v Variable whose estimate is requested. - /// @return The accumulated duplicated-schedule communication estimate for `v`. double get_precalculated_duplication_communication_time(variable v) { - sched_info &finfo = get_sched_info(v) ; - return(finfo.duplication_communication_time) ; + sched_info &finfo = get_sched_info(v); + return(finfo.duplication_communication_time); } - /// @brief Returns the computation timing model for rule `r`, or an invalid - /// sentinel model. - /// - /// Missing rules return `model(-100000, -100000)`. Check the coefficients - /// with `model::is_valid_val()` before using them in timing math. - /// @param[in] r Rule whose computation model is requested. - /// @return The computation timing model for `r`, or an invalid sentinel. model get_comp_model(rule r) const { - std::map::const_iterator mi = comp_model.find(r) ; + std::map::const_iterator mi = comp_model.find(r); if(mi != comp_model.end()) - return mi->second ; + return mi->second; else { - return model(-100000, -100000) ; + return model(-100000, -100000); } } + + model get_comm_model() const { return comm_model; } - /// @brief Returns the global communication timing model installed by - /// `add_model_info()`. - /// - /// Before model data is loaded, the coefficients remain invalid sentinel - /// values. - /// @return The global communication timing model. - model get_comm_model() const { return comm_model ; } - - /// @brief Returns the duplicate-analysis worklist for future target sets. - /// - /// This is not the final duplication decision; use - /// `is_duplicate_variable()` for that. The worklist is carried forward for - /// variables whose duplication should be reconsidered when they appear in a - /// later target set. - /// @return The current duplicate-analysis worklist. - variableSet get_possible_duplicate_vars() const { return possible_duplicate_vars ; } - - /// @brief Adds variables and their synonyms to the future duplicate-analysis - /// worklist. - /// - /// The set is append-only. Synonyms are inserted for each variable, while - /// aliases are not expanded here. - /// @param[in] vars Variables to add to the future duplicate-analysis - /// worklist. + variableSet get_possible_duplicate_vars() const { return possible_duplicate_vars; } void add_possible_duplicate_vars(variableSet vars) { for(variableSet::const_iterator vi = vars.begin(); vi != vars.end(); vi++) { - variableSet syns = get_synonyms(*vi) ; - possible_duplicate_vars += syns ; + variableSet syns = get_synonyms(*vi); + possible_duplicate_vars += syns; } } - - /// @brief Writes a readable summary of the scheduler's current existential - /// state. - /// - /// The summary includes each tracked variable's container kind, synonym - /// set, known existence, and current request set. - /// @param[in] facts Fact database used to identify each variable's - /// container kind. - /// @param[in,out] s Output stream that receives the summary text. - /// @return `s`. + std::ostream &print_summary(fact_db &facts, std::ostream &s) ; - /// @brief Returns cached send-entity sets for variables in `eset` whose - /// existential producers map output entities. - /// - /// Only variables with at least one output-mapped producer are returned. - /// Missing cache entries are skipped with a warning to `debugout`. - /// @param[in] eset Variables whose send-entity caches are requested. - /// @param[in] e Phase-specific send-entity cache to query. - /// @return Cached send-entity sets for the applicable variables in `eset`. - std::vector > get_send_entities(variableSet eset, send_entities_type e) ; - - /// @brief Stores send-entity cache entries for a specific scheduler phase. - /// - /// Existing entries are overwritten, with a warning when the same variable - /// is written more than once for that phase. - /// @param[in] evec Send-entity entries to store. - /// @param[in] e Phase-specific send-entity cache to update. - void update_send_entities( const std::vector >& evec, send_entities_type e) ; - - /// @brief Returns one of the cached communication lists for variables in - /// `eset`. - /// - /// Retrieval filters the cache to the requested variables, then repacks the - /// result so all sends appear first in processor order, followed by all - /// receives in processor order. - /// @param[in] eset Variables whose cached communication records are - /// requested. - /// @param[in] facts Fact database associated with the schedule. - /// @param[in] e Phase-specific communication cache to query. - /// @return The filtered and reordered communication list. - std::list get_comm_info_list(variableSet eset, fact_db& facts, list_type e) const ; + std::vector > get_send_entities(variableSet eset, send_entities_type e); + void update_send_entities( const std::vector >& evec, send_entities_type e); + std::list get_comm_info_list(variableSet eset, fact_db& facts, list_type e) const; + void update_comm_info_list(const std::list& elist, list_type e); - /// @brief Appends communication records into one of the phase-specific - /// communication caches. - /// - /// Records are stored per variable and are not cleared or deduplicated by - /// this helper. - /// @param[in] elist Communication records to append. - /// @param[in] e Phase-specific communication cache to update. - void update_comm_info_list(const std::list& elist, list_type e) ; - - /// @brief Returns the cached execution sequence for `r`. - /// @param[in] r Rule whose cached execution sequence is requested. - /// @return The cached sequence, or `EMPTY` when no sequence has been - /// stored. - /// - /// Reading before the sequence is populated emits a warning to `debugout`. - entitySet get_exec_seq(rule r) { - entitySet re = EMPTY ; - std::map::const_iterator mi = exec_seq_map.find(r) ; + entitySet get_exec_seq(rule r){ + entitySet re = EMPTY; + std::map::const_iterator mi = exec_seq_map.find(r); if(mi != exec_seq_map.end()){ - re += mi->second ; + re += mi->second; }else{ - debugout << "WARNING: exec_seq_map is read before write at rule " - << r << endl ; + debugout<<"WARNING: exec_seq_map is read before write at rule " << r << endl; } - return re ; + return re; } - - /// @brief Stores the execution sequence for `r` the first time it is seen. - /// - /// Later writes for the same rule are ignored and reported as warnings to - /// `debugout`. - /// @param[in] r Rule whose execution sequence is being cached. - /// @param[in] eset Execution sequence to cache for `r`. - void update_exec_seq(rule r, entitySet eset) { - std::map::const_iterator mi = exec_seq_map.find(r) ; - if(mi == exec_seq_map.end()) { - exec_seq_map[r] = eset ; + + void update_exec_seq(rule r, entitySet eset){ + std::map::const_iterator mi = exec_seq_map.find(r); + if(mi == exec_seq_map.end()){ + exec_seq_map[r] = eset; }else{ - debugout << "WARNING: exec_seq_map is written more than once at rule " - << r << endl ; + debugout<<"WARNING: exec_seq_map is written more than once at rule " << r << endl; } } } ; From b48bd60fb89b262f7e9282f62f9cc0659072a9c6 Mon Sep 17 00:00:00 2001 From: Christopher Neal Date: Thu, 14 May 2026 02:09:24 -0400 Subject: [PATCH 3/4] Improve sched_db quick tests --- quickTest/SystemTests/test_sched_db.cc | 156 +++++++------------------ 1 file changed, 43 insertions(+), 113 deletions(-) diff --git a/quickTest/SystemTests/test_sched_db.cc b/quickTest/SystemTests/test_sched_db.cc index 16fdc7c2..819409aa 100644 --- a/quickTest/SystemTests/test_sched_db.cc +++ b/quickTest/SystemTests/test_sched_db.cc @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -14,19 +15,11 @@ using namespace Loci; namespace { -/// @brief Returns an inclusive `entitySet` interval `[first, last]`. -/// @param[in] first First entity in the interval. -/// @param[in] last Last entity in the interval. -/// @return An `entitySet` spanning the requested inclusive interval. +// Keep scheduler fixtures explicit without repeating store/map allocation loops. entitySet make_range(int first, int last) { return entitySet(interval(first, last)); } -/// @brief Allocates `values` on `domain` and fills it from `entries`. -/// @param[in,out] values Store to allocate and populate. -/// @param[in] domain Domain to allocate in the store. -/// @param[in] entries Values copied into the store in domain iteration order. -/// @throws std::logic_error When `entries.size()` does not match `domain.size()`. void assign_store_values(store &values, const entitySet &domain, const std::vector &entries) { if(static_cast(domain.size()) != entries.size()) { @@ -41,12 +34,6 @@ void assign_store_values(store &values, const entitySet &domain, } } -/// @brief Allocates `mapping` on `domain` and fills it from `entries`. -/// @param[in,out] mapping Map to allocate and populate. -/// @param[in] domain Domain to allocate in the map. -/// @param[in] entries Target entities copied into the map in domain iteration -/// order. -/// @throws std::logic_error When `entries.size()` does not match `domain.size()`. void assign_map_values(Map &mapping, const entitySet &domain, const std::vector &entries) { if(static_cast(domain.size()) != entries.size()) { @@ -61,18 +48,10 @@ void assign_map_values(Map &mapping, const entitySet &domain, } } -/// @brief Returns true when `rep` wraps a non-null representation pointer. -/// @param[in] rep Store representation handle to inspect. -/// @return True when `rep` is non-null. bool has_rep(const storeRepP &rep) { return rep != static_cast(0); } -/// @brief Returns true when both handles refer to the same store representation. -/// @param[in] lhs First store representation handle. -/// @param[in] rhs Second store representation handle. -/// @return True when both handles are non-null and share the same underlying -/// representation pointer. bool same_rep(const storeRepP &lhs, const storeRepP &rhs) { if(!has_rep(lhs) || !has_rep(rhs)) { return false; @@ -80,11 +59,6 @@ bool same_rep(const storeRepP &lhs, const storeRepP &rhs) { return lhs.operator->() == rhs.operator->(); } -/// @brief Builds a rule descriptor string that `rule(...)` can parse. -/// @param[in] source_expr Source portion of the rule signature. -/// @param[in] target_expr Target portion of the rule signature. -/// @param[in] qualifier Qualifier portion of the rule signature. -/// @return A `rule` constructed from the assembled signature. rule make_rule(const std::string &source_expr, const std::string &target_expr, const std::string &qualifier) { std::ostringstream signature; @@ -93,9 +67,6 @@ rule make_rule(const std::string &source_expr, const std::string &target_expr, return rule(signature.str()); } -/// @brief Collects `vars` into a `variableSet`. -/// @param[in] vars Variables to insert. -/// @return A `variableSet` containing every variable from `vars`. variableSet make_var_set(const std::vector &vars) { variableSet set; for(size_t i = 0; i < vars.size(); ++i) { @@ -104,9 +75,6 @@ variableSet make_var_set(const std::vector &vars) { return set; } -/// @brief Copies a `std::list` into an indexable vector. -/// @param[in] entries Communication records to copy. -/// @return A vector containing the same records in the same order. std::vector to_vector(const std::list &entries) { return std::vector(entries.begin(), entries.end()); } @@ -117,20 +85,13 @@ TEST_CASE("sched_db constructor mirrors fact_db variables and existence") { fact_db facts; store cells; - // Seed one typed fact so the constructor has scheduler state to mirror. assign_store_values(cells, make_range(4, 6), {10, 20, 30}); facts.create_fact("cells", cells); sched_db sched(facts); - - // Verify the constructor registers the variable and its self-alias/synonym - // bookkeeping. CHECK(sched.get_typed_variables().inSet(variable("cells"))); CHECK(sched.get_aliases(variable("cells")).inSet(variable("cells"))); CHECK(sched.get_synonyms(variable("cells")).inSet(variable("cells"))); - - // Verify the mirrored fact domain becomes known existence and leaves the - // remaining scheduler state empty/defaulted. CHECK(sched.variable_existence(variable("cells")) == make_range(4, 6)); CHECK_FALSE(sched.is_a_Map(variable("cells"))); CHECK(sched.get_variable_requests(variable("cells")) == EMPTY); @@ -141,19 +102,13 @@ TEST_CASE("set_variable_type installs intensional facts and empty schedule state sched_db sched(facts); store derived; - // Build a store representation that will be installed as an intensional fact. assign_store_values(derived, make_range(10, 11), {3, 5}); sched.set_variable_type(variable("derived"), derived.Rep(), facts); - - // Verify the new variable is tracked by both sched_db and fact_db, and that - // sched_db starts it with no derived existence yet. CHECK(sched.get_typed_variables().inSet(variable("derived"))); CHECK(facts.get_intensional_facts().inSet(variable("derived"))); CHECK_FALSE(facts.get_extensional_facts().inSet(variable("derived"))); CHECK(sched.variable_existence(variable("derived")) == EMPTY); - - // Verify the installed fact points at the expected store contents. storeRepP rep = facts.get_variable("derived"); REQUIRE(has_rep(rep)); store fetched(rep); @@ -162,6 +117,27 @@ TEST_CASE("set_variable_type installs intensional facts and empty schedule state CHECK(fetched[11] == 5); } +TEST_CASE("set_variable_type preserves map metadata for intensional variables") { + fact_db facts; + sched_db sched(facts); + Map derived_map; + assign_map_values(derived_map, make_range(1, 3), {10, 11, 11}); + + sched.set_variable_type(variable("derived_map"), derived_map.Rep(), facts); + + CHECK(sched.get_typed_variables().inSet(variable("derived_map"))); + CHECK(facts.get_intensional_facts().inSet(variable("derived_map"))); + CHECK(sched.variable_existence(variable("derived_map")) == EMPTY); + CHECK(sched.is_a_Map(variable("derived_map"))); + CHECK(sched.image(variable("derived_map"), make_range(1, 3)) == + make_range(10, 11)); + + std::pair preimage = + sched.preimage(variable("derived_map"), make_range(11, 11)); + CHECK(preimage.first == make_range(2, 3)); + CHECK(preimage.second == make_range(2, 3)); +} + TEST_CASE("alias_variable keeps alias bookkeeping in sync with fact_db") { fact_db facts; store cells; @@ -172,17 +148,12 @@ TEST_CASE("alias_variable keeps alias bookkeeping in sync with fact_db") { sched.alias_variable(variable("cells"), variable("cells_alias"), facts); variableSet aliases = sched.get_aliases(variable("cells")); - - // Verify sched_db records both names in the shared alias metadata. CHECK(aliases.inSet(variable("cells"))); CHECK(aliases.inSet(variable("cells_alias"))); CHECK(sched.get_aliases(variable("cells_alias")) == aliases); CHECK(sched.get_antialiases(variable("cells_alias")) .inSet(variable("cells"))); CHECK(sched.get_typed_variables().inSet(variable("cells_alias"))); - - // Verify fact_db mirrors the alias relationship onto the same store - // representation. CHECK(same_rep(facts.get_variable("cells"), facts.get_variable("cells_alias"))); } @@ -196,9 +167,6 @@ TEST_CASE("alias_variable accepts either argument order when one name already ex sched.alias_variable(variable("cells_alias"), variable("cells")); variableSet aliases = sched.get_aliases(variable("cells")); - - // Verify the helper resolves the existing variable as the alias source rather - // than treating the reversed argument order as an error. CHECK_FALSE(sched.errors_found()); CHECK(aliases.inSet(variable("cells"))); CHECK(aliases.inSet(variable("cells_alias"))); @@ -216,14 +184,9 @@ TEST_CASE("synonym_variable records canonical lookup behavior") { sched_db sched(facts); sched.synonym_variable(variable("cells"), variable("cells_shadow"), facts); - - // Verify the synonym resolves back to the canonical scheduler name. CHECK(sched.remove_synonym(variable("cells_shadow")) == variable("cells")); variableSet synonyms = sched.get_synonyms(variable("cells")); - - // Verify the synonym set and fact_db both treat the two names as one logical - // variable. CHECK(synonyms.inSet(variable("cells"))); CHECK(synonyms.inSet(variable("cells_shadow"))); CHECK(sched.get_typed_variables().inSet(variable("cells_shadow"))); @@ -231,6 +194,26 @@ TEST_CASE("synonym_variable records canonical lookup behavior") { same_rep(facts.get_variable("cells"), facts.get_variable("cells_shadow"))); } +TEST_CASE("sched-only synonym_variable reports duplicate existing names" * + doctest::may_fail()) { + fact_db facts; + store cells; + store faces; + assign_store_values(cells, make_range(1, 2), {1, 2}); + assign_store_values(faces, make_range(3, 4), {3, 4}); + facts.create_fact("cells", cells); + facts.create_fact("faces", faces); + + sched_db sched(facts); + sched.clear_errors(); + + // The fact_db overload catches this; the sched-only overload currently logs + // the error without setting the sticky scheduler error flag. + sched.synonym_variable(variable("cells"), variable("faces")); + + CHECK(sched.errors_found()); +} + TEST_CASE("synonyms resolve requests and direct existence through the canonical variable") { fact_db facts; store cells; @@ -244,8 +227,6 @@ TEST_CASE("synonyms resolve requests and direct existence through the canonical entitySet expected_existence = make_range(2, 4); expected_existence += make_range(20, 21); - - // Verify both names resolve through the same canonical scheduler entry. CHECK(sched.remove_synonym(variable("cells_shadow")) == variable("cells")); CHECK(sched.variable_existence(variable("cells")) == expected_existence); CHECK(sched.variable_existence(variable("cells_shadow")) == expected_existence); @@ -266,12 +247,8 @@ TEST_CASE("rotation groups are recorded and try_get helpers stay safe for unknow sched_db sched(facts); variableSet rotations = make_var_set({variable("cells"), variable("faces")}); sched.set_variable_rotations(rotations); - - // Verify every member of the rotation group sees the full recorded set. CHECK(sched.get_rotations(variable("cells")) == rotations); CHECK(sched.get_rotations(variable("faces")) == rotations); - - // Verify the unknown-safe accessors return empty results instead of aborting. CHECK(sched.try_get_synonyms(variable("ghost")) == variableSet(EMPTY)); CHECK(sched.try_get_aliases(variable("ghost")) == variableSet(EMPTY)); CHECK(sched.try_get_antialiases(variable("ghost")) == variableSet(EMPTY)); @@ -288,14 +265,9 @@ TEST_CASE("aliases share scheduling data but keep separate existence and request sched.alias_variable(variable("cells"), variable("cells_alias"), facts); sched.set_variable_existence(variable("cells"), make_range(30, 31)); sched.variable_request(variable("cells"), make_range(31, 32)); - - // Verify the alias still participates in the shared alias group. CHECK(sched.get_aliases(variable("cells_alias")).inSet(variable("cells"))); CHECK(sched.get_aliases(variable("cells_alias")) .inSet(variable("cells_alias"))); - - // Verify existence and requests remain separate because aliases keep distinct - // sched_info records. CHECK(sched.variable_existence(variable("cells_alias")) == EMPTY); CHECK(sched.get_variable_requests(variable("cells_alias")) == EMPTY); } @@ -334,21 +306,14 @@ TEST_CASE("requests are intersected with rule existence and can be cleared") { sched.set_existential_info(variable("cells"), producer, make_range(10, 12)); sched.variable_request(variable("cells"), make_range(11, 13)); sched.add_extra_unit_request(variable("cells"), make_range(20, 21)); - - // Verify the direct getters expose the stored existential and request state. CHECK(sched.get_existential_info(variable("cells"), producer) == make_range(10, 12)); CHECK(sched.get_variable_requests(variable("cells")) == make_range(11, 13)); - - // Verify per-rule requests intersect the existential region, and that UNIT - // extras are tracked separately. CHECK(sched.get_variable_request(producer, variable("cells")) == make_range(11, 12)); CHECK(sched.get_extra_unit_request(variable("cells")) == make_range(20, 21)); sched.clear_variable_request(); - - // Verify clearing requests also clears the extra UNIT-request bookkeeping. CHECK(sched.get_variable_requests(variable("cells")) == EMPTY); CHECK(sched.get_extra_unit_request(variable("cells")) == EMPTY); } @@ -359,9 +324,6 @@ TEST_CASE("time variables exist everywhere and ignore request updates") { // Sanity-check the fixture before probing sched_db behavior. REQUIRE(time_step.get_info().tvar); - - // Verify time variables behave as globally existing scheduler variables and - // ignore request accumulation. CHECK(sched.variable_existence(time_step) == ~EMPTY); CHECK_NOTHROW(sched.variable_request(time_step, make_range(1, 3))); CHECK_FALSE(sched.errors_found()); @@ -408,13 +370,9 @@ TEST_CASE("repeated existential writes from the same rule accumulate without con sched.set_existential_info(variable("cells"), producer, make_range(1, 2)); sched.set_existential_info(variable("cells"), producer, make_range(2, 4)); sched.variable_request(variable("cells"), make_range(3, 5)); - - // Verify repeated writes from the same producer merge instead of conflicting. CHECK_FALSE(sched.errors_found()); CHECK(sched.get_existential_info(variable("cells"), producer) == make_range(1, 4)); - - // Verify unrelated rules still report no request overlap. CHECK(sched.get_variable_request(producer, variable("cells")) == make_range(3, 4)); CHECK(sched.get_variable_request(unrelated, variable("cells")) == EMPTY); @@ -430,19 +388,12 @@ TEST_CASE("map variables expose cached image and preimage queries") { facts.create_fact("cells", cells); sched_db sched(facts); - - // Verify map-ness is detected from the shared scheduling data. CHECK(sched.is_a_Map(variable("cell_to_parent"))); CHECK_FALSE(sched.is_a_Map(variable("cells"))); - - // Verify forward image queries return the mapped codomain and remain stable - // across repeated lookups. CHECK(sched.image(variable("cell_to_parent"), make_range(1, 3)) == make_range(10, 11)); CHECK(sched.image(variable("cell_to_parent"), make_range(1, 3)) == make_range(10, 11)); - - // Verify preimage queries preserve the underlying map semantics. std::pair preimage = sched.preimage(variable("cell_to_parent"), make_range(10, 10)); CHECK(preimage.first == make_range(1, 2)); @@ -472,9 +423,6 @@ TEST_CASE("direct existence and shadow helpers update bookkeeping without adding sched.set_variable_existence(variable("cells"), make_range(5, 6)); sched.set_variable_shadow(variable("cells"), make_range(20, 20)); sched.variable_shadow(variable("cells"), make_range(21, 22)); - - // Verify direct existence/shadow helpers update bookkeeping without - // synthesizing existential producer records. CHECK(sched.variable_existence(variable("cells")) == expected_existence); CHECK(sched.get_existential_rules(variable("cells")).size() == 0); CHECK(sched.get_variable_shadow(variable("cells")) == make_range(20, 22)); @@ -515,9 +463,6 @@ TEST_CASE("duplication policies and flags propagate across synonym sets") { sched.add_policy(variable("cells"), sched_db::ALWAYS); sched.add_policy(variable("cells"), sched_db::MODEL_BASED); sched.set_duplicate_variable(variable("cells"), true); - - // Verify both the policy bitmask and the final duplication choice propagate - // across the synonym set. CHECK(sched.is_policy(variable("cells"), sched_db::ALWAYS)); CHECK(sched.is_policy(variable("cells_shadow"), sched_db::ALWAYS)); CHECK(sched.is_policy(variable("cells"), sched_db::MODEL_BASED)); @@ -600,9 +545,6 @@ TEST_CASE("duplication metadata and timing models are retained") { sched.add_duplication_computation_time(variable("cells"), 2.5); sched.add_original_communication_time(variable("cells"), 3.25); sched.add_duplication_communication_time(variable("cells"), 1.75); - - // Verify the duplication bookkeeping helpers retain the stored entity sets - // and reduction-output-map flag. CHECK(sched.get_proc_able_entities(variable("cells"), producer) == make_range(5, 6)); CHECK(sched.get_my_proc_able_entities(variable("cells"), producer) == @@ -610,9 +552,6 @@ TEST_CASE("duplication metadata and timing models are retained") { CHECK(sched.get_reduce_proc_able_entities(variable("cells")) == make_range(5, 6)); CHECK(sched.is_reduction_outputmap(variable("cells"))); - - // Verify the timing accumulators preserve the totals written through the - // adder helpers. CHECK(sched.get_precalculated_original_computation_time(variable("cells")) == doctest::Approx(5.0)); CHECK(sched.get_precalculated_duplication_computation_time(variable("cells")) == @@ -621,8 +560,6 @@ TEST_CASE("duplication metadata and timing models are retained") { doctest::Approx(3.25)); CHECK(sched.get_precalculated_duplication_communication_time(variable("cells")) == doctest::Approx(1.75)); - - // Verify the installed communication and computation models can be read back. auto comm_model = sched.get_comm_model(); double ts = 0.0; double tw = 0.0; @@ -708,9 +645,6 @@ TEST_CASE("send entity caches only expose variables with mapped-output rules") { variableSet vars = make_var_set({variable("cells"), variable("plain_cells")}); - - // Verify get_send_entities filters out variables whose producers do not map - // output entities. std::vector > barrier = sched.get_send_entities(vars, sched_db::BARRIER); REQUIRE(barrier.size() == 1); @@ -771,10 +705,6 @@ TEST_CASE("communication list caches return sends before receives in processor o variableSet vars = make_var_set({variable("cells"), variable("flux")}); std::vector sorted = to_vector(sched.get_comm_info_list(vars, facts, sched_db::BARRIER_CLIST)); - - // Verify repeated cache updates append records, and retrieval repacks the - // combined cache so sends come first in processor order, followed by receives - // in processor order. REQUIRE(sorted.size() == 4); CHECK(sorted[0].processor == 0); CHECK(sorted[0].send_set == make_range(20, 20)); From 3b6b173e51b2c08d91005cd78faf1e07b36c0a24 Mon Sep 17 00:00:00 2001 From: Christopher Neal Date: Thu, 14 May 2026 02:13:02 -0400 Subject: [PATCH 4/4] Restore sched_db test comments --- quickTest/SystemTests/test_sched_db.cc | 114 ++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 1 deletion(-) diff --git a/quickTest/SystemTests/test_sched_db.cc b/quickTest/SystemTests/test_sched_db.cc index 819409aa..40c61d85 100644 --- a/quickTest/SystemTests/test_sched_db.cc +++ b/quickTest/SystemTests/test_sched_db.cc @@ -15,11 +15,19 @@ using namespace Loci; namespace { -// Keep scheduler fixtures explicit without repeating store/map allocation loops. +/// @brief Returns an inclusive `entitySet` interval `[first, last]`. +/// @param[in] first First entity in the interval. +/// @param[in] last Last entity in the interval. +/// @return An `entitySet` spanning the requested inclusive interval. entitySet make_range(int first, int last) { return entitySet(interval(first, last)); } +/// @brief Allocates `values` on `domain` and fills it from `entries`. +/// @param[in,out] values Store to allocate and populate. +/// @param[in] domain Domain to allocate in the store. +/// @param[in] entries Values copied into the store in domain iteration order. +/// @throws std::logic_error When `entries.size()` does not match `domain.size()`. void assign_store_values(store &values, const entitySet &domain, const std::vector &entries) { if(static_cast(domain.size()) != entries.size()) { @@ -34,6 +42,12 @@ void assign_store_values(store &values, const entitySet &domain, } } +/// @brief Allocates `mapping` on `domain` and fills it from `entries`. +/// @param[in,out] mapping Map to allocate and populate. +/// @param[in] domain Domain to allocate in the map. +/// @param[in] entries Target entities copied into the map in domain iteration +/// order. +/// @throws std::logic_error When `entries.size()` does not match `domain.size()`. void assign_map_values(Map &mapping, const entitySet &domain, const std::vector &entries) { if(static_cast(domain.size()) != entries.size()) { @@ -48,10 +62,18 @@ void assign_map_values(Map &mapping, const entitySet &domain, } } +/// @brief Returns true when `rep` wraps a non-null representation pointer. +/// @param[in] rep Store representation handle to inspect. +/// @return True when `rep` is non-null. bool has_rep(const storeRepP &rep) { return rep != static_cast(0); } +/// @brief Returns true when both handles refer to the same store representation. +/// @param[in] lhs First store representation handle. +/// @param[in] rhs Second store representation handle. +/// @return True when both handles are non-null and share the same underlying +/// representation pointer. bool same_rep(const storeRepP &lhs, const storeRepP &rhs) { if(!has_rep(lhs) || !has_rep(rhs)) { return false; @@ -59,6 +81,11 @@ bool same_rep(const storeRepP &lhs, const storeRepP &rhs) { return lhs.operator->() == rhs.operator->(); } +/// @brief Builds a rule descriptor string that `rule(...)` can parse. +/// @param[in] source_expr Source portion of the rule signature. +/// @param[in] target_expr Target portion of the rule signature. +/// @param[in] qualifier Qualifier portion of the rule signature. +/// @return A `rule` constructed from the assembled signature. rule make_rule(const std::string &source_expr, const std::string &target_expr, const std::string &qualifier) { std::ostringstream signature; @@ -67,6 +94,9 @@ rule make_rule(const std::string &source_expr, const std::string &target_expr, return rule(signature.str()); } +/// @brief Collects `vars` into a `variableSet`. +/// @param[in] vars Variables to insert. +/// @return A `variableSet` containing every variable from `vars`. variableSet make_var_set(const std::vector &vars) { variableSet set; for(size_t i = 0; i < vars.size(); ++i) { @@ -75,6 +105,9 @@ variableSet make_var_set(const std::vector &vars) { return set; } +/// @brief Copies a `std::list` into an indexable vector. +/// @param[in] entries Communication records to copy. +/// @return A vector containing the same records in the same order. std::vector to_vector(const std::list &entries) { return std::vector(entries.begin(), entries.end()); } @@ -85,13 +118,20 @@ TEST_CASE("sched_db constructor mirrors fact_db variables and existence") { fact_db facts; store cells; + // Seed one typed fact so the constructor has scheduler state to mirror. assign_store_values(cells, make_range(4, 6), {10, 20, 30}); facts.create_fact("cells", cells); sched_db sched(facts); + + // Verify the constructor registers the variable and its self-alias/synonym + // bookkeeping. CHECK(sched.get_typed_variables().inSet(variable("cells"))); CHECK(sched.get_aliases(variable("cells")).inSet(variable("cells"))); CHECK(sched.get_synonyms(variable("cells")).inSet(variable("cells"))); + + // Verify the mirrored fact domain becomes known existence and leaves the + // remaining scheduler state empty/defaulted. CHECK(sched.variable_existence(variable("cells")) == make_range(4, 6)); CHECK_FALSE(sched.is_a_Map(variable("cells"))); CHECK(sched.get_variable_requests(variable("cells")) == EMPTY); @@ -102,13 +142,19 @@ TEST_CASE("set_variable_type installs intensional facts and empty schedule state sched_db sched(facts); store derived; + // Build a store representation that will be installed as an intensional fact. assign_store_values(derived, make_range(10, 11), {3, 5}); sched.set_variable_type(variable("derived"), derived.Rep(), facts); + + // Verify the new variable is tracked by both sched_db and fact_db, and that + // sched_db starts it with no derived existence yet. CHECK(sched.get_typed_variables().inSet(variable("derived"))); CHECK(facts.get_intensional_facts().inSet(variable("derived"))); CHECK_FALSE(facts.get_extensional_facts().inSet(variable("derived"))); CHECK(sched.variable_existence(variable("derived")) == EMPTY); + + // Verify the installed fact points at the expected store contents. storeRepP rep = facts.get_variable("derived"); REQUIRE(has_rep(rep)); store fetched(rep); @@ -148,12 +194,17 @@ TEST_CASE("alias_variable keeps alias bookkeeping in sync with fact_db") { sched.alias_variable(variable("cells"), variable("cells_alias"), facts); variableSet aliases = sched.get_aliases(variable("cells")); + + // Verify sched_db records both names in the shared alias metadata. CHECK(aliases.inSet(variable("cells"))); CHECK(aliases.inSet(variable("cells_alias"))); CHECK(sched.get_aliases(variable("cells_alias")) == aliases); CHECK(sched.get_antialiases(variable("cells_alias")) .inSet(variable("cells"))); CHECK(sched.get_typed_variables().inSet(variable("cells_alias"))); + + // Verify fact_db mirrors the alias relationship onto the same store + // representation. CHECK(same_rep(facts.get_variable("cells"), facts.get_variable("cells_alias"))); } @@ -167,6 +218,9 @@ TEST_CASE("alias_variable accepts either argument order when one name already ex sched.alias_variable(variable("cells_alias"), variable("cells")); variableSet aliases = sched.get_aliases(variable("cells")); + + // Verify the helper resolves the existing variable as the alias source rather + // than treating the reversed argument order as an error. CHECK_FALSE(sched.errors_found()); CHECK(aliases.inSet(variable("cells"))); CHECK(aliases.inSet(variable("cells_alias"))); @@ -184,9 +238,14 @@ TEST_CASE("synonym_variable records canonical lookup behavior") { sched_db sched(facts); sched.synonym_variable(variable("cells"), variable("cells_shadow"), facts); + + // Verify the synonym resolves back to the canonical scheduler name. CHECK(sched.remove_synonym(variable("cells_shadow")) == variable("cells")); variableSet synonyms = sched.get_synonyms(variable("cells")); + + // Verify the synonym set and fact_db both treat the two names as one logical + // variable. CHECK(synonyms.inSet(variable("cells"))); CHECK(synonyms.inSet(variable("cells_shadow"))); CHECK(sched.get_typed_variables().inSet(variable("cells_shadow"))); @@ -227,6 +286,8 @@ TEST_CASE("synonyms resolve requests and direct existence through the canonical entitySet expected_existence = make_range(2, 4); expected_existence += make_range(20, 21); + + // Verify both names resolve through the same canonical scheduler entry. CHECK(sched.remove_synonym(variable("cells_shadow")) == variable("cells")); CHECK(sched.variable_existence(variable("cells")) == expected_existence); CHECK(sched.variable_existence(variable("cells_shadow")) == expected_existence); @@ -247,8 +308,12 @@ TEST_CASE("rotation groups are recorded and try_get helpers stay safe for unknow sched_db sched(facts); variableSet rotations = make_var_set({variable("cells"), variable("faces")}); sched.set_variable_rotations(rotations); + + // Verify every member of the rotation group sees the full recorded set. CHECK(sched.get_rotations(variable("cells")) == rotations); CHECK(sched.get_rotations(variable("faces")) == rotations); + + // Verify the unknown-safe accessors return empty results instead of aborting. CHECK(sched.try_get_synonyms(variable("ghost")) == variableSet(EMPTY)); CHECK(sched.try_get_aliases(variable("ghost")) == variableSet(EMPTY)); CHECK(sched.try_get_antialiases(variable("ghost")) == variableSet(EMPTY)); @@ -265,9 +330,14 @@ TEST_CASE("aliases share scheduling data but keep separate existence and request sched.alias_variable(variable("cells"), variable("cells_alias"), facts); sched.set_variable_existence(variable("cells"), make_range(30, 31)); sched.variable_request(variable("cells"), make_range(31, 32)); + + // Verify the alias still participates in the shared alias group. CHECK(sched.get_aliases(variable("cells_alias")).inSet(variable("cells"))); CHECK(sched.get_aliases(variable("cells_alias")) .inSet(variable("cells_alias"))); + + // Verify existence and requests remain separate because aliases keep distinct + // sched_info records. CHECK(sched.variable_existence(variable("cells_alias")) == EMPTY); CHECK(sched.get_variable_requests(variable("cells_alias")) == EMPTY); } @@ -306,14 +376,21 @@ TEST_CASE("requests are intersected with rule existence and can be cleared") { sched.set_existential_info(variable("cells"), producer, make_range(10, 12)); sched.variable_request(variable("cells"), make_range(11, 13)); sched.add_extra_unit_request(variable("cells"), make_range(20, 21)); + + // Verify the direct getters expose the stored existential and request state. CHECK(sched.get_existential_info(variable("cells"), producer) == make_range(10, 12)); CHECK(sched.get_variable_requests(variable("cells")) == make_range(11, 13)); + + // Verify per-rule requests intersect the existential region, and that UNIT + // extras are tracked separately. CHECK(sched.get_variable_request(producer, variable("cells")) == make_range(11, 12)); CHECK(sched.get_extra_unit_request(variable("cells")) == make_range(20, 21)); sched.clear_variable_request(); + + // Verify clearing requests also clears the extra UNIT-request bookkeeping. CHECK(sched.get_variable_requests(variable("cells")) == EMPTY); CHECK(sched.get_extra_unit_request(variable("cells")) == EMPTY); } @@ -324,6 +401,9 @@ TEST_CASE("time variables exist everywhere and ignore request updates") { // Sanity-check the fixture before probing sched_db behavior. REQUIRE(time_step.get_info().tvar); + + // Verify time variables behave as globally existing scheduler variables and + // ignore request accumulation. CHECK(sched.variable_existence(time_step) == ~EMPTY); CHECK_NOTHROW(sched.variable_request(time_step, make_range(1, 3))); CHECK_FALSE(sched.errors_found()); @@ -370,9 +450,13 @@ TEST_CASE("repeated existential writes from the same rule accumulate without con sched.set_existential_info(variable("cells"), producer, make_range(1, 2)); sched.set_existential_info(variable("cells"), producer, make_range(2, 4)); sched.variable_request(variable("cells"), make_range(3, 5)); + + // Verify repeated writes from the same producer merge instead of conflicting. CHECK_FALSE(sched.errors_found()); CHECK(sched.get_existential_info(variable("cells"), producer) == make_range(1, 4)); + + // Verify unrelated rules still report no request overlap. CHECK(sched.get_variable_request(producer, variable("cells")) == make_range(3, 4)); CHECK(sched.get_variable_request(unrelated, variable("cells")) == EMPTY); @@ -388,12 +472,19 @@ TEST_CASE("map variables expose cached image and preimage queries") { facts.create_fact("cells", cells); sched_db sched(facts); + + // Verify map-ness is detected from the shared scheduling data. CHECK(sched.is_a_Map(variable("cell_to_parent"))); CHECK_FALSE(sched.is_a_Map(variable("cells"))); + + // Verify forward image queries return the mapped codomain and remain stable + // across repeated lookups. CHECK(sched.image(variable("cell_to_parent"), make_range(1, 3)) == make_range(10, 11)); CHECK(sched.image(variable("cell_to_parent"), make_range(1, 3)) == make_range(10, 11)); + + // Verify preimage queries preserve the underlying map semantics. std::pair preimage = sched.preimage(variable("cell_to_parent"), make_range(10, 10)); CHECK(preimage.first == make_range(1, 2)); @@ -423,6 +514,9 @@ TEST_CASE("direct existence and shadow helpers update bookkeeping without adding sched.set_variable_existence(variable("cells"), make_range(5, 6)); sched.set_variable_shadow(variable("cells"), make_range(20, 20)); sched.variable_shadow(variable("cells"), make_range(21, 22)); + + // Verify direct existence/shadow helpers update bookkeeping without + // synthesizing existential producer records. CHECK(sched.variable_existence(variable("cells")) == expected_existence); CHECK(sched.get_existential_rules(variable("cells")).size() == 0); CHECK(sched.get_variable_shadow(variable("cells")) == make_range(20, 22)); @@ -463,6 +557,9 @@ TEST_CASE("duplication policies and flags propagate across synonym sets") { sched.add_policy(variable("cells"), sched_db::ALWAYS); sched.add_policy(variable("cells"), sched_db::MODEL_BASED); sched.set_duplicate_variable(variable("cells"), true); + + // Verify both the policy bitmask and the final duplication choice propagate + // across the synonym set. CHECK(sched.is_policy(variable("cells"), sched_db::ALWAYS)); CHECK(sched.is_policy(variable("cells_shadow"), sched_db::ALWAYS)); CHECK(sched.is_policy(variable("cells"), sched_db::MODEL_BASED)); @@ -545,6 +642,9 @@ TEST_CASE("duplication metadata and timing models are retained") { sched.add_duplication_computation_time(variable("cells"), 2.5); sched.add_original_communication_time(variable("cells"), 3.25); sched.add_duplication_communication_time(variable("cells"), 1.75); + + // Verify the duplication bookkeeping helpers retain the stored entity sets + // and reduction-output-map flag. CHECK(sched.get_proc_able_entities(variable("cells"), producer) == make_range(5, 6)); CHECK(sched.get_my_proc_able_entities(variable("cells"), producer) == @@ -552,6 +652,9 @@ TEST_CASE("duplication metadata and timing models are retained") { CHECK(sched.get_reduce_proc_able_entities(variable("cells")) == make_range(5, 6)); CHECK(sched.is_reduction_outputmap(variable("cells"))); + + // Verify the timing accumulators preserve the totals written through the + // adder helpers. CHECK(sched.get_precalculated_original_computation_time(variable("cells")) == doctest::Approx(5.0)); CHECK(sched.get_precalculated_duplication_computation_time(variable("cells")) == @@ -560,6 +663,8 @@ TEST_CASE("duplication metadata and timing models are retained") { doctest::Approx(3.25)); CHECK(sched.get_precalculated_duplication_communication_time(variable("cells")) == doctest::Approx(1.75)); + + // Verify the installed communication and computation models can be read back. auto comm_model = sched.get_comm_model(); double ts = 0.0; double tw = 0.0; @@ -645,6 +750,9 @@ TEST_CASE("send entity caches only expose variables with mapped-output rules") { variableSet vars = make_var_set({variable("cells"), variable("plain_cells")}); + + // Verify get_send_entities filters out variables whose producers do not map + // output entities. std::vector > barrier = sched.get_send_entities(vars, sched_db::BARRIER); REQUIRE(barrier.size() == 1); @@ -705,6 +813,10 @@ TEST_CASE("communication list caches return sends before receives in processor o variableSet vars = make_var_set({variable("cells"), variable("flux")}); std::vector sorted = to_vector(sched.get_comm_info_list(vars, facts, sched_db::BARRIER_CLIST)); + + // Verify repeated cache updates append records, and retrieval repacks the + // combined cache so sends come first in processor order, followed by receives + // in processor order. REQUIRE(sorted.size() == 4); CHECK(sorted[0].processor == 0); CHECK(sorted[0].send_set == make_range(20, 20));