Line data Source code
1 : /*
2 : * SPDX-License-Identifier: MPL-2.0
3 : *
4 : * This Source Code Form is subject to the terms of the Mozilla Public
5 : * License, v. 2.0. If a copy of the MPL was not distributed with this
6 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 : *
8 : * Copyright 2024 MonetDB Foundation;
9 : * Copyright August 2008 - 2023 MonetDB B.V.;
10 : * Copyright 1997 - July 2008 CWI.
11 : */
12 :
13 : /*
14 : * The first attempt of the multiplex optimizer is to locate
15 : * a properly typed multi-plexed implementation.
16 : * The policy is to search for bat<mod>.<fcn> before going
17 : * into the iterator code generation.
18 : */
19 : #include "monetdb_config.h"
20 : #include "opt_remap.h"
21 : #include "opt_inline.h"
22 : #include "opt_multiplex.h"
23 :
24 : static int
25 140870 : OPTremapDirect(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci, int idx,
26 : Module scope)
27 : {
28 140870 : str mod, fcn;
29 140870 : char buf[1024];
30 140870 : int i, retc = pci->retc;
31 140870 : InstrPtr p;
32 140870 : const char *bufName, *fcnName;
33 :
34 140870 : (void) cntxt;
35 140870 : (void) stk;
36 140870 : int plus_one = getArgType(mb, pci, pci->retc) == TYPE_lng ? 1 : 0;
37 140870 : mod = VALget(&getVar(mb, getArg(pci, retc + 0 + plus_one))->value);
38 140870 : fcn = VALget(&getVar(mb, getArg(pci, retc + 1 + plus_one))->value);
39 :
40 140870 : if (strncmp(mod, "bat", 3) == 0)
41 0 : mod += 3;
42 :
43 :
44 140870 : snprintf(buf, 1024, "bat%s", mod);
45 140870 : bufName = putName(buf);
46 140870 : fcnName = putName(fcn);
47 140870 : if (bufName == NULL || fcnName == NULL)
48 : return 0;
49 :
50 140870 : p = newInstructionArgs(mb, bufName, fcnName, pci->argc + 2);
51 140870 : if (p == NULL)
52 : return 0;
53 :
54 281782 : for (i = 0; i < pci->retc; i++)
55 140912 : if (i < 1)
56 140870 : getArg(p, i) = getArg(pci, i);
57 : else
58 42 : p = pushReturn(mb, p, getArg(pci, i));
59 140870 : p->retc = p->argc = pci->retc;
60 :
61 :
62 :
63 140870 : if (plus_one) {
64 97 : p = pushArgument(mb, p, getArg(pci, pci->retc)); // cardinality argument
65 : }
66 :
67 454509 : for (i = pci->retc + 2 + plus_one; i < pci->argc; i++)
68 313639 : p = pushArgument(mb, p, getArg(pci, i));
69 140870 : if (p->retc == 1 &&
70 140862 : ((bufName == batcalcRef
71 121356 : && (fcnName == mulRef
72 108377 : || fcnName == divRef
73 107105 : || fcnName == plusRef
74 74337 : || fcnName == minusRef
75 62647 : || fcnName == modRef))
76 82052 : || bufName == batmtimeRef
77 81662 : || bufName == batstrRef)) {
78 61731 : if (p->argc == 3 &&
79 : /* these two filter out unary batcalc.- with a candidate list */
80 43331 : getBatType(getArgType(mb, p, 1)) != TYPE_oid
81 43331 : && (getBatType(getArgType(mb, p, 2)) != TYPE_oid
82 43138 : && !(isVarConstant(mb, getArg(p, 2))
83 11 : && isaBatType(getArgType(mb, p, 2) )))) {
84 : /* add candidate lists */
85 43129 : if (isaBatType(getArgType(mb, p, 1)))
86 42927 : p = pushNilBat(mb, p);
87 43129 : if (isaBatType(getArgType(mb, p, 2)))
88 24307 : p = pushNilBat(mb, p);
89 : }
90 : }
91 :
92 : /* now see if we can resolve the instruction */
93 140870 : typeChecker(scope, mb, p, idx, TRUE);
94 140870 : if (!p->typeresolved) {
95 721 : freeInstruction(p);
96 721 : return 0;
97 : }
98 140149 : pushInstruction(mb, p);
99 140149 : return 1;
100 : }
101 :
102 : /*
103 : * Multiplex inline functions should be done with care.
104 : * The approach taken is to make a temporary copy of the function to be inlined.
105 : * To change all the statements to reflect the new situation
106 : * and, if no error occurs, replaces the target instruction
107 : * with this new block.
108 : *
109 : * By the time we get here, we know that function is
110 : * side-effect free.
111 : *
112 : * The multiplex upgrade is targeted at all function
113 : * arguments whose actual is a BAT and its formal
114 : * is a scalar.
115 : * This seems sufficient for the SQL generated PSM code,
116 : * but does in general not hold.
117 : * For example,
118 : *
119 : * function foo(b:int,c:bat[:oid,:int])
120 : * ... d:= batcalc.+(b,c)
121 : * and
122 : * multiplex("user","foo",ba:bat[:oid,:int],ca:bat[:oid,:int])
123 : * upgrades the first argument. The naive upgrade of
124 : * the statement that would fail. The code below catches
125 : * most of them by simple prepending "bat" to the MAL function
126 : * name and leave it to the type resolver to generate the
127 : * error.
128 : *
129 : * The process terminates as soon as we
130 : * find an instruction that does not have a multiplex
131 : * counterpart.
132 : */
133 : static int
134 260 : OPTmultiplexInline(Client cntxt, MalBlkPtr mb, InstrPtr p, int pc)
135 : {
136 260 : MalBlkPtr mq;
137 260 : InstrPtr q = NULL, sig;
138 260 : char buf[1024];
139 260 : int i, j, k, m;
140 260 : int refbat = 0, retc = p->retc;
141 260 : bit *upgrade;
142 260 : str msg;
143 :
144 :
145 260 : str mod = VALget(&getVar(mb, getArg(p, retc + 0))->value);
146 260 : str fcn = VALget(&getVar(mb, getArg(p, retc + 1))->value);
147 : //Symbol s = findSymbol(cntxt->usermodule, mod,fcn);
148 260 : Symbol s = findSymbolInModule(getModule(putName(mod)), putName(fcn));
149 :
150 260 : if (s == NULL || !isSideEffectFree(s->def)
151 212 : || getInstrPtr(s->def, 0)->retc != p->retc) {
152 48 : return 0;
153 : }
154 : /*
155 : * Determine the variables to be upgraded and adjust their type
156 : */
157 212 : if ((mq = copyMalBlk(s->def)) == NULL) {
158 : return 0;
159 : }
160 212 : sig = getInstrPtr(mq, 0);
161 :
162 212 : upgrade = (bit *) GDKzalloc(sizeof(bit) * mq->vtop);
163 212 : if (upgrade == NULL) {
164 0 : freeMalBlk(mq);
165 0 : return 0;
166 : }
167 :
168 212 : setVarType(mq, 0, newBatType(getArgType(mb, p, 0)));
169 212 : clrVarFixed(mq, getArg(getInstrPtr(mq, 0), 0)); /* for typing */
170 212 : upgrade[getArg(getInstrPtr(mq, 0), 0)] = TRUE;
171 :
172 538 : for (i = 3; i < p->argc; i++) {
173 326 : if (!isaBatType(getArgType(mq, sig, i - 2))
174 326 : && isaBatType(getArgType(mb, p, i))) {
175 :
176 316 : if (getBatType(getArgType(mb, p, i)) != getArgType(mq, sig, i - 2)) {
177 0 : goto terminateMX;
178 : }
179 :
180 316 : setVarType(mq, i - 2, newBatType(getArgType(mb, p, i)));
181 316 : upgrade[getArg(sig, i - 2)] = TRUE;
182 316 : refbat = getArg(sig, i - 2);
183 : }
184 : }
185 : /*
186 : * The next step is to check each instruction of the
187 : * to-be-inlined function for arguments that require
188 : * an upgrade and resolve it afterwards.
189 : */
190 257445 : for (i = 1; i < mq->stop; i++) {
191 257445 : int fnd = 0;
192 :
193 257445 : q = getInstrPtr(mq, i);
194 257445 : if (q->token == ENDsymbol)
195 : break;
196 603062 : for (j = 0; j < q->argc && !fnd; j++)
197 345829 : if (upgrade[getArg(q, j)]) {
198 355426 : for (k = 0; k < q->retc; k++) {
199 177713 : setVarType(mq, getArg(q, j),
200 : newBatType(getArgType(mq, q, j)));
201 : /* for typing */
202 177713 : clrVarFixed(mq, getArg(q, k));
203 177713 : if (!upgrade[getArg(q, k)]) {
204 1807 : upgrade[getArg(q, k)] = TRUE;
205 : /* lets restart */
206 1807 : i = 0;
207 : }
208 : }
209 : fnd = 1;
210 : }
211 : /* nil:type -> nil:bat[:oid,:type] */
212 257233 : if (!getModuleId(q) && q->token == ASSIGNsymbol && q->argc == 2
213 115688 : && isVarConstant(mq, getArg(q, 1)) && upgrade[getArg(q, 0)]
214 851 : && getArgType(mq, q, 0) == TYPE_void
215 0 : && !isaBatType(getArgType(mq, q, 1))) {
216 : /* handle nil assignment */
217 0 : if (ATOMcmp(getArgGDKType(mq, q, 1),
218 : VALptr(&getVar(mq, getArg(q, 1))->value),
219 : ATOMnilptr(getArgType(mq, q, 1))) == 0) {
220 0 : ValRecord cst;
221 0 : int tpe = getArgType(mq, q, 1);
222 :
223 0 : cst.vtype = tpe;
224 0 : cst.bat = true;
225 0 : cst.val.bval = bat_nil;
226 0 : cst.len = 0;
227 0 : tpe = newBatType(tpe);
228 0 : setVarType(mq, getArg(q, 0), tpe);
229 0 : m = defConstant(mq, tpe, &cst);
230 0 : if (m >= 0) {
231 0 : getArg(q, 1) = m;
232 0 : setVarType(mq, getArg(q, 1), tpe);
233 : }
234 : } else {
235 : /* handle constant tail setting */
236 0 : int tpe = newBatType(getArgType(mq, q, 1));
237 :
238 0 : setVarType(mq, getArg(q, 0), tpe);
239 0 : setModuleId(q, algebraRef);
240 0 : setFunctionId(q, projectRef);
241 0 : q = pushArgument(mb, q, getArg(q, 1));
242 0 : mq->stmt[i] = q;
243 0 : getArg(q, 1) = refbat;
244 : }
245 : }
246 : }
247 :
248 : /* now upgrade the statements */
249 2849 : for (i = 1; i < mq->stop; i++) {
250 2849 : q = getInstrPtr(mq, i);
251 2849 : if (q->token == ENDsymbol)
252 : break;
253 5976 : for (j = 0; j < q->argc; j++)
254 4215 : if (upgrade[getArg(q, j)]) {
255 1960 : if (blockStart(q) || q->barrier == REDOsymbol
256 1956 : || q->barrier == LEAVEsymbol)
257 4 : goto terminateMX;
258 1956 : if (getModuleId(q)) {
259 864 : snprintf(buf, 1024, "bat%s", getModuleId(q));
260 864 : setModuleId(q, putName(buf));
261 864 : q->typeresolved = false;
262 864 : if (q->retc == 1 &&
263 864 : ((getModuleId(q) == batcalcRef
264 585 : && (getFunctionId(q) == mulRef
265 562 : || getFunctionId(q) == divRef
266 561 : || getFunctionId(q) == plusRef
267 39 : || getFunctionId(q) == minusRef
268 31 : || getFunctionId(q) == modRef))
269 310 : || getModuleId(q) == batmtimeRef
270 309 : || getModuleId(q) == batstrRef)) {
271 815 : if (q->argc == 3 &&
272 : /* these two filter out unary batcalc.- with a candidate list */
273 554 : getBatType(getArgType(mq, q, 1)) != TYPE_oid
274 554 : && getBatType(getArgType(mq, q, 2)) != TYPE_oid) {
275 : /* add candidate lists */
276 554 : if (isaBatType(getArgType(mq, q, 1)))
277 381 : q = pushNilBat(mq, q);
278 554 : if (isaBatType(getArgType(mq, q, 2)))
279 285 : q = pushNilBat(mq, q);
280 261 : } else if (q->argc == 4
281 260 : && getBatType(getArgType(mq, q, 3)) == TYPE_bit
282 : /* these two filter out unary
283 : * batcalc.- with a candidate
284 : * list */
285 0 : && getBatType(getArgType(mq, q, 1)) != TYPE_oid
286 0 : && getBatType(getArgType(mq, q, 2)) != TYPE_oid) {
287 0 : int a = getArg(q, 3);
288 0 : q->argc--;
289 : /* add candidate lists */
290 0 : if (isaBatType(getArgType(mq, q, 1)))
291 0 : q = pushNilBat(mq, q);
292 0 : if (isaBatType(getArgType(mq, q, 2)))
293 0 : q = pushNilBat(mq, q);
294 0 : q = pushArgument(mq, q, a);
295 : }
296 : }
297 :
298 : /* now see if we can resolve the instruction */
299 864 : typeChecker(cntxt->usermodule, mq, q, i, TRUE);
300 864 : if (!q->typeresolved)
301 2 : goto terminateMX;
302 : break;
303 : }
304 : /* handle simple upgraded assignments as well */
305 1092 : if (q->token == ASSIGNsymbol && q->argc == 2
306 1092 : && !(isaBatType(getArgType(mq, q, 1)))) {
307 14 : setModuleId(q, algebraRef);
308 14 : setFunctionId(q, projectRef);
309 14 : q = pushArgument(mq, q, getArg(q, 1));
310 14 : mq->stmt[i] = q;
311 14 : getArg(q, 1) = refbat;
312 :
313 14 : q->typeresolved = false;
314 14 : typeChecker(cntxt->usermodule, mq, q, i, TRUE);
315 14 : if (!q->typeresolved)
316 0 : goto terminateMX;
317 : break;
318 : }
319 : }
320 : }
321 :
322 :
323 206 : if (mq->errors) {
324 0 : terminateMX:
325 :
326 6 : freeMalBlk(mq);
327 6 : GDKfree(upgrade);
328 :
329 : /* ugh ugh, fallback to non inline, but optimized code */
330 6 : msg = OPTmultiplexSimple(cntxt, s->def);
331 6 : if (msg)
332 0 : freeException(msg);
333 6 : if (s->kind == FUNCTIONsymbol)
334 6 : s->def->inlineProp = 0;
335 6 : return 0;
336 : }
337 : /*
338 : * We have successfully constructed a variant
339 : * of the to-be-inlined function. Put it in place
340 : * of the original multiplex.
341 : * But first, shift the arguments of the multiplex.
342 : */
343 206 : delArgument(p, 2);
344 206 : delArgument(p, 1);
345 206 : inlineMALblock(mb, pc, mq);
346 :
347 206 : freeMalBlk(mq);
348 206 : GDKfree(upgrade);
349 206 : return 1;
350 : }
351 :
352 : /*
353 : * The comparison multiplex operations with a constant head may be supported
354 : * by reverse of the operation.
355 : */
356 : static const struct {
357 : const char *src, *dst;
358 : const int len;
359 : } OperatorMap[] = {
360 : {"<", ">", 1},
361 : {">", "<", 1},
362 : {">=", "<=", 2},
363 : {"<=", ">=", 2},
364 : {"==", "==", 2},
365 : {"!=", "!=", 2},
366 : {0, 0, 0}
367 : };
368 :
369 : static int
370 721 : OPTremapSwitched(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci,
371 : int idx, Module scope)
372 : {
373 721 : char *fcn;
374 721 : int r, i;
375 721 : (void) stk;
376 721 : (void) scope;
377 :
378 721 : if (!isMultiplex(pci) && getArgType(mb, pci, pci->retc) != TYPE_lng
379 0 : && !isVarConstant(mb, getArg(pci, 1))
380 0 : && !isVarConstant(mb, getArg(pci, 2))
381 0 : && !isVarConstant(mb, getArg(pci, 4)) && pci->argc != 5)
382 : return 0;
383 721 : fcn = VALget(&getVar(mb, getArg(pci, 2))->value);
384 5768 : for (i = 0; OperatorMap[i].src; i++)
385 4326 : if (strcmp(fcn, OperatorMap[i].src) == 0) {
386 : /* found a candidate for a switch */
387 0 : getVarConstant(mb, getArg(pci, 2)).val.sval = (char *) putNameLen(OperatorMap[i].dst, OperatorMap[i].len);
388 0 : getVarConstant(mb, getArg(pci, 2)).len = OperatorMap[i].len;
389 0 : r = getArg(pci, 3);
390 0 : getArg(pci, 3) = getArg(pci, 4);
391 0 : getArg(pci, 4) = r;
392 0 : r = OPTremapDirect(cntxt, mb, stk, pci, idx, scope);
393 :
394 : /* always restore the allocated function name */
395 0 : getVarConstant(mb, getArg(pci, 2)).val.sval = fcn;
396 0 : getVarConstant(mb, getArg(pci, 2)).len = strlen(fcn);
397 :
398 0 : if (r)
399 : return 1;
400 :
401 : /* restore the arguments */
402 0 : r = getArg(pci, 3);
403 0 : getArg(pci, 3) = getArg(pci, 4);
404 0 : getArg(pci, 4) = r;
405 : }
406 : return 0;
407 : }
408 :
409 : str
410 547015 : OPTremapImplementation(Client cntxt, MalBlkPtr mb, MalStkPtr stk, InstrPtr pci)
411 : {
412 547015 : InstrPtr *old, p;
413 547015 : int i, limit, slimit, actions = 0;
414 547015 : Module scope = cntxt->usermodule;
415 547015 : str msg = MAL_SUCCEED;
416 :
417 24708912 : for (i = 0; i < mb->stop; i++) {
418 24179398 : p = getInstrPtr(mb, i);
419 24179398 : if (isMultiplex(p)
420 24161897 : || (p->argc == 4 && getModuleId(p) == aggrRef
421 0 : && getFunctionId(p) == avgRef)) {
422 : break;
423 : }
424 : }
425 548069 : if (i == mb->stop) {
426 529514 : goto wrapup;
427 : }
428 :
429 18555 : old = mb->stmt;
430 18555 : limit = mb->stop;
431 18555 : slimit = mb->ssize;
432 18555 : if (newMalBlkStmt(mb, mb->ssize) < 0)
433 0 : throw(MAL, "optmizer.remap", SQLSTATE(HY013) MAL_MALLOC_FAIL);
434 :
435 3113470 : for (i = 0; i < limit; i++) {
436 3094915 : p = old[i];
437 3094915 : if (isMultiplex(p)) {
438 : /*
439 : * The next step considered is to handle inlined functions.
440 : * It means we have already skipped the most obvious ones,
441 : * such as the calculator functions. It is particularly
442 : * geared at handling the PSM code.
443 : */
444 141130 : int plus_one = getArgType(mb, p, p->retc) == TYPE_lng ? 1 : 0;
445 141130 : str mod = VALget(&getVar(mb, getArg(p, p->retc + 0 + plus_one))-> value);
446 141130 : str fcn = VALget(&getVar(mb, getArg(p, p->retc + 1 + plus_one))-> value);
447 : //Symbol s = findSymbol(cntxt->usermodule, mod,fcn);
448 141130 : Symbol s = findSymbolInModule(getModule(putName(mod)), putName(fcn));
449 :
450 141130 : if (s && s->kind == FUNCTIONsymbol && s->def->inlineProp) {
451 260 : pushInstruction(mb, p);
452 260 : if (OPTmultiplexInline(cntxt, mb, p, mb->stop - 1)) {
453 206 : actions++;
454 : }
455 140870 : } else if (OPTremapDirect(cntxt, mb, stk, p, i, scope)
456 721 : || OPTremapSwitched(cntxt, mb, stk, p, i, scope)) {
457 140149 : freeInstruction(p);
458 140149 : actions++;
459 : } else {
460 721 : pushInstruction(mb, p);
461 : }
462 2953785 : } else if (p->argc == 4 && getModuleId(p) == aggrRef
463 0 : && getFunctionId(p) == avgRef) {
464 : /* group aggr.avg -> aggr.sum/aggr.count */
465 0 : InstrPtr sum, avg, t, iszero;
466 0 : InstrPtr cnt;
467 0 : sum = copyInstruction(p);
468 0 : if (sum == NULL) {
469 0 : msg = createException(MAL, "optimizer.remap",
470 : SQLSTATE(HY013) MAL_MALLOC_FAIL);
471 0 : break;
472 : }
473 0 : cnt = copyInstruction(p);
474 0 : if (cnt == NULL) {
475 0 : freeInstruction(sum);
476 0 : msg = createException(MAL, "optimizer.remap",
477 : SQLSTATE(HY013) MAL_MALLOC_FAIL);
478 0 : break;
479 : }
480 0 : setFunctionId(sum, sumRef);
481 0 : setFunctionId(cnt, countRef);
482 0 : getArg(sum, 0) = newTmpVariable(mb, getArgType(mb, p, 1));
483 0 : getArg(cnt, 0) = newTmpVariable(mb, newBatType(TYPE_lng));
484 0 : pushInstruction(mb, sum);
485 0 : pushInstruction(mb, cnt);
486 :
487 0 : t = newInstruction(mb, batcalcRef, eqRef);
488 0 : if (t == NULL) {
489 0 : msg = createException(MAL, "optimizer.remap",
490 : SQLSTATE(HY013) MAL_MALLOC_FAIL);
491 0 : break;
492 : }
493 0 : getArg(t, 0) = newTmpVariable(mb, newBatType(TYPE_bit));
494 0 : t = pushArgument(mb, t, getDestVar(cnt));
495 0 : t = pushLng(mb, t, 0);
496 0 : pushInstruction(mb, t);
497 0 : iszero = t;
498 :
499 0 : t = newInstruction(mb, batcalcRef, dblRef);
500 0 : if (t == NULL) {
501 0 : msg = createException(MAL, "optimizer.remap",
502 : SQLSTATE(HY013) MAL_MALLOC_FAIL);
503 0 : break;
504 : }
505 0 : getArg(t, 0) = newTmpVariable(mb, getArgType(mb, p, 0));
506 0 : t = pushArgument(mb, t, getDestVar(sum));
507 0 : pushInstruction(mb, t);
508 0 : sum = t;
509 :
510 0 : t = newInstruction(mb, batcalcRef, ifthenelseRef);
511 0 : if (t == NULL) {
512 0 : msg = createException(MAL, "optimizer.remap",
513 : SQLSTATE(HY013) MAL_MALLOC_FAIL);
514 0 : break;
515 : }
516 0 : getArg(t, 0) = newTmpVariable(mb, getArgType(mb, p, 0));
517 0 : t = pushArgument(mb, t, getDestVar(iszero));
518 0 : t = pushNil(mb, t, TYPE_dbl);
519 0 : t = pushArgument(mb, t, getDestVar(sum));
520 0 : pushInstruction(mb, t);
521 0 : sum = t;
522 :
523 0 : t = newInstruction(mb, batcalcRef, dblRef);
524 0 : if (t == NULL) {
525 0 : msg = createException(MAL, "optimizer.remap",
526 : SQLSTATE(HY013) MAL_MALLOC_FAIL);
527 0 : break;
528 : }
529 0 : getArg(t, 0) = newTmpVariable(mb, getArgType(mb, p, 0));
530 0 : t = pushArgument(mb, t, getDestVar(cnt));
531 0 : pushInstruction(mb, t);
532 0 : cnt = t;
533 :
534 0 : avg = newInstruction(mb, batcalcRef, divRef);
535 0 : if (avg == NULL) {
536 0 : msg = createException(MAL, "optimizer.remap",
537 : SQLSTATE(HY013) MAL_MALLOC_FAIL);
538 0 : break;
539 : }
540 0 : getArg(avg, 0) = getArg(p, 0);
541 0 : avg = pushArgument(mb, avg, getDestVar(sum));
542 0 : avg = pushArgument(mb, avg, getDestVar(cnt));
543 0 : avg = pushNilBat(mb, avg);
544 0 : avg = pushNilBat(mb, avg);
545 0 : freeInstruction(p);
546 0 : pushInstruction(mb, avg);
547 : } else {
548 2953785 : pushInstruction(mb, p);
549 : }
550 : }
551 2579850 : for (; i < slimit; i++)
552 2561295 : if (old[i])
553 0 : pushInstruction(mb, old[i]);
554 18555 : GDKfree(old);
555 :
556 : /* Defense line against incorrect plans */
557 18555 : if (msg == MAL_SUCCEED && actions > 0) {
558 18214 : msg = chkTypes(cntxt->usermodule, mb, FALSE);
559 18214 : if (!msg)
560 18214 : msg = chkFlow(mb);
561 18214 : if (!msg)
562 18214 : msg = chkDeclarations(mb);
563 : }
564 341 : wrapup:
565 : /* keep actions taken as a fake argument */
566 548069 : (void) pushInt(mb, pci, actions);
567 548069 : return msg;
568 : }
|