// cmd/9l/noop.c, cmd/9l/pass.c, cmd/9l/span.c from Vita Nuova. // // Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved. // Portions Copyright © 1995-1997 C H Forsyth (forsyth@terzarima.net) // Portions Copyright © 1997-1999 Vita Nuova Limited // Portions Copyright © 2000-2008 Vita Nuova Holdings Limited (www.vitanuova.com) // Portions Copyright © 2004,2006 Bruce Ellis // Portions Copyright © 2005-2007 C H Forsyth (forsyth@terzarima.net) // Revisions Copyright © 2000-2008 Lucent Technologies Inc. and others // Portions Copyright © 2009 The Go Authors. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #include #include #include #include #include "../cmd/9l/9.out.h" #include "../runtime/stack.h" #include "../runtime/funcdata.h" static Prog zprg = { .as = AGOK, .reg = NREG, .from = { .name = D_NONE, .type = D_NONE, .reg = NREG, }, .from3 = { .name = D_NONE, .type = D_NONE, .reg = NREG, }, .to = { .name = D_NONE, .type = D_NONE, .reg = NREG, }, }; static int symtype(Addr *a) { return a->name; } static int isdata(Prog *p) { return p->as == ADATA || p->as == AGLOBL; } static int iscall(Prog *p) { return p->as == ABL; } static int datasize(Prog *p) { return p->reg; } static int textflag(Prog *p) { return p->reg; } static void settextflag(Prog *p, int f) { p->reg = f; } static void progedit(Link *ctxt, Prog *p) { char literal[64]; LSym *s; USED(ctxt); p->from.class = 0; p->to.class = 0; // Rewrite BR/BL to symbol as D_BRANCH. switch(p->as) { case ABR: case ABL: case ARETURN: case ADUFFZERO: case ADUFFCOPY: if(p->to.sym != nil) p->to.type = D_BRANCH; break; } // Rewrite float constants to values stored in memory. switch(p->as) { case AFMOVS: if(p->from.type == D_FCONST) { int32 i32; float32 f32; f32 = p->from.u.dval; memmove(&i32, &f32, 4); sprint(literal, "$f32.%08ux", (uint32)i32); s = linklookup(ctxt, literal, 0); s->size = 4; p->from.type = D_OREG; p->from.sym = s; p->from.name = D_EXTERN; p->from.offset = 0; } break; case AFMOVD: if(p->from.type == D_FCONST) { int64 i64; memmove(&i64, &p->from.u.dval, 8); sprint(literal, "$f64.%016llux", (uvlong)i64); s = linklookup(ctxt, literal, 0); s->size = 8; p->from.type = D_OREG; p->from.sym = s; p->from.name = D_EXTERN; p->from.offset = 0; } break; case AMOVD: if(p->from.type == D_CONST && p->from.name == D_NONE && (int64)(uint32)p->from.offset != p->from.offset) { sprint(literal, "$i64.%016llux", (uvlong)p->from.offset); s = linklookup(ctxt, literal, 0); s->size = 8; p->from.type = D_OREG; p->from.sym = s; p->from.name = D_EXTERN; p->from.offset = 0; } } // Rewrite SUB constants into ADD. switch(p->as) { case ASUBC: if(p->from.type == D_CONST) { p->from.offset = -p->from.offset; p->as = AADDC; } break; case ASUBCCC: if(p->from.type == D_CONST) { p->from.offset = -p->from.offset; p->as = AADDCCC; } break; case ASUB: if(p->from.type == D_CONST) { p->from.offset = -p->from.offset; p->as = AADD; } break; } } static Prog* stacksplit(Link*, Prog*, int32, int); static void parsetextconst(vlong arg, vlong *textstksiz, vlong *textarg) { *textstksiz = arg & 0xffffffffLL; if(*textstksiz & 0x80000000LL) *textstksiz = -(-*textstksiz & 0xffffffffLL); *textarg = (arg >> 32) & 0xffffffffLL; if(*textarg & 0x80000000LL) *textarg = 0; *textarg = (*textarg+7) & ~7LL; } static void addstacksplit(Link *ctxt, LSym *cursym) { Prog *p, *q, *p1, *p2, *q1; int o, mov, aoffset; vlong textstksiz, textarg; int32 autosize; if(ctxt->symmorestack[0] == nil) { ctxt->symmorestack[0] = linklookup(ctxt, "runtime.morestack", 0); ctxt->symmorestack[1] = linklookup(ctxt, "runtime.morestack_noctxt", 0); // TODO(minux): add morestack short-cuts with small fixed frame-size. } ctxt->cursym = cursym; if(cursym->text == nil || cursym->text->link == nil) return; p = cursym->text; parsetextconst(p->to.offset, &textstksiz, &textarg); cursym->args = p->to.offset>>32; cursym->locals = textstksiz; /* * find leaf subroutines * strip NOPs * expand RET * expand BECOME pseudo */ if(ctxt->debugvlog) Bprint(ctxt->bso, "%5.2f noops\n", cputime()); Bflush(ctxt->bso); q = nil; for(p = cursym->text; p != nil; p = p->link) { switch(p->as) { /* too hard, just leave alone */ case ATEXT: q = p; p->mark |= LABEL|LEAF|SYNC; if(p->link) p->link->mark |= LABEL; break; case ANOR: q = p; if(p->to.type == D_REG) if(p->to.reg == REGZERO) p->mark |= LABEL|SYNC; break; case ALWAR: case ASTWCCC: case AECIWX: case AECOWX: case AEIEIO: case AICBI: case AISYNC: case ATLBIE: case ATLBIEL: case ASLBIA: case ASLBIE: case ASLBMFEE: case ASLBMFEV: case ASLBMTE: case ADCBF: case ADCBI: case ADCBST: case ADCBT: case ADCBTST: case ADCBZ: case ASYNC: case ATLBSYNC: case APTESYNC: case ATW: case AWORD: case ARFI: case ARFCI: case ARFID: case AHRFID: q = p; p->mark |= LABEL|SYNC; continue; case AMOVW: case AMOVWZ: case AMOVD: q = p; switch(p->from.type) { case D_MSR: case D_SPR: case D_FPSCR: case D_CREG: case D_DCR: p->mark |= LABEL|SYNC; } switch(p->to.type) { case D_MSR: case D_SPR: case D_FPSCR: case D_CREG: case D_DCR: p->mark |= LABEL|SYNC; } continue; case AFABS: case AFABSCC: case AFADD: case AFADDCC: case AFCTIW: case AFCTIWCC: case AFCTIWZ: case AFCTIWZCC: case AFDIV: case AFDIVCC: case AFMADD: case AFMADDCC: case AFMOVD: case AFMOVDU: /* case AFMOVDS: */ case AFMOVS: case AFMOVSU: /* case AFMOVSD: */ case AFMSUB: case AFMSUBCC: case AFMUL: case AFMULCC: case AFNABS: case AFNABSCC: case AFNEG: case AFNEGCC: case AFNMADD: case AFNMADDCC: case AFNMSUB: case AFNMSUBCC: case AFRSP: case AFRSPCC: case AFSUB: case AFSUBCC: q = p; p->mark |= FLOAT; continue; case ABL: case ABCL: case ADUFFZERO: case ADUFFCOPY: cursym->text->mark &= ~LEAF; case ABC: case ABEQ: case ABGE: case ABGT: case ABLE: case ABLT: case ABNE: case ABR: case ABVC: case ABVS: p->mark |= BRANCH; q = p; q1 = p->pcond; if(q1 != nil) { while(q1->as == ANOP) { q1 = q1->link; p->pcond = q1; } if(!(q1->mark & LEAF)) q1->mark |= LABEL; } else p->mark |= LABEL; q1 = p->link; if(q1 != nil) q1->mark |= LABEL; continue; case AFCMPO: case AFCMPU: q = p; p->mark |= FCMP|FLOAT; continue; case ARETURN: q = p; if(p->link != nil) p->link->mark |= LABEL; continue; case ANOP: q1 = p->link; q->link = q1; /* q is non-nop */ q1->mark |= p->mark; continue; default: q = p; continue; } } autosize = 0; for(p = cursym->text; p != nil; p = p->link) { o = p->as; switch(o) { case ATEXT: mov = AMOVD; aoffset = 0; autosize = textstksiz + 8; if((p->mark & LEAF) && autosize <= 8) autosize = 0; else if(autosize & 4) autosize += 4; p->to.offset = (p->to.offset & (0xffffffffull<<32)) | (uint32)(autosize-8); if(!(p->reg & NOSPLIT)) p = stacksplit(ctxt, p, autosize, !(cursym->text->reg&NEEDCTXT)); // emit split check q = p; if(autosize) { /* use MOVDU to adjust R1 when saving R31, if autosize is small */ if(!(cursym->text->mark & LEAF) && autosize >= -BIG && autosize <= BIG) { mov = AMOVDU; aoffset = -autosize; } else { q = appendp(ctxt, p); q->as = AADD; q->lineno = p->lineno; q->from.type = D_CONST; q->from.offset = -autosize; q->to.type = D_REG; q->to.reg = REGSP; q->spadj = +autosize; } } else if(!(cursym->text->mark & LEAF)) { if(ctxt->debugvlog) { Bprint(ctxt->bso, "save suppressed in: %s\n", cursym->name); Bflush(ctxt->bso); } cursym->text->mark |= LEAF; } if(cursym->text->mark & LEAF) { cursym->leaf = 1; break; } q = appendp(ctxt, q); q->as = AMOVD; q->lineno = p->lineno; q->from.type = D_SPR; q->from.offset = D_LR; q->to.type = D_REG; q->to.reg = REGTMP; q = appendp(ctxt, q); q->as = mov; q->lineno = p->lineno; q->from.type = D_REG; q->from.reg = REGTMP; q->to.type = D_OREG; q->to.offset = aoffset; q->to.reg = REGSP; if(q->as == AMOVDU) q->spadj = -aoffset; if(cursym->text->reg & WRAPPER) { // if(g->panic != nil && g->panic->argp == FP) g->panic->argp = bottom-of-frame // // MOVD g_panic(g), R3 // CMP R0, R3 // BEQ end // MOVD panic_argp(R3), R4 // ADD $(autosize+8), R1, R5 // CMP R4, R5 // BNE end // ADD $8, R1, R6 // MOVD R6, panic_argp(R3) // end: // NOP // // The NOP is needed to give the jumps somewhere to land. // It is a liblink NOP, not a ppc64 NOP: it encodes to 0 instruction bytes. q = appendp(ctxt, q); q->as = AMOVD; q->from.type = D_OREG; q->from.reg = REGG; q->from.offset = 4*ctxt->arch->ptrsize; // G.panic q->to.type = D_REG; q->to.reg = 3; q = appendp(ctxt, q); q->as = ACMP; q->from.type = D_REG; q->from.reg = 0; q->to.type = D_REG; q->to.reg = 3; q = appendp(ctxt, q); q->as = ABEQ; q->to.type = D_BRANCH; p1 = q; q = appendp(ctxt, q); q->as = AMOVD; q->from.type = D_OREG; q->from.reg = 3; q->from.offset = 0; // Panic.argp q->to.type = D_REG; q->to.reg = 4; q = appendp(ctxt, q); q->as = AADD; q->from.type = D_CONST; q->from.offset = autosize+8; q->reg = REGSP; q->to.type = D_REG; q->to.reg = 5; q = appendp(ctxt, q); q->as = ACMP; q->from.type = D_REG; q->from.reg = 4; q->to.type = D_REG; q->to.reg = 5; q = appendp(ctxt, q); q->as = ABNE; q->to.type = D_BRANCH; p2 = q; q = appendp(ctxt, q); q->as = AADD; q->from.type = D_CONST; q->from.offset = 8; q->reg = REGSP; q->to.type = D_REG; q->to.reg = 6; q = appendp(ctxt, q); q->as = AMOVD; q->from.type = D_REG; q->from.reg = 6; q->to.type = D_OREG; q->to.reg = 3; q->to.offset = 0; // Panic.argp q = appendp(ctxt, q); q->as = ANOP; p1->pcond = q; p2->pcond = q; } break; case ARETURN: if(p->from.type == D_CONST) { ctxt->diag("using BECOME (%P) is not supported!", p); break; } if(p->to.sym) { // retjmp p->as = ABR; p->to.type = D_BRANCH; break; } if(cursym->text->mark & LEAF) { if(!autosize) { p->as = ABR; p->from = zprg.from; p->to.type = D_SPR; p->to.offset = D_LR; p->mark |= BRANCH; break; } p->as = AADD; p->from.type = D_CONST; p->from.offset = autosize; p->to.type = D_REG; p->to.reg = REGSP; p->spadj = -autosize; q = ctxt->arch->prg(); q->as = ABR; q->lineno = p->lineno; q->to.type = D_SPR; q->to.offset = D_LR; q->mark |= BRANCH; q->spadj = +autosize; q->link = p->link; p->link = q; break; } p->as = AMOVD; p->from.type = D_OREG; p->from.offset = 0; p->from.reg = REGSP; p->to.type = D_REG; p->to.reg = REGTMP; q = ctxt->arch->prg(); q->as = AMOVD; q->lineno = p->lineno; q->from.type = D_REG; q->from.reg = REGTMP; q->to.type = D_SPR; q->to.offset = D_LR; q->link = p->link; p->link = q; p = q; if(autosize) { q = ctxt->arch->prg(); q->as = AADD; q->lineno = p->lineno; q->from.type = D_CONST; q->from.offset = autosize; q->to.type = D_REG; q->to.reg = REGSP; q->spadj = -autosize; q->link = p->link; p->link = q; } q1 = ctxt->arch->prg(); q1->as = ABR; q1->lineno = p->lineno; q1->to.type = D_SPR; q1->to.offset = D_LR; q1->mark |= BRANCH; q1->spadj = +autosize; q1->link = q->link; q->link = q1; break; case AADD: if(p->to.type == D_REG && p->to.reg == REGSP && p->from.type == D_CONST) p->spadj = -p->from.offset; break; } } #if 0 // instruction scheduling if(debug['Q'] == 0) return; curtext = nil; q = nil; /* p - 1 */ q1 = firstp; /* top of block */ o = 0; /* count of instructions */ for(p = firstp; p != nil; p = p1) { p1 = p->link; o++; if(p->mark & NOSCHED){ if(q1 != p){ sched(q1, q); } for(; p != nil; p = p->link){ if(!(p->mark & NOSCHED)) break; q = p; } p1 = p; q1 = p; o = 0; continue; } if(p->mark & (LABEL|SYNC)) { if(q1 != p) sched(q1, q); q1 = p; o = 1; } if(p->mark & (BRANCH|SYNC)) { sched(q1, p); q1 = p1; o = 0; } if(o >= NSCHED) { sched(q1, p); q1 = p1; o = 0; } q = p; } #endif } static Prog* stacksplit(Link *ctxt, Prog *p, int32 framesize, int noctxt) { Prog *q, *q1; // MOVD g_stackguard(g), R3 p = appendp(ctxt, p); p->as = AMOVD; p->from.type = D_OREG; p->from.reg = REGG; p->from.offset = 2*ctxt->arch->ptrsize; // G.stackguard0 if(ctxt->cursym->cfunc) p->from.offset = 3*ctxt->arch->ptrsize; // G.stackguard1 p->to.type = D_REG; p->to.reg = 3; q = nil; if(framesize <= StackSmall) { // small stack: SP < stackguard // CMP stackguard, SP p = appendp(ctxt, p); p->as = ACMPU; p->from.type = D_REG; p->from.reg = 3; p->to.type = D_REG; p->to.reg = REGSP; } else if(framesize <= StackBig) { // large stack: SP-framesize < stackguard-StackSmall // ADD $-framesize, SP, R4 // CMP stackguard, R4 p = appendp(ctxt, p); p->as = AADD; p->from.type = D_CONST; p->from.offset = -framesize; p->reg = REGSP; p->to.type = D_REG; p->to.reg = 4; p = appendp(ctxt, p); p->as = ACMPU; p->from.type = D_REG; p->from.reg = 3; p->to.type = D_REG; p->to.reg = 4; } else { // Such a large stack we need to protect against wraparound. // If SP is close to zero: // SP-stackguard+StackGuard <= framesize + (StackGuard-StackSmall) // The +StackGuard on both sides is required to keep the left side positive: // SP is allowed to be slightly below stackguard. See stack.h. // // Preemption sets stackguard to StackPreempt, a very large value. // That breaks the math above, so we have to check for that explicitly. // // stackguard is R3 // CMP R3, $StackPreempt // BEQ label-of-call-to-morestack // ADD $StackGuard, SP, R4 // SUB R3, R4 // MOVD $(framesize+(StackGuard-StackSmall)), R31 // CMPU R31, R4 p = appendp(ctxt, p); p->as = ACMP; p->from.type = D_REG; p->from.reg = 3; p->to.type = D_CONST; p->to.offset = StackPreempt; q = p = appendp(ctxt, p); p->as = ABEQ; p->to.type = D_BRANCH; p = appendp(ctxt, p); p->as = AADD; p->from.type = D_CONST; p->from.offset = StackGuard; p->reg = REGSP; p->to.type = D_REG; p->to.reg = 4; p = appendp(ctxt, p); p->as = ASUB; p->from.type = D_REG; p->from.reg = 3; p->to.type = D_REG; p->to.reg = 4; p = appendp(ctxt, p); p->as = AMOVD; p->from.type = D_CONST; p->from.offset = framesize + StackGuard - StackSmall; p->to.type = D_REG; p->to.reg = REGTMP; p = appendp(ctxt, p); p->as = ACMPU; p->from.type = D_REG; p->from.reg = REGTMP; p->to.type = D_REG; p->to.reg = 4; } // q1: BLT done q1 = p = appendp(ctxt, p); p->as = ABLT; p->to.type = D_BRANCH; // MOVD LR, R5 p = appendp(ctxt, p); p->as = AMOVD; p->from.type = D_SPR; p->from.offset = D_LR; p->to.type = D_REG; p->to.reg = 5; if(q) q->pcond = p; // BL runtime.morestack(SB) p = appendp(ctxt, p); p->as = ABL; p->to.type = D_BRANCH; if(ctxt->cursym->cfunc) p->to.sym = linklookup(ctxt, "runtime.morestackc", 0); else p->to.sym = ctxt->symmorestack[noctxt]; // BR start p = appendp(ctxt, p); p->as = ABR; p->to.type = D_BRANCH; p->pcond = ctxt->cursym->text->link; // placeholder for q1's jump target p = appendp(ctxt, p); p->as = ANOP; // zero-width place holder q1->pcond = p; return p; } static void xfol(Link*, Prog*, Prog**); static void follow(Link *ctxt, LSym *s) { Prog *firstp, *lastp; ctxt->cursym = s; firstp = ctxt->arch->prg(); lastp = firstp; xfol(ctxt, s->text, &lastp); lastp->link = nil; s->text = firstp->link; } static int relinv(int a) { switch(a) { case ABEQ: return ABNE; case ABNE: return ABEQ; case ABGE: return ABLT; case ABLT: return ABGE; case ABGT: return ABLE; case ABLE: return ABGT; case ABVC: return ABVS; case ABVS: return ABVC; } return 0; } static void xfol(Link *ctxt, Prog *p, Prog **last) { Prog *q, *r; int a, b, i; loop: if(p == nil) return; a = p->as; if(a == ABR) { q = p->pcond; if((p->mark&NOSCHED) || q && (q->mark&NOSCHED)){ p->mark |= FOLL; (*last)->link = p; *last = p; p = p->link; xfol(ctxt, p, last); p = q; if(p && !(p->mark & FOLL)) goto loop; return; } if(q != nil) { p->mark |= FOLL; p = q; if(!(p->mark & FOLL)) goto loop; } } if(p->mark & FOLL) { for(i=0,q=p; i<4; i++,q=q->link) { if(q == *last || (q->mark&NOSCHED)) break; b = 0; /* set */ a = q->as; if(a == ANOP) { i--; continue; } if(a == ABR || a == ARETURN || a == ARFI || a == ARFCI || a == ARFID || a == AHRFID) goto copy; if(!q->pcond || (q->pcond->mark&FOLL)) continue; b = relinv(a); if(!b) continue; copy: for(;;) { r = ctxt->arch->prg(); *r = *p; if(!(r->mark&FOLL)) print("cant happen 1\n"); r->mark |= FOLL; if(p != q) { p = p->link; (*last)->link = r; *last = r; continue; } (*last)->link = r; *last = r; if(a == ABR || a == ARETURN || a == ARFI || a == ARFCI || a == ARFID || a == AHRFID) return; r->as = b; r->pcond = p->link; r->link = p->pcond; if(!(r->link->mark&FOLL)) xfol(ctxt, r->link, last); if(!(r->pcond->mark&FOLL)) print("cant happen 2\n"); return; } } a = ABR; q = ctxt->arch->prg(); q->as = a; q->lineno = p->lineno; q->to.type = D_BRANCH; q->to.offset = p->pc; q->pcond = p; p = q; } p->mark |= FOLL; (*last)->link = p; *last = p; if(a == ABR || a == ARETURN || a == ARFI || a == ARFCI || a == ARFID || a == AHRFID){ if(p->mark & NOSCHED){ p = p->link; goto loop; } return; } if(p->pcond != nil) if(a != ABL && p->link != nil) { xfol(ctxt, p->link, last); p = p->pcond; if(p == nil || (p->mark&FOLL)) return; goto loop; } p = p->link; goto loop; } static Prog* prg(void) { Prog *p; p = emallocz(sizeof(*p)); *p = zprg; return p; } LinkArch linkppc64 = { .name = "ppc64", .thechar = '9', .endian = BigEndian, .addstacksplit = addstacksplit, .assemble = span9, .datasize = datasize, .follow = follow, .iscall = iscall, .isdata = isdata, .prg = prg, .progedit = progedit, .settextflag = settextflag, .symtype = symtype, .textflag = textflag, .minlc = 4, .ptrsize = 8, .regsize = 8, .D_ADDR = D_ADDR, .D_AUTO = D_AUTO, .D_BRANCH = D_BRANCH, .D_CONST = D_CONST, .D_EXTERN = D_EXTERN, .D_FCONST = D_FCONST, .D_NONE = D_NONE, .D_PARAM = D_PARAM, .D_SCONST = D_SCONST, .D_STATIC = D_STATIC, .D_OREG = D_OREG, .ACALL = ABL, .ADATA = ADATA, .AEND = AEND, .AFUNCDATA = AFUNCDATA, .AGLOBL = AGLOBL, .AJMP = ABR, .ANOP = ANOP, .APCDATA = APCDATA, .ARET = ARETURN, .ATEXT = ATEXT, .ATYPE = ATYPE, .AUSEFIELD = AUSEFIELD, }; LinkArch linkppc64le = { .name = "ppc64le", .thechar = '9', .endian = LittleEndian, .addstacksplit = addstacksplit, .assemble = span9, .datasize = datasize, .follow = follow, .iscall = iscall, .isdata = isdata, .prg = prg, .progedit = progedit, .settextflag = settextflag, .symtype = symtype, .textflag = textflag, .minlc = 4, .ptrsize = 8, .regsize = 8, .D_ADDR = D_ADDR, .D_AUTO = D_AUTO, .D_BRANCH = D_BRANCH, .D_CONST = D_CONST, .D_EXTERN = D_EXTERN, .D_FCONST = D_FCONST, .D_NONE = D_NONE, .D_PARAM = D_PARAM, .D_SCONST = D_SCONST, .D_STATIC = D_STATIC, .D_OREG = D_OREG, .ACALL = ABL, .ADATA = ADATA, .AEND = AEND, .AFUNCDATA = AFUNCDATA, .AGLOBL = AGLOBL, .AJMP = ABR, .ANOP = ANOP, .APCDATA = APCDATA, .ARET = ARETURN, .ATEXT = ATEXT, .ATYPE = ATYPE, .AUSEFIELD = AUSEFIELD, };