package ctypes-foreign

  1. Overview
  2. Docs

Source file ctypes_ffi.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
(*
 * Copyright (c) 2013 Jeremy Yallop.
 *
 * This file is distributed under the terms of the MIT License.
 * See the file LICENSE for details.
 *)

[@@@warning "-9-27"]

module type CLOSURE_PROPERTIES =
sig
  val record : Obj.t -> Obj.t -> int
  (** [record c v] links the lifetimes of [c] and [v], ensuring that [v] is not
      collected while [c] is still live.  The return value is a key
      that can be used to retrieve [v] while [v] is still live. *)

  val retrieve : int -> Obj.t
  (** [retrieve v] retrieves a value using a key returned by [record], or raises
      [Not_found] if [v] is no longer live. *)
end

module Make(Closure_properties : CLOSURE_PROPERTIES) =
struct

  open Ctypes_static
  open Libffi_abi

  (* Register the closure lookup function with C. *)
  let () = Ctypes_ffi_stubs.set_closure_callback Closure_properties.retrieve

  type _ ccallspec =
      Call : bool * (Ctypes_ptr.voidp -> 'a) -> 'a ccallspec
    | WriteArg : ('a -> Ctypes_ptr.voidp -> (Obj.t * int) array -> Obj.t) * 'b ccallspec ->
                 ('a -> 'b) ccallspec

  type arg_type = ArgType : 'a Ctypes_ffi_stubs.ffitype -> arg_type

  (* keep_alive ties the lifetimes of objects together.

     [keep_alive w ~while_live:v] ensures that [w] is not collected while [v] is
     still live.

     If the object v in the call [keep_alive w ~while_live:v] is
     static -- for example, if it is a top-level function -- then it
     is not possible to attach a finaliser to [v] and [w] should be
     kept alive indefinitely, which we achieve by adding it to the
     list [kept_alive_indefinitely].
  *)
  let kept_alive_indefinitely = ref []
  let keep_alive w ~while_live:v =
    try Gc.finalise (fun _ -> Ctypes_memory_stubs.use_value w; ()) v
    with Invalid_argument _ ->
      kept_alive_indefinitely := Obj.repr w :: !kept_alive_indefinitely

  let report_unpassable what =
    let msg = Printf.sprintf "libffi does not support passing %s" what in
    raise (Unsupported msg)

  let rec arg_type : type a. a typ -> arg_type = function
    | Void                                -> ArgType (Ctypes_ffi_stubs.void_ffitype ())
    | Primitive p as prim                 -> let ffitype = Ctypes_ffi_stubs.primitive_ffitype p in
                                             if ffitype = Ctypes_ptr.Raw.null
                                             then report_unpassable
                                               (Ctypes_type_printing.string_of_typ prim)
                                             else ArgType ffitype
    | Pointer _                           -> ArgType (Ctypes_ffi_stubs.pointer_ffitype ())
    | Funptr _                            -> ArgType (Ctypes_ffi_stubs.pointer_ffitype ())
    | OCaml _                             -> ArgType (Ctypes_ffi_stubs.pointer_ffitype ())
    | Union _                             -> report_unpassable "unions"
    | Struct ({ spec = Complete _ } as s) -> struct_arg_type s
    | View { ty }                         -> arg_type ty
    | Array _                             -> report_unpassable "arrays"
    | Bigarray _                          -> report_unpassable "bigarrays"
    | Abstract _                          -> (report_unpassable
                                                "values of abstract type")
    (* The following case should never happen; incomplete types are excluded
       during type construction. *)
    | Struct { spec = Incomplete _ }      -> report_unpassable "incomplete types"
  and struct_arg_type : type s. s structure_type -> arg_type =
     fun ({fields} as s) ->
       let bufspec = Ctypes_ffi_stubs.allocate_struct_ffitype (List.length fields) in
       (* Ensure that `bufspec' stays alive as long as the type does. *)
       keep_alive bufspec ~while_live:s;
       List.iteri
         (fun i (BoxedField {ftype; foffset}) ->
           let ArgType t = arg_type ftype in
           Ctypes_ffi_stubs.struct_type_set_argument bufspec i t)
         fields;
       Ctypes_ffi_stubs.complete_struct_type bufspec;
       ArgType (Ctypes_ffi_stubs.ffi_type_of_struct_type bufspec)

  (*
    call addr callspec
     (fun buffer ->
          write arg_1 buffer v_1
          write arg buffer v
          ...
          write arg_n buffer v_n)
     read_return_value
  *)
  let rec invoke : type a b m.
    string option ->
    a ccallspec ->
    (Ctypes_ptr.voidp -> (Obj.t * int) array -> Obj.t) list ->
    Ctypes_ffi_stubs.callspec ->
    (m, b fn) Ctypes_ptr.Fat.t ->
    a
    = fun name -> function
      | Call (check_errno, read_return_value) ->
        let name = match name with Some name -> name | None -> "" in
        fun writers callspec addr ->
          let r = ref [] in
          let v = Ctypes_ffi_stubs.call name addr callspec
              (fun buf arr -> List.iter (fun w -> r := w buf arr :: !r) writers)
              read_return_value
          in 
          Ctypes_memory_stubs.use_value r;
          v
      | WriteArg (write, ccallspec) ->
        let next = invoke name ccallspec in
        fun writers callspec addr v ->
          next (write v :: writers) callspec addr

  let add_argument : type a. Ctypes_ffi_stubs.callspec -> a typ -> int
    = fun callspec -> function
      | Void -> 0
      | ty   -> let ArgType ffitype = arg_type ty in
                Ctypes_ffi_stubs.add_argument callspec ffitype

  let prep_callspec callspec abi ty =
    let ArgType ctype = arg_type ty in
    Ctypes_ffi_stubs.prep_callspec callspec (abi_code abi) ctype

  let rec box_function : type a. abi -> a fn -> Ctypes_ffi_stubs.callspec -> a Ctypes_weak_ref.t ->
      Ctypes_ffi_stubs.boxedfn
    = fun abi fn callspec -> match fn with
      | Returns ty ->
        let () = prep_callspec callspec abi ty in
        let write_rv = Ctypes_memory.write ty in
        fun f ->
          let w = write_rv (Ctypes_weak_ref.get f) in
          Ctypes_ffi_stubs.Done ((fun p -> w (Ctypes_ptr.Fat.make
                                                ~managed:None ~reftyp:Void p)),
                          callspec)
      | Function (p, f) ->
        let _ = add_argument callspec p in
        let box = box_function abi f callspec in
        let read = Ctypes_memory.build p in
        fun f -> Ctypes_ffi_stubs.Fn (fun buf ->
          let f' =
            try Ctypes_weak_ref.get f (read (Ctypes_ptr.Fat.make
                                               ~managed:None ~reftyp:Void buf))
            with Ctypes_weak_ref.EmptyWeakReference ->
              raise Ctypes_ffi_stubs.CallToExpiredClosure
          in
          let v = box (Ctypes_weak_ref.make f') in
          let () = Gc.finalise (fun _ -> Ctypes_memory_stubs.use_value f') v in
          v)

  let rec write_arg : type a. a typ -> offset:int -> idx:int -> a ->
                  Ctypes_ptr.voidp -> (Obj.t * int) array -> Obj.t =
    let ocaml_arg elt_size =
      fun ~offset ~idx (OCamlRef (disp, obj, _)) dst mov ->
        mov.(idx) <- (Obj.repr obj, disp * elt_size);
        Obj.repr obj
    in function
    | OCaml String     -> ocaml_arg 1
    | OCaml Bytes      -> ocaml_arg 1
    | OCaml FloatArray -> ocaml_arg (Ctypes_primitives.sizeof Ctypes_primitive_types.Double)
    | View { write = w; ty } ->
      (fun ~offset ~idx v dst mov -> 
         let wv = w v in
         let wa = write_arg ty ~offset ~idx wv dst mov in
         Obj.repr (wv, wa))
    | ty -> (fun ~offset ~idx v dst mov -> 
        Ctypes_memory.write ty v
          (Ctypes_ptr.Fat.(add_bytes (make ~managed:None ~reftyp:Void dst) offset));
        Obj.repr v)

  (*
    callspec = allocate_callspec ()
    add_argument callspec arg1
    add_argument callspec arg2
    ...
    add_argument callspec argn
    prep_callspec callspec rettype
  *)
  let rec build_ccallspec : type a. abi:abi -> check_errno:bool -> ?idx:int -> a fn ->
    Ctypes_ffi_stubs.callspec -> a ccallspec
    = fun ~abi ~check_errno ?(idx=0) fn callspec -> match fn with
      | Returns t ->
        let () = prep_callspec callspec abi t in
        let b = Ctypes_memory.build t in
        Call (check_errno, (fun p -> b (Ctypes_ptr.Fat.make ~managed:None ~reftyp:Void p)))
      | Function (p, f) ->
        let offset = add_argument callspec p in
        let rest = build_ccallspec ~abi ~check_errno ~idx:(idx+1) f callspec in
        WriteArg (write_arg p ~offset ~idx, rest)

  let build_function ?name ~abi ~release_runtime_lock ~check_errno fn =
    let c = Ctypes_ffi_stubs.allocate_callspec ~check_errno
      ~runtime_lock:release_runtime_lock
      ~thread_registration:false
    in
    let e = build_ccallspec ~abi ~check_errno fn c in
    invoke name e [] c

  let funptr_of_rawptr fn raw_ptr =
    Static_funptr (Ctypes_ptr.Fat.make ~managed:None ~reftyp:fn raw_ptr)

  let function_of_pointer ?name ~abi ~check_errno ~release_runtime_lock fn =
    if release_runtime_lock && has_ocaml_argument fn
    then raise (Unsupported "Unsupported argument type when releasing runtime lock")
    else fun (Static_funptr p) ->
         build_function ?name ~abi ~check_errno ~release_runtime_lock fn p

  let pointer_of_function_internal ~abi ~acquire_runtime_lock ~thread_registration fn =
    let cs' = Ctypes_ffi_stubs.allocate_callspec
      ~check_errno:false
      ~runtime_lock:acquire_runtime_lock
      ~thread_registration
    in
    let cs = box_function abi fn cs' in
    fun f ->
      let boxed = cs (Ctypes_weak_ref.make f) in
      let id = Closure_properties.record (Obj.repr f) (Obj.repr boxed) in
      Ctypes_ffi_stubs.make_function_pointer cs' id

  let pointer_of_function ~abi ~acquire_runtime_lock ~thread_registration fn =
    let make_funptr = pointer_of_function_internal ~abi ~acquire_runtime_lock ~thread_registration fn in
    fun f ->
      let funptr = make_funptr f in
      (* TODO: use a more intelligent strategy for keeping function pointers
         associated with top-level functions alive (e.g. cache function
         pointer creation by (function, type), or possibly even just by
         function, since the C arity and types must be the same in each case.)
         See the note by [kept_alive_indefinitely].

         [dynamic_funptr_of_fun] allows for explicit life cycle management. *)
      let () = keep_alive funptr ~while_live:f in
      funptr_of_rawptr fn
        (Ctypes_ffi_stubs.raw_address_of_function_pointer funptr)

  type 'a funptr =
    { mutable gc_root : unit Ctypes.ptr
    ; fn : 'a Ctypes.static_funptr
    }

  let free_funptr t =
    if Ctypes.is_null t.gc_root then
       failwith "This funptr was previously freed"
    else (
      Ctypes.Root.release t.gc_root;
      t.gc_root <- Ctypes.null;
    )

  let report_leaked_funptr : (string -> unit) ref = ref (fun msg ->
    Printf.eprintf "%s\n%!" msg)

  let retain_funptr_root_to_avoid_segfaults_when_not_freed_correctly = ref []

  let create_funptr gc_root fn =
    let t = { gc_root = Ctypes.Root.create gc_root; fn } in
    Gc.finalise (fun t ->
      if Ctypes.is_null t.gc_root then
        ()
      else (
        retain_funptr_root_to_avoid_segfaults_when_not_freed_correctly :=
          t.gc_root :: !retain_funptr_root_to_avoid_segfaults_when_not_freed_correctly;
        t.gc_root <- Ctypes.null;
        !report_leaked_funptr
          "WARN: a ctypes function pointer was not explicitly released.\n\
           Releasing a function pointer or the associated OCaml closure while \n\
           the function pointer is still in use from C will cause segmentation faults.\n\
           Please call [Foreign.Funptr.free] explicitly when the funptr is no longer needed.\n\
           To avoid a segmentation fault we are preventing this funptr from\n\
           being garbage collected. Please use [Foreign.Funptr.free].\n%!")) t;
    t

  let funptr_of_fun ~abi ~acquire_runtime_lock ~thread_registration fn =
    let make_funptr = pointer_of_function_internal ~abi ~acquire_runtime_lock ~thread_registration fn in
    (fun f -> 
       let funptr = make_funptr f in
       create_funptr (f,funptr) (funptr_of_rawptr fn (Ctypes_ffi_stubs.raw_address_of_function_pointer funptr)))

  let funptr_of_static_funptr fp =
    create_funptr () fp

  let funptr_to_static_funptr t =
    if Ctypes.is_null t.gc_root then
      failwith "This funptr was previously freed"
    else t.fn
end