1 | /* |
2 | * This Source Code Form is subject to the terms of the Mozilla Public |
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
5 | * |
6 | * Copyright 1997 - July 2008 CWI, August 2008 - 2019 MonetDB B.V. |
7 | */ |
8 | |
9 | /* |
10 | * Author M. Kersten |
11 | * The MAL Interpreter |
12 | */ |
13 | #include "monetdb_config.h" |
14 | #include "mal_runtime.h" |
15 | #include "mal_interpreter.h" |
16 | #include "mal_resource.h" |
17 | #include "mal_listing.h" |
18 | #include "mal_debugger.h" /* for mdbStep() */ |
19 | #include "mal_type.h" |
20 | #include "mal_private.h" |
21 | |
22 | static lng qptimeout = 0; /* how often we print still running queries (usec) */ |
23 | |
24 | void |
25 | setqptimeout(lng usecs) |
26 | { |
27 | qptimeout = usecs; |
28 | } |
29 | |
30 | inline |
31 | ptr getArgReference(MalStkPtr stk, InstrPtr pci, int k) |
32 | { |
33 | /* the C standard says: "A pointer to a union object, suitably |
34 | * converted, points to each of its members (or if a member is a |
35 | * bit-field, then to the unit in which it resides), and vice |
36 | * versa." */ |
37 | return (ptr) &stk->stk[pci->argv[k]].val; |
38 | } |
39 | |
40 | str malCommandCall(MalStkPtr stk, InstrPtr pci) |
41 | { |
42 | str ret= MAL_SUCCEED; |
43 | |
44 | switch (pci->argc) { |
45 | case 0: ret = (*pci->fcn)(); |
46 | break; |
47 | case 1: ret = (*pci->fcn)( |
48 | getArgReference(stk, pci, 0)); |
49 | break; |
50 | case 2: ret = (*pci->fcn)( |
51 | getArgReference(stk, pci, 0), |
52 | getArgReference(stk, pci, 1)); |
53 | break; |
54 | case 3: ret = (*pci->fcn)( |
55 | getArgReference(stk, pci, 0), |
56 | getArgReference(stk, pci, 1), |
57 | getArgReference(stk, pci, 2)); |
58 | break; |
59 | case 4: ret = (*pci->fcn)( |
60 | getArgReference(stk, pci, 0), |
61 | getArgReference(stk, pci, 1), |
62 | getArgReference(stk, pci, 2), |
63 | getArgReference(stk, pci, 3)); |
64 | break; |
65 | case 5: ret = (*pci->fcn)( |
66 | getArgReference(stk, pci, 0), |
67 | getArgReference(stk, pci, 1), |
68 | getArgReference(stk, pci, 2), |
69 | getArgReference(stk, pci, 3), |
70 | getArgReference(stk, pci, 4)); |
71 | break; |
72 | case 6: ret = (*pci->fcn)( |
73 | getArgReference(stk, pci, 0), |
74 | getArgReference(stk, pci, 1), |
75 | getArgReference(stk, pci, 2), |
76 | getArgReference(stk, pci, 3), |
77 | getArgReference(stk, pci, 4), |
78 | getArgReference(stk, pci, 5)); |
79 | break; |
80 | case 7: ret = (*pci->fcn)( |
81 | getArgReference(stk, pci, 0), |
82 | getArgReference(stk, pci, 1), |
83 | getArgReference(stk, pci, 2), |
84 | getArgReference(stk, pci, 3), |
85 | getArgReference(stk, pci, 4), |
86 | getArgReference(stk, pci, 5), |
87 | getArgReference(stk, pci, 6)); |
88 | break; |
89 | case 8: ret = (*pci->fcn)( |
90 | getArgReference(stk, pci, 0), |
91 | getArgReference(stk, pci, 1), |
92 | getArgReference(stk, pci, 2), |
93 | getArgReference(stk, pci, 3), |
94 | getArgReference(stk, pci, 4), |
95 | getArgReference(stk, pci, 5), |
96 | getArgReference(stk, pci, 6), |
97 | getArgReference(stk, pci, 7)); |
98 | break; |
99 | case 9: ret = (*pci->fcn)( |
100 | getArgReference(stk, pci, 0), |
101 | getArgReference(stk, pci, 1), |
102 | getArgReference(stk, pci, 2), |
103 | getArgReference(stk, pci, 3), |
104 | getArgReference(stk, pci, 4), |
105 | getArgReference(stk, pci, 5), |
106 | getArgReference(stk, pci, 6), |
107 | getArgReference(stk, pci, 7), |
108 | getArgReference(stk, pci, 8)); |
109 | break; |
110 | case 10: ret = (*pci->fcn)( |
111 | getArgReference(stk, pci, 0), |
112 | getArgReference(stk, pci, 1), |
113 | getArgReference(stk, pci, 2), |
114 | getArgReference(stk, pci, 3), |
115 | getArgReference(stk, pci, 4), |
116 | getArgReference(stk, pci, 5), |
117 | getArgReference(stk, pci, 6), |
118 | getArgReference(stk, pci, 7), |
119 | getArgReference(stk, pci, 8), |
120 | getArgReference(stk, pci, 9)); |
121 | break; |
122 | case 11: ret = (*pci->fcn)( |
123 | getArgReference(stk, pci, 0), |
124 | getArgReference(stk, pci, 1), |
125 | getArgReference(stk, pci, 2), |
126 | getArgReference(stk, pci, 3), |
127 | getArgReference(stk, pci, 4), |
128 | getArgReference(stk, pci, 5), |
129 | getArgReference(stk, pci, 6), |
130 | getArgReference(stk, pci, 7), |
131 | getArgReference(stk, pci, 8), |
132 | getArgReference(stk, pci, 9), |
133 | getArgReference(stk, pci, 10)); |
134 | break; |
135 | case 12: ret = (*pci->fcn)( |
136 | getArgReference(stk, pci, 0), |
137 | getArgReference(stk, pci, 1), |
138 | getArgReference(stk, pci, 2), |
139 | getArgReference(stk, pci, 3), |
140 | getArgReference(stk, pci, 4), |
141 | getArgReference(stk, pci, 5), |
142 | getArgReference(stk, pci, 6), |
143 | getArgReference(stk, pci, 7), |
144 | getArgReference(stk, pci, 8), |
145 | getArgReference(stk, pci, 9), |
146 | getArgReference(stk, pci, 10), |
147 | getArgReference(stk, pci, 11)); |
148 | break; |
149 | case 13: ret = (*pci->fcn)( |
150 | getArgReference(stk, pci, 0), |
151 | getArgReference(stk, pci, 1), |
152 | getArgReference(stk, pci, 2), |
153 | getArgReference(stk, pci, 3), |
154 | getArgReference(stk, pci, 4), |
155 | getArgReference(stk, pci, 5), |
156 | getArgReference(stk, pci, 6), |
157 | getArgReference(stk, pci, 7), |
158 | getArgReference(stk, pci, 8), |
159 | getArgReference(stk, pci, 9), |
160 | getArgReference(stk, pci, 10), |
161 | getArgReference(stk, pci, 11), |
162 | getArgReference(stk, pci, 12)); |
163 | break; |
164 | case 14: ret = (*pci->fcn)( |
165 | getArgReference(stk, pci, 0), |
166 | getArgReference(stk, pci, 1), |
167 | getArgReference(stk, pci, 2), |
168 | getArgReference(stk, pci, 3), |
169 | getArgReference(stk, pci, 4), |
170 | getArgReference(stk, pci, 5), |
171 | getArgReference(stk, pci, 6), |
172 | getArgReference(stk, pci, 7), |
173 | getArgReference(stk, pci, 8), |
174 | getArgReference(stk, pci, 9), |
175 | getArgReference(stk, pci, 10), |
176 | getArgReference(stk, pci, 11), |
177 | getArgReference(stk, pci, 12), |
178 | getArgReference(stk, pci, 13)); |
179 | break; |
180 | case 15: ret = (*pci->fcn)( |
181 | getArgReference(stk, pci, 0), |
182 | getArgReference(stk, pci, 1), |
183 | getArgReference(stk, pci, 2), |
184 | getArgReference(stk, pci, 3), |
185 | getArgReference(stk, pci, 4), |
186 | getArgReference(stk, pci, 5), |
187 | getArgReference(stk, pci, 6), |
188 | getArgReference(stk, pci, 7), |
189 | getArgReference(stk, pci, 8), |
190 | getArgReference(stk, pci, 9), |
191 | getArgReference(stk, pci, 10), |
192 | getArgReference(stk, pci, 11), |
193 | getArgReference(stk, pci, 12), |
194 | getArgReference(stk, pci, 13), |
195 | getArgReference(stk, pci, 14)); |
196 | break; |
197 | case 16: ret = (*pci->fcn)( |
198 | getArgReference(stk, pci, 0), |
199 | getArgReference(stk, pci, 1), |
200 | getArgReference(stk, pci, 2), |
201 | getArgReference(stk, pci, 3), |
202 | getArgReference(stk, pci, 4), |
203 | getArgReference(stk, pci, 5), |
204 | getArgReference(stk, pci, 6), |
205 | getArgReference(stk, pci, 7), |
206 | getArgReference(stk, pci, 8), |
207 | getArgReference(stk, pci, 9), |
208 | getArgReference(stk, pci, 10), |
209 | getArgReference(stk, pci, 11), |
210 | getArgReference(stk, pci, 12), |
211 | getArgReference(stk, pci, 13), |
212 | getArgReference(stk, pci, 14), |
213 | getArgReference(stk, pci, 15)); |
214 | break; |
215 | default: |
216 | throw(MAL, "mal.interpreter" , "too many arguments for command call" ); |
217 | } |
218 | return ret; |
219 | } |
220 | |
221 | /* |
222 | * Copy the constant values onto the stack frame |
223 | * Also we cannot overwrite values on the stack as this maybe part of a |
224 | * sequence of factory calls. |
225 | */ |
226 | #define initStack(S, R)\ |
227 | for (i = S; i < mb->vtop; i++) {\ |
228 | lhs = &stk->stk[i];\ |
229 | if (isVarConstant(mb, i) > 0) {\ |
230 | if (!isVarDisabled(mb, i)) {\ |
231 | rhs = &getVarConstant(mb, i);\ |
232 | if(VALcopy(lhs, rhs) == NULL) \ |
233 | R = 0; \ |
234 | }\ |
235 | } else {\ |
236 | lhs->vtype = getVarGDKType(mb, i);\ |
237 | lhs->val.pval = 0;\ |
238 | lhs->len = 0;\ |
239 | }\ |
240 | } |
241 | |
242 | int |
243 | isNotUsedIn(InstrPtr p, int start, int a) |
244 | { |
245 | int k; |
246 | for (k = start; k < p->argc; k++) |
247 | if (getArg(p, k) == a) |
248 | return 0; |
249 | return 1; |
250 | } |
251 | |
252 | MalStkPtr |
253 | prepareMALstack(MalBlkPtr mb, int size) |
254 | { |
255 | MalStkPtr stk = NULL; |
256 | int i, res = 1; |
257 | ValPtr lhs, rhs; |
258 | |
259 | stk = newGlobalStack(size); |
260 | if (!stk) { |
261 | return NULL; |
262 | } |
263 | //memset((char *)stk, 0, stackSize(size)); already set |
264 | //stk->stksize = size; |
265 | stk->stktop = mb->vtop; |
266 | stk->blk = mb; |
267 | stk->workers = 0; |
268 | stk->memory = 0; |
269 | initStack(0, res); |
270 | if(!res) { |
271 | freeStack(stk); |
272 | return NULL; |
273 | } |
274 | return stk; |
275 | } |
276 | |
277 | str runMAL(Client cntxt, MalBlkPtr mb, MalBlkPtr mbcaller, MalStkPtr env) |
278 | { |
279 | MalStkPtr stk = NULL; |
280 | int i; |
281 | ValPtr lhs, rhs; |
282 | str ret; |
283 | (void) mbcaller; |
284 | |
285 | /* Prepare a new interpreter call. This involves two steps, (1) |
286 | * allocate the minimum amount of stack space needed, some slack |
287 | * resources are included to permit code optimizers to add a few |
288 | * variables at run time, (2) copying the arguments into the new |
289 | * stack frame. |
290 | * |
291 | * The env stackframe is set when a MAL function is called |
292 | * recursively. Alternatively, there is no caller but a stk to be |
293 | * re-used for interpretation. We assume here that it aligns with |
294 | * the variable table of the routine being called. |
295 | * |
296 | * allocate space for value stack the global stack should be large |
297 | * enough |
298 | */ |
299 | cntxt->lastcmd= time(0); |
300 | ATOMIC_SET(&cntxt->lastprint, GDKusec()); |
301 | if (env != NULL) { |
302 | int res = 1; |
303 | stk = env; |
304 | if (mb != stk->blk) |
305 | throw(MAL, "mal.interpreter" ,"misalignment of symbols" ); |
306 | if (mb->vtop > stk->stksize) |
307 | throw(MAL, "mal.interpreter" ,"stack too small" ); |
308 | initStack(env->stkbot, res); |
309 | if(!res) |
310 | throw(MAL, "mal.interpreter" , MAL_MALLOC_FAIL); |
311 | } else { |
312 | stk = prepareMALstack(mb, mb->vsize); |
313 | if (stk == 0) |
314 | throw(MAL, "mal.interpreter" , MAL_STACK_FAIL); |
315 | stk->blk = mb; |
316 | stk->cmd = cntxt->itrace; /* set debug mode */ |
317 | /*safeguardStack*/ |
318 | if( env){ |
319 | stk->stkdepth = stk->stksize + env->stkdepth; |
320 | stk->calldepth = env->calldepth + 1; |
321 | stk->up = env; |
322 | if (stk->calldepth > 256) |
323 | throw(MAL, "mal.interpreter" , MAL_CALLDEPTH_FAIL); |
324 | } |
325 | /* |
326 | * An optimization is to copy all constant variables used in |
327 | * functions immediately onto the value stack. Then we do not |
328 | * have to check for their location later on any more. At some |
329 | * point, the effect is optimal, if at least several constants |
330 | * are referenced in a function (a gain on tst400a of 20% has |
331 | * been observed due the small size of the function). |
332 | */ |
333 | } |
334 | if (stk->cmd && env && stk->cmd != 'f') |
335 | stk->cmd = env->cmd; |
336 | ret = runMALsequence(cntxt, mb, 1, 0, stk, env, 0); |
337 | |
338 | /* pass the new debug mode to the caller */ |
339 | if (stk->cmd && env && stk->cmd != 'f') |
340 | env->cmd = stk->cmd; |
341 | if (!stk->keepAlive && garbageControl(getInstrPtr(mb, 0))) |
342 | garbageCollector(cntxt, mb, stk, env != stk); |
343 | if (stk && stk != env) |
344 | freeStack(stk); |
345 | if (ret == MAL_SUCCEED && cntxt->querytimeout && mb->starttime && GDKusec()- mb->starttime > cntxt->querytimeout) |
346 | throw(MAL, "mal.interpreter" , SQLSTATE(HYT00) RUNTIME_QRY_TIMEOUT); |
347 | return ret; |
348 | } |
349 | |
350 | /* Single instruction |
351 | * It is possible to re-enter the interpreter at a specific place. |
352 | * This is used in the area where we need to support co-routines. |
353 | * |
354 | * A special case for MAL interpretation is to execute just one instruction. |
355 | * This is typically used by optimizers and schedulers that need part of the |
356 | * answer to direct their actions. Or, a dataflow scheduler could step in |
357 | * to enforce a completely different execution order. |
358 | */ |
359 | str reenterMAL(Client cntxt, MalBlkPtr mb, int startpc, int stoppc, MalStkPtr stk) |
360 | { |
361 | str ret; |
362 | int keepAlive; |
363 | |
364 | if (stk == NULL) |
365 | throw(MAL, "mal.interpreter" , MAL_STACK_FAIL); |
366 | keepAlive = stk->keepAlive; |
367 | ret = runMALsequence(cntxt, mb, startpc, stoppc, stk, 0, 0); |
368 | |
369 | /* pass the new debug mode to the caller */ |
370 | if (keepAlive == 0 && garbageControl(getInstrPtr(mb, 0))) |
371 | garbageCollector(cntxt, mb, stk, stk != 0); |
372 | return ret; |
373 | } |
374 | |
375 | /* |
376 | * Front ends may benefit from a more direct call to any of the MAL |
377 | * procedural abstractions. The argument list points to the arguments |
378 | * for the block to be executed. An old stack frame may be re-used, |
379 | * but it is then up to the caller to ensure it is properly |
380 | * initialized. |
381 | * The call does not return values, they are ignored. |
382 | */ |
383 | str |
384 | callMAL(Client cntxt, MalBlkPtr mb, MalStkPtr *env, ValPtr argv[], char debug) |
385 | { |
386 | MalStkPtr stk = NULL; |
387 | str ret = MAL_SUCCEED; |
388 | int i; |
389 | ValPtr lhs; |
390 | InstrPtr pci = getInstrPtr(mb, 0); |
391 | |
392 | cntxt->lastcmd= time(0); |
393 | #ifdef DEBUG_CALLMAL |
394 | fprintf(stderr, "callMAL\n" ); |
395 | fprintInstruction(stderr, mb, 0, pci, LIST_MAL_ALL); |
396 | #endif |
397 | switch (pci->token) { |
398 | case FUNCTIONsymbol: |
399 | case FCNcall: |
400 | /* |
401 | * Prepare the stack frame for this operation. Copy all the arguments |
402 | * in place. We assume that the caller has supplied pointers for |
403 | * all arguments and return values. |
404 | */ |
405 | if (*env == NULL) { |
406 | stk = prepareMALstack(mb, mb->vsize); |
407 | if (stk == NULL) |
408 | throw(MAL, "mal.interpreter" , SQLSTATE(HY001) MAL_MALLOC_FAIL); |
409 | stk->up = 0; |
410 | *env = stk; |
411 | } else { |
412 | ValPtr lhs, rhs; |
413 | int res = 1; |
414 | |
415 | stk = *env; |
416 | initStack(0, res); |
417 | if(!res) |
418 | throw(MAL, "mal.interpreter" , SQLSTATE(HY001) MAL_MALLOC_FAIL); |
419 | } |
420 | assert(stk); |
421 | for (i = pci->retc; i < pci->argc; i++) { |
422 | lhs = &stk->stk[pci->argv[i]]; |
423 | if (VALcopy(lhs, argv[i]) == NULL) |
424 | throw(MAL, "mal.interpreter" , SQLSTATE(HY001) MAL_MALLOC_FAIL); |
425 | if (lhs->vtype == TYPE_bat) |
426 | BBPretain(lhs->val.bval); |
427 | } |
428 | stk->cmd = debug; |
429 | ret = runMALsequence(cntxt, mb, 1, 0, stk, 0, 0); |
430 | break; |
431 | case FACTORYsymbol: |
432 | case FACcall: |
433 | ret = callFactory(cntxt, mb, argv, debug); |
434 | break; |
435 | case PATcall: |
436 | case CMDcall: |
437 | default: |
438 | throw(MAL, "mal.interpreter" , RUNTIME_UNKNOWN_INSTRUCTION); |
439 | } |
440 | if (stk) |
441 | garbageCollector(cntxt, mb, stk, TRUE); |
442 | if ( ret == MAL_SUCCEED && cntxt->querytimeout && mb->starttime && GDKusec()- mb->starttime > cntxt->querytimeout) |
443 | throw(MAL, "mal.interpreter" , SQLSTATE(HYT00) RUNTIME_QRY_TIMEOUT); |
444 | return ret; |
445 | } |
446 | |
447 | /* |
448 | * The core of the interpreter is presented next. It takes the context |
449 | * information and starts the interpretation at the designated |
450 | * instruction. Note that the stack frame is aligned and initialized |
451 | * in the enclosing routine. When we start executing the first |
452 | * instruction, we take the wall-clock time for resource management. |
453 | */ |
454 | str runMALsequence(Client cntxt, MalBlkPtr mb, int startpc, |
455 | int stoppc, MalStkPtr stk, MalStkPtr env, InstrPtr pcicaller) |
456 | { |
457 | ValPtr lhs, rhs, v; |
458 | int i, k; |
459 | InstrPtr pci = 0; |
460 | int exceptionVar; |
461 | str ret = MAL_SUCCEED, localGDKerrbuf= GDKerrbuf; |
462 | ValRecord backups[16]; |
463 | ValPtr backup; |
464 | int garbages[16], *garbage; |
465 | int stkpc = 0; |
466 | RuntimeProfileRecord runtimeProfile, runtimeProfileFunction; |
467 | lng lastcheck = 0; |
468 | int startedProfileQueue = 0; |
469 | #define CHECKINTERVAL 1000 /* how often do we check for client disconnect */ |
470 | runtimeProfile.ticks = runtimeProfileFunction.ticks = 0; |
471 | |
472 | if (stk == NULL) |
473 | throw(MAL, "mal.interpreter" , MAL_STACK_FAIL); |
474 | |
475 | /* prepare extended backup and garbage structures */ |
476 | if (startpc+1 == stoppc) { |
477 | pci = getInstrPtr(mb, startpc); |
478 | if (pci->argc > 16) { |
479 | backup = GDKmalloc(pci->argc * sizeof(ValRecord)); |
480 | garbage = (int*)GDKzalloc(pci->argc * sizeof(int)); |
481 | if( backup == NULL || garbage == NULL) { |
482 | GDKfree(backup); |
483 | GDKfree(garbage); |
484 | throw(MAL, "mal.interpreter" , SQLSTATE(HY001) MAL_MALLOC_FAIL); |
485 | } |
486 | } else { |
487 | backup = backups; |
488 | garbage = garbages; |
489 | memset(garbages, 0, sizeof(garbages)); |
490 | } |
491 | } else if ( mb->maxarg > 16 ){ |
492 | backup = GDKmalloc(mb->maxarg * sizeof(ValRecord)); |
493 | garbage = (int*)GDKzalloc(mb->maxarg * sizeof(int)); |
494 | if( backup == NULL || garbage == NULL) { |
495 | GDKfree(backup); |
496 | GDKfree(garbage); |
497 | throw(MAL, "mal.interpreter" , SQLSTATE(HY001) MAL_MALLOC_FAIL); |
498 | } |
499 | } else { |
500 | backup = backups; |
501 | garbage = garbages; |
502 | memset(garbages, 0, sizeof(garbages)); |
503 | } |
504 | |
505 | /* also produce event record for start of function */ |
506 | if ( startpc == 1 && startpc < mb->stop ){ |
507 | startedProfileQueue = 1; |
508 | runtimeProfileInit(cntxt, mb, stk); |
509 | runtimeProfileBegin(cntxt, mb, stk, getInstrPtr(mb,0), &runtimeProfileFunction); |
510 | mb->starttime = GDKusec(); |
511 | if (cntxt->sessiontimeout && mb->starttime - cntxt->session > cntxt->sessiontimeout) { |
512 | if ( backup != backups) GDKfree(backup); |
513 | if ( garbage != garbages) GDKfree(garbage); |
514 | throw(MAL, "mal.interpreter" , SQLSTATE(HYT00) RUNTIME_SESSION_TIMEOUT); |
515 | } |
516 | } |
517 | stkpc = startpc; |
518 | exceptionVar = -1; |
519 | |
520 | while (stkpc < mb->stop && stkpc != stoppc) { |
521 | // incomplete block being executed, requires at least signature and end statement |
522 | pci = getInstrPtr(mb, stkpc); |
523 | if (cntxt->mode == FINISHCLIENT){ |
524 | stkpc = stoppc; |
525 | if (ret == MAL_SUCCEED) |
526 | ret= createException(MAL, "mal.interpreter" , "prematurely stopped client" ); |
527 | break; |
528 | } |
529 | #ifndef NDEBUG |
530 | if (cntxt->itrace || stk->status) { |
531 | if (stk->status == 'p'){ |
532 | // execution is paused |
533 | while ( stk->status == 'p') |
534 | MT_sleep_ms(50); |
535 | continue; |
536 | } |
537 | if ( stk->status == 'q') |
538 | stk->cmd = 'x'; |
539 | |
540 | if (stk->cmd == 0) |
541 | stk->cmd = cntxt->itrace; |
542 | mdbStep(cntxt, mb, stk, stkpc); |
543 | if (stk->cmd == 'x' ) { |
544 | stk->cmd = 0; |
545 | stkpc = mb->stop; |
546 | ret= createException(MAL, "mal.interpreter" , "prematurely stopped client" ); |
547 | break; |
548 | } |
549 | } |
550 | #endif |
551 | |
552 | //Ensure we spread system resources over multiple users as well. |
553 | runtimeProfileBegin(cntxt, mb, stk, pci, &runtimeProfile); |
554 | if (runtimeProfile.ticks > lastcheck + CHECKINTERVAL) { |
555 | if (cntxt->fdin && !mnstr_isalive(cntxt->fdin->s)) { |
556 | cntxt->mode = FINISHCLIENT; |
557 | stkpc = stoppc; |
558 | ret= createException(MAL, "mal.interpreter" , "prematurely stopped client" ); |
559 | break; |
560 | } |
561 | lastcheck = runtimeProfile.ticks; |
562 | } |
563 | |
564 | if (qptimeout > 0) { |
565 | lng t = GDKusec(); |
566 | ATOMIC_BASE_TYPE lp = ATOMIC_GET(&cntxt->lastprint); |
567 | if ((lng) lp + qptimeout < t) { |
568 | /* if still the same, replace lastprint with current |
569 | * time and print the query */ |
570 | if (ATOMIC_CAS(&cntxt->lastprint, &lp, t)) { |
571 | const char *q = cntxt->getquery ? cntxt->getquery(cntxt) : NULL; |
572 | fprintf(stderr, "#%s: query already running " LLFMT"s: %.200s\n" , |
573 | cntxt->mythread->name, |
574 | (lng) (time(0) - cntxt->lastcmd), |
575 | q ? q : "" ); |
576 | } |
577 | } |
578 | } |
579 | |
580 | /* The interpreter loop |
581 | * The interpreter is geared towards execution a MAL |
582 | * procedure together with all its descendant |
583 | * invocations. As such, it provides the MAL abtract |
584 | * machine processor. |
585 | * |
586 | * The value-stack frame of the surrounding scope is |
587 | * needed to resolve binding values. Getting (putting) a |
588 | * value from (into) a surrounding scope should be guarded |
589 | * with the exclusive access lock. This situation is |
590 | * encapsulated by a bind() function call, whose |
591 | * parameters contain the access mode required. |
592 | * |
593 | * The formal procedure arguments are assumed to always |
594 | * occupy the first elements in the value stack. |
595 | * |
596 | * Before we execute an instruction the variables to be |
597 | * garbage collected are identified. In the post-execution |
598 | * phase they are removed. |
599 | */ |
600 | for (i = 0; i < pci->retc; i++) |
601 | backup[i] = stk->stk[getArg(pci, i)]; |
602 | |
603 | if (garbageControl(pci)) { |
604 | for (i = 0; i < pci->argc; i++) { |
605 | int a = getArg(pci, i); |
606 | |
607 | if (stk->stk[a].vtype == TYPE_bat && getEndScope(mb, a) == stkpc && isNotUsedIn(pci, i + 1, a)) |
608 | garbage[i] = a; |
609 | else |
610 | garbage[i] = -1; |
611 | } |
612 | } |
613 | |
614 | freeException(ret); |
615 | ret = MAL_SUCCEED; |
616 | switch (pci->token) { |
617 | case ASSIGNsymbol: |
618 | /* Assignment command |
619 | * The assignment statement copies values around on |
620 | * the stack frame, including multiple assignments. |
621 | * |
622 | * Pushing constants/initial values onto the stack is |
623 | * a separate operation. It takes the constant value |
624 | * discovered at compile time and stored in the symbol |
625 | * table and moves it to the stackframe location. This |
626 | * activity is made part of the start-up procedure. |
627 | * |
628 | * The before after calls should be reconsidered here, |
629 | * because their. They seem superflous and the way |
630 | * they are used will cause errors in multi-assignment |
631 | * statements. |
632 | */ |
633 | for (k = 0, i = pci->retc; k < pci->retc && i < pci->argc; i++, k++) { |
634 | lhs = &stk->stk[pci->argv[k]]; |
635 | rhs = &stk->stk[pci->argv[i]]; |
636 | if(VALcopy(lhs, rhs) == NULL) { |
637 | ret = createException(MAL, "mal.interpreter" , MAL_MALLOC_FAIL); |
638 | break; |
639 | } else if (lhs->vtype == TYPE_bat && !is_bat_nil(lhs->val.bval)) |
640 | BBPretain(lhs->val.bval); |
641 | } |
642 | break; |
643 | case PATcall: |
644 | if (pci->fcn == NULL) { |
645 | ret = createException(MAL,"mal.interpreter" , "address of pattern %s.%s missing" , pci->modname, pci->fcnname); |
646 | } else { |
647 | ret = (*pci->fcn)(cntxt, mb, stk, pci); |
648 | #ifndef NDEBUG |
649 | if (ret == MAL_SUCCEED) { |
650 | /* check that the types of actual results match |
651 | * expected results */ |
652 | for (i = 0; i < pci->retc; i++) { |
653 | int a = getArg(pci, i); |
654 | int t = getArgType(mb, pci, i); |
655 | |
656 | if (isaBatType(t)) { |
657 | bat bid = stk->stk[a].val.bval; |
658 | BAT *_b = BATdescriptor(bid); |
659 | t = getBatType(t); |
660 | assert(stk->stk[a].vtype == TYPE_bat); |
661 | assert(is_bat_nil(bid) || |
662 | t == TYPE_any || |
663 | ATOMtype(_b->ttype) == ATOMtype(t)); |
664 | if(_b) BBPunfix(bid); |
665 | } else { |
666 | assert(t == stk->stk[a].vtype); |
667 | } |
668 | } |
669 | } |
670 | #endif |
671 | } |
672 | break; |
673 | case CMDcall: |
674 | ret = malCommandCall(stk, pci); |
675 | #ifndef NDEBUG |
676 | if (ret == MAL_SUCCEED) { |
677 | /* check that the types of actual results match |
678 | * expected results */ |
679 | for (i = 0; i < pci->retc; i++) { |
680 | int a = getArg(pci, i); |
681 | int t = getArgType(mb, pci, i); |
682 | |
683 | if (isaBatType(t)) { |
684 | bat bid = stk->stk[a].val.bval; |
685 | t = getBatType(t); |
686 | assert(stk->stk[a].vtype == TYPE_bat); |
687 | assert(is_bat_nil(bid) || |
688 | t == TYPE_any || |
689 | ATOMtype(BBP_desc(bid)->ttype) == ATOMtype(t)); |
690 | } else { |
691 | assert(t == stk->stk[a].vtype); |
692 | } |
693 | } |
694 | } |
695 | #endif |
696 | break; |
697 | case FACcall: |
698 | /* |
699 | * Factory calls are more involved. At this stage it |
700 | * is a synchrononous call to the factory manager. |
701 | * Factory calls should deal with the reference |
702 | * counting. |
703 | */ |
704 | if (pci->blk == NULL) |
705 | ret = createException(MAL,"mal.interpreter" , "%s.%s[%d] reference to MAL function missing" , getModuleId(pci), getFunctionId(pci), pci->pc); |
706 | else { |
707 | /* show call before entering the factory */ |
708 | #ifndef NDEBUG |
709 | if (cntxt->itrace) { |
710 | if (stk->cmd == 0) |
711 | stk->cmd = cntxt->itrace; |
712 | mdbStep(cntxt, pci->blk, stk, 0); |
713 | if (stk->cmd == 'x') { |
714 | stk->cmd = 0; |
715 | stkpc = mb->stop; |
716 | } |
717 | } |
718 | #endif |
719 | ret = runFactory(cntxt, pci->blk, mb, stk, pci); |
720 | } |
721 | break; |
722 | case FCNcall: |
723 | /* |
724 | * MAL function calls are relatively expensive, |
725 | * because they have to assemble a new stack frame and |
726 | * do housekeeping, such as garbagecollection of all |
727 | * non-returned values. |
728 | */ |
729 | { MalStkPtr nstk; |
730 | InstrPtr q; |
731 | int ii, arg; |
732 | |
733 | stk->pcup = stkpc; |
734 | nstk = prepareMALstack(pci->blk, pci->blk->vsize); |
735 | if (nstk == 0){ |
736 | ret= createException(MAL,"mal.interpreter" ,MAL_STACK_FAIL); |
737 | break; |
738 | } |
739 | |
740 | /*safeguardStack*/ |
741 | nstk->stkdepth = nstk->stksize + stk->stkdepth; |
742 | nstk->calldepth = stk->calldepth + 1; |
743 | nstk->up = stk; |
744 | if (nstk->calldepth > 256) { |
745 | ret= createException(MAL, "mal.interpreter" , MAL_CALLDEPTH_FAIL); |
746 | GDKfree(nstk); |
747 | break; |
748 | } |
749 | if ((unsigned)nstk->stkdepth > THREAD_STACK_SIZE / sizeof(mb->var[0]) / 4 && THRhighwater()){ |
750 | /* we are running low on stack space */ |
751 | ret= createException(MAL, "mal.interpreter" , MAL_STACK_FAIL); |
752 | GDKfree(nstk); |
753 | break; |
754 | } |
755 | |
756 | /* copy arguments onto destination stack */ |
757 | q= getInstrPtr(pci->blk,0); |
758 | arg = q->retc; |
759 | for (ii = pci->retc; ii < pci->argc; ii++,arg++) { |
760 | lhs = &nstk->stk[q->argv[arg]]; |
761 | rhs = &stk->stk[pci->argv[ii]]; |
762 | if(VALcopy(lhs, rhs) == NULL) { |
763 | GDKfree(nstk); |
764 | ret = createException(MAL, "mal.interpreter" , MAL_MALLOC_FAIL); |
765 | break; |
766 | } else if (lhs->vtype == TYPE_bat) |
767 | BBPretain(lhs->val.bval); |
768 | } |
769 | if(ret == MAL_SUCCEED) { |
770 | ret = runMALsequence(cntxt, pci->blk, 1, pci->blk->stop, nstk, stk, pci); |
771 | for (ii = 0; ii < nstk->stktop; ii++) |
772 | if (ATOMextern(nstk->stk[ii].vtype)) |
773 | GDKfree(nstk->stk[ii].val.pval); |
774 | GDKfree(nstk); |
775 | } |
776 | } |
777 | break; |
778 | case NOOPsymbol: |
779 | case REMsymbol: |
780 | break; |
781 | case ENDsymbol: |
782 | if (getInstrPtr(mb, 0)->token == FACTORYsymbol) |
783 | ret = shutdownFactory(cntxt, mb); |
784 | runtimeProfileExit(cntxt, mb, stk, pci, &runtimeProfile); |
785 | runtimeProfileExit(cntxt, mb, stk, getInstrPtr(mb,0), &runtimeProfileFunction); |
786 | if (pcicaller && garbageControl(getInstrPtr(mb, 0))) |
787 | garbageCollector(cntxt, mb, stk, TRUE); |
788 | if (cntxt->querytimeout && mb->starttime && GDKusec()- mb->starttime > cntxt->querytimeout){ |
789 | freeException(ret); /* overrule exception */ |
790 | ret= createException(MAL, "mal.interpreter" , SQLSTATE(HYT00) RUNTIME_QRY_TIMEOUT); |
791 | break; |
792 | } |
793 | stkpc = mb->stop; // force end of loop |
794 | continue; |
795 | default: { |
796 | str w; |
797 | if (pci->token < 0) { |
798 | /* temporary NOOP instruction */ |
799 | break; |
800 | } |
801 | w= instruction2str(mb, 0, pci, FALSE); |
802 | if(w) { |
803 | ret = createException(MAL,"interpreter" , "unkown operation:%s" , w); |
804 | GDKfree(w); |
805 | } else { |
806 | ret = createException(MAL,"interpreter" , "failed instruction2str" ); |
807 | } |
808 | // runtimeProfileBegin already sets the time in the instruction |
809 | if (cntxt->querytimeout && mb->starttime && GDKusec()- mb->starttime > cntxt->querytimeout){ |
810 | freeException(ret); /* in case it's set */ |
811 | ret = createException(MAL, "mal.interpreter" , SQLSTATE(HYT00) RUNTIME_QRY_TIMEOUT); |
812 | break; |
813 | } |
814 | |
815 | stkpc= mb->stop; |
816 | continue; |
817 | } } |
818 | |
819 | /* monitoring information should reflect the input arguments, |
820 | which may be removed by garbage collection */ |
821 | /* BEWARE, the SQL engine or MAL function could zap the block, leaving garbage behind in pci */ |
822 | /* this hack means we loose a closing event */ |
823 | if( mb->stop <= 1) |
824 | continue; |
825 | runtimeProfileExit(cntxt, mb, stk, pci, &runtimeProfile); |
826 | /* check for strong debugging after each MAL statement */ |
827 | /* when we find a timeout situation, then the result is already known |
828 | * and assigned, the backup version is not removed*/ |
829 | if ( pci->token != FACcall && ret== MAL_SUCCEED) { |
830 | for (i = 0; i < pci->retc; i++) { |
831 | lhs = &backup[i]; |
832 | if (BATatoms[lhs->vtype].atomUnfix) |
833 | (*BATatoms[lhs->vtype].atomUnfix)(VALget(lhs)); |
834 | if (ATOMextern(lhs->vtype) && |
835 | lhs->val.pval && |
836 | lhs->val.pval != ATOMnilptr(lhs->vtype) && |
837 | lhs->val.pval != stk->stk[getArg(pci, i)].val.pval) |
838 | GDKfree(lhs->val.pval); |
839 | } |
840 | if (GDKdebug & (CHECKMASK|PROPMASK) && exceptionVar < 0) { |
841 | BAT *b; |
842 | |
843 | for (i = 0; i < pci->retc; i++) { |
844 | if (garbage[i] == -1 && stk->stk[getArg(pci, i)].vtype == TYPE_bat && |
845 | !is_bat_nil(stk->stk[getArg(pci, i)].val.bval)) { |
846 | assert(stk->stk[getArg(pci, i)].val.bval > 0); |
847 | b = BBPquickdesc(stk->stk[getArg(pci, i)].val.bval, false); |
848 | if (b == NULL) { |
849 | if (ret == MAL_SUCCEED) |
850 | ret = createException(MAL, "mal.propertyCheck" , SQLSTATE(HY002) RUNTIME_OBJECT_MISSING); |
851 | continue; |
852 | } |
853 | b = BATdescriptor(stk->stk[getArg(pci, i)].val.bval); |
854 | if (b) { |
855 | BATassertProps(b); |
856 | BBPunfix(b->batCacheid); |
857 | } |
858 | } |
859 | } |
860 | } |
861 | |
862 | /* general garbage collection */ |
863 | if (ret == MAL_SUCCEED && garbageControl(pci)) { |
864 | for (i = 0; i < pci->argc; i++) { |
865 | int a = getArg(pci, i); |
866 | |
867 | if (isaBatType(getArgType(mb, pci, i))) { |
868 | bat bid = stk->stk[a].val.bval; |
869 | |
870 | if (garbage[i] >= 0) { |
871 | PARDEBUG fprintf(stderr, "#GC pc=%d bid=%d %s done\n" , stkpc, bid, getVarName(mb, garbage[i])); |
872 | bid = stk->stk[garbage[i]].val.bval; |
873 | stk->stk[garbage[i]].val.bval = bat_nil; |
874 | BBPrelease(bid); |
875 | } |
876 | } |
877 | } |
878 | } |
879 | } |
880 | |
881 | /* Exception handling */ |
882 | if (localGDKerrbuf && localGDKerrbuf[0]) { |
883 | if( ret == MAL_SUCCEED) |
884 | ret = createException(MAL,"mal.interpreter" ,GDK_EXCEPTION); |
885 | // TODO take properly care of the GDK exception |
886 | localGDKerrbuf[0]=0; |
887 | } |
888 | |
889 | if (ret != MAL_SUCCEED) { |
890 | str msg = 0; |
891 | |
892 | #ifndef NDEBUG |
893 | if (stk->cmd) { |
894 | mnstr_printf(cntxt->fdout, "!ERROR: %s\n" , ret); |
895 | stk->cmd = '\n'; /* in debugging go to step mode */ |
896 | mdbStep(cntxt, mb, stk, stkpc); |
897 | if (stk->cmd == 'x' || stk->cmd == 'q' ) { |
898 | stkpc = mb->stop; |
899 | continue; |
900 | } |
901 | if (stk->cmd == 'r') { |
902 | stk->cmd = 'n'; |
903 | stkpc = startpc; |
904 | exceptionVar = -1; |
905 | continue; |
906 | } |
907 | } |
908 | #endif |
909 | /* Detect any exception received from the implementation. */ |
910 | /* The first identifier is an optional exception name */ |
911 | if (strstr(ret, "!skip-to-end" )) { |
912 | freeException(ret); |
913 | ret = MAL_SUCCEED; |
914 | stkpc = mb->stop; |
915 | continue; |
916 | } |
917 | /* |
918 | * Exceptions are caught based on their name, which is part of the |
919 | * exception message. The ANYexception variable catches all. |
920 | */ |
921 | exceptionVar = -1; |
922 | msg = strchr(ret, ':'); |
923 | if (msg) { |
924 | exceptionVar = findVariableLength(mb, ret, (int)(msg - ret)); |
925 | } |
926 | if (exceptionVar == -1) |
927 | exceptionVar = findVariable(mb, "ANYexception" ); |
928 | |
929 | /* unknown exceptions lead to propagation */ |
930 | if (exceptionVar == -1) { |
931 | if (cntxt->querytimeout && mb->starttime && GDKusec()- mb->starttime > cntxt->querytimeout) |
932 | ret= createException(MAL, "mal.interpreter" , SQLSTATE(HYT00) RUNTIME_QRY_TIMEOUT); |
933 | stkpc = mb->stop; |
934 | continue; |
935 | } |
936 | /* assure correct variable type */ |
937 | if (getVarType(mb, exceptionVar) == TYPE_str) { |
938 | /* watch out for concurrent access */ |
939 | MT_lock_set(&mal_contextLock); |
940 | v = &stk->stk[exceptionVar]; |
941 | if (v->val.sval) |
942 | freeException(v->val.sval); /* old exception*/ |
943 | v->vtype = TYPE_str; |
944 | v->val.sval = ret; |
945 | v->len = strlen(v->val.sval); |
946 | ret = MAL_SUCCEED; |
947 | MT_lock_unset(&mal_contextLock); |
948 | } else { |
949 | mnstr_printf(cntxt->fdout, "%s" , ret); |
950 | freeException(ret); |
951 | ret = MAL_SUCCEED; |
952 | } |
953 | /* position yourself at the catch instruction for further decisions */ |
954 | /* skipToCatch(exceptionVar,@2,@3) */ |
955 | #ifndef NDEBUG |
956 | if (stk->cmd == 'C') { |
957 | stk->cmd = 'n'; |
958 | mdbStep(cntxt, mb, stk, stkpc); |
959 | if (stk->cmd == 'x' ) { |
960 | stkpc = mb->stop; |
961 | continue; |
962 | } |
963 | } |
964 | #endif |
965 | /* skip to catch block or end */ |
966 | for (; stkpc < mb->stop; stkpc++) { |
967 | InstrPtr l = getInstrPtr(mb, stkpc); |
968 | if (l->barrier == CATCHsymbol) { |
969 | int j; |
970 | for (j = 0; j < l->retc; j++) |
971 | if (getArg(l, j) == exceptionVar) |
972 | break; |
973 | else if (strcmp(getArgName(mb, l, j), "ANYexception" ) == 0) |
974 | break; |
975 | if (j < l->retc) |
976 | break; |
977 | } |
978 | } |
979 | if (stkpc == mb->stop) { |
980 | if (cntxt->querytimeout && mb->starttime && GDKusec()- mb->starttime > cntxt->querytimeout){ |
981 | ret= createException(MAL, "mal.interpreter" , SQLSTATE(HYT00) RUNTIME_QRY_TIMEOUT); |
982 | stkpc = mb->stop; |
983 | } |
984 | continue; |
985 | } |
986 | pci = getInstrPtr(mb, stkpc); |
987 | } |
988 | |
989 | /* |
990 | * After the expression has been evaluated we should check for |
991 | * a possible change in the control flow. |
992 | */ |
993 | switch (pci->barrier) { |
994 | case BARRIERsymbol: |
995 | v = &stk->stk[getDestVar(pci)]; |
996 | /* skip to end of barrier, depends on the type */ |
997 | switch (v->vtype) { |
998 | case TYPE_bit: |
999 | if (v->val.btval == FALSE || is_bit_nil(v->val.btval)) |
1000 | stkpc = pci->jump; |
1001 | break; |
1002 | case TYPE_bte: |
1003 | if (is_bte_nil(v->val.btval)) |
1004 | stkpc = pci->jump; |
1005 | break; |
1006 | case TYPE_oid: |
1007 | if (is_oid_nil(v->val.oval)) |
1008 | stkpc = pci->jump; |
1009 | break; |
1010 | case TYPE_sht: |
1011 | if (is_sht_nil(v->val.shval)) |
1012 | stkpc = pci->jump; |
1013 | break; |
1014 | case TYPE_int: |
1015 | if (is_int_nil(v->val.ival)) |
1016 | stkpc = pci->jump; |
1017 | break; |
1018 | case TYPE_lng: |
1019 | if (is_lng_nil(v->val.lval)) |
1020 | stkpc = pci->jump; |
1021 | break; |
1022 | #ifdef HAVE_HGE |
1023 | case TYPE_hge: |
1024 | if (is_hge_nil(v->val.hval)) |
1025 | stkpc = pci->jump; |
1026 | break; |
1027 | #endif |
1028 | case TYPE_flt: |
1029 | if (is_flt_nil(v->val.fval)) |
1030 | stkpc = pci->jump; |
1031 | break; |
1032 | case TYPE_dbl: |
1033 | if (is_dbl_nil(v->val.dval)) |
1034 | stkpc = pci->jump; |
1035 | break; |
1036 | case TYPE_str: |
1037 | if (GDK_STRNIL(v->val.sval)) |
1038 | stkpc = pci->jump; |
1039 | break; |
1040 | default: |
1041 | ret = createException(MAL,"mal.interpreter" , "%s: Unknown barrier type" , getVarName(mb, getDestVar(pci))); |
1042 | } |
1043 | stkpc++; |
1044 | break; |
1045 | case LEAVEsymbol: |
1046 | case REDOsymbol: |
1047 | v = &stk->stk[getDestVar(pci)]; |
1048 | /* skip to end of barrier, depending on the type */ |
1049 | switch (v->vtype) { |
1050 | case TYPE_bit: |
1051 | if (v->val.btval == TRUE ) |
1052 | stkpc = pci->jump; |
1053 | else |
1054 | stkpc++; |
1055 | break; |
1056 | case TYPE_str: |
1057 | if (!GDK_STRNIL(v->val.sval)) |
1058 | stkpc = pci->jump; |
1059 | else |
1060 | stkpc++; |
1061 | break; |
1062 | case TYPE_oid: |
1063 | if (!is_oid_nil(v->val.oval)) |
1064 | stkpc = pci->jump; |
1065 | else |
1066 | stkpc++; |
1067 | break; |
1068 | case TYPE_sht: |
1069 | if (!is_sht_nil(v->val.shval)) |
1070 | stkpc = pci->jump; |
1071 | else |
1072 | stkpc++; |
1073 | break; |
1074 | case TYPE_int: |
1075 | if (!is_int_nil(v->val.ival)) |
1076 | stkpc = pci->jump; |
1077 | else |
1078 | stkpc++; |
1079 | break; |
1080 | case TYPE_bte: |
1081 | if (!is_bte_nil(v->val.btval)) |
1082 | stkpc = pci->jump; |
1083 | else |
1084 | stkpc++; |
1085 | break; |
1086 | case TYPE_lng: |
1087 | if (!is_lng_nil(v->val.lval)) |
1088 | stkpc = pci->jump; |
1089 | else |
1090 | stkpc++; |
1091 | break; |
1092 | #ifdef HAVE_HGE |
1093 | case TYPE_hge: |
1094 | if (!is_hge_nil(v->val.hval)) |
1095 | stkpc = pci->jump; |
1096 | else |
1097 | stkpc++; |
1098 | break; |
1099 | #endif |
1100 | case TYPE_flt: |
1101 | if (!is_flt_nil(v->val.fval)) |
1102 | stkpc = pci->jump; |
1103 | else |
1104 | stkpc++; |
1105 | break; |
1106 | case TYPE_dbl: |
1107 | if (!is_dbl_nil(v->val.dval)) |
1108 | stkpc = pci->jump; |
1109 | else |
1110 | stkpc++; |
1111 | break; |
1112 | default: |
1113 | break; |
1114 | } |
1115 | break; |
1116 | case CATCHsymbol: |
1117 | /* catch blocks are skipped unless |
1118 | searched for explicitly*/ |
1119 | if (exceptionVar < 0) { |
1120 | stkpc = pci->jump; |
1121 | break; |
1122 | } |
1123 | exceptionVar = -1; |
1124 | stkpc++; |
1125 | break; |
1126 | case EXITsymbol: |
1127 | if (getDestVar(pci) == exceptionVar) |
1128 | exceptionVar = -1; |
1129 | stkpc++; |
1130 | break; |
1131 | case RAISEsymbol: |
1132 | exceptionVar = getDestVar(pci); |
1133 | //freeException(ret); |
1134 | ret = MAL_SUCCEED; |
1135 | if (getVarType(mb, getDestVar(pci)) == TYPE_str) { |
1136 | char nme[256]; |
1137 | snprintf(nme,256,"%s.%s[%d]" , getModuleId(getInstrPtr(mb,0)), getFunctionId(getInstrPtr(mb,0)), stkpc); |
1138 | ret = createException(MAL, nme, "%s" , stk->stk[getDestVar(pci)].val.sval); |
1139 | } |
1140 | /* skipToCatch(exceptionVar, @2, stk) */ |
1141 | #ifndef NDEBUG |
1142 | if (stk->cmd == 'C') { |
1143 | stk->cmd = 'n'; |
1144 | mdbStep(cntxt, mb, stk, stkpc); |
1145 | if (stk->cmd == 'x' ) { |
1146 | stkpc = mb->stop; |
1147 | continue; |
1148 | } |
1149 | } |
1150 | #endif |
1151 | /* skip to catch block or end */ |
1152 | for (; stkpc < mb->stop; stkpc++) { |
1153 | InstrPtr l = getInstrPtr(mb, stkpc); |
1154 | if (l->barrier == CATCHsymbol) { |
1155 | int j; |
1156 | for (j = 0; j < l->retc; j++) |
1157 | if (getArg(l, j) == exceptionVar) |
1158 | break; |
1159 | else if (strcmp(getArgName(mb, l, j), "ANYexception" ) == 0) |
1160 | break; |
1161 | if (j < l->retc) |
1162 | break; |
1163 | } |
1164 | } |
1165 | if (stkpc == mb->stop) { |
1166 | runtimeProfileExit(cntxt, mb, stk, pci, &runtimeProfile); |
1167 | runtimeProfileExit(cntxt, mb, stk, getInstrPtr(mb,0), &runtimeProfileFunction); |
1168 | break; |
1169 | } |
1170 | if (stkpc == mb->stop) |
1171 | ret = mb->errors = createMalException(mb, stkpc, TYPE, |
1172 | "Exception raised\n" ); |
1173 | break; |
1174 | case YIELDsymbol: /* to be defined */ |
1175 | if( startedProfileQueue) |
1176 | runtimeProfileFinish(cntxt, mb, stk); |
1177 | if ( backup != backups) GDKfree(backup); |
1178 | if ( garbage != garbages) GDKfree(garbage); |
1179 | return yieldFactory(mb, pci, stkpc); |
1180 | case RETURNsymbol: |
1181 | /* Return from factory involves cleanup */ |
1182 | |
1183 | if (getInstrPtr(mb, 0)->token == FACTORYsymbol) { |
1184 | yieldResult(mb, pci, stkpc); |
1185 | shutdownFactory(cntxt, mb); |
1186 | } else { |
1187 | /* a fake multi-assignment */ |
1188 | if (env != NULL && pcicaller != NULL) { |
1189 | InstrPtr pp = pci; |
1190 | pci = pcicaller; |
1191 | for (i = 0; i < pci->retc; i++) { |
1192 | rhs = &stk->stk[pp->argv[i]]; |
1193 | lhs = &env->stk[pci->argv[i]]; |
1194 | if(VALcopy(lhs, rhs) == NULL) { |
1195 | ret = createException(MAL, "mal.interpreter" , MAL_MALLOC_FAIL); |
1196 | break; |
1197 | } else if (lhs->vtype == TYPE_bat) |
1198 | BBPretain(lhs->val.bval); |
1199 | } |
1200 | if (garbageControl(getInstrPtr(mb, 0))) |
1201 | garbageCollector(cntxt, mb, stk, TRUE); |
1202 | /* reset the clock */ |
1203 | runtimeProfileExit(cntxt, mb, stk, pp, &runtimeProfile); |
1204 | runtimeProfileExit(cntxt, mb, stk, getInstrPtr(mb,0), &runtimeProfileFunction); |
1205 | } |
1206 | } |
1207 | stkpc = mb->stop; |
1208 | continue; |
1209 | default: |
1210 | stkpc++; |
1211 | } |
1212 | if (cntxt->querytimeout && mb->starttime && GDKusec()- mb->starttime > cntxt->querytimeout){ |
1213 | if (ret == MAL_SUCCEED) |
1214 | ret= createException(MAL, "mal.interpreter" , SQLSTATE(HYT00) RUNTIME_QRY_TIMEOUT); |
1215 | stkpc= mb->stop; |
1216 | } |
1217 | } |
1218 | |
1219 | /* if we could not find the exception variable, cascade a new one */ |
1220 | if (exceptionVar >= 0) { |
1221 | char nme[256]; |
1222 | snprintf(nme,256,"%s.%s[%d]" , getModuleId(getInstrPtr(mb,0)), getFunctionId(getInstrPtr(mb,0)), stkpc); |
1223 | if (ret != MAL_SUCCEED) { |
1224 | str new, n; |
1225 | n = createException(MAL,nme,"exception not caught" ); |
1226 | if (n) { |
1227 | new = GDKzalloc(strlen(ret) + strlen(n) +16); |
1228 | if (new){ |
1229 | strcpy(new, ret); |
1230 | if( new[strlen(new)-1] != '\n') |
1231 | strcat(new,"\n" ); |
1232 | strcat(new,"!" ); |
1233 | strcat(new,n); |
1234 | freeException(n); |
1235 | freeException(ret); |
1236 | ret = new; |
1237 | } |
1238 | } |
1239 | } else { |
1240 | ret = createException(MAL, nme, "Exception not caught" ); |
1241 | } |
1242 | } |
1243 | if( startedProfileQueue) |
1244 | runtimeProfileFinish(cntxt, mb, stk); |
1245 | if ( backup != backups) GDKfree(backup); |
1246 | if ( garbage != garbages) GDKfree(garbage); |
1247 | return ret; |
1248 | } |
1249 | |
1250 | |
1251 | /* |
1252 | * MAL API |
1253 | * The linkage between MAL interpreter and compiled C-routines |
1254 | * is kept as simple as possible. |
1255 | * Basically we distinguish four kinds of calling conventions: |
1256 | * CMDcall, FCNcall, FACcall, and PATcall. |
1257 | * The FCNcall indicates calling a MAL procedure, which leads |
1258 | * to a recursive call to the interpreter. |
1259 | * |
1260 | * CMDcall initiates calling a linked function, passing pointers |
1261 | * to the parameters and result variable, i.e. f(ptr a0,..., ptr aN) |
1262 | * The function returns a MAL-SUCCEED upon success and a pointer |
1263 | * to an exception string upon failure. |
1264 | * Failure leads to raise-ing an exception in the interpreter loop, |
1265 | * by either looking up the relevant exception message in the module |
1266 | * administration or construction of a standard string. |
1267 | * |
1268 | * The PATcall initiates a call which contains the MAL context, |
1269 | * i.e. f(MalBlkPtr mb, MalStkPtr stk, InstrPtr pci) |
1270 | * The mb provides access to the code definitions. It is primarilly |
1271 | * used by routines intended to manipulate the code base itself, such |
1272 | * as the optimizers. The Mal stack frame pointer provides access |
1273 | * to the values maintained. The arguments passed are offsets |
1274 | * into the stack frame rather than pointers to the actual value. |
1275 | * |
1276 | * BAT parameters require some care. Ideally, a BAT should not be kept |
1277 | * around long. This would mean that each time we access a BAT it has to be |
1278 | * pinned in memory and upon leaving the function, it is unpinned. |
1279 | * This degrades performance significantly. |
1280 | * After the parameters are fixed, we can safely free the destination |
1281 | * variable and re-initialize it to nil. |
1282 | * |
1283 | */ |
1284 | |
1285 | /* |
1286 | * The type dispatching table in getArgReference can be removed if we |
1287 | * determine at compile time the address offset within a ValRecord. |
1288 | * We leave this optimization for the future, it leads to about 10% |
1289 | * improvement (100ms for 1M calls). |
1290 | * |
1291 | * Flow of control statements |
1292 | * Each assignment (function call) may be part of the initialization |
1293 | * of a barrier- block. In that case we have to test the |
1294 | * outcome of the operation and possibly skip the block altogether. |
1295 | * The latter is implemented as a linear scan for the corresponding |
1296 | * labeled statemtent. This might be optimized later. |
1297 | * |
1298 | * You can skip to a catch block by searching for the corresponding 'lab' |
1299 | * The return value should be set to pass the error automatically upon |
1300 | * reaching end of function block. |
1301 | */ |
1302 | |
1303 | /* |
1304 | * Each time we enter a barrier block, we could keep its position in the |
1305 | * interpreter stack frame. It forms the starting point to issue a redo. |
1306 | * Unfortunately, this does not easily work in the presence of optimizers, which |
1307 | * may change the order/block structure. Therefore, we simple have to search |
1308 | * the beginning or ensure that during chkProgram the barrier/redo/leave/catch |
1309 | * jumps are re-established. |
1310 | * |
1311 | * Exception handling |
1312 | * Calling a built-in or user-defined routine may lead to an error or a |
1313 | * cached status message to be dealt with in MAL. |
1314 | * To improve error handling in MAL, an exception handling |
1315 | * scheme based on @sc{catch}-@sc{exit} blocks. The @sc{catch} |
1316 | * statement identifies a (string-valued) variable, which carries the |
1317 | * exception message from |
1318 | * the originally failed routine or @sc{raise} exception assignment. |
1319 | * During normal processing @sc{catch}-@sc{exit} blocks are simply skipped. |
1320 | * Upon receiving an exception status from a function call, we set the |
1321 | * exception variable and skip to the first associated @sc{catch}-@sc{exit} |
1322 | * block. |
1323 | * MAL interpretation then continues until it reaches the end of the block. |
1324 | * If no exception variable was defined, we should abandon the function |
1325 | * alltogether searching for a catch block at a higher layer. |
1326 | * |
1327 | * For the time being we have ignored cascaded/stacked exceptions. |
1328 | * The policy is to pass the first recognized exception to a context |
1329 | * in which it can be handled. |
1330 | * |
1331 | * Exceptions raised within a linked-in function requires some care. |
1332 | * First, the called procedure does not know anything about the MAL |
1333 | * interpreter context. Thus, we need to return all relevant information |
1334 | * upon leaving the linked library routine. |
1335 | * |
1336 | * Second, exceptional cases can be handled deeply in the recursion, where they |
1337 | * may also be handled, i.e. by issueing an GDKerror message. The upper layers |
1338 | * merely receive a negative integer value to indicate occurrence of an |
1339 | * error somewhere in the calling sequence. |
1340 | * We then have to also look into GDKerrbuf to see if there was |
1341 | * an error raised deeply inside the system. |
1342 | * |
1343 | * The policy is to require all C-functions to return a string-pointer. |
1344 | * Upon a successfull call, it is a NULL string. Otherwise it contains an |
1345 | * encoding of the exceptional state encountered. This message |
1346 | * starts with the exception identifer, followed by contextual details. |
1347 | */ |
1348 | |
1349 | /* |
1350 | * Garbage collection |
1351 | * Garbage collection is relatively straightforward, because most values are |
1352 | * retained on the stackframe of an interpreter call. However, two storage |
1353 | * types and possibly user-defined type garbage collector definitions |
1354 | * require attention: BATs and strings. |
1355 | * |
1356 | * A key issue is to deal with temporary BATs in an efficient way. |
1357 | * References to bats in the buffer pool may cause dangling references |
1358 | * at the language level. This appears as soons as your share |
1359 | * a reference and delete the BAT from one angle. If not carefull, the |
1360 | * dangling pointer may subsequently be associated with another BAT |
1361 | * |
1362 | * All string values are private to the VALrecord, which means they |
1363 | * have to be freed explicitly before a MAL function returns. |
1364 | * The first step is to always safe the destination variable |
1365 | * before a function call is made. |
1366 | */ |
1367 | void garbageElement(Client cntxt, ValPtr v) |
1368 | { |
1369 | (void) cntxt; |
1370 | if (v->vtype == TYPE_str) { |
1371 | if (v->val.sval) { |
1372 | GDKfree(v->val.sval); |
1373 | v->val.sval = NULL; |
1374 | } |
1375 | v->len = 0; |
1376 | } else if (v->vtype == TYPE_bat) { |
1377 | /* |
1378 | * All operations are responsible to properly set the |
1379 | * reference count of the BATs being produced or destroyed. |
1380 | * The libraries should not leave the |
1381 | * physical reference count being set. This is only |
1382 | * allowed during the execution of a GDK operation. |
1383 | * All references should be logical. |
1384 | */ |
1385 | bat bid = v->val.bval; |
1386 | /* printf("garbage collecting: %d lrefs=%d refs=%d\n", |
1387 | bid, BBP_lrefs(bid),BBP_refs(bid));*/ |
1388 | v->val.bval = bat_nil; |
1389 | if (is_bat_nil(bid)) |
1390 | return; |
1391 | if (!BBP_lrefs(bid)) |
1392 | return; |
1393 | BBPrelease(bid); |
1394 | } else if (0 < v->vtype && v->vtype < MAXATOMS && ATOMextern(v->vtype)) { |
1395 | if (v->val.pval) |
1396 | GDKfree(v->val.pval); |
1397 | v->val.pval = 0; |
1398 | v->len = 0; |
1399 | } |
1400 | } |
1401 | |
1402 | /* |
1403 | * Before we return from the interpreter, we should free all |
1404 | * dynamically allocated objects and adjust the BAT reference counts. |
1405 | * Early experience shows that for small stack frames the overhead |
1406 | * is about 200 ms for a 1M function call loop (tst400e). This means that |
1407 | * for the time being we do not introduce more complex garbage |
1408 | * administration code. |
1409 | * |
1410 | * Also note that for top-level stack frames (no environment available), |
1411 | * we should retain the value stack because it acts as a global variables. |
1412 | * This situation is indicated by the 'global' in the stack frame. |
1413 | * Upon termination of the session, the stack should be cleared. |
1414 | * Beware that variables may be know polymorphic, their actual |
1415 | * type should be saved for variables that recide on a global |
1416 | * stack frame. |
1417 | */ |
1418 | void garbageCollector(Client cntxt, MalBlkPtr mb, MalStkPtr stk, int flag) |
1419 | { |
1420 | int k; |
1421 | ValPtr v; |
1422 | |
1423 | #ifdef STACKTRACE |
1424 | if (cntxt) { |
1425 | mnstr_printf(cntxt->fdout, "#--->stack before garbage collector\n" ); |
1426 | printStack(cntxt->fdout, mb, stk, 0); |
1427 | } |
1428 | #endif |
1429 | assert(mb->vtop <= mb->vsize); |
1430 | (void) flag; |
1431 | for (k = 0; k < mb->vtop; k++) { |
1432 | // if (isVarCleanup(mb, k) ){ |
1433 | garbageElement(cntxt, v = &stk->stk[k]); |
1434 | v->vtype = TYPE_int; |
1435 | v->val.ival = int_nil; |
1436 | // } |
1437 | } |
1438 | #ifdef STACKTRACE |
1439 | if (cntxt) { |
1440 | mnstr_printf(cntxt->fdout, "#-->stack after garbage collector\n" ); |
1441 | printStack(cntxt->fdout, mb, stk, 0); |
1442 | } |
1443 | #else |
1444 | (void)cntxt; |
1445 | #endif |
1446 | } |
1447 | |