/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* * Main authors: * Graeme Gange */ /* 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/. */ #ifndef __MINIZINC_CODEGEN_HH__ #define __MINIZINC_CODEGEN_HH__ // Simple single-pass bytecode compiler for MiniZinc. #include #include #include #include #include #include namespace MiniZinc { struct CodeGen; // Location is either a register or global. class Loc { private: Loc(int _x) : x(_x) {} public: static Loc reg(int r) { return r << 1; } static Loc global(int g) { return (g << 1) | 1; } inline bool is_reg(void) const { return !(x & 1); } inline bool is_global(void) const { return x & 1; } inline int index(void) const { return x >> 1; } int x; }; // Code generators for structures. // Expressions can be executed in three modes: // - If Boolean, it is always just executed. // - If non-Boolean but total, it is similarly just executed. // - If non-Boolean and partial, we first evaluate its partiality // constraints in the enclosing Boolean expression, _then_ // evaluate its numeric component. // For any function that is not total, we generate two procedures: // one which is // A slightly more structured representation for code generation. class CG_Value { public: enum CG_ValueKind { V_Immi, V_Global, V_Reg, V_Proc, V_Label }; protected: CG_Value(CG_ValueKind _kind, int _value) : kind(_kind), value(_value) {} public: CG_Value(void) : kind(V_Immi), value(0) {} static CG_Value reg(int r) { return CG_Value(V_Reg, r); } static CG_Value global(int r) { return CG_Value(V_Global, r); } static CG_Value immi(long long int r) { return CG_Value(V_Immi, r); } static CG_Value proc(int p) { return CG_Value(V_Proc, p); } static CG_Value label(int l) { return CG_Value(V_Label, l); } CG_ValueKind kind; long long int value; }; struct CG_Instr { protected: CG_Instr(unsigned int _tag) : tag(_tag) {} public: static CG_Instr instr(BytecodeStream::Instr i) { return static_cast(i) << 1; } static CG_Instr label(unsigned int l) { return (l << 1) + 1; } unsigned int tag; std::vector params; }; struct CG_ProcID { protected: CG_ProcID(int _p) : p(_p) {} public: static CG_ProcID builtin(int b) { return CG_ProcID((b << 1) | 1); } static CG_ProcID proc(int p) { return CG_ProcID(p << 1); } bool is_builtin(void) const { return p & 1; } unsigned int id(void) const { return p >> 1; } static CG_ProcID of_val(CG_Value v) { assert(v.kind == CG_Value::V_Proc); return CG_ProcID(v.value); } unsigned int p; }; struct CG_Builder { std::vector instrs; void append(CG_Builder& o) { instrs.insert(instrs.end(), o.instrs.begin(), o.instrs.end()); o.instrs.clear(); } void clear(void) { instrs.clear(); } }; // When we bind a non-Boolean expression, we also construct // a set of conditions we need to insert into any use-contexts. // We use CG_Cond to track the conditionality of values. struct CG_Cond { // FIXME: Currently not GC'd, so this will leak a bunch of memory. enum Kind { CC_Reg, CC_Call, CC_And }; class C_Reg; class C_Call; class C_And; struct cond_reg { cond_reg(void) : is_root(0), is_seen(0), is_par(0), reg(-1) {} cond_reg(int _reg, bool _par) : is_root(0), is_seen(0), is_par(_par), reg(_reg) {} int operator*(void) const { assert(reg >= 0); return reg; } bool has_reg(void) const { return reg >= 0; } int is_root : 1; int is_seen : 1; int is_par : 1; int reg : 29; }; class _T { public: cond_reg reg[2]; _T(void) {} _T(int r, bool is_par) { reg[0].reg = r; reg[0].is_par = is_par; } virtual Kind kind(void) const = 0; }; class T { T(uintptr_t _p) : p(_p) {} public: T(void) : p(0) {} static T ttt(void) { return T(0); } static T fff(void) { return T(1); } static T of_ptr(_T* p) { return T(reinterpret_cast(p)); } T operator~(void) const { return T(p ^ 1); } T operator^(bool b) const { return T(p ^ b); } bool sign(void) const { return p & 1; } _T* get(void) const { return reinterpret_cast<_T*>(p & ~((uintptr_t)1)); } uintptr_t p; }; static T ttt(void) { return T::ttt(); } static T fff(void) { return T::fff(); } class C_Reg : public _T { public: static const Kind _kind = CC_Reg; Kind kind(void) const { return _kind; } C_Reg(int reg, bool is_par) : _T(reg, is_par) {} }; class C_Call : public _T { public: static const Kind _kind = CC_Call; Kind kind(void) const { return _kind; } C_Call(ASTString _ident, BytecodeProc::Mode _m, bool _cse, const std::vector& _ty, const std::vector& _params) : ident(_ident), m(std::move(_m)), ty(_ty), cse(_cse), params(_params) {} ASTString ident; // Return Types + argument types BytecodeProc::Mode m; bool cse; std::vector ty; std::vector params; }; class C_And : public _T { public: static const Kind _kind = CC_And; Kind kind(void) const { return _kind; } C_And(BytecodeProc::Mode _m, std::vector& _children) : m(_m), children(_children) { assert(children.size() > 1); } BytecodeProc::Mode m; std::vector children; }; static T reg(int r, bool is_par) { return T::of_ptr(new C_Reg(r, is_par)); } static T call(ASTString ident, BytecodeProc::Mode m, bool cse, const std::vector& ty, const std::vector& params) { return _call(ident, m, cse, ty, params); } static T _call(ASTString ident, BytecodeProc::Mode m, bool cse, const std::vector& ty, const std::vector& params) { return T::of_ptr(new C_Call(ident, m, cse, ty, params)); } template static T _forall(BytecodeProc::Mode m, std::vector& args, T next, Args... rest) { args.push_back(next); return _forall(m, args, rest...); } template static void clear_seen(It b, It e) { for (; b != e; ++b) { if (!b->get()) continue; b->get()->reg[b->sign()].is_seen = false; } } static T _forall(BytecodeProc::Mode m, std::vector& args) { size_t count = 0; for (T x : args) { _T* p(x.get()); if (!p) { // Either true or false. if (x.sign()) { clear_seen(args.begin(), args.end()); return T::fff(); } continue; } // Otherwise, check if we've already seen this or its negation. if (p->reg[1 - x.sign()].is_seen || p->reg[1 - x.sign()].is_root) { clear_seen(args.begin(), args.end()); return T::fff(); } if (p->reg[x.sign()].is_seen || p->reg[x.sign()].is_root) continue; // Haven't seen this yet, so save and mark it. p->reg[x.sign()].is_seen = true; args[count] = x; count++; } clear_seen(args.begin(), args.end()); args.resize(count); if (count == 0) { return T::ttt(); } if (count == 1) { return args[0]; } return T::of_ptr(new C_And(m, args)); } template static T forall(BytecodeProc::Mode m, Args... args) { std::vector vec; return _forall(m, vec, args...); } static T forall(BytecodeProc::Mode m, std::vector& args) { return _forall(m, args); } template static T _exists(BytecodeProc::Mode m, std::vector& args, T next, Args... rest) { args.push_back(next); return _exists(m, args, rest...); } static T _exists(BytecodeProc::Mode m, std::vector& args); template static T exists(BytecodeProc::Mode m, Args... args) { std::vector vec; return _exists(m, vec, args...); } static T exists(BytecodeProc::Mode m, std::vector& args) { return _exists(m, args); } }; // An environment should never outlive its parent. template class CG_Env { private: CG_Env(CG_Env* _p) : p(_p), sz(p ? p->sz : 0) {} // Forbid copy and assignment operators. CG_Env(const CG_Env& _o) = delete; CG_Env& operator=(const CG_Env& o) = delete; public: CG_Env(void) : p(nullptr), sz(0) {} CG_Env(CG_Env&& o) : bindings(std::move(o.bindings)), available(std::move(o.available)), available_csts(std::move(o.available_csts)), available_ranges(std::move(o.available_ranges)), cached_conds(std::move(o.cached_conds)), occurs(std::move(o.occurs)), p(o.p), sz(o.sz) {} void clear_cached_conds() { for (CG_Cond::T c : cached_conds) { CG_Cond::_T* p(c.get()); bool sign(c.sign()); p->reg[sign].reg = -1; } cached_conds.clear(); } void record_cached_cond(CG_Cond::T c) { cached_conds.push_back(c); } class NotFound : public std::exception { public: NotFound(void) {} }; T lookup(const ASTString& s) const { auto it(bindings.find(s)); if (it != bindings.end()) return (*it).second; if (!p) throw NotFound(); return p->lookup(s); } void bind(const ASTString& s, T val) { auto it(bindings.find(s)); if (it != bindings.end()) bindings.erase(it); else sz++; bindings.insert(std::make_pair(s, val)); // Invalidate any cached values mentioning s. auto o_it(occurs.find(s)); if (o_it != occurs.end()) { // We're lazy here, in that we don't remove e from // other occurs lists. for (Expression* e : (*o_it).second) available.erase(e); occurs.erase(o_it); } } // Check whether e is already available in an enclosing environment. T cache_lookup(Expression* e, ASTStSet e_scope) { auto it(available.find(e)); // Anything in the current table is hasn't been invalidated. if (it != available.end()) { return (*it).second; } if (!p) throw NotFound(); // If there's a parent table, check whether we've re-bound // something in its scope. for (auto p : bindings) { if (e_scope.find(p.first) != e_scope.end()) throw NotFound(); } // If the scope hasn't been invalidated, check the parent. return p->cache_lookup(e, e_scope); } void cache_store(Expression* e, ASTStSet e_scope, T val) { available.insert(std::make_pair(e, val)); // Add e to the occurs lists for variables in its scope. for (ASTString s : e_scope) occurs[s].push_back(e); } bool cache_lookup_cst(int x, T& ret) { auto it(available_csts.find(x)); // Anything in the current table is hasn't been invalidated. if (it != available_csts.end()) { ret = (*it).second; return true; } if (!p) return false; return p->cache_lookup_cst(x, ret); } void cache_store_cst(int x, T val) { available_csts.insert(std::make_pair(x, val)); } static uint64_t range_key(int l, int u) { return (((uint64_t)u) << 32ull | (uint64_t)l); } bool cache_lookup_range(int l, int u, T& ret) { auto it(available_ranges.find(range_key(l, u))); // Anything in the current table is hasn't been invalidated. if (it != available_ranges.end()) { ret = (*it).second; return true; } if (!p) return false; return p->cache_lookup_range(l, u, ret); } void cache_store_range(int l, int u, T val) { available_ranges.insert(std::make_pair(range_key(l, u), val)); } static CG_Env* spawn(CG_Env* p) { return new CG_Env(p); } unsigned int size(void) const { return sz; } typename ASTStringMap::t bindings; typename ExprMap::t available; std::unordered_map available_csts; std::unordered_map available_ranges; // std::unordered_map, T> available_ranges; typename ASTStringMap>::t occurs; std::vector cached_conds; // Parent environment. CG_Env* p; unsigned int sz; }; struct CG { typedef std::pair Binding; // This is currently (probably) sound, but unnecessarily weak. If an expression ever appears in // root context, the root appearance should dominate. struct Mode { enum Strength { Root = 0, Imp = 1, Fun = 2 }; Mode(BytecodeProc::Mode _m) : m(_m) {} Mode(Strength s, bool is_neg) { switch (s) { case Root: m = is_neg ? BytecodeProc::ROOT_NEG : BytecodeProc::ROOT; break; case Imp: m = is_neg ? BytecodeProc::IMP_NEG : BytecodeProc::IMP; break; case Fun: m = is_neg ? BytecodeProc::FUN_NEG : BytecodeProc::FUN; break; } } bool is_neg(void) const { switch (m) { case BytecodeProc::ROOT_NEG: case BytecodeProc::IMP_NEG: case BytecodeProc::FUN_NEG: return true; default: return false; } } Strength strength(void) const { switch (m) { case BytecodeProc::ROOT: case BytecodeProc::ROOT_NEG: return Root; case BytecodeProc::IMP: case BytecodeProc::IMP_NEG: return Imp; case BytecodeProc::FUN: case BytecodeProc::FUN_NEG: return Fun; default: throw InternalError("Unexpected mode."); } } bool is_root(void) const { return strength() == Root; } Mode join(Mode o) { if (is_neg() != o.is_neg()) return BytecodeProc::FUN; return Mode(std::max(strength(), o.strength()), is_neg()); } bool is_submode(Mode o) { return is_neg() == o.is_neg() && strength() <= o.strength(); } // Half Mode operator+(void) const { return Mode(strength() == Root ? Imp : strength(), is_neg()); } Mode operator-(void) const { return Mode(strength(), !is_neg()); } // Switch the current mode to functional. Mode operator*(void) const { return Mode(Fun, is_neg()); } operator BytecodeProc::Mode() const { return m; } BytecodeProc::Mode m; }; inline static CG_Value g(int g) { return CG_Value::global(g); } inline static CG_Value r(int r) { return CG_Value::reg(r); } inline static CG_Value i(int i) { return CG_Value::immi(i); } inline static CG_Value l(int l) { return CG_Value::label(l); } // Place a non-Boolean value in a register, and collect its partiality. static Binding bind(Expression* e, CodeGen& cg, CG_Builder& frag); // Compile a Boolean expression into a condition. static CG_Cond::T compile(Expression* e, CodeGen& cg, CG_Builder& frag); // Reify a condion, putting it in a register. static int force(CG_Cond::T cond, Mode ctx, CodeGen& cg, CG_Builder& frag); // Force compiled expression or Bind value depending on type static Binding force_or_bind(Expression* e, Mode ctx, CodeGen& cg, CG_Builder& frag); static int force_or_bind(Expression* e, Mode ctx, std::vector& cond, CodeGen& cg, CG_Builder& frag); static void run(CodeGen& cg, Model* m); static int locate_immi(int x, CodeGen& cg, CG_Builder& frag); static int vec2array(std::vector vec, CodeGen& cg, CG_Builder& frag); static Binding bind(Id* x, Mode ctx, CodeGen& cg, CG_Builder& frag); static Binding bind(SetLit* l, Mode ctx, CodeGen& cg, CG_Builder& frag); static Binding bind(ArrayLit* a, Mode ctx, CodeGen& cg, CG_Builder& frag); static Binding bind(ArrayAccess* a, Mode ctx, CodeGen& cg, CG_Builder& frag); static Binding bind(ITE* ite, Mode ctx, CodeGen& cg, CG_Builder& frag); static Binding bind(BinOp* op, Mode ctx, CodeGen& cg, CG_Builder& frag); static Binding bind(UnOp* op, Mode ctx, CodeGen& cg, CG_Builder& frag); static Binding bind(Call* call, Mode ctx, CodeGen& cg, CG_Builder& frag); static Binding bind(Let* let, Mode ctx, CodeGen& cg, CG_Builder& frag); static Binding bind(Comprehension* let, Mode ctx, CodeGen& cg, CG_Builder& frag); static CG_Cond::T compile(Id* x, Mode ctx, CodeGen& cg, CG_Builder& frag); static CG_Cond::T compile(ArrayAccess* a, Mode ctx, CodeGen& cg, CG_Builder& frag); static CG_Cond::T compile(ITE* ite, Mode ctx, CodeGen& cg, CG_Builder& frag); static CG_Cond::T compile(BinOp* op, Mode ctx, CodeGen& cg, CG_Builder& frag); static CG_Cond::T compile(UnOp* op, Mode ctx, CodeGen& cg, CG_Builder& frag); static CG_Cond::T compile(Call* call, Mode ctx, CodeGen& cg, CG_Builder& frag); static CG_Cond::T compile(Let* let, Mode ctx, CodeGen& cg, CG_Builder& frag); static CG_Cond::T compile(Comprehension* let, Mode ctx, CodeGen& cg, CG_Builder& frag); }; inline CG_Cond::T CG_Cond::_exists(BytecodeProc::Mode m, std::vector& args) { size_t count = 0; for (T x : args) { _T* p(x.get()); if (!p) { // Either true or false. if (!x.sign()) { clear_seen(args.begin(), args.end()); return T::ttt(); } continue; } // Otherwise, check if we've already seen this or its negation. if (p->reg[1 - x.sign()].is_seen || p->reg[x.sign()].is_root) { clear_seen(args.begin(), args.end()); return T::ttt(); } if (p->reg[x.sign()].is_seen || p->reg[1 - x.sign()].is_root) { continue; } // Haven't seen this yet, so save and mark it. p->reg[x.sign()].is_seen = true; args[count] = x; count++; } clear_seen(args.begin(), args.end()); args.resize(count); if (count == 0) { return T::fff(); } if (count == 1) { return args[0]; } return ~T::of_ptr(new C_And(-CG::Mode(m), args)); } // Partially compiled bytecode. struct CG_Proc { typedef std::vector body_t; static unsigned char mode_mask(BytecodeProc::Mode m) { return 1 << (static_cast(m)); } struct mode_iterator { mode_iterator(unsigned int _x) : x(_x) {} bool operator!=(const mode_iterator& o) const { return x != o.x; } BytecodeProc::Mode operator*(void) const { assert(x); return static_cast(find_lsb(x)); } mode_iterator& operator++(void) { x &= (x - 1); return *this; } unsigned int x; }; mode_iterator begin(void) { return mode_iterator(available_modes); } mode_iterator end(void) { return mode_iterator(0); } CG_Proc(std::string _ident, int _arity) : ident(_ident), arity(_arity), available_modes(0) {} CG_Proc(CG_Proc&& o) : ident(o.ident), arity(o.arity), available_modes(o.available_modes) { unsigned char rm(available_modes); while (rm) { unsigned char m(find_lsb(rm)); rm &= (rm - 1); new (_body + m) body_t(std::move(o._body[m])); o._body[m].~body_t(); } o.available_modes = 0; } std::string ident; unsigned int arity; bool is_available(BytecodeProc::Mode m) const { return available_modes & mode_mask(m); } std::vector& body(BytecodeProc::Mode m) { static_assert(BytecodeProc::MAX_MODE < 8 * sizeof(unsigned char), "Too many modes to to represent as unsigned char."); if (!(available_modes & mode_mask(m))) { available_modes |= mode_mask(m); new (_body + m) body_t(); } return _body[m]; } unsigned char available_modes; std::vector _body[BytecodeProc::MAX_MODE + 1]; }; // For identifying a call... struct CallSig { ASTString id; std::vector params; CallSig(ASTString _id, std::vector _params) : id(_id) { // Normalize the call types to par. for (Type p : _params) { p.ti(Type::TI_PAR); params.push_back(p); } } struct HashSig { size_t operator()(const CallSig& c) const { return c.hash(); } }; struct EqSig { bool operator()(const CallSig& x, const CallSig& y) const { return x == y; } }; bool operator==(const CallSig& o) const { if (id != o.id || params.size() != o.params.size()) return false; for (int ii = 0; ii < params.size(); ++ii) { if (params[ii] != o.params[ii]) return false; } return true; } size_t hash(void) const { size_t h(id.hash()); for (int ii = 0; ii < params.size(); ++ii) h ^= params[ii].toInt() + 0x9e3779b9 + (h << 6) + (h >> 2); return h; } }; template struct SigMap { typedef std::unordered_map t; }; struct CG_FunMap { struct CG_FunDefn { CG_FunDefn(ASTString _id) : id(_id) {} ASTString id; std::vector bodies; }; ASTStringMap::t id_map; std::vector functions; void add_body(FunctionI* f) { // FIXME: Currently discarding anything with var-set. ASTExprVec params(f->params()); for (int ii = 0; ii < params.size(); ++ii) { if (params[ii]->type().is_set() && !params[ii]->type().ispar()) return; } unsigned int fun_id; ASTString id(f->id()); auto it(id_map.find(id)); if (it != id_map.end()) { fun_id = (*it).second; } else { fun_id = functions.size(); id_map.insert(std::make_pair(id, fun_id)); functions.push_back(CG_FunDefn(id)); } functions[fun_id].bodies.push_back(f); } bool dominates(FunctionI* f, FunctionI* g) { auto f_params(f->params()); auto g_params(g->params()); assert(f_params.size() == g_params.size()); int sz(f_params.size()); for (int ii = 0; ii < sz; ++ii) { if (!Type::bt_subtype(f_params[ii]->type(), g_params[ii]->type(), false)) return false; } return true; } void filter_bodies(std::vector::iterator& dest, std::vector::iterator b, std::vector::iterator e, int arg, int sz) { if (!(b != e)) // Empty partition return; if (arg == sz) { // Find the best candidate between b and e, add it to the output. // FIXME auto best(b); for (++b; b != e; ++b) { if (dominates(*b, *best)) best = b; } (*dest) = (*best); ++dest; return; } // Otherwise, partition the arguments and recurse. std::vector::iterator mid = std::partition(b, e, [arg](FunctionI* b) { return b->params()[arg]->type().ispar(); }); filter_bodies(dest, b, mid, arg + 1, sz); filter_bodies(dest, mid, e, arg + 1, sz); } std::vector get_bodies(unsigned int fun_id, const std::vector& args) { CG_FunDefn& defn(functions[fun_id]); // First, restrict consideration to feasible specialisations. std::vector candidates; int sz = args.size(); for (FunctionI* b : defn.bodies) { ASTExprVec b_params(b->params()); if (b_params.size() == sz) { for (int pi = 0; pi < sz; ++pi) { if (!args[pi].isSubtypeOf(b_params[pi]->type(), false)) goto get_bodies_continue; } // Can coerce args to b_params. candidates.push_back(b); } get_bodies_continue: continue; } // Now collect the relevant par-based refinements. std::vector::iterator dest(candidates.begin()); filter_bodies(dest, candidates.begin(), candidates.end(), 0, sz); candidates.erase(dest, candidates.end()); return candidates; } std::vector get_bodies(const ASTString& ident, const std::vector& args) { auto it(id_map.find(ident)); if (it == id_map.end()) return {}; unsigned int fun_id((*it).second); return get_bodies(fun_id, args); } std::vector get_bodies(Call* call) { // Normalize all types to par, so isSubtype does what we want. std::vector args; int sz(call->n_args()); for (int ii = 0; ii < sz; ++ii) { Type arg(call->arg(ii)->type()); arg.ti(Type::TI_PAR); args.push_back(arg); } return get_bodies(call->id(), args); } std::pair defines_mode(const ASTString& ident, const std::vector& args, BytecodeProc::Mode mode) { GCLock lock; ASTString nident; switch (mode) { case BytecodeProc::ROOT_NEG: nident = ident.str() + "_neg"; break; case BytecodeProc::FUN: nident = ident.str() + "_reif"; break; case BytecodeProc::FUN_NEG: nident = ident.str() + "_neg_reif"; break; case BytecodeProc::IMP: nident = ident.str() + "_imp"; break; case BytecodeProc::IMP_NEG: nident = ident.str() + "_neg_imp"; break; default: return {false, ASTString("")}; } auto it(id_map.find(nident)); if (it == id_map.end()) { return {false, nident}; } unsigned int fun_id((*it).second); std::vector reif_args; reif_args.reserve(args.size() + 1); for (int ii = 0; ii < args.size(); ++ii) { Type arg(args[ii]); arg.ti(Type::TI_PAR); reif_args.push_back(arg); } if (mode != BytecodeProc::ROOT && mode != BytecodeProc::ROOT_NEG) { reif_args.push_back(Type::parbool()); } return {!get_bodies(fun_id, reif_args).empty(), nident}; } }; struct CodeGen { typedef unsigned int proc_id; typedef unsigned int reg_id; typedef std::pair Binding; CodeGen(void) : /*entry_proc(0) ,*/ current_env(new CG_Env()), num_globals(0), current_reg_count(0), current_label_count(0) { register_builtins(); } void append(int proc, BytecodeProc::Mode m, CG_Builder& b) { std::vector& body(bytecode[proc].body(m)); body.insert(body.end(), b.instrs.begin(), b.instrs.end()); b.clear(); } void env_push(void) { current_env = CG_Env::spawn(current_env); } void env_pop(void) { assert(current_env); CG_Env* c(current_env); c->clear_cached_conds(); current_env = current_env->p; delete c; } // Consult/update the available expressions in the current environment. Binding cache_lookup(Expression* e); void cache_store(Expression* e, Binding l); // Function resolution void register_function(FunctionI* f) { fun_map.add_body(f); } CG_ProcID resolve_fun(FunctionI* f, bool reserved_name = false); // CG_ProcID resolve_fun_pred(FunctionI* f); // CG_ProcID resolve_pred_def(FunctionI* f, BytecodeProc::Mode m); std::vector bytecode; // Bytecode we've built inline CG_Env& env(void) { return *current_env; } CG_Env* current_env; // Where are things in scope? // Id -> std::unordered_map> globals_env; int num_globals; std::vector req_solver_predicates; int add_global(VarDecl* vd, bool input = false) { globals_env.insert(std::make_pair(vd, std::make_pair(num_globals, input))); return num_globals++; } int find_global(VarDecl* str) { auto pair = globals_env.at(str); return pair.first; } std::vector reg_trail; unsigned int current_reg_count; // How many registers have been used? unsigned int current_label_count; // Helper information. For an expression, which variables does it refer to? ASTStSet scope(Expression* e); ExprMap::t _exp_scope; ExprMap::t mode_map; // Procedure information void register_builtins(void); CG_ProcID register_builtin(std::string s, unsigned int p); CG_ProcID find_builtin(std::string s); std::vector> _builtins; std::unordered_map _proc_map; // Procedures yet to be emitted. CG_FunMap fun_map; SigMap>::t dispatch; std::unordered_map fun_bodies; std::vector>> pending_bodies; }; const char* instr_name(BytecodeStream::Instr i); const char* agg_name(AggregationCtx::Symbol s); const char* mode_name(BytecodeProc::Mode m); std::tuple find_call_fun(CodeGen& cg, const ASTString& ident, const Type& ret_type, std::vector arg_types, BytecodeProc::Mode m, bool reserved_name = false); std::tuple find_call_fun(CodeGen& cg, Call* call, BytecodeProc::Mode m, bool reserved_name = false); void eval_let_body(Let* let, BytecodeProc::Mode ctx, CodeGen& cg, CG_Builder& frag, std::vector& conj); }; // namespace MiniZinc #endif