Skip to content

Commit

Permalink
Init repo
Browse files Browse the repository at this point in the history
  • Loading branch information
jochasinga committed Oct 3, 2023
1 parent 85a9a5b commit e867fbb
Show file tree
Hide file tree
Showing 7 changed files with 259 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bin
modules/compiled.wat
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[submodules "rts"]
path = "rts"
url = "[email protected]:jochasinga/chum-runtime.git"

65 changes: 65 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# chum 🚧

A friendly WASM compiler for scheme (experimental).

## run

```shell
$ guile
> GNU Guile 3.0.9
> Copyright (C) 1995-2023 Free Software Foundation, Inc.
>
> Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'.
> This program is free software, and you are welcome to redistribute it
> under certain conditions; type `,show c' for details.
>
> Enter `,help' for help.
> scheme@(guile-user)> (load "compiler.scm")
> scheme@(guile-user)> (compile-and-run '(primcall integer? 3))
> #t
```

Instead of compiling to x86 Assembly, chum compiles to hand-rolled a WAT program.

Running `compile-to-wasm` procedure will compile an expression to a corresponding WAT in `modules/compiled.wat`.

```shell
> scheme@(guile-user)> (load "compiler.scm")
> scheme@(guile-user)> (compile-to-wasm #\a)
```
To invoke the runtime, run `compile-and-run`:
```shell
> scheme@(guile-user)> (load "compiler.scm")
> scheme@(guile-user)> (compile-and-run #\a)
> char: a
```
## components
### [compiler](compiler.scm)
Does most of the work. Use tagged pointers to determine types such as fix-num, boolean, and character. Emit the source code in **Webassembly Text Format (WAT)** in [`modules/compiled.wat`](modules/compiled.wat).
### [runtime](./rts)
Rust runtime that links a bunch of WAT modules together, convert the tagged binaries into the corresponding types, and simply print them out.
### [lib](./modules/lib)
Helper functions written in WAT.
## movitation
I started coding from literature, thus writing computer code has been nothing short of writing a novel for computers. Writing a compiler for a programming language is one of my programming epitaphs.
Lisp (and in effect, Scheme) is, in my opinion, the most expressive computer language humanity has ever created.
Moreover, the [homoiconicity](https://en.wikipedia.org/wiki/Homoiconicity#:~:text=A%20language%20is%20homoiconic%20if,language%20treats%20code%20as%20data.)<sup>1</sup> of Lisp makes it possible to skip the parsing process of a compiler.
This project is also overwhelming inspired by [An Incremental Approach to Compiler Construction](http://scheme2006.cs.uchicago.edu/11-ghuloum.pdf)<sup>2</sup> and [Let's Build a Compiler Series](https://generalproblem.net/lets_build_a_compiler/01-starting-out/)<sup>3</sup>.
> 1) I wish we could stop linking Wikipedia. It's at the point where it's just downright scaring learners away.
> 2) An Incremental Approach to Compiler Construction, Abdulazia Ghuloum
> 3) Let's Build a Compiler, Noah Zentzis
119 changes: 119 additions & 0 deletions compiler.scm
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
(define emit (lambda args (apply simple-format #t args)
(newline)))

(define fixnum-shift 2)
(define fixnum-mask 3)

(define ptr-mask 7) ; mask for pointer type tag
(define ptr-mask-inv #xfffffff8) ; mask for pointer value

(define pair-tag 1)
(define vec-tag 2)
(define str-tag 3)
(define sym-tag 5)
(define closure-tag 6)

(define char-mask 255) ; character type mask
(define char-shift 8)
(define char-tag 7)

(define bool-mask 255)
(define bool-shift 8)
(define bool-tag 15)

;; Convenient function
(define (b<< x n) (ash x n))
(define (bor a b) (logior a b))
(define (band a b) (logand a b))

(define (immediate? x) (or (integer? x) (char? x) (boolean? x) (null? x)))

(define (immediate-rep x)
(cond ((integer? x) (logand (ash x fixnum-shift) #xffffffff))
((char? x) (logior (ash (char->integer x) char-shift) char-tag))
((boolean? x)
(if x
(logior (ash 1 bool-shift) bool-tag)
bool-tag))))

(define (compile-expr e)
(cond
((immediate? e) (emit " i32.const ~a" (immediate-rep e)))
((primitive-call? e) (compile-primitive-call e))))

(define (emit-is-equal-to val)
(emit " i32.const ~a" val)
(emit " i32.eq")
(emit " i32.const 0")
(emit " call $sete")
(emit " i32.const ~a" bool-shift)
(emit " i32.shl")
(emit " i32.const ~a" bool-tag)
(emit " i32.or"))

(define (compile-primitive-call form)
(case (primitive-op form)
((add1)
(compile-expr (primitive-op-arg1 form))
(emit " i32.const ~a" (immediate-rep 1))
(emit " i32.add"))
((sub1)
(compile-expr (primitive-op-arg1 form))
(emit " i32.const ~a" (immediate-rep 1))
(emit " i32.sub"))
; integer? - check whether the first arg is an integer
((integer?)
(compile-expr (primitive-op-arg1 form))
(emit " i32.const ~a" fixnum-mask)
(emit " i32.and")
(emit-is-equal-to 0))
; boolean? - check whether the first arg is a boolean
((boolean?)
(compile-expr (primitive-op-arg1 form))
(emit " i32.const ~a" bool-mask)
(emit " i32.and")
(emit-is-equal-to bool-tag))
; char? - check whether the first arg is a character
((char?)
(compile-expr (primitive-op-arg1 form))
(emit " i32.const ~a" char-mask)
(emit " i32.and")
(emit-is-equal-to char-tag))
; zero? - check whether the first arg is zero
((zero?)
(compile-expr (primitive-op-arg1 form))
(emit-is-equal-to 0))))

(define (compile-program program)
(emit "(module")
(emit " (import \"asm_x86\" \"sete\" (func $sete (param i32 i32) (result i32)))")
(emit " (func (export \"scheme_entry\") (result i32)")
(compile-expr program)
(emit " )")
(emit ")"))

; Check whether the passed form is a primitive call (primcall) form
(define (primitive-call? form) (eq? 'primcall (car form)))

; Get the primitive operation from a passed primcall form
(define (primitive-op form) (cadr form))

; Get the Nth argument of a passed primcall form
(define (primitive-op-arg1 form) (caddr form))
(define (primitive-op-arg2 form) (cadddr form))

; Get all arguments of a passed primcall form
(define (primitive-op-args form) (cddr form))

(define (compile-to-wasm program)
(begin
(with-output-to-file "modules/compiled.wat"
(lambda () (compile-program program)))
; (system "cargo run --release --bin wasm-compile rts/modules/compiled.wat modules/out.wat")
(system "/Users/pancy/Code/practical-webassembly/wabt/bin/wat2wasm modules/compiled.wat -o bin/compiled.wasm")))

(define (compile-and-run program)
(begin (compile-to-wasm program)
(system "cd rts; cargo run")))


31 changes: 31 additions & 0 deletions modules/lib/asm_x86.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
(module
(type $t0 (func (param i32 i32) (result i32)))
(type $t1 (func (param i32 i32) (result i32)))
(func $sete (export "sete") (param $zf i32) (param $val i32) (result i32)
;; - arg0 | $zf is the zero flag
;; - arg1 | $val is the value to set

;; function to set the last byte of a 32 bit integer
;; if the first parameter (zero flag) is non-zero
;; and otherwise just return the second parameter.

;; get zero flag
local.get $zf
(if
(then
local.get $val
i32.const 0xff ;; 255 for mask
i32.or
return
)
)
local.get $val
)
(func $sall (export "sall") (param $val i32) (param $by i32) (result i32)
;; - arg0 | $val is the value to shift
;; - arg1 | $by is the shift-by number
local.get $val
local.get $by
i32.shl
)
)
37 changes: 37 additions & 0 deletions modules/lib/tests.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
(module
(import "asm_x86" "sete" (func $sete (param i32 i32) (result i32)))
(func $test_sete_eq (export "test_sete_eq") (result i32)
i32.const 3
i32.const 3
;; 3 == 3 so sete should return 0xff or 255
i32.eq
i32.const 99
call $sete
)
(func $test_sete_ne (export "test_sete_ne") (result i32)
i32.const 3
i32.const 4
;; 3 != 4 so sete should return 99
i32.eq
i32.const 99
call $sete
)
(func $test_sall (export "test_sall") (result i32)
i32.const 4
i32.const 8
;; 4 << 8 should be 1024
i32.shl
)
(func $test_emit_is_equal_to (export "test_emit_is_equal_to") (result i32)
;; 3 == 3 so this should return 1
i32.const 3
i32.const 3
i32.eq
i32.const 0
call $sete
i32.const 8 ;; shift by one byte
i32.shl
i32.const 15 ;; bool-tag is 15
i32.or
)
)
1 change: 1 addition & 0 deletions rts
Submodule rts added at 4889b6

0 comments on commit e867fbb

Please sign in to comment.