(* Production of a bytecode executable file *)

open Misc BasicIO Nonstdio Miscsys Obj Fnlib Const Mixture Config;
open Code_dec Symtable Patch Tr_const;

val autolink = ref true
val verbose  = ref false

(* First pass: check the consistency of files *)

fun read_file name = 
    let val truename = find_in_path name
	val is = open_in_bin truename
	val tables =
	    let val n = input_binary_int is in
		seek_in is n;
		input_value is : compiled_unit_tables
	    end
	    handle x =>
		(close_in is;
		 msgIBlock 0;
		 errPrompt "Error on file ";
		 msgString truename; msgEOL();
		 msgEBlock();
		 raise x)
	val _ = close_in is
    in (truename, tables) end

exception WrongStamp and NotYet

fun check_file name stampOpt pending processed = 
  let val simplename = Filename.chop_suffix name ".uo"
      val uname = normalizedUnitName(Filename.basename simplename)
      val () =
	  if member uname pending then
	      raise Fail ("Unit " ^ name ^ " depends on itself")
	  else ()
      val () =
	  if member uname reservedUnitNames then
	      raise Fail ("Unit "^uname^" is built-in, and cannot be linked")
	  else ()

      val already = (SOME (Hasht.find (!watchDog) uname)) 
	            handle Subscript => NONE 

      fun needs subuname substamp processed =
	  (check_file (subuname ^ ".uo") (SOME substamp) 
	              (uname :: pending) processed)
	  handle WrongStamp => 
	              raise Fail ("Compiled body of unit " ^ uname 
				    ^ " is incompatible with unit "^ subuname)
	       | NotYet => 
		      raise Fail ("Unit " ^ subuname ^ " is mentioned by "
				  ^ uname ^ " but not yet linked")
  in 
      case already of
	  SOME stamp' =>
	      (case stampOpt of
		   SOME stamp => 
		       if stamp <> stamp' then raise WrongStamp 
		       else processed
		 | NONE => 
                       (msgIBlock 0;
			errPrompt "Warning: unit ";
			msgString uname; 
			msgString " is needed by a unit preceding it";
			msgEOL();
			msgEBlock();
			processed))
	| NONE => let val (truename, tables) = 
		          case stampOpt of 
			      NONE       => read_file name
			    | SOME stamp => 
				  if !autolink then 
				      let val res as (_, tables) = 
					  read_file name
				      in 
					  if stamp = #cu_sig_stamp tables then
					      res
					  else
					      raise WrongStamp 
				      end
				  else
				      raise NotYet 
		      val precedingUnits = 
			  Hasht.fold needs processed (#cu_mentions tables)
		   in 
		      Hasht.insert (!watchDog) uname (#cu_sig_stamp tables);
		      (uname, truename, tables) :: precedingUnits
		  end
  end

val check_file = fn name => fn processed => check_file name NONE [] processed


(* Second pass: determine which phrases are required *)

val missing_globals =
    ref (Hasht.new 1 : (QualifiedIdent * int, unit) Hasht.t)
;

fun is_in_missing g =
  (Hasht.find (!missing_globals) g; true)
  handle Subscript => false
;

fun remove_from_missing g =
  Hasht.remove (!missing_globals) g
;

fun add_to_missing g =
  Hasht.insert (!missing_globals) g ()
;

fun is_required (Reloc_setglobal g, _) = is_in_missing g
  | is_required _ = false
;

fun remove_required (Reloc_setglobal g, _) = remove_from_missing g
  | remove_required _ = ()
;

fun add_required (Reloc_getglobal g, _) = add_to_missing g
  | add_required _ = ()
;

fun scan_val uname (id, stamp) tolink =
  let val q = {qual=uname, id=[id]} in
    if is_in_missing (q, 0) then
      (remove_from_missing (q, 0);
       add_to_missing (q, stamp);
       (id, stamp) :: tolink)
    else
      tolink
  end;

fun scan_phrase (phr : compiled_phrase) tolink =
    let val (_, otherlist) = #cph_reloc phr
    in 
	if not (#cph_pure phr) orelse List.exists is_required otherlist then
	    (List.app remove_required otherlist;
	     List.app add_required otherlist;
	     phr :: tolink)
	else
	    tolink
    end;

fun scan_file (uname, truename, (tables : compiled_unit_tables)) tolink =
  let val exportedE = #cu_exc_ren_list tables
      val valRenList = #cu_val_ren_list tables
      val exportedV = foldL (scan_val uname) [] valRenList
      val phraseIndex = #cu_phrase_index tables
      val required = foldL scan_phrase [] phraseIndex
  in
    (uname, truename, required, exportedE, exportedV) :: tolink
  end;

(* Third pass : link in the required phrases. *)

fun link_object os (uname, truename, required, exportedE, exportedV) =
  let val is = open_in_bin truename in
    (List.app
       (fn (phr : compiled_phrase) =>
         let val () = seek_in is (#cph_pos phr)
             val buff = input(is, #cph_len phr)
             val () = if size buff < #cph_len phr
                      then raise Size else ()
         in
           patch_object buff 0 (#cph_reloc phr);
           output(os, buff)
         end)
       required;
     exportPublicNames uname exportedE exportedV;
     close_in is)
    handle x =>
      (close_in is;
       msgIBlock 0;
       errPrompt "Error while linking file ";
       msgString truename; msgEOL();
       msgEBlock();
       raise x)
  end;

(* To build the initial table of globals *)

local
  prim_val vector_ : int -> '_a -> '_a vector       = 2 "make_vect"
  prim_val sub_    : 'a vector -> int -> 'a         = 2 "get_vect_item"
  prim_val update_ : 'a vector -> int -> 'a -> unit = 3 "set_vect_item"
in

  fun emit_data os =
    let val len = number_of_globals()
        val globals = vector_ len (repr 0)
    in
      List.app
        (fn (n,sc) => update_ globals n (translStructuredConst sc))
        (!literal_table);
      output_value os globals
    end;

end;

(* To build a bytecode executable file *)

val write_symbols = ref false;
val no_header     = ref false;
val stand_alone   = ref false; (* cvr: 144 merge *)

fun reportLinked toscan =
    let fun reportUnit (uname, _, _) = (msgString uname; msgString ".uo ")
    in 
	msgIBlock 0; 
	msgString "Linking: ";
	List.app reportUnit (rev toscan); 
	msgEOL(); msgEBlock()
    end

fun link unit_list exec_name =
  let val _ = missing_globals :=  (* 04Sep95 e *)
               (Hasht.new 263 : (QualifiedIdent * int, unit) Hasht.t)
      val toscan = foldL check_file [] unit_list
      val _ = if !verbose then reportLinked toscan else ()
      val tolink = foldL scan_file [] toscan
      val os = if !no_header andalso not (!stand_alone) then 
	           open_out_bin exec_name
	       else 
		   open_out_exe exec_name
      fun copy name =
	  let val buff = CharArray.array(4096, #"\000")
	      val is = open_in_bin (Filename.concat (!path_library) name)
	      fun loop () = 
		  case buff_input is buff 0 4096 of
		      0 => ()
		    | n => (buff_output os buff 0 n; loop ())
	  in 
	      (loop (); close_in is) handle x => (close_in is; raise x)
	  end
  in
     ((* Prepend the runtime system? *)
      if !stand_alone then copy "camlrunm" else ();
      (* Prepend the header? *)
      if !no_header orelse !stand_alone then () else copy "header";
      missing_globals := (* for gc -- 04Sep95 e *)
       (Hasht.new 1 : (QualifiedIdent * int, unit) Hasht.t);
      (* The bytecode *)
      let val pos1 = pos_out os
          val () = List.app (link_object os) tolink
          val () = output_byte os Opcodes.STOP;
          (* The table of global data *)
          val pos2 = pos_out os
          val () = emit_data os
          (* Linker tables *)
          val pos3 = pos_out os
          val () =
            if !write_symbols then save_linker_tables os
            else ();
          (* Debugging info (none, presently) *)
          val pos4 = pos_out os
      in
        (* The trailer *)
        output_binary_int os (pos2 - pos1);
        output_binary_int os (pos3 - pos2);
        output_binary_int os (pos4 - pos3);
        output_binary_int os 0;
        output(os, "ML08");
        close_out os
      end
    ) handle x =>
       (close_out os;
        remove_file exec_name;
        raise x)
  end;



