2011-05-09 Paul Brook <paul@codesourcery.com>
[binutils-gdb.git] / gas / write.c
index a148b248d20bd31de5ea6ab0dc3fdd76dda7f446..1bd1a622cbe0f189174bdd752e96da1c695d679c 100644 (file)
@@ -1,7 +1,7 @@
 /* write.c - emit .o file
    Copyright 1986, 1987, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997,
-   1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009
-   Free Software Foundation, Inc.
+   1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
+   2010 Free Software Foundation, Inc.
 
    This file is part of GAS, the GNU Assembler.
 
@@ -28,6 +28,7 @@
 #include "output-file.h"
 #include "dwarf2dbg.h"
 #include "libbfd.h"
+#include "compress-debug.h"
 
 #ifndef TC_ADJUST_RELOC_COUNT
 #define TC_ADJUST_RELOC_COUNT(FIX, COUNT)
@@ -931,6 +932,8 @@ fixup_segment (fixS *fixP, segT this_segment)
          sub_symbol_segment = S_GET_SEGMENT (fixP->fx_subsy);
          if (fixP->fx_addsy != NULL
              && sub_symbol_segment == add_symbol_segment
+             && !S_FORCE_RELOC (fixP->fx_addsy, 0)
+             && !S_FORCE_RELOC (fixP->fx_subsy, 0)
              && !TC_FORCE_RELOCATION_SUB_SAME (fixP, add_symbol_segment))
            {
              add_number += S_GET_VALUE (fixP->fx_addsy);
@@ -944,6 +947,7 @@ fixup_segment (fixS *fixP, segT this_segment)
 #endif
            }
          else if (sub_symbol_segment == absolute_section
+                  && !S_FORCE_RELOC (fixP->fx_subsy, 0)
                   && !TC_FORCE_RELOCATION_SUB_ABS (fixP, add_symbol_segment))
            {
              add_number -= S_GET_VALUE (fixP->fx_subsy);
@@ -951,6 +955,7 @@ fixup_segment (fixS *fixP, segT this_segment)
              fixP->fx_subsy = NULL;
            }
          else if (sub_symbol_segment == this_segment
+                  && !S_FORCE_RELOC (fixP->fx_subsy, 0)
                   && !TC_FORCE_RELOCATION_SUB_LOCAL (fixP, add_symbol_segment))
            {
              add_number -= S_GET_VALUE (fixP->fx_subsy);
@@ -993,6 +998,7 @@ fixup_segment (fixS *fixP, segT this_segment)
       if (fixP->fx_addsy)
        {
          if (add_symbol_segment == this_segment
+             && !S_FORCE_RELOC (fixP->fx_addsy, 0)
              && !TC_FORCE_RELOCATION_LOCAL (fixP))
            {
              /* This fixup was made when the symbol's segment was
@@ -1006,6 +1012,7 @@ fixup_segment (fixS *fixP, segT this_segment)
              fixP->fx_pcrel = 0;
            }
          else if (add_symbol_segment == absolute_section
+                  && !S_FORCE_RELOC (fixP->fx_addsy, 0)
                   && !TC_FORCE_RELOCATION_ABS (fixP))
            {
              add_number += S_GET_VALUE (fixP->fx_addsy);
@@ -1288,6 +1295,195 @@ write_relocs (bfd *abfd, asection *sec, void *xxx ATTRIBUTE_UNUSED)
 #endif
 }
 
+static int
+compress_frag (struct z_stream_s *strm, const char *contents, int in_size,
+              fragS **last_newf, struct obstack *ob)
+{
+  int out_size;
+  int total_out_size = 0;
+  fragS *f = *last_newf;
+  char *next_out;
+  int avail_out;
+
+  /* Call the compression routine repeatedly until it has finished
+     processing the frag.  */
+  while (in_size > 0)
+    {
+      /* Reserve all the space available in the current chunk.
+         If none is available, start a new frag.  */
+      avail_out = obstack_room (ob);
+      if (avail_out <= 0)
+        {
+          obstack_finish (ob);
+          f = frag_alloc (ob);
+         f->fr_type = rs_fill;
+          (*last_newf)->fr_next = f;
+          *last_newf = f;
+          avail_out = obstack_room (ob);
+        }
+      if (avail_out <= 0)
+       as_fatal (_("can't extend frag"));
+      next_out = obstack_next_free (ob);
+      obstack_blank_fast (ob, avail_out);
+      out_size = compress_data (strm, &contents, &in_size,
+                               &next_out, &avail_out);
+      if (out_size < 0)
+        return -1;
+
+      f->fr_fix += out_size;
+      total_out_size += out_size;
+
+      /* Return unused space.  */
+      if (avail_out > 0)
+       obstack_blank_fast (ob, -avail_out);
+    }
+
+  return total_out_size;
+}
+
+static void
+compress_debug (bfd *abfd, asection *sec, void *xxx ATTRIBUTE_UNUSED)
+{
+  segment_info_type *seginfo = seg_info (sec);
+  fragS *f;
+  fragS *first_newf;
+  fragS *last_newf;
+  struct obstack *ob = &seginfo->frchainP->frch_obstack;
+  bfd_size_type uncompressed_size = (bfd_size_type) sec->size;
+  bfd_size_type compressed_size;
+  const char *section_name;
+  char *compressed_name;
+  char *header;
+  struct z_stream_s *strm;
+  int x;
+  flagword flags = bfd_get_section_flags (abfd, sec);
+
+  if (seginfo == NULL
+      || sec->size < 32
+      || (flags & (SEC_ALLOC | SEC_HAS_CONTENTS)) == SEC_ALLOC)
+    return;
+
+  section_name = bfd_get_section_name (stdoutput, sec);
+  if (strncmp (section_name, ".debug_", 7) != 0)
+    return;
+
+  strm = compress_init ();
+  if (strm == NULL)
+    return;
+
+  /* Create a new frag to contain the "ZLIB" header.  */
+  first_newf = frag_alloc (ob);
+  if (obstack_room (ob) < 12)
+    first_newf = frag_alloc (ob);
+  if (obstack_room (ob) < 12)
+    as_fatal (_("can't extend frag %u chars"), 12);
+  last_newf = first_newf;
+  obstack_blank_fast (ob, 12);
+  last_newf->fr_type = rs_fill;
+  last_newf->fr_fix = 12;
+  header = last_newf->fr_literal;
+  memcpy (header, "ZLIB", 4);
+  header[11] = uncompressed_size; uncompressed_size >>= 8;
+  header[10] = uncompressed_size; uncompressed_size >>= 8;
+  header[9] = uncompressed_size; uncompressed_size >>= 8;
+  header[8] = uncompressed_size; uncompressed_size >>= 8;
+  header[7] = uncompressed_size; uncompressed_size >>= 8;
+  header[6] = uncompressed_size; uncompressed_size >>= 8;
+  header[5] = uncompressed_size; uncompressed_size >>= 8;
+  header[4] = uncompressed_size;
+  compressed_size = 12;
+
+  /* Stream the frags through the compression engine, adding new frags
+     as necessary to accomodate the compressed output.  */
+  for (f = seginfo->frchainP->frch_root;
+       f;
+       f = f->fr_next)
+    {
+      offsetT fill_size;
+      char *fill_literal;
+      offsetT count;
+      int out_size;
+
+      gas_assert (f->fr_type == rs_fill);
+      if (f->fr_fix)
+       {
+         out_size = compress_frag (strm, f->fr_literal, f->fr_fix,
+                                   &last_newf, ob);
+         if (out_size < 0)
+           return;
+         compressed_size += out_size;
+       }
+      fill_literal = f->fr_literal + f->fr_fix;
+      fill_size = f->fr_var;
+      count = f->fr_offset;
+      gas_assert (count >= 0);
+      if (fill_size && count)
+       {
+         while (count--)
+           {
+             out_size = compress_frag (strm, fill_literal, (int) fill_size,
+                                       &last_newf, ob);
+             if (out_size < 0)
+               return;
+             compressed_size += out_size;
+           }
+       }
+    }
+
+  /* Flush the compression state.  */
+  for (;;)
+    {
+      int avail_out;
+      char *next_out;
+      int out_size;
+
+      /* Reserve all the space available in the current chunk.
+        If none is available, start a new frag.  */
+      avail_out = obstack_room (ob);
+      if (avail_out <= 0)
+       {
+         fragS *newf;
+
+         obstack_finish (ob);
+         newf = frag_alloc (ob);
+         newf->fr_type = rs_fill;
+         last_newf->fr_next = newf;
+         last_newf = newf;
+         avail_out = obstack_room (ob);
+       }
+      if (avail_out <= 0)
+       as_fatal (_("can't extend frag"));
+      next_out = obstack_next_free (ob);
+      obstack_blank_fast (ob, avail_out);
+      x = compress_finish (strm, &next_out, &avail_out, &out_size);
+      if (x < 0)
+       return;
+
+      last_newf->fr_fix += out_size;
+      compressed_size += out_size;
+
+      /* Return unused space.  */
+      if (avail_out > 0)
+       obstack_blank_fast (ob, -avail_out);
+
+      if (x == 0)
+       break;
+    }
+
+  /* Replace the uncompressed frag list with the compressed frag list.  */
+  seginfo->frchainP->frch_root = first_newf;
+  seginfo->frchainP->frch_last = last_newf;
+
+  /* Update the section size and its name.  */
+  x = bfd_set_section_size (abfd, sec, compressed_size);
+  gas_assert (x);
+  compressed_name = (char *) xmalloc (strlen (section_name) + 2);
+  compressed_name[0] = '.';
+  compressed_name[1] = 'z';
+  strcpy (compressed_name + 2, section_name + 1);
+  bfd_section_name (stdoutput, sec) = compressed_name;
+}
+
 static void
 write_contents (bfd *abfd ATTRIBUTE_UNUSED,
                asection *sec,
@@ -1369,7 +1565,9 @@ write_contents (bfd *abfd ATTRIBUTE_UNUSED,
                    (stdoutput, sec, buf, (file_ptr) offset,
                     (bfd_size_type) n_per_buf * fill_size);
                  if (!x)
-                   as_fatal (_("cannot write to output file"));
+                   as_fatal (_("cannot write to output file '%s': %s"),
+                             stdoutput->filename,
+                             bfd_errmsg (bfd_get_error ()));
                  offset += n_per_buf * fill_size;
                }
            }
