Copyright (c) 2006 Microsoft Corporation
Module Name:
dl_mk_rule_inliner.cpp
Abstract:
Rule transformer which simplifies interpreted tails
Author:
Krystof Hoder (t-khoder) 2011-10-01.
Revision History:
Added linear_inline 2012-9-10 (nbjorner)
Disable inliner for quantified rules 2012-10-31 (nbjorner)
Notes:
Resolution transformation (resolve):
P(x) :- Q(y), phi(x,y) Q(y) :- R(z), psi(y,z)
--------------------------------------------------
P(x) :- R(z), phi(x,y), psi(y,z)
Proof converter:
replace assumption (*) by rule and upper assumptions.
Subsumption transformation (remove rule):
P(x) :- Q(y), phi(x,y) Rules
---------------------------------
Rules
Model converter:
P(x) := P(x) or (exists y . Q(y) & phi(x,y))
--*/
#include <sstream>
#include "ast/ast_pp.h"
#include "ast/rewriter/rewriter.h"
#include "ast/rewriter/rewriter_def.h"
#include "muz/transforms/dl_mk_rule_inliner.h"
#include "muz/base/fp_params.hpp"
namespace datalog {
bool rule_unifier::unify_rules(const rule& tgt, unsigned tgt_idx, const rule& src) {
rule_counter& vc = m_rm.get_counter();
unsigned var_cnt = std::max(vc.get_max_rule_var(tgt), vc.get_max_rule_var(src))+1;
m_subst.reset();
m_subst.reserve(2, var_cnt);
m_ready = m_unif(tgt.get_tail(tgt_idx), src.get_head(), m_subst);
if (m_ready) {
m_deltas[0] = 0;
m_deltas[1] = var_cnt;
TRACE("dl",
output_predicate(m_context, src.get_head(), tout << "unify rules ");
output_predicate(m_context, tgt.get_head(), tout << "\n");
tout << "\n";);
}
return m_ready;
}
void rule_unifier::apply(app * a, bool is_tgt, app_ref& res) {
expr_ref res_e(m);
TRACE("dl", output_predicate(m_context, a, tout); tout << "\n";);
m_subst.apply(2, m_deltas, expr_offset(a, is_tgt ? 0 : 1), res_e);
SASSERT(is_app(res_e.get()));
res = to_app(res_e.get());
}
void rule_unifier::apply(
rule const& r, bool is_tgt, unsigned skipped_index,
app_ref_vector& res, bool_vector& res_neg) {
unsigned rule_len = r.get_tail_size();
for (unsigned i = 0; i < rule_len; i++) {
if (i != skipped_index) {
app_ref new_tail_el(m);
apply(r.get_tail(i), is_tgt, new_tail_el);
res.push_back(new_tail_el);
res_neg.push_back(r.is_neg_tail(i));
}
}
}
bool rule_unifier::apply(rule const& tgt, unsigned tail_index, rule const& src, rule_ref& res) {
SASSERT(m_ready);
app_ref new_head(m);
app_ref_vector tail(m);
bool_vector tail_neg;
rule_ref simpl_rule(m_rm);
apply(tgt.get_head(), true, new_head);
apply(tgt, true, tail_index, tail, tail_neg);
apply(src, false, UINT_MAX, tail, tail_neg);
mk_rule_inliner::remove_duplicate_tails(tail, tail_neg);
SASSERT(tail.size()==tail_neg.size());
std::ostringstream comb_name;
comb_name << tgt.name().str() << ";" << src.name().str();
symbol combined_rule_name(comb_name.str());
res = m_rm.mk(new_head, tail.size(), tail.data(), tail_neg.data(), combined_rule_name, m_normalize);
res->set_accounting_parent_object(m_context, const_cast<rule*>(&tgt));
TRACE("dl",
tgt.display(m_context, tout << "tgt (" << tail_index << "): \n");
src.display(m_context, tout << "src:\n");
res->display(m_context, tout << "res\n");
);
if (m_normalize) {
m_rm.fix_unbound_vars(res, true);
if (m_interp_simplifier.transform_rule(res.get(), simpl_rule)) {
res = simpl_rule;
return true;
}
else {
return false;
}
}
else {
return true;
}
}
expr_ref_vector rule_unifier::get_rule_subst(const rule& r, bool is_tgt) {
SASSERT(m_ready);
expr_ref_vector result(m);
ptr_vector<sort> sorts;
expr_ref v(m), w(m);
r.get_vars(m, sorts);
for (unsigned i = 0; i < sorts.size(); ++i) {
v = m.mk_var(i, sorts[i]);
m_subst.apply(2, m_deltas, expr_offset(v, is_tgt?0:1), w);
result.push_back(w);
}
return result;
}
Inline occurrences of rule src at tail_index in tgt and return the result in res.
*/
bool mk_rule_inliner::try_to_inline_rule(rule& tgt, rule& src, unsigned tail_index, rule_ref& res)
{
SASSERT(tail_index<tgt.get_positive_tail_size());
SASSERT(!tgt.is_neg_tail(tail_index));
tgt.norm_vars(m_context.get_rule_manager());
if (has_quantifier(src))
throw has_new_quantifier();
if (!m_unifier.unify_rules(tgt, tail_index, src)) {
return false;
}
if (m_unifier.apply(tgt, tail_index, src, res)) {
if (m_context.generate_proof_trace()) {
expr_ref_vector s1 = m_unifier.get_rule_subst(tgt, true);
expr_ref_vector s2 = m_unifier.get_rule_subst(src, false);
datalog::resolve_rule(m_rm, tgt, src, tail_index, s1, s2, *res.get());
}
return true;
}
else {
TRACE("dl", res->display(m_context, tout << "interpreted tail is unsat\n"););
return false;
}
}
bool mk_rule_inliner::has_quantifier(rule const& r) const {
unsigned utsz = r.get_uninterpreted_tail_size();
for (unsigned i = utsz; i < r.get_tail_size(); ++i) {
if (r.get_tail(i)->has_quantifiers()) return true;
}
return false;
}
void mk_rule_inliner::count_pred_occurrences(rule_set const & orig)
{
rel_context_base* rel = m_context.get_rel_context();
if (rel) {
rel->collect_non_empty_predicates(m_preds_with_facts);
}
for (rule * r : orig) {
func_decl * head_pred = r->get_decl();
m_head_pred_ctr.inc(head_pred);
if (r->get_tail_size()>0) {
m_head_pred_non_empty_tails_ctr.inc(head_pred);
}
unsigned ut_len = r->get_uninterpreted_tail_size();
for (unsigned i=0; i<ut_len; i++) {
func_decl * pred = r->get_decl(i);
m_tail_pred_ctr.inc(pred);
if (r->is_neg_tail(i)) {
m_preds_with_neg_occurrence.insert(pred);
}
}
}
}
bool mk_rule_inliner::inlining_allowed(rule_set const& source, func_decl * pred)
{
if (
source.is_output_predicate(pred) ||
m_preds_with_facts.contains(pred) ||
m_preds_with_neg_occurrence.contains(pred) ||
m_forbidden_preds.contains(pred)) {
return false;
}
return
m_head_pred_ctr.get(pred) <= 1
|| (m_tail_pred_ctr.get(pred) <= 1 && m_head_pred_ctr.get(pred) <= 4)
;
}
rule_set * mk_rule_inliner::create_allowed_rule_set(rule_set const & orig)
{
rule_set * res = alloc(rule_set, m_context);
for (rule * r : orig) {
if (inlining_allowed(orig, r->get_decl())) {
res->add_rule(r);
}
}
VERIFY(res->close());
return res;
}
Try to make the set of inlined predicates acyclic by forbidding inlining of one
predicate from each strongly connected component. Return true if we did forbide some
predicate, and false if the set of rules is already acyclic.
*/
bool mk_rule_inliner::forbid_preds_from_cycles(rule_set const & r)
{
SASSERT(r.is_closed());
bool something_forbidden = false;
const rule_stratifier::comp_vector& comps = r.get_stratifier().get_strats();
for (rule_stratifier::item_set * stratum : comps) {
if (stratum->size() == 1) {
continue;
}
SASSERT(stratum->size() > 1);
func_decl * first_stratum_pred = *stratum->begin();
m_forbidden_preds.insert(first_stratum_pred);
something_forbidden = true;
}
return something_forbidden;
}
bool mk_rule_inliner::forbid_multiple_multipliers(const rule_set & orig,
rule_set const & proposed_inlined_rules) {
bool something_forbidden = false;
const rule_stratifier::comp_vector& comps =
proposed_inlined_rules.get_stratifier().get_strats();
for (rule_stratifier::item_set * stratum : comps) {
SASSERT(stratum->size()==1);
func_decl * head_pred = *stratum->begin();
bool is_multi_head_pred = m_head_pred_ctr.get(head_pred)>1;
bool is_multi_occurrence_pred = m_tail_pred_ctr.get(head_pred)>1;
const rule_vector& pred_rules = proposed_inlined_rules.get_predicate_rules(head_pred);
for (rule * r : pred_rules) {
unsigned pt_len = r->get_positive_tail_size();
for (unsigned ti = 0; ti<pt_len; ++ti) {
func_decl * tail_pred = r->get_decl(ti);
if (!inlining_allowed(orig, tail_pred)) {
continue;
}
unsigned tail_pred_head_cnt = m_head_pred_ctr.get(tail_pred);
if (tail_pred_head_cnt<=1) {
continue;
}
if (is_multi_head_pred) {
m_forbidden_preds.insert(head_pred);
something_forbidden = true;
goto process_next_pred;
}
if (is_multi_occurrence_pred) {
m_forbidden_preds.insert(tail_pred);
something_forbidden = true;
}
else {
is_multi_head_pred = true;
m_head_pred_ctr.get(head_pred) =
m_head_pred_ctr.get(head_pred)*tail_pred_head_cnt;
}
}
}
process_next_pred:;
}
unsigned rule_cnt = orig.get_num_rules();
for (unsigned ri=0; ri<rule_cnt; ri++) {
rule * r = orig.get_rule(ri);
func_decl * head_pred = r->get_decl();
if (inlining_allowed(orig, head_pred)) {
continue;
}
bool has_multi_head_pred = false;
unsigned pt_len = r->get_positive_tail_size();
for (unsigned ti = 0; ti<pt_len; ++ti) {
func_decl * pred = r->get_decl(ti);
if (!inlining_allowed(orig, pred)) {
continue;
}
if (m_head_pred_ctr.get(pred)<=1) {
continue;
}
if (has_multi_head_pred) {
m_forbidden_preds.insert(pred);
something_forbidden = true;
}
else {
has_multi_head_pred = true;
}
}
}
return something_forbidden;
}
void mk_rule_inliner::plan_inlining(rule_set const & orig)
{
count_pred_occurrences(orig);
scoped_ptr<rule_set> candidate_inlined_set = create_allowed_rule_set(orig);
while (forbid_preds_from_cycles(*candidate_inlined_set)) {
candidate_inlined_set = create_allowed_rule_set(orig);
}
if (forbid_multiple_multipliers(orig, *candidate_inlined_set)) {
candidate_inlined_set = create_allowed_rule_set(orig);
}
TRACE("dl", tout<<"rules to be inlined:\n" << (*candidate_inlined_set); );
SASSERT(m_inlined_rules.get_num_rules() == 0);
const rule_stratifier::comp_vector& comps = candidate_inlined_set->get_stratifier().get_strats();
for (rule_stratifier::item_set * stratum : comps) {
SASSERT(stratum->size() == 1);
func_decl * pred = *stratum->begin();
for (rule * r : candidate_inlined_set->get_predicate_rules(pred)) {
transform_rule(orig, r, m_inlined_rules);
}
}
TRACE("dl", tout << "inlined rules after mutual inlining:\n" << m_inlined_rules; );
for (rule * r : m_inlined_rules) {
datalog::del_rule(m_mc, *r, false);
}
}
bool mk_rule_inliner::transform_rule(rule_set const& orig, rule * r0, rule_set& tgt) {
bool modified = false;
rule_ref_vector todo(m_rm);
todo.push_back(r0);
while (!todo.empty()) {
rule_ref r(todo.back(), m_rm);
todo.pop_back();
unsigned pt_len = r->get_positive_tail_size();
unsigned i = 0;
for (; i < pt_len && !inlining_allowed(orig, r->get_decl(i)); ++i) {};
CTRACE("dl", has_quantifier(*r.get()), r->display(m_context, tout););
if (has_quantifier(*r.get())) {
tgt.add_rule(r);
continue;
}
if (i == pt_len) {
tgt.add_rule(r);
continue;
}
modified = true;
func_decl * pred = r->get_decl(i);
const rule_vector& pred_rules = m_inlined_rules.get_predicate_rules(pred);
for (rule * inl_rule : pred_rules) {
rule_ref inl_result(m_rm);
if (try_to_inline_rule(*r.get(), *inl_rule, i, inl_result)) {
todo.push_back(inl_result);
}
}
}
if (modified) {
datalog::del_rule(m_mc, *r0, true);
}
return modified;
}
bool mk_rule_inliner::transform_rules(const rule_set & orig, rule_set & tgt) {
bool something_done = false;
for (rule* rl : orig) {
rule_ref r(rl, m_rm);
func_decl * pred = r->get_decl();
something_done |= !inlining_allowed(orig, pred) && transform_rule(orig, r, tgt);
}
if (something_done && m_mc) {
for (rule* r : orig) {
if (inlining_allowed(orig, r->get_decl())) {
datalog::del_rule(m_mc, *r, true);
}
}
}
return something_done;
}
Check whether rule r is oriented in a particular ordering.
This is to avoid infinite cycle of inlining in the eager inliner.
Out ordering is lexicographic, comparing atoms first on stratum they are in,
then on arity and then on ast ID of their func_decl.
*/
bool mk_rule_inliner::is_oriented_rewriter(rule * r, rule_stratifier const& strat) {
func_decl * head_pred = r->get_decl();
unsigned head_strat = strat.get_predicate_strat(head_pred);
unsigned head_arity = head_pred->get_arity();
unsigned pt_len = r->get_positive_tail_size();
for (unsigned ti=0; ti < pt_len; ++ti) {
func_decl * pred = r->get_decl(ti);
unsigned pred_strat = strat.get_predicate_strat(pred);
SASSERT(pred_strat <= head_strat);
if (pred_strat == head_strat) {
if (pred->get_arity()>head_arity
|| (pred->get_arity()==head_arity && pred->get_id()>=head_pred->get_id()) ) {
return false;
}
}
}
return true;
}
bool mk_rule_inliner::do_eager_inlining(rule * r, rule_set const& rules, rule_ref& res) {
if (r->has_negation()) {
return false;
}
SASSERT(rules.is_closed());
const rule_stratifier& strat = rules.get_stratifier();
func_decl * head_pred = r->get_decl();
unsigned pt_len = r->get_positive_tail_size();
for (unsigned ti = 0; ti < pt_len; ++ti) {
func_decl * pred = r->get_decl(ti);
if (pred == head_pred || m_preds_with_facts.contains(pred)) { continue; }
const rule_vector& pred_rules = rules.get_predicate_rules(pred);
rule * inlining_candidate = nullptr;
unsigned rule_cnt = pred_rules.size();
if (rule_cnt == 0) {
inlining_candidate = nullptr;
}
else if (rule_cnt == 1) {
inlining_candidate = pred_rules[0];
}
else {
inlining_candidate = nullptr;
for (unsigned ri = 0; ri < rule_cnt; ++ri) {
rule * pred_rule = pred_rules[ri];
if (!m_unifier.unify_rules(*r, ti, *pred_rule)) {
continue;
}
if (inlining_candidate != nullptr) {
goto process_next_tail;
}
inlining_candidate = pred_rule;
}
}
if (inlining_candidate == nullptr) {
res = nullptr;
datalog::del_rule(m_mc, *r, false);
return true;
}
if (!is_oriented_rewriter(inlining_candidate, strat)) {
goto process_next_tail;
}
if (!try_to_inline_rule(*r, *inlining_candidate, ti, res)) {
datalog::del_rule(m_mc, *r, false);
res = nullptr;
}
return true;
process_next_tail:;
}
return false;
}
bool mk_rule_inliner::do_eager_inlining(scoped_ptr<rule_set> & rules) {
scoped_ptr<rule_set> res = alloc(rule_set, m_context);
bool done_something = false;
rule_set::iterator rend = rules->end();
for (rule_set::iterator rit = rules->begin(); rit!=rend; ++rit) {
rule_ref r(*rit, m_rm);
rule_ref replacement(m_rm);
while (r && do_eager_inlining(r, *rules, replacement)) {
r = replacement;
done_something = true;
}
if (!r) {
continue;
}
res->add_rule(r);
}
if (done_something) {
rules = res.detach();
}
return done_something;
}
Inline predicates that are known to not be join-points.
P(1,x) :- P(0,y), phi(x,y)
P(0,x) :- P(1,z), psi(x,z)
->
P(1,x) :- P(1,z), phi(x,y), psi(y,z)
whenever P(0,x) is not unifiable with the
body of the rule where it appears (P(1,z))
and P(0,x) is unifiable with at most one (?)
other rule (and it does not occur negatively).
*/
bool mk_rule_inliner::visitor::operator()(expr* e) {
m_unifiers.append(m_positions.find(e));
TRACE("dl",
tout << "unifier: " << (m_unifiers.empty()?0:m_unifiers.back());
tout << " num unifiers: " << m_unifiers.size();
tout << " num positions: " << m_positions.find(e).size() << "\n";
output_predicate(m_context, to_app(e), tout); tout << "\n";);
return m_unifiers.size() <= 1;
}
void mk_rule_inliner::visitor::reset(unsigned sz) {
m_unifiers.reset();
m_can_remove.reset();
m_can_remove.resize(sz, true);
m_can_expand.reset();
m_can_expand.resize(sz, true);
m_positions.reset();
}
unsigned_vector const& mk_rule_inliner::visitor::add_position(expr* e, unsigned j) {
auto& value = m_positions.insert_if_not_there(e, unsigned_vector());
value.push_back(j);
return value;
}
unsigned_vector const& mk_rule_inliner::visitor::del_position(expr* e, unsigned j) {
obj_map<expr, unsigned_vector>::obj_map_entry * et = m_positions.find_core(e);
SASSERT(et && et->get_data().m_value.contains(j));
et->get_data().m_value.erase(j);
return et->get_data().m_value;
}
void mk_rule_inliner::add_rule(rule_set const& source, rule* r, unsigned i) {
bool_vector& can_remove = m_head_visitor.can_remove();
bool_vector& can_expand = m_head_visitor.can_expand();
app* head = r->get_head();
func_decl* headd = head->get_decl();
m_head_visitor.add_position(head, i);
m_head_index.insert(head);
m_pinned.push_back(r);
if (source.is_output_predicate(headd) ||
m_preds_with_facts.contains(headd)) {
can_remove.set(i, false);
TRACE("dl", output_predicate(m_context, head, tout << "cannot remove: " << i << " "); tout << "\n";);
}
unsigned tl_sz = r->get_uninterpreted_tail_size();
for (unsigned j = 0; j < tl_sz; ++j) {
app* tail = r->get_tail(j);
m_tail_visitor.add_position(tail, i);
m_tail_index.insert(tail);
}
bool can_exp =
tl_sz == 1
&& r->get_positive_tail_size() == 1
&& !m_preds_with_facts.contains(r->get_decl(0))
&& !source.is_output_predicate(r->get_decl(0));
can_expand.set(i, can_exp);
}
void mk_rule_inliner::del_rule(rule* r, unsigned i) {
app* head = r->get_head();
m_head_visitor.del_position(head, i);
unsigned tl_sz = r->get_uninterpreted_tail_size();
for (unsigned j = 0; j < tl_sz; ++j) {
app* tail = r->get_tail(j);
m_tail_visitor.del_position(tail, i);
}
}
#define PRT(_x_) ((_x_)?"T":"F")
bool mk_rule_inliner::inline_linear(scoped_ptr<rule_set>& rules) {
bool done_something = false;
unsigned sz = rules->get_num_rules();
m_head_visitor.reset(sz);
m_tail_visitor.reset(sz);
m_head_index.reset();
m_tail_index.reset();
TRACE("dl", rules->display(tout););
rule_ref_vector acc(m_rm);
for (unsigned i = 0; i < sz; ++i) {
acc.push_back(rules->get_rule(i));
}
bool_vector& can_remove = m_head_visitor.can_remove();
bool_vector& can_expand = m_head_visitor.can_expand();
for (unsigned i = 0; i < sz; ++i) {
add_rule(*rules, acc[i].get(), i);
}
rule_counter& vc = m_rm.get_counter();
unsigned max_var = 0;
for (unsigned i = 0; i < sz; ++i) {
rule* r = acc[i].get();
max_var = std::max(max_var, vc.get_max_var(r->get_head()));
unsigned tl_sz = r->get_uninterpreted_tail_size();
for (unsigned j = 0; j < tl_sz; ++j) {
max_var = std::max(max_var, vc.get_max_var(r->get_tail(j)));
}
}
m_subst.reset();
m_subst.reserve_vars(max_var+1);
m_subst.reserve_offsets(std::max(m_tail_index.get_approx_num_regs(), 2+m_head_index.get_approx_num_regs()));
bool_vector valid;
valid.reset();
valid.resize(sz, true);
bool allow_branching = m_context.get_params().xform_inline_linear_branch();
for (unsigned i = 0; i < sz; ++i) {
while (true) {
rule_ref r(acc[i].get(), m_rm);
TRACE("dl", r->display(m_context, tout << "processing: " << i << "\n"););
if (!valid.get(i)) {
TRACE("dl", tout << "invalid: " << i << "\n";);
break;
}
if (!can_expand.get(i)) {
TRACE("dl", tout << "cannot expand: " << i << "\n";);
break;
}
m_head_visitor.reset();
m_head_index.unify(r->get_tail(0), m_head_visitor);
unsigned num_head_unifiers = m_head_visitor.get_unifiers().size();
if (num_head_unifiers != 1) {
TRACE("dl", tout << "no unique unifier " << num_head_unifiers << "\n";);
break;
}
unsigned j = m_head_visitor.get_unifiers()[0];
if (!can_remove.get(j) || !valid.get(j) || i == j) {
TRACE("dl", tout << PRT(can_remove.get(j)) << " " << PRT(valid.get(j)) << " " << PRT(i != j) << "\n";);
break;
}
rule* r2 = acc[j].get();
TRACE("dl", output_predicate(m_context, r2->get_head(), tout << "unify head: "); tout << "\n";);
m_tail_visitor.reset();
m_tail_index.unify(r2->get_head(), m_tail_visitor);
unsigned_vector const& tail_unifiers = m_tail_visitor.get_unifiers();
unsigned num_tail_unifiers = tail_unifiers.size();
SASSERT(!tail_unifiers.empty());
if (!allow_branching && num_tail_unifiers != 1) {
TRACE("dl", tout << "too many tails " << num_tail_unifiers << "\n";);
break;
}
rule_ref rl_res(m_rm);
if (!try_to_inline_rule(*r.get(), *r2, 0, rl_res)) {
TRACE("dl", r->display(m_context, tout << "inlining failed\n"); r2->display(m_context, tout); );
break;
}
done_something = true;
TRACE("dl", r->display(m_context, tout); r2->display(m_context, tout); rl_res->display(m_context, tout); );
del_rule(r, i);
add_rule(*rules, rl_res.get(), i);
r = rl_res;
acc[i] = r.get();
can_expand.set(i, can_expand.get(j));
if (num_tail_unifiers == 1) {
TRACE("dl", tout << "setting invalid: " << j << "\n";);
valid.set(j, false);
datalog::del_rule(m_mc, *r2, true);
del_rule(r2, j);
}
max_var = std::max(max_var, vc.get_max_rule_var(*r.get()));
m_subst.reserve_vars(max_var+1);
}
}
if (done_something) {
scoped_ptr<rule_set> res = alloc(rule_set, m_context);
for (unsigned i = 0; i < sz; ++i) {
if (valid.get(i)) {
res->add_rule(acc[i].get());
}
}
res->inherit_predicates(*rules);
TRACE("dl", res->display(tout););
rules = res.detach();
}
return done_something;
}
rule_set * mk_rule_inliner::operator()(rule_set const & source) {
bool something_done = false;
ref<horn_subsume_model_converter> hsmc;
if (source.get_num_rules() == 0) {
return nullptr;
}
for (rule const* r : source)
if (has_quantifier(*r))
return nullptr;
if (m_context.get_model_converter()) {
hsmc = alloc(horn_subsume_model_converter, m);
}
m_mc = hsmc.get();
scoped_ptr<rule_set> res = alloc(rule_set, m_context);
if (m_context.get_params().xform_inline_eager()) {
TRACE("dl", source.display(tout << "before eager inlining\n"););
plan_inlining(source);
try {
something_done = transform_rules(source, *res);
}
catch (has_new_quantifier) {
return nullptr;
}
VERIFY(res->close());
if (do_eager_inlining(res)) {
something_done = true;
}
TRACE("dl", res->display(tout << "after eager inlining\n"););
}
if (something_done) {
res->inherit_predicates(source);
}
else {
res = alloc(rule_set, source);
}
if (m_context.get_params().xform_inline_linear() && inline_linear(res)) {
something_done = true;
}
if (!something_done) {
res = nullptr;
}
else {
m_context.add_model_converter(hsmc.get());
}
return res.detach();
}
};