Embeddable Common-Lisp

Content from 2016-02

ECL 16.1.2 release

posted on 2016-02-29

We are happy to inform that the official ECL 16.1.2 release is available for download:

ECL Quarterly Volume III

posted on 2016-02-06

1 Preface

Dear all,

I'm proud to publish the third ECL Quarterly volume. I'm aware that it's a bit late, but very happy that delay didn't extend any further. Some exciting stuff is going on. First of all Embeddable Common-Lisp has been successfully ported to the Android platform. Additionally we have untested ports to NaCL and PNaCL (any volunteer?) and an alpha quality Android application - ecl-android (fully fledged swank-accessible Common Lisp running in Dalvik via JNI with all the goodies ECL provides).

I'm very happy to open this volume with a great guide written and contributed by Earl Ducaine – Stupid ECL tricks. This is set of a very useful hints for the development with ECL. We invite everyone to contribute to the repository located at Earl's GitHub repository. Moreover Angelo Rossi has finished his project embodying ECL on the embedded board SBC MIPS Creator CI20 and written very nice summary to share with us.

Third chapter will resolve around the Android port and how to build the ecl-android application.

I want to apology for this delay everyone, who waited for this volume. I'm quite busy lately and despite having some unfinished material I couldn't find time to polish it, so I've decided to skip it for the next volume. I'm very grateful to both Earl and Angelo for providing good material for a readers. I hope the next volume will be published very soon, or at least on schedule :-). Thank you for waiting.

We're testing now a new release of ECL (version number 16.1.2) and if everything goes fine it will be released on February 29th. If you are curious about the changes, you may skim the "Pending changes" section here.

*Please* send all the feedback to the mailing list or directly to me. It is really ensuring to know, that someone reads this. Also if you want to be published here please let me know. Thank you!


Daniel Kochmański ;; aka jackdaniel | TurtleWare
Poznań, Poland
February 2016

2 Stupid ECL tricks

Mostly these are half baked hacks. But hopefully they stimulate the imagination of real programmers by providing a glims of what ELC is cabable of.

2.1 Running ECL in gdb.

I've always had a total mental block when it comes to C pointers. It makes no sense to my brain that * indicates a variable is a pointer when used in a declaration, but retrieves a value when used as an operator. And an array of pointers to a character string makes total sense to me in words but char** str[] causes my mind to go blank. As a consequence any C code I write, or even look at too intently immediately blows up when compiled and run. A big inconvenience when embedding Lisp. Replacing the usual,

(setq inferior-lisp-program "ecl")

with,

(setq inferior-lisp-program
      "gdb --eval-command=run --eval-command=quit --args ecl")

Will run ecl under gdb, which will provide you the normal gdb environment with c runtime errors, while throwing you into the lisp debugger for Lisp errors. Note that gdb by default breaks on SIGPWR and SIGXCPU which ecl uses for internal processing. So, you'll also want to add the following to your .gdbinit file.

handle SIGPWR nostop noprint
handle SIGXCPU nostop noprint

2.2 Embedding Swank in a c application.

Swank is a Lisp program that provides remote access to a Lisp instance. It started as client/server application layer in CMUCL and the Hemlock editor it ran. It's since been ported to most Lisps. Slime is the Emacs front-end client to Swank. Together the two tools provide a powerful Lisp development environment in Emacs. The easiest way to install Swank and Slime is simply to get it from quicklisp. See:

https://www.quicklisp.org/beta/

Swank and slime work in following way:

+----------+     launch ecl in                +--------------------+ 
| emacs    |---- process buffer, tell ------> | ecl process buffer |
+----------+     ecl to start swank           +-----+--------------+                         
   |                                                       |
   |                                         start swank server:
create slime                                 (swank-loader:init)
buffer                                       (swank:start-server)
   |                                                |
   |                                                |
  \/                                              \/
+--------------+      integrated      +--------------------------------+
| repl:        +<---- lisp repl   --->| swank server listening         |
| slime buffer |      interaction     | on some arbitrary              |
+--------------+                      | TCP/IP port e.g.               |
                                      | "Swank started at port: 46493" |
                                      +--------------------------------+
                                                      /\
+--------------------------+                           |
| edit:                    +<--------------------------+
| buffer with Lisp source  |
+--------------------------+

To embed swank in a C application we need the application to launch Swank and then for Emacs to establish the connection to the swank server using slime-connect. Below is the C code that launches Swank.

Note, the following example is for a GNU/Linux type system. ecl needs to explicitly load load a shared library in order to access binary symbols such as C functions or C variables in the process, this is a hackish way of handling it since the library was already loaded when the applicaiton started, and could cause problems on platforms that put different constraints on loading shared libraries.

/* -*- mode: c;  -*-
   file: main.c
*/

