#!/usr/bin/lua -f

INSTALLROOT='/proj/rh71/install'

mksnow_version = "1.9.3"

function execute_or_die(s)
   local rv = execute(s)
   if rv ~= 0 then
      _ALERT("command failed: " .. s)
      _ALERT(" with error code " .. rv .. "\n")
      exit(1)
   end
end

function write_table(t) 
  write("{")
  for i,v in t do
    write(format("%s=%q, ", i, v))
  end
  write("}")
end

--SPEC_DIR='/opt/snow-gcc/specs'
SPEC_DIR=INSTALLROOT.."/lib/snow/specs"


if arg.n < 1 or arg.n > 2 then
   print("Usage: mksnow <lib> {init|rebuild}")
   exit(1)
end

library_name = gsub(arg[1], '(.*)%.a$', "%1")
spec_file = SPEC_DIR .."/" .. library_name .. ".spec"
order_file = SPEC_DIR .. "/" .. library_name .. ".order"

if arg.n == 2 then
   if arg[2] == "init" then
      mode_init = 1
      mode_default = nil
   elseif arg[2] == "rebuild" then
      mode_rebuild = 1
      mode_default = nil
   else
      print("Usage: mksnow <lib> {init|rebuild}")
      exit(1)
   end
else
   ofp = openfile(order_file, "r")
   if ofp == nil then
      mode_init = 1
   else
      closefile(ofp)
      mode_default = 1
   end
end

do 
  local success,msg = ""
  success,msg = readfrom(spec_file)
  if not success then 
    error("can't read spec file: " .. msg)
  end
end

spec = {}
while 1 do
  local _, matchcount
  local line = read()
  if not line then break end
  -- blank lines and comments
  _,matchcount = gsub(line, "^%s*(#.*)?$", "")
  if matchcount == 0 then
    _,matchcount = gsub(line, "^([^= ]+) *=\"?([^\"]*)\"?", 
      function(i,v) spec[i]=v end)
    if matchcount == 0 then
      _ALERT("garbage line in spec file: " .. line)
    end
  end
end
readfrom()

-- for i,v in spec do
--  print(i, v)
-- end

LDSCRIPT = library_name .."-ldscript"
SFILE= library_name .. "_syms.s"
OFILE= library_name .. "_syms.o"
SSOFILE= library_name .. "." .. spec.LIBRARY_MAJORVERSION .. ".sso"


-- done
-- snow-make-ldscript --startaddr=$LIBRARY_ADDRESS > $LDSCRIPT


-- print(gsub(l, '^(0...............) ([lg])([ w])   ([d ])([OFf])%s+(%S+)%s+(%w+)%s(%S+)', ''))

-- readfrom("|mipsel-linux-objdump --syms _test.o") -- " .. SSOFILE)
-- readfrom("|mipsel-linux-objdump --syms libtcl8.3.a") -- " .. SSOFILE)

function fp(...) if arg[2] ~= "l" and arg[3] ~="w" then print("---------") for i,v in arg do print(i,v) end end end

common_t = {}
function_t = {}
rodata_t = {}
data_t = {}

export_only = nil

function process_symbol(address, global, weak, a4, object_or_function, section, size, name)  
   -- print(address, global, weak, object_or_function, section, size,name)
   if export_only then
      if not strfind(section, "^%.export%.") then
         return
      end
   end
   -- pretend common symbols are global

   if section == "*ABS*" then
      return
   end

   if section == "*UND*" then
     return
   end

   if name == ".hidden" then
     return
   end

   if section == "*COM*" then global = 'g' end
   if weak == "w" then
      global = 'g'
   end
   if global ~= "g" then return end
   local output_table
   local skip_it = nil
   local is_really_bss = nil

   if object_or_function == "F" or strfind(name, "^__JUMP__") then
      output_table = function_t
   elseif object_or_function == "O" then
      if section == ".text" or strfind(section, "^%.gnu%.linkonce%.t%.")  then 
         -- This is us overriding a symbol from another library
         skip_it = 1
      elseif section == "*COM*" or section == ".scommon" or strfind(section,"^%.bss%.") or strfind(section,"^%.sbss%.") or strfind(section, "^%.export%.bss") then
         output_table = common_t
         if strfind(section,"^%.sbss") or strfind(section,"^%.bss") then
            is_really_bss = 1
         end
      else
         if strfind(section, "^%.data%.") then 
            output_table = data_t
         elseif strfind(section, "^%.gnu%.linkonce%.d%.") then 
            output_table = data_t
         elseif strfind(section, "^%.rodata%.") then
            output_table = rodata_t
         elseif strfind(section, "^%.gnu%.linkonce%.r%.") then 
            output_table = rodata_t
         elseif strfind(section, "^%.export%.data") then
            output_table = data_t
         elseif strfind(section, "^%.export%.rodata") then
            output_table = rodata_t
         elseif strfind(section, "^%.[cd]tors") then
            skip_it = 1
         elseif name == "__jump_init" or name == "__jump_fini" or name == "__start" or name == "_fini" or name == "__EH_FRAME_BEGIN__" then
            skip_it = 1
         elseif strfind(name, "^__NEEDS_SHRLIB") then
            skip_it = 1
         else
            error("unknown object symbol found: " .. name .. "  section: " .. section)
         end
      end
   else
      error("unknown symbol type found: " .. name)
   end
   
   if not skip_it then
      entry={address=address, size=size, name=name}
      if is_really_bss then
         entry.true_bss = 1
      end
      -- if output_table[name] then
      --  error("collision on name " .. name)
      -- end
      tinsert(output_table, entry)
   end
end


function read_symbol_table(filename, exported_only)
   export_only = exported_only
   if not readfrom("|mipsel-linux-objdump --syms " .. filename) then
      _ALERT("can't popen mipsel-linux-objdump!\n")
      exit(1)
   end
   while 1 do
      local line = read()
      if not line then break end
      local _,count
      if not (strfind(line, 'file format')  or strfind(line, 'SYMBOL TABLE:') or strfind(line, '^%s*$')) then
        -- _,count = gsub(line, '^(0...............) ([lg ])([w ])   ([d ])([OFf ])%s+(%S+)%s+(%S+)%s*(%S*)', process_symbol)
        print(line)
         _,count = gsub(line, '^(0.......) ([lg ])([w ])   ([d ])([OFf ])%s+(%S+)%s+(%S+)%s*(%S*)', process_symbol)
         if count ~= 1 then
            print("Unable to parse objdump symbol line:", line)
         end
      end
   end
   readfrom()               
end

-- read_symbol_table("_test.o")

-- sort(common_list, function (a, b) 
--    if a.address == b.address then return a.name < b.name end
--    return a.address < b.address
--  end
--)


function write_jumptable_s(function_l, common_l)
   local library_banner = "SNOW LIBRARY INFO: snow library " .. library_name .. " produced by mksnow v" .. mksnow_version .. " by " .. getenv("USER") .. "@" .. getenv("HOSTNAME") .. " at " .. date()

   if not writeto("__jumptable.s") then
      _ALERT("can't write to __jumptable.s!")
      exit(1)
   end
   write([[

 .section .rodata
 .asciiz "]] .. library_banner .. [["
 .byte 0x25,0xac,0xbe,0x4d,0xaa,0xe2,0x55,0xa9,0xf6,0x4a,0xd6,0xab

 .section .__jumptable,"ax"
 .global __start
__start:
 j __start
 .global __jump_init
__jump_init:
 j _init
 .global __jump_fini
__jump_fini:
 j _fini
 j __start
 j __start
 j __start
]])

   for i = 1,getn(function_l) do
      local fname = function_l[i].name
      local jumpname = "__JUMP__" .. fname
      write(" .global " .. jumpname .."\n")
      write(jumpname .. ": j " .. fname .. "\n\n")
   end

   for i = 1,getn(common_l) do
       local entry = common_l[i]
       if not entry.true_bss then
          local align = tonumber(entry.size, 16)
          if align > 8 then
             align = 8
          end
          write(" .section .bss."..entry.name..",\"aw\",@nobits\n")
          write(" .balign ", align, "\n")
          write(" .global ", entry.name, "\n")
          -- write(entry.name, ": .skip 0x", entry.address, "\n")
          write(" .type "..entry.name..",@object\n")
          write(" .size "..entry.name..",0x"..entry.size.."\n")
          write(entry.name, ": .space 0x", entry.size, "\n")
          -- write(" .comm ", entry.name, ", 0x", entry.address, "\n")
       end
    end
    writeto()
