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.

arson 6dac554ff3 Update 'README.md' 1 year ago
examples c58dfce914 Added llllnx examples 2 years ago
graphics 2bd87f4ab7 uploaded logo and new graphics folder 2 years ago
src b05333da47 Version change 2 years ago
test b468d3f8cf Added JPM testing file. 2 years ago
LICENSE e8c31b039d Initial commit 2 years ago
README.md 6dac554ff3 Update 'README.md' 1 year ago
project.janet 086b28918b Update 'project.janet' 2 years ago
setup.sh 84bad9ffa0 Uploaded basic setup.sh (mainly for reinstalling llllms after hacking on them) 2 years 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 x86_64 but it would be trivial 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 first -and currently only- Janet lllm implementation*. Janet is a quite portable and embeddable pseudo-Lisp that is mostly written in standard C99; think of it as Clojure if you don't like the Java ecosystem, or, better, as Lisp with much easier UNIX-/C-y data manipulation.

lllm is not a "compiler" nor is it "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. it 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(sudo) 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(sudo) 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 x86_64 Linux, use lllm/llllnx. For barebones x86_64 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 x86_64 opcodes cheat sheet like this one , and maybe another for Linux syscalls.

And maybe a guide on x86_64 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.