Deduplicate trigger and effect conditions in packages

This refactor introduces extra indices for triggers and
effect of a condition, so that the corresponding clauses
are evaluated once for every condition they apply to.
This commit is contained in:
Massimiliano Culpo 2023-06-22 19:43:08 +02:00 committed by Todd Gamblin
parent ae553051c8
commit c1a73878ea
2 changed files with 95 additions and 28 deletions

View file

@ -890,6 +890,10 @@ def __init__(self, tests=False):
# id for dummy variables
self._condition_id_counter = itertools.count()
self._trigger_id_counter = itertools.count()
self._trigger_cache = collections.defaultdict(dict)
self._effect_id_counter = itertools.count()
self._effect_cache = collections.defaultdict(dict)
# Caches to optimize the setup phase of the solver
self.target_specs_cache = None
@ -1152,6 +1156,32 @@ def pkg_rules(self, pkg, tests):
self.package_requirement_rules(pkg)
# trigger and effect tables
self.trigger_rules(pkg.name)
self.effect_rules(pkg.name)
def trigger_rules(self, name):
self.gen.h2("Trigger conditions")
cache = self._trigger_cache[name]
for spec_str, (trigger_id, requirements) in cache.items():
self.gen.fact(fn.facts(name, fn.trigger_id(trigger_id)))
self.gen.fact(fn.facts(name, fn.trigger_msg(spec_str)))
for predicate in requirements:
self.gen.fact(fn.condition_requirement(trigger_id, *predicate.args))
self.gen.newline()
cache.clear()
def effect_rules(self, name):
self.gen.h2("Imposed requirements")
cache = self._effect_cache[name]
for spec_str, (effect_id, requirements) in cache.items():
self.gen.fact(fn.facts(name, fn.effect_id(effect_id)))
self.gen.fact(fn.facts(name, fn.effect_msg(spec_str)))
for predicate in requirements:
self.gen.fact(fn.imposed_constraint(effect_id, *predicate.args))
self.gen.newline()
cache.clear()
def variant_rules(self, pkg):
for name, entry in sorted(pkg.variants.items()):
variant, when = entry
@ -1265,16 +1295,35 @@ def condition(self, required_spec, imposed_spec=None, name=None, msg=None, node=
# Check if we can emit the requirements before updating the condition ID counter.
# In this way, if a condition can't be emitted but the exception is handled in the caller,
# we won't emit partial facts.
requirements = self.spec_clauses(named_cond, body=True, required_from=name)
condition_id = next(self._condition_id_counter)
self.gen.fact(fn.facts(named_cond.name, fn.condition(condition_id)))
self.gen.fact(fn.condition_reason(condition_id, msg))
for pred in requirements:
self.gen.fact(fn.condition_requirement(condition_id, *pred.args))
if imposed_spec:
self.impose(condition_id, imposed_spec, node=node, name=name)
cache = self._trigger_cache[named_cond.name]
if named_cond not in cache:
trigger_id = next(self._trigger_id_counter)
requirements = self.spec_clauses(named_cond, body=True, required_from=name)
cache[named_cond] = (trigger_id, requirements)
trigger_id, requirements = cache[named_cond]
self.gen.fact(fn.facts(named_cond.name, fn.condition_trigger(condition_id, trigger_id)))
if not imposed_spec:
return condition_id
cache = self._effect_cache[named_cond.name]
if imposed_spec not in cache:
effect_id = next(self._effect_id_counter)
requirements = self.spec_clauses(imposed_spec, body=False, required_from=name)
if not node:
requirements = list(
filter(lambda x: x.args[0] not in ("node", "virtual_node"), requirements)
)
cache[imposed_spec] = (effect_id, requirements)
effect_id, requirements = cache[imposed_spec]
self.gen.fact(fn.facts(named_cond.name, fn.condition_effect(condition_id, effect_id)))
# FIXME: self.gen.fact(fn.imposed_constraint(condition_id, *predicate.args))
return condition_id
@ -1375,6 +1424,8 @@ def provider_requirements(self):
virtual_str, requirements, kind=RequirementKind.VIRTUAL
)
self.emit_facts_from_requirement_rules(rules)
self.trigger_rules(virtual_str)
self.effect_rules(virtual_str)
def emit_facts_from_requirement_rules(self, rules: List[RequirementRule]):
"""Generate facts to enforce requirements.
@ -2332,9 +2383,12 @@ def setup(self, driver, specs, reuse=None):
self.preferred_variants(pkg)
self.target_preferences(pkg)
self.gen.h1("Develop specs")
# Inject dev_path from environment
for ds in dev_specs:
self.condition(spack.spec.Spec(ds.name), ds, msg="%s is a develop spec" % ds.name)
self.trigger_rules(ds.name)
self.effect_rules(ds.name)
self.gen.h1("Spec Constraints")
self.literal_specs(specs)

View file

@ -251,26 +251,28 @@ condition_set(ID, VirtualNode, Type) :- condition_set(ID, PackageNode, Type), pr
condition_set(ID, PackageNode) :- condition_set(ID, PackageNode, _).
condition_set(VirtualNode, X) :- provider(PackageNode, VirtualNode), condition_set(PackageNode, X).
condition_packages(ID, A1) :- condition_requirement(ID, _, A1).
condition_packages(ID, A1) :- condition_requirement(ID, _, A1, _).
condition_packages(ID, A1) :- condition_requirement(ID, _, A1, _, _).
condition_packages(ID, A1) :- condition_requirement(ID, _, A1, _, _, _).
node_condition(ID, node(PackageID, Package)) :- facts(Package, condition(ID)), attr("node", node(PackageID, Package)).
node_condition(ID, node(PackageID, Package)) :- facts(Virtual, condition(ID)), provider(node(PackageID, Package), node(_, Virtual)).
trigger_node(ID, node(PackageID, Package), node(PackageID, Package)) :- facts(Package, trigger_id(ID)), attr("node", node(PackageID, Package)).
trigger_node(ID, node(PackageID, Package), node(VirtualID, Virtual)) :- facts(Virtual, trigger_id(ID)), provider(node(PackageID, Package), node(VirtualID, Virtual)).
condition_nodes(ConditionID, PackageNode, node(X, A1))
:- condition_packages(ConditionID, A1),
condition_nodes(TriggerID, PackageNode, node(X, A1))
:- condition_packages(TriggerID, A1),
condition_set(PackageNode, node(X, A1)),
node_condition(ConditionID, PackageNode).
trigger_node(TriggerID, PackageNode, _).
cannot_hold(ConditionID, PackageNode)
:- condition_packages(ConditionID, A1),
not condition_set(PackageNode, node(_, A1), _),
node_condition(ConditionID, PackageNode).
cannot_hold(TriggerID, PackageNode)
:- condition_packages(TriggerID, A1),
not condition_set(PackageNode, node(_, A1)),
trigger_node(TriggerID, PackageNode, _).
condition_holds(ID, PackageNode) :-
node_condition(ID, PackageNode);
trigger_condition_holds(ID, RequestorNode) :-
trigger_node(ID, PackageNode, RequestorNode);
attr(Name, node(X, A1)) : condition_requirement(ID, Name, A1), condition_nodes(ID, PackageNode, node(X, A1));
attr(Name, node(X, A1), A2) : condition_requirement(ID, Name, A1, A2), condition_nodes(ID, PackageNode, node(X, A1));
attr(Name, node(X, A1), A2, A3) : condition_requirement(ID, Name, A1, A2, A3), condition_nodes(ID, PackageNode, node(X, A1)), not special_case(Name);
@ -279,14 +281,21 @@ condition_holds(ID, PackageNode) :-
attr("node_flag_source", node(X, A1), A2, node(Y, A3)) : condition_requirement(ID, "node_flag_source", A1, A2, A3), condition_nodes(ID, PackageNode, node(X, A1)), condition_nodes(ID, PackageNode, node(Y, A3));
not cannot_hold(ID, PackageNode).
condition_holds(ID, node(VirtualID, Virtual))
:- condition_holds(ID, PackageNode),
facts(Virtual, condition(ID)),
provider(PackageNode, node(VirtualID, Virtual)).
condition_holds(ConditionID, node(X, Package))
:- facts(Package, condition_trigger(ConditionID, TriggerID)),
trigger_condition_holds(TriggerID, node(X, Package)).
trigger_and_effect(Package, TriggerID, EffectID)
:- facts(Package, condition_trigger(ID, TriggerID)),
facts(Package, condition_effect(ID, EffectID)).
% condition_holds(ID, node(ID, Package)) implies all imposed_constraints, unless do_not_impose(ID, node(ID, Package))
% is derived. This allows imposed constraints to be canceled in special cases.
impose(ID, PackageNode) :- condition_holds(ID, PackageNode), node_condition(ID, PackageNode), not do_not_impose(ID, PackageNode).
impose(EffectID, node(X, Package))
:- trigger_and_effect(Package, TriggerID, EffectID),
trigger_node(TriggerID, _, node(X, Package)),
trigger_condition_holds(TriggerID, node(X, Package)),
not do_not_impose(EffectID, node(X, Package)).
imposed_packages(ID, A1) :- imposed_constraint(ID, _, A1).
imposed_packages(ID, A1) :- imposed_constraint(ID, _, A1, _).
@ -294,17 +303,20 @@ imposed_packages(ID, A1) :- imposed_constraint(ID, _, A1, _, _).
imposed_packages(ID, A1) :- imposed_constraint(ID, _, A1, _, _, _).
imposed_packages(ID, A1) :- imposed_constraint(ID, "depends_on", _, A1, _).
imposed_nodes(ConditionID, PackageNode, node(X, A1))
:- imposed_packages(ConditionID, A1),
condition_set(PackageNode, node(X, A1), _),
node_condition(ConditionID, PackageNode).
imposed_nodes(EffectID, node(NodeID, Package), node(X, A1))
:- facts(Package, condition_trigger(ID, TriggerID)),
facts(Package, condition_effect(ID, EffectID)),
imposed_packages(EffectID, A1),
condition_set(node(NodeID, Package), node(X, A1)),
trigger_node(TriggerID, _, node(NodeID, Package)).
imposed_nodes(ConditionID, PackageNode, node(X, A1))
:- imposed_packages(ConditionID, A1),
condition_set(PackageNode, node(X, A1), _),
condition_set(PackageNode, node(X, A1)),
attr("hash", PackageNode, ConditionID).
:- imposed_packages(ID, A1), impose(ID, PackageNode), not condition_set(PackageNode, node(_, A1)).
:- imposed_packages(ID, A1), impose(ID, PackageNode), not imposed_nodes(ID, PackageNode, node(_, A1)).
% Conditions that hold impose may impose constraints on other specs
attr(Name, node(X, A1)) :- impose(ID, PackageNode), imposed_constraint(ID, Name, A1), imposed_nodes(ID, PackageNode, node(X, A1)).
@ -375,10 +387,11 @@ dependency_holds(node(NodeID, Package), Dependency, Type) :-
% We cut off dependencies of externals (as we don't really know them).
% Don't impose constraints on dependencies that don't exist.
do_not_impose(ID, node(NodeID, Package)) :-
do_not_impose(EffectID, node(NodeID, Package)) :-
not dependency_holds(node(NodeID, Package), Dependency, _),
attr("node", node(NodeID, Package)),
facts(Package, dependency_condition(ID, Dependency)).
facts(Package, dependency_condition(ID, Dependency)),
facts(Package, condition_effect(ID, EffectID)).
% If a dependency holds on a package node, there must be one and only one dependency node satisfying it
1 { attr("depends_on", PackageNode, node(0..Y-1, Dependency), Type) : max_nodes(Dependency, Y) } 1