疑问:SBCL如何生成Lisp可执行文件

在sbcl的手册中查询到的有关信息

sbcl手册 3.2.3 Saving a Core Image

SBCL has the ability to save its state as a file for later execution. This functionality is important for its bootstrapping process, and is also provided as an extension to the user.

Function: save-lisp-and-die [sb-ext] core-file-name &key toplevel executable save-runtime-options callable-exports purify root-structures environment-name compression

Save a "core image", i.e. enough information to restart a Lisp process later in the same state, in the file of the specified name. Only global state is preserved: the stack is unwound in the process.

The following &key arguments are defined:

:toplevel

    The function to run when the created core file is resumed. The default function handles command line toplevel option processing and runs the top level read-eval-print loop. This function returning is equivalent to (sb-ext:exit :code 0) being called.

    toplevel functions should always provide an abort restart: otherwise code they call will run without one.
:executable

    If true, arrange to combine the sbcl runtime and the core image to create a standalone executable. If false (the default), the core image will not be executable on its own. Executable images always behave as if they were passed the –noinform runtime option.
:save-runtime-options

    If true, values of runtime options –dynamic-space-size and –control-stack-size that were used to start sbcl are stored in the standalone executable, and restored when the executable is run. This also inhibits normal runtime option processing, causing all command line arguments to be passed to the toplevel. Meaningless if :executable is nil.
:callable-exports

    This should be a list of symbols to be initialized to the appropriate alien callables on startup. All exported symbols should be present as global symbols in the symbol table of the runtime before the saved core is loaded. When this list is non-empty, the :toplevel argument cannot be supplied.
:purify

    If true (the default on cheneygc), do a purifying gc which moves all dynamically allocated objects into static space. This takes somewhat longer than the normal gc which is otherwise done, but it’s only done once, and subsequent GC’s will be done less often and will take less time in the resulting core file. See the purify function. This parameter has no effect on platforms using the generational garbage collector.
:root-structures

    This should be a list of the main entry points in any newly loaded systems. This need not be supplied, but locality and/or gc performance may be better if they are. This has two different but related meanings: If :purify is true - and only for cheneygc - the root structures are those which anchor the set of objects moved into static space. On gencgc - and only on platforms supporting immobile code - these are the functions and/or function-names which commence a depth-first scan of code when reordering based on the statically observable call chain. The complete set of reachable objects is not affected per se. This argument is meaningless if neither enabling precondition holds.
:environment-name

    This has no purpose; it is accepted only for legacy compatibility.
:compression

    This is only meaningful if the runtime was built with the :sb-core-compression feature enabled. If nil (the default), saves to uncompressed core files. If :sb-core-compression was enabled at build-time, the argument may also be an integer from -1 to 9, corresponding to zlib compression levels, or t (which is equivalent to the default compression level, -1).
:application-type

    Present only on Windows and is meaningful only with :executable t. Specifies the subsystem of the executable, :console or :gui. The notable difference is that :gui doesn’t automatically create a console window. The default is :console.

The save/load process changes the values of some global variables:

*standard-output*, *debug-io*, etc.

    Everything related to open streams is necessarily changed, since the os won’t let us preserve a stream across save and load.
*default-pathname-defaults*

    This is reinitialized to reflect the working directory where the saved core is loaded.

save-lisp-and-die interacts with sb-alien:load-shared-object: see its documentation for details.

On threaded platforms only a single thread may remain running after sb-ext:*save-hooks* have run. Applications using multiple threads can be save-lisp-and-die friendly by registering a save-hook that quits any additional threads, and an init-hook that restarts them.

This implementation is not as polished and painless as you might like:

    It corrupts the current Lisp image enough that the current process needs to be killed afterwards. This can be worked around by forking another process that saves the core.
    There is absolutely no binary compatibility of core images between different runtime support programs. Even runtimes built from the same sources at different times are treated as incompatible for this purpose. 

This isn’t because we like it this way, but just because there don’t seem to be good quick fixes for either limitation and no one has been sufficiently motivated to do lengthy fixes. 