end

function write_rodata_sections()
  for i=1,getn(rodata_t) do
    write("  *(.rodata." .. rodata_t[i].name .. ")\n")
    write("  *(.gnu.linkonce.r." .. rodata_t[i].name .. ")\n")
  end
end
  
function write_data_sections()
  for i=1,getn(data_t) do
    write("  *(.data." .. data_t[i].name .. ")\n")
    write("  *(.gnu.linkonce.d." .. data_t[i].name .. ")\n")
    write("  *(.sdata." .. data_t[i].name .. ")\n")
    write("  *(.gnu.linkonce.s." .. data_t[i].name .. ")\n")
  end
end


function write_bss_sections()
  for i=1,getn(common_t) do
     local entry = common_t[i]
    write("  *(.bss." .. entry.name .. ")\n")
    write("  *(.gnu.linkonce.b." .. entry.name .. ")\n")
    write("  *(.sbss." .. entry.name .. ")\n")
    write("  *(.gnu.linkonce.sb." .. entry.name .. ")\n")
  end
end


function dump_order(filename)
   if not writeto(filename) then
      _ALERT("can't write order to " .. filename .. "!\n")
      exit(1)
   end
   
   write("function_order={\n")
   for i=1,getn(function_t) do
      write(format("  {name=%q},\n", function_t[i].name))
   end
   write("}\n\n")

   write("rodata_order={\n")
   for i=1,getn(rodata_t) do
      write(format("  {name=%q, size=%q},\n", rodata_t[i].name, rodata_t[i].size))
   end
   write("}\n\n")

   write("data_order={\n")
   for i=1,getn(data_t) do
      item = data_t[i]
      write(format("  {name=%q, size=%q},\n", item.name, item.size))
   end
   write("}\n\n")

   write("common_order={\n")
   for i=1,getn(common_t) do
      item = common_t[i]
      if item.true_bss then
         write(format("  {name=%q, size=%q, true_bss=1},\n", item.name, item.size))
      else
         write(format("  {name=%q, size=%q},\n", item.name, item.size))
      end
   end
   write("}\n\n")
   writeto()
end


-- execute_or_die("mipsel-linux-as -o __jumptable.o __jumptable.s")

-- CMD="mipsel-linux-objdump --syms $SSOFILE | \
--   snow-makesyms --libname $LIBRARY_NAME \
--   --noinit --majorversion 1"
-- for LIB in $LIBRARY_DEPENDS; do
--   CMD="$CMD --library $LIB"
-- done
-- eval $CMD > $SFILE
-- mipsel-linux-as $SFILE -o $OFILE
-- mipsel-linux-ar rc ${LIBRARY_NAME}.sa $OFILE
-- mipsel-linux-ranlib ${LIBRARY_NAME}.sa
-- 
-- #
-- # Cleanup.
-- #
-- rm $LDSCRIPT
-- rm $SFILE
-- rm $OFILE

function write_ldscript()
   if not writeto(LDSCRIPT) then
      _ALERT("can't write " .. LDSCRIPT .. "!\n")
      exit(1)
   end

