/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* * Main authors: * Jip J. Dekker * Guido Tack */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #include #include #include #include #include #include #include // #define DBG_CALLTRACE(msg) std::cerr << msg #define DBG_CALLTRACE(msg) \ do { \ } while (0) namespace MiniZinc { Val AggregationCtx::createVec(Interpreter* interpreter, int timestamp) const { return Val(Vec::a(interpreter, timestamp, stack)); } AggregationCtx::~AggregationCtx(void) { /// TODO } const std::string AggregationCtx::symbol_to_string[] = {"AND", "OR", "VEC", "OTHER"}; const std::string Interpreter::status_to_string[] = {"Roger", "Aborted", "Inconsistent", "Error"}; void Interpreter::pushAgg(const Val& v, int stackOffset) { assert(stackOffset < 0); assert(_agg.size() + stackOffset >= 0); // push value onto surrounding context _agg[_agg.size() + stackOffset].push(this, v); } void Interpreter::pushConstraint(Constraint* c) { _agg.back().constraints.push_back(c); } PropStatus Interpreter::subscribe(Constraint* c) { // TODO: This disables propagation after trailing, this shouldn't be necessary if we can correctly // communicate with the solvers if (trail.is_trailed(_root_var)) { return PS_OK; } if (c->pred() < primitiveMap().size()) { return primitiveMap()[c->pred()]->subscribe(*this, c); } return PS_OK; } void Interpreter::unsubscribe(Constraint* c) { if (c->pred() < primitiveMap().size()) { primitiveMap()[c->pred()]->unsubscribe(*this, c); } } void Interpreter::schedule(Constraint* c, const Variable::SubscriptionEvent& ev) { if (!c->scheduled()) { _propQueue.push_back(c); c->scheduled(true); } } void Interpreter::deschedule(Constraint* c) { /// TODO: is this required? seems to be unused if (c->scheduled()) { auto it = std::find(_propQueue.begin(), _propQueue.end(), c); if (it != _propQueue.end()) { _propQueue.erase(it); } } } void Interpreter::propagate(void) { while (!_propQueue.empty()) { Constraint* c = _propQueue.front(); _propQueue.pop_front(); c->scheduled(false); auto ps = primitiveMap()[c->pred()]->propagate(*this, c); switch (ps) { case PS_OK: break; case PS_FAILED: _status = INCONSISTENT; return; case PS_ENTAILED: // TODO: remove constraint break; } } } void Interpreter::run(void) { if (_status != ROGER) { return; } for (;;) { DBG_INTERPRETER(frame().pc << " "); switch (frame().bs->instr(frame().pc)) { case BytecodeStream::ADDI: { if (frame().pc > 130) { DBG_INTERPRETER("NOOOOO!\n"); } int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); int r3 = frame().bs->reg(frame().pc); DBG_INTERPRETER("ADDI R" << r1 << "(" << reg(frame(), r1).toString() << ")" << " R" << r2 << "(" << reg(frame(), r2).toString() << ")"); assign(frame(), r3, reg(frame(), r1) + reg(frame(), r2)); DBG_INTERPRETER(" R" << r3 << "(" << reg(frame(), r3).toString() << ")" << "\n"); } break; case BytecodeStream::SUBI: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); int r3 = frame().bs->reg(frame().pc); DBG_INTERPRETER("SUBI R" << r1 << "(" << reg(frame(), r1).toString() << ")" << " R" << r2 << "(" << reg(frame(), r2).toString() << ")"); assign(frame(), r3, reg(frame(), r1) - reg(frame(), r2)); DBG_INTERPRETER(" R" << r3 << "(" << reg(frame(), r3).toString() << ")" << "\n"); } break; case BytecodeStream::MULI: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); int r3 = frame().bs->reg(frame().pc); DBG_INTERPRETER("MULI R" << r1 << "(" << reg(frame(), r1).toString() << ")" << " R" << r2 << "(" << reg(frame(), r2).toString() << ")"); assign(frame(), r3, reg(frame(), r1) * reg(frame(), r2)); DBG_INTERPRETER(" R" << r3 << "(" << reg(frame(), r3).toString() << ")" << "\n"); } break; case BytecodeStream::DIVI: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); int r3 = frame().bs->reg(frame().pc); if (reg(frame(), r2) == 0) frame().pc = frame().bs->size() - 1; else assign(frame(), r3, reg(frame(), r1) / reg(frame(), r2)); assign(frame(), r3, reg(frame(), r1) / reg(frame(), r2)); DBG_INTERPRETER("DIVI R" << r1 << "(" << reg(frame(), r1).toString() << ")" << " R" << r2 << "(" << reg(frame(), r2).toString() << ")" << " " << r3 << "(" << reg(frame(), r3).toString() << ")" << "\n"); } break; case BytecodeStream::MODI: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); int r3 = frame().bs->reg(frame().pc); if (reg(frame(), r2) == 0) frame().pc = frame().bs->size() - 1; else assign(frame(), r3, reg(frame(), r1) % reg(frame(), r2)); DBG_INTERPRETER("MODI R" << r1 << "(" << reg(frame(), r1).toString() << ")" << " R" << r2 << "(" << reg(frame(), r2).toString() << ")" << " " << r3 << "(" << reg(frame(), r3).toString() << ")" << "\n"); } break; case BytecodeStream::INCI: { int r1 = frame().bs->reg(frame().pc); assign(frame(), r1, reg(frame(), r1) + 1); DBG_INTERPRETER("INCI R" << r1 << "(" << reg(frame(), r1).toString() << ")" << "\n"); } break; case BytecodeStream::DECI: { int r1 = frame().bs->reg(frame().pc); DBG_INTERPRETER("DECI R" << r1 << "(" << reg(frame(), r1).toString() << ")" << "\n"); assign(frame(), r1, reg(frame(), r1) - 1); } break; case BytecodeStream::IMMI: { Val i = frame().bs->intval(frame().pc); int r1 = frame().bs->reg(frame().pc); assign(frame(), r1, i); DBG_INTERPRETER("IMMI " << i.toString() << " R" << r1 << "(" << reg(frame(), r1).toString() << ")" << "\n"); } break; case BytecodeStream::CLEAR: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); assert(r1 <= r2); for (int i = r1; i <= r2; i++) { assign(frame(), i, 0); } DBG_INTERPRETER("CLEAR " << " R" << r1 << " " << r2 << "\n"); } break; case BytecodeStream::LOAD_GLOBAL: { int i = frame().bs->reg(frame().pc); int r1 = frame().bs->reg(frame().pc); globals.cp(this, i, _registers, frame().reg_offset + r1); DBG_INTERPRETER("LOAD_GLOBAL " << i << " R" << r1 << "(" << reg(frame(), r1).toString(DBG_TRIM_OUTPUT) << ")" << "\n"); } break; case BytecodeStream::STORE_GLOBAL: { int r1 = frame().bs->reg(frame().pc); int i = frame().bs->reg(frame().pc); _registers.cp(this, frame().reg_offset + r1, globals, i); DBG_INTERPRETER("STORE_GLOBAL R" << r1 << "(" << reg(frame(), r1).toString(DBG_TRIM_OUTPUT) << ")" << " " << i << "\n"); } break; case BytecodeStream::MOV: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); DBG_INTERPRETER("MOV R" << r1 << "(" << reg(frame(), r1).toString(DBG_TRIM_OUTPUT) << ")"); _registers.cp(this, frame().reg_offset + r1, frame().reg_offset + r2); DBG_INTERPRETER(" R" << r2 << "(" << reg(frame(), r2).toString(DBG_TRIM_OUTPUT) << ")" << "\n"); } break; case BytecodeStream::JMP: { int i = frame().bs->reg(frame().pc); DBG_INTERPRETER("JMP " << i << "\n"); frame().pc = i; } break; case BytecodeStream::JMPIF: { int r0 = frame().bs->reg(frame().pc); int i = frame().bs->reg(frame().pc); DBG_INTERPRETER("JMPIF R" << r0 << "(" << reg(frame(), r0).toString() << ")" << " " << i << "\n"); if (reg(frame(), r0) != 0) { frame().pc = i; } } break; case BytecodeStream::JMPIFNOT: { int r0 = frame().bs->reg(frame().pc); int i = frame().bs->reg(frame().pc); DBG_INTERPRETER("JMPIFNOT R" << r0 << "(" << reg(frame(), r0).toString() << ")" << " " << i << "\n"); if (reg(frame(), r0) == 0) { frame().pc = i; } } break; case BytecodeStream::EQI: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); int r3 = frame().bs->reg(frame().pc); DBG_INTERPRETER("EQI R" << r1 << "(" << reg(frame(), r1).toString() << ")" << " R" << r2 << "(" << reg(frame(), r2).toString() << ")"); assert(reg(frame(), r1) == reg(frame(), r2) || reg(frame(), r1).toInt() != reg(frame(), r2).toInt()); assign(frame(), r3, reg(frame(), r1) == reg(frame(), r2)); DBG_INTERPRETER(" R" << r3 << "(" << reg(frame(), r3).toString() << ")" << "\n"); } break; case BytecodeStream::LTI: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); int r3 = frame().bs->reg(frame().pc); DBG_INTERPRETER("LTI R" << r1 << "(" << reg(frame(), r1).toString() << ")" << " R" << r2 << "(" << reg(frame(), r2).toString() << ")"); assign(frame(), r3, (reg(frame(), r1) < reg(frame(), r2))); DBG_INTERPRETER(" R" << r3 << "(" << reg(frame(), r3).toString() << ")" << "\n"); } break; case BytecodeStream::LEI: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); int r3 = frame().bs->reg(frame().pc); DBG_INTERPRETER("LEI R" << r1 << "(" << reg(frame(), r1).toString() << ")" << " R" << r2 << "(" << reg(frame(), r2).toString() << ")"); assign(frame(), r3, (reg(frame(), r1) <= reg(frame(), r2))); DBG_INTERPRETER(" R" << r3 << "(" << reg(frame(), r3).toString() << ")" << "\n"); } break; case BytecodeStream::AND: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); int r3 = frame().bs->reg(frame().pc); DBG_INTERPRETER("AND R" << r1 << "(" << reg(frame(), r1).toString() << ")" << " R" << r2 << "(" << reg(frame(), r2).toString() << ")"); assign(frame(), r3, (reg(frame(), r1) != 0 && reg(frame(), r2) != 0)); DBG_INTERPRETER(" " << r3 << "(" << reg(frame(), r3).toString() << ")" << "\n"); } break; case BytecodeStream::OR: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); int r3 = frame().bs->reg(frame().pc); DBG_INTERPRETER("OR R" << r1 << "(" << reg(frame(), r1).toString() << ")" << " R" << r2 << "(" << reg(frame(), r2).toString() << ")"); assign(frame(), r3, (reg(frame(), r1) != 0 || reg(frame(), r2) != 0)); DBG_INTERPRETER(" " << r3 << "(" << reg(frame(), r3).toString() << ")" << "\n"); } break; case BytecodeStream::NOT: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); DBG_INTERPRETER("NOT R" << r1); assign(frame(), r2, (reg(frame(), r1) == 0)); DBG_INTERPRETER(" R" << r2 << "(" << reg(frame(), r2).toString() << ")" << "\n"); } break; case BytecodeStream::XOR: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); int r3 = frame().bs->reg(frame().pc); DBG_INTERPRETER("XOR R" << r1 << "(" << reg(frame(), r1).toString() << ")" << " R" << r2 << "(" << reg(frame(), r2).toString() << ")"); assign(frame(), r3, ((reg(frame(), r1) != 0) ^ (reg(frame(), r2) != 0))); DBG_INTERPRETER(" R" << r3 << "(" << reg(frame(), r3).toString() << ")" << "\n"); } break; case BytecodeStream::ISPAR: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); DBG_INTERPRETER("ISPAR R" << r1 << "(" << reg(frame(), r1).toString(DBG_TRIM_OUTPUT) << ")"); Val v = Val::follow_alias(reg(frame(), r1), this); if (v.isInt()) { assign(frame(), r2, 1); } else if (v.isVar()) { assign(frame(), r2, 0); } else { assert(v.isVec()); Val ret = v.toVec()->isPar(); assign(frame(), r2, ret); } DBG_INTERPRETER(" R" << r2 << "(" << reg(frame(), r2).toString() << ")" << "\n"); } break; case BytecodeStream::ISEMPTY: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); DBG_INTERPRETER("ISEMPTY R" << r1 << "(" << reg(frame(), r1).toString(DBG_TRIM_OUTPUT) << ")"); assert(reg(frame(), r1).isInt() || reg(frame(), r1).isVec()); assign(frame(), r2, (reg(frame(), r1).isInt() || reg(frame(), r1).size() == 0)); DBG_INTERPRETER(" R" << r2 << "(" << reg(frame(), r2).toString() << ")" << "\n"); } break; case BytecodeStream::LENGTH: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); DBG_INTERPRETER("LENGTH R" << r1 << "(" << reg(frame(), r1).toString(DBG_TRIM_OUTPUT) << ")"); assert(reg(frame(), r1).isVec()); if (!reg(frame(), r1).toVec()->hasIndexSet()) { assign(frame(), r2, reg(frame(), r1).size()); } else { assign(frame(), r2, reg(frame(), r1)[0].size()); } DBG_INTERPRETER(" R" << r2 << "(" << reg(frame(), r2).toString() << ")" << "\n"); } break; case BytecodeStream::GET_VEC: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); int r3 = frame().bs->reg(frame().pc); DBG_INTERPRETER("GET_VEC R" << r1 << "(" << reg(frame(), r1).toString(DBG_TRIM_OUTPUT) << ")" << " R" << r2 << "(" << reg(frame(), r2).toString() << ")"); assert(reg(frame(), r1).isVec()); assert(reg(frame(), r2).isInt()); assert(reg(frame(), r2) > 0 && reg(frame(), r2) <= reg(frame(), r1).size()); Val v = Val::follow_alias(reg(frame(), r1)[reg(frame(), r2).toInt() - 1], this); assign(frame(), r3, v); DBG_INTERPRETER(" R" << r3 << "(" << v.toString(DBG_TRIM_OUTPUT) << ")" << "\n"); } break; case BytecodeStream::GET_ARRAY: { Val n = frame().bs->intval(frame().pc); int r1 = frame().bs->reg(frame().pc); DBG_INTERPRETER("GET_ARRAY " << n.toString() << " R" << r1 << "(" << reg(frame(), r1).toString(DBG_TRIM_OUTPUT) << ")"); std::vector idx(n.toInt()); for (int i = 0; i < n; i++) { int rr = frame().bs->reg(frame().pc); DBG_INTERPRETER(" R" << rr << "(" << reg(frame(), rr).toString(DBG_TRIM_OUTPUT) << ")"); idx[i] = reg(frame(), rr); } int r_res = frame().bs->reg(frame().pc); int r_cond = frame().bs->reg(frame().pc); assert(reg(frame(), r1).isVec()); bool success = true; Val v = Val(-2000); Val content = reg(frame(), r1).toVec()->raw_data(); if (reg(frame(), r1).toVec()->hasIndexSet()) { // N-Dimensional array representation Val index_set = reg(frame(), r1).toVec()->index_set(); std::vector> dimensions; Val realdim = 1; for (int i = 0; i < index_set.size(); i += 2) { Val a = index_set[i]; Val b = index_set[i + 1]; dimensions.emplace_back(a, b); realdim *= b - a + 1; } Val realidx = 0; for (int i = 0; i < idx.size(); i++) { Val ix = idx[i]; if (ix < dimensions[i].first || ix > dimensions[i].second) { success = false; break; } realdim /= dimensions[i].second - dimensions[i].first + 1; realidx += (ix - dimensions[i].first) * realdim; } assert(realidx >= 0 && realidx < content.size()); if (success) { v = Val::follow_alias(content[realidx.toInt()], this); } } else { // Compact array representation (index set 1..n) success = 1 <= idx[0] && idx[0] <= reg(frame(), r1).size(); if (success) { v = Val::follow_alias(content[idx[0].toInt() - 1], this); } } assign(frame(), r_res, v); assign(frame(), r_cond, Val(success)); DBG_INTERPRETER(" R" << r_res << "(" << v.toString(DBG_TRIM_OUTPUT) << ") R" << r_cond << "(" << Val(success).toString(DBG_TRIM_OUTPUT) << ")" << "\n"); } break; case BytecodeStream::LB: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); DBG_INTERPRETER("LB R" << r1 << "(" << reg(frame(), r1).toString(DBG_TRIM_OUTPUT) << ")"); Val v = Val::follow_alias(reg(frame(), r1), this); assign(frame(), r2, v.lb()); DBG_INTERPRETER(" R" << r2 << "(" << reg(frame(), r2).toString(DBG_TRIM_OUTPUT) << ")" << "\n"); } break; case BytecodeStream::UB: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); DBG_INTERPRETER("UB R" << r1 << "(" << reg(frame(), r1).toString(DBG_TRIM_OUTPUT) << ")"); Val v = Val::follow_alias(reg(frame(), r1), this); assign(frame(), r2, v.ub()); DBG_INTERPRETER(" R" << r2 << "(" << reg(frame(), r2).toString(DBG_TRIM_OUTPUT) << ")" << "\n"); } break; case BytecodeStream::DOM: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); DBG_INTERPRETER("DOM R" << r1 << "(" << reg(frame(), r1).toString(DBG_TRIM_OUTPUT) << ")"); Val v = Val::follow_alias(reg(frame(), r1), this); if (v.isInt()) { assign(frame(), r2, Val(Vec::a(this, newIdent(), {v, v}))); } else if (v.isVar()) { Variable* var = v.toVar(); assign(frame(), r2, Val(var->domain())); } else { throw Error("Error: dom on invalid type"); } DBG_INTERPRETER(" R" << r2 << "(" << reg(frame(), r2).toString(DBG_TRIM_OUTPUT) << ")" << "\n"); } break; case BytecodeStream::MAKE_SET: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); DBG_INTERPRETER("MAKE_SET R" << r1 << "(" << reg(frame(), r1).toString(DBG_TRIM_OUTPUT) << ")"); Val v1 = Val::follow_alias(reg(frame(), r1), this); assert(reg(frame(), r1).isVec()); Vec* a1 = v1.toVec(); std::vector result; if (a1->size() > 0) { std::vector vals(a1->size()); for (int i = 0; i < a1->size(); i++) vals[i] = (*a1)[i]; std::sort(vals.begin(), vals.end()); Val l(vals[0]); Val u(vals[0]); for (int i = 1; i < vals.size(); ++i) { if (u + 1 < vals[i]) { result.emplace_back(l); result.emplace_back(u); l = vals[i]; } u = vals[i]; } result.emplace_back(l); result.emplace_back(u); } Val result_val(Vec::a(this, newIdent(), result)); assign(frame(), r2, result_val); DBG_INTERPRETER(" R" << r2 << "(" << result_val.toString(DBG_TRIM_OUTPUT) << ")" << "\n"); } break; case BytecodeStream::INTERSECTION: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); int r3 = frame().bs->reg(frame().pc); DBG_INTERPRETER("INTERSECTION R" << r1 << "(" << reg(frame(), r1).toString(DBG_TRIM_OUTPUT) << ") R" << r2 << "(" << reg(frame(), r2).toString(DBG_TRIM_OUTPUT) << ")"); Val v1 = Val::follow_alias(reg(frame(), r1), this); Val v2 = Val::follow_alias(reg(frame(), r2), this); Val result_val; if (v1.isInt()) { result_val = v2; } else if (v2.isInt()) { result_val = v1; } else { Vec* s1 = v1.toVec(); Vec* s2 = v2.toVec(); VecSetRanges vsr1(s1); VecSetRanges vsr2(s2); Ranges::Inter inter(vsr1, vsr2); std::vector result; for (; inter(); ++inter) { result.emplace_back(inter.min()); result.emplace_back(inter.max()); } result_val = Val(Vec::a(this, newIdent(), result)); } assign(frame(), r3, result_val); DBG_INTERPRETER(" R" << r3 << "(" << result_val.toString(DBG_TRIM_OUTPUT) << ")" << "\n"); } break; case BytecodeStream::UNION: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); int r3 = frame().bs->reg(frame().pc); DBG_INTERPRETER("UNION R" << r1 << "(" << reg(frame(), r1).toString(DBG_TRIM_OUTPUT) << ") R" << r2 << "(" << reg(frame(), r2).toString(DBG_TRIM_OUTPUT) << ")"); Val v1 = Val::follow_alias(reg(frame(), r1), this); Val v2 = Val::follow_alias(reg(frame(), r2), this); Val result_val; if (v1.isInt()) { result_val = v1; } else if (v2.isInt()) { result_val = v2; } else { Vec* s1 = v1.toVec(); Vec* s2 = v2.toVec(); VecSetRanges vsr1(s1); VecSetRanges vsr2(s2); Ranges::Union union_r(vsr1, vsr2); std::vector result; for (; union_r(); ++union_r) { result.emplace_back(union_r.min()); result.emplace_back(union_r.max()); } result_val = Val(Vec::a(this, newIdent(), result)); } assign(frame(), r3, result_val); DBG_INTERPRETER(" R" << r3 << "(" << result_val.toString(DBG_TRIM_OUTPUT) << ")" << "\n"); } break; case BytecodeStream::DIFF: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); int r3 = frame().bs->reg(frame().pc); DBG_INTERPRETER("DIFF R" << r1 << "(" << reg(frame(), r1).toString(DBG_TRIM_OUTPUT) << ") R" << r2 << "(" << reg(frame(), r2).toString(DBG_TRIM_OUTPUT) << ")"); Val v1 = Val::follow_alias(reg(frame(), r1), this); Val v2 = Val::follow_alias(reg(frame(), r2), this); Val result_val; Vec* s1 = v1.toVec(); Vec* s2 = v2.toVec(); VecSetRanges vsr1(s1); VecSetRanges vsr2(s2); Ranges::Diff diff_r(vsr1, vsr2); std::vector result; for (; diff_r(); ++diff_r) { result.emplace_back(diff_r.min()); result.emplace_back(diff_r.max()); } result_val = Val(Vec::a(this, newIdent(), result)); assign(frame(), r3, result_val); DBG_INTERPRETER(" R" << r3 << "(" << result_val.toString(DBG_TRIM_OUTPUT) << ")" << "\n"); } break; case BytecodeStream::INTERSECT_DOMAIN: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); int r3 = frame().bs->reg(frame().pc); DBG_INTERPRETER("INTERSECT_DOMAIN R" << r1 << "(" << reg(frame(), r1).toString(DBG_TRIM_OUTPUT) << ") R" << r2 << "(" << reg(frame(), r2).toString(DBG_TRIM_OUTPUT) << ")"); Val v1 = Val::follow_alias(reg(frame(), r1), this); Val v2 = reg(frame(), r2); assert(v2.isVec()); Val result_val; Vec* s2 = v2.toVec(); if (v1.isVar()) { Vec* s1 = v1.toVar()->domain(); VecSetRanges vsr1(s1); VecSetRanges vsr2(s2); Ranges::Inter inter(vsr1, vsr2); std::vector result; for (; inter(); ++inter) { result.emplace_back(inter.min()); result.emplace_back(inter.max()); } if (result.empty()) { _status = INCONSISTENT; // Invariant: Last instruction in the frame is always an ABORT instruction frame().pc = frame().bs->size() - 1; break; } v1.toVar()->domain(this, result, true); Val v1a = Val::follow_alias(v1); if (v1a.isVar()) { result_val = Val(v1a.toVar()->domain()); } else { result_val = Val(Vec::a(this, newIdent(), {v1a, v1a})); } } else { Ranges::Const vsr1(v1, v1); VecSetRanges vsr2(s2); if (Ranges::subset(vsr1, vsr2)) { result_val = Val(Vec::a(this, newIdent(), {v1, v1})); } else { _status = INCONSISTENT; // Invariant: Last instruction in the frame is always an ABORT instruction frame().pc = frame().bs->size() - 1; break; } } assign(frame(), r3, result_val); DBG_INTERPRETER(" R" << r3 << "(" << result_val.toString(DBG_TRIM_OUTPUT) << ")" << "\n"); } break; case BytecodeStream::RET: { DBG_INTERPRETER("RET"); if (frame().cse_frame_depth < _cse_stack.size()) { CSEFrame& info = _cse_stack.back(); DBG_INTERPRETER(" %-- " << info.proc << " (" << _procs[info.proc].name << ") " << BytecodeProc::mode_to_string[info.mode]); } DBG_INTERPRETER("\n"); assert(!_stack.empty()); execute_ret: if (_stack.size() == 1) { // Always leave final frame on the stack // Copy remaining constraints into toplevel assert(_agg.size() == 1); for (Constraint* c : _agg[0].constraints) { root()->addDefinition(this, c); } _agg[0].constraints.clear(); return; } DBG_CALLTRACE("<"); for (size_t i = 0; i < _stack.size() - 1; ++i) { DBG_CALLTRACE("--"); } DBG_CALLTRACE(" " << (_stack.back()._mode == BytecodeProc::ROOT || _stack.back()._mode == BytecodeProc::ROOT_NEG ? "POSTED" : (_cse_stack.back().stack_size == _agg.back().size() - 1 ? _agg[_agg.size() - 1].back().toString(DBG_TRIM_OUTPUT) : "")) << "\n"); while (_cse_stack.size() > frame().cse_frame_depth) { CSEFrame& entry = _cse_stack.back(); int nargs = _procs[entry.proc].nargs; if (entry.mode == BytecodeProc::ROOT || entry.mode == BytecodeProc::ROOT_NEG) { Val v = Val(1); cse_insert(entry.proc, entry.getKey(), entry.mode, v); } else if (entry.stack_size == _agg.back().size() - 1) { Val ret = _agg[_agg.size() - 1].back(); cse_insert(entry.proc, entry.getKey(), entry.mode, ret); } else { entry.destroy(*this); } _cse_stack.pop_back(); } _registers.resize(this, frame().reg_offset); _stack.pop_back(); } break; case BytecodeStream::CALL: { char mode_c = frame().bs->chr(frame().pc); int code = frame().bs->reg(frame().pc); bool cse = frame().bs->chr(frame().pc); assert(code >= 0); assert(code < _procs.size()); assert(mode_c >= 0); assert(mode_c <= BytecodeProc::MAX_MODE); auto mode = static_cast(mode_c); int n = _procs[code].nargs; DBG_INTERPRETER("CALL " << BytecodeProc::mode_to_string[mode] << " " << code << "(" << _procs[code].name << ")" << (cse ? "" : " no_cse")); for (size_t i = 0; i < _stack.size(); ++i) { DBG_CALLTRACE("--"); } DBG_CALLTRACE("> " << _procs[code].name << "("); // Allocate new frame on the stack _stack.emplace_back(_procs[code].mode[mode], code, mode); frame().reg_offset = _registers.size(); _registers.resize(this, frame().reg_offset + frame().bs->maxRegister() + 1); // Copy arguments for (int i = 0; i < n; i++) { int r = _stack[_stack.size() - 2].bs->reg(_stack[_stack.size() - 2].pc); assign(frame(), i, reg(_stack[_stack.size() - 2], r)); DBG_INTERPRETER(" R" << r << "(" << reg(frame(), i).toString(DBG_TRIM_OUTPUT) << ")" << ((i + 1 < n) ? "" : "\n")); DBG_CALLTRACE(reg(frame(), i).toString(DBG_TRIM_OUTPUT) << ((i + 1 < n) ? ", " : ")\n")); } auto immediate_return = [&] { _registers.resize(this, frame().reg_offset); _stack.pop_back(); }; // Lookup for Common Subexpression Elimination size_t cse_depth = _cse_stack.size(); if (cse) { _cse_stack.emplace_back(*this, code, mode, _registers.iter_n(frame().reg_offset), _registers.iter_n(frame().reg_offset + n), n, _agg.back().size()); // Lookup item in CSE auto lookup = cse_find(code, _cse_stack.back().getKey(), mode); if (lookup.second) { _cse_stack.back().destroy(*this); _cse_stack.pop_back(); if (mode == BytecodeProc::ROOT || mode == BytecodeProc::ROOT_NEG) { assert(lookup.first.isInt()); if (lookup.first.toInt() != 1) { _status = INCONSISTENT; // Invariant: Last instruction in the frame is always an ABORT instruction frame().pc = frame().bs->size() - 1; } } else { pushAgg(lookup.first, -1); } immediate_return(); break; } } if (_procs[code].mode[mode].size() == 0 || _procs[code].delay) { DBG_INTERPRETER((_procs[code].delay ? "--- Delayed CALL\n" : "--- FZN Builtin\n")); DBG_CALLTRACE("<"); for (size_t i = 0; i < _stack.size(); ++i) { DBG_CALLTRACE("--"); } // this is a FlatZinc builtin if (code == PrimitiveMap::MK_INTVAR) { assert(mode == BytecodeProc::ROOT); Variable* v = Variable::a(this, _registers[frame().reg_offset], true, newIdent()); pushAgg(Val(v), -1); DBG_CALLTRACE(" " << Val(v).toString() << "\n"); } else { assert(mode == BytecodeProc::ROOT || mode == BytecodeProc::ROOT_NEG); auto c = Constraint::a(this, code, mode, _registers.iter_n(frame().reg_offset), _registers.iter_n(frame().reg_offset + n), n); if (!(c.first || c.second)) { // Propagation failed _status = INCONSISTENT; // Invariant: Last instruction in the frame is always an ABORT instruction frame().pc = frame().bs->size() - 1; break; } if (c.first) { pushConstraint(c.first); } if (cse) { Val ret(c.second); cse_insert(code, _cse_stack.back().getKey(), mode, ret); _cse_stack.pop_back(); } DBG_CALLTRACE(" POSTED\n"); /// TODO: delayed calls // if (_procs[code].delay) { // delayed_calls.push_back(c); // } } immediate_return(); } } break; case BytecodeStream::BUILTIN: { int code = frame().bs->reg(frame().pc); assert(code >= 0); DBG_INTERPRETER("BUILTIN " << code << "(" << _procs[code].name << ")"); assert(code < primitiveMap().size()); // this is a Interpreter builtin int n = _procs[code].nargs; std::vector args(n); for (int i = 0; i < n; i++) { int r = frame().bs->reg(frame().pc); // Do not increase the reference count. Vector is destroyed after // BUILTIN execution without cleanup args[i] = reg(frame(), r); DBG_INTERPRETER(" R" << r << "(" << args[i].toString(DBG_TRIM_OUTPUT) << ")"); } DBG_INTERPRETER("\n"); primitiveMap()[code]->execute(*this, args); } break; case BytecodeStream::TCALL: { char mode_c = frame().bs->chr(frame().pc); int code = frame().bs->reg(frame().pc); bool cse = frame().bs->chr(frame().pc); assert(code >= 0); assert(code < _procs.size()); assert(mode_c >= 0); assert(mode_c <= BytecodeProc::MAX_MODE); auto mode = static_cast(mode_c); DBG_INTERPRETER("TCALL " << BytecodeProc::mode_to_string[mode] << " " << code << "(" << _procs[code].name << ")" << (cse ? "" : " no_cse") << "\n"); for (size_t i = 0; i < _stack.size() - 1; ++i) { DBG_CALLTRACE("=="); } DBG_CALLTRACE("> " << _procs[code].name << "("); int nargs = _procs[code].nargs; for (int i = 0; i < nargs; ++i) { DBG_CALLTRACE(reg(frame(), i).toString(DBG_TRIM_OUTPUT) << ((i + 1 < nargs) ? ", " : ")\n")); } if (cse) { _cse_stack.emplace_back(*this, code, mode, _registers.iter_n(frame().reg_offset), _registers.iter_n(frame().reg_offset + nargs), nargs, _agg.back().size()); bool found; Val ret; std::tie(ret, found) = cse_find(code, _cse_stack.back().getKey(), mode); if (found) { _cse_stack.back().destroy(*this); _cse_stack.pop_back(); // RET with CSE found value if (mode == BytecodeProc::ROOT || mode == BytecodeProc::ROOT_NEG) { assert(ret.isInt()); if (ret.toInt() != 1) { _status = INCONSISTENT; // Invariant: Last instruction in the frame is always an ABORT instruction frame().pc = frame().bs->size() - 1; } } else { pushAgg(ret, -1); } // TODO: Does this actually "return" correctly?? It seems the code would continue; while (_cse_stack.size() > frame().cse_frame_depth) { CSEFrame& entry = _cse_stack.back(); int nargs = _procs[entry.proc].nargs; cse_insert(entry.proc, entry.getKey(), entry.mode, ret); _cse_stack.pop_back(); } _registers.resize(this, frame().reg_offset); _stack.pop_back(); break; } } if (_procs[code].mode[mode].size() == 0 || _procs[code].delay) { DBG_INTERPRETER((_procs[code].delay ? "--- Delayed CALL\n" : "--- FZN Builtin\n")); // this is a FlatZinc builtin assert(mode == BytecodeProc::ROOT || mode == BytecodeProc::ROOT_NEG); auto c = Constraint::a(this, code, mode, _registers.iter_n(frame().reg_offset), _registers.iter_n(frame().reg_offset + nargs), nargs); if (!(c.first || c.second)) { // Propagation failed _status = INCONSISTENT; // Invariant: Last instruction in the frame is always an ABORT instruction frame().pc = frame().bs->size() - 1; break; } if (c.first) { pushConstraint(c.first); } if (cse) { Val ret(c.second); // TODO: Does this actually "return" correctly?? It seems this would not set all // necessary values in the _cse_stack cse_insert(code, _cse_stack.back().getKey(), mode, ret); _cse_stack.pop_back(); } /// TODO: delayed calls // if (_procs[code].delay) { // delayed_calls.push_back(def); // } goto execute_ret; } else { // Replace frame with new procedure frame()._pred = code; frame()._mode = mode; frame().bs = &_procs[code].mode[mode]; _registers.resize(this, frame().reg_offset + frame().bs->maxRegister() + 1); frame().pc = 0; } } break; case BytecodeStream::ITER_ARRAY: { int r1 = frame().bs->reg(frame().pc); int l = frame().bs->reg(frame().pc); DBG_INTERPRETER("ITER_ARRAY " << r1 << " " << l << "\n"); assert(reg(frame(), r1).isVec()); if (reg(frame(), r1).toVec()->hasIndexSet()) { assert(reg(frame(), r1).size() == 2); assert(reg(frame(), r1)[0].isVec()); Vec* v(reg(frame(), r1)[0].toVec()); _loops.push_back(LoopState(v, l)); } else { // Compact array representation (index set 1..n) Vec* v(reg(frame(), r1).toVec()); _loops.push_back(LoopState(v, l)); } } break; case BytecodeStream::ITER_VEC: { int r1 = frame().bs->reg(frame().pc); int l = frame().bs->reg(frame().pc); DBG_INTERPRETER("ITER_VEC " << r1 << " " << l << "\n"); assert(reg(frame(), r1).isVec()); Vec* v(reg(frame(), r1).toVec()); _loops.push_back(LoopState(v, l)); } break; case BytecodeStream::ITER_RANGE: { int r1 = frame().bs->reg(frame().pc); int r2 = frame().bs->reg(frame().pc); int lbl = frame().bs->reg(frame().pc); Val lb = Val::follow_alias(reg(frame(), r1), this); Val ub = Val::follow_alias(reg(frame(), r2), this); assert(lb.isInt()); assert(ub.isInt()); DBG_INTERPRETER("ITER_RANGE " << r1 << " " << r2 << " " << lbl << "\n"); _loops.push_back(LoopState(lb.toInt(), ub.toInt(), lbl)); } break; case BytecodeStream::ITER_NEXT: { int r1 = frame().bs->reg(frame().pc); DBG_INTERPRETER("ITER_NEXT"); assert(_loops.size() > 0); LoopState& outer(_loops.back()); if (outer.pos < outer.end) { Val v; if (outer.is_range) { v = outer.pos; outer.pos++; } else { Val* ptr(reinterpret_cast(outer.pos)); v = Val::follow_alias(*ptr); outer.pos += sizeof(Val*); } assign(frame(), r1, v); DBG_INTERPRETER(" R" << r1 << "(" << v.toString(DBG_TRIM_OUTPUT) << ")" << "\n"); } else { frame().pc = outer.exit_pc; _loops.pop_back(); DBG_INTERPRETER(" (EXIT " << outer.exit_pc << ")\n"); } } break; case BytecodeStream::ITER_BREAK: { int num = frame().bs->intval(frame().pc).toInt(); DBG_INTERPRETER("ITER_BREAK " << num << "\n"); assert(_loops.size() >= num); auto it(_loops.end() - num); frame().pc = it->exit_pc; _loops.erase(it, _loops.end()); } break; case BytecodeStream::TRACE: { int r = frame().bs->reg(frame().pc); DBG_INTERPRETER("TRACE " << r << "\n"); std::cerr << reg(frame(), r).toString() << "\n"; // std::cerr << frame().reg.e(r)->cast(); // std::cerr << frame()().reg.e(r) << "\n"; } break; case BytecodeStream::ABORT: { DBG_INTERPRETER("ABORT\n"); _registers.resize(this, 0); _stack.clear(); // TODO: Should the Aggregation stack be emptied? if (_status == ROGER) { _status = ABORTED; } return; } case BytecodeStream::PUSH: { int r = frame().bs->reg(frame().pc); DBG_INTERPRETER("PUSH R" << r << " (" << reg(frame(), r).toString(DBG_TRIM_OUTPUT) << ")\n"); assert(!_agg.empty()); _agg.back().push(this, reg(frame(), r)); } break; case BytecodeStream::POP: { int r = frame().bs->reg(frame().pc); assert(!_agg.empty()); assert(!_agg.back().empty()); assign(frame(), r, _agg.back().back()); DBG_INTERPRETER("POP R" << r << " (" << reg(frame(), r).toString(DBG_TRIM_OUTPUT) << ")\n"); _agg.back().pop(this); } break; case BytecodeStream::POST: { int r = frame().bs->reg(frame().pc); DBG_INTERPRETER("POST R" << r << " (" << reg(frame(), r).toString(DBG_TRIM_OUTPUT) << ")\n"); Val v1 = Val::follow_alias(reg(frame(), r), this); if (v1.isInt()) { if (v1 == 0) { _status = INCONSISTENT; // Invariant: Last instruction in the frame is always an ABORT instruction frame().pc = frame().bs->size() - 1; } } else { bool success = v1.toVar()->setVal(this, 1); if (!success) { _status = INCONSISTENT; // Invariant: Last instruction in the frame is always an ABORT instruction frame().pc = frame().bs->size() - 1; } /// TODO: check why this is adding a ref /// Shouldn't be necessary // v1.toVar()->addRef(this); } } break; case BytecodeStream::OPEN_AGGREGATION: { int r = frame().bs->chr(frame().pc); DBG_INTERPRETER("OPEN_AGGREGATION " << AggregationCtx::symbol_to_string[r] << ", depth " << _agg.size() + 1 << "\n"); assert(r >= 0 && r <= AggregationCtx::VCTX_OTHER); if (r == AggregationCtx::VCTX_OTHER || r == AggregationCtx::VCTX_VEC || _agg.empty() || _agg.back().symbol != r) { // Push a new aggregation context _agg.emplace_back(this, r); } else { // Increment depth counter for current aggregation context _agg.back().n_symbols++; } } break; case BytecodeStream::SIMPLIFY_LIN: { DBG_INTERPRETER("SIMPLIFY_LIN"); int r0 = frame().bs->reg(frame().pc); int r1 = frame().bs->reg(frame().pc); Val i = frame().bs->intval(frame().pc); DBG_INTERPRETER(" R" << r0 << "(" << reg(frame(), r0).toString(DBG_TRIM_OUTPUT) << ")"); DBG_INTERPRETER(" R" << r1 << "(" << reg(frame(), r1).toString(DBG_TRIM_OUTPUT) << ")"); DBG_INTERPRETER(" " << i.toString()); int r2 = frame().bs->reg(frame().pc); int r3 = frame().bs->reg(frame().pc); int r4 = frame().bs->reg(frame().pc); std::vector coeffs({1, -1}); std::vector vars({reg(frame(), r0), reg(frame(), r1)}); Val d = i; simplify_linexp(coeffs, vars, d); Val coeffs_v = Val(Vec::a(this, newIdent(), coeffs)); Val vars_v = Val(Vec::a(this, newIdent(), vars)); assign(frame(), r2, coeffs_v); assign(frame(), r3, vars_v); assign(frame(), r4, -d); DBG_INTERPRETER(" R" << r2 << "(" << reg(frame(), r2).toString(DBG_TRIM_OUTPUT) << ")"); DBG_INTERPRETER(" R" << r3 << "(" << reg(frame(), r3).toString(DBG_TRIM_OUTPUT) << ")\n"); DBG_INTERPRETER(" R" << r4 << "(" << reg(frame(), r4).toString(DBG_TRIM_OUTPUT) << ")\n"); } break; case BytecodeStream::CLOSE_AGGREGATION: { DBG_INTERPRETER("CLOSE_AGGREGATION (" << AggregationCtx::symbol_to_string[_agg.back().symbol] << ", depth " << _agg.size() << ")\n"); assert(!_agg.empty()); // Decrement depth counter for current aggregation context _agg.back().n_symbols--; if (_agg.back().n_symbols == 0) { // Result produced by this aggregation Variable* result = nullptr; assert(_agg.size() >= 2); switch (_agg.back().symbol) { case AggregationCtx::VCTX_AND: { // Create a conjunction on the definition stack std::vector args; args.reserve(_agg.back().size()); bool isFalse = false; for (int i = 0; i < _agg.back().size(); i++) { const Val& v = _agg.back()[i]; if (v.isInt()) { if (v == 0) { // Disjunction is constant false isFalse = true; break; } } else { args.push_back(v); } } if (isFalse || args.empty()) { /// TODO: check, what if _agg.size()==2 as below? // Conjunction is constant true or false pushAgg(!isFalse, -2); } else if (_agg.size() == 2) { // Push into root context for (Val v : args) { auto success = v.toVar()->setVal(this, 1); // FIXME: Deal with unsuccessful setVal assert(success); } pushAgg(1, -2); // Why do we need a definition? If this is in ROOT, then all arguments should be // true /* Definition* d = * Definition::a(this,boolean_domain(),false,PrimitiveMap::FORALL,BytecodeProc::ROOT, */ /* {Val(Vec::a(this,newIdent(),args))},-1); */ /* if (defs) { */ /* d->appendBefore(this, defs); */ /* } else { */ /* defs = d; */ /* } */ } else if (args.size() == 1) { pushAgg(args[0], -2); } else { Vec* arr = Vec::a(this, newIdent(), args); result = Variable::a(this, boolean_domain(), false, newIdent()); auto def_c = Constraint::a(this, PrimitiveMap::FORALL, BytecodeProc::ROOT, {Val(arr), Val(result)}); assert(def_c.first); result->addRef(this); result->addDefinition(this, def_c.first); pushAgg(Val(result), -2); RefCountedObject::rmRef(this, result); } } break; case AggregationCtx::VCTX_OR: { // Create a clause on the definition stack, and push a reference // to it onto the aggregation stack std::vector pos; std::vector neg; bool isTrue = false; for (int i = 0; i < _agg.back().size(); i++) { const Val& v = Val::follow_alias(_agg.back()[i]); if (v.isInt()) { if (v != 0) { // Disjunction is constant true isTrue = true; break; } } else { Variable* cur = v.toVar(); if (Constraint* defby = cur->defined_by()) { if (defby->pred() == PrimitiveMap::BOOLNOT) { if (Val::follow_alias(defby->arg(0)) == v) { neg.push_back(Val::follow_alias(defby->arg(1))); } else { neg.push_back(Val::follow_alias(defby->arg(0))); } continue; } } pos.push_back(v); } } if (isTrue || (pos.empty() && neg.empty())) { // Disjunction is constant true or false pushAgg(isTrue, -2); } else if (pos.size() == 1 && neg.empty()) { if (_agg.size() == 2) { auto success = pos[0].toVar()->setVal(this, 1); // FIXME: Deal with unsuccessful setVal assert(success); pushAgg(1, -2); } else { pushAgg(pos[0], -2); } } else { // Push into root context Vec* vpos = Vec::a(this, newIdent(), pos); Vec* vneg = Vec::a(this, newIdent(), neg); if (_agg.size() == 2) { auto c = Constraint::a(this, PrimitiveMap::CLAUSE, BytecodeProc::ROOT, {Val(vpos), Val(vneg)}, 0, 1, true); assert(c.first); root()->addDefinition(this, c.first); pushAgg(1, -2); } else { result = Variable::a(this, boolean_domain(), false, newIdent()); auto def_c = Constraint::a(this, PrimitiveMap::CLAUSE_REIF, BytecodeProc::ROOT, {Val(vpos), Val(vneg), Val(result)}, 0, 1, true); assert(def_c.first); result->addRef(this); result->addDefinition(this, def_c.first); pushAgg(Val(result), -2); RefCountedObject::rmRef(this, result); } } } break; case AggregationCtx::VCTX_VEC: { // Create a vector on the aggregation stack _agg[_agg.size() - 2].push(this, _agg.back().createVec(this, newIdent())); } break; case AggregationCtx::VCTX_OTHER: // When closing a VCTX_OTHER context, it should contain at most one value assert(_agg.back().size() <= 1); if (_agg.back().size() == 1) { if (_agg.back()[0].isVar()) { result = _agg.back()[0].toVar(); } // push value onto surrounding context pushAgg(_agg.back()[0], -2); } break; } _agg.back().destroyStack(this); if (result && !_agg.back().constraints.empty()) { // Aggregation produced constraints and exactly one return value, which is a variable if (result->timestamp() >= _agg.back().def_ident_start) { // the definition was produced by the current frame, so // attach all constraints to it for (Constraint* c : _agg.back().constraints) { result->addDefinition(this, c); } _agg.back().constraints.clear(); // INVARIANT: The result of aggregation is not referenced by any of the registers. assert(std::none_of(_registers.cbegin() + frame().reg_offset, _registers.cend(), [result](Val v) { return v.contains(Val(result)); })); result->binding(this, false); } } if (!_agg.back().constraints.empty()) { if (_agg.size() == 2) { // only one frame left, so move all constraints into root context for (Constraint* c : _agg.back().constraints) { root()->addDefinition(this, c); } } else { // Move definitions to parent aggregation for (Constraint* c : _agg.back().constraints) { _agg[_agg.size() - 2].constraints.push_back(c); } } _agg.back().constraints.clear(); } _agg.pop_back(); } } break; } assert(!frame().bs->eos(frame().pc)); } } void Interpreter::dumpState(std::ostream& os) { Variable::dump(root(), _procs, os); // if (!_agg.empty()) { // Definition::dump(_agg.back().def_stack, _procs, os, true); // } } void Interpreter::dumpState(const BytecodeFrame& bf, std::ostream& os) { for (unsigned int i = bf.reg_offset; i < bf.reg_offset + bf.bs->maxRegister(); i++) { os << " R" << i << " = " << _registers[i].toString() << "\n"; } } void Interpreter::dumpState() { dumpState(std::cerr); } Interpreter::~Interpreter(void) { globals.destroy(this); _registers.resize(this, 0); _stack.clear(); for (auto& a : _agg) { a.destroyStack(this); // a.destroyDef(this); /// TODO: replace with what? Just delete all constraints? } assert(cse.size() == _procs.size()); for (int i = 0; i < _procs.size(); ++i) { switch (_procs[i].nargs) { case 1: { auto table = static_cast>*>(cse[i]); table->destroy(this); break; } case 2: { auto table = static_cast>*>(cse[i]); table->destroy(this); break; } case 3: { auto table = static_cast>*>(cse[i]); table->destroy(this); break; } case 4: { auto table = static_cast>*>(cse[i]); table->destroy(this); break; } default: { auto table = static_cast*>(cse[i]); table->destroy(this); break; } } } RefCountedObject::rmRef(this, infinite_dom); RefCountedObject::rmRef(this, boolean_dom); RefCountedObject::rmRef(this, true_dom); } void Interpreter::call(int code, std::vector&& args) { if (_status != ROGER) { return; } assert(code >= 0); assert(code < _procs.size()); int n = _procs[code].nargs; const bool cse = true; BytecodeProc::Mode mode = BytecodeProc::ROOT; DBG_INTERPRETER("Interpreter::call " << BytecodeProc::mode_to_string[mode] << " " << code << "(" << _procs[code].name << ")" << (cse ? "" : " no_cse")); DBG_CALLTRACE("\n> " << _procs[code].name << "("); assert(n == args.size()); for (int i = 0; i < n; i++) { DBG_INTERPRETER(" R" << i << "(" << args[i].toString(DBG_TRIM_OUTPUT) << ")"); DBG_CALLTRACE(args[i].toString(DBG_TRIM_OUTPUT) << ((i + 1 < n) ? ", " : ")\n")); } DBG_INTERPRETER("\n"); // Lookup for Common Subexpression Elimination size_t cse_depth = _cse_stack.size(); if (cse) { _cse_stack.emplace_back(*this, code, mode, args.cbegin(), args.cend(), n, _agg.back().size()); // Lookup item in CSE auto lookup = cse_find(code, _cse_stack.back().getKey(), mode); if (lookup.second) { _cse_stack.back().destroy(*this); _cse_stack.pop_back(); if (mode == BytecodeProc::ROOT || mode == BytecodeProc::ROOT_NEG) { assert(lookup.first.isInt()); if (lookup.first.toInt() != 1) { _status = INCONSISTENT; // Invariant: Last instruction in the frame is always an ABORT instruction return; } } else { // pushAgg(lookup.first, -1); assert(false); // Delayed calls can only be ROOT mode } return; } } if (_procs[code].mode[mode].size() == 0) { DBG_INTERPRETER("--- FZN Builtin\n"); // this is a FlatZinc builtin assert(code != PrimitiveMap::MK_INTVAR); assert(mode == BytecodeProc::ROOT || mode == BytecodeProc::ROOT_NEG); auto c = Constraint::a(this, code, mode, args); if (!(c.first || c.second)) { // Propagation failed _status = INCONSISTENT; return; } if (c.first) { pushConstraint(c.first); } if (cse) { Val ret(c.second); cse_insert(code, _cse_stack.back().getKey(), mode, ret); _cse_stack.pop_back(); } return; } // Make the next instruction RET _stack.back().pc--; _stack.emplace_back(_procs[code].mode[mode], code, mode); BytecodeFrame& newFrame = _stack.back(); newFrame.reg_offset = _registers.size(); _registers.resize(this, newFrame.reg_offset + newFrame.bs->maxRegister() + 1); for (int i = 0; i < args.size(); i++) { assign(newFrame, i, args[i]); } newFrame.cse_frame_depth = cse_depth; return run(); } bool Interpreter::runDelayed() { std::unordered_map wave = std::move(delayed_constraints); delayed_constraints.clear(); for (auto pair : wave) { // Only run delayed constraints that have a definition. Constraint* c = pair.first; assert(c->delayed()); if (_procs[c->pred()].mode[BytecodeProc::ROOT].size() == 0) { continue; } // Remove placeholder constraint from variable Val alias = Val::follow_alias(Val(pair.second)); Variable* v = alias.isVar() ? alias.toVar() : root(); // TODO: This might be quite expensive. Should we store _definitions differently? auto pos = std::find(std::begin(v->_definitions), std::end(v->_definitions), c); assert(pos != v->_definitions.end()); v->_definitions.erase(pos); // Copy arguments into vector std::vector args(c->size()); for (int i = 0; i < c->size(); ++i) { args[i] = c->arg(i); } // Execute constraint std::swap(v, _root_var); call(c->pred(), std::move(args)); std::swap(v, _root_var); // Stop if execution caused an error if (_status != ROGER) { break; } } return !delayed_constraints.empty(); } size_t Trail::save_state(MiniZinc::Interpreter* interpreter) { trail_size.emplace_back(var_list_trail.size(), obj_trail.size(), alias_trail.size(), domain_trail.size(), def_trail.size()); timestamp_trail.push_back(interpreter->_identCount); for (int i = 0; i < interpreter->_procs.size(); ++i) { switch (interpreter->_procs[i].nargs) { case 1: { auto table = static_cast>*>(interpreter->cse[i]); table->push(interpreter, !last_operation_pop); break; } case 2: { auto table = static_cast>*>(interpreter->cse[i]); table->push(interpreter, !last_operation_pop); break; } case 3: { auto table = static_cast>*>(interpreter->cse[i]); table->push(interpreter, !last_operation_pop); break; } case 4: { auto table = static_cast>*>(interpreter->cse[i]); table->push(interpreter, !last_operation_pop); break; } default: { auto table = static_cast*>(interpreter->cse[i]); table->push(interpreter, !last_operation_pop); break; } } } last_operation_pop = false; return len(); } void Trail::untrail(MiniZinc::Interpreter* interpreter) { assert(len() > 0); assert(interpreter->_stack.size() == 1); size_t vlt_size, ot_size, at_size, dt_size, deft_size; std::tie(vlt_size, ot_size, at_size, dt_size, deft_size) = trail_size.back(); trail_size.pop_back(); int timestamp = timestamp_trail.back(); timestamp_trail.pop_back(); // Reconstruct destroyed items while (obj_trail.size() > ot_size) { auto obj = obj_trail.back(); switch (obj->rcoType()) { case RefCountedObject::VAR: static_cast(obj)->reconstruct(interpreter); break; case RefCountedObject::VEC: static_cast(obj)->reconstruct(interpreter); break; default: assert(false); } obj_trail.pop_back(); } // Restore hedge pointers back to their previous versions while (var_list_trail.size() > vlt_size) { auto entry = var_list_trail.back(); *entry.first = entry.second; var_list_trail.pop_back(); } // Restore original definitions for created aliases while (alias_trail.size() > at_size) { Variable* var; Val dom; std::tie(var, dom) = alias_trail.back(); var->unalias(interpreter, dom); dom.rmMemRef(interpreter); alias_trail.pop_back(); } // Restore original domains while (domain_trail.size() > dt_size) { Variable* var; Vec* dom; std::tie(var, dom) = domain_trail.back(); Val nd(dom); nd.addRef(interpreter); var->_domain.rmRef(interpreter); var->_domain = nd; RefCountedObject::rmMemRef(interpreter, dom); domain_trail.pop_back(); } // Remove all additions/changes to the CSE table for (int i = 0; i < interpreter->_procs.size(); ++i) { switch (interpreter->_procs[i].nargs) { case 1: { auto table = static_cast>*>(interpreter->cse[i]); table->pop(interpreter); break; } case 2: { auto table = static_cast>*>(interpreter->cse[i]); table->pop(interpreter); break; } case 3: { auto table = static_cast>*>(interpreter->cse[i]); table->pop(interpreter); break; } case 4: { auto table = static_cast>*>(interpreter->cse[i]); table->pop(interpreter); break; } default: { auto table = static_cast*>(interpreter->cse[i]); table->pop(interpreter); break; } } } // Undo all changes to definitions while (def_trail.size() > deft_size) { Variable* var; Constraint* con; bool remove; std::tie(var, con, remove) = def_trail.back(); if (remove) { assert(var->definitions().back() == con); var->_definitions.pop_back(); } else { assert(false); } def_trail.pop_back(); } // Remove all newly created variables Variable* back = interpreter->root()->prev(); while (back->timestamp() > timestamp) { Variable* rem = back; back = back->prev(); rem->destroy(interpreter); Variable::free(rem); } // TODO: Should we remove newly created propagators?? // Reset the timestamp count to its previous value interpreter->_identCount = timestamp; last_operation_pop = true; } void Interpreter::optimize(void) {} } // namespace MiniZinc