KSeExpr  4.0.4.0
Interpreter.cpp
Go to the documentation of this file.
1 // SPDX-FileCopyrightText: 2011-2019 Disney Enterprises, Inc.
2 // SPDX-License-Identifier: LicenseRef-Apache-2.0
3 // SPDX-FileCopyrightText: 2020 L. E. Segovia <amy@amyspark.me>
4 // SPDX-License-Identifier: GPL-3.0-or-later
5 
6 #include <iostream>
7 #include <cstdio>
8 #include <algorithm>
9 #ifndef SEEXPR_WIN32
10 #include <dlfcn.h>
11 #endif
12 
13 #include "ExprNode.h"
14 #include "Interpreter.h"
15 #include "VarBlock.h"
16 
17 // TODO: optimize to write to location directly on a CondNode
18 namespace KSeExpr {
19 
20 void Interpreter::eval(VarBlock* block, bool debug) {
21  // get pointers to the working data
22  double* fp = d.data();
23  char** str = s.data();
24 
25  // if we have a VarBlock instance, we need to update the working data
26  if (block) {
27  // if the VarBlock is flagged as thread safe, copy the interpreter's data to it.
28  if (block->threadSafe == true) {
29  // copy double data
30  block->d.resize(d.size());
31  fp = block->d.data();
32  memcpy(fp, d.data(), d.size() * sizeof(double));
33 
34  // copy string data
35  block->s.resize(s.size());
36  str = block->s.data();
37  memcpy(str, s.data(), s.size() * sizeof(char*));
38  }
39 
40  // set the variable evaluation data
41  str[0] = reinterpret_cast<char*>(block->data());
42  str[1] = reinterpret_cast<char*>(static_cast<size_t>(block->indirectIndex));
43  }
44 
45  int pc = _pcStart;
46  int end = static_cast<int>(ops.size());
47  while (pc < end) {
48  if (debug) {
49  std::cerr << "Running op at " << pc << std::endl;
50  print(pc);
51  }
52  const std::pair<OpF, int>& op = ops[pc];
53  int* opCurr = &opData[0] + op.second;
54  pc += op.first(opCurr, fp, str, callStack);
55  }
56 }
57 
58 void Interpreter::print(int pc) const
59 {
60  std::cerr << "---- ops ----------------------" << std::endl;
61  for (size_t i = 0; i < ops.size(); i++) {
62  const char *name = "";
63 #ifndef SEEXPR_WIN32
64  Dl_info info;
65  if (dladdr((void *)ops[i].first, &info))
66  name = info.dli_sname;
67 #endif
68  fprintf(stderr, "%s %s %p (", pc == (int)i ? "-->" : " ", name, (void *)ops[i].first);
69  int nextGuy = (i == ops.size() - 1 ? static_cast<int>(opData.size()) : ops[i + 1].second);
70  for (int k = ops[i].second; k < nextGuy; k++) {
71  fprintf(stderr, " %d", opData[k]);
72  }
73  fprintf(stderr, ")\n");
74  }
75  std::cerr << "---- opdata ----------------------" << std::endl;
76  for (size_t k = 0; k < opData.size(); k++) {
77  std::cerr << "opData[" << k << "]= " << opData[k] << std::endl;
78  }
79  std::cerr << "----- fp --------------------------" << std::endl;
80  for (size_t k = 0; k < d.size(); k++) {
81  std::cerr << "fp[" << k << "]= " << d[k] << std::endl;
82  }
83  std::cerr << "---- str ----------------------" << std::endl;
84  std::cerr << "s[0] reserved for datablock = " << reinterpret_cast<size_t>(s[0]) << std::endl;
85  std::cerr << "s[1] is indirectIndex = " << reinterpret_cast<size_t>(s[1]) << std::endl;
86  for (size_t k = 2; k < s.size(); k++) {
87  std::cerr << "s[" << k << "]= " << (void *)(s[k]);
88  if (s[k]) {
89  fprintf(stderr, " '%c%c%c%c...'", s[k][0], s[k][1], s[k][2], s[k][3]);
90  }
91  std::cerr << std::endl;
92  }
93  std::fflush(stderr);
94 }
95 
96 // template Interpreter::OpF* getTemplatizedOp<Promote<1> >(int);
97 // template Interpreter::OpF* getTemplatizedOp<Promote<2> >(int);
98 // template Interpreter::OpF* getTemplatizedOp<Promote<3> >(int);
99 
101 // template using c)
102 template <char c, template <char c1, int d> class T>
104  switch (i) {
105  case 1:
106  return T<c, 1>::f;
107  case 2:
108  return T<c, 2>::f;
109  case 3:
110  return T<c, 3>::f;
111  case 4:
112  return T<c, 4>::f;
113  case 5:
114  return T<c, 5>::f;
115  case 6:
116  return T<c, 6>::f;
117  case 7:
118  return T<c, 7>::f;
119  case 8:
120  return T<c, 8>::f;
121  case 9:
122  return T<c, 9>::f;
123  case 10:
124  return T<c, 10>::f;
125  case 11:
126  return T<c, 11>::f;
127  case 12:
128  return T<c, 12>::f;
129  case 13:
130  return T<c, 13>::f;
131  case 14:
132  return T<c, 14>::f;
133  case 15:
134  return T<c, 15>::f;
135  case 16:
136  return T<c, 16>::f;
137  default:
138  assert(false && "Invalid dynamic parameter (not supported template)");
139  break;
140  }
141  return nullptr;
142 }
143 
144 namespace {
145 
147 struct BinaryStringOp {
148  static int f(int* opData, double*, char** c, std::vector<int>&) {
149  // get the operand data
150  char*& out = *(char**)c[opData[0]];
151  char* in1 = c[opData[1]];
152  char* in2 = c[opData[2]];
153 
154  // delete previous data and allocate a new buffer, only if needed
155  // NOTE: this is more efficient, but might consume more memory...
156  // Maybe make this behaviour configurable ?
157  size_t len1 = strlen(in1);
158  size_t len2 = strlen(in2);
159  if (out == nullptr || len1 + len2 + 1 > strlen(out))
160  {
161  delete [] out;
162  out = new char [len1 + len2 + 1];
163  }
164 
165  // clear previous evaluation content
166  memset(out, 0, len1 + len2 + 1);
167 
168  // concatenate strings
169  strcat(out, in1);
170  strcat(out + len1, in2);
171  out[len1 + len2] = '\0';
172 
173  // copy to the output
174  c[opData[3]] = out;
175 
176  return 1;
177  }
178 };
179 
181 template <char op, int d>
182 struct BinaryOp {
183  static double niceMod(double a, double b) {
184  if (b == 0) return 0;
185  return a - floor(a / b) * b;
186  }
187 
188  static int f(int* opData, double* fp, char**, std::vector<int>& ) {
189  double* in1 = fp + opData[0];
190  double* in2 = fp + opData[1];
191  double* out = fp + opData[2];
192 
193  for (int k = 0; k < d; k++) {
194  switch (op) {
195  case '+':
196  *out = (*in1) + (*in2);
197  break;
198  case '-':
199  *out = (*in1) - (*in2);
200  break;
201  case '*':
202  *out = (*in1) * (*in2);
203  break;
204  case '/':
205  *out = (*in1) / (*in2);
206  break;
207  case '%':
208  *out = niceMod(*in1, *in2);
209  break;
210  case '^':
211  *out = pow(*in1, *in2);
212  break;
213  // these only make sense with d==1
214  case '<':
215  *out = (*in1) < (*in2);
216  break;
217  case '>':
218  *out = (*in1) > (*in2);
219  break;
220  case 'l':
221  *out = (*in1) <= (*in2);
222  break;
223  case 'g':
224  *out = (*in1) >= (*in2);
225  break;
226  case '&':
227  *out = (*in1) && (*in2);
228  break;
229  case '|':
230  *out = (*in1) || (*in2);
231  break;
232  default:
233  assert(false);
234  }
235  in1++;
236  in2++;
237  out++;
238  }
239  return 1;
240  }
241 };
242 
244 template <char op, int d>
245 struct UnaryOp {
246  static int f(int* opData, double* fp, char**, std::vector<int>&) {
247  double* in = fp + opData[0];
248  double* out = fp + opData[1];
249  for (int k = 0; k < d; k++) {
250  switch (op) {
251  case '-':
252  *out = -(*in);
253  break;
254  case '~':
255  *out = 1 - (*in);
256  break;
257  case '!':
258  *out = !*in;
259  break;
260  default:
261  assert(false);
262  }
263  in++;
264  out++;
265  }
266  return 1;
267  }
268 };
269 
271 template <int d>
272 struct Subscript {
273  static int f(int* opData, double* fp, char**, std::vector<int>&) {
274  int tuple = opData[0];
275  int subscript = int(fp[opData[1]]);
276  int out = opData[2];
277  if (subscript >= d || subscript < 0)
278  fp[out] = 0;
279  else
280  fp[out] = fp[tuple + subscript];
281  return 1;
282  }
283 };
284 
286 template <int d>
287 struct Tuple {
288  static int f(int* opData, double* fp, char**, std::vector<int>&) {
289  int out = opData[d];
290  for (int k = 0; k < d; k++) {
291  fp[out + k] = fp[opData[k]];
292  }
293  return 1;
294  }
295 };
296 
298 template <int d>
299 struct AssignOp {
300  static int f(int* opData, double* fp, char**, std::vector<int>&) {
301  int in = opData[0];
302  int out = opData[1];
303  for (int k = 0; k < d; k++) {
304  fp[out + k] = fp[in + k];
305  }
306  return 1;
307  }
308 };
309 
311 struct AssignStrOp {
312  static int f(int* opData, double*, char** c, std::vector<int>&) {
313  int in = opData[0];
314  int out = opData[1];
315  c[out] = c[in];
316  return 1;
317  }
318 };
319 
321 struct CondJmpRelativeIfFalse {
322  static int f(int* opData, double* fp, char**, std::vector<int>&) {
323  bool cond = (bool)fp[opData[0]];
324  if (!cond)
325  return opData[1];
326  else
327  return 1;
328  }
329 };
330 
332 struct CondJmpRelativeIfTrue {
333  static int f(int* opData, double* fp, char**, std::vector<int>&) {
334  bool cond = (bool)fp[opData[0]];
335  if (cond)
336  return opData[1];
337  else
338  return 1;
339  }
340 };
341 
343 struct JmpRelative {
344  static int f(int* opData, double*, char**, std::vector<int>&) { return opData[0]; }
345 };
346 
348 struct EvalVar {
349  static int f(int* opData, double* fp, char** c, std::vector<int>&) {
350  auto* ref = reinterpret_cast<ExprVarRef*>(c[opData[0]]);
351  if (ref->type().isFP()) {
352  ref->eval(fp + opData[1]);
353  } else {
354  ref->eval(const_cast<const char**>(c + opData[1]));
355  }
356  return 1;
357  }
358 };
359 
361 template <int dim>
362 struct EvalVarBlock {
363  static int f(int* opData, double* fp, char** c, std::vector<int>& callStack) {
364  if (c[0]) {
365  double* basePointer = reinterpret_cast<double*>(c[0]) + opData[0];
366  double* destPointer = fp + opData[1];
367  for (int i = 0; i < dim; i++) destPointer[i] = basePointer[i];
368  }
369  return 1;
370  }
371 };
372 
374 template <char uniform, int dim>
375 struct EvalVarBlockIndirect {
376  static int f(int* opData, double* fp, char** c, std::vector<int>&) {
377  if (c[0]) {
378  int stride = opData[2];
379  int outputVarBlockOffset = opData[0];
380  int destIndex = opData[1];
381  auto indirectIndex = reinterpret_cast<size_t>(c[1]);
382  double* basePointer =
383  reinterpret_cast<double**>(c[0])[outputVarBlockOffset] + (uniform ? 0 : (stride * indirectIndex));
384  double* destPointer = fp + destIndex;
385  for (int i = 0; i < dim; i++) destPointer[i] = basePointer[i];
386  } else {
387  // TODO: this happens in initial evaluation!
388  // std::cerr<<"Did not get data block"<<std::endl;
389  // assert(false && "Did not get data block");
390  }
391  return 1;
392  }
393 };
394 
395 template <char op, int d>
396 struct CompareEqOp {
397  static int f(int* opData, double* fp, char**, std::vector<int>&) {
398  bool result = true;
399  double* in0 = fp + opData[0];
400  double* in1 = fp + opData[1];
401  double* out = fp + opData[2];
402  for (int k = 0; k < d; k++) {
403  switch (op) {
404  case '=':
405  result &= (*in0) == (*in1);
406  break;
407  case '!':
408  result &= (*in0) != (*in1);
409  break;
410  default:
411  assert(false);
412  }
413  in0++;
414  in1++;
415  }
416  *out = result;
417  return 1;
418  }
419 };
420 
421 template <char op>
422 struct CompareEqOp<op, 3> {
423  static int f(int* opData, double* fp, char**, std::vector<int>&) {
424  bool eq = fp[opData[0]] == fp[opData[1]] && fp[opData[0] + 1] == fp[opData[1] + 1] &&
425  fp[opData[0] + 2] == fp[opData[1] + 2];
426  if (op == '=') fp[opData[2]] = eq;
427  if (op == '!') fp[opData[2]] = !eq;
428  return 1;
429  }
430 };
431 
432 template <char op, int d>
433 struct StrCompareEqOp {
434  // TODO: this should rely on tokenization and not use strcmp
435  static int f(int* opData, double* fp, char** c, std::vector<int>&) {
436  switch (op) {
437  case '=':
438  fp[opData[2]] = strcmp(c[opData[0]], c[opData[1]]) == 0;
439  break;
440  case '!':
441  fp[opData[2]] = strcmp(c[opData[0]], c[opData[1]]) != 0;
442  break;
443  }
444  return 1;
445  }
446 };
447 } // namespace
448 
449 namespace {
450 int ProcedureReturn(int* opData, double*, char**, std::vector<int>& callStack) {
451  int newPC = callStack.back();
452  callStack.pop_back();
453  return newPC - opData[0];
454 }
455 }
456 
457 namespace {
458 int ProcedureCall(int* opData, double*, char**, std::vector<int>& callStack) {
459  callStack.push_back(opData[0]);
460  return opData[1];
461 }
462 }
463 
465  _procedurePC = interpreter->nextPC();
466  int lastOperand = 0;
467  for (int c = 0; c < numChildren(); c++) lastOperand = child(c)->buildInterpreter(interpreter);
468  int basePC = interpreter->nextPC();
469  ;
470  interpreter->addOp(ProcedureReturn);
471  // int endPC =
472  interpreter->addOperand(basePC);
473  interpreter->endOp(false);
474  _returnedDataOp = lastOperand;
475 
476  return 0;
477 }
478 
479 int ExprLocalFunctionNode::buildInterpreterForCall(const ExprFuncNode* callerNode, Interpreter* interpreter) const {
480  std::vector<int> operands;
481  for (int c = 0; c < callerNode->numChildren(); c++) {
482  const ExprNode* child = callerNode->child(c);
483  // evaluate the argument
484  int operand = callerNode->child(c)->buildInterpreter(interpreter);
485  if (child->type().isFP()) {
486  if (callerNode->promote(c) != 0) {
487  // promote the argument to the needed type
488  interpreter->addOp(getTemplatizedOp<Promote>(callerNode->promote(c)));
489  // int promotedOperand=interpreter->allocFP(callerNode->promote(c));
490  interpreter->addOperand(operand);
491  interpreter->addOperand(prototype()->interpreterOps(c));
492  interpreter->endOp();
493  } else {
494  interpreter->addOp(getTemplatizedOp<AssignOp>(child->type().dim()));
495  interpreter->addOperand(operand);
496  interpreter->addOperand(prototype()->interpreterOps(c));
497  interpreter->endOp();
498  }
499  } else {
500  // TODO: string
501  assert(false);
502  }
503  operands.push_back(operand);
504  }
505  int outoperand = -1;
506  if (callerNode->type().isFP())
507  outoperand = interpreter->allocFP(callerNode->type().dim());
508  else if (callerNode->type().isString())
509  outoperand = interpreter->allocPtr();
510  else
511  assert(false);
512 
513  int basePC = interpreter->nextPC();
514  interpreter->addOp(ProcedureCall);
515  int returnAddress = interpreter->addOperand(0);
516  interpreter->addOperand(_procedurePC - basePC);
517  interpreter->endOp(false);
518  // set return address
519  interpreter->opData[returnAddress] = interpreter->nextPC();
520 
521  // TODO: copy result back and string
522  interpreter->addOp(getTemplatizedOp<AssignOp>(callerNode->type().dim()));
523  interpreter->addOperand(_returnedDataOp);
524  interpreter->addOperand(outoperand);
525  interpreter->endOp();
526 
527  return outoperand;
528 }
529 
530 int ExprNode::buildInterpreter(Interpreter* interpreter) const {
531  for (int c = 0; c < numChildren(); c++) child(c)->buildInterpreter(interpreter);
532  return -1;
533 }
534 
536  int loc = interpreter->allocFP(1);
537  interpreter->d[loc] = value();
538  return loc;
539 }
540 
542  int loc = interpreter->allocPtr();
543  interpreter->s[loc] = const_cast<char*>(_str.c_str());
544  return loc;
545 }
546 
548  std::vector<int> locs;
549  for (int k = 0; k < numChildren(); k++) {
550  const ExprNode* c = child(k);
551  locs.push_back(c->buildInterpreter(interpreter));
552  }
553  interpreter->addOp(getTemplatizedOp<Tuple>(numChildren()));
554  for (int k = 0; k < numChildren(); k++) interpreter->addOperand(locs[k]);
555  int loc = interpreter->allocFP(numChildren());
556  interpreter->addOperand(loc);
557  interpreter->endOp();
558  return loc;
559 }
560 
562  const ExprNode * child0 = child(0);
563  const ExprNode *child1 = child(1);
564  int dim0 = child0->type().dim();
565  int dim1 = child1->type().dim();
566  int dimout = type().dim();
567  int op0 = child0->buildInterpreter(interpreter);
568  int op1 = child1->buildInterpreter(interpreter);
569  if (dimout > 1) {
570  if (dim0 != dimout) {
571  interpreter->addOp(getTemplatizedOp<Promote>(dimout));
572  int promoteOp0 = interpreter->allocFP(dimout);
573  interpreter->addOperand(op0);
574  interpreter->addOperand(promoteOp0);
575  op0 = promoteOp0;
576  interpreter->endOp();
577  }
578  if (dim1 != dimout) {
579  interpreter->addOp(getTemplatizedOp<Promote>(dimout));
580  int promoteOp1 = interpreter->allocFP(dimout);
581  interpreter->addOperand(op1);
582  interpreter->addOperand(promoteOp1);
583  op1 = promoteOp1;
584  interpreter->endOp();
585  }
586  }
587 
588  // check if the node will output a string of numerical value
589  bool isString = child0->type().isString() || child1->type().isString();
590 
591  // add the operator
592  if (isString == false) {
593  switch (_op) {
594  case '+':
595  interpreter->addOp(getTemplatizedOp2<'+', BinaryOp>(dimout));
596  break;
597  case '-':
598  interpreter->addOp(getTemplatizedOp2<'-', BinaryOp>(dimout));
599  break;
600  case '*':
601  interpreter->addOp(getTemplatizedOp2<'*', BinaryOp>(dimout));
602  break;
603  case '/':
604  interpreter->addOp(getTemplatizedOp2<'/', BinaryOp>(dimout));
605  break;
606  case '^':
607  interpreter->addOp(getTemplatizedOp2<'^', BinaryOp>(dimout));
608  break;
609  case '%':
610  interpreter->addOp(getTemplatizedOp2<'%', BinaryOp>(dimout));
611  break;
612  default:
613  assert(false);
614  }
615  } else {
616  switch (_op) {
617  case '+': {
618  interpreter->addOp(BinaryStringOp::f);
619  int intermediateOp = interpreter->allocPtr();
620  interpreter->s[intermediateOp] = (char*)(&_out);
621  interpreter->addOperand(intermediateOp);
622  break;
623  }
624  default:
625  assert(false);
626  }
627  }
628 
629  // allocate the output
630  int op2 = -1;
631  if (isString == false) {
632  op2 = interpreter->allocFP(dimout);
633  } else {
634  op2 = interpreter->allocPtr();
635  }
636 
637  interpreter->addOperand(op0);
638  interpreter->addOperand(op1);
639  interpreter->addOperand(op2);
640 
641  // NOTE: one of the operand can be a function. If it's the case for
642  // strings, since functions are not immediately executed (they have
643  // endOp(false)) using endOp() here would result in a nullptr
644  // input operand during eval, thus the following arg to endOp.
645  //
646  // TODO: only stop execution if one of the operand is either a
647  // function of a var ref.
648  interpreter->endOp(isString == false);
649 
650  return op2;
651 }
652 
654  const ExprNode* child0 = child(0);
655  int dimout = type().dim();
656  int op0 = child0->buildInterpreter(interpreter);
657 
658  switch (_op) {
659  case '-':
660  interpreter->addOp(getTemplatizedOp2<'-', UnaryOp>(dimout));
661  break;
662  case '~':
663  interpreter->addOp(getTemplatizedOp2<'~', UnaryOp>(dimout));
664  break;
665  case '!':
666  interpreter->addOp(getTemplatizedOp2<'!', UnaryOp>(dimout));
667  break;
668  default:
669  assert(false);
670  }
671  int op1 = interpreter->allocFP(dimout);
672  interpreter->addOperand(op0);
673  interpreter->addOperand(op1);
674  interpreter->endOp();
675 
676  return op1;
677 }
678 
680  const ExprNode * child0 = child(0);
681  const ExprNode *child1 = child(1);
682  int dimin = child0->type().dim();
683  int op0 = child0->buildInterpreter(interpreter);
684  int op1 = child1->buildInterpreter(interpreter);
685  int op2 = interpreter->allocFP(1);
686 
687  interpreter->addOp(getTemplatizedOp<Subscript>(dimin));
688  interpreter->addOperand(op0);
689  interpreter->addOperand(op1);
690  interpreter->addOperand(op2);
691  interpreter->endOp();
692  return op2;
693 }
694 
696  if (const ExprLocalVar* var = _localVar) {
697  // if (const ExprLocalVar* phi = var->getPhi()) var = phi;
698  auto i = interpreter->varToLoc.find(var);
699  if (i != interpreter->varToLoc.end())
700  return i->second;
701  else
702  throw std::runtime_error("Unallocated variable encountered.");
703  } else if (const ExprVarRef* var = _var) {
704  ExprType type = var->type();
705  int destLoc = -1;
706  if (type.isFP()) {
707  int dim = type.dim();
708  destLoc = interpreter->allocFP(dim);
709  } else
710  destLoc = interpreter->allocPtr();
711  if (const auto* blockVarRef = dynamic_cast<const VarBlockCreator::Ref*>(var)) {
712  // TODO: handle strings
713  if (blockVarRef->type().isLifetimeUniform())
714  interpreter->addOp(getTemplatizedOp2<1, EvalVarBlockIndirect>(type.dim()));
715  else
716  interpreter->addOp(getTemplatizedOp2<0, EvalVarBlockIndirect>(type.dim()));
717  interpreter->addOperand(blockVarRef->offset());
718  interpreter->addOperand(destLoc);
719  interpreter->addOperand(blockVarRef->stride());
720  interpreter->endOp();
721  } else {
722  int varRefLoc = interpreter->allocPtr();
723  interpreter->addOp(EvalVar::f);
724  interpreter->s[varRefLoc] = const_cast<char*>(reinterpret_cast<const char*>(var));
725  interpreter->addOperand(varRefLoc);
726  interpreter->addOperand(destLoc);
727  interpreter->endOp();
728  }
729  return destLoc;
730  }
731  return -1;
732 }
733 
735  return interpreter->varToLoc[this] =
736  _type.isFP() ? interpreter->allocFP(_type.dim()) : _type.isString() ? interpreter->allocPtr() : -1;
737 }
738 
740  int loc = _localVar->buildInterpreter(interpreter);
741  assert(loc != -1 && "Invalid type found");
742 
743  ExprType child0Type = child(0)->type();
744  int op0 = child(0)->buildInterpreter(interpreter);
745  if (child0Type.isFP()) {
746  interpreter->addOp(getTemplatizedOp<AssignOp>(child0Type.dim()));
747  } else if (child0Type.isString()) {
748  interpreter->addOp(AssignStrOp::f);
749  } else {
750  assert(false && "Invalid desired assign type");
751  return -1;
752  }
753  interpreter->addOperand(op0);
754  interpreter->addOperand(loc);
755  interpreter->endOp(child0Type.isString() == false);
756  return loc;
757 }
758 
759 void copyVarToPromotedPosition(Interpreter* interpreter, ExprLocalVar* varSource, ExprLocalVar* varDest) {
760  if (varDest->type().isFP()) {
761  int destDim = varDest->type().dim();
762  if (destDim != varSource->type().dim()) {
763  assert(varSource->type().dim() == 1);
764  interpreter->addOp(getTemplatizedOp<Promote>(destDim));
765  } else {
766  interpreter->addOp(getTemplatizedOp<AssignOp>(destDim));
767  }
768  interpreter->addOperand(interpreter->varToLoc[varSource]);
769  interpreter->addOperand(interpreter->varToLoc[varDest]);
770  interpreter->endOp();
771  } else if (varDest->type().isString()) {
772  interpreter->addOp(AssignStrOp::f);
773  interpreter->addOperand(interpreter->varToLoc[varSource]);
774  interpreter->addOperand(interpreter->varToLoc[varDest]);
775  interpreter->endOp();
776  } else {
777  assert(false && "failed to promote invalid type");
778  }
779 }
780 
782  int condop = child(0)->buildInterpreter(interpreter);
783  int basePC = interpreter->nextPC();
784 
785  const auto& merges = _varEnv->merge(_varEnvMergeIndex);
786  // Allocate spots for all the join variables
787  // they are before in the sequence of operands, but it doesn't matter
788  // NOTE: at this point the variables thenVar and elseVar have not been codegen'd
789  for (const auto & it : merges) {
790  ExprLocalVarPhi* finalVar = it.second;
791  if (finalVar->valid()) {
792  finalVar->buildInterpreter(interpreter);
793  }
794  }
795 
796  // Setup the conditional jump
797  interpreter->addOp(CondJmpRelativeIfFalse::f);
798  interpreter->addOperand(condop);
799  int destFalse = interpreter->addOperand(0);
800  interpreter->endOp();
801 
802  // Then block (build interpreter and copy variables out then jump to end)
803  child(1)->buildInterpreter(interpreter);
804  for (const auto & it : merges) {
805  ExprLocalVarPhi* finalVar = it.second;
806  if (finalVar->valid()) {
807  copyVarToPromotedPosition(interpreter, finalVar->_thenVar, finalVar);
808  }
809  }
810  interpreter->addOp(JmpRelative::f);
811  int destEnd = interpreter->addOperand(0);
812  interpreter->endOp();
813 
814  // Else block (build interpreter, copy variables out and then we're at end)
815  int child2PC = interpreter->nextPC();
816  child(2)->buildInterpreter(interpreter);
817  for (const auto & it : merges) {
818  ExprLocalVarPhi* finalVar = it.second;
819  if (finalVar->valid()) {
820  copyVarToPromotedPosition(interpreter, finalVar->_elseVar, finalVar);
821  }
822  }
823 
824  // Patch the jump addresses in the conditional
825  interpreter->opData[destFalse] = child2PC - basePC;
826  interpreter->opData[destEnd] = interpreter->nextPC() - (child2PC - 1);
827 
828  return -1;
829 }
830 
832  const ExprNode * child0 = child(0);
833  const ExprNode *child1 = child(1);
834  assert(type().dim() == 1 && type().isFP());
835 
836  if (_op == '&' || _op == '|') {
837  // Handle short circuiting
838 
839  // allocate output
840  int op2 = interpreter->allocFP(1);
841  // unconditionally evaluate first argument
842  int op0 = child0->buildInterpreter(interpreter);
843  // conditional to check if that argument could continue
844  int basePC = (interpreter->nextPC());
845  interpreter->addOp(_op == '&' ? CondJmpRelativeIfFalse::f : CondJmpRelativeIfTrue::f);
846  interpreter->addOperand(op0);
847  int destFalse = interpreter->addOperand(0);
848  interpreter->endOp();
849  // this is the no-branch case (op1=true for & and op0=false for |), so eval op1
850  int op1 = child1->buildInterpreter(interpreter);
851  // combine with &
852  interpreter->addOp(_op == '&' ? getTemplatizedOp2<'&', BinaryOp>(1) : getTemplatizedOp2<'|', BinaryOp>(1));
853  interpreter->addOperand(op0);
854  interpreter->addOperand(op1);
855  interpreter->addOperand(op2);
856  interpreter->endOp();
857  interpreter->addOp(JmpRelative::f);
858  int destEnd = interpreter->addOperand(0);
859  interpreter->endOp();
860  // this is the branch case (op1=false for & and op0=true for |) so no eval of op1 required
861  // just copy from the op0's value
862  int falseConditionPC = interpreter->nextPC();
863  interpreter->addOp(AssignOp<1>::f);
864  interpreter->addOperand(op0);
865  interpreter->addOperand(op2);
866  interpreter->endOp();
867 
868  // fix PC relative jump addressses
869  interpreter->opData[destFalse] = falseConditionPC - basePC;
870  interpreter->opData[destEnd] = interpreter->nextPC() - (falseConditionPC - 1);
871 
872  return op2;
873 
874  } else {
875  // Noraml case, always have to evaluatee everything
876  int op0 = child0->buildInterpreter(interpreter);
877  int op1 = child1->buildInterpreter(interpreter);
878  switch (_op) {
879  case '<':
880  interpreter->addOp(getTemplatizedOp2<'<', BinaryOp>(1));
881  break;
882  case '>':
883  interpreter->addOp(getTemplatizedOp2<'>', BinaryOp>(1));
884  break;
885  case 'l':
886  interpreter->addOp(getTemplatizedOp2<'l', BinaryOp>(1));
887  break;
888  case 'g':
889  interpreter->addOp(getTemplatizedOp2<'g', BinaryOp>(1));
890  break;
891  case '&':
892  assert(false); // interpreter->addOp(getTemplatizedOp2<'&',BinaryOp>(1));break;
893  case '|':
894  assert(false); // interpreter->addOp(getTemplatizedOp2<'|',BinaryOp>(1));break;
895  default:
896  assert(false);
897  }
898  int op2 = interpreter->allocFP(1);
899  interpreter->addOperand(op0);
900  interpreter->addOperand(op1);
901  interpreter->addOperand(op2);
902  interpreter->endOp();
903  return op2;
904  }
905 }
906 
907 int ExprPrototypeNode::buildInterpreter(Interpreter* interpreter) const {
908  // set up parents
909  _interpreterOps.clear();
910  for (int c = 0; c < numChildren(); c++) {
911  if (const auto* childVarNode = dynamic_cast<const ExprVarNode*>(child(c))) {
912  const ExprType& childType = childVarNode->type();
913  if (childType.isFP()) {
914  int operand = interpreter->allocFP(childType.dim());
915  _interpreterOps.push_back(operand);
916  interpreter->varToLoc[childVarNode->localVar()] = operand;
917  }
918  } else {
919  assert(false);
920  }
921  child(c)->buildInterpreter(interpreter);
922 
923  // make sure we have a slot in our global activation record for the variables!
924  }
925  return 0;
926 }
927 
928 int ExprCompareEqNode::buildInterpreter(Interpreter* interpreter) const {
929  const ExprNode * child0 = child(0);
930  const ExprNode *child1 = child(1);
931  int op0 = child0->buildInterpreter(interpreter);
932  int op1 = child1->buildInterpreter(interpreter);
933 
934  if (child0->type().isFP()) {
935  int dim0 = child0->type().dim();
936  int dim1 = child1->type().dim();
937  int dimCompare = std::max(dim0, dim1);
938  if (dimCompare > 1) {
939  if (dim0 == 1) {
940  interpreter->addOp(getTemplatizedOp<Promote>(dim1));
941  int promotedOp0 = interpreter->allocFP(dim1);
942  interpreter->addOperand(op0);
943  interpreter->addOperand(promotedOp0);
944  interpreter->endOp();
945  op0 = promotedOp0;
946  }
947  if (dim1 == 1) {
948  interpreter->addOp(getTemplatizedOp<Promote>(dim0));
949  int promotedOp1 = interpreter->allocFP(dim0);
950  interpreter->addOperand(op1);
951  interpreter->addOperand(promotedOp1);
952  interpreter->endOp();
953  op1 = promotedOp1;
954  }
955  }
956  if (_op == '=')
957  interpreter->addOp(getTemplatizedOp2<'=', CompareEqOp>(dimCompare));
958  else if (_op == '!')
959  interpreter->addOp(getTemplatizedOp2<'!', CompareEqOp>(dimCompare));
960  else
961  assert(false && "Invalid operation");
962  } else if (child0->type().isString()) {
963  if (_op == '=')
964  interpreter->addOp(getTemplatizedOp2<'=', StrCompareEqOp>(1));
965  else if (_op == '!')
966  interpreter->addOp(getTemplatizedOp2<'!', StrCompareEqOp>(1));
967  else
968  assert(false && "Invalid operation");
969  } else
970  assert(false && "Invalid type for comparison");
971  int op2 = interpreter->allocFP(1);
972  interpreter->addOperand(op0);
973  interpreter->addOperand(op1);
974  interpreter->addOperand(op2);
975  interpreter->endOp(child0->type().isString() == false);
976  return op2;
977 }
978 
979 int ExprCondNode::buildInterpreter(Interpreter* interpreter) const {
980  int opOut = -1;
981  // TODO: handle strings!
982  int dimout = type().dim();
983 
984  // conditional
985  int condOp = child(0)->buildInterpreter(interpreter);
986  int basePC = (interpreter->nextPC());
987  interpreter->addOp(CondJmpRelativeIfFalse::f);
988  interpreter->addOperand(condOp);
989  int destFalse = interpreter->addOperand(0);
990  interpreter->endOp();
991 
992  // true way of working
993  int op1 = child(1)->buildInterpreter(interpreter);
994  if (type().isFP())
995  interpreter->addOp(getTemplatizedOp<AssignOp>(dimout));
996  else if (type().isString())
997  interpreter->addOp(AssignStrOp::f);
998  else
999  assert(false);
1000  interpreter->addOperand(op1);
1001  int dataOutTrue = interpreter->addOperand(-1);
1002  interpreter->endOp(false);
1003 
1004  // jump past false way of working
1005  interpreter->addOp(JmpRelative::f);
1006  int destEnd = interpreter->addOperand(0);
1007  interpreter->endOp();
1008 
1009  // record start of false condition
1010  int child2PC = interpreter->nextPC();
1011 
1012  // false way of working
1013  int op2 = child(2)->buildInterpreter(interpreter);
1014  if (type().isFP())
1015  interpreter->addOp(getTemplatizedOp<AssignOp>(dimout));
1016  else if (type().isString())
1017  interpreter->addOp(AssignStrOp::f);
1018  else
1019  assert(false);
1020  interpreter->addOperand(op2);
1021  int dataOutFalse = interpreter->addOperand(-1);
1022  interpreter->endOp(false);
1023 
1024  // patch up relative jumps
1025  interpreter->opData[destFalse] = child2PC - basePC;
1026  interpreter->opData[destEnd] = interpreter->nextPC() - (child2PC - 1);
1027 
1028  // allocate output
1029  if (type().isFP())
1030  opOut = interpreter->allocFP(type().dim());
1031  else if (type().isString())
1032  opOut = interpreter->allocPtr();
1033  else
1034  assert(false);
1035 
1036  // patch outputs on assigns in each condition
1037  interpreter->opData[dataOutTrue] = opOut;
1038  interpreter->opData[dataOutFalse] = opOut;
1039 
1040  return opOut;
1041 }
1042 
1043 int ExprBlockNode::buildInterpreter(Interpreter* interpreter) const {
1044  assert(numChildren() == 2);
1045  child(0)->buildInterpreter(interpreter);
1046  return child(1)->buildInterpreter(interpreter);
1047 }
1048 
1049 int ExprModuleNode::buildInterpreter(Interpreter* interpreter) const {
1050  int lastIdx = 0;
1051  for (int c = 0; c < numChildren(); c++) {
1052  if (c == numChildren() - 1) interpreter->setPCStart(interpreter->nextPC());
1053  lastIdx = child(c)->buildInterpreter(interpreter);
1054  }
1055  return lastIdx;
1056 }
1057 } // namespace KSeExpr
ExprLocalVar * _localVar
Definition: ExprNode.h:442
int buildInterpreter(Interpreter *interpreter) const override
builds an interpreter. Returns the location index for the evaluated data
int buildInterpreter(Interpreter *interpreter) const override
builds an interpreter. Returns the location index for the evaluated data
char _op
_op '<' less-than, 'l' less-than-eq, '>' greater-than, 'g' greater-than-eq
Definition: ExprNode.h:541
int buildInterpreter(Interpreter *interpreter) const override
builds an interpreter. Returns the location index for the evaluated data
Node that calls a function.
Definition: ExprNode.h:654
int promote(int i) const
Definition: ExprNode.h:758
int buildInterpreter(Interpreter *interpreter) const override
builds an interpreter. Returns the location index for the evaluated data
int buildInterpreter(Interpreter *interpreter) const override
Build the interpreter.
int buildInterpreterForCall(const ExprFuncNode *callerNode, Interpreter *interpreter) const
Build interpreter if we are called.
const ExprPrototypeNode * prototype() const
TODO: Accessor for prototype (probably not needed when we use prep right)
Definition: ExprNode.h:363
ExprLocalVar join (merge) references. Remembers which variables are possible assigners to this.
Definition: ExprEnv.h:84
ExprLocalVar * _thenVar
Definition: ExprEnv.h:115
bool valid() const
Definition: ExprEnv.h:102
ExprLocalVar * _elseVar
Definition: ExprEnv.h:115
ExprLocalVar reference, all local variables in seexpr are subclasses of this or this itself.
Definition: ExprEnv.h:28
int buildInterpreter(Interpreter *interpreter) const
Allocates variable for interpreter.
ExprType type() const
returns type of the variable
Definition: ExprEnv.h:52
virtual int buildInterpreter(Interpreter *interpreter) const
builds an interpreter. Returns the location index for the evaluated data
int numChildren() const
Number of children.
Definition: ExprNode.h:108
const ExprType & type() const
The type of the node.
Definition: ExprNode.h:150
const ExprNode * child(size_t i) const
Get 0 indexed child.
Definition: ExprNode.h:114
int buildInterpreter(Interpreter *interpreter) const override
builds an interpreter. Returns the location index for the evaluated data
double value() const
Definition: ExprNode.h:621
int buildInterpreter(Interpreter *interpreter) const override
builds an interpreter. Returns the location index for the evaluated data
std::string _str
Definition: ExprNode.h:649
int buildInterpreter(Interpreter *interpreter) const override
builds an interpreter. Returns the location index for the evaluated data
int dim() const
Definition: ExprType.h:180
bool isString() const
Definition: ExprType.h:210
bool isFP() const
Direct is predicate checks.
Definition: ExprType.h:190
int buildInterpreter(Interpreter *interpreter) const override
builds an interpreter. Returns the location index for the evaluated data
std::vector< std::pair< std::string, ExprLocalVarPhi * > > & merge(size_t index)
Definition: ExprEnv.h:171
ExprVarRef * _var
Definition: ExprNode.h:605
ExprLocalVar * _localVar
Definition: ExprNode.h:604
int buildInterpreter(Interpreter *interpreter) const override
builds an interpreter. Returns the location index for the evaluated data
const ExprVarRef * var() const
Definition: ExprNode.h:597
abstract class for implementing variable references
Definition: Expression.h:36
virtual ExprType type() const
returns (current) type
Definition: Expression.h:50
int buildInterpreter(Interpreter *interpreter) const override
builds an interpreter. Returns the location index for the evaluated data
std::vector< std::pair< OpF, int > > ops
Definition: Interpreter.h:46
std::vector< double > d
Double data (constants and evaluated)
Definition: Interpreter.h:33
int addOp(OpF op)
! adds an operator to the program (pointing to the data at the current location)
Definition: Interpreter.h:63
void eval(VarBlock *varBlock, bool debug=false)
Evaluate program.
Definition: Interpreter.cpp:20
void endOp(bool execute=true)
Definition: Interpreter.h:73
std::vector< int > callStack
Definition: Interpreter.h:47
std::vector< char * > s
constant and evaluated pointer data
Definition: Interpreter.h:35
int allocFP(int n)
! Allocate a floating point set of data of dimension n
Definition: Interpreter.h:94
int(*)(int *, double *, char **, std::vector< int > &) OpF
Op function pointer arguments are (int* currOpData,double* currD,char** c,std::stack<int>& callStacku...
Definition: Interpreter.h:44
int nextPC() const
Return the position that the next instruction will be placed at.
Definition: Interpreter.h:60
int allocPtr()
Allocate a pointer location (can be anything, but typically space for char*)
Definition: Interpreter.h:101
std::vector< int > opData
Ooperands to op.
Definition: Interpreter.h:37
int addOperand(int param)
! Adds an operand. Note this should be done after doing the addOp!
Definition: Interpreter.h:86
void print(int pc=-1) const
Debug by printing program.
Definition: Interpreter.cpp:58
Internally implemented var ref used by SeExpr.
Definition: VarBlock.h:94
A thread local evaluation context. Just allocate and fill in with data.
Definition: VarBlock.h:22
int indirectIndex
indirect index to add to pointer based data
Definition: VarBlock.h:64
std::vector< char * > s
copy of Interpreter's str data
Definition: VarBlock.h:73
std::vector< double > d
copy of Interpreter's double data
Definition: VarBlock.h:70
char ** data()
Raw data of the data block pointer (used by compiler)
Definition: VarBlock.h:76
bool threadSafe
if true, interpreter's data will be copied to this instance before evaluation.
Definition: VarBlock.h:67
KSeExpr_DEFAULT double_t floor(double_t val)
Definition: Utils.cpp:168
static Interpreter::OpF getTemplatizedOp2(int i)
Return the function f encapsulated in class T for the dynamic i converted to a static d....
const ExprStrNode * isString(const ExprNode *testee)
Definition: ExprPatterns.h:36
void copyVarToPromotedPosition(Interpreter *interpreter, ExprLocalVar *varSource, ExprLocalVar *varDest)