(**************************************************************************)
(*                                                                        *)
(*                                 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.          *)
(*                                                                        *)
(**************************************************************************)

(* To assign numbers to globals and primitives *)

open Dynlink_cmo_format
module Config = Dynlink_config

module Style = struct
  let inline_code = Format.pp_print_string
end

#25 "bytecomp/symtable.ml"
module Compunit = struct
  type t = compunit
  let name (Compunit cu_name) = cu_name
  let is_packed (Compunit name) = String.contains name '.'
#32 "bytecomp/symtable.ml"
end
#42 "bytecomp/symtable.ml"
module Global = struct
  type t =
    | Glob_compunit of compunit
    | Glob_predef of predef

  let name = function
    | Glob_compunit (Compunit cu) -> cu
    | Glob_predef (Predef_exn exn) -> exn

  let quote s = "`" ^ s ^ "'"

  let description ppf g =
#46 "otherlibs/dynlink/byte/dynlink_symtable.ml"
    let open Format in
#55 "bytecomp/symtable.ml"
    match g with
    | Glob_compunit (Compunit cu) ->
        fprintf ppf "compilation unit %a"
          Style.inline_code (quote cu)
    | Glob_predef (Predef_exn exn) ->
        fprintf ppf "predefined exception %a"
          Style.inline_code (quote exn)
#72 "bytecomp/symtable.ml"
  module Map = Map.Make(struct type nonrec t = t let compare = compare end)
end
#77 "bytecomp/symtable.ml"
type error =
    Undefined_global of Global.t
  | Unavailable_primitive of string
  | Wrong_vm of string
  | Uninitialized_global of Global.t

exception Error of error
#67 "otherlibs/dynlink/byte/dynlink_symtable.ml"
module Dll = struct
#18 "bytecomp/dll.ml"
type dll_handle
type dll_address
#22 "bytecomp/dll.ml"
external dll_open: string -> dll_handle = "caml_dynlink_open_lib"
#24 "bytecomp/dll.ml"
external dll_sym: dll_handle -> string -> dll_address
                = "caml_dynlink_lookup_symbol"
         (* returned dll_address may be Val_unit *)
external add_primitive: dll_address -> int = "caml_dynlink_add_primitive"
external get_current_dlls: unit -> dll_handle array
                                           = "caml_dynlink_get_current_libs"

(* Current search path for DLLs *)
let search_path = ref ([] : string list)
#42 "bytecomp/dll.ml"
(* DLLs currently opened *)
#86 "otherlibs/dynlink/byte/dynlink_symtable.ml"
let opened_dlls = ref ([] : (string * dll_handle) list)
(* Each known primitive and its ID number *)
let primitives : (string, int) Hashtbl.t = Hashtbl.create 100
#52 "bytecomp/dll.ml"
(* Extract the name of a DLLs from its external name (xxx.so or -lxxx) *)

let extract_dll_name (~suffixed, file) =
  if not suffixed && Filename.check_suffix file Config.ext_dll then
    Filename.chop_suffix file Config.ext_dll
  else
    let file =
      if String.starts_with ~prefix:"-l" file then
      "dll" ^ String.sub file 2 (String.length file - 2)
    else
      file
    in
      if suffixed then
#104 "otherlibs/dynlink/byte/dynlink_symtable.ml"
        (* This name must be in sync with Misc.RuntimeID.stubslib *)
        Printf.sprintf "%s-%s-%s" file Config.target Config.bytecode_runtime_id
#66 "bytecomp/dll.ml"
      else
        file
#110 "otherlibs/dynlink/byte/dynlink_symtable.ml"
(* Specialized version of [Dll.{open_dll,open_dlls,find_primitive}] for the
    execution mode. *)
