Skip to content

Commit 8fd2eae

Browse files
committed
Define a foreign callable interface and make libsbcl.so useful.
This change adds many things: * There is now a proper interface to defining and using foreign callbacks in Lisp. * There is now a usable script to build libsbcl.so ("make-shared-library.sh"), which also slightly adjusts the runtime to make it more amenable for external use with respect to signalling. * There is now a way to use libsbcl.so in conjunction with other object code to actually initialize Lisp and call Lisp foreign callables from C directly. (this works, at least on Linux, but support is not quite complete yet, because we don't have a way to de-initialize Lisp) * `save-lisp-and-die' has gained a new argument to support the above. I guess technically DEFINE-ALIEN-CALLBACK can be removed, since the DEFINE-ALIEN-CALLABLE interface now supersedes it, but internal stuff like tests still use it, so that change can wait for now.
1 parent 2471368 commit 8fd2eae

File tree

9 files changed

+165
-53
lines changed

9 files changed

+165
-53
lines changed

NEWS

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
;;;; -*- coding: utf-8; fill-column: 78 -*-
22

3+
changes relative to sbcl-2.1.9:
4+
* new feature: there is now a defined interface for defining foreign
5+
callable functions, which can be used for passing callbacks to foreign
6+
functions or for calling Lisp code from the foreign world as a shared
7+
library (preliminary support). See the revised manual section "Calling
8+
into Lisp From C" for more details.
9+
310
changes in sbcl-2.1.9 relative to sbcl-2.1.8:
411
* minor incompatible change: the experimental DEFCAS macro has been removed.
512
* minor incompatible change: finalizing classes with slots with duplicate

doc/manual/ffi.texinfo

Lines changed: 67 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ notably in the name of the @code{SB-ALIEN} package.
2626
* Foreign Data Structure Examples::
2727
* Loading Shared Object Files::
2828
* Foreign Function Calls::
29+
* Calling Lisp From C::
2930
* Step-By-Step Example of the Foreign Function Interface::
3031
@end menu
3132

@@ -718,7 +719,6 @@ the only documentation. Users of a Lisp built with the
718719
* The alien-funcall Primitive::
719720
* The define-alien-routine Macro::
720721
* define-alien-routine Example::
721-
* Calling Lisp From C::
722722
@end menu
723723

724724
@node The alien-funcall Primitive
@@ -883,40 +883,6 @@ This can be described by the following call to
883883
The Lisp function @code{cfoo} will have two arguments (@var{str} and
884884
@var{a}) and two return values (@var{a} and @var{i}).
885885
886-
@node Calling Lisp From C
887-
@comment node-name, next, previous, up
888-
@subsection Calling Lisp From C
889-
890-
Calling Lisp functions from C is sometimes possible, but is extremely
891-
hackish and poorly supported as of SBCL 0.7.5. See @code{funcall0}
892-
@dots{} @code{funcall3} in the runtime system. The arguments must be
893-
valid SBCL object descriptors (so that e.g. fixnums must be
894-
left-shifted by 2.) As of SBCL 0.7.5, the format of object descriptors
895-
is documented only by the source code and, in parts, by the old CMUCL
896-
@file{INTERNALS} documentation.
897-
898-
Note that the garbage collector moves objects, and won't be
899-
able to fix up any references in C variables. There are three
900-
mechanisms for coping with this:
901-
902-
@enumerate
903-
@item
904-
The @code{sb-ext:purify} moves all live Lisp
905-
data into static or read-only areas such that it will never be moved
906-
(or freed) again in the life of the Lisp session
907-
908-
@item
909-
@code{sb-sys:with-pinned-objects} is a macro which arranges for some
910-
set of objects to be pinned in memory for the dynamic extent of its
911-
body forms. On ports which use the generational garbage collector (most,
912-
as of this writing) this affects exactly the specified objects. On
913-
other ports it is implemented by turning off GC for the duration (so
914-
could be said to have a whole-world granularity).
915-
916-
@item
917-
Disable GC, using the @code{without-gcing} macro.
918-
@end enumerate
919-
920886
@c <!-- FIXME: This is a "changebar" section from the CMU CL manual.
921887
@c I (WHN 2002-07-14) am not very familiar with this content, so
922888
@c I'm not immediately prepared to try to update it for SBCL, and
@@ -1058,6 +1024,72 @@ Disable GC, using the @code{without-gcing} macro.
10581024
@c LaTeX
10591025
@c -->
10601026
1027+
@node Calling Lisp From C
1028+
@comment node-name, next, previous, up
1029+
@section Calling Lisp From C
1030+
1031+
SBCL supports the calling of Lisp functions using the C calling
1032+
convention. This is useful for both defining callbacks and for creating
1033+
an interface for calling into Lisp as a shared library directly from C.
1034+
1035+
The @code{define-alien-callable} macro wraps Lisp code and creates a C
1036+
foreign function which can be called with the C calling convention.
1037+
1038+
@include macro-sb-alien-define-alien-callable.texinfo
1039+
1040+
The @code{alien-callable-function} function returns the foreign callable
1041+
value associated with any name defined by @code{define-alien-callable},
1042+
so that we can, for example, pass the callable value to C as a callback.
1043+
1044+
@include fun-sb-alien-alien-callable-function.texinfo
1045+
1046+
Note that the garbage collector moves objects, and won't be able to fix
1047+
up any references in C variables. There are three mechanisms for coping
1048+
with this:
1049+
1050+
@enumerate
1051+
@item
1052+
The @code{sb-ext:purify} moves all live Lisp data into static or
1053+
read-only areas such that it will never be moved (or freed) again in the
1054+
life of the Lisp session
1055+
1056+
@item
1057+
@code{sb-sys:with-pinned-objects} is a macro which arranges for some set
1058+
of objects to be pinned in memory for the dynamic extent of its body
1059+
forms. On ports which use the generational garbage collector (most, as
1060+
of this writing) this affects exactly the specified objects. On other
1061+
ports it is implemented by turning off GC for the duration (so could be
1062+
said to have a whole-world granularity).
1063+
1064+
@item
1065+
Disable GC, using the @code{without-gcing} macro.
1066+
@end enumerate
1067+
1068+
@menu
1069+
* Lisp as a Shared Library::
1070+
@end menu
1071+
1072+
@node Lisp as a Shared Library
1073+
@comment node-name, next, previous, up
1074+
@subsection Lisp as a Shared Library
1075+
SBCL supports the use of Lisp as a shared library that can be used by C
1076+
programs using the @code{define-alien-callable} interface. See the
1077+
@code{:callable-exports} keyword to @code{save-lisp-and-die} for how to
1078+
save the Lisp image in a way that allows a C program to initialize the
1079+
Lisp runtime and the exported symbols. When SBCL is built as a library,
1080+
it exposes the symbol @code{initialize_lisp} which can be used in
1081+
conjunction with a core initializing global symbols to foreign callables
1082+
as function pointers and with object code allocating those symbols to
1083+
initialize the runtime properly. The arguments to @code{initialize_lisp}
1084+
are the same as the arguments to the main @code{sbcl} program.
1085+
1086+
While standalone C code can call exposed Lisp functions which spawn Lisp
1087+
threads after the runtime has been initialized, it is currently not
1088+
advised to call into Lisp this way from separate C threads running
1089+
concurrently.
1090+
1091+
Note: There is also currently no way to run exit hooks or otherwise undo
1092+
Lisp initialization gracefully from C.
10611093
10621094
@node Step-By-Step Example of the Foreign Function Interface
10631095
@comment node-name, next, previous, up

make-shared-library.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/sh
2+
3+
. output/build-config
4+
5+
echo //entering make-shared-library.sh
6+
echo //building sbcl runtime into a shared library
7+
8+
$GNUMAKE -C src/runtime libsbcl.so

src/code/alien-callback.lisp

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,16 @@
1212

1313
(in-package "SB-ALIEN")
1414

15-
;;; ALIEN-CALLBACK is supposed to be external in SB-ALIEN-INTERNALS, but the
16-
;;; export gets lost, and then 'chill' gets a conflict with SB-ALIEN over it.
15+
;;; ALIEN-CALLBACK is supposed to be external in SB-ALIEN-INTERNALS,
16+
;;; but the export gets lost (as this is now a warm-loaded file), and
17+
;;; then 'chill' gets a conflict with SB-ALIEN over it.
1718
(eval-when (:compile-toplevel :load-toplevel :execute)
1819
(export (intern "ALIEN-CALLBACK" "SB-ALIEN-INTERNALS")
19-
"SB-ALIEN-INTERNALS"))
20+
"SB-ALIEN-INTERNALS")
21+
(export (intern "DEFINE-ALIEN-CALLABLE" "SB-ALIEN")
22+
"SB-ALIEN")
23+
(export (intern "ALIEN-CALLABLE-FUNCTION" "SB-ALIEN")
24+
"SB-ALIEN"))
2025

