| // Inferno utils/5l/thumb.c |
| // http://code.google.com/p/inferno-os/source/browse/utils/5l/thumb.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 "l.h" |
| |
| static int32 thumboprr(int); |
| static int32 thumboprrr(int, int); |
| static int32 thumbopirr(int , int); |
| static int32 thumbopri(int); |
| static int32 thumbophh(int); |
| static int32 thumbopbra(int); |
| static int32 thumbopmv(int, int); |
| static void lowreg(Prog *, int); |
| static void mult(Prog *, int, int); |
| static void numr(Prog *, int, int, int); |
| static void regis(Prog *, int, int, int); |
| static void dis(int, int); |
| |
| // build a constant using neg, add and shift - only worth it if < 6 bytes */ |
| static int |
| immbuildcon(int c, Prog *p) |
| { |
| int n = 0; |
| |
| USED(p); |
| if(c >= 0 && c <= 255) |
| return 0; // mv |
| if(c >= -255 && c < 0) // mv, neg |
| return 1; |
| if(c >= 256 && c <= 510) // mv, add |
| return 1; |
| if(c < 0) |
| return 0; |
| while(!(c & 1)){ |
| n++; |
| c >>= 1; |
| } |
| if(c >= 0 && c <= 255) // mv, lsl |
| return 1; |
| return 0; |
| } |
| |
| // positive 5 bit offset from register - O(R) |
| // positive 8 bit offset from register - mov O, R then [R, R] |
| // otherwise O goes in literal pool - mov O1(PC), R then [R, R] |
| static int |
| immoreg(int off, Prog *p) |
| { |
| int v = 1; |
| int as = p->as; |
| |
| if(off < 0) |
| return C_GOREG; |
| if(as == AMOVW) |
| v = 4; |
| else if(as == AMOVH || as == AMOVHU) |
| v = 2; |
| else if(as == AMOVB || as == AMOVBU) |
| v = 1; |
| else |
| diag("bad op in immoreg"); |
| if(off/v <= 31) |
| return C_SOREG; |
| if(off <= 255) |
| return C_LOREG; |
| return C_GOREG; |
| } |
| |
| // positive 8 bit - mov O, R then 0(R) |
| // otherwise O goes in literal pool - mov O1(PC), R then 0(R) |
| static int |
| immacon(int off, Prog *p, int t1, int t2) |
| { |
| USED(p); |
| if(off < 0) |
| return t2; |
| if(off <= 255) |
| return t1; |
| return t2; |
| } |
| |
| // unsigned 8 bit in words |
| static int |
| immauto(int off, Prog *p) |
| { |
| if(p->as != AMOVW) |
| diag("bad op in immauto"); |
| mult(p, off, 4); |
| if(off >= 0 && off <= 1020) |
| return C_SAUTO; |
| return C_LAUTO; |
| } |
| |
| static int |
| immsmall(int off, Prog *p, int t1, int t2, int t3) |
| { |
| USED(p); |
| if(off >= 0 && off <= 7) |
| return t1; |
| if(off >= 0 && off <= 255) |
| return t2; |
| return t3; |
| } |
| |
| static int |
| immcon(int off, Prog *p) |
| { |
| int as = p->as; |
| |
| if(as == ASLL || as == ASRL || as == ASRA) |
| return C_SCON; |
| if(p->to.type == D_REG && p->to.reg == REGSP){ |
| if(as == AADD || as == ASUB){ |
| if(off >= 0 && off <= 508) |
| return C_SCON; |
| if(as == ASUB){ |
| p->as = AADD; |
| p->from.offset = -p->from.offset; |
| } |
| return C_LCON; |
| } |
| diag("unknown type in immcon"); |
| } |
| if(as == AADD || as == ASUB){ |
| if(p->reg != NREG) |
| return immsmall(off, p, C_SCON, C_LCON, C_GCON); |
| return immacon(off, p, C_SCON, C_LCON); |
| } |
| if(as == AMOVW && p->from.type == D_CONST && p->to.type == D_REG && immbuildcon(off, p)) |
| return C_BCON; |
| if(as == ACMP && p->from.type == D_CONST && immbuildcon(off, p)) |
| return C_BCON; |
| if(as == ACMP || as == AMOVW) |
| return immacon(off, p, C_SCON, C_LCON); |
| return C_LCON; |
| } |
| |
| int |
| thumbaclass(Adr *a, Prog *p) |
| { |
| Sym *s; |
| int t; |
| |
| switch(a->type) { |
| case D_NONE: |
| return C_NONE; |
| case D_REG: |
| if(a->reg == REGSP) |
| return C_SP; |
| if(a->reg == REGPC) |
| return C_PC; |
| if(a->reg >= 8) |
| return C_HREG; |
| return C_REG; |
| case D_SHIFT: |
| diag("D_SHIFT in thumbaclass"); |
| return C_SHIFT; |
| case D_FREG: |
| diag("D_FREG in thumbaclass"); |
| return C_FREG; |
| case D_FPCR: |
| diag("D_FPCR in thumbaclass"); |
| return C_FCR; |
| case D_OREG: |
| switch(a->name) { |
| case D_EXTERN: |
| case D_STATIC: |
| if(a->sym == 0 || a->sym->name == 0) { |
| print("null sym external\n"); |
| print("%D\n", a); |
| return C_GOK; |
| } |
| t = a->sym->type; |
| if(t == 0 || t == SXREF) { |
| diag("undefined external: %s in %s\n", |
| a->sym->name, TNAME); |
| a->sym->type = SDATA; |
| } |
| instoffset = a->sym->value + a->offset; |
| return C_ADDR; /* INITDAT unknown at this stage */ |
| case D_AUTO: |
| instoffset = autosize + a->offset; |
| return immauto(instoffset, p); |
| case D_PARAM: |
| instoffset = autosize + a->offset + 4L; |
| // print("D_PARAM %s %d+%d+%d = %d\n", a->sym != S ? a->sym->name : "noname", autosize, a->offset, 4, autosize+a->offset+4); |
| return immauto(instoffset, p); |
| case D_NONE: |
| instoffset = a->offset; |
| if(a->reg == REGSP) |
| return immauto(instoffset, p); |
| else |
| return immoreg(instoffset, p); |
| } |
| return C_GOK; |
| case D_PSR: |
| diag("D_PSR in thumbaclass"); |
| return C_PSR; |
| case D_OCONST: |
| switch(a->name) { |
| case D_EXTERN: |
| case D_STATIC: |
| s = a->sym; |
| t = s->type; |
| if(t == 0 || t == SXREF) { |
| diag("undefined external: %s in %s\n", |
| s->name, TNAME); |
| s->type = SDATA; |
| } |
| instoffset = s->value + a->offset; |
| if(s->type == STEXT){ |
| instoffset = s->value + a->offset; |
| #ifdef CALLEEBX |
| instoffset += fnpinc(s); |
| #else |
| if(s->thumb) |
| instoffset++; // T bit |
| #endif |
| return C_LCON; |
| } |
| return C_LCON; /* INITDAT unknown at this stage */ |
| // return immcon(instoffset, p); |
| } |
| return C_GOK; |
| case D_FCONST: |
| diag("D_FCONST in thumaclass"); |
| return C_LFCON; |
| case D_CONST: |
| switch(a->name) { |
| case D_NONE: |
| instoffset = a->offset; |
| if(a->reg != NREG) |
| goto aconsize; |
| return immcon(instoffset, p); |
| case D_EXTERN: |
| case D_STATIC: |
| s = a->sym; |
| if(s == S) |
| break; |
| t = s->type; |
| switch(t) { |
| case 0: |
| case SXREF: |
| diag("undefined external: %s in %s\n", |
| s->name, TNAME); |
| s->type = SDATA; |
| break; |
| case SCONST: |
| case STEXT: |
| instoffset = s->value + a->offset; |
| #ifdef CALLEEBX |
| instoffset += fnpinc(s); |
| #else |
| if(s->thumb) |
| instoffset++; // T bit |
| #endif |
| return C_LCON; |
| } |
| instoffset = s->value + a->offset; |
| return C_LCON; /* INITDAT unknown at this stage */ |
| // return immcon(instoffset, p); |
| case D_AUTO: |
| instoffset = autosize + a->offset; |
| goto aconsize; |
| case D_PARAM: |
| instoffset = autosize + a->offset + 4L; |
| aconsize: |
| if(p->from.reg == REGSP || p->from.reg == NREG) |
| return instoffset >= 0 && instoffset < 1024 ? C_SACON : C_GACON; |
| else if(p->from.reg == p->to.reg) |
| return immacon(instoffset, p, C_SACON, C_GACON); |
| return immsmall(instoffset, p, C_SACON, C_LACON, C_GACON); |
| } |
| return C_GOK; |
| case D_BRANCH: { |
| int v, va; |
| |
| p->align = 0; |
| v = -4; |
| va = 0; |
| if(p->cond != P){ |
| v = (p->cond->pc - p->pc) - 4; |
| va = p->cond->pc; |
| } |
| instoffset = v; |
| if(p->as == AB){ |
| if(v >= -2048 && v <= 2046) |
| return C_SBRA; |
| p->align = 4; |
| instoffset = va; |
| return C_LBRA; |
| } |
| if(p->as == ABL){ |
| #ifdef CALLEEBX |
| int e; |
| |
| if((e = fninc(p->to.sym))) { |
| v += e; |
| va += e; |
| instoffset += e; |
| } |
| #endif |
| if(v >= -4194304 && v <= 4194302) |
| return C_SBRA; |
| p->align = 2; |
| instoffset = va; |
| return C_LBRA; |
| } |
| if(p->as == ABX){ |
| v = va; |
| if(v >= 0 && v <= 255) |
| return C_SBRA; |
| p->align = 2; |
| instoffset = va; |
| return C_LBRA; |
| } |
| if(v >= -256 && v <= 254) |
| return C_SBRA; |
| if(v >= -(2048-2) && v <= (2046+2)) |
| return C_LBRA; |
| p->align = 2; |
| instoffset = va; |
| return C_GBRA; |
| } |
| } |
| return C_GOK; |
| } |
| |
| // as a1 a2 a3 type size param lit vers |
| Optab thumboptab[] = |
| { |
| { ATEXT, C_ADDR, C_NONE, C_LCON, 0, 0, 0 }, |
| { ATEXT, C_ADDR, C_REG, C_LCON, 0, 0, 0 }, |
| { AMVN, C_REG, C_NONE, C_REG, 1, 2, 0 }, |
| { ASRL, C_REG, C_NONE, C_REG, 1, 2, 0 }, |
| { ACMP, C_REG, C_REG, C_NONE, 1, 2, 0 }, |
| { ACMN, C_REG, C_REG, C_NONE, 1, 2, 0 }, |
| { AADD, C_REG, C_REG, C_REG, 2, 2, 0 }, |
| { AADD, C_REG, C_NONE, C_REG, 2, 2, 0 }, |
| { AADD, C_SCON, C_REG, C_REG, 3, 2, 0 }, |
| { AADD, C_LCON, C_REG, C_REG, 49, 4, 0 }, |
| { AADD, C_GCON, C_REG, C_REG, 36, 4, 0, LFROM }, |
| // { AADD, C_LCON, C_NONE, C_REG, 3, 2, 0, LFROM }, |
| { ASRL, C_SCON, C_REG, C_REG, 4, 2, 0 }, |
| { ASRL, C_SCON, C_NONE, C_REG, 4, 2, 0 }, |
| { AADD, C_SCON, C_NONE, C_REG, 5, 2, 0 }, |
| { AADD, C_LCON, C_NONE, C_REG, 37, 4, 0, LFROM }, |
| { ACMP, C_SCON, C_REG, C_NONE, 5, 2, 0 }, |
| { ACMP, C_BCON, C_REG, C_NONE, 48, 6, 0 }, |
| { ACMP, C_LCON, C_REG, C_NONE, 39, 4, 0, LFROM }, |
| { AMOVW, C_SCON, C_NONE, C_REG, 5, 2, 0 }, |
| { AMOVW, C_BCON, C_NONE, C_REG, 47, 4, 0 }, |
| { AMOVW, C_LCON, C_NONE, C_REG, 38, 2, 0, LFROM }, |
| // { AADD, C_LCON, C_PC, C_REG, 6, 2, 0, LFROM }, |
| // { AADD, C_LCON, C_SP, C_REG, 6, 2, 0, LFROM }, |
| { AADD, C_SCON, C_NONE, C_SP, 7, 2, 0 }, |
| { AADD, C_LCON, C_NONE, C_SP, 40, 4, 0, LFROM }, |
| { AADD, C_REG, C_NONE, C_HREG, 8, 2, 0 }, |
| { AADD, C_HREG, C_NONE, C_REG, 8, 2, 0 }, |
| { AADD, C_HREG, C_NONE, C_HREG, 8, 2, 0 }, |
| { AMOVW, C_REG, C_NONE, C_HREG, 8, 2, 0 }, |
| { AMOVW, C_HREG, C_NONE, C_REG, 8, 2, 0 }, |
| { AMOVW, C_HREG, C_NONE, C_HREG, 8, 2, 0 }, |
| { ACMP, C_REG, C_HREG, C_NONE, 8, 2, 0 }, |
| { ACMP, C_HREG, C_REG, C_NONE, 8, 2, 0 }, |
| { ACMP, C_HREG, C_HREG, C_NONE, 8, 2, 0 }, |
| { AB, C_NONE, C_NONE, C_SBRA, 9, 2, 0, LPOOL }, |
| { ABEQ, C_NONE, C_NONE, C_SBRA, 10, 2, 0 }, |
| { ABL, C_NONE, C_NONE, C_SBRA, 11, 4, 0 }, |
| { ABX, C_NONE, C_NONE, C_SBRA, 12, 10, 0 }, |
| { AB, C_NONE, C_NONE, C_LBRA, 41, 8, 0, LPOOL }, |
| { ABEQ, C_NONE, C_NONE, C_LBRA, 46, 4, 0 }, |
| { ABL, C_NONE, C_NONE, C_LBRA, 43, 14, 0 }, |
| { ABX, C_NONE, C_NONE, C_LBRA, 44, 14, 0 }, |
| { ABEQ, C_NONE, C_NONE, C_GBRA, 42, 10, 0 }, |
| // { AB, C_NONE, C_NONE, C_SOREG, 13, 0, 0 }, |
| // { ABL, C_NONE, C_NONE, C_SOREG, 14, 0, 0 }, |
| { ABL, C_NONE, C_NONE, C_REG, 51, 4, 0 }, |
| { ABX, C_NONE, C_NONE, C_REG, 15, 8, 0 }, |
| { ABX, C_NONE, C_NONE, C_HREG, 15, 8, 0 }, |
| { ABXRET, C_NONE, C_NONE, C_REG, 45, 2, 0 }, |
| { ABXRET, C_NONE, C_NONE, C_HREG, 45, 2, 0 }, |
| { ASWI, C_NONE, C_NONE, C_LCON, 16, 2, 0 }, |
| { AWORD, C_NONE, C_NONE, C_LCON, 17, 4, 0 }, |
| { AWORD, C_NONE, C_NONE, C_GCON, 17, 4, 0 }, |
| { AWORD, C_NONE, C_NONE, C_ADDR, 17, 4, 0 }, |
| { ADWORD, C_LCON, C_NONE, C_LCON, 50, 8, 0 }, |
| { AMOVW, C_SAUTO, C_NONE, C_REG, 18, 2, REGSP }, |
| { AMOVW, C_LAUTO, C_NONE, C_REG, 33, 6, 0, LFROM }, |
| // { AMOVW, C_OFFPC, C_NONE, C_REG, 18, 2, REGPC, LFROM }, |
| { AMOVW, C_SOREG, C_NONE, C_REG, 19, 2, 0 }, |
| { AMOVHU, C_SOREG, C_NONE, C_REG, 19, 2, 0 }, |
| { AMOVBU, C_SOREG, C_NONE, C_REG, 19, 2, 0 }, |
| { AMOVW, C_REG, C_NONE, C_SAUTO, 20, 2, 0 }, |
| { AMOVW, C_REG, C_NONE, C_LAUTO, 34, 6, 0, LTO }, |
| { AMOVW, C_REG, C_NONE, C_SOREG, 21, 2, 0 }, |
| { AMOVH, C_REG, C_NONE, C_SOREG, 21, 2, 0 }, |
| { AMOVB, C_REG, C_NONE, C_SOREG, 21, 2, 0 }, |
| { AMOVHU, C_REG, C_NONE, C_SOREG, 21, 2, 0 }, |
| { AMOVBU, C_REG, C_NONE, C_SOREG, 21, 2, 0 }, |
| { AMOVW, C_REG, C_NONE, C_REG, 22, 2, 0 }, |
| { AMOVB, C_REG, C_NONE, C_REG, 23, 4, 0 }, |
| { AMOVH, C_REG, C_NONE, C_REG, 23, 4, 0 }, |
| { AMOVBU, C_REG, C_NONE, C_REG, 23, 4, 0 }, |
| { AMOVHU, C_REG, C_NONE, C_REG, 23, 4, 0 }, |
| { AMOVH, C_SOREG, C_NONE, C_REG, 24, 4, 0 }, |
| { AMOVB, C_SOREG, C_NONE, C_REG, 24, 4, 0 }, |
| { AMOVW, C_SACON, C_NONE, C_REG, 25, 2, 0 }, |
| { AMOVW, C_LACON, C_NONE, C_REG, 35, 4, 0 }, |
| { AMOVW, C_GACON, C_NONE, C_REG, 35, 4, 0, LFROM }, |
| { AMOVM, C_LCON, C_NONE, C_REG, 26, 2, 0 }, |
| { AMOVM, C_REG, C_NONE, C_LCON, 27, 2, 0 }, |
| { AMOVW, C_LOREG, C_NONE, C_REG, 28, 4, 0 }, |
| { AMOVH, C_LOREG, C_NONE, C_REG, 28, 4, 0 }, |
| { AMOVB, C_LOREG, C_NONE, C_REG, 28, 4, 0 }, |
| { AMOVHU, C_LOREG, C_NONE, C_REG, 28, 4, 0 }, |
| { AMOVBU, C_LOREG, C_NONE, C_REG, 28, 4, 0 }, |
| { AMOVW, C_REG, C_NONE, C_LOREG, 29, 4, 0 }, |
| { AMOVH, C_REG, C_NONE, C_LOREG, 29, 4, 0 }, |
| { AMOVB, C_REG, C_NONE, C_LOREG, 29, 4, 0 }, |
| { AMOVHU, C_REG, C_NONE, C_LOREG, 29, 4, 0 }, |
| { AMOVBU, C_REG, C_NONE, C_LOREG, 29, 4, 0 }, |
| { AMOVW, C_GOREG, C_NONE, C_REG, 28, 4, 0, LFROM }, |
| { AMOVH, C_GOREG, C_NONE, C_REG, 28, 4, 0, LFROM }, |
| { AMOVB, C_GOREG, C_NONE, C_REG, 28, 4, 0, LFROM }, |
| { AMOVHU, C_GOREG, C_NONE, C_REG, 28, 4, 0, LFROM }, |
| { AMOVBU, C_GOREG, C_NONE, C_REG, 28, 4, 0, LFROM }, |
| { AMOVW, C_REG, C_NONE, C_GOREG, 29, 4, 0, LTO }, |
| { AMOVH, C_REG, C_NONE, C_GOREG, 29, 4, 0, LTO }, |
| { AMOVB, C_REG, C_NONE, C_GOREG, 29, 4, 0, LTO }, |
| { AMOVHU, C_REG, C_NONE, C_GOREG, 29, 4, 0, LTO }, |
| { AMOVBU, C_REG, C_NONE, C_GOREG, 29, 4, 0, LTO }, |
| { AMOVW, C_ADDR, C_NONE, C_REG, 30, 4, 0, LFROM }, |
| { AMOVH, C_ADDR, C_NONE, C_REG, 32, 6, 0, LFROM }, |
| { AMOVB, C_ADDR, C_NONE, C_REG, 32, 6, 0, LFROM }, |
| { AMOVHU, C_ADDR, C_NONE, C_REG, 30, 4, 0, LFROM }, |
| { AMOVBU, C_ADDR, C_NONE, C_REG, 30, 4, 0, LFROM }, |
| { AMOVW, C_REG, C_NONE, C_ADDR, 31, 4, 0, LTO }, |
| { AMOVH, C_REG, C_NONE, C_ADDR, 31, 4, 0, LTO }, |
| { AMOVB, C_REG, C_NONE, C_ADDR, 31, 4, 0, LTO }, |
| { AMOVHU, C_REG, C_NONE, C_ADDR, 31, 4, 0, LTO }, |
| { AMOVBU, C_REG, C_NONE, C_ADDR, 31, 4, 0, LTO }, |
| |
| { AXXX, C_NONE, C_NONE, C_NONE, 0, 2, 0 }, |
| }; |
| |
| #define OPCNTSZ 52 |
| int opcount[OPCNTSZ]; |
| |
| // is this too pessimistic ? |
| int |
| brextra(Prog *p) |
| { |
| int c; |
| |
| // +2 is for padding |
| if(p->as == ATEXT) |
| return 0-0+2; |
| if(!isbranch(p)) |
| diag("bad op in brextra()"); |
| c = thumbaclass(&p->to, p); |
| switch(p->as){ |
| case AB: |
| if(c != C_SBRA) |
| return 0; |
| return 8-2+2; |
| case ABL: |
| if(c != C_SBRA) |
| return 0; |
| return 14-4+2; |
| case ABX: |
| if(c == C_REG || c == C_HREG) |
| return 0; |
| #ifdef CALLEEBX |
| diag("ABX $I in brextra"); |
| #endif |
| if(c != C_SBRA) |
| return 0; |
| return 14-10+2; |
| default: |
| if(c == C_GBRA) |
| return 0; |
| if(c == C_LBRA) |
| return 10-4+2; |
| return 10-2+2; |
| } |
| return 0; |
| } |
| |
| #define high(r) ((r)>=8) |
| |
| static int32 |
| mv(Prog *p, int r, int off) |
| { |
| int v, o; |
| if(p != nil && p->cond != nil){ // in literal pool |
| v = p->cond->pc - p->pc - 4; |
| if(p->cond->pc & 3) |
| diag("mv: bad literal pool alignment"); |
| if(v & 3) |
| v += 2; // ensure M(4) offset |
| mult(p, v, 4); |
| off = v/4; |
| numr(p, off, 0, 255); |
| o = 0x9<<11; |
| } |
| else{ |
| numr(p, off, 0, 255); |
| o = 0x4<<11; |
| } |
| o |= (r<<8) | off; |
| return o; |
| } |
| |
| static void |
| mvcon(Prog *p, int r, int c, int32 *o1, int32 *o2) |
| { |
| int op = 0, n = 0; |
| |
| if(c >= 0 && c <= 255) |
| diag("bad c in mvcon"); |
| if(c >= -255 && c < 0) // mv, neg |
| c = -c; |
| else if(c >= 256 && c <= 510){ // mv, add |
| n = rand()%(511-c) + (c-255); |
| c -= n; |
| // n = c-255; |
| // c = 255; |
| op = AADD; |
| } |
| else{ |
| if(c < 0) |
| diag("-ve in mvcon"); |
| while(!(c & 1)){ |
| n++; |
| c >>= 1; |
| } |
| if(c >= 0 && c <= 255) // mv, lsl |
| op = ASLL; |
| else |
| diag("bad shift in mvcon"); |
| } |
| *o1 = mv(p, r, c); |
| switch(op){ |
| case 0: |
| *o2 = (1<<14) | (9<<6) | (r<<3) | r; |
| break; |
| case AADD: |
| *o2 = (6<<11) | (r<<8) | n; |
| break; |
| case ASLL: |
| *o2 = (n<<6) | (r<<3) | r; |
| break; |
| } |
| } |
| |
| static int32 |
| mvlh(int rs, int rd) |
| { |
| int o = 0x46<<8; |
| |
| if(high(rs)){ |
| rs -= 8; |
| o |= 1<<6; |
| } |
| if(high(rd)){ |
| rd -= 8; |
| o |= 1<<7; |
| } |
| o |= (rs<<3) | rd; |
| return o; |
| } |
| |
| void |
| thumbbuildop() |
| { |
| int i, n, r; |
| Optab *optab = thumboptab; |
| Oprang *oprange = thumboprange; |
| |
| for(n=0; optab[n].as != AXXX; n++) |
| ; |
| qsort(optab, n, sizeof(optab[0]), ocmp); |
| for(i=0; i<n; i++) { |
| r = optab[i].as; |
| oprange[r].start = optab+i; |
| while(optab[i].as == r) |
| i++; |
| oprange[r].stop = optab+i; |
| i--; |
| |
| switch(r) |
| { |
| default: |
| break; |
| case ABEQ: |
| oprange[ABNE] = oprange[r]; |
| oprange[ABCS] = oprange[r]; |
| oprange[ABHS] = oprange[r]; |
| oprange[ABCC] = oprange[r]; |
| oprange[ABLO] = oprange[r]; |
| oprange[ABMI] = oprange[r]; |
| oprange[ABPL] = oprange[r]; |
| oprange[ABVS] = oprange[r]; |
| oprange[ABVC] = oprange[r]; |
| oprange[ABHI] = oprange[r]; |
| oprange[ABLS] = oprange[r]; |
| oprange[ABGE] = oprange[r]; |
| oprange[ABLT] = oprange[r]; |
| oprange[ABGT] = oprange[r]; |
| oprange[ABLE] = oprange[r]; |
| break; |
| case AMVN: |
| oprange[AADC] = oprange[r]; |
| oprange[ASBC] = oprange[r]; |
| oprange[AMUL] = oprange[r]; |
| oprange[AAND] = oprange[r]; |
| oprange[AEOR] = oprange[r]; |
| oprange[AORR] = oprange[r]; |
| oprange[ABIC] = oprange[r]; |
| oprange[AMULU] = oprange[r]; |
| break; |
| case ACMN: |
| oprange[ATST] = oprange[r]; |
| break; |
| case ASRL: |
| oprange[ASRA] = oprange[r]; |
| oprange[ASLL] = oprange[r]; |
| break; |
| case AADD: |
| oprange[ASUB] = oprange[r]; |
| break; |
| } |
| } |
| } |
| |
| void |
| thumbasmout(Prog *p, Optab *o) |
| { |
| int32 o1, o2, o3, o4, o5, o6, o7, v; |
| int r, rf, rt; |
| |
| rf = p->from.reg; |
| rt = p->to.reg; |
| r = p->reg; |
| o1 = o2 = o3 = o4 = o5 = o6 = o7 = 0; |
| if(debug['P']) print("%ux: %P type %d %d\n", (uint32)(p->pc), p, o->type, p->align); |
| opcount[o->type] += o->size; |
| switch(o->type) { |
| default: |
| diag("unknown asm %d", o->type); |
| prasm(p); |
| break; |
| case 0: /* pseudo ops */ |
| if(debug['G']) print("%ux: %s: thumb\n", (uint32)(p->pc), p->from.sym->name); |
| break; |
| case 1: /* op R, -, R or op R, R, - */ |
| o1 = thumboprr(p->as); |
| if(rt == NREG) |
| rt = r; |
| lowreg(p, rf); |
| lowreg(p, rt); |
| o1 |= (0x10<<10) | (rf<<3) | rt; |
| break; |
| case 2: /* add/sub R, R, R or add/sub R, -, R */ |
| o1 = p->as == AADD ? 0x0<<9 : 0x1<<9; |
| if(r == NREG) |
| r = rt; |
| lowreg(p, rf); |
| lowreg(p, r); |
| lowreg(p, rt); |
| o1 |= (0x6<<10) | (rf<<6) | (r<<3) | rt; |
| break; |
| case 3: /* add/sub $I, R, R or add/sub $I, -, R */ |
| thumbaclass(&p->from, p); |
| o1 = p->as == AADD ? 0x0<<9 : 0x1<<9; |
| if(r == NREG) |
| r = rt; |
| numr(p, instoffset, 0, 7); |
| lowreg(p, r); |
| lowreg(p, rt); |
| o1 |= (0x7<<10) | (instoffset<<6) | (r<<3) | rt; |
| break; |
| case 4: /* shift $I, R, R or shift $I, -, R */ |
| thumbaclass(&p->from, p); |
| if(instoffset < 0) |
| diag("negative shift in thumbasmout"); |
| instoffset %= 32; |
| o1 = thumbopri(p->as); |
| if(r == NREG) |
| r = rt; |
| numr(p, instoffset, 0, 31); |
| lowreg(p, r); |
| lowreg(p, rt); |
| o1 |= (0x0<<13) | (instoffset<<6) | (r<<3) | rt; |
| break; |
| case 5: /* add/sub/mov $I, -, R or cmp $I, R, - */ |
| thumbaclass(&p->from, p); |
| o1 = thumbopri(p->as); |
| if(rt == NREG) |
| rt = r; |
| numr(p, instoffset, 0, 255); |
| lowreg(p, rt); |
| o1 |= (0x1<<13) | (rt<<8) | instoffset; |
| break; |
| case 6: /* add $I, PC/SP, R */ |
| if(p->as == ASUB) |
| diag("subtract in add $I, PC/SP, R"); |
| thumbaclass(&p->from, p); |
| o1 = r == REGSP ? 0x1<<11 : 0x0<<11; |
| numr(p, instoffset, 0, 255); |
| regis(p, r, REGSP, REGPC); |
| lowreg(p, rt); |
| o1 |= (0xa<<12) | (rt<<8) | instoffset; |
| break; |
| case 7: /* add, sub $I, SP */ |
| thumbaclass(&p->from, p); |
| o1 = p->as == AADD ? 0x0<<7 : 0x1<<7; |
| numr(p, instoffset, 0, 508); |
| mult(p, instoffset, 4); |
| regis(p, rt, REGSP, REGSP); |
| o1 |= (0xb0<<8) | (instoffset>>2); |
| break; |
| case 8: /* add/mov/cmp R, R where at least 1 reg is high */ |
| o1 = 0; |
| if(rt == NREG) |
| rt = r; |
| if(high(rf)){ |
| o1 |= 1<<6; |
| rf -= 8; |
| } |
| if(high(rt)){ |
| o1 |= 2<<6; |
| rt -= 8; |
| } |
| if(o1 == 0) |
| diag("no high register(%P)", p); |
| o1 |= thumbophh(p->as); |
| o1 |= (0x11<<10) | (rf<<3) | rt; |
| break; |
| case 9: /* B $I */ |
| thumbaclass(&p->to, p); |
| numr(p, instoffset, -2048, 2046); |
| o1 = (0x1c<<11) | ((instoffset>>1)&0x7ff); |
| break; |
| case 10: /* Bcc $I */ |
| thumbaclass(&p->to, p); |
| numr(p, instoffset, -256, 254); |
| o1 = thumbopbra(p->as); |
| o1 |= (0xd<<12) | ((instoffset>>1)&0xff); |
| break; |
| case 11: /* BL $I */ |
| thumbaclass(&p->to, p); |
| numr(p, instoffset, -4194304, 4194302); |
| o1 = (0x1e<<11) | ((instoffset>>12)&0x7ff); |
| o2 = (0x1f<<11) | ((instoffset>>1)&0x7ff); |
| break; |
| case 12: /* BX $I */ |
| #ifdef CALLEEBX |
| diag("BX $I case"); |
| #endif |
| thumbaclass(&p->to, p); |
| if(p->to.sym->thumb) |
| instoffset |= 1; // T bit |
| o1 = mvlh(REGPC, REGTMPT); |
| o2 = (0x6<<11) | (REGTMPT<<8) | 7; // add 7, RTMP (T bit + PC offset) |
| o3 = mvlh(REGTMPT, REGLINK); |
| o4 = mv(nil, REGTMPT, instoffset); |
| o5 = (0x11c<<6) | (REGTMPT<<3); |
| // o1 = mv(nil, REGTMPT, v); |
| // o2 = (0x11b<<6) | (REGPC<<3) | REGLINK; |
| // o3 = (0x11c<<6) | (REGTMPT<<3); |
| break; |
| case 13: /* B O(R) */ |
| diag("B O(R)"); |
| break; |
| case 14: /* BL O(R) */ |
| diag("BL O(R)"); |
| break; |
| case 15: /* BX R */ |
| o1 = mvlh(REGPC, REGTMPT); |
| o2 = (0x6<<11) | (REGTMPT<<8) | 5; // add 5, RTMP (T bit + PC offset) |
| o3 = mvlh(REGTMPT, REGLINK); |
| o4 = 0; |
| if(high(rt)){ |
| rt -= 8; |
| o4 |= 1<<6; |
| } |
| o4 |= (0x8e<<7) | (rt<<3); |
| // o1 = (0x11c<<6) | (rt<<3); |
| break; |
| case 16: /* SWI $I */ |
| thumbaclass(&p->to, p); |
| numr(p, instoffset, 0, 255); |
| o1 = (0xdf<<8) | instoffset; |
| break; |
| case 17: /* AWORD */ |
| thumbaclass(&p->to, p); |
| o1 = instoffset&0xffff; |
| o2 = (instoffset>>16)&0xffff; |
| break; |
| case 18: /* AMOVW O(SP), R and AMOVW O(PC), R */ |
| thumbaclass(&p->from, p); |
| rf = o->param; |
| o1 = rf == REGSP ? 0x13<<11 : 0x9<<11; |
| regis(p, rf, REGSP, REGPC); |
| lowreg(p, rt); |
| mult(p, instoffset, 4); |
| numr(p, instoffset/4, 0, 255); |
| o1 |= (rt<<8) | (instoffset/4); |
| break; |
| case 19: /* AMOVW... O(R), R */ |
| thumbaclass(&p->from, p); |
| o1 = thumbopmv(p->as, 1); |
| v = 4; |
| if(p->as == AMOVHU) |
| v = 2; |
| else if(p->as == AMOVBU) |
| v = 1; |
| mult(p, instoffset, v); |
| lowreg(p, rf); |
| lowreg(p, rt); |
| numr(p, instoffset/v, 0, 31); |
| o1 |= ((instoffset/v)<<6) | (rf<<3) | rt; |
| break; |
| case 20: /* AMOVW R, O(SP) */ |
| thumbaclass(&p->to, p); |
| o1 = 0x12<<11; |
| if(rt != NREG) regis(p, rt, REGSP, REGSP); |
| lowreg(p, rf); |
| mult(p, instoffset, 4); |
| numr(p, instoffset/4, 0, 255); |
| o1 |= (rf<<8) | (instoffset/4); |
| break; |
| case 21: /* AMOVW... R, O(R) */ |
| thumbaclass(&p->to, p); |
| o1 = thumbopmv(p->as, 0); |
| v = 4; |
| if(p->as == AMOVHU || p->as == AMOVH) |
| v = 2; |
| else if(p->as == AMOVBU || p->as == AMOVB) |
| v = 1; |
| lowreg(p, rf); |
| lowreg(p, rt); |
| mult(p, instoffset, v); |
| numr(p, instoffset/v, 0, 31); |
| o1 |= ((instoffset/v)<<6) | (rt<<3) | rf; |
| break; |
| case 22: /* AMOVW R, R -> ASLL $0, R, R */ |
| o1 = thumbopri(ASLL); |
| lowreg(p, rf); |
| lowreg(p, rt); |
| o1 |= (0x0<<13) | (rf<<3) | rt; |
| break; |
| case 23: /* AMOVB/AMOVH/AMOVBU/AMOVHU R, R */ |
| o1 = thumbopri(ASLL); |
| o2 = p->as == AMOVB || p->as == AMOVH ? thumbopri(ASRA) : thumbopri(ASRL); |
| v = p->as == AMOVB || p->as == AMOVBU ? 24 : 16; |
| lowreg(p, rf); |
| lowreg(p, rt); |
| o1 |= (0x0<<13) | (v<<6) | (rf<<3) | rt; |
| o2 |= (0x0<<13) | (v<<6) | (rt<<3) | rt; |
| break; |
| case 24: /* AMOVH/AMOVB O(R), R -> AMOVH/AMOVB [R, R], R */ |
| thumbaclass(&p->from, p); |
| lowreg(p, rf); |
| lowreg(p, rt); |
| if(rf == rt) |
| r = REGTMPT; |
| else |
| r = rt; |
| if(p->as == AMOVB) |
| numr(p, instoffset, 0, 31); |
| else{ |
| mult(p, instoffset, 2); |
| numr(p, instoffset, 0, 62); |
| } |
| o1 = mv(p, r, instoffset); |
| o2 = p->as == AMOVH ? 0x2f<<9 : 0x2b<<9; |
| o2 |= (r<<6) | (rf<<3) | rt; |
| break; |
| case 25: /* MOVW $sacon, R */ |
| thumbaclass(&p->from, p); |
| // print("25: %d %d %d %d\n", instoffset, rf, r, rt); |
| if(rf == NREG) |
| rf = REGSP; |
| lowreg(p, rt); |
| if(rf == REGSP){ |
| mult(p, instoffset, 4); |
| numr(p, instoffset>>2, 0, 255); |
| o1 = (0x15<<11) | (rt<<8) | (instoffset>>2); // add $O, SP, R |
| } |
| else if(rf == rt){ |
| numr(p, instoffset, 0, 255); |
| o1 = (0x6<<11) | (rt<<8) | instoffset; // add $O, R |
| } |
| else{ |
| lowreg(p, rf); |
| numr(p, instoffset, 0, 7); |
| o1 = (0xe<<9) | (instoffset<<6) | (rf<<3) | rt; // add $O, Rs, Rd |
| } |
| break; |
| case 26: /* AMOVM $c, oreg -> stmia */ |
| lowreg(p, rt); |
| numr(p, p->from.offset, -256, 255); |
| o1 = (0x18<<11) | (rt<<8) | (p->from.offset&0xff); |
| break; |
| case 27: /* AMOVM oreg, $c ->ldmia */ |
| lowreg(p, rf); |
| numr(p, p->to.offset, -256, 256); |
| o1 = (0x19<<11) | (rf<<8) | (p->to.offset&0xff); |
| break; |
| case 28: /* AMOV* O(R), R -> AMOV* [R, R], R (offset large) */ |
| thumbaclass(&p->from, p); |
| lowreg(p, rf); |
| lowreg(p, rt); |
| if(rf == rt) |
| r = REGTMPT; |
| else |
| r = rt; |
| o1 = mv(p, r, instoffset); |
| o2 = thumboprrr(p->as, 1); |
| o2 |= (r<<6) | (rf<<3) | rt; |
| break; |
| case 29: /* AMOV* R, O(R) -> AMOV* R, [R, R] (offset large) */ |
| thumbaclass(&p->to, p); |
| lowreg(p, rf); |
| lowreg(p, rt); |
| if(rt == REGTMPT){ // used as tmp reg |
| if(instoffset >= 0 && instoffset <= 255){ |
| o1 = (1<<13) | (2<<11) | (rt<<8) | instoffset; // add $O, R7 |
| o2 = thumbopirr(p->as, 0); |
| o2 |= (0<<6) | (rt<<3) | rf; // mov* R, 0(R) |
| } |
| else |
| diag("big offset - case 29"); |
| } |
| else{ |
| o1 = mv(p, REGTMPT, instoffset); |
| o2 = thumboprrr(p->as, 0); |
| o2 |= (REGTMPT<<6) | (rt<<3) | rf; |
| } |
| break; |
| case 30: /* AMOVW... *addr, R */ |
| diag("likely broken"); // does this still refer to SB? |
| thumbaclass(&p->from, p); |
| o1 = mv(p, rt, instoffset); // MOV addr, rtmp |
| o2 = thumbopmv(p->as, 1); |
| lowreg(p, rt); |
| o2 |= (rt<<3) | rt; // MOV* 0(rtmp), R |
| break; |
| case 31: /* AMOVW... R, *addr */ |
| diag("likely broken"); // does this still refer to SB? |
| thumbaclass(&p->to, p); |
| o1 = mv(p, REGTMPT, instoffset); |
| o2 = thumbopmv(p->as, 0); |
| lowreg(p, rf); |
| o2 |= (REGTMPT<<3) | rf; |
| break; |
| case 32: /* AMOVH/AMOVB *addr, R -> AMOVH/AMOVB [R, R], R */ |
| thumbaclass(&p->from, p); |
| o1 = mv(p, rt, instoffset); |
| lowreg(p, rt); |
| o2 = mv(nil, REGTMPT, 0); |
| o3 = p->as == AMOVH ? 0x2f<<9 : 0x2b<<9; |
| o3 |= (REGTMPT<<6) | (rt<<3) | rt; |
| break; |
| case 33: /* AMOVW O(SP), R (O large) */ |
| thumbaclass(&p->from, p); |
| lowreg(p, rt); |
| o1 = mv(p, rt, instoffset); |
| o2 = (0x111<<6) | (REGSP-8)<<3 | rt; // add SP, rt |
| o3 = thumbopmv(p->as, 1); |
| o3 |= (rt<<3) | rt; |
| break; |
| case 34: /* AMOVW R, O(SP) (O large) */ |
| thumbaclass(&p->to, p); |
| lowreg(p, rf); |
| o1 = mv(p, REGTMPT, instoffset); |
| o2 = (0x111<<6) | (REGSP-8)<<3 | REGTMPT; // add SP, REGTMP |
| o3 = thumbopmv(p->as, 0); |
| o3 |= (REGTMPT<<3) | rf; |
| break; |
| case 35: /* AMOVW $lacon, R */ |
| thumbaclass(&p->from, p); |
| lowreg(p, rt); |
| if(rf == NREG) |
| rf = REGSP; |
| if(rf == rt) |
| rf = r = REGTMPT; |
| else |
| r = rt; |
| // print("35: io=%d rf=%d rt=%d\n", instoffset, rf, rt); |
| o1 = mv(p, r, instoffset); // mov O, Rd |
| if(high(rf)) |
| o2 = (0x44<<8) | (0x1<<6) | ((rf-8)<<3) | rt; // add Rs, Rd |
| else |
| o2 = (0x6<<10) | (rf<<6) | (rt<<3) | rt; // add Rs, Rd |
| break; |
| case 36: /* AADD/ASUB $i, r, r when $i too big */ |
| thumbaclass(&p->from, p); |
| lowreg(p, r); |
| lowreg(p, rt); |
| o1 = mv(p, REGTMPT, instoffset); |
| o2 = p->as == AADD ? 0xc<<9 : 0xd<<9; |
| o2 |= (REGTMPT<<6) | (r<<3) | rt; |
| break; |
| case 37: /* AADD/ASUB $i, r when $i too big */ |
| thumbaclass(&p->from, p); |
| lowreg(p, rt); |
| o1 = mv(p, REGTMPT, instoffset); |
| o2 = p->as == AADD ? 0xc<<9 : 0xd<<9; |
| o2 |= (REGTMPT<<6) | (rt<<3) | rt; |
| break; |
| case 38: /* AMOVW $i, r when $i too big */ |
| thumbaclass(&p->from, p); |
| lowreg(p, rt); |
| o1 = mv(p, rt, instoffset); |
| break; |
| case 39: /* ACMP $i, r when $i too big */ |
| thumbaclass(&p->from, p); |
| lowreg(p, r); |
| o1 = mv(p, REGTMPT, instoffset); |
| o2 = (0x10a<<6) | (REGTMPT<<3) | r; |
| break; |
| case 40: /* add, sub $I, SP when $I large*/ |
| thumbaclass(&p->from, p); |
| if(p->as == ASUB) |
| instoffset = -instoffset; |
| o1 = mv(p, REGTMPT, instoffset); |
| o2 = (0x112<<6) | (REGTMPT<<3) | (REGSP-8); |
| regis(p, rt, REGSP, REGSP); |
| break; |
| case 41: /* BL LBRA */ |
| thumbaclass(&p->to, p); |
| o1 = (0x9<<11) | (REGTMPT<<8); // mov 0(pc), r7 |
| o2 = mvlh(REGTMPT, REGPC); // mov r7, pc |
| o3 = instoffset&0xffff; // $lab |
| o4 = (instoffset>>16)&0xffff; |
| break; |
| case 42: /* Bcc GBRA */ |
| thumbaclass(&p->to, p); |
| o1 = (0xd<<12) | thumbopbra(relinv(p->as)) | (6>>1); // bccnot |
| // ab lbra |
| o2 = (0x9<<11) | (REGTMPT<<8); // mov 0(pc), r7 |
| o3 = mvlh(REGTMPT, REGPC); // mov r7, pc |
| o4 = instoffset&0xffff; // $lab |
| o5 = (instoffset>>16)&0xffff; |
| break; |
| case 43: /* BL LBRA */ |
| thumbaclass(&p->to, p); |
| o1 = mvlh(REGPC, REGTMPT); // mov pc, r7 |
| o2 = (0x6<<11) | (REGTMPT<<8) | 10; // add 10, r7 |
| o3 = mvlh(REGTMPT, REGLINK); // mov r7, lr |
| o4 = (0x9<<11) | (REGTMPT<<8); // mov o(pc), r7 |
| o5 = mvlh(REGTMPT, REGPC); // mov r7, pc |
| o6 = instoffset&0xffff; // $lab |
| o7 = (instoffset>>16)&0xffff; |
| break; |
| case 44: /* BX LBRA */ |
| #ifdef CALLEEBX |
| diag("BX LBRA case"); |
| #endif |
| thumbaclass(&p->to, p); |
| if(p->to.sym->thumb) |
| instoffset |= 1; // T bit |
| o1 = mvlh(REGPC, REGTMPT); // mov pc, r7 |
| o2 = (0x6<<11) | (REGTMPT<<8) | 11; // add 11, r7 |
| o3 = mvlh(REGTMPT, REGLINK); // mov r7, lr |
| o4 = (0x9<<11) | (REGTMPT<<8); // mov o(pc), r7 |
| o5 = (0x11c<<6) | (REGTMPT<<3); // bx r7 |
| o6 = instoffset&0xffff; // $lab |
| o7 = (instoffset>>16)&0xffff; |
| break; |
| case 45: /* BX R when returning from fn */ |
| o1 = 0; |
| if(high(rt)){ |
| rt -= 8; |
| o1 |= 1<<6; |
| } |
| o1 |= (0x8e<<7) | (rt<<3); |
| break; |
| case 46: /* Bcc LBRA */ |
| thumbaclass(&p->to, p); |
| o1 = (0xd<<12) | thumbopbra(relinv(p->as)) | (0>>1); // bccnot |
| // ab lbra |
| instoffset -= 2; |
| numr(p, instoffset, -2048, 2046); |
| o2 = (0x1c<<11) | ((instoffset>>1)&0x7ff); |
| break; |
| case 47: /* mov $i, R where $i can be built */ |
| thumbaclass(&p->from, p); |
| mvcon(p, rt, instoffset, &o1, &o2); |
| break; |
| case 48: /* ACMP $i, r when $i built up */ |
| thumbaclass(&p->from, p); |
| lowreg(p, r); |
| mvcon(p, REGTMPT, instoffset, &o1, &o2); |
| o3 = (0x10a<<6) | (REGTMPT<<3) | r; |
| break; |
| case 49: /* AADD $i, r, r when $i is between 0 and 255 - could merge with case 36 */ |
| thumbaclass(&p->from, p); |
| lowreg(p, r); |
| lowreg(p, rt); |
| numr(p, instoffset, 0, 255); |
| o1 = mv(p, REGTMPT, instoffset); |
| o2 = p->as == AADD ? 0xc<<9 : 0xd<<9; |
| o2 |= (REGTMPT<<6) | (r<<3) | rt; |
| break; |
| case 50: /* ADWORD */ |
| thumbaclass(&p->from, p); |
| o1 = instoffset&0xffff; |
| o2 = (instoffset>>16)&0xffff; |
| thumbaclass(&p->to, p); |
| o3 = instoffset&0xffff; |
| o4 = (instoffset>>16)&0xffff; |
| break; |
| case 51: /* BL r */ |
| o1 = mvlh(REGPC, REGLINK); // mov pc, lr |
| o2 = mvlh(rt, REGPC); // mov r, pc |
| break; |
| } |
| |
| v = p->pc; |
| switch(o->size) { |
| default: |
| if(debug['a']) |
| Bprint(&bso, " %.8ux:\t\t%P\n", v, p); |
| break; |
| case 2: |
| if(debug['a']) |
| Bprint(&bso, " %.8ux: %.8ux\t%P\n", v, o1, p); |
| hputl(o1); |
| break; |
| case 4: |
| if(debug['a']) |
| Bprint(&bso, " %.8ux: %.8ux %.8ux\t%P\n", v, o1, o2, p); |
| hputl(o1); |
| hputl(o2); |
| break; |
| case 6: |
| if(debug['a']) |
| Bprint(&bso, "%.8ux: %.8ux %.8ux %.8ux\t%P\n", v, o1, o2, o3, p); |
| hputl(o1); |
| hputl(o2); |
| hputl(o3); |
| break; |
| case 8: |
| if(debug['a']) |
| Bprint(&bso, "%.8ux: %.8ux %.8ux %.8ux %.8ux\t%P\n", v, o1, o2, o3, o4, p); |
| hputl(o1); |
| hputl(o2); |
| hputl(o3); |
| hputl(o4); |
| break; |
| case 10: |
| if(debug['a']) |
| Bprint(&bso, "%.8ux: %.8ux %.8ux %.8ux %.8ux %.8ux\t%P\n", v, o1, o2, o3, o4, o5, p); |
| hputl(o1); |
| hputl(o2); |
| hputl(o3); |
| hputl(o4); |
| hputl(o5); |
| break; |
| case 12: |
| if(debug['a']) |
| Bprint(&bso, "%.8ux: %.8ux %.8ux %.8ux %.8ux %.8ux %.8ux\t%P\n", v, o1, o2, o3, o4, o5, o6, p); |
| hputl(o1); |
| hputl(o2); |
| hputl(o3); |
| hputl(o4); |
| hputl(o5); |
| hputl(o6); |
| break; |
| case 14: |
| if(debug['a']) |
| Bprint(&bso, "%.8ux: %.8ux %.8ux %.8ux %.8ux %.8ux %.8ux %.8ux\t%P\n", v, o1, o2, o3, o4, o5, o6, o7, p); |
| hputl(o1); |
| hputl(o2); |
| hputl(o3); |
| hputl(o4); |
| hputl(o5); |
| hputl(o6); |
| hputl(o7); |
| break; |
| } |
| if(debug['G']){ |
| if(o->type == 17){ |
| print("%x: word %d\n", p->pc, (o2<<16)+o1); |
| return; |
| } |
| if(o->type == 50){ |
| print("%x: word %d\n", p->pc, (o2<<16)+o1); |
| print("%x: word %d\n", p->pc, (o4<<16)+o3); |
| return; |
| } |
| if(o->size > 0) dis(o1, p->pc); |
| if(o->size > 2) dis(o2, p->pc+2); |
| if(o->size > 4) dis(o3, p->pc+4); |
| if(o->size > 6) dis(o4, p->pc+6); |
| if(o->size > 8) dis(o5, p->pc+8); |
| if(o->size > 10) dis(o6, p->pc+10); |
| if(o->size > 12) dis(o7, p->pc+12); |
| // if(o->size > 14) dis(o8, p->pc+14); |
| } |
| } |
| |
| static int32 |
| thumboprr(int a) |
| { |
| switch(a) { |
| case AMVN: return 0xf<<6; |
| case ACMP: return 0xa<<6; |
| case ACMN: return 0xb<<6; |
| case ATST: return 0x8<<6; |
| case AADC: return 0x5<<6; |
| case ASBC: return 0x6<<6; |
| case AMUL: |
| case AMULU: return 0xd<<6; |
| case AAND: return 0x0<<6; |
| case AEOR: return 0x1<<6; |
| case AORR: return 0xc<<6; |
| case ABIC: return 0xe<<6; |
| case ASRL: return 0x3<<6; |
| case ASRA: return 0x4<<6; |
| case ASLL: return 0x2<<6; |
| } |
| diag("bad thumbop oprr %d", a); |
| prasm(curp); |
| return 0; |
| } |
| |
| static int32 |
| thumbopirr(int a, int ld) |
| { |
| if(ld) |
| diag("load in thumbopirr"); |
| switch(a){ |
| case AMOVW: return 0xc<<11; |
| case AMOVH: |
| case AMOVHU: return 0x10<<11; |
| case AMOVB: |
| case AMOVBU: return 0xe<<11; |
| } |
| return 0; |
| } |
| |
| static int32 |
| thumboprrr(int a, int ld) |
| { |
| if(ld){ |
| switch(a){ |
| case AMOVW: return 0x2c<<9; |
| case AMOVH: return 0x2f<<9; |
| case AMOVB: return 0x2b<<9; |
| case AMOVHU: return 0x2d<<9; |
| case AMOVBU: return 0x2e<<9; |
| } |
| } |
| else{ |
| switch(a){ |
| case AMOVW: return 0x28<<9; |
| case AMOVHU: |
| case AMOVH: return 0x29<<9; |
| case AMOVBU: |
| case AMOVB: return 0x2a<<9; |
| } |
| } |
| diag("bad thumbop oprrr %d", a); |
| prasm(curp); |
| return 0; |
| } |
| |
| static int32 |
| thumbopri(int a) |
| { |
| switch(a) { |
| case ASRL: return 0x1<<11; |
| case ASRA: return 0x2<<11; |
| case ASLL: return 0x0<<11; |
| case AADD: return 0x2<<11; |
| case ASUB: return 0x3<<11; |
| case AMOVW: return 0x0<<11; |
| case ACMP: return 0x1<<11; |
| } |
| diag("bad thumbop opri %d", a); |
| prasm(curp); |
| return 0; |
| } |
| |
| static int32 |
| thumbophh(int a) |
| { |
| switch(a) { |
| case AADD: return 0x0<<8; |
| case AMOVW: return 0x2<<8; |
| case ACMP: return 0x1<<8; |
| } |
| diag("bad thumbop ophh %d", a); |
| prasm(curp); |
| return 0; |
| } |
| |
| static int32 |
| thumbopbra(int a) |
| { |
| switch(a) { |
| case ABEQ: return 0x0<<8; |
| case ABNE: return 0x1<<8; |
| case ABCS: return 0x2<<8; |
| case ABHS: return 0x2<<8; |
| case ABCC: return 0x3<<8; |
| case ABLO: return 0x3<<8; |
| case ABMI: return 0x4<<8; |
| case ABPL: return 0x5<<8; |
| case ABVS: return 0x6<<8; |
| case ABVC: return 0x7<<8; |
| case ABHI: return 0x8<<8; |
| case ABLS: return 0x9<<8; |
| case ABGE: return 0xa<<8; |
| case ABLT: return 0xb<<8; |
| case ABGT: return 0xc<<8; |
| case ABLE: return 0xd<<8; |
| } |
| diag("bad thumbop opbra %d", a); |
| prasm(curp); |
| return 0; |
| } |
| |
| static int32 |
| thumbopmv(int a, int ld) |
| { |
| switch(a) { |
| case AMOVW: return (ld ? 0xd : 0xc)<<11; |
| case AMOVH: |
| case AMOVHU: return (ld ? 0x11: 0x10)<<11; |
| case AMOVB: |
| case AMOVBU: return (ld ? 0xf : 0xe)<<11; |
| } |
| diag("bad thumbop opmv %d", a); |
| prasm(curp); |
| return 0; |
| } |
| |
| static void |
| lowreg(Prog *p, int r) |
| { |
| if(high(r)) |
| diag("high reg [%P]", p); |
| } |
| |
| static void |
| mult(Prog *p, int n, int m) |
| { |
| if(m*(n/m) != n) |
| diag("%d not M(%d) [%P]", n, m, p); |
| } |
| |
| static void |
| numr(Prog *p, int n, int min, int max) |
| { |
| if(n < min || n > max) |
| diag("%d not in %d-%d [%P]", n, min, max, p); |
| } |
| |
| static void |
| regis(Prog *p, int r, int r1, int r2) |
| { |
| if(r != r1 && r != r2) |
| diag("reg %d not %d or %d [%P]", r, r1, r2, p); |
| } |
| |
| void |
| hputl(int n) |
| { |
| cbp[1] = n>>8; |
| cbp[0] = n; |
| cbp += 2; |
| cbc -= 2; |
| if(cbc <= 0) |
| cflush(); |
| } |
| |
| void |
| thumbcount() |
| { |
| int i, c = 0, t = 0; |
| |
| for (i = 0; i < OPCNTSZ; i++) |
| t += opcount[i]; |
| if(t == 0) |
| return; |
| for (i = 0; i < OPCNTSZ; i++){ |
| c += opcount[i]; |
| print("%d: %d %d %d%%\n", i, opcount[i], c, (opcount[i]*100+t/2)/t); |
| } |
| } |
| |
| char *op1[] = { "lsl", "lsr", "asr" }; |
| char *op2[] = { "add", "sub" }; |
| char *op3[] = { "movw", "cmp", "add", "sub" }; |
| char *op4[] = { "and", "eor", "lsl", "lsr", "asr", "adc", "sbc", "ror", |
| "tst", "neg", "cmp", "cmpn", "or", "mul", "bitc", "movn" }; |
| char *op5[] = { "add", "cmp", "movw", "bx" }; |
| char *op6[] = { "smovw", "smovh", "smovb", "lmovb", "lmovw", "lmovhu", "lmovbu", "lmovh" }; |
| char *op7[] = { "smovw", "lmovw", "smovb", "lmovbu" }; |
| char *op8[] = { "smovh", "lmovhu" }; |
| char *op9[] = { "smovw", "lmovw" }; |
| char *op10[] = { "push", "pop" }; |
| char *op11[] = { "stmia", "ldmia" }; |
| |
| char *cond[] = { "eq", "ne", "hs", "lo", "mi", "pl", "vs", "vc", |
| "hi", "ls", "ge", "lt", "gt", "le", "al", "nv" }; |
| |
| #define B(h, l) bits(i, h, l) |
| #define IMM(h, l) B(h, l) |
| #define REG(h, l) reg(B(h, l)) |
| #define LHREG(h, l, lh) lhreg(B(h, l), B(lh, lh)) |
| #define COND(h, l) cond[B(h, l)] |
| #define OP1(h, l) op1[B(h, l)] |
| #define OP2(h, l) op2[B(h, l)] |
| #define OP3(h, l) op3[B(h, l)] |
| #define OP4(h, l) op4[B(h, l)] |
| #define OP5(h, l) op5[B(h, l)] |
| #define OP6(h, l) op6[B(h, l)] |
| #define OP7(h, l) op7[B(h, l)] |
| #define OP8(h, l) op8[B(h, l)] |
| #define OP9(h, l) op9[B(h, l)] |
| #define OP10(h, l) op10[B(h, l)] |
| #define OP11(h, l) op11[B(h, l)] |
| #define SBZ(h, l) if(IMM(h, l) != 0) diag("%x: %x bits %d,%d not zero", pc, i, h, l) |
| #define SNBZ(h, l) if(IMM(h, l) == 0) diag("%x: %x bits %d,%d zero", pc, i, h, l) |
| #define SBO(h, l) if(IMM(h, l) != 1) diag("%x: %x bits %d,%d not one", pc, i, h, l) |
| |
| static int |
| bits(int i, int h, int l) |
| { |
| if(h < l) |
| diag("h < l in bits"); |
| return (i&(((1<<(h-l+1))-1)<<l))>>l; |
| } |
| |
| static char * |
| reg(int r) |
| { |
| static char s[4][4]; |
| static int i = 0; |
| |
| if(r < 0 || r > 7) |
| diag("register %d out of range", r); |
| i++; |
| if(i == 4) |
| i = 0; |
| sprint(s[i], "r%d", r); |
| return s[i]; |
| } |
| |
| static char *regnames[] = { "sp", "lr", "pc" }; |
| |
| static char * |
| lhreg(int r, int lh) |
| { |
| static char s[4][4]; |
| static int i = 0; |
| |
| if(lh == 0) |
| return reg(r); |
| if(r < 0 || r > 7) |
| diag("high register %d out of range", r); |
| i++; |
| if(i == 4) |
| i = 0; |
| if(r >= 5) |
| sprint(s[i], "%s", regnames[r-5]); |
| else |
| sprint(s[i], "r%d", r+8); |
| return s[i]; |
| } |
| |
| static void |
| illegal(int i, int pc) |
| { |
| diag("%x: %x illegal instruction", pc, i); |
| } |
| |
| static void |
| dis(int i, int pc) |
| { |
| static int lasto; |
| int o, l; |
| char *op; |
| |
| print("%x: %x: ", pc, i); |
| if(i&0xffff0000) |
| illegal(i, pc); |
| o = B(15, 13); |
| switch(o){ |
| case 0: |
| o = B(12, 11); |
| switch(o){ |
| case 0: |
| case 1: |
| case 2: |
| print("%s %d, %s, %s\n", OP1(12, 11), IMM(10, 6), REG(5, 3), REG(2, 0)); |
| return; |
| case 3: |
| if(B(10, 10) == 0) |
| print("%s %s, %s, %s\n", OP2(9, 9), REG(8, 6), REG(5, 3), REG(2, 0)); |
| else |
| print("%s %d, %s, %s\n", OP2(9, 9), IMM(8, 6), REG(5, 3), REG(2, 0)); |
| return; |
| } |
| case 1: |
| print("%s %d, %s\n", OP3(12, 11), IMM(7, 0), REG(10, 8)); |
| return; |
| case 2: |
| o = B(12, 10); |
| if(o == 0){ |
| print("%s %s, %s\n", OP4(9, 6), REG(5, 3), REG(2, 0)); |
| return; |
| } |
| if(o == 1){ |
| o = B(9, 8); |
| if(o == 3){ |
| SBZ(7, 7); |
| SBZ(2, 0); |
| print("%s %s\n", OP5(9, 8), LHREG(5, 3, 6)); |
| return; |
| } |
| SNBZ(7, 6); |
| print("%s %s, %s\n", OP5(9, 8), LHREG(5, 3, 6), LHREG(2, 0, 7)); |
| return; |
| } |
| if(o == 2 || o == 3){ |
| print("movw %d(pc)[%x], %s\n", 4*IMM(7, 0), 4*IMM(7, 0)+pc+4, REG(10, 8)); |
| return; |
| } |
| op = OP6(11, 9); |
| if(*op == 'l') |
| print("%s [%s, %s], %s\n", op+1, REG(8, 6), REG(5, 3), REG(2, 0)); |
| else |
| print("%s %s, [%s, %s]\n", op+1, REG(2, 0), REG(8, 6), REG(5, 3)); |
| return; |
| case 3: |
| op = OP7(12, 11); |
| if(B(12, 11) == 0 || B(12,11) == 1) |
| l = 4; |
| else |
| l = 1; |
| if(*op == 'l') |
| print("%s %d(%s), %s\n", op+1, l*IMM(10, 6), REG(5, 3), REG(2, 0)); |
| else |
| print("%s %s, %d(%s)\n", op+1, REG(2, 0), l*IMM(10, 6), REG(5, 3)); |
| return; |
| case 4: |
| if(B(12, 12) == 0){ |
| op = OP8(11, 11); |
| if(*op == 'l') |
| print("%s %d(%s), %s\n", op+1, 2*IMM(10, 6), REG(5, 3), REG(2, 0)); |
| else |
| print("%s %s, %d(%s)\n", op+1, REG(2, 0), 2*IMM(10, 6), REG(5, 3)); |
| return; |
| } |
| op = OP9(11, 11); |
| if(*op == 'l') |
| print("%s %d(sp), %s\n", op+1, 4*IMM(7, 0), REG(10, 8)); |
| else |
| print("%s %s, %d(sp)\n", op+1, REG(10, 8), 4*IMM(7, 0)); |
| return; |
| case 5: |
| if(B(12, 12) == 0){ |
| if(B(11, 11) == 0) |
| print("add %d, pc, %s\n", 4*IMM(7, 0), REG(10, 8)); |
| else |
| print("add %d, sp, %s\n", 4*IMM(7, 0), REG(10, 8)); |
| return; |
| } |
| if(B(11, 8) == 0){ |
| print("%s %d, sp\n", OP2(7, 7), 4*IMM(6, 0)); |
| return; |
| } |
| SBO(10, 10); |
| SBZ(9, 9); |
| if(B(8, 8) == 0) |
| print("%s sp, %d\n", OP10(11, 11), IMM(7, 0)); |
| else |
| print("%s sp, %d|15\n", OP10(11, 11), IMM(7, 0)); |
| return; |
| case 6: |
| if(B(12, 12) == 0){ |
| print("%s %s, %d\n", OP11(11, 11), REG(10, 8), IMM(7, 0)); |
| return; |
| } |
| if(B(11, 8) == 0xf){ |
| print("swi %d\n", IMM(7, 0)); |
| return; |
| } |
| o = IMM(7, 0); |
| if(o&0x80) |
| o |= 0xffffff00; |
| o = pc+4+(o<<1); |
| print("b%s %x\n", COND(11, 8), o); |
| return; |
| case 7: |
| o = B(12, 11); |
| switch(o){ |
| case 0: |
| o = IMM(10, 0); |
| if(o&0x400) |
| o |= 0xfffff800; |
| o = pc+4+(o<<1); |
| print("b %x\n", o); |
| return; |
| case 1: |
| illegal(i, pc); |
| return; |
| case 2: |
| lasto = IMM(10, 0); |
| print("bl\n"); |
| return; |
| case 3: |
| if(lasto&0x400) |
| lasto |= 0xfffff800; |
| o = IMM(10, 0); |
| o = (pc-2)+4+(o<<1)+(lasto<<12); |
| print("bl %x\n", o); |
| return; |
| } |
| } |
| } |