A set of benchmarks to compare a new prototype MiniZinc implementation
1/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
2
3/*
4 * Main authors:
5 * Jip J. Dekker <jip.dekker@monash.edu>
6 * Guido Tack <guido.tack@monash.edu>
7 */
8
9/* This Source Code Form is subject to the terms of the Mozilla Public
10 * License, v. 2.0. If a copy of the MPL was not distributed with this
11 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
12
13#pragma once
14
15#include <minizinc/ast.hh>
16#include <minizinc/interpreter/constraint.hh>
17#include <minizinc/interpreter/cse.hh>
18#include <minizinc/interpreter/values.hh>
19#include <minizinc/support/dtrace.h>
20
21#include <deque>
22#include <iostream>
23#include <map>
24#include <unordered_set>
25#include <vector>
26
27// #define DBG_INTERPRETER(msg) std::cerr << msg
28#define DBG_INTERPRETER(msg) \
29 do { \
30 } while (0)
31#define DBG_TRIM_OUTPUT true
32
33namespace MiniZinc {
34
35class Interpreter;
36
37class RegisterFile {
38protected:
39 std::vector<Val> _r;
40 size_t _size;
41 // Note: It seems that the standard library sometimes tries to be smart when
42 // using resize(n) when n < capacity. We therefore use _r.size() as capacity
43 // and _size as the actual size.
44
45 // FIXME: The RegisterFile is said to always have a given size, but it is
46 // currently unknown how many globals are used. This can trigger an assertion.
47public:
48 RegisterFile(int n = 0) : _r(n), _size(n) {}
49 const Val& operator[](int r) {
50 assert(r < _r.size());
51 return _r[r];
52 }
53 size_t size() { return _size; }
54 void resize(Interpreter* interpreter, size_t n) {
55 if (n >= _r.size()) {
56 _r.resize(_r.size() * 2);
57 }
58 if (n < _size) {
59 for (int i = n; i < _size; ++i) {
60 _r[i].rmRef(interpreter);
61 _r[i] = 0;
62 }
63 }
64 _size = n;
65 }
66 std::vector<Val>::const_iterator cbegin() { return _r.cbegin(); }
67 std::vector<Val>::const_iterator cend() { return _r.cend(); }
68 std::vector<Val>::const_iterator iter_n(size_t n) { return this->cbegin() + n; }
69 void assign(Interpreter* interpreter, int r, const Val& v) {
70 assert(r < size());
71 _r[r].assign(interpreter, v);
72 }
73
74 void cp(Interpreter* interpreter, int r1, int r2) {
75 assert(r1 < size());
76 _r[r2].assign(interpreter, _r[r1]);
77 }
78 void cp(Interpreter* interpreter, int r1, RegisterFile& rf, int r2) {
79 assert(r1 < size());
80 rf._r[r2].assign(interpreter, _r[r1]);
81 }
82 /// Destroy this register file
83 void destroy(Interpreter* interpreter) {
84 for (auto& v : _r) {
85 v.rmRef(interpreter);
86 }
87 }
88 void clear(Interpreter* interpreter) {
89 destroy(interpreter);
90 _r.clear();
91 }
92 void dump(std::ostream& os) {
93 for (unsigned int i = 0; i < size(); i++) {
94 os << " R" << i << " = " << _r[i].toString() << "\n";
95 }
96 }
97};
98
99class AggregationCtx {
100protected:
101 /// Stack of values that need to be aggregated
102 std::vector<Val> stack;
103
104public:
105 /// Earliest time stamp for definitions in the current aggregation
106 int def_ident_start;
107 /// Constraints attached to the computed value
108 std::vector<Constraint*> constraints;
109 /// Type of function represented by this context
110 enum Symbol { VCTX_AND, VCTX_OR, VCTX_VEC, VCTX_OTHER, MAX_SYMBOL = VCTX_OTHER } symbol;
111 static const std::string symbol_to_string[MAX_SYMBOL + 1];
112 /// Nesting depth for this symbol (how many of these are open)
113 int n_symbols;
114 /// Constructor
115 AggregationCtx(Interpreter* interpreter, int s);
116 /// Destructor
117 ~AggregationCtx(void);
118 /// Push value onto aggregation stack
119 void push(Interpreter* interpreter, const Val& v) {
120 assert(stack.empty() || symbol != VCTX_OTHER);
121 stack.push_back(v);
122 stack.back().addRef(interpreter);
123 }
124 void pop(Interpreter* interpreter) {
125 stack.back().rmRef(interpreter);
126 stack.pop_back();
127 }
128 const Val& back(void) const { return stack.back(); }
129 const Val& operator[](int i) const { return stack[i]; }
130 int size(void) const { return stack.size(); }
131 bool empty(void) const { return stack.empty(); }
132 Val createVec(Interpreter* interpreter, int timestamp) const;
133 /// Destroy stack values
134 void destroyStack(Interpreter* interpreter) {
135 for (auto& v : stack) {
136 v.rmRef(interpreter);
137 }
138 }
139};
140
141// Frame information for Common Subexpression Elimination
142class CSEFrame {
143public:
144 int proc;
145 BytecodeProc::Mode mode;
146 int nargs;
147 union KeyUnion {
148 VariadicKey vk;
149 FixedKey<1> f1;
150 FixedKey<2> f2;
151 FixedKey<3> f3;
152 FixedKey<4> f4;
153
154 KeyUnion() { new (&vk) VariadicKey(); }
155 ~KeyUnion() {}
156 } key;
157 size_t stack_size;
158
159 CSEFrame(Interpreter& interpreter, int _proc, BytecodeProc::Mode _mode, arg_iter arg_start,
160 arg_iter arg_end, size_t _nargs, size_t _stack_size)
161 : proc(_proc), mode(_mode), stack_size(_stack_size), nargs(_nargs) {
162 switch (nargs) {
163 case 1: {
164 key.f1 = FixedKey<1>(interpreter, arg_start, arg_end);
165 break;
166 }
167 case 2: {
168 key.f2 = FixedKey<2>(interpreter, arg_start, arg_end);
169 break;
170 }
171 case 3: {
172 key.f3 = FixedKey<3>(interpreter, arg_start, arg_end);
173 break;
174 }
175 case 4: {
176 key.f4 = FixedKey<4>(interpreter, arg_start, arg_end);
177 break;
178 }
179 default: {
180 key.vk = VariadicKey(interpreter, arg_start, arg_end, nargs);
181 break;
182 }
183 }
184 }
185
186 CSEFrame(const CSEFrame& other)
187 : proc(other.proc), mode(other.mode), stack_size(other.stack_size), nargs(other.nargs) {
188 switch (nargs) {
189 case 1: {
190 key.f1 = other.key.f1;
191 break;
192 }
193 case 2: {
194 key.f2 = other.key.f2;
195 break;
196 }
197 case 3: {
198 key.f3 = other.key.f3;
199 break;
200 }
201 case 4: {
202 key.f4 = other.key.f4;
203 break;
204 }
205 default: {
206 key.vk = other.key.vk;
207 break;
208 }
209 }
210 };
211
212 inline void destroy(Interpreter& interpreter) {
213 switch (nargs) {
214 case 1: {
215 key.f1.destroy(interpreter);
216 break;
217 }
218 case 2: {
219 key.f2.destroy(interpreter);
220 break;
221 }
222 case 3: {
223 key.f3.destroy(interpreter);
224 break;
225 }
226 case 4: {
227 key.f4.destroy(interpreter);
228 break;
229 }
230 default: {
231 key.vk.destroy(interpreter);
232 break;
233 }
234 }
235 }
236
237 inline CSEKey& getKey() {
238 switch (nargs) {
239 case 1: {
240 return key.f1;
241 }
242 case 2: {
243 return key.f2;
244 }
245 case 3: {
246 return key.f3;
247 }
248 case 4: {
249 return key.f4;
250 }
251 default: {
252 return key.vk;
253 }
254 }
255 }
256};
257
258class BytecodeFrame {
259public:
260 size_t reg_offset;
261 const BytecodeStream* bs;
262 int pc;
263 int _pred;
264 char _mode;
265 size_t cse_frame_depth = 0;
266
267 BytecodeFrame(const BytecodeStream& bs0, int pred, char mode)
268 : reg_offset(0), bs(&bs0), pc(0), _pred(pred), _mode(mode) {}
269};
270
271class Trail {
272 friend class MznSolver;
273
274protected:
275 std::vector<std::pair<Variable**, Variable*>> var_list_trail;
276 std::vector<RefCountedObject*> obj_trail;
277 // <Definition, procedure, size, arg(0)>
278 std::vector<std::tuple<Variable*, Val>> alias_trail;
279 std::vector<std::pair<Variable*, Vec*>> domain_trail;
280 std::vector<std::tuple<Variable*, Constraint*, bool>> def_trail;
281 // <Var list trail size, Obj trail size, Alias trail size, Domain trail size, Def trail size>
282 std::vector<std::tuple<size_t, size_t, size_t, size_t, size_t>> trail_size;
283 std::vector<int> timestamp_trail;
284 bool last_operation_pop = false;
285
286public:
287 Trail() = default;
288 virtual ~Trail() {
289 for (auto& i : obj_trail) {
290 if (i->rcoType() == RefCountedObject::VAR) {
291 Variable::free(static_cast<Variable*>(i));
292 } else {
293 free(i);
294 }
295 }
296 obj_trail.clear();
297 };
298
299 size_t len() { return trail_size.size(); }
300 inline bool is_trailed(RefCountedObject* rco) {
301 return (!trail_size.empty() && timestamp_trail.back() > rco->timestamp());
302 }
303
304 // Trail hedge pointer change
305 inline bool trail_ptr(Variable* obj, Variable** member) {
306 if (!is_trailed(obj)) {
307 return false;
308 }
309 var_list_trail.emplace_back(member, *member);
310 return true;
311 }
312 // Trail Reference Counted Object removal
313 inline bool trail_removal(RefCountedObject* obj) {
314 if (!is_trailed(obj)) {
315 return false;
316 }
317 obj_trail.push_back(obj);
318 return true;
319 }
320 // Trail variable aliasing
321 inline bool trail_alias(Interpreter* interpreter, Variable* v) {
322 if (!is_trailed(v)) {
323 return false;
324 }
325 v->alias().addMemRef(interpreter);
326 alias_trail.emplace_back(v, v->alias());
327 return true;
328 }
329 // Trail definition domain change
330 inline bool trail_domain(Interpreter* interpreter, Variable* v, Vec* dom) {
331 if (!is_trailed(v)) {
332 return false;
333 }
334 assert(dom);
335 dom->addMemRef(interpreter);
336 domain_trail.emplace_back(v, dom);
337 return true;
338 }
339 bool trail_add_def(Variable* var, Constraint* c) {
340 if (!is_trailed(var)) {
341 return false;
342 }
343 def_trail.emplace_back(var, c, true);
344 return true;
345 }
346 bool trail_rm_def(Variable* var, Constraint* c) {
347 if (!is_trailed(var)) {
348 return false;
349 }
350 def_trail.emplace_back(var, c, false);
351 return true;
352 }
353
354 size_t save_state(Interpreter* interpreter);
355 void untrail(Interpreter* interpreter);
356};
357
358// Structure for active loops
359struct LoopState {
360 LoopState(Vec* vec, int _exit_pc)
361 : pos(reinterpret_cast<intptr_t>(vec->begin())),
362 end(reinterpret_cast<intptr_t>(vec->end())),
363 exit_pc(_exit_pc),
364 is_range(false) {}
365
366 LoopState(int l, int u, int _exit_pc) : pos(l), end(u + 1), exit_pc(_exit_pc), is_range(true) {}
367
368 intptr_t pos;
369 intptr_t end;
370
371 int exit_pc : 31;
372 int is_range : 1;
373};
374
375class Interpreter {
376 friend class Trail;
377 friend class MznSolver;
378
379public:
380 enum Status { ROGER, ABORTED, INCONSISTENT, ERROR, MAX_STATUS = ERROR };
381 static const std::string status_to_string[MAX_STATUS + 1];
382
383protected:
384 RegisterFile _registers;
385 std::vector<BytecodeFrame> _stack;
386 std::vector<CSEFrame> _cse_stack;
387 std::vector<AggregationCtx> _agg;
388 std::vector<LoopState> _loops;
389 std::vector<BytecodeProc>& _procs;
390 int _identCount;
391 std::vector<void*> cse; // Different instantiations of CSETable
392 std::unordered_map<Constraint*, Variable*> delayed_constraints;
393 std::deque<Constraint*> _propQueue;
394 RegisterFile globals;
395 Status _status = ROGER;
396
397 // The root variable. It's fixed to true, all toplevel constraints
398 // are attached to it, and it's the head of the linked list of
399 // all variables
400 Variable* _root_var;
401
402 Vec* infinite_dom;
403 Vec* boolean_dom;
404 Vec* true_dom;
405
406public:
407 Trail trail;
408 std::unordered_map<int, Val> solutions;
409
410 Interpreter(std::vector<BytecodeProc>& procs, const BytecodeFrame& f, int max_globals = -1)
411 : _registers(4096),
412 _procs(procs),
413 _identCount(0),
414 cse(procs.size()),
415 globals(max_globals + 1) {
416 _stack.reserve(32);
417 _cse_stack.reserve(32);
418 _stack.emplace_back(f);
419 _registers.resize(this, f.bs->maxRegister() + 1);
420
421 infinite_dom = Vec::a(this, newIdent(), {-Val::infinity(), Val::infinity()});
422 infinite_dom->addRef(this);
423 boolean_dom = Vec::a(this, newIdent(), {0, 1});
424 boolean_dom->addRef(this);
425 true_dom = Vec::a(this, newIdent(), {1, 1});
426 true_dom->addRef(this);
427 _root_var = Variable::createRoot(this, Val(true_dom), newIdent());
428 _root_var->addRef(this);
429 for (int i = 0; i < cse.size(); ++i) {
430 switch (_procs[i].nargs) {
431 case 1: {
432 cse[i] = new CSETable<FixedKey<1>>();
433 break;
434 }
435 case 2: {
436 cse[i] = new CSETable<FixedKey<2>>();
437 break;
438 }
439 case 3: {
440 cse[i] = new CSETable<FixedKey<3>>();
441 break;
442 }
443 case 4: {
444 cse[i] = new CSETable<FixedKey<4>>();
445 break;
446 }
447 default: {
448 cse[i] = new CSETable<VariadicKey>();
449 break;
450 }
451 }
452 }
453 }
454 ~Interpreter(void);
455 Status status() { return _status; }
456 void run(void);
457 bool runDelayed();
458 void pushAgg(const Val& v, int stackOffset);
459 void pushConstraint(Constraint* d);
460 std::pair<Val, bool> cse_find(int proc, const CSEKey& key, BytecodeProc::Mode& mode);
461 void cse_insert(int proc, CSEKey& key, BytecodeProc::Mode& mode, Val& val);
462 void set_global(int i, const Val& val) { globals.assign(this, i, val); }
463 void clear_globals() { globals.clear(this); }
464 const Val get_global(int i) { return globals[i]; }
465 PropStatus subscribe(Constraint* c);
466 void unsubscribe(Constraint* d);
467 int newIdent(void) { return _identCount++; }
468 int currentIdent(void) const { return _identCount; }
469 void dumpState(std::ostream& os);
470 void dumpState(const BytecodeFrame& bf, std::ostream& os);
471 void dumpState();
472 void schedule(Constraint* d, const Variable::SubscriptionEvent& ev);
473 void deschedule(Constraint* d);
474 void propagate(void);
475 void call(int code, std::vector<Val>&& args);
476
477 const Val& reg(const BytecodeFrame& bf, int r) { return _registers[bf.reg_offset + r]; }
478 void assign(const BytecodeFrame& bf, int r, const Val& v) {
479 _registers.assign(this, bf.reg_offset + r, v);
480 }
481 BytecodeFrame& frame() { return _stack.back(); }
482
483 Val infinite_domain() { return Val(infinite_dom); }
484 Val boolean_domain() { return Val(boolean_dom); }
485 void register_delayed(Constraint* c, Variable* v) { delayed_constraints[c] = v; }
486 void remove_delayed(Constraint* c) { delayed_constraints.erase(c); }
487
488 Variable* root() { return _root_var; }
489
490 /// Perform optimizatin by basic propagation on generated FlatZinc
491 void optimize(void);
492};
493
494inline void RefCountedObject::rmRef(Interpreter* interpreter, RefCountedObject* rco) {
495 assert(rco->_model_ref_count > 0);
496 if (--rco->_model_ref_count == 0) {
497 switch (rco->rcoType()) {
498 case VAR:
499 static_cast<Variable*>(rco)->destroy(interpreter);
500 break;
501 case VEC:
502 static_cast<Vec*>(rco)->destroyModel(interpreter);
503 break;
504 default:
505 assert(false);
506 }
507 if (interpreter->trail.is_trailed(rco)) {
508 interpreter->trail.trail_removal(rco);
509 } else if (rco->_memory_ref_count == 0) {
510 // INVARIANT: All children of a definition are already promoted, cut, or freed.
511 // assert(rco->rcoType() != VAR ||
512 // !static_cast<Variable*>(rco)->constraints().empty());
513 if (rco->rcoType() == VAR) {
514 Variable::free(static_cast<Variable*>(rco));
515 } else {
516 ::free(rco);
517 }
518 }
519 }
520}
521
522inline void RefCountedObject::rmMemRef(Interpreter* interpreter, RefCountedObject* rco) {
523 if (--rco->_memory_ref_count == 0u && !interpreter->trail.is_trailed(rco) &&
524 rco->_model_ref_count == 0u) {
525 // INVARIANT: All children of a definition are already promoted, cut, or freed.
526 // assert(rco->rcoType() != DEF || !static_cast<Definition*>(rco)->defs());
527 if (rco->rcoType() == VEC) {
528 static_cast<Vec*>(rco)->destroyMemory(interpreter);
529 }
530 free(rco);
531 }
532}
533
534inline AggregationCtx::AggregationCtx(Interpreter* interpreter, int s)
535 : def_ident_start(interpreter->currentIdent()), symbol(static_cast<Symbol>(s)), n_symbols(1) {
536 assert(s >= 0 && s <= VCTX_OTHER);
537}
538} // namespace MiniZinc
539
540#include <minizinc/interpreter.hpp>
541#include <minizinc/interpreter/cse.hpp>