Variable: save-hooks [sb-ext]

A list of function designators which are called in an unspecified order before creating a saved core image.

Unused by sbcl itself: reserved for user and applications. 

In cases where the standard initialization files have already been loaded into the saved core, and alternative ones should be used (or none at all), SBCL allows customizing the initfile pathname computation.

Variable: sysinit-pathname-function [sb-ext]

Designator for a function of zero arguments called to obtain a pathname designator for the default sysinit file, or nil. If the function returns nil, no sysinit file is used unless one has been specified on the command-line. 

Variable: userinit-pathname-function [sb-ext]

Designator for a function of zero arguments called to obtain a pathname designator or a stream for the default userinit file, or nil. If the function returns nil, no userinit file is used unless one has been specified on the command-line. 

To facilitate distribution of SBCL applications using external resources, the filesystem location of the SBCL core file being used is available from Lisp.

Variable: core-pathname [sb-ext]

The absolute pathname of the running sbcl core. 

但是,由于缺乏demo,我并不清楚如何编译链接出相应的out文件(叹气,叹气)

1 个赞

SBCL 做不到的,不用想了

想要独立可执行文件,设置这个

:executable

If true, arrange to combine the sbcl runtime and the core image to create a standalone executable. If false (the default), the core image will not be executable on its own. Executable images always behave as if they were passed the –noinform runtime option.

1 个赞

那么Lisp是不能编译链接成.out可执行文件吗?

只有编译到 C 的 Kyoto Common Lisp 系列可以,比如 GCL 和 ECL

1 个赞

在这里补充一个答案
Building Common Lisp Executables - Susam's Maze.

但是sbcl生成的文件特别大 34.9 MB (34,937,464 字节)

https://www.xach.com/lisp/buildapp/

本人最后的记录:使用ccl编译器处理lisp 生成可执行文件 ccl编译器地址 https://ccl.clozure.com/

(defun main() ;; 主函数
;; Your code here...
)
(ccl:save-application "output-filename" :toplevel-function `main :prepend-kernel t)

CCL 也就和 SBCL 半斤八两,运行速度也不是很快。你真在意这几十MB的话可以花钱买 lispworks 或者 allegro,他们那个支持裁剪 runtime

但说实话没必要一定追求导出个可执行文件,就和 Java 自己电脑上用不会每编译出来个程序都配个 JRE 一样的道理。

3 个赞

这让我联想到了 smalltalk 。

确实,Lisp是一门解释性语言.

设计 smalltalk 的 Xerox 之前也做过 Lisp Machine 的

这个可以参考?

1 个赞

补充一点点可能有用的信息…

测试环境:Win11,sbcl 2.4.2,Lispworks 8.0.1 64bit

SBCL未加载任何system未压缩save executable大小为42.6MB,压缩后为11.7MB

Lispworks运行examples/delivery/hello/deliver.lisp官方示例,内容是CAPI dialog显示hello world,Tree shaking level 5产出的exe大小为12.1MB

为了对比,这边给SBCL装载了Ltk,同样写了个hello world窗体作为toplevel,压缩后exe大小是和Lispworks相同的12.1MB

不知道为什么感觉这两年sbcl的体验越来越好了,executable的表现也变好了,大概不是幻觉

所以tree shaking确实有用,大概和压缩的效果差不多吧:(

不过lispworks tree-shaking过的二进制还可以进一步压缩成1.32MB大的自解压可执行文件就是另一回事了。另一边compressed过后的SBCL binary再压缩就没有效果了。

实际生产中感觉Lispworks tree-shaking效率最明显的是在level 5,看manual也可以知道,level 5向symbols和repl开刀,大概能摇掉至少十几M。但实际上要把代码优化到通过level 5的程度也很繁琐,因为很多依赖和特性就是会依赖于reader、interpreter甚至是restarts才能工作,这时候就只能把deliver的:keep-debug-mode打开,产出的二进制大小就和level 4差不多了…中间还会有因为shake掉symbol-name呀class definitions呀导致的各种奇怪的问题需要解决…

3 个赞