write(
[[OUTPUT_FORMAT("elf32-tradlittlemips")
OUTPUT_ARCH(mips)
ENTRY(__start)
PHDRS
{
  /* headers PT_PHDR PHDRS ;   */
  jumpheader PT_LOAD PHDRS FILEHDR /* AT ( ]]..spec.LIBRARY_ADDRESS..[[ ) FLAGS ( 5 ) */;
  exportdata PT_LOAD ;
  text PT_LOAD ;
  data PT_LOAD ;
  /* foo PT_REGINFO ; */
  
}

SECTIONS
{
  . = ]]..spec.LIBRARY_ADDRESS..[[ + SIZEOF_HEADERS ;
  /* Read-only sections, merged into text segment: */
  /* . = __library_start + SIZEOF_HEADERS; */
  /* . += SIZEOF_HEADERS; */
  /DISCARD/ : { *(.reginfo) }
  /* .reginfo       : { *(.reginfo) } :reginfo */
  /* . = ALIGN(0x1000); */
  .dynamic       : { *(.dynamic) } 
  .dynstr        : { *(.dynstr)		}
  .dynsym        : { *(.dynsym)		}
  .hash          : { *(.hash)		}
  .rel.text      : { *(.rel.text)		}
  .rela.text     : { *(.rela.text) 	}
  .rel.data      : { *(.rel.data)		}
  .rela.data     : { *(.rela.data) 	}
  .rel.rodata    : { *(.rel.rodata) 	}
  .rela.rodata   : { *(.rela.rodata) 	}
  .rel.got       : { *(.rel.got)		}
  .rela.got      : { *(.rela.got)		}
  .rel.ctors     : { *(.rel.ctors)	}
  .rela.ctors    : { *(.rela.ctors)	}
  .rel.dtors     : { *(.rel.dtors)	}
  .rela.dtors    : { *(.rela.dtors)	}
  .rel.init      : { *(.rel.init)	}
  .rela.init     : { *(.rela.init)	}
  .rel.fini      : { *(.rel.fini)	}
  .rela.fini     : { *(.rela.fini)	}
  .rel.bss       : { *(.rel.bss)		}
  .rela.bss      : { *(.rela.bss)		}
  .rel.plt       : { *(.rel.plt)		}
  .rela.plt      : { *(.rela.plt)		}

  . = ALIGN(0x100) ;

  .export.jumptable : {
    *(.__jumptable)
  } :jumpheader

  . += 400 ; /* allow for least 50 new functions */
  . = ALIGN(0x1000) ;

  .export.rodata : { 
]])
write_rodata_sections()
write ([[
  } :jumpheader 
  . += 0x800 ; /* allow for half a page more */
  . = ALIGN(0x1000) ;
  .export.data : {
]])
write_data_sections()
write([[
    . += 0x400 ;

  } :exportdata

  .export.bss : {
]])
write_bss_sections()
write([[
   } :exportdata

  . = ALIGN(0x1000) ;

  .text      :
  {
  *(.library_list)
  
  /* This doesn't belong here, but I'm having problems getting binutils
     to give me more segments to write to.  -- nop@nop.com */
  *(.rodata)
  *(.rodata.*)
  *(.rodata1) 
  *(.gnu.linkonce.r.*)

  *(.init)

    *(.text)
    *(.text.*)
    *(.stub)
    /* .gnu.warning sections are handled specially by elf32.em.  */
    *(.gnu.warning)
    *(.gnu.linkonce.t.*)
  } :text
  .fini      : { *(.fini)    } =0
  /* Adjust the address for the data segment.  We want to adjust up to
     the same address within the page on the next page up.  It would
     be more correct to do this:
       . = 0x10000000;
     The current expression does not correctly handle the case of a
     text segment ending precisely at the end of a page; it causes the
     data segment to skip a page.  The above expression does not have
     this problem, but it will currently (2/95) cause BFD to allocate
     a single segment, combining both text and data, for this case.
     This will prevent the text segment from being shared among
     multiple executions of the program; I think that is more
     important than losing a page of the virtual address space (note
     that no actual memory is lost; the page which is skipped can not
     be referenced).  */
  /* . += 0x10000; */
  /* . += ALIGN(0x1000); */
  /* . = ALIGN(0x40000); */
  . += 0x10000; 
  /* . = ALIGN(0x40000) + (ALIGN(8) & (0x40000 - 1)); */
  .data    :
  {
    *(.data)
    *(.data.*)
    *(.gnu.linkonce.d.*)
    *(.eh_frame)
    CONSTRUCTORS
  } :data
  .data1   : { *(.data1) } 
  .gcc_except_table : { *(.gcc_except_table) }
  .ctors         : { *(.ctors)   }
  .dtors         : { *(.dtors)   }
  /* _gp = ALIGN(16) + 0x7ff0; */
  .got           :
  {
    *(.got.plt) *(.got)
   }
  /* We want the small data sections together, so single-instruction offsets
     can access them all, and initialized data all before uninitialized, so
     we can shorten the on-disk segment size.  */
  .sdata     : { *(.sdata) }
  .lit8 : { *(.lit8) }
  .lit4 : { *(.lit4) }
  .sbss      : { *(.sbss) *(.scommon) *(.gnu.linkonce.sb*) }
  .bss       :
  {
   *(.dynbss)
   *(.bss)
   *(.bss.*)
   *(.gnu.linkonce.b*)
   *(COMMON)
  }
  /* The normal linker scripts created by the binutils doesn't have the
     symbols end and _end which breaks ld.so's dl-minimal.c.  */
  /* _end = . ; */
  PROVIDE (end = .);
  /* These are needed for ELF backends which have not yet been
     converted to the new style linker.  */
  .stab 0 : { *(.stab) }
  .stabstr 0 : { *(.stabstr) }
  /* DWARF debug sections.
     Symbols in the .debug DWARF section are relative to the beginning of the
     section so we begin .debug at 0.  It''s not clear yet what needs to happen
     for the others.   */
  .debug          0 : { *(.debug) } :dumpme
  .debug_srcinfo  0 : { *(.debug_srcinfo) }
  .debug_aranges  0 : { *(.debug_aranges) }
  .debug_pubnames 0 : { *(.debug_pubnames) }
  .debug_sfnames  0 : { *(.debug_sfnames) }
  .line           0 : { *(.line) }
  /* These must appear regardless of  .  */
  .gptab.sdata : { *(.gptab.data) *(.gptab.sdata) }
  .gptab.sbss : { *(.gptab.bss) *(.gptab.sbss) }
}
]])

  write("\n") flush()
  writeto()
