Alucard
The easiest way to install Alucard at the moment is through ros.
ros install sbcl-bin
ros install asdf
git clone https://github.com/anoma/juvix-circuits.git
cd juvix-circuits
ros install "nobody-famous/alive-lsp"
make install
make all
will install it in ./build/
as well.The last command will add it to ~/.local/bin/
, so make sure that is on your path!
Now we can run alucard!
There are basically 2 ways to run alucard programs.
The interactive mode is via a REPL in Emacs/VSCode. To run the interactive mode,
$ cd ~/.local/bin
$ ./alu.image -y
;; Slynk started at port: 4005.
?
If you are using a SLIME (VS-Code uses SLIME), replace the -y
with -s
.
Then use your client (e.g. Slime or Sly) to connect to localhost with port 4005 (or whatever port is shown in the terminal). You can set the port with the -p
option.
If you are using vscode it might spit you out in the cl-user package. to check your prompt should say ALUSER>
in vs-code or emacs. If that is the case then run the following
(in-package :aluser)
one can quit out of the repl by typing (quit)
.
Batch mode lets you provide input from files to the alu.image
executable with the -i
flag and specify the output file with the -o
flag.
$ ./alu.image -i "my_program.lisp" -o "output/my_program_output.txt"
If the -o
flag is not given, you will go into interactive mode.
To specify the function that will act as the entry point to one's circuit, you can write
(entry-point function-name)
in the file, otherwise no code will be generated.
If you are running sly, M-x sly-connect
to localhost with the port number shown in your terminal earlier.
If one is using ccl
instead of SBCL
you might want to run:
(setf *print-pretty* t)
(in-package :aluser)
You can also (ql:quickload :alu)
if one has the asd
file on the path or have loaded into one's environemnt, and have the same functionality!
Please check the readme
You know you are ready to get going when typing *package*
in your REPL returns
#<PACKAGE "ALUSER">
or your REPL prompt says ALUSER>
.
Let's define a simple circuit, which represents the equation, \(x+3 = 0\).
(defcircuit add-three ((public x int) (output bool))
(= (+ x 3) 0))
In the above code, the keyword defcircuit
is used to denote that we are defining a polynomial circuit, named add-three
. The circuit takes as input the integer x
, which is a public
input, and the output is true
if the equation is satisfied, and false if not.
To get the VAMP-IR code corresponding to any circuit defined below, call the function
vampir
on it.
For example, the VAMP-IR code for add-three
is shown below.
ALUSER> (vampir add-three)
def add_three x1333 -> g1335 {
g1334 = x1333 + 3
g1334 = 0
g1335 = g1334
}
Note that since the compiler pipeline is currently incomplete, it is not yet possible to run VAMP-IR and generate output.
Currently it's good to run vampir
on various circuits created, as the type checker currently only runs when trying to extract.
Let's look at some more examples.
The follow circuit represents the equation \(x^3 + 3x^2 + 2x + 3y+ 4 =0\).
(defcircuit multivar ((public x int)
(public y int)
(output int))
(= (+ (exp x 3)
(* 3 (exp x 2))
(* 2 x)
(* 3 y)
4)
0))
We can also define a circuit to calculate the square root of an input in the following manner.
(defcircuit square-root ((private p int)
(output int))
(def ((with-constraint (x)
(= p (* x x))))
x))
The def
plays a role similar to let
in Rust. The with-constraint
macro can be used in any situation where we want a variable to be subject to certain constraints.
Alucard also allows us to define custom datatypes.
Let us define the type point
which defines a point on the Cartesian plane.
(deftype point ()
(x int)
(y int))
Let's write a function to calculate the distance of this point from the origin. We'll make use of the square-root
function we wrote earlier.
(defcircuit distance ((public pt point)
(output int))
(square-root (+ (exp (x pt) 2) (exp (y pt) 2))))
(entry-point distance) ; marking the function as the main!
Let us now mark this function as the entry point to the file
(entry-point distance)
4 taichi@Gensokyo:~/Documents/Work/Repo/alu/alu git:main:*? % alu.image -i example.lisp -o example.vampir
4 taichi@Gensokyo:~/Documents/Work/Repo/alu/alu git:main:*? % cat example.vampir
def distance pt10733_x pt10733_y -> g10770 {
g10766 = pt10733_x ^ 2
g10768 = pt10733_y ^ 2
g10769 = g10766 + g10768
g10770 = square_root g10769
}
def square_root p10729 -> x10772 {
g10784 = x10772 * x10772
p10729 = g10784
g10785 = p10729
}%
We can also invoke the same functionality from the repl as well
ALUSER> (pipeline:dump-entry-point)
def distance pt16131_x pt16131_y -> g16712 {
g16708 = pt16131_x ^ 2
g16710 = pt16131_y ^ 2
g16711 = g16708 + g16710
g16712 = square_root g16711
}
def square_root p15972 -> x16714 {
g16726 = x16714 * x16714
p15972 = g16726
g16727 = p15972
}
#<SLYNK-GRAY::SLY-OUTPUT-STREAM #x302000D6CA7D>
ALUSER> (pipeline:dump-entry-point-to-file "example.vampir")
#<BASIC-FILE-CHARACTER-OUTPUT-STREAM ("example.vampir"/:closed #x30200297CAED>
Some of this formula is repetitive, namely the (exp (lookup pt) 2)
we can use the common lisp integration to remove such repetitive looking code.
Namely we can use flet
, which defines a local function to be our square function.
(defcircuit distance ((public pt point)
(output int))
(flet ((square (cord)
(exp cord 2)))
(square-root (+ (square (x pt))
(square (y pt))))))
If we find this function general enough we can move it to the top level with defun
.
(defun square (cord)
(exp cord 2))
(defcircuit distance ((public pt point)
(output int))
(square-root (+ (square (x pt))
(square (y pt)))))
A lot more can be abstracted, we have the entire toolkit of the common lisp programming language to help abstract away alucard code!
A lot more information on this can be found in the reference manual
Let us say we try to make a type error.
(deftype point ()
(x int)
(y int))
(defun square (cord)
(check (exp cord 2) (int 32)))
(defcircuit distance ((public pt point)
(output int))
(square-root (+ (square (x pt))
(square (y pt)))))
In this code, we are saying the expected output of square should be of (int 32)
, however the type should really just be int
. We can move this check
to any part of the expression in the square
function and the error would be the same.
ALUSER> (vampir distance)
;;;;;;;;;;;;;;;;;;;;;;
In Function DISTANCE
;;;;;;;;;;;;;;;;;;;;;;
2022-08-08 08:35:27
[ERROR] <UNIFIER><TYPE>:
The types #<#<REFERENCE-TYPE INT> 32> and #<REFERENCE-TYPE INT> are not equivalent
[STACK]
((:IN SQUARE
(EXP CORD 2)
(CHECK (EXP CORD 2) (INT 32)))
(SQUARE (X PT))
(+ (SQUARE (X PT)) (SQUARE (Y PT)))
(SQUARE-ROOT (+ (SQUARE (X PT)) (SQUARE (Y PT)))))
Here we get the type error message saying that (int 32)
and int
are not compatable, and further the stack trace to the point.
It is saying that the expression (EXP CORD 2)
caused the error as we stated that the type must be of (int 32)
right before!
The stack trace just give the expression dump until the current point where the error occurs.
If the system errors with a very baroque error message like
4 1 taichi@Gensokyo:~/Documents/Work/Repo/alu/alu git:main:*? % alu.image -i example.lisp -o example.vampir
> Debug: Undefined function ALU.PASS.DEPENDENCIES::TRACK-FUNCTION-DEPS called with arguments ((#<LET G10547 = #<#<REFERENCE *> #1=#<REFERENCE X10535> #1#>>
> #<LET G10548 = #<#<REFERENCE => #<REFERENCE P10517> #<REFERENCE G10547>>>)) .
> While executing: SIGNAL, in process toplevel(4).
> Type :GO to continue, :POP to abort, :R for a list of available restarts.
> If continued: Retry applying ALU.PASS.DEPENDENCIES::TRACK-FUNCTION-DEPS to ((#<LET G10547 = #<#<REFERENCE *> #1=#<REFERENCE X10535> #1#>>
#<LET G10548 = #<#<REFERENCE => #<REFERENCE P10517> #<REFERENCE G10547>>>)).
> Type :? for other options.
1 >
And spits you into a repl, please file an issue or message mariari
directly. These are internal errors and they might show up!