Change RELOPPC to allow gap between ha16 and lo16.
authorGeorge Koehler <xkernigh@netscape.net>
Mon, 6 Feb 2017 19:17:28 +0000 (14:17 -0500)
committerGeorge Koehler <xkernigh@netscape.net>
Mon, 6 Feb 2017 19:17:28 +0000 (14:17 -0500)
This change breaks old .o files.  The linker will probably (but
perhaps not always) say, "Don't know how to read from PowerPC fixup",
when it encounters a RELOPPC in the old format.

The new format divides the 32-bit value into two parts.  The lower 26
bits encode a signed 26-bit offset from the symbol.  The higher 6 bits
encode the distance (in 1 to 63 instructions) from the hi16 or ha16 to
the lo16.  The old format had a 32-bit offset, but the lo16 needed to
be in the next instruction after the hi16 or ha16.

The assembler fails if an offset is too big for signed 26-bit.  Before
this change, we restricted branch instructions to a signed 26-bit
offset, but now I also restrict hi16 and ha16 instructions.

mach/powerpc/as/mach5.c
util/led/relocate.c

index 446c801..e54adb8 100644 (file)
@@ -1,8 +1,8 @@
 /*
- * We use RELOPPC to relocate hi16[expr] or ha16[expr].  This requires
- * a matching lo16[expr] in the next instruction, because RELOPPC
- * handles a hi16/lo16 or ha16/lo16 pair.  RELOPPC only works with
- * certain specific opcodes.
+ * To relocate hi16[expr] or ha16[expr], we must find a matching
+ * lo16[expr] in a later instruction.  This is because RELOPPC needs a
+ * hi16/lo16 or ha16/lo16 pair.  Also, RELOPPC only works with certain
+ * specific opcodes.
  */
 
 static item_t *last_und_item;