2126
;;;; ALIEN CALLBACKS
2227
;;;;
@@ -250,7 +255,7 @@ and a secondary return value of true if the callback is still valid."
250255
(defun invalidate-alien-callback (alien)
251256
"Invalidates the callback designated by the alien, if any, allowing the
252257
associated lisp function to be GC'd, and causing further calls to the same
253-
callback signal an error."
258+
callback to signal an error."
254259
(let ((info (alien-callback-info alien)))
255260
(when (and info (callback-info-function info))
256261
;; sap cache
@@ -288,6 +293,49 @@ the alien callback for that function with the given alien type."
288293
(defun ,name ,lambda-list ,@forms)
289294
(defparameter ,name (alien-callback ,specifier #',name)))))
290295

296+
;;;; Alien callables
297+
298+
(define-load-time-global *alien-callables* (make-hash-table :test #'eq)
299+
"Map from Lisp symbols to the alien callable functions they name.")
300+
301+
(defmacro define-alien-callable (name result-type typed-lambda-list &body body)
302+
"Define an alien callable function in the alien callable namespace with result
303+
type RESULT-TYPE and with lambda list specifying the alien types of the
304+
arguments."
305+
(multiple-value-bind (lisp-name alien-name)
306+
(pick-lisp-and-alien-names name)
307+
(declare (ignore alien-name))
308+
`(progn
309+
(invalidate-alien-callable ',lisp-name)
310+
(setf (gethash ',lisp-name *alien-callables*)
311+
(alien-lambda ,result-type ,typed-lambda-list ,@body)))))
312+
313+
(defun alien-callable-function (name)
314+
"Return the alien callable function associated with NAME."
315+
(gethash name *alien-callables*))
316+
317+
(defun invalidate-alien-callable (name)
318+
"Invalidates the callable designated by the alien, if any, allowing the
319+
associated lisp function to be GC'd, and causing further calls to the same
320+
callable to signal an error."
321+
(multiple-value-bind (lisp-name alien-name)
322+
(pick-lisp-and-alien-names name)
323+
(declare (ignore alien-name))
324+
(let ((alien (alien-callable-function lisp-name)))
325+
(when alien
326+
(invalidate-alien-callback alien)))
327+
(remhash lisp-name *alien-callables*)))
328+
329+
(defun initialize-alien-callable-symbol (name)
330+
"Initialize the alien symbol named by NAME with its alien callable
331+
function value."
332+
(multiple-value-bind (lisp-name alien-name)
333+
(pick-lisp-and-alien-names name)
334+
(setf (%alien-value (foreign-symbol-sap alien-name t)
335+
0
336+
(make-alien-pointer-type))
337+
(cast (alien-callable-function lisp-name) (* t)))))
338+
291339
(in-package "SB-THREAD")
292340
#+sb-thread
293341
(defun enter-foreign-callback (index return arguments)

src/code/save.lisp

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,22 @@
8080
sb-thread::*session*
8181
sb-kernel::*gc-epoch*))
8282

83-
(defun start-lisp (toplevel)
83+
(defun start-lisp (toplevel callable-exports)
8484
(named-lambda start-lisp ()
85-
(handling-end-of-the-world
86-
(reinit t)
87-
(funcall toplevel))))
85+
(cond (callable-exports
86+
(reinit t)
87+
(dolist (export callable-exports)
88+
(sb-alien::initialize-alien-callable-symbol export)))
89+
(t
90+
(handling-end-of-the-world
91+
(reinit t)
92+
(funcall toplevel))))))
8893

8994
(defun save-lisp-and-die (core-file-name &key
90-
(toplevel #'toplevel-init)
95+
(toplevel #'toplevel-init toplevel-supplied)
9196
(executable nil)
9297
(save-runtime-options nil)
98+
(callable-exports ())
9399
(purify t)
94100
(root-structures ())
95101
(environment-name "auxiliary")
@@ -125,6 +131,13 @@ The following &KEY arguments are defined:
125131
all command line arguments to be passed to the toplevel.
126132
Meaningless if :EXECUTABLE is NIL.
127133
134+
:CALLABLE-EXPORTS
135+
This should be a list of symbols to be initialized to the
136+
appropriate alien callables on startup. All exported symbols should
137+
be present as global symbols in the symbol table of the runtime
138+
before the saved core is loaded. When this list is non-empty, the
139+
:TOPLEVEL argument cannot be supplied.
140+
128141
:PURIFY
129142
If true (the default on cheneygc), do a purifying GC which moves all
130143
dynamically allocated objects into static space. This takes
@@ -194,6 +207,8 @@ sufficiently motivated to do lengthy fixes."
194207
(declare (ignore environment-name))
195208
#+gencgc
196209
(declare (ignore purify) (ignorable root-structures))
210+
(when (and callable-exports toplevel-supplied)
211+
(error ":TOPLEVEL cannot be supplied when there are callable exports."))
197212
;; If the toplevel function is not defined, this will signal an
198213
;; error before saving, not at startup time.
199214
(let ((toplevel (%coerce-callable-to-fun toplevel))
@@ -217,7 +232,7 @@ sufficiently motivated to do lengthy fixes."
217232
(if value 1 0)))
218233
(let ((name (native-namestring (physicalize-pathname core-file-name)
219234
:as-file t))
220-
(startfun (start-lisp toplevel)))
235+
(startfun (start-lisp toplevel callable-exports)))
221236
(deinit)
222237
;; FIXME: Would it be possible to unmix the PURIFY logic from this
223238
;; function, and just do a GC :FULL T here? (Then if the user wanted

src/runtime/GNUmakefile

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,9 @@ libsbcl.so: $(PIC_OBJS)
122122
# for this to work, you must have with-gcc-tls in your build features already.
123123
# can't define it here because then it conflicts if you have it in both places.
124124
%.pic.o: %.c
125-
$(CC) -fPIC -c $(filter-out -fno-pie,$(CFLAGS)) $< -o $@
125+
$(CC) -fPIC -c $(CPPFLAGS) -DSHARED_LIBRARY=1 $(filter-out -fno-pie,$(CFLAGS)) $< -o $@
126126
%.pic.o: %.S # (-fPIC doesn't affect hand-written assembly source)
127-
$(CC) -c $(CFLAGS) $< -o $@
128-
testmain: testmain.c libsbcl.so -ldl
127+
$(CC) -c $(CPPFLAGS) -DSHARED_LIBRARY=1 $(CFLAGS) $< -o $@
129128

130129
SHRINKWRAP_DEPS = ../../output/sbcl.core ../../tools-for-build/editcore.lisp
131130
shrinkwrap-sbcl.s shrinkwrap-sbcl-core.o: $(SHRINKWRAP_DEPS)

src/runtime/interrupt.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ static void record_signal(int sig, void* context)
324324
#define RECORD_SIGNAL(sig,ctxt)
325325
#endif
326326

327-
#ifdef LISP_FEATURE_WIN32
327+
#if defined(SHARED_LIBRARY) || defined(LISP_FEATURE_WIN32)
328328
# define should_handle_in_this_thread(c) (1)
329329
#else
330330
# define should_handle_in_this_thread(c) lisp_thread_p(c)

src/runtime/main.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
#include <interr.h>
2+
13
int main(int argc, char *argv[], char *envp[])
24
{
3-
extern int sbcl_main(int argc, char *argv[], char *envp[]);
4-
return sbcl_main(argc, argv, envp);
5+
extern int initialize_lisp(int argc, char *argv[], char *envp[]);
6+
initialize_lisp(argc, argv, envp);
7+
lose("unexpected return from initial thread in main()");
8+
return 0;
59
}

src/runtime/runtime.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ char *dir_name(char *path) {
395395
extern void write_protect_immobile_space();
396396
struct lisp_startup_options lisp_startup_options;
397397
int
398-
sbcl_main(int argc, char *argv[], char *envp[])
398+
initialize_lisp(int argc, char *argv[], char *envp[])
399399
{
400400
#ifdef LISP_FEATURE_WIN32
401401
/* Exception handling support structure. Evil Win32 hack. */
@@ -737,6 +737,5 @@ sbcl_main(int argc, char *argv[], char *envp[])
737737
FSHOW((stderr, "/funcalling initial_function=0x%lx\n",
738738
(unsigned long)initial_function));
739739
create_main_lisp_thread(initial_function);
740-
lose("unexpected return from initial thread in main()");
741740
return 0;
742741
}

0 commit comments

Comments
 (0)