mirror of
https://gitlab.com/embeddable-common-lisp/ecl.git
synced 2025-12-05 18:30:24 -08:00
doc: document the cross compilation feature
This commit is contained in:
parent
e30c37691b
commit
872622bd0c
2 changed files with 259 additions and 0 deletions
|
|
@ -4,6 +4,7 @@
|
|||
@menu
|
||||
* Compiling with ECL::
|
||||
* Compiling with ASDF::
|
||||
* Cross compilation::
|
||||
* C compiler configuration::
|
||||
@end menu
|
||||
@c * Compiling with Matroska::
|
||||
|
|
@ -538,6 +539,212 @@ here, since our Lisp library is statically bundled with the executable.
|
|||
The result is the same as the shared library example above. You can also
|
||||
build all dependent libraries separately as static libraries.
|
||||
|
||||
@node Cross compilation
|
||||
@subsection Cross compilation
|
||||
|
||||
ECL supports cross compiling Lisp files for a target system that differs
|
||||
from the host system on which the compilation takes place. This section
|
||||
of the manual describes how to use this feature and explains important
|
||||
things to keep in mind when cross compiling Common Lisp.
|
||||
|
||||
@node Getting started with cross compilation
|
||||
@subsubsection Getting started with cross compilation
|
||||
|
||||
To get started, follow the steps described below:
|
||||
|
||||
@enumerate
|
||||
@item
|
||||
Cross compile ECL itself following the steps described in @ref{Building
|
||||
ECL}.
|
||||
@item
|
||||
Step one will create a Lisp file called @file{target-info.lsp}
|
||||
containing all the information necessary for the ECL compiler to create
|
||||
compiled files for the chosen target system. Load this information into
|
||||
a running ECL process on the host system by calling
|
||||
@code{c:read-target-info} on this file.
|
||||
@item
|
||||
Supply the resulting target information to the @code{:target} option of
|
||||
@coderef{compile-file} or @coderef{with-compilation-unit}.
|
||||
@end enumerate
|
||||
|
||||
For an example, consider two files @file{a.lisp} and @file{b.lisp} which
|
||||
are supposed to be linked into a shared library. This can be
|
||||
accomplished with the following steps:
|
||||
|
||||
@example
|
||||
(defvar *target* (c:read-target-info "/path/to/ecl/installation/lib/ecl-xx.x.x/target-info.lsp"))
|
||||
|
||||
(compile-file "a.lisp" :target *target* :system-p t)
|
||||
(load "a.lisp") ; make macro definitions in "a.lisp" accessible to "b.lisp"
|
||||
(compile-file "b.lisp" :target *target* :system-p t)
|
||||
|
||||
(with-compilation-unit (:target *target*)
|
||||
(c:build-shared-library "example"
|
||||
:lisp-files '("a.o" "b.o")
|
||||
:init-name "init_example"))
|
||||
@end example
|
||||
|
||||
Cross compilation using ASDF is not supported yet but is planned for the
|
||||
future.
|
||||
|
||||
@node Limitations and pitfalls for cross compiling Common Lisp
|
||||
@subsubsection Limitations and pitfalls for cross compiling Common Lisp
|
||||
|
||||
The ubiquity with which typical Common Lisp programs run code at compile
|
||||
time in macros or other similar constructs makes cross compilation
|
||||
somewhat more challenging than in other programming languages. Since
|
||||
compilation happens on a different system than the one in which the code
|
||||
generated by a macro is run, differences between the host and target
|
||||
environment can lead to bugs if the macro hasn't been written with cross
|
||||
compilation in mind. Consider for instance the following example macro
|
||||
iterating over prime number in the range from @code{a} to @code{b}:
|
||||
|
||||
@example
|
||||
(defmacro do-prime-numbers ((a b) p &body body)
|
||||
`(loop with ,p = (next-prime ,a)
|
||||
until (> ,p ,b)
|
||||
do ,(if (typep b 'fixnum)
|
||||
`(locally (declare (fixnum ,p))
|
||||
,@@body)
|
||||
`(progn ,@@body))
|
||||
(setf ,p (next-prime (1+ ,p)))))
|
||||
@end example
|
||||
|
||||
While this macro works fine in a standard setting, it will produce
|
||||
an incorrect declaration when cross compiling if the size of a fixnum
|
||||
is larger in the host than in the target and the upper bound @code{b} is
|
||||
larger than @code{most-positive-fixnum} in the target but smaller than
|
||||
@code{most-positive-fixnum} in the host. In general, any observable
|
||||
difference between target and host system may contribute to such issues.
|
||||
In the list below, we collect a number of such potential issues and
|
||||
explain strategies to solve them.
|
||||
|
||||
@itemize
|
||||
@item
|
||||
Different type or subtype definitions. This may arise from differing
|
||||
word sizes, as in the fixnum example above, or different feature support
|
||||
such as complex floating point number support in the C compiler leading
|
||||
to differing subtype relationships for complex numbers in Lisp. There
|
||||
are two main strategies for dealing with this problem.
|
||||
@enumerate
|
||||
@item
|
||||
Avoid the problem by rewriting the code not to rely on target specific
|
||||
information. For instance, in the above example we can change the
|
||||
declaration as follows to avoid relying on fixnums:
|
||||
@example
|
||||
(defmacro do-prime-numbers ((a b) p &body body)
|
||||
`(loop with ,p = (next-prime ,a)
|
||||
until (> ,p ,b)
|
||||
do (locally (declare (type (integer ,a ,b) ,p))
|
||||
,@@body)
|
||||
(setf ,p (next-prime (1+ ,p)))))
|
||||
@end example
|
||||
If the declared integer type is a subtype of fixnum, the ECL compiler
|
||||
will automatically take this into account and optimize the same as if a
|
||||
fixnum declaration had been made.
|
||||
@item
|
||||
Use the lexical environment parameter to @clhs{f_typep.htm,typep} and
|
||||
@clhs{f_subtpp.htm,subtypep}. Both functions take an optional parameter
|
||||
which can be used to supply information about type relationships in the
|
||||
target system. In the above example, this works as follows:
|
||||
@example
|
||||
(defmacro do-prime-numbers ((a b) p &body body &environment env)
|
||||
`(loop with ,p = (next-prime ,a)
|
||||
until (> ,p ,b)
|
||||
do ,(if (typep b 'fixnum env)
|
||||
`(locally (declare (fixnum ,p))
|
||||
,@@body)
|
||||
`(progn ,@@body))
|
||||
(setf ,p (next-prime (1+ ,p)))))
|
||||
@end example
|
||||
It is important, however, not to blindly modify every occurrence of
|
||||
@code{typep} and @code{subtypep} in this way. Some calls to these
|
||||
functions may check for types in the host environment, in which case
|
||||
they should not receive an environment parameter. It is necessary to
|
||||
know for which environment the call is meant in order to decide what to
|
||||
do.
|
||||
@end enumerate
|
||||
@item
|
||||
Different value of @code{*features*}, leading to mismatched @code{#+}
|
||||
and @code{#-} read time conditionals. During cross compilation,
|
||||
@code{*features*} will be rebound to the value in the target
|
||||
system.@footnote{Changes to @code{*features*} during compilation are
|
||||
carried over in the target information structure. This means that in a
|
||||
scenario where one cross compiles a file @file{a.lisp} containing
|
||||
@code{(eval-when (:compile-toplevel) (push :my-feature *features*))}
|
||||
before cross compiling another file @file{b.lisp} containing
|
||||
@code{#+my-feature}, the read time conditional in @file{b.lisp} will
|
||||
evaluate to true.} However, macros that are defined in the host system
|
||||
will see the host @code{*features*} during read time. Consider again the
|
||||
example given above where @file{a.lisp} was cross compiled, then loaded
|
||||
before @file{b.lisp} was cross compiled. If @file{a.lisp} contains a
|
||||
macro definition
|
||||
@example
|
||||
(defmacro my-macro (...)
|
||||
#+android `(do-something ...)
|
||||
#-android `(do-something-else ...))
|
||||
@end example
|
||||
then @code{my-macro} will expand to @code{do-something} in @file{a.lisp}
|
||||
but @code{do-something-else} in @file{b.lisp} if cross compiling for the
|
||||
android target. There are several ways to deal with this issue:
|
||||
@enumerate
|
||||
@item
|
||||
Load source files containing macro definitions in an environment where
|
||||
@code{*features*} has the same value as in the target system. This can
|
||||
be accomplished either by wrapping @code{load} in a
|
||||
@coderef{with-compilation-unit} call with a @code{:target} option given
|
||||
or by using the @code{:load} keyword option of @coderef{compile-file} in
|
||||
conjunction with @code{:target}. In the above example, we would compile
|
||||
as follows:
|
||||
@example
|
||||
(compile-file "a.lisp" :target *target* :system-p t :load t)
|
||||
(compile-file "b.lisp" :target *target* :system-p t)
|
||||
@end example
|
||||
Note that this will not work if the file contains read time conditionals
|
||||
selecting between different compile time code paths due to differences
|
||||
in the host system (let's say for example some code generation from a
|
||||
file that is located in a different location for different compilation
|
||||
hosts).
|
||||
@item
|
||||
Replace read time conditionals by lookups that run during macro
|
||||
expansion, e.g.
|
||||
@example
|
||||
(defmacro my-macro (...)
|
||||
(if (member :android *features*)
|
||||
`(do-something ...)
|
||||
`(do-something-else ...)))
|
||||
@end example
|
||||
@item
|
||||
Add feature keywords for the target system to @code{*features*} in the
|
||||
host before compilation. This is best suited for conditionals which only
|
||||
add but not subtract code (keywords which only appear in @code{#+}
|
||||
conditionals). In our example, we would call
|
||||
@example
|
||||
(push :android *features*)
|
||||
@end example
|
||||
before starting cross compilation.
|
||||
@end enumerate
|
||||
@item
|
||||
Floating point accuracy issues. ECL relies on the C standard library for
|
||||
numerical functions which may return slightly different values for
|
||||
different systems. If your code depends sensitively on numerical
|
||||
functions returning bitwise identical results, it is best not to run any
|
||||
numerical code at compile time and restrict yourself to only running run
|
||||
time code.
|
||||
@end itemize
|
||||
|
||||
Another option to avoid the aformentioned issues entirely is to use
|
||||
emulation instead of cross compilation. Even if full-blown emulation is
|
||||
too complicated, simply cross compiling from a version of ECL compiled
|
||||
for the same word size as the target system would for instance avoid the
|
||||
fixnum issues in our example macro alltogether.
|
||||
|
||||
Whether the compiler is switched to cross compilation mode or not can be
|
||||
diagnosed from the presence of a @code{:cross} keyword in
|
||||
@code{*features*}. If necessary, this can be used to select different
|
||||
code paths as well.
|
||||
|
||||
|
||||
@node C compiler configuration
|
||||
@subsection C compiler configuration
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,58 @@
|
|||
@node System construction
|
||||
@section System construction
|
||||
|
||||
@node System construction - Dictionary
|
||||
@subsection Dictionary
|
||||
|
||||
@subsubsection Extensions
|
||||
|
||||
See also @ref{System building}.
|
||||
|
||||
@lspdef compile-file
|
||||
@defun compile-file input-file &key output-file verbose print external-format target system-p load c-file h-file data-file
|
||||
Additional options for @clhs{f_cmp_fi.htm,compile-file} include:
|
||||
@table @code
|
||||
@item :system-p
|
||||
Create an object file (.o)
|
||||
@item :target
|
||||
Cross compilation target, see @ref{Cross compilation}. Can be either the
|
||||
target information obtained by calling @code{c:read-target-info} or a
|
||||
pathname pointing to the installation directory of the target ECL for
|
||||
which to compile.
|
||||
@item :load
|
||||
Load the output file after compilation. If a @code{:target} argument is
|
||||
given alongside @code{:load}, load the source file in the target
|
||||
environment.
|
||||
@item :c-file, :h-file, :data-file
|
||||
Boolean flags controlling whether to keep temporary files created by the compiler
|
||||
@end table
|
||||
@end defun
|
||||
|
||||
@lspdef compile-file-pathname
|
||||
@defun compile-file-pathname input-file &key output-file type system-p
|
||||
Additional options for @clhs{f_cmp__1.htm,compile-file-pathname} include:
|
||||
@table @code
|
||||
@item :type
|
||||
Specify the type of output file. One of @code{:fasl} (default, alias @code{:fas}), @code{:c}, @code{:h}, @code{:data}, @code{:object}, @code{:program}, @code{:shared-library} (alias @code{:dll}), @code{:static-library} (alias @code{:library}, @code{:lib}), @code{:precompiled-header} or @code{:import-library} (MSVC only)
|
||||
@item :system-p
|
||||
If no type is supplied, default to @code{:object} type instead of @code{:fasl}
|
||||
@end table
|
||||
@end defun
|
||||
|
||||
@lspdef with-compilation-unit
|
||||
@deffn {Macro} with-compilation-unit (&key override target) &body body
|
||||
Additional options for @clhs{m_w_comp.htm,with-compilation-unit} include:
|
||||
@table @code
|
||||
@item :target
|
||||
Cross compilation target, see @ref{Cross compilation}. Any compilation
|
||||
operation produces outputs for the specified target system. This
|
||||
includes @clhs{f_cmp_fi.htm,compile-file},
|
||||
@clhs{f_disass.htm,disassemble} and all ECL specific functions described
|
||||
in the section on @ref{Compiling with ECL}. The only exception is
|
||||
@clhs{f_cmp.htm,compile} which continues to work as usual.
|
||||
@end table
|
||||
@end deffn
|
||||
|
||||
@subsection C Reference
|
||||
|
||||
@subsubsection ANSI Dictionary
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue