# 31aug25 Software Lab. Alexander Burger

   Special Build Modes
   ===================

Since version 25.8.28, PicoLisp supports two new build modes. Although
these modes are only loosely related, they may well be used in
combination.

One mode enables the entire interpreter to be built as a shared library
"picolisp.so", making it possible to link and use PicoLisp within other
programs. The other, called "Quiche Mode", adds run-time protections to
improve interpreter stability.


   Shared Library
   --------------

To build PicoLisp as a shared library instead of as a standalone
executable, pass 'so' as target to make:

   $ (cd src; make so)

This should be done after building the standard executable because the
executable is needed to compile the "base.so.ll" file. The process
creates the library in "lib/picolisp.so".

The trick is that in Makefile the file "src/lib/ex.l" is loaded to build
the executable, while for the shared library "src/lib/so.l" is used. The
latter excludes the main() function from the final product, to avoid a
conflict with the program that links the library.


When a C program (or anything else with FFI capabilities) wants to use
the PicoLisp library, it should

1. Declare the following three external functions:

      void picolisp(char*, int, int, char**);
      char *evaluate(char*);
      void stoplisp(void);

2. Create an array of strings for the invocation, analog to the typical
   main() arguments, e.g.:

      static char *init[] = {"picolisp", "lib.l", "+"};

   (This minimal setup only loads "lib.l" and enables debug mode)

3. Allocate a sufficiently large local (!) memory area for the PicoLisp
   runtime stack:

      char stack[1000000];

4. Call the picolisp() function to start the interpreter:

      picolisp(stack, sizeof(stack), (int)(sizeof(init)/sizeof(char*)), init);

   The arguments are

      -- A pointer to the stack
      -- The size of the stack
      -- The argument count
      -- The argument vector

5. Send an executable expression as text to the interpreter:

      char *result = evaluate(expression);

6. Repeat (5) as often as necessary. PicoLisp keeps its state between
   calls, similar to a coroutine (in fact you may even call real
   coroutines from inside 'expression').

7. Call

      stoplisp();

   when done, to stop PicoLisp and clean up everything.


   Quiche Mode
   -----------

All PicoLisp functions check their arguments for correct types, but do
so - for performance reasons - only up to a certain depth in nested
structures. This is no problem in practical use, but still may cause
crashes in pathological cases.

For example, passing a number to 'cdr' (correctly) triggers an error:

   : (cdr 7)
   !? (cdr 7)
   7 -- List expected

But evaluating a malformed expression fails terribly:

   : (cdr . 7)
   Segmentation fault

The 'nth' function, for example, accepts an arbitrarily long list:

   : (nth '(a b c d . 7) 3)
   -> (c d . 7)

The traversal of that list should be fast. So PicoLisp does not always
check on the lowest levels, trusting the programmer to supply a proper
list. This, however, may go wrong:

   : (nth '(a b c d . 7) 9)
   Segmentation fault

Another possible cause for crashes is the fact that builtin functions
are called directly (again for performance reasons) via function
pointers.

If you look at the value of 'cdr'

   : cdr
   -> 5875599656908

you see that it is a number, which is simply the address of the internal
machine code of 'cdr'.

Unfortunately, this can easily be abused

   : (setq foo 12345)
   -> 12345

   : (foo)
   Segmentation fault

or result from the miscalculation of a function

   : ((* 20 30) 40)
   Segmentation fault

typically because a quote symbol before the list had been forgotten.


All the above situations are caught in Quiche Mode. They are still bugs,
of course, and the program will not run correctly. But at least they
will not crash.

   : (cdr . 7)
   -> NIL

   : (nth '(a b c d . 7) 9)
   -> NIL

Quiche Mode does not allow user-controlled function pointers. It uses
small numberic indexes into a table of allowed functions.

   : cdr
   -> 243

   : (setq foo 12345)
   -> 12345

   : (foo)
   -> NIL

   : ((* 20 30) 40)
   -> NIL


To enable Quiche Mode, PicoLisp needs to be rebuilt. The drawback is
that it will be 10-30% slower, and about 13% larger.

Quiche Mode is switched on by calling (quiche) right after loading
"lib/llvm.l" in Makefile, either on the command line

   $(PIL) lib/llvm.l -quiche lib/ex.l main.l -bye > base.ll

or by inserting a (quiche) call into "lib/ex.l".
