{"id":25195868,"url":"https://github.com/s-expressionists/maclina","last_synced_at":"2026-02-17T15:03:01.380Z","repository":{"id":199064373,"uuid":"701599912","full_name":"s-expressionists/Maclina","owner":"s-expressionists","description":"Common Lisp bytecode compiler","archived":false,"fork":false,"pushed_at":"2025-11-11T19:20:36.000Z","size":773,"stargazers_count":14,"open_issues_count":1,"forks_count":1,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-11-11T21:15:21.241Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://s-expressionists.github.io/Maclina/","language":"Common Lisp","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/s-expressionists.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-10-07T02:56:59.000Z","updated_at":"2025-06-22T22:07:53.000Z","dependencies_parsed_at":"2024-07-19T18:37:28.276Z","dependency_job_id":"bb550e8c-2cfe-4a75-9b77-412aac5b89a7","html_url":"https://github.com/s-expressionists/Maclina","commit_stats":null,"previous_names":["s-expressionists/maclina"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/s-expressionists/Maclina","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/s-expressionists%2FMaclina","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/s-expressionists%2FMaclina/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/s-expressionists%2FMaclina/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/s-expressionists%2FMaclina/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/s-expressionists","download_url":"https://codeload.github.com/s-expressionists/Maclina/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/s-expressionists%2FMaclina/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29548201,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-17T14:33:00.708Z","status":"ssl_error","status_checked_at":"2026-02-17T14:32:58.657Z","response_time":100,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-02-10T01:38:54.257Z","updated_at":"2026-02-17T15:03:01.364Z","avatar_url":"https://github.com/s-expressionists.png","language":"Common Lisp","funding_links":[],"categories":[],"sub_categories":[],"readme":"Maclina is an implementation of a Common Lisp evaluator, compiler, file compiler, and FASL loader, all written in portable Common Lisp. It compiles Lisp into a bytecode representation, which is interpreted by a simple virtual machine. The compiler is complete but simple, performing only easy optimizations; this makes it fast, and suited for code that does not necessarily need to run quickly, such as that evaluated just once.\n\nCompilation and bytecode interpretation take place relative to specified environments. [Clostrum](https://github.com/s-expressionists/Clostrum) can be used to build a first-class environment to execute code in, meaning Maclina can be used to execute code in an isolated environment, like a sandbox.\n\nBytecode functions exist as real functions, so they can be called the same as any other functions. Bytecode functions and native functions can coexist without difficulty and call each other.\n\nThe FASL format is simple and portable. Lisp source files can be compiled in one implementation and then loaded it into another, as long as the compilation and loading environments agree.\n\n# Quick start\n\nLoad the `maclina` ASDF system. There is a dependency on Clostrum, which is not available on Quicklisp, so you'll need to set that up yourself.\n\nBefore compiling or evaluating code, you need to set the client in order to inform Trucler how to get global definitions. On SBCL you can use the host environment as follows:\n\n```lisp\n(setf maclina.machine:*client* (make-instance 'trucler-native-sbcl:client))\n```\n\nThe procedure on CCL is analogous. Or, you can use some other trucler client and environment, such as Trucler's reference implementation.\n\nNow you can compile code with `maclina.compile:compile` and disassemble it with `maclina.machine:disassemble`:\n\n```lisp\n(defvar *f* (maclina.compile:compile '(lambda (x) (let ((y 5)) (print y) #'(lambda () (+ y x))))))\n(maclina.machine:disassemble *f*) ; =\u003e\n---module---\n  check-arg-count-= 1\n  bind-required-args 1\n  const '5\n  set 1\n  fdefinition 'PRINT\n  ref 1\n  call 1\n  ref 1\n  ref 0\n  make-closure '#\u003cMACLINA.MACHINE:BYTECODE-FUNCTION {100C2D803B}\u003e\n  pop\n  return\n; No value\n```\n\nTo actually run code, first set up a stack for the VM with `(maclina.vm-native:initialize-vm N)`, where N is how many objects the stack will be able to hold, say 20000. Then you can simply call the functions returned by `compile`:\n\n```lisp\n(funcall *f* 5) ; =\u003e\n5\n#\u003cMACLINA.MACHINE:BYTECODE-CLOSURE\u003e\n```\n\nYou can get a running trace of the machine state by binding `maclina.vm-native:*trace*` to true around a call:\n\n```lisp\n(let ((maclina.vm-native:*trace* t)) (funcall *f* 3)) ; =\u003e\n\n  check-arg-count-= 1 ; bp 1 sp 3 locals #(0 0) stack #()\n  bind-required-args 1 ; bp 1 sp 3 locals #(0 0) stack #()\n  const '5 ; bp 1 sp 3 locals #(3 0) stack #()\n  set 1 ; bp 1 sp 4 locals #(3 0) stack #(5)\n  fdefinition 'PRINT ; bp 1 sp 3 locals #(3 5) stack #()\n  ref 1 ; bp 1 sp 4 locals #(3 5) stack #(#\u003cFUNCTION PRINT\u003e)\n  call 1 ; bp 1 sp 5 locals #(3 5) stack #(#\u003cFUNCTION PRINT\u003e 5)\n\n5\n  ref 1 ; bp 1 sp 3 locals #(3 5) stack #()\n  ref 0 ; bp 1 sp 4 locals #(3 5) stack #(5)\n  make-closure '#\u003cMACLINA.MACHINE:BYTECODE-FUNCTION NIL\u003e ; bp 1 sp 5 locals #(3 5) stack #(5 3)\n  pop ; bp 1 sp 4 locals #(3 5) stack #(#\u003cMACLINA.MACHINE:BYTECODE-CLOSURE NIL\u003e)\n  return ; bp 1 sp 3 locals #(3 5) stack #()\n\n#\u003cMACLINA.MACHINE:BYTECODE-CLOSURE {100C2D80CB}\u003e\n```\n\n# First-class environments\n\nThe `maclina/vm-cross` subsystem allows Maclina to be used for compiling and running Lisp code in arbitrary first-class environments, in concert with Clostrum. Here is an example:\n\n```lisp\n;;; maclina-cross does not itself load a global environment implementation,\n;;; since it can be used with any. Here we use clostrum-basic for that.\n;;; We also need clostrum-trucler to be able to compile relative to\n;;; a Clostrum environment.\n(ql:quickload '(:clostrum-basic :clostrum-trucler))\n\n;;; Set up the client to use maclina-cross, and initialize the VM.\n(setf maclina.machine:*client* (make-instance 'maclina.vm-cross:client))\n(maclina.vm-cross:initialize-vm 20000)\n\n;;; Construct our environments.\n(defvar *rte* (make-instance 'clostrum-basic:run-time-environment))\n(defvar *env* (make-instance 'clostrum-basic:compilation-environment\n                 :parent *rte*))\n\n;;; These new environments are totally devoid of bindings.\n;;; To do anything useful, we have to define at least a few.\n;;; We'll define + and *readtable* weirdly to emphasize that we are\n;;; not operating in the host environment.\n(setf (clostrum:fdefinition maclina.machine:*client* *rte* '+) #'-)\n(clostrum:make-variable maclina.machine:*client* *rte* '*readtable* 17)\n\n;;; Now behold:\n(maclina.compile:eval '(+ *readtable* 3) *env*) ; =\u003e 14\n;;; And of course, the host *READTABLE* and + are unaffected.\n```\n\n## Sandboxing\n\nA more complete CL experience requires a richer environment. The [Extrinsicl](https://github.com/s-expressionists/Extrinsicl) project can be used to construct such an environment, but more generally you can just fill a Clostrum environment. Extrinsicl can additionally be configured to provide functions like `eval` through Maclina.\n\nFor real sandboxing of untrusted code, you will need a \"safe\" environment lacking any undesirable operators. What is undesirable depends on your application, but might include, for instance, file I/O. If your environment is well constructed, you don't need to worry about functions that carry out evaluation or introspection in themselves, because they will only operate with respect to your safe environment.\n\nAnother danger of untrusted code is it not halting, which can be a denial of service attack. The cross VM has a `with-timeout` macro that can be used to abort evaluation after executing some number of VM instructions. This covers all evaluations within the cross VM, including indirectly as from calls to VM functions. Note that computations outside of the VM are not tracked, so for example there will be no abort if the untrusted code calls a non-VM function that does not halt.\n\nHere is an example of a basic sandbox:\n\n```lisp\n(ql:quickload '(:clostrum-basic :extrinsicl :extrinsicl/maclina :maclina))\n\n;;; Set up Maclina.\n(setf maclina.machine:*client* (make-instance 'maclina.vm-cross:client))\n(maclina.vm-cross:initialize-vm 20000)\n\n;;; Create the (empty) environment.\n(defvar *rte* (make-instance 'clostrum-basic:run-time-environment))\n(defvar *env* (make-instance 'clostrum-basic:compilation-environment\n                :parent *rte*))\n\n;;; Install most of CL.\n(extrinsicl:install-cl maclina.machine:*client* *rte*)\n(extrinsicl.maclina:install-eval maclina.machine:*client* *rte*)\n\n;;; Uninstall filesystem access.\n(loop for f in '(open directory probe-file ensure-directories-exist truename\n                 file-author file-write-date rename-file delete-file)\n      do (clostrum:fmakunbound maclina.machine:*client* *rte* f))\n\n;;; Also add a trap.\n(setf (clostrum:fdefinition maclina.machine:*client* *rte* 'o)\n      (lambda (\u0026rest args) (apply #'open args)))\n\n;;; Try it out.\n(maclina.compile:eval '(+ 2 7) *env*) ;=\u003e 9\n(defparameter *fib*\n  (maclina.compile:compile\n   '(lambda (n)\n      (loop for a = 0 for b = 1\n            repeat n\n            do (psetf a b b (+ a b))\n            finally (return a)))\n   *rte*))\n(funcall *fib* 37) ;=\u003e big number\n\n;;; But we can't access the filesystem.\n(maclina.compile:eval '(open \"/tmp/hello.txt\") *env*)\n;=\u003e error: UNDEFINED-FUNCTION OPEN\n\n;;; Tricky stuff is available but doesn't help escape.\n(maclina.compile:eval '(eval 'pi) *env*) ;=\u003e pi\n(maclina.compile:eval `(funcall ,*fib* 37) *env*) ;=\u003e big number\n(maclina.compile:eval '(find-symbol \"OPEN\") *env*) ;=\u003e OPEN\n(maclina.compile:eval '(eval (list (find-symbol \"OPEN\") \"/tmp/hello.txt\")) *env*)\n;=\u003e error: UNDEFINED-FUNCTION OPEN\n\n;;; Whoops, we forgot WITH-OPEN-FILE. But that's okay.\n(maclina.compile:eval '(with-open-file (s \"/tmp/hello.txt\")) *env*)\n;=\u003e error: UNDEFINED-FUNCTION OPEN\n\n;;; But the VM can't intercept a function call within a host function.\n(maclina.compile:eval '(o \"/tmp/hello.txt\") *env*) ;=\u003e actually opens\n\n;;; DoS denied.\n(maclina.vm-cross:with-timeout (1000000)\n  (maclina.compile:eval '(loop) *env*))\n;=\u003e error: TIMEOUT\n\n;;; Watch out for more exotic DoS outside of the VM, though.\n(maclina.vm-cross:with-timeout (100000)\n  (maclina.compile:compile '(lambda () (progn . #1=(nil . #1#))) *env*))\n; =\u003e compiler hangs\n(maclina.vm-cross:with-timeout (100000)\n  (maclina.compile:eval '(typep 17 '#1=(not #1#)) *env*))\n; =\u003e hang or stack overflow\n```\n\n# Subsystems\n\nMaclina defines a variety of subsystems that can be loaded independently. It's set up this way so that you can, for example, load one of the VM definitions and run bytecode compiled elsewhere, without needing to load any of the compiler's multitudinous dependencies.\n\n* `maclina/base` is the base system. Everything depends on `maclina/base`. `maclina/base` defines various shared conditions, the MOP magic that lets bytecode functions be run in a host Lisp,the names of instructions, and the disassembler.\n* `maclina/compile` turns Lisp forms into bytecode. You need it in order to compile or evaluate forms. But this alone won't let you run bytecode; you'll need one of the VM systems for that. And Lisp compilation frequently involves evaluation, so you'll probably need to load a VM before you can compile much of anything.\n* `maclina/compile-file` implements the file compiler. It depends on the compiler in `maclina/compile` to do that.\n* `maclina/vm-shared` is an internal system containing some code shared by the VM implementations.\n* `maclina/vm-native` is the \"native\" implementation of the VM, which is to say that it operates entirely in the host Lisp's normal global environment. This is simple but a bit inflexible.\n* `maclina/vm-cross` is an implementation of the VM that operates relative to a Clostrum environment. This is what you want to do anything first-class-environment-related.\n* `maclina/load` loads FASL files created by `maclina/compile-file`. `maclina/load` and one of the VMs is sufficient to load and run FASLs.\n* For general use and convenience, the `maclina` oversystem just loads everything.\n\nAssuming the compilation and loader environments match (e.g. any function appearing in a macroexpansion is actually available in the load-time environment), there is no problem with compiling code with one VM and loading it with another. Using multiple VMs in the same image also works.\n\n# Implementation status\n\nWorks. Except:\n\n* A bespoke environment structure is used rather than host environments. As such, imported host macros that actually access their environment (e.g. `setf`) will not work.\n\n## More TODO\n\n* VM optimizations\n  * Profile\n  * Values\n  * Arguments\n  * Maybe elide some array bounds checks when safe, but keep a guard on overflowing the stack\n* Compiler optimizations\n  * Entirely elide unused blocks (i.e. don't even do `save-sp`)\n  * Use `restore-sp` + `jump` for local exits to blocks even if they are also nonlocally exited to\n  * Optimize some degenerate forms, like `(block nil)` =\u003e `nil`\n  * Inline lambda forms\n  * Inline functions more generally\n  * Contify?\n  * Use multiple-value contexts for `multiple-value-call` with a lambda\n* Instructions for inline operations like `car`, possibly\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fs-expressionists%2Fmaclina","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fs-expressionists%2Fmaclina","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fs-expressionists%2Fmaclina/lists"}