The elegance of Lisp with the cold-blooded efficiency of Assembly. Tl;dr: lllm is a DSL for writing Assembly via the Lisp family of programming languages. This is the Janet -first and currently only- implementation.

debris b05333da47 Version change 1 year ago
examples c58dfce914 Added llllnx examples 1 year ago
graphics 2bd87f4ab7 uploaded logo and new graphics folder 1 year ago
src b05333da47 Version change 1 year ago
test b468d3f8cf Added JPM testing file. 1 year ago
LICENSE e8c31b039d Initial commit 1 year ago
README.md 0919ca09b7 Added setup.sh "install" "method" 1 year ago
project.janet 086b28918b Update 'project.janet' 1 year ago
setup.sh 84bad9ffa0 Uploaded basic setup.sh (mainly for reinstalling llllms after hacking on them) 1 year ago

README.md

lllm-janet

Lisp's elegance and Assembly's cold-blooded efficiency.

the-lllm-logo

"Lisp is a slow language." --Famous last words

This is the Janet-flavoured implementation of lllm.

It currently focuses on the x64_86 but it would be trivial-esque to add support for other architectures.


Introduction

Tl;dr: lllm is a family of DSLs for writing Assembly via the Lisp family of programming languages.

This is the Janet -first and currently only- lllm implementation*. Janet is a quite portable and embeddable pseudo-Lisp that is mostly written in standard C99; think of it as Clojure but nicer if you don't like the Java ecosystem, or, better, as Lisp with much easier UNIX-/C-y data manipulation.

lllm in itself is not a "compiler" nor is "compiled" in the standard sense.

It could be said that current compilers are built "top-down", ie. abstractions are first defined, such as arrays, and then bruteforced into fitting the architecture's machine code (or the architecture is built around the compiler, which could promote some amount of language lock-in in the worst cases)

By analogy, lllm would build itself "bottom-up": you begin by defining extremely simple abstractions (such as loops) that directly translate to a list of assembly instructions; then, you begin abstracting those abstractions, raising the level of abstraction higher (print statements, BIOS mode handling, a generic syscall function ...) as necessary, while keeping your code highly flexible and hackable.

Keep in mind lllm (and lllm-janet) is still in its infancy; while it seems to work as intended and is a very simple concept in and of itself, it and its libraries are still heavily subject to change.

With that said, you can use it to generate perfectly valid assembly code and even bootloaders.

(*: There is a systems programming lisp called Ink, which uses extremely similar concepts to lllm to write Assembly, that was independently developped for a Lisp operating system called Yalo; I did not know of it when I came up with lllm, but I find it amazing someone else thought of combining Assembly and Lisp too.)

Dependencies

  • Janet, as the client- and server- lisp.
  • NASM, as the default backend compiler
  • JPM if you're planning to install lllm, though you could manually move the project's files around your filesystem.

Installation

Building from source

Manually

The REAL (unless declared INTEGER) and official way.

git clone "https://notabug.org/debris/lllm-janet"
cd lllm-janet 
jpm build
jpm test
doas jpm install
setup.sh

Alternatively, the setup.sh script should probably do most of the job itself:

git clone "https://notabug.org/debris/lllm-janet"
cd lllm-janet && sh setup.sh

No warranty, though!

Via JPM

Alternatively:

doas jpm install "https://notabug.org/debris/lllm-janet"

Usage

The name of the executable is lllma , which stands for LLLM Assembler. Name may change in the future (lljm ?)

Symbols in janet

There are plenty of symbols provided as llllms (Libraries for LLLM). For development under x64_86 Linux, use lllm/llllnx. For barebones x64_86 development, use lllm/lllb.

Documentation

This section is not complete.

Use (doc symbol) in the Janet REPL after importing a llllm ((import lllm/llllnx) for example) for the meantime.

An lllm-operation (in lllm-janet) is data of this kind:

['operationA 'argA1 ... 'argn]

such that operationA is a symbol that is equivalent to an assembly operation, argA1 is a argument to that operation, etc...

It will be converted to assembly as the line operationA argA1, ..., argn.

lllm will transemble data of this kind:

@[
lllm-operation
...
lllm-operation
]

(ie. an array of lllm-operations of any length.)

As such, any function or macro that generates a valid lllm-operation can be used to generate and abstract assembly away. This is where the power of lllm resides.

Inserting an indexable data structure like ["string"] into an indexable to transemble will escape "string" into assembly code. This is your escape hatch. It's useful for defining odd macros like "$-$$" (though you could use a prefix-infix converter to write (- '$ '$$) or something).

Also, you'll probably need some x64_86 opcodes cheat sheet like this one , and maybe another for Linux syscalls.

And maybe a guide on x64_86 too.

For OS development and lllm/lllb, or just a really good introduction to assembly, check out this great tutorial by Daedalus Community.

Contributing

I have no idea how repository management works. If you want to contribute, please contact me through the community space or directly on Matrix.

Community and support et cetera

We have a space on Matrix here, feel free to join.