liblink: fail for too-large register offset constants

Previously, liblink would silently truncate register offset constants
to 32 bits.  For example,

    MOVD $0x200000004(R2),R3

would assemble to

    addis   r31,r2,0
    addi    r3,r31,4

To fix this, limit C_LACON to 32 bit (signed) offsets and introduce a
new C_DACON operand type for larger register offsets.  We don't
implement this currently, but at least liblink will now give an error
if it encounters an address like this.

Change-Id: I8e87def8cc4cc5b75498b0fb543ac7666cf2964e
Reviewed-on: https://go-review.googlesource.com/1758
Reviewed-by: Minux Ma <minux@golang.org>
diff --git a/src/cmd/9l/9.out.h b/src/cmd/9l/9.out.h
index 016163b..e7601f0 100644
--- a/src/cmd/9l/9.out.h
+++ b/src/cmd/9l/9.out.h
@@ -101,15 +101,16 @@
 	C_SPR,		/* special processor register */
 	C_ZCON,
 	C_SCON,		/* 16 bit signed */
-	C_UCON,		/* low 16 bits 0 */
+	C_UCON,		/* 32 bit signed, low 16 bits 0 */
 	C_ADDCON,	/* -0x8000 <= v < 0 */
 	C_ANDCON,	/* 0 < v <= 0xFFFF */
 	C_LCON,		/* other 32 */
 	C_DCON,		/* other 64 (could subdivide further) */
-	C_SACON,	/* $n(REG) where n is small */
+	C_SACON,	/* $n(REG) where n <= int16 */
 	C_SECON,
-	C_LACON,	/* $n(REG) where n is large */
+	C_LACON,	/* $n(REG) where int16 < n <= int32 */
 	C_LECON,
+	C_DACON,	/* $n(REG) where int32 < n */
 	C_SBRA,
 	C_LBRA,
 	C_SAUTO,
diff --git a/src/liblink/asm9.c b/src/liblink/asm9.c
index 5a37927..64fc651 100644
--- a/src/liblink/asm9.c
+++ b/src/liblink/asm9.c
@@ -683,7 +683,9 @@
 			if(a->reg != NREG) {
 				if(-BIG <= ctxt->instoffset && ctxt->instoffset <= BIG)
 					return C_SACON;
-				return C_LACON;
+				if(isint32(ctxt->instoffset))
+					return C_LACON;
+				return C_DACON;
 			}
 		consize:
 			if(ctxt->instoffset >= 0) {
@@ -1800,12 +1802,10 @@
 		if(p->to.reg == REGTMP)
 			ctxt->diag("can't synthesize large constant\n%P", p);
 		v = regoff(ctxt, &p->from);
-		if(v & 0x8000L)
-			v += 0x10000L;
 		r = p->from.reg;
 		if(r == NREG)
 			r = o->param;
-		o1 = AOP_IRR(OP_ADDIS, REGTMP, r, v>>16);
+		o1 = AOP_IRR(OP_ADDIS, REGTMP, r, high16adjusted(v));
 		o2 = AOP_IRR(OP_ADDI, p->to.reg, REGTMP, v);
 		break;
 
@@ -1913,34 +1913,28 @@
 
 	case 35:	/* mov r,lext/lauto/loreg ==> cau $(v>>16),sb,r'; store o(r') */
 		v = regoff(ctxt, &p->to);
-		if(v & 0x8000L)
-			v += 0x10000L;
 		r = p->to.reg;
 		if(r == NREG)
 			r = o->param;
-		o1 = AOP_IRR(OP_ADDIS, REGTMP, r, v>>16);
+		o1 = AOP_IRR(OP_ADDIS, REGTMP, r, high16adjusted(v));
 		o2 = AOP_IRR(opstore(ctxt, p->as), p->from.reg, REGTMP, v);
 		break;
 
 	case 36:	/* mov bz/h/hz lext/lauto/lreg,r ==> lbz/lha/lhz etc */
 		v = regoff(ctxt, &p->from);
-		if(v & 0x8000L)
-			v += 0x10000L;
 		r = p->from.reg;
 		if(r == NREG)
 			r = o->param;
-		o1 = AOP_IRR(OP_ADDIS, REGTMP, r, v>>16);
+		o1 = AOP_IRR(OP_ADDIS, REGTMP, r, high16adjusted(v));
 		o2 = AOP_IRR(opload(ctxt, p->as), p->to.reg, REGTMP, v);
 		break;
 
 	case 37:	/* movb lext/lauto/lreg,r ==> lbz o(reg),r; extsb r */
 		v = regoff(ctxt, &p->from);
-		if(v & 0x8000L)
-			v += 0x10000L;
 		r = p->from.reg;
 		if(r == NREG)
 			r = o->param;
-		o1 = AOP_IRR(OP_ADDIS, REGTMP, r, v>>16);
+		o1 = AOP_IRR(OP_ADDIS, REGTMP, r, high16adjusted(v));
 		o2 = AOP_IRR(opload(ctxt, p->as), p->to.reg, REGTMP, v);
 		o3 = LOP_RRR(OP_EXTSB, p->to.reg, p->to.reg, 0);
 		break;