Compiling lisp game to C with ECL

This commit is contained in:
Joseph Ferano 2024-11-13 10:56:48 +07:00
parent 90e927ced2
commit b8770d04e3
5 changed files with 302 additions and 2 deletions

1
.gitignore vendored
View File

@ -8,3 +8,4 @@
/boids_main
/libboids.so
/game.fasl
/writeimage

270
ECL-INSTALL Normal file
View File

@ -0,0 +1,270 @@
You will find detailed installation instructions in the ECL manual
https://common-lisp.net/project/ecl/static/manual/Building-ECL.html
If you do not have access to the online version, follow the following recipies.
* Unix and similar platforms.
1. Type
./configure --help
to get a list of the flags with which ECL can be configured.
2. Enter
./configure ...
where "..." is the set of flags you have chosen.
3. Use "make" followed by "make install" to build and install ECL.
* Windows with Visual Studio C++ 2008
1. Open the Visual Studio x86 or x64 native tools command prompt
2. Enter the msvc directory
3. Read the file Makefile to find the configuration options. They
typically have the form ECL_UNICODE=1, ECL_THREADS=1, etc
4. Enter
nmake ...
followed by zero or more of those options
5. Use "nmake install" to create a directory called "package" with ECL in it.
6. Move that directory wherever you need.
* Cross-compile for the android platform (from the UNIX machine)
1. Build the host ECL
#+BEGIN_SRC shell-script
# C99 complex numbers are not fully supported on Android
./configure ABI=32 CFLAGS="-m32 -g -O2" LDFLAGS="-m32 -g -O2"\
--prefix=`pwd`/ecl-android-host --disable-c99complex
make -j9
make install
rm -r build
export ECL_TO_RUN=`pwd`/ecl-android-host/bin/ecl
#+END_SRC
2. Configure the toolchain (requires android-ndk version 15 or higher, known to work with version 17c)
and export the necessary paths:
#+BEGIN_SRC shell-script
export NDK_PATH=/opt/android-ndk
export ANDROID_API=23
export TOOLCHAIN_PATH=`pwd`/android-toolchain
${NDK_PATH}/build/tools/make_standalone_toolchain.py --arch arm --install-dir ${TOOLCHAIN_PATH} --api ${ANDROID_API}
export SYSROOT=${TOOLCHAIN_PATH}/sysroot
export PATH=${TOOLCHAIN_PATH}/bin:$PATH
#+END_SRC
3. Build and install the target library
#+BEGIN_SRC shell-script
# boehm GC is not compatible with ld.gold linker, force use of ld.bfd
export LDFLAGS="--sysroot=${SYSROOT} -D__ANDROID_API__=${ANDROID_API} -fuse-ld=bfd"
export CPPFLAGS="--sysroot=${SYSROOT} -D__ANDROID_API__=${ANDROID_API} -isystem ${SYSROOT}/usr/include/arm-linux-androideabi"
export CC=arm-linux-androideabi-clang
./configure --host=arm-linux-androideabi \
--prefix=`pwd`/ecl-android \
--disable-c99complex \
--with-cross-config=`pwd`/src/util/android-arm.cross_config
make -j9
make install
#+END_SRC
4. Library and assets in the ecl-android directory are ready to run on
the Android system.
** Building ecl-android on Darwin (OSX)
If your host platform is darwin, then the host compiler should be
built with the Apple's GCC (not the GCC from Macports). Using the
MacPort command:
#+BEGIN_SRC shell-script
sudo port select --set gcc none
#+END_SRC
Hint provided by Pascal J. Bourguignon.
* Cross-compile for the iOS platform (needs Xcode 11 or higher)
1. Build the host ECL
#+BEGIN_SRC shell-script
./configure CFLAGS="-DECL_C_COMPATIBLE_VARIADIC_DISPATCH" --prefix=`pwd`/ecl-iOS-host --disable-c99complex
make -j9
make install
rm -r build
export ECL_TO_RUN=`pwd`/ecl-iOS-host/bin/ecl
#+END_SRC
2. Configure the toolchain
#+BEGIN_SRC shell-script
export IOS_VERSION_MIN="8.0"
export IOS_SDK_DIR="`xcode-select --print-path`/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/"
export CC="clang"
export CXX="clang++"
export CFLAGS="-arch arm64 -miphoneos-version-min=${IOS_VERSION_MIN} -isysroot ${IOS_SDK_DIR}"
export CFLAGS="$CFLAGS -pipe -Wno-trigraphs -Wreturn-type -Wunused-variable"
export CFLAGS="$CFLAGS -fpascal-strings -fasm-blocks -fmessage-length=0 -fvisibility=hidden"
export CFLAGS="$CFLAGS -O2 -DNO_ASM"
export LD="ld"
export LDFLAGS="-arch arm64 -pipe -std=c99 -gdwarf-2 -isysroot ${IOS_SDK_DIR}"
export LIBS="-framework Foundation"
#+END_SRC
3. Build and install the target library
#+BEGIN_SRC shell-script
export CFLAGS="$CFLAGS -DGC_DISABLE_INCREMENTAL -DECL_RWLOCK"
export CXXFLAGS="$CFLAGS"
./configure --host=aarch64-apple-darwin \
--prefix=`pwd`/ecl-iOS \
--disable-c99complex \
--disable-shared \
--with-cross-config=`pwd`/src/util/iOS-arm64.cross_config
make -j9
make install
#+END_SRC
4. Library and assets in the ecl-iOS directory are ready to run on
the iOS system.
* Cross-compile for the WASM platform (via emscripten)
Emscripten target is a little fickle so keep in mind that:
- shared libraries are supported but come with some drawbacks (e.g.
increased code size if used together with -sASYNCIFY), therefore the
build instructions default to disable-shared
- disable-tcp is needed, because accept can't be found (lack of
-lsockets or something in this spirit?)
- select for interactive streams does not work, because reading operations are
blocking (as they should be!), so there is no EOF returned -- clear-input will
hang without a proper file-cnt
- to build emscripten you need to use their SDK that provides the toolchain, and
set the environment variable EMSDK_PATH
- for the garbage collector to be able to identify roots on the stack,
you need to pass the -sBINARYEN_EXTRA_PASSES=--spill-pointers option
to the linker for all of your code that might store pointer on the
stack (for instance when embedding ECL)
- the optimization level -O0 is used because higher optimization
levels seem to interfere with the binaryen options needed to get the
garbage collector to work correctly and tend slow down the program
(might be worth experimenting with the optimization options)
1. Build the host ECL
#+begin_src shell-script
./configure ABI=32 CFLAGS="-m32 -g -O2 -DECL_C_COMPATIBLE_VARIADIC_DISPATCH" LDFLAGS="-m32 -g -O2" \
--prefix=`pwd`/ecl-emscripten-host --disable-threads
make -j16 && make install
rm -rf build/
#+end_src
2. Configure the toolchain
Install the Emscripten SDK using the official instructions:
https://emscripten.org/docs/getting_started/downloads.html
These build instructions were tested against ~emsdk 3.1.41~. If things doesn't
work try that version instead of ~latest~.
After that activate the toolchain and configure build flags:
#+begin_src shell-script
source ${EMSDK_PATH}/emsdk_env.sh
export ECL_TO_RUN=`pwd`/ecl-emscripten-host/bin/ecl
# You may customize various emscripten flags here, i.e:
# export LDFLAGS="-sASYNCIFY=1"
#+end_src
3. Build the core environment and install it
#+begin_src shell-script
emconfigure ./configure \
--host=wasm32-unknown-emscripten \
--build=x86_64-pc-linux-gnu \
--with-cross-config=`pwd`/src/util/wasm32-unknown-emscripten.cross_config \
--prefix=`pwd`/ecl-emscripten \
--disable-shared \
--with-tcp=no \
--with-cmp=no
emmake make && emmake make install
# some files need to be copied manually
cp build/bin/ecl.js build/bin/ecl.wasm ecl-emscripten/
#+end_src
4. ECL may be hosted on a web page. Assuming that you have quicklisp installed:
#+begin_src shell-script
export WEBSERVER=`pwd`/src/util/webserver.lisp
pushd ecl-emscripten/
lisp --load $WEBSERVER
# After the server is loaded run:
# firefox localhost:8888/ecl.html
popd
#+end_src
If the output does not show on the webpage then open the javascript console.
This is a default html website produced by emscripten.
5. Build an external program linked against libecl.a
The default stack size proposed by emscripten is 64KB. This is too little for
ECL, so when you build a program that is linked against libecl.a, then it is
imoprtant to specify a different size. For example:
#+begin_src shell-script
emcc program.c -sSTACK_SIZE=1048576 lib/*.a -I./include -o program.o
#+end_src
* Build using Cosmopolitan toolchain (experimental)
Binaries built with cosmopolitan toolchain can be executed on numerous platforms
without changes. An inititial ECL port has been completed.
1. Download cosmopolitan toolchain and activate it
Instructions for downloading the toolchain are available in its [[https://github.com/jart/cosmopolitan/blob/master/README.md ][repository]].
For example:
#+begin_src sh
mkdir cosmocc
pushd cosmocc
curl -o https://cosmo.zip/pub/cosmocc/cosmocc.zip
unzip *zip
bin/make --version # sanity test
export PATH="`pwd`/bin:${PATH}"
export CC=x86_64-unknown-cosmo-cc
export CXX=x86_64-unknown-cosmo-c++
popd
#+end_src
2. Build ECL
#+begin_src sh
./configure --disable-shared --prefix=/tmp/cosmo-cl
make -j15
make install
# make check
#+end_src
- The platform is reported in *FEATURES* as :COSMO and as :LINUX
- make check will fail because libc segfaults when using fenv.h
This platform is not upstreamed yet in libgc, so it may be necessary to add the
following code to the file gcconfig.h:
#+begin_src c
# if defined(__COSMOPOLITAN__)
# if defined(__x86_64__)
# define mach_type_known
# define X86_64
# define LINUX // optional?
# elif defined(__aarch64__)
# define mach_type_known
# define AARCH64
# define LINUX // optional?
# endif
# endif
#+end_src
For details see: https://github.com/jart/cosmopolitan/issues/939.
Cosmopolitan condition variables can't be used with mutexes that are not
initialized as PTHREAD_MUTEX_NORMAL. That means that recursive locks and
deterministic deadlock detection are not supported on this platform.
Moreover floating point exception handling is not working and it is libc issue
that manifests itself with segfaults.

12
README.org Normal file
View File

@ -0,0 +1,12 @@
#+begin_src shell
gcc -lecl ecl_test.c -o test-game libtest-game.a
#+end_src
#+begin_src lisp
(uiop:getcwd)
(uiop:chdir "Development/tinyswords/")
(compile-file "game.lisp" :system-p t)
(c:build-static-library "test-game" :lisp-files '("game.o") :init-name "game")
#+end_src

15
ecl_test.c Normal file
View File

@ -0,0 +1,15 @@
#include <ecl/ecl.h>
extern void game(cl_object cblock);
int main(int argc, char **argv)
{
/* setup the lisp runtime */
cl_boot(argc, argv);
/* call the init function via read_VV */
read_VV(OBJNULL, game);
/* ... */
/* shutdown the lisp runtime */
cl_shutdown();
return 0;
}

View File

@ -1,3 +1,5 @@
(load "~/quicklisp/setup.lisp")
(eval-when (:compile-toplevel :load-toplevel :execute)
(ql:quickload :cl-raylib))
@ -66,7 +68,7 @@
:white)
(rl:draw-fps 10 5))
(defun main ()
(defun game ()
(let* ((screen-width 900)
(screen-height 500))
(rl:with-window (screen-width screen-height "RTS")
@ -80,4 +82,4 @@
(loop for value being the hash-values of (game-state-textures *game-state*)
do (rl:unload-texture value)))))
(main)
(game)