@@ -1536,13 +1734,6 @@ write_object_file (void)
       }
   }
 
-#ifdef OBJ_VMS
-  /* Under VMS we try to be compatible with VAX-11 "C".  Thus, we call
-     a routine to check for the definition of the procedure "_main",
-     and if so -- fix it up so that it can be program entry point.  */
-  vms_check_for_main ();
-#endif /* OBJ_VMS  */
-
   /* From now on, we don't care about sub-segments.  Build one frag chain
      for each segment. Linked thru fr_next.  */
 
@@ -1912,6 +2103,13 @@ write_object_file (void)
   obj_frob_file_after_relocs ();
 #endif
 
+  /* Once all relocations have been written, we can compress the
+     contents of the debug sections.  This needs to be done before
+     we start writing any sections, because it will affect the file
+     layout, which is fixed once we start writing contents.  */
+  if (flag_compress_debug)
+    bfd_map_over_sections (stdoutput, compress_debug, (char *) 0);
+
   bfd_map_over_sections (stdoutput, write_contents, (char *) 0);
 }
 
@@ -1952,16 +2150,28 @@ relax_frag (segT segment, fragS *fragP, long stretch)
            || sym_frag == &zero_address_frag);
       target += S_GET_VALUE (symbolP);
 
-      /* If frag has yet to be reached on this pass,
-        assume it will move by STRETCH just as we did.
-        If this is not so, it will be because some frag
-        between grows, and that will force another pass.  */
+      /* If SYM_FRAG has yet to be reached on this pass, assume it
+        will move by STRETCH just as we did, unless there is an
+        alignment frag between here and SYM_FRAG.  An alignment may
+        well absorb any STRETCH, and we don't want to choose a larger
+        branch insn by overestimating the needed reach of this
+        branch.  It isn't critical to calculate TARGET exactly;  We
+        know we'll be doing another pass if STRETCH is non-zero.  */
 
       if (stretch != 0
          && sym_frag->relax_marker != fragP->relax_marker
          && S_GET_SEGMENT (symbolP) == segment)
        {
-         target += stretch;
+         if (stretch < 0
+             || sym_frag->region == fragP->region)
+           target += stretch;
+         /* If we get here we know we have a forward branch.  This
+            relax pass may have stretched previous instructions so
+            far that omitting STRETCH would make the branch
+            negative.  Don't allow this in case the negative reach is
+            large enough to require a larger branch instruction.  */
+         else if (target < address)
+           target = fragP->fr_next->fr_address + stretch;
        }
     }
 
@@ -2049,6 +2259,7 @@ relax_segment (struct frag *segment_frag_root, segT segment, int pass)
   unsigned long frag_count;
   struct frag *fragP;
   relax_addressT address;
+  int region;
   int ret;
 
   /* In case md_estimate_size_before_relax() wants to make fixSs.  */
@@ -2057,10 +2268,12 @@ relax_segment (struct frag *segment_frag_root, segT segment, int pass)
   /* For each frag in segment: count and store  (a 1st guess of)
      fr_address.  */
   address = 0;
+  region = 0;
   for (frag_count = 0, fragP = segment_frag_root;
        fragP;
        fragP = fragP->fr_next, frag_count ++)
     {
+      fragP->region = region;
       fragP->relax_marker = 0;
       fragP->fr_address = address;
       address += fragP->fr_fix;
@@ -2089,12 +2302,16 @@ relax_segment (struct frag *segment_frag_root, segT segment, int pass)
              }
 
            address += offset;
+           region += 1;
          }
          break;
 
        case rs_org:
-       case rs_space:
          /* Assume .org is nugatory. It will grow with 1st relax.  */
+         region += 1;
+         break;
+
+       case rs_space:
          break;
 
        case rs_machine_dependent:
@@ -2317,6 +2534,7 @@ relax_segment (struct frag *segment_frag_root, segT segment, int pass)
                              fragP->fr_literal + fragP->fr_fix,
                              fragP->fr_var);
                      newf->fr_type = rs_fill;
+                     newf->fr_address = address + fragP->fr_fix + newoff;
                      newf->fr_fix = 0;
                      newf->fr_offset = (((offsetT) 1 << fragP->fr_offset)
                                         / fragP->fr_var);
@@ -2326,13 +2544,11 @@ relax_segment (struct frag *segment_frag_root, segT segment, int pass)
                          newf->fr_offset = (offsetT) 1 << fragP->fr_offset;
                          newf->fr_var = 1;
                        }
-                     /* Include growth of new frag, because rs_fill
-                        frags don't normally grow.  */
+                     /* Include size of new frag in GROWTH.  */
                      growth += newf->fr_offset * newf->fr_var;
-                     /* The new frag address is newoff.  Adjust this
-                        for the amount we'll add when we process the
-                        new frag.  */
-                     newf->fr_address = newoff - stretch - growth;
+                     /* Adjust the new frag address for the amount
+                        we'll add when we process the new frag.  */
+                     newf->fr_address -= stretch + growth;
                      newf->relax_marker ^= 1;
                      fragP->fr_next = newf;
 #ifdef DEBUG