@@ -53,6 +53,7 @@ void no_hl(void) {
 word_t eval_hl(expr_t* expr, int token)
 {
     struct hirel* hr;
+    ADDR_T distance;
     word_t val = expr->val;
     uint16_t hi = val >> 16;
     uint16_t lo = val & 0xffff;
@@ -64,6 +65,30 @@ word_t eval_hl(expr_t* expr, int token)
     else
         hl_item = NULL;
 
+    if (pass == PASS_3) {
+        hr = hr_head;
+        if (hr && hr->hr_hidot == DOTVAL && hr->hr_dottyp == DOTTYP) {
+            /*
+             * We have a hirel from emit_hl() PASS_2.  Encode distance
+             * to the matching lo16 in top 6 bits of hi.
+             */
+            distance = hr->hr_lodot - DOTVAL;
+            if (distance & 0x0003)
+                serror("matching lo16 is misaligned");
+            if (distance & ~0x00fc)
+                serror("matching lo16 is too far away");
+            fit(fitx((int16_t)hi, 10));
+            hi = (distance << 8) | (hi & 0x03ff);
+
+            /* Remove hirel from list. */
+            hr_head = hr->hr_next;
+            free(hr);
+
+            /* No sign adjustment; linker will do it later. */
+            return hi;
+        }
+    }
+
     switch (token) {
     case OP_HA:  /* ha16[expr] */
         /*
@@ -75,19 +100,6 @@ word_t eval_hl(expr_t* expr, int token)
             hi++;
         /* FALLTHROUGH */
     case OP_HI:  /* hi16[expr] */
-        if (pass == PASS_3) {
-            /* Get the hirel from emit_hl() PASS_2. */
-            hr = hr_head;
-            if (hr && hr->hr_hidot == DOTVAL && hr->hr_dottyp == DOTTYP) {
-                /* For now, the lo16 must be in the next instruction. */
-                if (DOTVAL + 4 != hr->hr_lodot)
-                    serror("%s needs lo16 in next instruction",
-                           token == OP_HI ? "hi16" : "ha16");
-
-                hr_head = hr->hr_next;
-                free(hr);
-            }
-        }
         return hi;
     case OP_LO:  /* lo16[expr] */
         return lo;
@@ -176,6 +188,7 @@ void emit_hl(word_t in)
                     if (hr_match(hr, und)) {
                         /* Found hr, remove it from list. */
                         *hrlink = hr->hr_inext;
+                        hr->hr_inext = NULL;
                         break;
                     }
                     hrlink = &(hr->hr_inext);
index 036b7db..9455b64 100644 (file)
@@ -129,44 +129,46 @@ static bool is_powerpc_memory_op(uint32_t opcode)
        return false;
 }
 
-/* PowerPC fixups are complex as we need to patch up to the next two
- * instructions in one of several different ways, depending on what the
- * instructions area.
+static bool is_powerpc_ori(uint32_t opcode)
+{
+       return (opcode & 0xfc000000) == 0x60000000;
+}
+
+/* PowerPC fixups are complex as we need to patch one or two
+ * instructions in one of several different ways.
  */
 
 static uint32_t get_powerpc_valu(char* addr, uint16_t type)
 {
-       uint32_t opcode1 = read4(addr+0, type);
-       uint32_t opcode2 = read4(addr+4, type);
+       uint32_t opcode1 = read4(addr, type);
+       uint32_t opcode2 = 0;
 
        if ((opcode1 & 0xfc000000) == 0x48000000)
        {
                /* branch instruction */
                return opcode1 & 0x03fffffd;
        }
-       else if (((opcode1 & 0xfc1f0000) == 0x3c000000) &&
-                ((opcode2 & 0xfc000000) == 0x60000000))
+       else if ((opcode1 & 0xfc1f0000) == 0x3c000000)
        {
-               /* addis / ori instruction pair */
-               return ((opcode1 & 0xffff) << 16) | (opcode2 & 0xffff);
-       }
-       else if (((opcode1 & 0xfc1f0000) == 0x3c000000) &&
-                is_powerpc_memory_op(opcode2))
-       {
-               /* addis / memoryop instruction pair */
-               uint16_t hi = opcode1 & 0xffff;
-               uint16_t lo = opcode2 & 0xffff;
+               /* addis, paired with a later instruction */
+               uint16_t distance, hi, lo;
 
-               /* Undo the sign adjustment (see mach/powerpc/as/mach5.c). */
+               distance = (opcode1 & 0xfc00) >> 8;
+               /* XXX addr + distance might be too big */
+               if (distance)
+                       opcode2 = read4(addr + distance, type);
 
-               if (lo > 0x7fff)
-                       hi--;
+               hi = opcode1 & 0x03ff;
+               lo = opcode2 & 0xffff;
+               if (hi & 0x0200)
+                       hi |= 0xfc00;  /* sign extension */
 
-               return ((hi << 16) | lo);
+               if (is_powerpc_memory_op(opcode2) || is_powerpc_ori(opcode2))
+                       return (hi << 16) | lo;
        }
 
-       fatal("Don't know how to read from PowerPC fixup on instructions 0x%08x+0x%08x",
-               opcode1, opcode2);
+       fatal("Don't know how to read from PowerPC fixup on instructions 0x%08lx+0x%08lx",
+               (unsigned long)opcode1, (unsigned long)opcode2);
 }
 
 /*
@@ -283,15 +285,14 @@ static void put_vc4_valu(char* addr, uint32_t value)
                assert(0 && "unrecognised VC4 instruction");
 }
 
-/* PowerPC fixups are complex as we need to patch up to the next two
- * instructions in one of several different ways, depending on what the
- * instructions area.
+/* PowerPC fixups are complex as we need to patch one or two
+ * instructions in one of several different ways.
  */
 
 static void put_powerpc_valu(char* addr, uint32_t value, uint16_t type)
 {
-       uint32_t opcode1 = read4(addr+0, type);
-       uint32_t opcode2 = read4(addr+4, type);
+       uint32_t opcode1 = read4(addr, type);
+       uint32_t opcode2 = 0;
 
        if ((opcode1 & 0xfc000000) == 0x48000000)
        {
@@ -299,36 +300,38 @@ static void put_powerpc_valu(char* addr, uint32_t value, uint16_t type)
                uint32_t i = opcode1 & ~0x03fffffd;
                i |= value & 0x03fffffd;
                write4(i, addr, type);
+               return;
        }
-       else if (((opcode1 & 0xfc1f0000) == 0x3c000000) &&
-                ((opcode2 & 0xfc000000) == 0x60000000))
+       else if ((opcode1 & 0xfc1f0000) == 0x3c000000)
        {
-               /* addis / ori instruction pair */
-               uint16_t hi = value >> 16;
-               uint16_t lo = value & 0xffff;
+               /* addis, paired with a later instruction */
+               uint16_t distance, hi, lo;
+               bool adj;
 
-               write4((opcode1 & 0xffff0000) | hi, addr+0, type);
-               write4((opcode2 & 0xffff0000) | lo, addr+4, type);
-       }
-       else if (((opcode1 & 0xfc1f0000) == 0x3c000000) &&
-                is_powerpc_memory_op(opcode2))
-       {
-               /* addis / memoryop instruction pair */
-               uint16_t hi = value >> 16;
-               uint16_t lo = value & 0xffff;
+               distance = (opcode1 & 0xfc00) >> 8;
+               /* XXX addr + distance might be too big */
+               if (distance)
+                       opcode2 = read4(addr + distance, type);
 
-               /* Apply the sign adjustment (see mach/powerpc/as/mach5.c). */
+               hi = value >> 16;
+               lo = value & 0xffff;
 
-               if (lo > 0x7fff)
+               /* Apply the sign adjustment (see mach/powerpc/as/mach5.c). */
+               adj = is_powerpc_memory_op(opcode2);
+               if (adj && lo > 0x7fff)
                        hi++;
 
-               write4((opcode1 & 0xffff0000) | hi, addr+0, type);
-               write4((opcode2 & 0xffff0000) | lo, addr+4, type);
+               if (adj || is_powerpc_ori(opcode2)) {
+                       opcode1 = (opcode1 & 0xffff0000) | hi;
+                       opcode2 = (opcode2 & 0xffff0000) | lo;
+                       write4(opcode1, addr, type);
+                       write4(opcode2, addr + distance, type);
+                       return;
+               }
        }
 
-       else
-               fatal("Don't know how to write a PowerPC fixup to instructions 0x%08x+0x%08x",
-                       opcode1, opcode2);
+       fatal("Don't know how to write a PowerPC fixup to instructions 0x%08lx+0x%08lx",
+               (unsigned long)opcode1, (unsigned long)opcode2);
 }
 
 /*