end

function do_real_link()
  -- local gl = function(s) return INSTALLROOT.."/mipsel-linux/lib/" .. s end
  local gl = function(s) return INSTALLROOT.."/mipsel-linux/lib/soft-float/pic-snow/" .. s end
  local sl = function(s) return INSTALLROOT.."/lib/snow/" .. s end
 
--  local ld_args = {"mipsel-linux-ld", "-T", LDSCRIPT, gl'crti.o', sl'crtbegin.o', "__jumptable.o",
--   "--whole-archive", spec.LIBRARY_ARCHIVES, "--no-whole-archive",
--    sl'crtend.o', gl'crtn.o', "-L/opt/snow-gcc/lib/snow",
--    spec.LIBRARY_DEPEND_PATHS, "-o", SSOFILE}
  


  local striplist = ""
  for i=1,getn(common_t) do
     local entry = common_t[i]

     if entry.true_bss then
        -- originally I was using -N to nuke symbols, but sometimes innocent bystanders got caught.  Now just weaken them.
        striplist = striplist .. " -W "..entry.name
        -- striplist = striplist .. " -N "..entry.name
     end
  end
  
  local archive_list = {n=0}
  gsub(spec.LIBRARY_ARCHIVES, '(lib[^ ]+)', function (n) tinsert(%archive_list, n) end)
  execute_or_die("rm -rf _temp_lib_dir/")
  execute_or_die("mkdir _temp_lib_dir")

  local new_archive_names = ""
  for i=1,getn(archive_list) do
     local archive = archive_list[i]
     local new_archive_name = "_temp_lib_dir/"..archive
     new_archive_names = new_archive_names .. " " .. new_archive_name
     -- print("stripping", "mipsel-linux-objcopy "..striplist.." "..archive.." "..new_archive_name)
     execute_or_die("mipsel-linux-objcopy "..striplist.." "..archive.." "..new_archive_name)
  end

  local ld_args = {"mipsel-linux-ld", "-T", LDSCRIPT, gl'crti.o', sl'crtbeginS.o', "__jumptable.o",
    "--whole-archive", new_archive_names, "--no-whole-archive",
    sl'crtendS.o', gl'crtn.o', "-L"..INSTALLROOT.."/lib/snow",
    spec.LIBRARY_DEPEND_PATHS, "-o", SSOFILE}

  local ld_cmd = ""
  for i = 1,getn(ld_args) do ld_cmd = ld_cmd .. ld_args[i] .. ' ' end

  -- print(ld_cmd)
  execute_or_die(ld_cmd)
end

