(**************************************************************************)
(*                                                                        *)
(*                                 OCaml                                  *)
(*                                                                        *)
(*             Xavier Leroy, projet Cristal, INRIA Rocquencourt           *)
(*                                                                        *)
(*   Copyright 1996 Institut National de Recherche en Informatique et     *)
(*     en Automatique.                                                    *)
(*                                                                        *)
(*   All rights reserved.  This file is distributed under the terms of    *)
(*   the GNU Lesser General Public License version 2.1, with the          *)
(*   special exception on linking described in the file LICENSE.          *)
(*                                                                        *)
(**************************************************************************)

(* Native toplevel dynamic loading interface *)

open Config
open Misc
open Topcommon

type[@warning "-37"] res = Ok of Obj.t | Err of string

external ndl_run_toplevel: string -> string -> res
  = "caml_natdynlink_run_toplevel"

let lookup sym =
  Dynlink.unsafe_get_global_value ~bytecode_or_asm_symbol:sym

let need_symbol sym =
  Option.is_none (Dynlink.unsafe_get_global_value ~bytecode_or_asm_symbol:sym)

let dll_run dll entry =
  match ndl_run_toplevel dll entry with
  | Ok x -> Result x
  | Err s -> fatal_error ("Toploop.dll_run " ^ s)
  | exception exn -> Exception exn

(* CR-soon trefis for mshinwell: copy/pasted from Optmain. Should it be shared
   or?
   mshinwell: It should be shared, but after 4.03. *)
module Backend = struct
  (* See backend_intf.mli. *)

  let symbol_for_global' = Compilenv.symbol_for_global'
  let closure_symbol = Compilenv.closure_symbol

  let really_import_approx = Import_approx.really_import_approx
  let import_symbol = Import_approx.import_symbol

  let size_int = Arch.size_int
  let big_endian = Arch.big_endian

  let max_sensible_number_of_arguments =
    (* The "-1" is to allow for a potential closure environment parameter. *)
    Proc.max_arguments_for_tailcalls - 1
end
let backend = (module Backend : Backend_intf.S)

let load ppf phrase_name program =
  let dll =
    if !Clflags.keep_asm_file then phrase_name ^ ext_dll
    else Filename.temp_file ("caml" ^ phrase_name) ext_dll
  in
  let filename = Filename.chop_extension dll in
  let middle_end =
    if Config.flambda then Flambda_middle_end.lambda_to_clambda
    else Closure_middle_end.lambda_to_clambda
  in
  Asmgen.compile_implementation ~toplevel:need_symbol
    ~backend ~prefixname:filename
    ~middle_end ~ppf_dump:ppf program;
  Asmlink.call_linker_shared [filename ^ ext_obj] dll;
  Sys.remove (filename ^ ext_obj);

  let dll =
    if Filename.is_implicit dll
    then Filename.concat (Sys.getcwd ()) dll
    else dll in
  let remove_dll () =
    (* note: under windows, cannot remove a loaded dll
       (should remember the handles, close them in at_exit, and then
       remove files) *)
    try Sys.remove dll with Sys_error _ -> ()
  in
  match
    dll_run dll phrase_name
  with
  | res ->
     remove_dll ();
     res
  | exception x ->
     record_backtrace ();
     remove_dll ();
     Exception x

type lookup_fn = string -> Obj.t option
type load_fn =
  Format.formatter -> string -> Lambda.program -> Topcommon.evaluation_outcome
type assembler = {mutable lookup: lookup_fn; mutable load: load_fn}

let fns = {lookup; load}

let load ppf = fns.load ppf

let lookup sym = fns.lookup sym

let register_loader ~lookup ~load =
  fns.lookup <- lookup;
  fns.load <- load