let open_dll name =
  let name = (extract_dll_name name) ^ Config.ext_dll in
  let fullname =
    if Filename.is_implicit name then
      !search_path
      |> List.find_map (fun dir ->
        let fullname = Filename.concat dir name in
        let fullname =
          if Filename.is_implicit fullname then
            Filename.concat Filename.current_dir_name fullname
          else fullname
        in
        if Sys.file_exists fullname then Some fullname else None)
      |> Option.value ~default:name
    else
      name
  in
  match List.assoc_opt fullname !opened_dlls with
  | Some _ -> ()
  | None ->
      begin match dll_open fullname with
      | dll ->
          opened_dlls := (fullname, dll) :: !opened_dlls
      | exception Failure msg ->
          failwith (fullname ^ ": " ^ msg)
      end

(* Open a list of DLLs, adding them to opened_dlls.
   Raise [Failure msg] in case of error. *)

let open_dlls names =
  List.iter open_dll names

let find_primitive prim_name =
  try Hashtbl.find primitives prim_name
  with Not_found ->
    let rec find seen = function
      [] ->
        raise (Error (Unavailable_primitive prim_name))
    | (_, dll) as curr :: rem ->
        let addr = dll_sym dll prim_name in
        if addr == Obj.magic () then find (curr :: seen) rem else begin
          if seen <> [] then opened_dlls := curr :: List.rev_append seen rem;
          let n = add_primitive addr in
          assert (n = Hashtbl.length primitives);
          Hashtbl.add primitives prim_name n;
          n
        end
    in
    find [] !opened_dlls
(* Adapted from Dll.init_toplevel *)
let init ~dllpaths ~prims =
  search_path := dllpaths;
  opened_dlls :=
    List.map (fun dll -> "", dll)
      (Array.to_list (get_current_dlls ()));
  List.iteri (fun n p -> Hashtbl.add primitives p n) prims
end
let of_prim = Dll.find_primitive
let open_dlls = Dll.open_dlls
(* Adapted from "bytecomp/symtable.ml"*)
module GlobalMap = struct

  type t = {
    cnt: int; (* The next number *)
    tbl: int Global.Map.t ; (* The table of already numbered objects *)
  }

  let empty = { cnt = 0; tbl = Global.Map.empty }

  let find nt key =
    Global.Map.find key nt.tbl

  let enter nt key =
    let n = !nt.cnt in
    nt := { cnt = n + 1; tbl = Global.Map.add key n !nt.tbl };
    n

  let incr nt =
    let n = !nt.cnt in
    nt := { cnt = n + 1; tbl = !nt.tbl };
    n

end
#111 "bytecomp/symtable.ml"
(* Global variables *)

let global_table = ref GlobalMap.empty
and literal_table = ref([] : (int * Obj.t) list)
#119 "bytecomp/symtable.ml"
let slot_for_getglobal global =
  try
    GlobalMap.find !global_table global
  with Not_found ->
    raise(Error (Undefined_global global))

let slot_for_setglobal global =
  GlobalMap.enter global_table global

let slot_for_literal cst =
  let n = GlobalMap.incr global_table in
  literal_table := (n, cst) :: !literal_table;
  n
#282 "bytecomp/symtable.ml"
(* Relocate a block of object bytecode *)

let patch_int buff pos n =
  let open Bigarray.Array1 in
  set buff pos (Char.unsafe_chr n);
  set buff (pos + 1) (Char.unsafe_chr (n asr 8));
  set buff (pos + 2) (Char.unsafe_chr (n asr 16));
  set buff (pos + 3) (Char.unsafe_chr (n asr 24))

let patch_object buff patchlist =
  List.iter
    (function
        (Reloc_literal sc, pos) ->
          patch_int buff pos (slot_for_literal sc)
      | (Reloc_getcompunit cu, pos) ->
          let global = Global.Glob_compunit cu in
          patch_int buff pos (slot_for_getglobal global)
      | (Reloc_getpredef pd, pos) ->
          let global = Global.Glob_predef pd in
          patch_int buff pos (slot_for_getglobal global)
      | (Reloc_setcompunit cu, pos) ->
          let global = Global.Glob_compunit cu in
          patch_int buff pos (slot_for_setglobal global)
      | (Reloc_primitive name, pos) ->
          patch_int buff pos (of_prim name))
    patchlist