function do_trial_link()
  -- local gl = function(s) return INSTALLROOT.."/mipsel-linux/lib/" .. s end
  local gl = function(s) return INSTALLROOT.."/mipsel-linux/lib/soft-float/pic-snow/" .. s end
  local sl = function(s) return INSTALLROOT.."/lib/snow/" .. s end

  local ld_args = {"mipsel-linux-ld",  "-r",
    "--whole-archive", spec.LIBRARY_ARCHIVES, "--no-whole-archive",
    "-L"..INSTALLROOT.."/lib/snow", 
    spec.LIBRARY_DEPEND_PATHS, "-o", "_trial.sso"}

 local ld_cmd = ""
 write(ld_cmd .. "\n")
 for i = 1,getn(ld_args) do ld_cmd = ld_cmd .. ld_args[i] .. ' ' end

 -- print(ld_cmd)
 execute_or_die(ld_cmd)
end

function size_alphabetically(a,b)
   if a.size == b.size then
      return a.name < b.name
   else
      return a.size < b.size
   end
end

function write_stub_entry(symbol, value)
   write(format(
[[  .global %s
  %s = 0x%s
  .weak %s
]], symbol, symbol, value, symbol))
end

function write_stub_library()
   if not writeto("__stubs.s") then
      _ALERT("can't write to __stubs.s!\n")
      exit(1)
   end
   for i = 1,getn(function_t) do
      local entry = function_t[i]
      local realname, match
      realname,match = gsub(entry.name, "^__JUMP__", "")
      if match == 1 then
         write_stub_entry(realname, entry.address)
      end
   end
   for i = 1,getn(rodata_t) do
      write_stub_entry(rodata_t[i].name, rodata_t[i].address)
   end
   for i = 1,getn(data_t) do
      write_stub_entry(data_t[i].name, data_t[i].address)
   end
   for i = 1,getn(common_t) do
      write_stub_entry(common_t[i].name, common_t[i].address)
   end

   local init_addr, fini_addr
   if do_init then
      -- busted
   else
      init_addr = "0"
      fini_addr = "0"
   end

   local needsym = "__NEEDS_SHRLIB_" .. library_name
   write(
[[
  .section .rodata
  .global ]]..needsym ..[[
  ]]..needsym..[[ = 0

libname:
  .asciiz "]]..library_name.."."..spec.LIBRARY_MAJORVERSION..[[.sso"

libinfo:
  .word libname
  .word ]]..init_addr..[[
  .word ]]..fini_addr..[[

  .section .library_list
  .word libinfo
]])
   gsub(spec.LIBRARY_DEPENDS, "(lib[%w%.%_%-]+)", function (n) 
                                             write("  .section .rodata\n")
                                             write("  .word __NEEDS_SHRLIB_" ..n.."\n") end)
   writeto()
end

if mode_init or mode_default then
   do_trial_link()
   
   read_symbol_table("_trial.sso", nil)

   sort(function_t, function (a,b) return a.name < b.name end)
   sort(rodata_t, size_alphabetically)
   sort(data_t, size_alphabetically)
   sort(common_t, size_alphabetically)

   dump_order(library_name .. ".order")
   execute("mv -f " .. library_name .. ".order " .. order_file)
end

-- read stuff from _order
if not dofile(library_name .. ".order") then
   local f = SPEC_DIR .. "/" .. library_name .. ".order"
   print("not found locally; trying " .. f)
   if not dofile(f) then
      _ALERT("couldn't find " .. library_name .. ".order in current directory or " .. SPEC_DIR .. "\n")
      exit(1)
   end
end
function_t = function_order
rodata_t = rodata_order
data_t = data_order
common_t = common_order

write_jumptable_s(function_t, common_t)
execute_or_die("mipsel-linux-as -o __jumptable.o __jumptable.s")
write_ldscript()
do_real_link()
function_t = {}
rodata_t = {}
data_t = {}
common_t = {}
read_symbol_table(SSOFILE, 1)
write_stub_library()
execute_or_die("mipsel-linux-as __stubs.s -o __stubs.o")
execute_or_die("mipsel-linux-ar cr " .. library_name .. ".sa __stubs.o")
execute_or_die("mipsel-linux-ranlib " .. library_name .. ".sa")
execute_or_die("mipsel-linux-objdump --syms " .. library_name .. ".sa >" .. library_name .. ".verify")

-- execute_or_die("rm -f _trial.sso __stubs.s __stubs.o __jumptable.s __jumptable.o " .. library_name .. "-ldscript")
-- execute_or_die("rm -rf _temp_lib_dir/")
