# IPUlisp

This adaptation of IPUpy runs [Rui Ueyama’s minilisp](https://github.com/rui314/minilisp)
entirely on IPU tiles. Each selected tile has an independent, persistent Lisp
interpreter. The host transfers source and collects output; parsing, evaluation,
closures, macros and copying garbage collection execute on the IPU.

## Build and run

Enable your Poplar SDK first (tested with SDK 3.4.0 and target `ipu21`):

```sh
source /path/to/poplar-sdk/enable
make
./examples/ipulisp --eval '(+ 20 22)'
./examples/ipulisp --tiles all --eval '(list __tileid (+ __tileid 1))'
./examples/ipulisp --file examples/demo.lisp
./examples/ipulisp --tiles 4
```

The default is four tiles on one hardware IPU. `--tiles all` uses every tile
reported by the attached device. There is no implicit simulator fallback.
Equal outputs are grouped as `[Nx]`. `__tileid` is the logical tile index used
by the graph. Interactive input accepts complete forms per line and preserves
bindings between lines; `:quit` or EOF exits. Files can contain multiline forms.
The executable locates its codelets using the absolute path recorded at build
time; rebuild if moving the checkout.

```lisp
(define counter ((lambda (n) (lambda () (setq n (+ n 1)))) 0))
(counter)
(counter)
(defmacro unless (condition expr) (list 'if condition () expr))
(unless () (list 'hello __tileid))
```

`list` is registered as a primitive in this port (upstream’s examples implement
it with a variadic function). Minilisp supports integers, symbols, lists,
lexical closures and macros; it does not support Python or strings.

## Tests

```sh
make test                 # upstream language suite, normal and forced GC, on CPU
make test-hardware        # persistent state, closures, macros, recursion, GC,
                          # tile identity, errors and output overflow on four tiles
./examples/ipulisp --tiles all --self-test
```

`--stress-gc` forces a collection on every allocation. The hardware self-test
runs both modes and checks the output/status of every selected tile, including
that collections actually occurred. The suite passed on all 1,472 tiles of the
attached IPU; see [recorded hardware results](tests/HARDWARE.md).

## Port details and limits

* `libIPUlisp/vendor/minilisp.c` is a pinned public-domain upstream source with
  small embedding and validation changes; see `libIPUlisp/vendor/README.md`.
* Each tile uses two fixed 64 KiB semispaces in place of Unix `mmap`/`munmap`.
  Roots and the global environment survive separate compute calls. Only one
  worker may invoke the interpreter on a tile at a time.
* Input and output are each limited to 16 KiB per invocation. Output remains
  NUL-terminated on overflow; status 2 reports truncation. Interpreter errors
  return status 1 using the original IPUpy IPU setjmp/longjmp implementation.
  The REPL can continue after an error; earlier side effects are retained.
* The recursive C interpreter has a 32 KiB stack reservation, without a dynamic
  stack-depth check. Deep recursion, cyclic printed lists and nonterminating
  programs are not bounded or sandboxed. Use trusted, terminating programs.
* The Python-specific notebook integration, data sharding and `alltoall`
  coroutine protocol have not been ported to Lisp. Original Python code and
  targets are retained for reference; their documentation is in
  [README-IPUpy.md](README-IPUpy.md).

This remains an experimental project, not a Graphcore-supported product.
