aboutsummaryrefslogtreecommitdiff
path: root/src/liblink/obj5.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/liblink/obj5.c')
-rw-r--r--src/liblink/obj5.c1187
1 files changed, 1187 insertions, 0 deletions
diff --git a/src/liblink/obj5.c b/src/liblink/obj5.c
new file mode 100644
index 0000000000..e9c0b57318
--- /dev/null
+++ b/src/liblink/obj5.c
@@ -0,0 +1,1187 @@
+// Derived from Inferno utils/5c/swt.c
+// http://code.google.com/p/inferno-os/source/browse/utils/5c/swt.c
+//
+// 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-2007 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-2007 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 <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <link.h>
+#include "../cmd/5l/5.out.h"
+#include "../pkg/runtime/stack.h"
+
+static Addr noaddr = {
+ .type = D_NONE,
+ .name = D_NONE,
+ .reg = NREG,
+};
+
+static Prog zprg = {
+ .as = AGOK,
+ .scond = 14,
+ .reg = NREG,
+ .from = {
+ .name = D_NONE,
+ .type = D_NONE,
+ .reg = NREG,
+ },
+ .to = {
+ .name = D_NONE,
+ .type = D_NONE,
+ .reg = NREG,
+ },
+};
+
+static void
+zname(Biobuf *b, LSym *s, int t)
+{
+ BPUTC(b, ANAME); /* as */
+ BPUTC(b, t); /* type */
+ BPUTC(b, s->symid); /* sym */
+ Bwrite(b, s->name, strlen(s->name)+1);
+}
+
+static void
+zfile(Biobuf *b, char *p, int n)
+{
+ BPUTC(b, ANAME);
+ BPUTC(b, D_FILE);
+ BPUTC(b, 1);
+ BPUTC(b, '<');
+ Bwrite(b, p, n);
+ BPUTC(b, 0);
+}
+
+static void
+zaddr(Biobuf *b, Addr *a, int s, int gotype)
+{
+ int32 l;
+ uint64 e;
+ int i;
+ char *n;
+
+ switch(a->type) {
+ case D_STATIC:
+ case D_AUTO:
+ case D_EXTERN:
+ case D_PARAM:
+ // TODO(kaib): remove once everything seems to work
+ sysfatal("We should no longer generate these as types");
+
+ default:
+ BPUTC(b, a->type);
+ BPUTC(b, a->reg);
+ BPUTC(b, s);
+ BPUTC(b, a->name);
+ BPUTC(b, gotype);
+ }
+
+ switch(a->type) {
+ default:
+ print("unknown type %d in zaddr\n", a->type);
+
+ case D_NONE:
+ case D_REG:
+ case D_FREG:
+ case D_PSR:
+ break;
+
+ case D_CONST2:
+ l = a->offset2;
+ BPUTLE4(b, l); // fall through
+ case D_OREG:
+ case D_CONST:
+ case D_SHIFT:
+ case D_STATIC:
+ case D_AUTO:
+ case D_EXTERN:
+ case D_PARAM:
+ l = a->offset;
+ BPUTLE4(b, l);
+ break;
+
+ case D_BRANCH:
+ if(a->offset == 0 || a->u.branch != nil) {
+ if(a->u.branch == nil)
+ sysfatal("unpatched branch %D", a);
+ a->offset = a->u.branch->loc;
+ }
+ l = a->offset;
+ BPUTLE4(b, l);
+ break;
+
+ case D_SCONST:
+ n = a->u.sval;
+ for(i=0; i<NSNAME; i++) {
+ BPUTC(b, *n);
+ n++;
+ }
+ break;
+
+ case D_REGREG:
+ case D_REGREG2:
+ BPUTC(b, a->offset);
+ break;
+
+ case D_FCONST:
+ double2ieee(&e, a->u.dval);
+ BPUTLE4(b, e);
+ BPUTLE4(b, e >> 32);
+ break;
+ }
+}
+
+static void
+zhist(Biobuf *b, int line, vlong offset)
+{
+ Addr a;
+
+ BPUTC(b, AHISTORY);
+ BPUTC(b, C_SCOND_NONE);
+ BPUTC(b, NREG);
+ BPUTLE4(b, line);
+ zaddr(b, &noaddr, 0, 0);
+ a = noaddr;
+ if(offset != 0) {
+ a.offset = offset;
+ a.type = D_CONST;
+ }
+ zaddr(b, &a, 0, 0);
+}
+
+static int
+symtype(Addr *a)
+{
+ return a->name;
+}
+
+static void
+zprog(Link *ctxt, Biobuf *b, Prog *p, int sf, int gf, int st, int gt)
+{
+ USED(ctxt);
+
+ BPUTC(b, p->as);
+ BPUTC(b, p->scond);
+ BPUTC(b, p->reg);
+ BPUTLE4(b, p->lineno);
+ zaddr(b, &p->from, sf, gf);
+ zaddr(b, &p->to, st, gt);
+}
+
+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 Prog*
+prg(void)
+{
+ Prog *p;
+
+ p = emallocz(sizeof(*p));
+ *p = zprg;
+ return p;
+}
+
+static Prog* stacksplit(Link*, Prog*, int32);
+static void initdiv(Link*);
+static void softfloat(Link*, LSym*);
+
+// Prog.mark
+enum
+{
+ FOLL = 1<<0,
+ LABEL = 1<<1,
+ LEAF = 1<<2,
+};
+
+static void
+linkcase(Prog *casep)
+{
+ Prog *p;
+
+ for(p = casep; p != nil; p = p->link){
+ if(p->as == ABCASE) {
+ for(; p != nil && p->as == ABCASE; p = p->link)
+ p->pcrel = casep;
+ break;
+ }
+ }
+}
+
+static void
+nocache(Prog *p)
+{
+ p->optab = 0;
+ p->from.class = 0;
+ p->to.class = 0;
+}
+
+static void
+addstacksplit(Link *ctxt, LSym *cursym)
+{
+ Prog *p, *pl, *q, *q1, *q2;
+ int o;
+ LSym *tlsfallback;
+ int32 autosize, autoffset;
+
+ autosize = 0;
+
+ if(ctxt->symmorestack[0] == nil)
+ ctxt->symmorestack[0] = linklookup(ctxt, "runtime.morestack", 0);
+
+ tlsfallback = linklookup(ctxt, "runtime.read_tls_fallback", 0);
+ ctxt->gmsym = nil;
+ if(ctxt->linkmode == LinkExternal)
+ ctxt->gmsym = linklookup(ctxt, "runtime.tlsgm", 0);
+ q = nil;
+
+ ctxt->cursym = cursym;
+
+ if(cursym->text == nil || cursym->text->link == nil)
+ return;
+
+ softfloat(ctxt, cursym);
+
+ if(ctxt->debugzerostack) {
+ p = cursym->text;
+ autoffset = p->to.offset;
+ if(autoffset < 0)
+ autoffset = 0;
+ if(autoffset && !(p->reg&NOSPLIT)) {
+ // MOVW $4(R13), R1
+ p = appendp(ctxt, p);
+ p->as = AMOVW;
+ p->from.type = D_CONST;
+ p->from.reg = 13;
+ p->from.offset = 4;
+ p->to.type = D_REG;
+ p->to.reg = 1;
+
+ // MOVW $n(R13), R2
+ p = appendp(ctxt, p);
+ p->as = AMOVW;
+ p->from.type = D_CONST;
+ p->from.reg = 13;
+ p->from.offset = 4 + autoffset;
+ p->to.type = D_REG;
+ p->to.reg = 2;
+
+ // MOVW $0, R3
+ p = appendp(ctxt, p);
+ p->as = AMOVW;
+ p->from.type = D_CONST;
+ p->from.offset = 0;
+ p->to.type = D_REG;
+ p->to.reg = 3;
+
+ // L:
+ // MOVW.nil R3, 0(R1) +4
+ // CMP R1, R2
+ // BNE L
+ p = pl = appendp(ctxt, p);
+ p->as = AMOVW;
+ p->from.type = D_REG;
+ p->from.reg = 3;
+ p->to.type = D_OREG;
+ p->to.reg = 1;
+ p->to.offset = 4;
+ p->scond |= C_PBIT;
+
+ p = appendp(ctxt, p);
+ p->as = ACMP;
+ p->from.type = D_REG;
+ p->from.reg = 1;
+ p->reg = 2;
+
+ p = appendp(ctxt, p);
+ p->as = ABNE;
+ p->to.type = D_BRANCH;
+ p->pcond = pl;
+ }
+ }
+
+ /*
+ * find leaf subroutines
+ * strip NOPs
+ * expand RET
+ * expand BECOME pseudo
+ * fixup TLS
+ */
+
+ for(p = cursym->text; p != nil; p = p->link) {
+ switch(p->as) {
+ case ACASE:
+ if(ctxt->flag_shared)
+ linkcase(p);
+ break;
+
+ case ATEXT:
+ p->mark |= LEAF;
+ break;
+
+ case ARET:
+ break;
+
+ case ADIV:
+ case ADIVU:
+ case AMOD:
+ case AMODU:
+ q = p;
+ if(ctxt->sym_div == nil)
+ initdiv(ctxt);
+ cursym->text->mark &= ~LEAF;
+ continue;
+
+ case ANOP:
+ q1 = p->link;
+ q->link = q1; /* q is non-nop */
+ if(q1 != nil)
+ q1->mark |= p->mark;
+ continue;
+
+ case ABL:
+ case ABX:
+ cursym->text->mark &= ~LEAF;
+
+ case ABCASE:
+ case AB:
+
+ case ABEQ:
+ case ABNE:
+ case ABCS:
+ case ABHS:
+ case ABCC:
+ case ABLO:
+ case ABMI:
+ case ABPL:
+ case ABVS:
+ case ABVC:
+ case ABHI:
+ case ABLS:
+ case ABGE:
+ case ABLT:
+ case ABGT:
+ case ABLE:
+ q1 = p->pcond;
+ if(q1 != nil) {
+ while(q1->as == ANOP) {
+ q1 = q1->link;
+ p->pcond = q1;
+ }
+ }
+ break;
+ case AWORD:
+ // Rewrite TLS register fetch: MRC 15, 0, <reg>, C13, C0, 3
+ if((p->to.offset & 0xffff0fff) == 0xee1d0f70) {
+ if(ctxt->headtype == Hopenbsd) {
+ p->as = ARET;
+ } else if(ctxt->goarm < 7) {
+ if(tlsfallback->type != STEXT) {
+ ctxt->diag("runtime·read_tls_fallback not defined");
+ sysfatal("tlsfallback");
+ }
+ // BL runtime.read_tls_fallback(SB)
+ p->as = ABL;
+ p->to.type = D_BRANCH;
+ p->to.sym = tlsfallback;
+ p->pcond = tlsfallback->text;
+ p->to.offset = 0;
+ cursym->text->mark &= ~LEAF;
+ }
+ if(ctxt->linkmode == LinkExternal) {
+ // runtime.tlsgm is relocated with R_ARM_TLS_LE32
+ // and $runtime.tlsgm will contain the TLS offset.
+ //
+ // MOV $runtime.tlsgm+ctxt->tlsoffset(SB), REGTMP
+ // ADD REGTMP, <reg>
+ //
+ // In shared mode, runtime.tlsgm is relocated with
+ // R_ARM_TLS_IE32 and runtime.tlsgm(SB) will point
+ // to the GOT entry containing the TLS offset.
+ //
+ // MOV runtime.tlsgm(SB), REGTMP
+ // ADD REGTMP, <reg>
+ // SUB -ctxt->tlsoffset, <reg>
+ //
+ // The SUB compensates for ctxt->tlsoffset
+ // used in runtime.save_gm and runtime.load_gm.
+ q = p;
+ p = appendp(ctxt, p);
+ p->as = AMOVW;
+ p->scond = 14;
+ p->reg = NREG;
+ if(ctxt->flag_shared) {
+ p->from.type = D_OREG;
+ p->from.offset = 0;
+ } else {
+ p->from.type = D_CONST;
+ p->from.offset = ctxt->tlsoffset;
+ }
+ p->from.sym = ctxt->gmsym;
+ p->from.name = D_EXTERN;
+ p->to.type = D_REG;
+ p->to.reg = REGTMP;
+ p->to.offset = 0;
+
+ p = appendp(ctxt, p);
+ p->as = AADD;
+ p->scond = 14;
+ p->reg = NREG;
+ p->from.type = D_REG;
+ p->from.reg = REGTMP;
+ p->to.type = D_REG;
+ p->to.reg = (q->to.offset & 0xf000) >> 12;
+ p->to.offset = 0;
+
+ if(ctxt->flag_shared) {
+ p = appendp(ctxt, p);
+ p->as = ASUB;
+ p->scond = 14;
+ p->reg = NREG;
+ p->from.type = D_CONST;
+ p->from.offset = -ctxt->tlsoffset;
+ p->to.type = D_REG;
+ p->to.reg = (q->to.offset & 0xf000) >> 12;
+ p->to.offset = 0;
+ }
+ }
+ }
+ }
+ q = p;
+ }
+
+ for(p = cursym->text; p != nil; p = p->link) {
+ o = p->as;
+ switch(o) {
+ case ATEXT:
+ autosize = p->to.offset + 4;
+ if(autosize <= 4)
+ if(cursym->text->mark & LEAF) {
+ p->to.offset = -4;
+ autosize = 0;
+ }
+
+ if(!autosize && !(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;
+ if(!autosize)
+ break;
+ }
+
+ if(!(p->reg & NOSPLIT))
+ p = stacksplit(ctxt, p, autosize); // emit split check
+
+ // MOVW.W R14,$-autosize(SP)
+ p = appendp(ctxt, p);
+ p->as = AMOVW;
+ p->scond |= C_WBIT;
+ p->from.type = D_REG;
+ p->from.reg = REGLINK;
+ p->to.type = D_OREG;
+ p->to.offset = -autosize;
+ p->to.reg = REGSP;
+ p->spadj = autosize;
+
+ if(cursym->text->reg & WRAPPER) {
+ // g->panicwrap += autosize;
+ // MOVW panicwrap_offset(g), R3
+ // ADD $autosize, R3
+ // MOVW R3 panicwrap_offset(g)
+ p = appendp(ctxt, p);
+ p->as = AMOVW;
+ p->from.type = D_OREG;
+ p->from.reg = REGG;
+ p->from.offset = 2*ctxt->arch->ptrsize;
+ p->to.type = D_REG;
+ p->to.reg = 3;
+
+ p = appendp(ctxt, p);
+ p->as = AADD;
+ p->from.type = D_CONST;
+ p->from.offset = autosize;
+ p->to.type = D_REG;
+ p->to.reg = 3;
+
+ p = appendp(ctxt, p);
+ p->as = AMOVW;
+ p->from.type = D_REG;
+ p->from.reg = 3;
+ p->to.type = D_OREG;
+ p->to.reg = REGG;
+ p->to.offset = 2*ctxt->arch->ptrsize;
+ }
+ break;
+
+ case ARET:
+ nocache(p);
+ if(cursym->text->mark & LEAF) {
+ if(!autosize) {
+ p->as = AB;
+ p->from = zprg.from;
+ if(p->to.sym) { // retjmp
+ p->to.type = D_BRANCH;
+ p->pcond = p->to.sym->text;
+ } else {
+ p->to.type = D_OREG;
+ p->to.offset = 0;
+ p->to.reg = REGLINK;
+ }
+ break;
+ }
+ }
+
+ if(cursym->text->reg & WRAPPER) {
+ int scond;
+
+ // Preserve original RET's cond, to allow RET.EQ
+ // in the implementation of reflect.call.
+ scond = p->scond;
+ p->scond = C_SCOND_NONE;
+
+ // g->panicwrap -= autosize;
+ // MOVW panicwrap_offset(g), R3
+ // SUB $autosize, R3
+ // MOVW R3 panicwrap_offset(g)
+ p->as = AMOVW;
+ p->from.type = D_OREG;
+ p->from.reg = REGG;
+ p->from.offset = 2*ctxt->arch->ptrsize;
+ p->to.type = D_REG;
+ p->to.reg = 3;
+ p = appendp(ctxt, p);
+
+ p->as = ASUB;
+ p->from.type = D_CONST;
+ p->from.offset = autosize;
+ p->to.type = D_REG;
+ p->to.reg = 3;
+ p = appendp(ctxt, p);
+
+ p->as = AMOVW;
+ p->from.type = D_REG;
+ p->from.reg = 3;
+ p->to.type = D_OREG;
+ p->to.reg = REGG;
+ p->to.offset = 2*ctxt->arch->ptrsize;
+ p = appendp(ctxt, p);
+
+ p->scond = scond;
+ }
+
+ p->as = AMOVW;
+ p->scond |= C_PBIT;
+ p->from.type = D_OREG;
+ p->from.offset = autosize;
+ p->from.reg = REGSP;
+ p->to.type = D_REG;
+ p->to.reg = REGPC;
+ // If there are instructions following
+ // this ARET, they come from a branch
+ // with the same stackframe, so no spadj.
+
+ if(p->to.sym) { // retjmp
+ p->to.reg = REGLINK;
+ q2 = appendp(ctxt, p);
+ q2->as = AB;
+ q2->to.type = D_BRANCH;
+ q2->to.sym = p->to.sym;
+ q2->pcond = p->to.sym->text;
+ p->to.sym = nil;
+ p = q2;
+ }
+ break;
+
+ case AADD:
+ if(p->from.type == D_CONST && p->from.reg == NREG && p->to.type == D_REG && p->to.reg == REGSP)
+ p->spadj = -p->from.offset;
+ break;
+
+ case ASUB:
+ if(p->from.type == D_CONST && p->from.reg == NREG && p->to.type == D_REG && p->to.reg == REGSP)
+ p->spadj = p->from.offset;
+ break;
+
+ case ADIV:
+ case ADIVU:
+ case AMOD:
+ case AMODU:
+ if(ctxt->debugdivmod)
+ break;
+ if(p->from.type != D_REG)
+ break;
+ if(p->to.type != D_REG)
+ break;
+ q1 = p;
+
+ /* MOV a,4(SP) */
+ p = appendp(ctxt, p);
+ p->as = AMOVW;
+ p->lineno = q1->lineno;
+ p->from.type = D_REG;
+ p->from.reg = q1->from.reg;
+ p->to.type = D_OREG;
+ p->to.reg = REGSP;
+ p->to.offset = 4;
+
+ /* MOV b,REGTMP */
+ p = appendp(ctxt, p);
+ p->as = AMOVW;
+ p->lineno = q1->lineno;
+ p->from.type = D_REG;
+ p->from.reg = q1->reg;
+ if(q1->reg == NREG)
+ p->from.reg = q1->to.reg;
+ p->to.type = D_REG;
+ p->to.reg = REGTMP;
+ p->to.offset = 0;
+
+ /* CALL appropriate */
+ p = appendp(ctxt, p);
+ p->as = ABL;
+ p->lineno = q1->lineno;
+ p->to.type = D_BRANCH;
+ p->pcond = p;
+ switch(o) {
+ case ADIV:
+ p->to.sym = ctxt->sym_div;
+ break;
+ case ADIVU:
+ p->to.sym = ctxt->sym_divu;
+ break;
+ case AMOD:
+ p->to.sym = ctxt->sym_mod;
+ break;
+ case AMODU:
+ p->to.sym = ctxt->sym_modu;
+ break;
+ }
+
+ /* MOV REGTMP, b */
+ p = appendp(ctxt, p);
+ p->as = AMOVW;
+ p->lineno = q1->lineno;
+ p->from.type = D_REG;
+ p->from.reg = REGTMP;
+ p->from.offset = 0;
+ p->to.type = D_REG;
+ p->to.reg = q1->to.reg;
+
+ /* ADD $8,SP */
+ p = appendp(ctxt, p);
+ p->as = AADD;
+ p->lineno = q1->lineno;
+ p->from.type = D_CONST;
+ p->from.reg = NREG;
+ p->from.offset = 8;
+ p->reg = NREG;
+ p->to.type = D_REG;
+ p->to.reg = REGSP;
+ p->spadj = -8;
+
+ /* Keep saved LR at 0(SP) after SP change. */
+ /* MOVW 0(SP), REGTMP; MOVW REGTMP, -8!(SP) */
+ /* TODO: Remove SP adjustments; see issue 6699. */
+ q1->as = AMOVW;
+ q1->from.type = D_OREG;
+ q1->from.reg = REGSP;
+ q1->from.offset = 0;
+ q1->reg = NREG;
+ q1->to.type = D_REG;
+ q1->to.reg = REGTMP;
+
+ /* SUB $8,SP */
+ q1 = appendp(ctxt, q1);
+ q1->as = AMOVW;
+ q1->from.type = D_REG;
+ q1->from.reg = REGTMP;
+ q1->reg = NREG;
+ q1->to.type = D_OREG;
+ q1->to.reg = REGSP;
+ q1->to.offset = -8;
+ q1->scond |= C_WBIT;
+ q1->spadj = 8;
+
+ break;
+ case AMOVW:
+ if((p->scond & C_WBIT) && p->to.type == D_OREG && p->to.reg == REGSP)
+ p->spadj = -p->to.offset;
+ if((p->scond & C_PBIT) && p->from.type == D_OREG && p->from.reg == REGSP && p->to.reg != REGPC)
+ p->spadj = -p->from.offset;
+ if(p->from.type == D_CONST && p->from.reg == REGSP && p->to.type == D_REG && p->to.reg == REGSP)
+ p->spadj = -p->from.offset;
+ break;
+ }
+ }
+}
+
+static void
+softfloat(Link *ctxt, LSym *cursym)
+{
+ Prog *p, *next, *psfloat;
+ LSym *symsfloat;
+ int wasfloat;
+
+ if(!ctxt->debugfloat)
+ return;
+
+ symsfloat = linklookup(ctxt, "_sfloat", 0);
+ psfloat = nil;
+ if(symsfloat->type == STEXT)
+ psfloat = symsfloat->text;
+
+ wasfloat = 0;
+ for(p = cursym->text; p != nil; p = p->link)
+ if(p->pcond != nil)
+ p->pcond->mark |= LABEL;
+ for(p = cursym->text; p != nil; p = p->link) {
+ switch(p->as) {
+ case AMOVW:
+ if(p->to.type == D_FREG || p->from.type == D_FREG)
+ goto soft;
+ goto notsoft;
+
+ case AMOVWD:
+ case AMOVWF:
+ case AMOVDW:
+ case AMOVFW:
+ case AMOVFD:
+ case AMOVDF:
+ case AMOVF:
+ case AMOVD:
+
+ case ACMPF:
+ case ACMPD:
+ case AADDF:
+ case AADDD:
+ case ASUBF:
+ case ASUBD:
+ case AMULF:
+ case AMULD:
+ case ADIVF:
+ case ADIVD:
+ case ASQRTF:
+ case ASQRTD:
+ case AABSF:
+ case AABSD:
+ goto soft;
+
+ default:
+ goto notsoft;
+
+ soft:
+ if (psfloat == nil)
+ ctxt->diag("floats used with _sfloat not defined");
+ if (!wasfloat || (p->mark&LABEL)) {
+ next = ctxt->arch->prg();
+ *next = *p;
+
+ // BL _sfloat(SB)
+ *p = zprg;
+ p->link = next;
+ p->as = ABL;
+ p->to.type = D_BRANCH;
+ p->to.sym = symsfloat;
+ p->pcond = psfloat;
+ p->lineno = next->lineno;
+
+ p = next;
+ wasfloat = 1;
+ }
+ break;
+
+ notsoft:
+ wasfloat = 0;
+ }
+ }
+}
+
+static Prog*
+stacksplit(Link *ctxt, Prog *p, int32 framesize)
+{
+ int32 arg;
+
+ // MOVW g_stackguard(g), R1
+ p = appendp(ctxt, p);
+ p->as = AMOVW;
+ p->from.type = D_OREG;
+ p->from.reg = REGG;
+ p->to.type = D_REG;
+ p->to.reg = 1;
+
+ if(framesize <= StackSmall) {
+ // small stack: SP < stackguard
+ // CMP stackguard, SP
+ p = appendp(ctxt, p);
+ p->as = ACMP;
+ p->from.type = D_REG;
+ p->from.reg = 1;
+ p->reg = REGSP;
+ } else if(framesize <= StackBig) {
+ // large stack: SP-framesize < stackguard-StackSmall
+ // MOVW $-framesize(SP), R2
+ // CMP stackguard, R2
+ p = appendp(ctxt, p);
+ p->as = AMOVW;
+ p->from.type = D_CONST;
+ p->from.reg = REGSP;
+ p->from.offset = -framesize;
+ p->to.type = D_REG;
+ p->to.reg = 2;
+
+ p = appendp(ctxt, p);
+ p->as = ACMP;
+ p->from.type = D_REG;
+ p->from.reg = 1;
+ p->reg = 2;
+ } 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.
+ // CMP $StackPreempt, R1
+ // MOVW.NE $StackGuard(SP), R2
+ // SUB.NE R1, R2
+ // MOVW.NE $(framesize+(StackGuard-StackSmall)), R3
+ // CMP.NE R3, R2
+ p = appendp(ctxt, p);
+ p->as = ACMP;
+ p->from.type = D_CONST;
+ p->from.offset = (uint32)StackPreempt;
+ p->reg = 1;
+
+ p = appendp(ctxt, p);
+ p->as = AMOVW;
+ p->from.type = D_CONST;
+ p->from.reg = REGSP;
+ p->from.offset = StackGuard;
+ p->to.type = D_REG;
+ p->to.reg = 2;
+ p->scond = C_SCOND_NE;
+
+ p = appendp(ctxt, p);
+ p->as = ASUB;
+ p->from.type = D_REG;
+ p->from.reg = 1;
+ p->to.type = D_REG;
+ p->to.reg = 2;
+ p->scond = C_SCOND_NE;
+
+ p = appendp(ctxt, p);
+ p->as = AMOVW;
+ p->from.type = D_CONST;
+ p->from.offset = framesize + (StackGuard - StackSmall);
+ p->to.type = D_REG;
+ p->to.reg = 3;
+ p->scond = C_SCOND_NE;
+
+ p = appendp(ctxt, p);
+ p->as = ACMP;
+ p->from.type = D_REG;
+ p->from.reg = 3;
+ p->reg = 2;
+ p->scond = C_SCOND_NE;
+ }
+
+ // MOVW.LS $framesize, R1
+ p = appendp(ctxt, p);
+ p->as = AMOVW;
+ p->scond = C_SCOND_LS;
+ p->from.type = D_CONST;
+ p->from.offset = framesize;
+ p->to.type = D_REG;
+ p->to.reg = 1;
+
+ // MOVW.LS $args, R2
+ p = appendp(ctxt, p);
+ p->as = AMOVW;
+ p->scond = C_SCOND_LS;
+ p->from.type = D_CONST;
+ arg = ctxt->cursym->text->to.offset2;
+ if(arg == 1) // special marker for known 0
+ arg = 0;
+ if(arg&3)
+ ctxt->diag("misaligned argument size in stack split");
+ p->from.offset = arg;
+ p->to.type = D_REG;
+ p->to.reg = 2;
+
+ // MOVW.LS R14, R3
+ p = appendp(ctxt, p);
+ p->as = AMOVW;
+ p->scond = C_SCOND_LS;
+ p->from.type = D_REG;
+ p->from.reg = REGLINK;
+ p->to.type = D_REG;
+ p->to.reg = 3;
+
+ // BL.LS runtime.morestack(SB) // modifies LR, returns with LO still asserted
+ p = appendp(ctxt, p);
+ p->as = ABL;
+ p->scond = C_SCOND_LS;
+ p->to.type = D_BRANCH;
+ p->to.sym = ctxt->symmorestack[0];
+
+ // BLS start
+ p = appendp(ctxt, p);
+ p->as = ABLS;
+ p->to.type = D_BRANCH;
+ p->pcond = ctxt->cursym->text->link;
+
+ return p;
+}
+
+static void
+initdiv(Link *ctxt)
+{
+ if(ctxt->sym_div != nil)
+ return;
+ ctxt->sym_div = linklookup(ctxt, "_div", 0);
+ ctxt->sym_divu = linklookup(ctxt, "_divu", 0);
+ ctxt->sym_mod = linklookup(ctxt, "_mod", 0);
+ ctxt->sym_modu = linklookup(ctxt, "_modu", 0);
+}
+
+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 ABCS: return ABCC;
+ case ABHS: return ABLO;
+ case ABCC: return ABCS;
+ case ABLO: return ABHS;
+ case ABMI: return ABPL;
+ case ABPL: return ABMI;
+ case ABVS: return ABVC;
+ case ABVC: return ABVS;
+ case ABHI: return ABLS;
+ case ABLS: return ABHI;
+ case ABGE: return ABLT;
+ case ABLT: return ABGE;
+ case ABGT: return ABLE;
+ case ABLE: return ABGT;
+ }
+ sysfatal("unknown relation: %s", anames5[a]);
+ return 0;
+}
+
+static void
+xfol(Link *ctxt, Prog *p, Prog **last)
+{
+ Prog *q, *r;
+ int a, i;
+
+loop:
+ if(p == nil)
+ return;
+ a = p->as;
+ if(a == AB) {
+ q = p->pcond;
+ if(q != nil && q->as != ATEXT) {
+ 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 == nil)
+ break;
+ a = q->as;
+ if(a == ANOP) {
+ i--;
+ continue;
+ }
+ if(a == AB || (a == ARET && q->scond == 14) || a == ARFE || a == AUNDEF)
+ goto copy;
+ if(q->pcond == nil || (q->pcond->mark&FOLL))
+ continue;
+ if(a != ABEQ && a != ABNE)
+ continue;
+ copy:
+ for(;;) {
+ r = ctxt->arch->prg();
+ *r = *p;
+ if(!(r->mark&FOLL))
+ print("can't 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 == AB || (a == ARET && q->scond == 14) || a == ARFE || a == AUNDEF)
+ return;
+ r->as = ABNE;
+ if(a == ABNE)
+ r->as = ABEQ;
+ r->pcond = p->link;
+ r->link = p->pcond;
+ if(!(r->link->mark&FOLL))
+ xfol(ctxt, r->link, last);
+ if(!(r->pcond->mark&FOLL))
+ print("can't happen 2\n");
+ return;
+ }
+ }
+ a = AB;
+ 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 == AB || (a == ARET && p->scond == 14) || a == ARFE || a == AUNDEF){
+ return;
+ }
+ if(p->pcond != nil)
+ if(a != ABL && a != ABX && p->link != nil) {
+ q = brchain(ctxt, p->link);
+ if(a != ATEXT && a != ABCASE)
+ if(q != nil && (q->mark&FOLL)) {
+ p->as = relinv(a);
+ p->link = p->pcond;
+ p->pcond = q;
+ }
+ xfol(ctxt, p->link, last);
+ q = brchain(ctxt, p->pcond);
+ if(q == nil)
+ q = p->pcond;
+ if(q->mark&FOLL) {
+ p->pcond = q;
+ return;
+ }
+ p = q;
+ goto loop;
+ }
+ p = p->link;
+ goto loop;
+}
+
+LinkArch linkarm = {
+ .name = "arm",
+
+ .addstacksplit = addstacksplit,
+ .assemble = span5,
+ .datasize = datasize,
+ .follow = follow,
+ .iscall = iscall,
+ .isdata = isdata,
+ .ldobj = ldobj5,
+ .nopout = nopout5,
+ .prg = prg,
+ .settextflag = settextflag,
+ .symtype = symtype,
+ .textflag = textflag,
+ .zfile = zfile,
+ .zhist = zhist,
+ .zname = zname,
+ .zprog = zprog,
+
+ .minlc = 4,
+ .ptrsize = 4,
+
+ .D_ADDR = D_ADDR,
+ .D_BRANCH = D_BRANCH,
+ .D_CONST = D_CONST,
+ .D_EXTERN = D_EXTERN,
+ .D_FCONST = D_FCONST,
+ .D_NONE = D_NONE,
+ .D_PCREL = D_PCREL,
+ .D_SCONST = D_SCONST,
+ .D_SIZE = D_SIZE,
+
+ .ACALL = ABL,
+ .AFUNCDATA = AFUNCDATA,
+ .AJMP = AB,
+ .ANOP = ANOP,
+ .APCDATA = APCDATA,
+ .ARET = ARET,
+ .ATEXT = ATEXT,
+ .AUSEFIELD = AUSEFIELD,
+};