-
Notifications
You must be signed in to change notification settings - Fork 56
/
Copy pathdl.m
397 lines (348 loc) · 13.2 KB
/
dl.m
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
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 1998-2006 The University of Melbourne.
% Copyright (C) 2015-2018, 2025 The Mercury team.
% This file is distributed under the terms specified in COPYING.LIB.
%---------------------------------------------------------------------------%
%
% File: dl.m.
% Purpose: dynamic linking support.
% Main author: fjh.
% Stability: medium.
%
% This file provides an interface to the C functions dlopen(), dlsym(),
% and dlclose(). For details about the behaviour of those procedures,
% see the documentation for those procedures (i.e. `man dlopen').
%
%---------------------------------------------------------------------------%
:- module mdb.dl.
:- interface.
:- import_module io.
:- import_module mdb.name_mangle.
:- type link_mode
---> lazy % RTLD_LAZY
; now. % RTLD_NOW
:- type scope
---> scope_local
; scope_global. % RTLD_GLOBAL or not.
:- type handle.
:- type dl_result(T)
---> dl_ok(T)
; dl_error(string).
:- type dl_result
---> dl_ok
; dl_error(string).
% Interface to the C function dlopen().
%
:- pred open(string::in, link_mode::in, scope::in, dl_result(handle)::out,
io::di, io::uo) is det.
% Low-level interface to the C function dlsym() -- returns a c_pointer.
%
:- pred sym(handle::in, string::in, dl_result(c_pointer)::out,
io::di, io::uo) is det.
% High-level interface to the C function dlsym().
%
% This version returns a higher-order predicate or function term.
% The user must use an inst cast (implemented using pragma foreign_proc)
% to cast this term to the appropriate higher-order inst before calling it;
% see dl_test.m for an example of this.
%
% The type `T' below must be a higher-order type whose arity and
% argument types exactly match that of the specified procedure.
% The implementation may check this at runtime, but is not required
% to do so. (The current implementation checks that the type is a
% higher-order type with the appropriate arity, but it does not
% check the argument types.)
%
% WARNING: for the `--high-level-code' back-end (the `hl*' grades),
% calling mercury_sym for procedures with argument types `float'
% or `char' is not supported.
%
:- pred mercury_sym(handle::in, mercury_proc::in, dl_result(T)::out,
io::di, io::uo) is det.
% Interface to the C function dlclose().
%
% WARNING: dlclose() is form of manual memory management.
% You need to make sure that no remaining references to code or
% static data in the dynamically linked module before you call close,
% because if you do reference code or static data from the dynamically
% linked module after close has been called, then the behaviour is
% undefined (and probably harmful!).
%
% This can be difficult to ensure. You need to make sure that you
% don't keep any references to the higher-order terms return by sym.
% Furthermore you need to make sure that you don't keep any references
% to terms constructed by procedures in the dynamically loaded module,
% since such terms may contain references to static data in the
% dynamically loaded module. You must also ensure that you don't keep
% any references to types or instances defined in the dynamically loaded
% module, as might be the case if you're using existentially quantified
% data types, since they too can contain references to static data.
%
% (Note that using builtin.copy/2, to make copies rather than
% keeping references, is *not* guaranteed to work in all cases.)
%
:- pred close(handle::in, dl_result::out, io::di, io::uo) is det.
:- implementation.
:- import_module int.
:- import_module list.
:- import_module require.
:- import_module string.
:- import_module type_desc.
:- pragma foreign_decl("C", "
#include <stdio.h>
#include ""mercury_conf.h""
#include ""mercury_string.h"" /* for MR_make_aligned_string_copy() */
#include ""mercury_ho_call.h""
#ifdef MR_HAVE_DLFCN_H
#include <dlfcn.h>
#endif
").
:- type handle
---> handle(c_pointer).
:- pred is_null(c_pointer::in) is semidet.
:- pragma no_determinism_warning(pred(is_null/1)).
:- pragma foreign_proc("C",
is_null(Pointer::in),
[will_not_call_mercury, promise_pure, thread_safe],
"
SUCCESS_INDICATOR = ((void *) Pointer == NULL)
").
is_null(_) :-
private_builtin.sorry("is_null").
open(FileName, Mode, Scope, Result, !IO) :-
dlopen(FileName, Mode, Scope, Pointer, !IO),
( if is_null(Pointer) then
dlerror(ErrorMsg, !IO),
Result = dl_error(ErrorMsg)
else
Result = dl_ok(handle(Pointer))
).
:- pred dlopen(string::in, link_mode::in, scope::in, c_pointer::out,
io::di, io::uo) is det.
:- pragma no_determinism_warning(pred(dlopen/6)).
:- pragma foreign_proc("C",
dlopen(FileName::in, Mode::in, Scope::in, Result::out, _IO0::di, _IO::uo),
% Note that dlopen() may call startup code (e.g. constructors for global
% variables in C++) which may end up calling Mercury, so it is NOT safe
% to assert that it `will_not_call_mercury'.
[may_call_mercury, promise_pure, tabled_for_io],
"{
#if defined(MR_HAVE_DLFCN_H) && defined(MR_HAVE_DLOPEN) \
&& defined(RTLD_NOW) && defined(RTLD_LAZY)
int mode = (Mode ? RTLD_NOW : RTLD_LAZY);
/* not all systems have RTLD_GLOBAL */
#ifdef RTLD_GLOBAL
if (Scope) mode |= RTLD_GLOBAL;
#endif
Result = (MR_Word) dlopen(FileName, mode);
#else
Result = (MR_Word) NULL;
#endif
}").
dlopen(_, _, _, _, !IO) :-
private_builtin.sorry("dlopen").
:- pragma foreign_decl("C",
"
#include ""mercury_ho_call.h""
").
% Convert the given procedure address to a closure.
%
:- func make_closure(c_pointer) = c_pointer.
:- pragma no_determinism_warning(func(make_closure/1)).
:- pragma foreign_proc("C",
make_closure(ProcAddr::in) = (Closure::out),
[will_not_call_mercury, promise_pure],
"{
MR_save_transient_hp();
Closure = (MR_Word) MR_make_closure((MR_Code *) ProcAddr);
MR_restore_transient_hp();
}").
make_closure(_) = _ :-
private_builtin.sorry("make_closure").
% Check that the result type matches the information in the
% procedure specification.
%
:- pred check_proc_spec_matches_result_type(dl_result(T)::unused, T::unused,
mercury_proc::in, mercury_proc::out) is det.
check_proc_spec_matches_result_type(_Result, Value, Proc0, Proc) :-
Proc0 = mercury_proc(IsPredOrFunc, _Module, _Name, ProcArity, _Mode),
ResultType = type_of(Value),
type_ctor_name_and_arity(type_ctor(ResultType),
TypeModule, TypeName, TypeArity),
( if TypeName = "func" then
TypeProcArity = TypeArity - 1
else
TypeProcArity = TypeArity
),
( if
( TypeModule \= "builtin"
; TypeName \= "pred", TypeName \= "func"
)
then
unexpected($pred,
"result type (`" ++ type_name(ResultType) ++ "')" ++
" is not a higher-order type")
else if
IsPredOrFunc = predicate, TypeName \= "pred"
then
unexpected($pred,
"predicate/function mismatch: " ++
"argument is a predicate, result type is a function")
else if
IsPredOrFunc = function, TypeName \= "func"
then
unexpected($pred,
"predicate/function mismatch: " ++
"argument is a function, result type is a predicate")
else if
ProcArity \= TypeProcArity
then
string.int_to_string(ProcArity, ProcArityString),
string.int_to_string(TypeProcArity, TypeArityString),
string.append_list(["arity mismatch: ",
"argument has ", ProcArityString, " argument(s), ",
"result type has ", TypeArityString, " arguments(s)"], Msg),
unexpected($pred, Msg)
else
Proc = Proc0
).
% Check that the given higher-order type is supported.
%
% For the MLDS back-end, we normally need wrapper functions for closures;
% the wrapper functions convert from type MR_Box to the appropriate
% argument type, and then call the function with the unboxed argument
% types. Generating those on-the-fly here would be tricky! Instead,
% we only try to handle the cases where we can use a single generic
% wrapper, i.e. arguments with types other than `char' or `float'.
% All other argument types are word-sized, and will hopefully be passed
% in the same way by the C compiler.
%
% This procedure checks, for the MLDS back-end, that you are not using it
% on a procedure with argument types `char' or `float', and that the
% procedure doesn't have more arguments than the generic wrapper
% can handle.
%
% XXX This doesn't catch the case of no_tag types that end up
% being equivalent to `float' or `char'.
%
:- pred check_type_is_supported(dl_result(T)::unused, T::unused,
mercury_proc::in, mercury_proc::out) is det.
check_type_is_supported(_Result, Value, Proc0, Proc) :-
( if
high_level_code,
list.member(ArgType, type_args(type_of(Value))),
% The following line might be more efficient,
% but is not yet supported by the MLDS back-end
% ArgType = type_of(_ `with_type` float))
ArgTypeCtor = type_ctor(ArgType),
( type_ctor_name(ArgTypeCtor) = "float"
; type_ctor_name(ArgTypeCtor) = "char"
),
type_ctor_module_name(ArgTypeCtor) = "builtin"
then
sorry($pred, "procedure with argument type `float' or `char'")
else if
high_level_code,
% The generic wrapper only works for procedures with up to 20
% arguments. For nondet procedures, two of the arguments get used up
% for the continuation function and the environment pointer, so we can
% only support 18 other arguments.
type_ctor_arity(type_ctor(type_of(Value))) > 18
then
sorry($pred, "procedure with more than 18 arguments")
else
Proc = Proc0
).
sym(handle(Handle), Name, Result, !IO) :-
dlsym(Handle, Name, Pointer, !IO),
( if is_null(Pointer) then
dlerror(ErrorMsg, !IO),
Result = dl_error(ErrorMsg)
else
Result = dl_ok(Pointer)
).
mercury_sym(Handle, MercuryProc0, Result, !IO) :-
check_proc_spec_matches_result_type(Result, _, MercuryProc0, MercuryProc1),
check_type_is_supported(Result, _, MercuryProc1, MercuryProc),
MangledName = proc_name_mangle(MercuryProc),
sym(Handle, MangledName, Result0, !IO),
(
Result0 = dl_error(Msg),
Result = dl_error(Msg)
;
Result0 = dl_ok(Address),
private_builtin.unsafe_type_cast(make_closure(Address), Closure),
Result = dl_ok(Closure)
).
:- pred dlsym(c_pointer::in, string::in, c_pointer::out,
io::di, io::uo) is det.
:- pragma no_determinism_warning(pred(dlsym/5)).
:- pragma foreign_proc("C",
dlsym(Handle::in, Name::in, Pointer::out, _IO0::di, _IO::uo),
[will_not_call_mercury, promise_pure, tabled_for_io],
"{
#if defined(MR_HAVE_DLFCN_H) && defined(MR_HAVE_DLSYM)
Pointer = (MR_Word) dlsym((void *) Handle, Name);
#else
Pointer = (MR_Word) NULL;
#endif
}").
dlsym(_, _, _, !IO) :-
private_builtin.sorry("dlsym").
:- pred dlerror(string::out, io::di, io::uo) is det.
:- pragma no_determinism_warning(pred(dlerror/3)).
:- pragma foreign_proc("C",
dlerror(ErrorMsg::out, _IO0::di, _IO::uo),
[will_not_call_mercury, promise_pure, tabled_for_io],
"{
const char *msg;
#if defined(MR_HAVE_DLFCN_H) && defined(MR_HAVE_DLERROR)
msg = dlerror();
if (msg == NULL) msg = """";
#else
MR_make_aligned_string(msg, ""sorry, not implemented: ""
""dynamic linking not supported on this platform"");
#endif
MR_make_aligned_string_copy(ErrorMsg, msg);
}").
dlerror(_, !IO) :-
private_builtin.sorry("dlerror").
close(handle(Handle), Result, !IO) :-
dlclose(Handle, !IO),
dlerror(ErrorMsg, !IO),
Result = (if ErrorMsg = "" then dl_ok else dl_error(ErrorMsg)).
:- pred dlclose(c_pointer::in, io::di, io::uo) is det.
:- pragma no_determinism_warning(pred(dlclose/3)).
:- pragma foreign_proc("C",
dlclose(Handle::in, _IO0::di, _IO::uo),
% Note that dlclose() may call finalization code (e.g. destructors
% for global variables in C++) which may end up calling Mercury,
% so it is NOT safe to declare this as `will_not_call_mercury'.
[may_call_mercury, promise_pure, tabled_for_io],
"
#if defined(MR_HAVE_DLFCN_H) && defined(MR_HAVE_DLCLOSE)
dlclose((void *) Handle);
#endif
").
dlclose(_, !IO) :-
private_builtin.sorry("dlclose").
%---------------------------------------------------------------------------%
:- pred high_level_code is semidet.
:- pragma no_determinism_warning(pred(high_level_code/0)).
:- pragma foreign_proc("C",
high_level_code,
[will_not_call_mercury, promise_pure, thread_safe],
"
#ifdef MR_HIGHLEVEL_CODE
SUCCESS_INDICATOR = MR_TRUE;
#else
SUCCESS_INDICATOR = MR_FALSE;
#endif
").
high_level_code :-
private_builtin.sorry("high_level_code").
%---------------------------------------------------------------------------%
:- end_module mdb.dl.
%---------------------------------------------------------------------------%