#327 "bytecomp/symtable.ml"
(* Functions for toplevel use *)

(* Update the in-core table of globals *)
#247 "otherlibs/dynlink/byte/dynlink_symtable.ml"
module Meta = struct
#16 "bytecomp/meta.ml"
external global_data : unit -> Obj.t array = "caml_get_global_data"
external realloc_global_data : int -> unit = "caml_realloc_global"
#252 "otherlibs/dynlink/byte/dynlink_symtable.ml"
end
#331 "bytecomp/symtable.ml"
let update_global_table () =
  let ng = !global_table.cnt in
  if ng > Array.length(Meta.global_data()) then Meta.realloc_global_data ng;
  let glob = Meta.global_data() in
  List.iter
    (fun (slot, cst) -> glob.(slot) <- cst)
    !literal_table;
  literal_table := []

type bytecode_sections =
  { symb: GlobalMap.t;
    crcs: (string * Digest.BLAKE128.t option) list;
    prim: string list;
    dlpt: string list }

external get_bytecode_sections : unit -> bytecode_sections =
  "caml_dynlink_get_bytecode_sections"

(* Initialize the linker for toplevel use *)

let init_toplevel () =
  let sect = get_bytecode_sections () in
  global_table := sect.symb;
#278 "otherlibs/dynlink/byte/dynlink_symtable.ml"
  Dll.init ~dllpaths:sect.dlpt ~prims:sect.prim;
#357 "bytecomp/symtable.ml"
  sect.crcs

(* Find the value of a global identifier *)
#363 "bytecomp/symtable.ml"
let get_global_value global =
  (Meta.global_data()).(slot_for_getglobal global)
#368 "bytecomp/symtable.ml"
(* Check that all compilation units referenced in the given patch list
   have already been initialized *)

let initialized_compunits patchlist =
  List.fold_left (fun compunits rel ->
      match fst rel with
      | Reloc_setcompunit compunit -> compunit :: compunits
      | Reloc_literal _ | Reloc_getcompunit _ | Reloc_getpredef _
      | Reloc_primitive _ -> compunits)
    []
    patchlist

let required_compunits patchlist =
  List.fold_left (fun compunits rel ->
      match fst rel with
      | Reloc_getcompunit compunit -> compunit :: compunits
      | Reloc_literal _ | Reloc_getpredef _ | Reloc_setcompunit _
      | Reloc_primitive _ -> compunits)
    []
    patchlist

let check_global_initialized patchlist =
  (* First determine the compilation units we will define *)
  let initialized_compunits = initialized_compunits patchlist in
  (* Then check that all referenced, not defined comp units have a value *)
  let check_reference (rel, _) = match rel with
      Reloc_getcompunit compunit ->
        let global = Global.Glob_compunit compunit in
        if not (List.mem compunit initialized_compunits)
        && Obj.is_int (get_global_value global)
        then raise (Error(Uninitialized_global global))
    | Reloc_literal _ | Reloc_getpredef _ | Reloc_setcompunit _
    | Reloc_primitive _ -> () in
  List.iter check_reference patchlist

(* Save and restore the current state *)

type global_map = GlobalMap.t

let current_state () = !global_table
#411 "bytecomp/symtable.ml"
let hide_additions (st : global_map) =
  if st.cnt > !global_table.cnt then
#331 "otherlibs/dynlink/byte/dynlink_symtable.ml"
    failwith "Symtable.hide_additions";
#414 "bytecomp/symtable.ml"
  global_table :=
    {GlobalMap.
      cnt = !global_table.cnt;
      tbl = st.tbl }
#433 "bytecomp/symtable.ml"
let is_defined_in_global_map (gmap : global_map) global =
  Global.Map.mem global gmap.tbl

let empty_global_map = GlobalMap.empty
