1 /* Id: code.c,v 1.96 2015/11/17 19:19:40 ragge Exp */
2 /* $NetBSD: code.c,v 1.1.1.6 2016/02/09 20:28:17 plunky Exp $ */
3 /*
4 * Copyright (c) 2003 Anders Magnusson (ragge@ludd.luth.se).
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30
31 # include "pass1.h"
32
33 #ifdef LANG_CXX
34 #define p1listf listf
35 #define p1tfree tfree
36 #else
37 #define NODE P1ND
38 #define talloc p1alloc
39 #endif
40
41 /*
42 * Print out assembler segment name.
43 */
44 void
setseg(int seg,char * name)45 setseg(int seg, char *name)
46 {
47 switch (seg) {
48 case PROG: name = ".text"; break;
49 case DATA:
50 case LDATA: name = ".data"; break;
51 case UDATA: break;
52 #ifdef MACHOABI
53 case PICLDATA:
54 case PICDATA: name = ".section .data.rel.rw,\"aw\""; break;
55 case PICRDATA: name = ".section .data.rel.ro,\"aw\""; break;
56 case STRNG: name = ".cstring"; break;
57 case RDATA: name = ".const_data"; break;
58 #else
59 case PICLDATA: name = ".section .data.rel.local,\"aw\",@progbits";break;
60 case PICDATA: name = ".section .data.rel.rw,\"aw\",@progbits"; break;
61 case PICRDATA: name = ".section .data.rel.ro,\"aw\",@progbits"; break;
62 case STRNG:
63 #ifdef AOUTABI
64 case RDATA: name = ".data"; break;
65 #else
66 case RDATA: name = ".section .rodata"; break;
67 #endif
68 #endif
69 case TLSDATA: name = ".section .tdata,\"awT\",@progbits"; break;
70 case TLSUDATA: name = ".section .tbss,\"awT\",@nobits"; break;
71 #ifdef MACHOABI
72 case CTORS: name = ".mod_init_func\n\t.align 2"; break;
73 case DTORS: name = ".mod_term_func\n\t.align 2"; break;
74 #else
75 case CTORS: name = ".section\t.ctors,\"aw\",@progbits"; break;
76 case DTORS: name = ".section\t.dtors,\"aw\",@progbits"; break;
77 #endif
78 case NMSEG:
79 printf(PRTPREF "\t.section %s,\"a%c\",@progbits\n", name,
80 cftnsp ? 'x' : 'w');
81 return;
82 }
83 printf(PRTPREF "\t%s\n", name);
84 }
85
86 #ifdef MACHOABI
87 void
defalign(int al)88 defalign(int al)
89 {
90 printf(PRTPREF "\t.align %d\n", ispow2(al/ALCHAR));
91 }
92 #endif
93
94 /*
95 * Define everything needed to print out some data (or text).
96 * This means segment, alignment, visibility, etc.
97 */
98 void
defloc(struct symtab * sp)99 defloc(struct symtab *sp)
100 {
101 char *name;
102
103 name = getexname(sp);
104 if (sp->sclass == EXTDEF) {
105 printf(PRTPREF " .globl %s\n", name);
106 #if defined(ELFABI)
107 printf(PRTPREF "\t.type %s,@%s\n", name,
108 ISFTN(sp->stype)? "function" : "object");
109 #endif
110 }
111 #if defined(ELFABI)
112 if (!ISFTN(sp->stype)) {
113 if (sp->slevel == 0)
114 printf(PRTPREF "\t.size %s,%d\n", name,
115 (int)tsize(sp->stype, sp->sdf, sp->sap)/SZCHAR);
116 else
117 printf(PRTPREF "\t.size " LABFMT ",%d\n", sp->soffset,
118 (int)tsize(sp->stype, sp->sdf, sp->sap)/SZCHAR);
119 }
120 #endif
121 if (sp->slevel == 0)
122 printf(PRTPREF "%s:\n", name);
123 else
124 printf(PRTPREF LABFMT ":\n", sp->soffset);
125 }
126
127 int structrettemp;
128
129 /*
130 * code for the end of a function
131 * deals with struct return here
132 */
133 void
efcode(void)134 efcode(void)
135 {
136 extern int gotnr;
137 NODE *p, *q;
138 int sz;
139
140 gotnr = 0; /* new number for next fun */
141 if (cftnsp->stype != STRTY+FTN && cftnsp->stype != UNIONTY+FTN)
142 return;
143
144 /* struct return for small structs */
145 sz = tsize(BTYPE(cftnsp->stype), cftnsp->sdf, cftnsp->sap);
146 #if defined(os_openbsd)
147 if (sz == SZCHAR || sz == SZSHORT || sz == SZINT || sz == SZLONGLONG) {
148 #else
149 if (sz == SZLONGLONG && attr_find(cftnsp->sap, ATTR_COMPLEX)) {
150 #endif
151 /* Pointer to struct in eax */
152 if (sz == SZLONGLONG) {
153 q = block(OREG, NIL, NIL, INT, 0, 0);
154 slval(q, 4);
155 p = block(REG, NIL, NIL, INT, 0, 0);
156 p->n_rval = EDX;
157 ecomp(buildtree(ASSIGN, p, q));
158 }
159 if (sz < SZSHORT) sz = CHAR;
160 else if (sz > SZSHORT) sz = INT;
161 else sz = SHORT;
162 q = block(OREG, NIL, NIL, sz, 0, 0);
163 p = block(REG, NIL, NIL, sz, 0, 0);
164 ecomp(buildtree(ASSIGN, p, q));
165 return;
166 }
167
168 /* Create struct assignment */
169 q = tempnode(structrettemp, PTR+STRTY, 0, cftnsp->sap);
170 q = buildtree(UMUL, q, NIL);
171 p = block(REG, NIL, NIL, PTR+STRTY, 0, cftnsp->sap);
172 p = buildtree(UMUL, p, NIL);
173 p = buildtree(ASSIGN, q, p);
174 ecomp(p);
175
176 /* put hidden arg in eax on return */
177 q = tempnode(structrettemp, INT, 0, 0);
178 p = block(REG, NIL, NIL, INT, 0, 0);
179 regno(p) = EAX;
180 ecomp(buildtree(ASSIGN, p, q));
181 }
182
183 #ifdef GCC_COMPAT
184 static TWORD reparegs[] = { EAX, EDX, ECX };
185 static TWORD fastregs[] = { ECX, EDX };
186 #endif
187 static TWORD longregs[] = { EAXEDX, EDXECX };
188 static TWORD charregs[] = { AL, DL, CL };
189 static TWORD *regpregs;
190
191 /*
192 * code for the beginning of a function; a is an array of
193 * indices in symtab for the arguments; n is the number
194 *
195 * Classifying args on i386; not simple:
196 * - Args may be on stack or in registers (regparm)
197 * - There may be a hidden first arg, unless OpenBSD struct return.
198 * - Regparm syntax is not well documented.
199 * - There may be stdcall functions, where the called function pops stack
200 * - ...probably more
201 */
202 void
203 bfcode(struct symtab **sp, int cnt)
204 {
205 extern int argstacksize;
206 #ifdef GCC_COMPAT
207 struct attr *ap;
208 #endif
209 struct symtab *sp2;
210 extern int gotnr;
211 NODE *n, *p;
212 int i, regparmarg;
213 int argbase, nrarg, sz;
214
215 /* Take care of PIC stuff first */
216 if (kflag) {
217 #define STL 200
218 char *str = xmalloc(STL);
219 #if !defined(MACHOABI)
220 int l = getlab();
221 #else
222 char *name;
223 #endif
224
225 /* Generate extended assembler for PIC prolog */
226 p = tempnode(0, INT, 0, 0);
227 gotnr = regno(p);
228 p = block(XARG, p, NIL, INT, 0, 0);
229 p->n_name = "=g";
230 p = block(XASM, p, bcon(0), INT, 0, 0);
231
232 #if defined(MACHOABI)
233 if ((name = cftnsp->soname) == NULL)
234 name = cftnsp->sname;
235 if (snprintf(str, STL, "call L%s$pb\nL%s$pb:\n\tpopl %%0\n",
236 name, name) >= STL)
237 cerror("bfcode");
238 #else
239 if (snprintf(str, STL,
240 "call " LABFMT ";" LABFMT ":;\tpopl %%0;"
241 "\taddl $_GLOBAL_OFFSET_TABLE_+[.-" LABFMT "], %%0;",
242 l, l, l) >= STL)
243 cerror("bfcode");
244 #endif
245 p->n_name = addstring(str);
246 p->n_right->n_type = STRTY;
247 free(str);
248 ecomp(p);
249 }
250
251 argbase = ARGINIT;
252 nrarg = regparmarg = 0;
253 argstacksize = 0;
254
255 #ifdef GCC_COMPAT
256 regpregs = reparegs;
257 if (attr_find(cftnsp->sap, GCC_ATYP_STDCALL) != NULL)
258 cftnsp->sflags |= SSTDCALL;
259 if ((ap = attr_find(cftnsp->sap, GCC_ATYP_REGPARM)))
260 regparmarg = ap->iarg(0);
261 if ((ap = attr_find(cftnsp->sap, GCC_ATYP_FASTCALL)))
262 regparmarg = 2, regpregs = fastregs;
263 #endif
264
265 /* Function returns struct, create return arg node */
266 if (cftnsp->stype == STRTY+FTN || cftnsp->stype == UNIONTY+FTN) {
267 sz = tsize(BTYPE(cftnsp->stype), cftnsp->sdf, cftnsp->sap);
268 #if defined(os_openbsd)
269 /* OpenBSD uses non-standard return for small structs */
270 if (sz > SZLONGLONG)
271 #else
272 if (sz != SZLONGLONG ||
273 attr_find(cftnsp->sap, ATTR_COMPLEX) == 0)
274 #endif
275 {
276 if (regparmarg) {
277 n = block(REG, 0, 0, INT, 0, 0);
278 regno(n) = regpregs[nrarg++];
279 } else {
280 n = block(OREG, 0, 0, INT, 0, 0);
281 slval(n, argbase/SZCHAR);
282 argbase += SZINT;
283 regno(n) = FPREG;
284 argstacksize += 4; /* popped by callee */
285 }
286 p = tempnode(0, INT, 0, 0);
287 structrettemp = regno(p);
288 p = buildtree(ASSIGN, p, n);
289 ecomp(p);
290 }
291 }
292
293 /*
294 * Find where all params are so that they end up at the right place.
295 * At the same time recalculate their arg offset on stack.
296 * We also get the "pop size" for stdcall.
297 */
298 for (i = 0; i < cnt; i++) {
299 sp2 = sp[i];
300 sz = tsize(sp2->stype, sp2->sdf, sp2->sap);
301
302 SETOFF(sz, SZINT);
303
304 if (cisreg(sp2->stype) == 0 ||
305 ((regparmarg - nrarg) * SZINT < sz)) { /* not in reg */
306 sp2->soffset = argbase;
307 argbase += sz;
308 nrarg = regparmarg; /* no more in reg either */
309 } else { /* in reg */
310 sp2->soffset = regpregs[nrarg];
311 nrarg += sz/SZINT;
312 sp2->sclass = REGISTER;
313 }
314 }
315
316 /*
317 * Now (argbase - ARGINIT) is used space on stack.
318 * Move (if necessary) the args to something new.
319 */
320 for (i = 0; i < cnt; i++) {
321 int reg, j;
322
323 sp2 = sp[i];
324
325 if ((ISSOU(sp2->stype) && sp2->sclass == REGISTER) ||
326 (sp2->sclass == REGISTER && xtemps == 0)) {
327 /* must move to stack */
328 sz = tsize(sp2->stype, sp2->sdf, sp2->sap);
329 SETOFF(sz, SZINT);
330 SETOFF(autooff, SZINT);
331 reg = sp2->soffset;
332 sp2->sclass = AUTO;
333 sp2->soffset = NOOFFSET;
334 oalloc(sp2, &autooff);
335 for (j = 0; j < sz/SZCHAR; j += 4) {
336 p = block(OREG, 0, 0, INT, 0, 0);
337 slval(p, sp2->soffset/SZCHAR + j);
338 regno(p) = FPREG;
339 n = block(REG, 0, 0, INT, 0, 0);
340 regno(n) = regpregs[reg++];
341 p = block(ASSIGN, p, n, INT, 0, 0);
342 ecomp(p);
343 }
344 } else if (cisreg(sp2->stype) && !ISSOU(sp2->stype) &&
345 ((cqual(sp2->stype, sp2->squal) & VOL) == 0) && xtemps) {
346 /* just put rest in temps */
347 if (sp2->sclass == REGISTER) {
348 n = block(REG, 0, 0, sp2->stype,
349 sp2->sdf, sp2->sap);
350 if (ISLONGLONG(sp2->stype))
351 regno(n) = longregs[sp2->soffset];
352 else if (DEUNSIGN(sp2->stype) == CHAR ||
353 sp2->stype == BOOL)
354 regno(n) = charregs[sp2->soffset];
355 else
356 regno(n) = regpregs[sp2->soffset];
357 } else {
358 n = block(OREG, 0, 0, sp2->stype,
359 sp2->sdf, sp2->sap);
360 slval(n, sp2->soffset/SZCHAR);
361 regno(n) = FPREG;
362 }
363 p = tempnode(0, sp2->stype, sp2->sdf, sp2->sap);
364 sp2->soffset = regno(p);
365 sp2->sflags |= STNODE;
366 n = buildtree(ASSIGN, p, n);
367 ecomp(n);
368 }
369 }
370
371 if (cftnsp->sflags & SSTDCALL) {
372 #ifdef PECOFFABI
373 char buf[256];
374 char *name;
375 #endif
376 /* XXX interaction STDCALL and struct return? */
377 argstacksize += (argbase - ARGINIT)/SZCHAR;
378 #ifdef PECOFFABI
379 /*
380 * mangle name in symbol table as a callee.
381 */
382 if ((name = cftnsp->soname) == NULL)
383 name = exname(cftnsp->sname);
384 snprintf(buf, 256, "%s@%d", name, argstacksize);
385 cftnsp->soname = addname(buf);
386 #endif
387 }
388
389 }
390
391 #if defined(MACHOABI)
392 struct stub stublist;
393 struct stub nlplist;
394 #endif
395
396 /* called just before final exit */
397 /* flag is 1 if errors, 0 if none */
398 void
399 ejobcode(int flag)
400 {
401 #if defined(MACHOABI)
402 /*
403 * iterate over the stublist and output the PIC stubs
404 ` */
405 if (kflag) {
406 struct stub *p;
407
408 DLIST_FOREACH(p, &stublist, link) {
409 printf(PRTPREF "\t.section __IMPORT,__jump_table,symbol_stubs,self_modifying_code+pure_instructions,5\n");
410 printf(PRTPREF "L%s$stub:\n", p->name);
411 printf(PRTPREF "\t.indirect_symbol %s\n", p->name);
412 printf(PRTPREF "\thlt ; hlt ; hlt ; hlt ; hlt\n");
413 printf(PRTPREF "\t.subsections_via_symbols\n");
414 }
415
416 printf(PRTPREF "\t.section __IMPORT,__pointers,non_lazy_symbol_pointers\n");
417 DLIST_FOREACH(p, &nlplist, link) {
418 printf(PRTPREF "L%s$non_lazy_ptr:\n", p->name);
419 printf(PRTPREF "\t.indirect_symbol %s\n", p->name);
420 printf(PRTPREF "\t.long 0\n");
421 }
422
423 }
424 #endif
425
426 printf(PRTPREF "\t.ident \"PCC: %s\"\n", VERSSTR);
427 }
428
429 void
430 bjobcode(void)
431 {
432 #ifdef os_sunos
433 astypnames[SHORT] = astypnames[USHORT] = "\t.2byte";
434 #endif
435 astypnames[INT] = astypnames[UNSIGNED] = "\t.long";
436 #if defined(MACHOABI)
437 DLIST_INIT(&stublist, link);
438 DLIST_INIT(&nlplist, link);
439 #endif
440 #if defined(__GNUC__) || defined(__PCC__)
441 /* Be sure that the compiler uses full x87 */
442 /* XXX cross-compiling will fail here */
443 int fcw;
444 __asm("fstcw (%0)" : : "r"(&fcw));
445 fcw |= 0x300;
446 __asm("fldcw (%0)" : : "r"(&fcw));
447 #endif
448 }
449
450 /*
451 * Convert FUNARG to assign in case of regparm.
452 */
453 static int regcvt, rparg, fcall;
454 static void
455 addreg(NODE *p)
456 {
457 TWORD t;
458 NODE *q;
459 int sz, r;
460
461 sz = tsize(p->n_type, p->n_df, p->n_ap)/SZCHAR;
462 sz = (sz + 3) >> 2; /* sz in regs */
463 if ((regcvt+sz) > rparg) {
464 regcvt = rparg;
465 return;
466 }
467 if (sz > 2)
468 uerror("cannot put struct in 3 regs (yet)");
469
470 if (sz == 2)
471 r = regcvt == 0 ? EAXEDX : EDXECX;
472 else if (fcall)
473 r = regcvt == 0 ? ECX : EDX;
474 else
475 r = regcvt == 0 ? EAX : regcvt == 1 ? EDX : ECX;
476
477 if (p->n_op == FUNARG) {
478 /* at most 2 regs */
479 if (p->n_type < INT) {
480 p->n_left = ccast(p->n_left, INT, 0, 0, 0);
481 p->n_type = INT;
482 }
483
484 p->n_op = ASSIGN;
485 p->n_right = p->n_left;
486 } else if (p->n_op == STARG) {
487 /* convert to ptr, put in reg */
488 q = p->n_left;
489 t = sz == 2 ? LONGLONG : INT;
490 q = cast(q, INCREF(t), 0);
491 q = buildtree(UMUL, q, NIL);
492 p->n_op = ASSIGN;
493 p->n_type = t;
494 p->n_right = q;
495 } else
496 cerror("addreg");
497 p->n_left = block(REG, 0, 0, p->n_type, 0, 0);
498 regno(p->n_left) = r;
499 regcvt += sz;
500 }
501
502 /*
503 * Called with a function call with arguments as argument.
504 * This is done early in buildtree() and only done once.
505 * Returns p.
506 */
507 NODE *
508 funcode(NODE *p)
509 {
510 extern int gotnr;
511 struct attr *ap;
512 NODE *r, *l;
513 TWORD t = DECREF(DECREF(p->n_left->n_type));
514 int stcall;
515
516 stcall = ISSOU(t);
517 /*
518 * We may have to prepend:
519 * - Hidden arg0 for struct return (in reg or on stack).
520 * - ebx in case of PIC code.
521 */
522
523 /* Fix function call arguments. On x86, just add funarg */
524 for (r = p->n_right; r->n_op == CM; r = r->n_left) {
525 if (r->n_right->n_op != STARG)
526 r->n_right = block(FUNARG, r->n_right, NIL,
527 r->n_right->n_type, r->n_right->n_df,
528 r->n_right->n_ap);
529 }
530 if (r->n_op != STARG) {
531 l = talloc();
532 *l = *r;
533 r->n_op = FUNARG;
534 r->n_left = l;
535 r->n_type = l->n_type;
536 }
537 #ifdef os_openbsd
538 if (stcall && (ap = strattr(p->n_left->n_ap)) &&
539 ap->amsize != SZCHAR && ap->amsize != SZSHORT &&
540 ap->amsize != SZINT && ap->amsize != SZLONGLONG)
541 #else
542 if (stcall &&
543 (attr_find(p->n_left->n_ap, ATTR_COMPLEX) == 0 ||
544 ((ap = strattr(p->n_left->n_ap)) && ap->amsize > SZLONGLONG)))
545 #endif
546 {
547 /* Prepend a placeholder for struct address. */
548 /* Use EBP, can never show up under normal circumstances */
549 l = talloc();
550 *l = *r;
551 r->n_op = CM;
552 r->n_right = l;
553 r->n_type = INT;
554 l = block(REG, 0, 0, INCREF(VOID), 0, 0);
555 regno(l) = EBP;
556 l = block(FUNARG, l, 0, INCREF(VOID), 0, 0);
557 r->n_left = l;
558 }
559
560 #ifdef GCC_COMPAT
561 fcall = 0;
562 if ((ap = attr_find(p->n_left->n_ap, GCC_ATYP_REGPARM)))
563 rparg = ap->iarg(0);
564 else if ((ap = attr_find(p->n_left->n_ap, GCC_ATYP_FASTCALL)))
565 fcall = rparg = 2;
566 else
567 #endif
568 rparg = 0;
569
570 regcvt = 0;
571 if (rparg)
572 p1listf(p->n_right, addreg);
573
574 if (kflag == 0)
575 return p;
576
577 #if defined(ELFABI)
578 /* Create an ASSIGN node for ebx */
579 l = block(REG, NIL, NIL, INT, 0, 0);
580 l->n_rval = EBX;
581 l = buildtree(ASSIGN, l, tempnode(gotnr, INT, 0, 0));
582 if (p->n_right->n_op != CM) {
583 p->n_right = block(CM, l, p->n_right, INT, 0, 0);
584 } else {
585 for (r = p->n_right; r->n_left->n_op == CM; r = r->n_left)
586 ;
587 r->n_left = block(CM, l, r->n_left, INT, 0, 0);
588 }
589 #endif
590 return p;
591 }
592
593 /* fix up type of field p */
594 void
595 fldty(struct symtab *p)
596 {
597 }
598
599 /*
600 * XXX - fix genswitch.
601 */
602 int
603 mygenswitch(int num, TWORD type, struct swents **p, int n)
604 {
605 return 0;
606 }
607
608 NODE *
609 builtin_return_address(const struct bitable *bt, NODE *a)
610 {
611 int nframes;
612 NODE *f;
613
614 if (a->n_op != ICON)
615 goto bad;
616
617 nframes = (int)glval(a);
618
619 p1tfree(a);
620
621 f = block(REG, NIL, NIL, PTR+VOID, 0, 0);
622 regno(f) = FPREG;
623
624 while (nframes--)
625 f = block(UMUL, f, NIL, PTR+VOID, 0, 0);
626
627 f = block(PLUS, f, bcon(4), INCREF(PTR+VOID), 0, 0);
628 f = buildtree(UMUL, f, NIL);
629
630 return f;
631 bad:
632 uerror("bad argument to __builtin_return_address");
633 return bcon(0);
634 }
635
636 NODE *
637 builtin_frame_address(const struct bitable *bt, NODE *a)
638 {
639 int nframes;
640 NODE *f;
641
642 if (a->n_op != ICON)
643 goto bad;
644
645 nframes = (int)glval(a);
646
647 p1tfree(a);
648
649 f = block(REG, NIL, NIL, PTR+VOID, 0, 0);
650 regno(f) = FPREG;
651
652 while (nframes--)
653 f = block(UMUL, f, NIL, PTR+VOID, 0, 0);
654
655 return f;
656 bad:
657 uerror("bad argument to __builtin_frame_address");
658 return bcon(0);
659 }
660
661 /*
662 * Return "canonical frame address".
663 */
664 NODE *
665 builtin_cfa(const struct bitable *bt, NODE *a)
666 {
667 uerror("missing builtin_cfa");
668 return bcon(0);
669 }
670
671