#include "app_main.h"
/* a.out wrapper for call into a shared library. */
int main() {
  return app_main();
}
/* -*- mode: c;  -*-
   file: app_main.h
*/

#ifndef __APP_MAIN_H__
#define __APP_MAIN_H__

#include <ecl/ecl.h>

int app_main();

#endif /* APP_MAIN_H */

The following creates the shared library app_main used by both the C program and ECL for symbols. The embedded ECL code initializes the ECL environment and calls the Common Lisp load function to load a local Lisp file with the code to run swank.

/* -*- mode: c;  -*-
   file: app_main.c
*/

#include <stdlib.h>
#include <math.h>
#include "app_main.h"

void run_swank();

/* TODO: Are embedded quotes really needed? */
char start_swank[] =
  "\"/mnt/pixel-512/dev/stupid-ecl-tricks-1/start-swank-server.lisp\"";

char* argv;
char** pargv;

int app_main() {
  argv = "app";
  pargv = &argv;

  cl_boot(1, pargv);
  atexit(cl_shutdown);

  /* Set up handler for Lisp errors to prevent buggy Lisp (an */
  /* imposibility, I know!) from killing the app. */
  const cl_env_ptr l_env = ecl_process_env();
  CL_CATCH_ALL_BEGIN(l_env) {
    CL_UNWIND_PROTECT_BEGIN(l_env) {
      run_swank();
    }
    CL_UNWIND_PROTECT_EXIT {}
    CL_UNWIND_PROTECT_END;
  }
  CL_CATCH_ALL_END;

  return 0;

}

void run_swank() {
  cl_object cl_start_swank_path = c_string_to_object(start_swank);
  cl_object cl_load =  ecl_make_symbol("LOAD","CL");
  cl_funcall(2, cl_load, cl_start_swank_path);
  return;
}

The following Lisp file, loaded by appmain, contains a couple of snippets of code I copied from the Emacs Slime client that launches the Swank server. When Swank launches it will print out the socket you can use to connect to it, e.g.

;; Swank started at port: 58252.

you can then connect to it in Emacs using Slime:

M-x slime-connect

;;; -*- mode: lisp ; syntax: ansi-common-lisp -*-

;; standard quicklisp init file, since with be launching ecl without ~/.eclrc
(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp"
                                       (user-homedir-pathname))))
  (when (probe-file quicklisp-init)
    (load quicklisp-init)))

(when (probe-file  "/tmp/slime.2565")
  (delete-file "/tmp/slime.2565"))

(load
 "~/quicklisp/dists/quicklisp/software/slime-2.14/swank-loader.lisp"
 :verbose t)

(funcall (read-from-string "swank-loader:init"))
(funcall (read-from-string "swank:start-server")
         "/tmp/slime.2565"))

A quick and dirty script file to build a shared library.

# -*- mode: bash;  -*-


rm -f *.o *.so app

export libs="-lm"

# Note, the -Wl,-R flags will make our shared library available to the
# executable app from the location that it was compiled, rather than
# having to be installed globably or adding the build path to
# LD_LIBRARY_PATH.

export ldflags="-L. -Wl,-R -Wl,."
export cflags="-DGC_LINUX_THREADS -D_REENTRANT -fPIC  -g -pipe -Wall"

gcc $cflags -c app_main.c
gcc -shared -Wl,-soname,libapp_main.so $ldflags -lecl -o libapp_main.so *o $libs
gcc main.c $cflags $ldflags -lapp_main -lecl -o app

To build and run

$ ./build_app.sh
$ ./app

2.3 Troubleshooting compilation problems with ffi:c-inline

ECL provide a facility for embedding C code directly in Lisp code like the following:

