Files
mercury/browser/dl.m
Tyson Dowd a355bebec9 Improve the code handling foreign_proc_attribute parsing.
Estimated hours taken: 4
Branches: main

Improve the code handling foreign_proc_attribute parsing.  This is in
anticipation of my next change which will add another attribute.

browser/dl.m:
	Fix an incorrect pragma foreign_code that should have been
	pragma foreign_proc.

compiler/prog_io_pragma.m:
	Improve the error handling when parsing attributes.
	Also improve the code for detecting conflicting attributes (e.g.
	thread_safe and not_thread_safe).
	This fixes an XXX in parse_pragma_foreign_proc_attributes_term
	concerning the lack of error return mechanisms in the code.
2001-07-06 14:39:30 +00:00

485 lines
17 KiB
Mathematica

%-----------------------------------------------------------------------------%
% Copyright (C) 1998-2001 The University of Melbourne.
% This file may only be copied under the terms of the GNU Library General
% Public License - see the file COPYING.LIB in the Mercury distribution.
%-----------------------------------------------------------------------------%
% 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 (mode) ---> lazy ; now. % RTLD_LAZY or RTLD_NOW
:- type scope ---> local ; global. % RTLD_GLOBAL or not.
:- type handle.
:- type result(T) ---> ok(T) ; error(string).
:- type result ---> ok ; error(string).
% interface to the C function dlopen()
:- pred dl__open(string::in, (mode)::in, scope::in, dl__result(handle)::out,
io__state::di, io__state::uo) is det.
% low-level interface to the C function dlsym() -- returns a c_pointer.
:- pred dl__sym(handle::in, string::in, dl__result(c_pointer)::out,
io__state::di, io__state::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 c_code)
% 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 dl__mercury_sym for procedures with argument types `float'
% or `char' is not supported.
:- pred dl__mercury_sym(handle::in, mercury_proc::in, dl__result(T)::out,
io__state::di, io__state::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 dl__close,
% because if you do reference code or static data from the dynamically
% linked module after dl__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 dl__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 dl__close(handle::in, dl__result::out,
io__state::di, io__state::uo) is det.
:- implementation.
:- import_module std_util, require, string, list, int.
:- pragma c_header_code("
#include <stdio.h>
#include ""mercury_conf.h""
#include ""mercury_string.h"" /* for MR_make_aligned_string_copy() */
#ifdef HAVE_DLFCN_H
#include <dlfcn.h>
#endif
").
:- type handle ---> handle(c_pointer).
:- pred is_null(c_pointer::in) is semidet.
:- pragma c_code(is_null(Pointer::in),
[will_not_call_mercury, thread_safe],
"SUCCESS_INDICATOR = ((void *)Pointer == NULL)").
open(FileName, Mode, Scope, Result) -->
dlopen(FileName, Mode, Scope, Pointer),
( { is_null(Pointer) } ->
dlerror(ErrorMsg),
{ Result = error(ErrorMsg) }
;
{ Result = ok(handle(Pointer)) }
).
/*
** Note that dlopen() may call startup code (e.g. constructors for global
** variables in C++) which may end up calling Mercury, so it's not safe
** to declare this as `will_not_call_mercury'.
*/
:- pred dlopen(string::in, (mode)::in, scope::in, c_pointer::out,
io__state::di, io__state::uo) is det.
:- pragma c_code(dlopen(FileName::in, Mode::in, Scope::in, Result::out,
_IO0::di, _IO::uo), [], "
{
#if defined(HAVE_DLFCN_H) && defined(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
}").
% closures for the LLDS backend
:- type ll_closure
---> ll_closure(
c_pointer, % really MR_Closure_Layout
c_pointer, % the address of the procedure to call
int % the number of curried arguments;
% always zero, for closures created
% by dl.m
).
% closures for the --high-level-code (MLDS) backend
:- type hl_closure
---> hl_closure(
c_pointer, % really MR_Closure_Layout
c_pointer, % the wrapper function;
% this gets passed the closure
% as an argument
int, % the number of curried arguments;
% always one, for closures created
% by dl.m
c_pointer % the real function, which gets
% called by the wrapper function
).
mercury_sym(Handle, MercuryProc0, Result) -->
{ 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),
{
Result0 = error(Msg),
Result = error(Msg)
;
Result0 = ok(Address),
%
% convert the procedure address to a closure
%
( high_level_code ->
NumCurriedInputArgs = 1,
ClosureLayout = make_closure_layout,
HL_Closure = hl_closure(ClosureLayout,
dl__generic_closure_wrapper,
NumCurriedInputArgs, Address),
private_builtin__unsafe_type_cast(HL_Closure, Value)
;
NumCurriedInputArgs = 0,
ClosureLayout = make_closure_layout,
LL_Closure = ll_closure(ClosureLayout, Address,
NumCurriedInputArgs),
private_builtin__unsafe_type_cast(LL_Closure, Value)
),
Result = ok(Value)
}.
:- pragma foreign_decl("C",
"
#include ""mercury_ho_call.h""
extern int ML_DL_closure_counter;
").
:- pragma foreign_code("C",
"
int ML_DL_closure_counter = 0;
").
:- func make_closure_layout = c_pointer.
:- pragma foreign_proc("C", make_closure_layout = (ClosureLayout::out),
[will_not_call_mercury, thread_safe],
"{
MR_Closure_Id *closure_id;
MR_Closure_Dyn_Link_Layout *closure_layout;
char buf[80];
/* create a goal path that encodes a unique id for this closure */
ML_DL_closure_counter++;
sprintf(buf, ""@%d;"", ML_DL_closure_counter);
/*
** XXX All the allocations in this code should use malloc
** in deep profiling grades.
*/
MR_incr_hp_type(closure_id, MR_Closure_Id);
closure_id->proc_id.MR_proc_user.MR_user_pred_or_func = MR_PREDICATE;
closure_id->proc_id.MR_proc_user.MR_user_decl_module = ""unknown"";
closure_id->proc_id.MR_proc_user.MR_user_def_module = ""unknown"";
closure_id->proc_id.MR_proc_user.MR_user_name = ""unknown"";
closure_id->proc_id.MR_proc_user.MR_user_arity = -1;
closure_id->proc_id.MR_proc_user.MR_user_mode = -1;
closure_id->module_name = ""dl"";
closure_id->file_name = __FILE__;
closure_id->line_number = __LINE__;
MR_make_aligned_string_copy(closure_id->goal_path, buf);
MR_incr_hp_type(closure_layout, MR_Closure_Dyn_Link_Layout);
closure_layout->closure_id = closure_id;
closure_layout->type_params = NULL;
closure_layout->num_all_args = 0;
ClosureLayout = (MR_Word) closure_layout;
}").
:- pragma c_header_code("
extern MR_Box MR_CALL ML_DL_generic_closure_wrapper(void *closure,
MR_Box arg1, MR_Box arg2, MR_Box arg3, MR_Box arg4, MR_Box arg5,
MR_Box arg6, MR_Box arg7, MR_Box arg8, MR_Box arg9, MR_Box arg10,
MR_Box arg11, MR_Box arg12, MR_Box arg13, MR_Box arg14, MR_Box arg15,
MR_Box arg16, MR_Box arg17, MR_Box arg18, MR_Box arg19, MR_Box arg20);
").
:- pragma c_code("
/*
** For the --high-level-code grades, the closure will be passed
** as an argument to the wrapper procedure. The wrapper procedure
** then extracts any needed curried arguments from the closure,
** and calls the real procedure. Normally the wrapper procedure
** knows which real procedure it will call, but for dl.m we use
** a generic wrapper procedure, and treat the real procedure
** as a curried argument of the generic wrapper. That is always
** the only curried argument, so all the wrapper needs to do
** is to extract the procedure address from the closure, and
** then call it, passing the same arguments that it was passed,
** except for the closure itself.
**
** XXX Using a single generic wrapper procedure is a nasty hack.
** We play fast and loose with the C type system here. In reality
** this will get called with different return type, different
** argument types, and with fewer than 20 arguments. Likewise, the
** procedure that it calls may actually have different arity, return type
** and argument types than we pass. So we really ought to have lots of
** different wrapper procedures, for each different return type, number
** of arguments, and even for each different set of argument types.
** Doing it right might require run-time code generation!
** But with traditional C calling conventions, using a single wrapper
** like this will work anyway, at least for arguments whose type is the
** same size as MR_Box. It fails for arguments of type `char' or `float'.
**
** XXX This will also fail for calling conventions where the callee pops the
** arguments. To handle that right, we'd need different wrappers for
** each different number of arguments. (Doing that would also be slightly
** more efficient, so it may worth doing...)
**
** There are also a couple of libraries called `ffcall' and `libffi'
** which we might be able use to do this in a more portable manner.
*/
MR_Box MR_CALL
ML_DL_generic_closure_wrapper(void *closure,
MR_Box arg1, MR_Box arg2, MR_Box arg3, MR_Box arg4, MR_Box arg5,
MR_Box arg6, MR_Box arg7, MR_Box arg8, MR_Box arg9, MR_Box arg10,
MR_Box arg11, MR_Box arg12, MR_Box arg13, MR_Box arg14, MR_Box arg15,
MR_Box arg16, MR_Box arg17, MR_Box arg18, MR_Box arg19, MR_Box arg20)
{
typedef MR_Box MR_CALL FuncType(
MR_Box a1, MR_Box a2, MR_Box a3, MR_Box a4, MR_Box a5,
MR_Box a6, MR_Box a7, MR_Box a8, MR_Box a9, MR_Box a10,
MR_Box a11, MR_Box a12, MR_Box a13, MR_Box a14, MR_Box a15,
MR_Box a16, MR_Box a17, MR_Box a18, MR_Box a19, MR_Box a20);
FuncType *proc = (FuncType *)
MR_field(MR_mktag(0), closure, (MR_Integer) 3);
return (*proc)(arg1, arg2, arg3, arg4, arg5,
arg6, arg7, arg8, arg9, arg10,
arg11, arg12, arg13, arg14, arg15,
arg16, arg17, arg18, arg19, arg20);
}
").
:- func dl__generic_closure_wrapper = c_pointer.
:- pragma c_code(dl__generic_closure_wrapper = (WrapperFuncAddr::out),
[thread_safe, will_not_call_mercury],
"
WrapperFuncAddr = (MR_Word) &ML_DL_generic_closure_wrapper;
").
%
% 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),
( TypeName = "func" ->
TypeProcArity = TypeArity - 1
;
TypeProcArity = TypeArity
),
(
( TypeModule \= "builtin"
; TypeName \= "pred", TypeName \= "func"
)
->
error(
"dl__mercury_sym: result type (`" ++
type_name(ResultType) ++
"') is not a higher-order type")
;
IsPredOrFunc = predicate, TypeName \= "pred"
->
string__append(
"dl__mercury_sym: predicate/function mismatch: ",
"argument is a predicate, result type is a function",
Msg),
error(Msg)
;
IsPredOrFunc = function, TypeName \= "func"
->
string__append(
"dl__mercury_sym: predicate/function mismatch: ",
"argument is a function, result type is a predicate",
Msg),
error(Msg)
;
ProcArity \= TypeProcArity
->
string__int_to_string(ProcArity, ProcArityString),
string__int_to_string(TypeProcArity, TypeArityString),
string__append_list([
"dl__mercury_sym: arity mismatch: ",
"argument has ", ProcArityString, " argument(s), ",
"result type has ", TypeArityString, " arguments(s)"],
Msg),
error(Msg)
;
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're
% 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) :-
(
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"
->
error("sorry, not implemented: dl__mercury_sym for procedure with argument type `float' or `char'")
;
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
->
error("sorry, not implemented: dl__mercury_sym for procedure with more than 18 arguments")
;
Proc = Proc0
).
sym(handle(Handle), Name, Result) -->
dlsym(Handle, Name, Pointer),
( { is_null(Pointer) } ->
dlerror(ErrorMsg),
{ Result = error(ErrorMsg) }
;
{ Result = ok(Pointer) }
).
:- pred dlsym(c_pointer::in, string::in, c_pointer::out,
io__state::di, io__state::uo) is det.
:- pragma c_code(dlsym(Handle::in, Name::in, Pointer::out,
_IO0::di, _IO::uo), [will_not_call_mercury], "
{
#if defined(HAVE_DLFCN_H) && defined(HAVE_DLSYM)
Pointer = (MR_Word) dlsym((void *) Handle, Name);
#else
Pointer = (MR_Word) NULL;
#endif
}").
:- pred dlerror(string::out, io__state::di, io__state::uo) is det.
:- pragma c_code(dlerror(ErrorMsg::out, _IO0::di, _IO::uo),
[will_not_call_mercury], "
{
const char *msg;
#if defined(HAVE_DLFCN_H) && defined(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);
}").
close(handle(Handle), Result) -->
dlclose(Handle),
dlerror(ErrorMsg),
{ Result = (if ErrorMsg = "" then ok else error(ErrorMsg)) }.
/*
** Note that dlclose() may call finalization code (e.g. destructors for global
** variables in C++) which may end up calling Mercury, so it's not safe
** to declare this as `will_not_call_mercury'.
*/
:- pred dlclose(c_pointer::in, io__state::di, io__state::uo) is det.
:- pragma c_code(dlclose(Handle::in, _IO0::di, _IO::uo), [], "
#if defined(HAVE_DLFCN_H) && defined(HAVE_DLCLOSE)
dlclose((void *)Handle)
#endif
").
%-----------------------------------------------------------------------------%
:- pred high_level_code is semidet.
:- pragma c_code(high_level_code, [will_not_call_mercury, thread_safe], "
#ifdef MR_HIGHLEVEL_CODE
SUCCESS_INDICATOR = TRUE;
#else
SUCCESS_INDICATOR = FALSE;
#endif
").
%-----------------------------------------------------------------------------%