(defun c-sin (x)
  (ffi:clines "#include \"ecl/ecl.h\"")
  ;; Whoops!  mathh.h should be math.h
  (ffi:clines "#include <mathh.h>")
  (ffi:clines  "#include \"app_main.h\"")
  (ffi:c-inline (x) (:double) :double "{
@(return 0)= sin(#0);
}" :one-liner nil))

To use this function you need to compile the defun. When you issue the explicit compile,

(compile 'c-sin)

ECL will invoke your underlying C compiler. However, C syntax and header include errors, like we included in the above example, will cause compilation to fail. Unfortunately, ECL doesn't pass along the compilers output. You'll get something like the following:

;;; OPTIMIZE levels: Safety=2, Space=0, Speed=3, Debug=3
;;;
;;; End of Pass 1.
;;; Internal error:
;;;   ** Error code 1 when executing
;;; (RUN-PROGRAM "gcc" ("-I." "-I/usr/local/include/" "-D_GNU_SOURCE" "-D_FILE_OFFSET_BITS=64" "-g" "-O2" "-fPIC" "-D_THREAD_SAFE" "-Dlinux" "-O2" "-c" "/tmp/ecl001QoKf80.c" "-o" "/tmp/ecl001QoKf80.o"))

if you try to recreate the error by invoking the implied shell command:

$ gcc -I. -I/usr/local/include/ -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 \
    -g -O2 -fPIC -D_THREAD_SAFE -Dlinux -O2 -c /tmp/ecl001QoKf8.c \
    -o /tmp/ecl001QoKf80.o

You'll get the error:

gcc: error: /tmp/ecl001QoKf80.c: No such file or directory
gcc: fatal error: no input files
compilation terminated.

Because ECL has already cleaned it from /tmp.

But, ECL has a special variable, compiler::*delete-files* that controls cleaning up c output files. By setting it to nil, (setf compiler::*delete-files* nil) you can troubleshoot compilation errors. Re-running above gcc command on from the Unix shell gives us the following:

In file included from /tmp/ecl001QoKf80.c:6:0:
/tmp/ecl001QoKf80.eclh:8:19: fatal error: mathh.h: No such file or directory
#include <mathh.h>
                ^
compilation terminated.

2.4 Cache Files

Swank and ECL's embedded C in Lisp facility seem to have some issues with caching where compiled C snippets and a Swank images don't get refreshed when they should (at least on GNU/Linux). If you start noticing strange issues with changes to ffi:c-inline not taking effect or Swank having the wrong image, try deleting the following cache files:

rm -rf ~/.cache/common-lisp/ecl-15.2.21-ee989b97-linux-x64
rm -rf ~/.slime

/Earl Ducaine/

3 ADIO project with ECL and a bunch of electronic devices

3.1 Synopsis

This document describe my experience with the SBC MIPS Creator CI20 from Imagination and ecl (Embeddable Common Lisp) interpreter/compiler. The goal is to program a distributed diagnostic/supervision system for DVB transmitter equipment's.

development-board.jpg

3.2 The experience

The Creator CI20 comes with a linux Debian Jessie distro preinstalled on its 8 GB internal flash drive. After upgrading it and install some packages I was ready to deploy ECL. The version choosen for the prototype was the 16.1.0, compilation was really straightforward (I modified a bash script written for SBCL to download, compile and install the git version of ECL) and after 20/30 minutes of code crunching I've got a shiny working ECL. I must say that problems didn't come from ECL itself, but from other packages. As I installed quicklisp soon came troubles, for example: slime does not add the correct architecture to the *features* variable and some other minor problems with iolib too. The Lisp community is the most active and helpful among those living on irc and with their help I've got fixed and ready in short time. The project I'm currently working on is divided in two main areas: the hardware design and test phase and the software one. The first, at this stage, contemplates the design and production of a limited number of ADIO boards (designed by me). More basic an ADIO board is an insulated analog and digital i/o board, it is interfaced with the Creator CI20 via the i2c bus and externally powered. The software part is the more vast part of the project. The idea is to provide a system that is able to interface to external users and feed them with sensors data regarding the state of the DVB apparatuses plus every ADIO + Creator CI20 hold the configuration for the apparatus they're going to monitor/supervise. In addition to that the system should be responsible to warn about anomalies and take some pre-emptive actions just to not make the whole thing blow up. So the Customer was facing a dilemma: Arduino + RS485 and a lot of amulets or rely on a modern and saner idea? My advice for the landing party was: "why do not use a cheap sbc (80 Eur) with an embedded OS powered by a Lisp interpreter?" here are the benefits:

  • ethernet based communication hardware layer is proven to be cost effective and reliable, plus every oses and cheap sbcs support it;
  • an embedded os, in most cases, is better than start a baremetal application: e.g. tcp/ip connections and standard network services like http, ftp, ssh, samba and snmp require a lot time to program, debug and test even if you use on the shelf solutions. on the contrary with an embedded os you've got these things already coded and ready to use, shortening the deployment time of about one order of magnitude (in man hours);
  • using lisp is useful for almost three aspects: portability, remote debugging and last but not least the possibility to create compiled code for mips;
  • possibility to expand hardware and software via external modules.

lcd-panel.jpg

So lisp at last, the first prototype is now running a test code which provide basic functionalities like: a general i2c library to access the linux i2c device via /dev/i2c-*, a simple i2c gpio expander library for the mcp23017 and another library to cope with hitachi compatible 16x2 lcd and a simple looping program which displays some random characters on it. next improvements are ready to be tested too, like a machine learning algorithm to detect anomalies and so on.

3.3 Last words

At this stage, ECL was truly the right choice: first of all the time to develop this small set of libraries was incredibly short compared to c/c++ due to that now I've got a test prototype running on my desk happily showing on a 16x2 display some test patterns in just about one month circa from Lisp environment setup. I must thank the friend PJB for his invaluable help and patience and jackdaniel for his work on ECL and the patience too.

/Angelo Rossi/

4 ECL Android port

I'm happy to announce, that the android port has been merged to the develop branch of the main ECL repository. This work is mainly based on Sylvain Ageneau's work. I've also adjusted and incorporated NaCL and pNaCL patches but these are not tested (yet).

To use ECL fully on the android, user want's to have an application which may be started from within the phone, not in the terminal. That's why I've created a separate project ECL Android which is based on Sylvain's integration example code (further adjusted by Evrim Ulu). For now it is in alpha state and it's discouraged to use it in applications meant for the end user.

The following sections will explain current compilation process of the ECL and how to build ECL Android application and deploy it to the computer.

4.1 Building ECL

ECL may be built as a library. This great feature allows us to dynamically link ECL with arbitrary applications capable of using shared objects. We take advantage of this and the fact, that the android platform allows us to embed shared objects with the .apk and use it with the Java Native Interface (JNI).

To use libecl we first need to cross compile it for the target platform. There is some work pending to simplify the cross compilation process, but for a time being you have to build a host ECL compiler and after that the target library.

Before that, however, you have to build a proper android toolchain. If you don't have a prebuilt one you may use android NDK scripts. Both Android SDK and NDK are a prerequisites and installing them is an exercise for the reader.

export PLATFORM_PREFIX=/opt/toolchains/android-ndk9/
export NDK_PATH=/opt/android-ndk/
export NDK_PLATFORM=android-12

mkdir ${PLATFORM_PREFIX}
/opt/android-ndk/build/tools/make-standalone-toolchain.sh \
    --platform=${NDK_PLATFORM} \
    --install-dir=${PLATFORM_PREFIX} \
    --arch=arm

export PATH=${PLATFORM_PREFIX}/bin:${PATH}

Now you have to build the host compiler1 and the final library for the android. Note, that using the preexisting ECL binary could work if you it is a 32 bit installation with disabled longdouble.

./configure ABI=32 CFLAGS="-m32 -g -O2" LDFLAGS="-m32 -g -O2" \
            --disable-longdouble \
            --prefix=`pwd`/ecl-android-host \
            --enable-libatomic=included

make && make install
export ECL_TO_RUN=`pwd`/ecl-android-host/bin/ecl
rm -r build

./configure --host=arm-linux-androideabi \
            --prefix=`pwd`/ecl-android-target \
            --with-cross-config=`pwd`/src/util/android.cross_config \
            --disable-soname

# You have to adjust build/cross_config to your settings (especially
# set ECL_TO_RUN to your host ecl)
make && make install

You should have libecl.so and the other necessary files in the ecl-android/ directory. Some pre-compiled modules are located in the ecl-android/lib/ecl-16.1.0 directory. You will want them on the target system.

4.2 ECL Android (application/service)

This application isn't stable yet and documentation is still rather scarce. API isn't stable and it is discouraged to base any work on it for now. It is provided on terms of AGPL-3.0+ license, however alternative licensing is possible.

First thing to do is to clone the repository and adjust the project configuration to your local setup.

git clone https://gitlab.common-lisp.net/ecl/ecl-android.git
cd ecl-android
# update the project (sets sdk path and the other android "magic")
android update project -t android-10 -p .
# create symlinks (sets ECL directories). For instance:
ln -s ../ecl-android-target ecl-android
ln -s ecl-android/lib/ecl-*.*.* ecl-libdir

Now it's worth to explain how the application works on the target platform. We put the library libecl.so in the apk file, but rest of the module and other ECL-related files are packed as a resource in the assets/lisp/ directory at which *default-pathname-defaults* points to.

Initialization is performed by assets/lisp/etc/init.lisp file, which loads the user.lisp. The latter contains some sample code loading swank (if it's put in the home/slime-2.14/ directory – swank is not bundled with the repository) and defines auxiliary functions: #'get-quicklisp, #'start-swank and #'stop-swank.

Function #'get-quicklisp will download and install the Quicklisp. It will also replace it's bundled compression tools with a prebuilt one (it is essential for performance – GCC produces faster code then the byte compiler).

Before you build the ecl-android you may want to copy some files for further use and edit the initialization script:

mkdir assets/lisp/home
cp -rf ~/things/slime-2.14 assets/lisp/home
cp ~/things/my-awesome-lisp-app/awesome.lisp assets/lisp/
emacs assets/etc/user.lisp

When you are ready you may build and deploy the application on the phone:

ndk-build
ant debug install

ECL Android launcher should appear on your phone.

Footnotes:

1

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:

sudo port select --set gcc none

Hint provided by Pascal J. Bourguignon.