├── .cargo └── config.toml ├── .gitignore ├── .gitmodules ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── examples ├── blink-led.scm ├── calc.scm ├── fizzbuzz.scm ├── mandelbrot.png ├── mandelbrot.scm └── test.lisp ├── library └── prelude.scm ├── rv32-asm ├── Cargo.toml ├── LICENSE ├── README.md └── src │ ├── assembler.rs │ ├── error.rs │ ├── instruction.rs │ └── lib.rs ├── scripts └── compile_and_run.sh ├── src ├── data_structure.rs ├── data_structure │ └── graph.rs ├── lib.rs ├── lispi.rs ├── lispi │ ├── ast.rs │ ├── cli_option.rs │ ├── common.rs │ ├── console.rs │ ├── environment.rs │ ├── error.rs │ ├── evaluator.rs │ ├── include_expander.rs │ ├── ir.rs │ ├── ir │ │ ├── basic_block.rs │ │ ├── compiler.rs │ │ ├── instruction.rs │ │ ├── register_allocation.rs │ │ ├── removing_phi_instructions.rs │ │ └── tag.rs │ ├── macro_expander.rs │ ├── parser.rs │ ├── pass.rs │ ├── pass │ │ ├── constant_folding.rs │ │ ├── context_folding.rs │ │ ├── immediate_unfolding.rs │ │ ├── placing_on_memory.rs │ │ ├── removing_duplicated_assignments.rs │ │ ├── removing_redundant_assignments.rs │ │ ├── removing_ref_and_deref.rs │ │ ├── removing_uncalled_functions.rs │ │ └── tail_recursion.rs │ ├── riscv.rs │ ├── riscv │ │ ├── cmp_translator.rs │ │ ├── code_generator.rs │ │ └── stack_frame.rs │ ├── tokenizer.rs │ ├── typer.rs │ └── unique_generator.rs └── main.rs └── tests ├── common └── mod.rs ├── compiler_test.rs ├── interpret_test.rs └── type_test.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [env] 2 | RUST_TEST_THREADS = "1" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out.* 2 | cfg.gv 3 | ir.txt 4 | 5 | test.* 6 | 7 | compiler_test_files 8 | 9 | /target 10 | .vscode 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "rv32emu"] 2 | path = rv32emu 3 | url = https://github.com/long-long-float/rv32emu.git 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | workspace = { members = ["rv32-asm"] } 2 | 3 | [package] 4 | name = "lisp-rs" 5 | version = "0.1.0" 6 | edition = "2021" 7 | 8 | [features] 9 | default = [] 10 | rv32emu-test = [] 11 | 12 | [lib] 13 | doctest = false 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [dependencies] 18 | rustc-hash = "1.1" 19 | anyhow = "1.0" 20 | thiserror = "1.0" 21 | clap = { version = "4.3", features = ["derive"] } 22 | crossterm = "0.23" 23 | object = { version = "0.30", features = ["write"] } 24 | id-arena = "2.2" 25 | colored = "2" 26 | itertools = "0.13" 27 | uuid = { version = "1.4.1", features = ["v4", "fast-rng", "macro-diagnostics"] } 28 | strum = "0.25" 29 | strum_macros = "0.25" 30 | rv32-asm = { path = "./rv32-asm" } 31 | 32 | [dev-dependencies] 33 | serde_json = "1.0" 34 | function_name = "0.3" 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 long_long_float 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | lisp-rs 2 | ========= 3 | 4 | This is a lisp interpreter written in Rust. 5 | 6 | ```shell 7 | $ cargo run -r examples/mandelbrot.scm 8 | ``` 9 | 10 | ![mandelbrot](./examples/mandelbrot.png) 11 | 12 | To compile for RISC-V 32, use a option `-c`. 13 | 14 | The compiler outputs `out.bin` and `out.elf`. 15 | A file `out.bin` is raw instructions. 16 | A file `out.elf` is instructions formatted by ELF. 17 | 18 | Note that it is experimental, therefore the compiler fails or outputs invalid code. 19 | 20 | ```shell 21 | $ cargo run -r -- -c source.scm 22 | ``` 23 | 24 | ## Testing 25 | 26 | ``` 27 | $ cargo test 28 | ``` 29 | 30 | To test the compiler, add a feature flag `rv32emu-test`. 31 | 32 | Note that it uses [rv32emu](https://github.com/long-long-float/rv32emu.git) as a submodule. 33 | 34 | ``` 35 | $ cargo test --features rv32emu-test 36 | ``` 37 | 38 | ## Features 39 | 40 | - Basic form and functions (define, if, +, -...) 41 | - Macros 42 | - Statically typed with type inference 43 | - Human readable errors (like rustc) 44 | - REPL 45 | - Optimizing tail recursion 46 | - Generate code for RISC-V (WIP) 47 | 48 | ## Milestones 49 | 50 | - [x] Run FizzBuzz 51 | - [x] Draw Mandelbrot set 52 | - [x] Support statically type system with inference. 53 | - [x] Compiler for RISC-V 54 | - [ ] Implement a lisp interpreter running in lisp-rs. 55 | 56 | ## Reference 57 | 58 | * https://schemers.org/Documents/Standards/R5RS/HTML/ 59 | * https://cs61a.org/articles/scheme-spec/ 60 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::process::{Command, Stdio}; 2 | 3 | fn main() { 4 | if cfg!(feature = "rv32emu-test") { 5 | println!("Build rv32emu"); 6 | let output = Command::new("make") 7 | .args(["-C", "rv32emu"]) 8 | .stdout(Stdio::inherit()) 9 | .stderr(Stdio::inherit()) 10 | .output() 11 | .expect("Failed to execute make"); 12 | if !output.status.success() { 13 | panic!("make failed with status {}", output.status); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/blink-led.scm: -------------------------------------------------------------------------------- 1 | ; This works only on Wio Lite RISC V 2 | ; https://wiki.seeedstudio.com/Wio_Lite_RISC_V_GD32VF103_with_ESP8266/ 3 | 4 | (io-write 5 | (+ 0x40021000 0x18) 6 | (<< 0b0001 2)) 7 | 8 | ; GPIO A 9 | (define base 0x40010800) 10 | 11 | ; Configure output mode 12 | (io-write 13 | (+ base 0x04) 14 | ; Push-pull output, output mode (50MHz) 15 | 0x3) 16 | 17 | ; Clear bit 18 | (io-write 19 | (+ base 0x18) 20 | (<< 0b0001 8)) 21 | 22 | ; Set bit (lighting) 23 | (io-write 24 | (+ 0x40010800 0x10) 25 | (<< 0b0001 8)) 26 | -------------------------------------------------------------------------------- /examples/calc.scm: -------------------------------------------------------------------------------- 1 | (include "library/prelude.scm") 2 | 3 | (struct Context 4 | cursor: int) 5 | 6 | (fn is_digit (ch) 7 | (define chi (as ch int)) 8 | (and (<= (as #\0 int) chi) (<= chi (as #\9 int)))) 9 | 10 | (fn count-digit (ctx input) 11 | (define digit 0) 12 | (for (i (Context->cursor ctx)) 13 | (and (is_digit (array->get input i)) (< i (array->len input))) 14 | (+ i 1) 15 | (set! digit (+ digit 1))) 16 | digit) 17 | 18 | (fn parse-int (ctx input) 19 | (define sum 0) 20 | (define len (count-digit ctx input)) 21 | (define digit 1) 22 | (define start (Context->cursor ctx)) 23 | (for (i (- len 1)) (>= i 0) (- i 1) (begin 24 | (define n (char->int (array->get input (+ start i)))) 25 | (set! sum (+ sum (* n digit))) 26 | (set! digit (* digit 10)))) 27 | (Context->cursor= ctx (+ start len)) 28 | sum) 29 | 30 | (fn parse-op (ctx input) 31 | (define op (array->get input (Context->cursor ctx))) 32 | (Context->cursor= ctx (+ (Context->cursor ctx) 1)) 33 | op) 34 | 35 | (fn parse-spaces (ctx input) 36 | (for (i (Context->cursor ctx)) 37 | (and (= (array->get input i) #\ ) (< i (array->len input))) 38 | (+ i 1) 39 | (Context->cursor= ctx (+ (Context->cursor ctx) 1))) 40 | 0) 41 | 42 | (fn calc-expr (ctx input) 43 | (define left (parse-int ctx input)) 44 | (parse-spaces ctx input) 45 | (define op (parse-op ctx input)) 46 | (parse-spaces ctx input) 47 | (define right (parse-int ctx input)) 48 | (cond 49 | ((= op #\+) (+ left right)) 50 | ((= op #\-) (- left right)) 51 | (#t 0))) 52 | 53 | (fn calc (input) 54 | (define ctx (Context 0)) 55 | (calc-expr &ctx input)) 56 | 57 | (calc &"10 + 20") 58 | -------------------------------------------------------------------------------- /examples/fizzbuzz.scm: -------------------------------------------------------------------------------- 1 | (define fizzbuzz (lambda (x) 2 | (cond 3 | ((= (mod x 15) 0) "fizzbuzz") 4 | ((= (mod x 3) 0) "fizz") 5 | ((= (mod x 5) 0) "buzz") 6 | (#t (int->string x))))) 7 | 8 | (let loop ((s 1) (e 50)) 9 | (if (not (> s e)) 10 | (begin 11 | (print (fizzbuzz s)) 12 | (loop (+ s 1) e)))) 13 | -------------------------------------------------------------------------------- /examples/mandelbrot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/long-long-float/lisp-rs/be6a6ccd36d783c3f7823062f07ee6d83dfcd60f/examples/mandelbrot.png -------------------------------------------------------------------------------- /examples/mandelbrot.scm: -------------------------------------------------------------------------------- 1 | ; Reference https://gist.github.com/LeopoldTal/2aa79947c4cab728402c4054d6733254 2 | 3 | (define MAX_ITERS 200) 4 | 5 | (define get-rejection-iters (lambda (ptx pty) 6 | (let ( 7 | [re 0.0] 8 | [im 0.0] 9 | [re-sq 0.0] 10 | [im-sq 0.0]) 11 | (let loop ([nb-iters 0]) 12 | (if (or (> (+ im-sq re-sq) 4.0) (>= nb-iters MAX_ITERS)) 13 | nb-iters 14 | (begin 15 | (set! im (+ (* 2.0 (* re im)) pty)) 16 | (set! re (+ (- re-sq im-sq) ptx)) 17 | 18 | (set! im-sq (* im im)) 19 | (set! re-sq (* re re)) 20 | (loop (+ nb-iters 1)))))))) 21 | 22 | (define palette (string->list ".:-=+*#%@")) 23 | 24 | (define draw-point (lambda (x y) 25 | (let ([iters (get-rejection-iters x y)]) 26 | (if (>= iters MAX_ITERS) 27 | (display #\ ) 28 | (display (list-ref palette (mod (- iters 1) (length palette)))) 29 | )))) 30 | 31 | (let* ( 32 | (step 0.045) 33 | (step-x (/ step 2)) 34 | (step-y step) 35 | (lines 53) 36 | (columns 151) 37 | (center-x (- 0.0 0.6)) 38 | (center-y 0) 39 | ) 40 | (let loop1 ((cur-line 0)) (if (< cur-line lines) 41 | (let ((y (+ center-y (* step-y (- (- (* 0.5 lines) cur-line) 0.5))))) 42 | (let loop2 ((cur-col 0)) (if (< cur-col columns) 43 | (let ((x (- center-x (* step-x (- (- (* 0.5 columns) cur-col) 0.5))))) 44 | (draw-point x y) 45 | (loop2 (+ cur-col 1)) 46 | ))) 47 | (newline) 48 | (loop1 (+ cur-line 1))) 49 | ))) 50 | -------------------------------------------------------------------------------- /examples/test.lisp: -------------------------------------------------------------------------------- 1 | (print 10) 2 | (print (+ 10 20 30)) 3 | (print (+ (* 1 2) (- 3 4))) 4 | 5 | (setq x 10) 6 | (print x) 7 | 8 | (setq y '(1 2 3 4)) 9 | (print y) -------------------------------------------------------------------------------- /library/prelude.scm: -------------------------------------------------------------------------------- 1 | ; Macros 2 | 3 | (define-macro for (init condn update body) 4 | (define label (__gen-unique-sym)) 5 | (list 'begin 6 | (list 'let label (list init) 7 | (list 'if condn (list 'begin 8 | body 9 | (list label update)))) 10 | 0)) 11 | 12 | ; Functions 13 | 14 | (fn print (str) 15 | (syscall3 64 1 (array->data &str) (array->len &str))) 16 | 17 | (fn println (str) 18 | (print str) 19 | (print "\n") 20 | 0) 21 | 22 | (fn println-bool (val) 23 | (println (if val "true" "false"))) 24 | 25 | (fn char->int (ch) 26 | (- (as ch int) (as #\0 int))) 27 | 28 | (fn int->char (val) 29 | (as (+ (as #\0 int) val) char)) 30 | 31 | (fn print-char (ch) 32 | (define str " ") 33 | (array->set &str 0 ch) 34 | (syscall3 64 1 (array->data &str) (array->len &str))) 35 | 36 | (fn println-int (value) 37 | ; TODO: Remove `_` and then fix to refer inner function 38 | (fn _count-digit (value) 39 | (define digit 0) 40 | (for (i 1) (< 0 (/ value i)) (* i 10) 41 | (set! digit (+ digit 1))) 42 | digit) 43 | 44 | (if (= value 0) 45 | ; Special case for 0 46 | (begin 47 | (print-char #\0) 48 | (print "\n") 49 | 1) 50 | (begin 51 | (define digit (_count-digit value)) 52 | (define buf (array->new digit)) 53 | 54 | (define d 1) 55 | (for (i 0) (< i digit) (+ i 1) (begin 56 | (array->set &buf i (% (/ value d) 10)) 57 | (set! d (* d 10)))) 58 | 59 | (for (i (- (array->len &buf) 1)) (>= i 0) (- i 1) (begin 60 | (print-char (int->char (array->get &buf i))))) 61 | (print "\n") 62 | (array->len &buf)))) 63 | -------------------------------------------------------------------------------- /rv32-asm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rv32-asm" 3 | version = "0.1.1" 4 | edition = "2021" 5 | authors = ["long-long-float "] 6 | description = "RISC-V(RV32IM) instructions and assembler for Rust" 7 | license = "Apache-2.0" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | anyhow = "1.0" 13 | thiserror = "1.0" 14 | rustc-hash = "1.1" 15 | colored = "2.0" -------------------------------------------------------------------------------- /rv32-asm/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024 long-long-float 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /rv32-asm/README.md: -------------------------------------------------------------------------------- 1 | rv32-asm 2 | ====================== 3 | 4 | RISC-V(RV32IM) instructions and assembler for Rust. 5 | 6 | ## License 7 | 8 | This package is distributed under [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0). -------------------------------------------------------------------------------- /rv32-asm/src/assembler.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::Write; 3 | use std::path::Path; 4 | 5 | use anyhow::Result; 6 | use colored::*; 7 | use rustc_hash::FxHashMap; 8 | 9 | use crate::error::*; 10 | use crate::instruction::*; 11 | 12 | struct Context { 13 | label_addrs: FxHashMap, 14 | } 15 | 16 | impl Context { 17 | fn new() -> Context { 18 | Context { 19 | label_addrs: FxHashMap::default(), 20 | } 21 | } 22 | 23 | fn get_addr_by_label(&self, name: &str) -> Result { 24 | Ok(self 25 | .label_addrs 26 | .get(name) 27 | .cloned() 28 | .ok_or_else(|| Error::LabelNotDefined(name.to_string()))?) 29 | } 30 | } 31 | 32 | type Code = u32; 33 | type Codes = Vec; 34 | 35 | pub fn dump_instructions(insts: &[InstructionWithLabel]) { 36 | println!("{}", "RISC-V Instructions:".red()); 37 | for (addr, InstructionWithLabel { inst, labels, ir }) in insts.iter().enumerate() { 38 | for label in labels { 39 | let addr = format!("; 0x{:x}", addr * 4); 40 | println!("{}: {}", &label.name, addr.dimmed()); 41 | } 42 | 43 | if let Some(ir) = ir { 44 | let ir = format!(";{}", ir); 45 | println!(" {}", ir.dimmed()); 46 | } 47 | println!(" {}", inst); 48 | } 49 | println!(); 50 | } 51 | 52 | fn replace_label(imm: Immediate, ctx: &Context) -> Result { 53 | match imm { 54 | Immediate::Value(_) => Ok(imm), 55 | Immediate::Label(label) => Ok(Immediate::new(ctx.get_addr_by_label(&label.name)? as i32)), 56 | } 57 | } 58 | 59 | fn replace_redaddr_label(rel_addr: RelAddress, addr: usize, ctx: &Context) -> Result { 60 | match rel_addr { 61 | RelAddress::Immediate(_) => Ok(rel_addr), 62 | RelAddress::Label(label) => Ok(RelAddress::Immediate(Immediate::Value( 63 | ctx.get_addr_by_label(&label.name)? as i32 - addr as i32, 64 | ))), 65 | } 66 | } 67 | 68 | fn replace_labels(inst: InstructionWithLabel, ctx: &Context) -> Result { 69 | use Instruction::*; 70 | 71 | let InstructionWithLabel { inst, labels, ir } = inst; 72 | let replaced = match inst { 73 | R(_) => inst, 74 | I(IInstruction { op, imm, rs1, rd }) => I(IInstruction { 75 | op, 76 | imm: replace_label(imm, ctx)?, 77 | rs1, 78 | rd, 79 | }), 80 | S(SInstruction { op, imm, rs1, rs2 }) => S(SInstruction { 81 | op, 82 | imm: replace_label(imm, ctx)?, 83 | rs1, 84 | rs2, 85 | }), 86 | J(_) => inst, 87 | U(UInstruction { op, imm, rd }) => U(UInstruction { 88 | op, 89 | imm: replace_label(imm, ctx)?, 90 | rd, 91 | }), 92 | SB(_) => inst, 93 | }; 94 | Ok(InstructionWithLabel::new(replaced, labels, ir)) 95 | } 96 | 97 | fn replace_reladdr_labels( 98 | inst: InstructionWithLabel, 99 | addr: usize, 100 | ctx: &Context, 101 | ) -> Result { 102 | use Instruction::*; 103 | 104 | let InstructionWithLabel { inst, labels, ir } = inst; 105 | let replaced = match inst { 106 | J(JInstruction { op, imm, rd }) => J(JInstruction { 107 | op, 108 | imm: replace_redaddr_label(imm, addr, ctx)?, 109 | rd, 110 | }), 111 | SB(SBInstruction { op, imm, rs1, rs2 }) => SB(SBInstruction { 112 | op, 113 | imm: replace_redaddr_label(imm, addr, ctx)?, 114 | rs1, 115 | rs2, 116 | }), 117 | _ => inst, 118 | }; 119 | Ok(InstructionWithLabel::new(replaced, labels, ir)) 120 | } 121 | 122 | pub fn assemble

(instructions: Vec, dump_to: Option

) -> Result 123 | where 124 | P: AsRef, 125 | { 126 | use Instruction::*; 127 | 128 | let mut ctx = Context::new(); 129 | 130 | for (idx, inst) in instructions.iter().enumerate() { 131 | for label in &inst.labels { 132 | ctx.label_addrs.insert(label.name.clone(), idx * 4); 133 | } 134 | } 135 | 136 | let mut insts = Vec::with_capacity(instructions.len()); 137 | for (addr, inst) in instructions.into_iter().enumerate() { 138 | let inst = replace_labels(inst, &ctx)?; 139 | insts.push(replace_reladdr_labels(inst, addr * 4, &ctx)?); 140 | } 141 | 142 | if let Some(dump_to) = dump_to { 143 | let mut asm = File::create(dump_to)?; 144 | for (addr, InstructionWithLabel { inst, ir, labels }) in insts.iter().enumerate() { 145 | for label in labels { 146 | writeln!(asm, "{}: # 0x{:x}", &label.name, addr * 4)?; 147 | } 148 | 149 | if let Some(ir) = ir { 150 | writeln!(asm, " #{}", ir)?; 151 | } 152 | let a = match inst { 153 | R(ri) => ri.generate_asm(), 154 | I(ii) => ii.generate_asm(), 155 | S(si) => si.generate_asm(), 156 | J(ji) => ji.generate_asm(), 157 | U(ui) => ui.generate_asm(), 158 | SB(sbi) => sbi.generate_asm(), 159 | }; 160 | writeln!(asm, " {}", a)?; 161 | } 162 | } 163 | 164 | let result = insts 165 | .into_iter() 166 | .map(|InstructionWithLabel { inst, .. }| match inst { 167 | R(ri) => ri.generate_code(), 168 | I(ii) => ii.generate_code(), 169 | S(si) => si.generate_code(), 170 | J(ji) => ji.generate_code(), 171 | U(ui) => ui.generate_code(), 172 | SB(sbi) => sbi.generate_code(), 173 | }) 174 | .collect(); 175 | 176 | Ok(result) 177 | } 178 | -------------------------------------------------------------------------------- /rv32-asm/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror; 2 | 3 | #[derive(PartialEq, Debug, Clone, thiserror::Error)] 4 | pub enum Error { 5 | #[error("A label {0} is not defined.")] 6 | LabelNotDefined(String), 7 | 8 | #[error("Undefined variable: `{0}` at {1}")] 9 | UndefinedVariable(String, &'static str), 10 | 11 | #[error("Bug: {message:?} at {file:?}:{line:?}")] 12 | Bug { 13 | message: String, 14 | file: &'static str, 15 | line: u32, 16 | }, 17 | 18 | // For non-local exists 19 | #[error("")] 20 | DoNothing, 21 | } 22 | -------------------------------------------------------------------------------- /rv32-asm/src/instruction.rs: -------------------------------------------------------------------------------- 1 | use core::panic; 2 | use std::fmt::Display; 3 | use std::ops; 4 | 5 | type RegisterType = u32; 6 | pub const XLEN: u8 = 32; 7 | 8 | #[derive(Clone, PartialEq, Debug)] 9 | pub struct InstructionWithLabel { 10 | pub inst: Instruction, 11 | pub labels: Vec

(filename: P) -> Result, Error> 10 | where 11 | P: AsRef, 12 | { 13 | if let Ok(file) = File::open(filename) { 14 | let lines = io::BufReader::new(file) 15 | .lines() 16 | .map_while(Result::ok) 17 | .collect::>(); 18 | Ok(lines) 19 | } else { 20 | Err(Error::Io("Cannot open source file".to_string())) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/lispi/console.rs: -------------------------------------------------------------------------------- 1 | use crossterm::cursor; 2 | use crossterm::terminal; 3 | use crossterm::ExecutableCommand; 4 | use std::fmt::Display; 5 | use std::io::{stdout, Write}; 6 | 7 | pub fn print(text: &T) -> std::io::Result<()> 8 | where 9 | T: Display + ?Sized, 10 | { 11 | write!(stdout(), "{}", text)?; 12 | stdout().flush()?; 13 | Ok(()) 14 | } 15 | 16 | pub fn printuw(text: &T) 17 | where 18 | T: Display + ?Sized, 19 | { 20 | print(text).unwrap(); 21 | } 22 | 23 | pub fn println(text: &T) -> std::io::Result<()> 24 | where 25 | T: Display + ?Sized, 26 | { 27 | print(text)?; 28 | newline()?; 29 | Ok(()) 30 | } 31 | 32 | pub fn printlnuw(text: &T) 33 | where 34 | T: Display + ?Sized, 35 | { 36 | println(text).unwrap(); 37 | } 38 | 39 | pub fn newline() -> std::io::Result<()> { 40 | stdout() 41 | .execute(cursor::MoveToNextLine(1))? 42 | .execute(terminal::ScrollUp(1))?; 43 | Ok(()) 44 | } 45 | 46 | pub fn newlineuw() { 47 | newline().unwrap(); 48 | } 49 | -------------------------------------------------------------------------------- /src/lispi/environment.rs: -------------------------------------------------------------------------------- 1 | use rustc_hash::FxHashMap; 2 | use std::{ 3 | cell::{Ref, RefCell}, 4 | collections::HashMap, 5 | rc::Rc, 6 | }; 7 | 8 | use super::{console::*, error::*, SymbolValue}; 9 | 10 | pub struct Environment { 11 | pub head_local: LocalRef, 12 | /// For referencing variables from closure. 13 | pub lambda_local: LocalRef, 14 | } 15 | 16 | impl Environment 17 | where 18 | T: Clone, 19 | { 20 | pub fn update_var(&mut self, name: SymbolValue, value: &T) -> Result<(), Error> { 21 | let mut local = self.head_local.as_mut().unwrap().borrow_mut(); 22 | if local.update_var(name.clone(), value) { 23 | Ok(()) 24 | } else { 25 | Err(Error::Eval(format!( 26 | "A variable `{}` is not defined.", 27 | name 28 | ))) 29 | } 30 | } 31 | 32 | pub fn insert_var(&mut self, name: SymbolValue, value: T) { 33 | // local_stack must have least one local 34 | let mut local = self.head_local.as_ref().unwrap().borrow_mut(); 35 | local.variables.insert(name, value); 36 | } 37 | 38 | pub fn current_local(&self) -> Ref> { 39 | self.head_local.as_ref().unwrap().borrow() 40 | } 41 | 42 | pub fn find_var(&mut self, name: &SymbolValue) -> Option { 43 | self.head_local 44 | .as_mut() 45 | .unwrap() 46 | .borrow_mut() 47 | .find_var(name.to_owned()) 48 | } 49 | 50 | pub fn push_local(&mut self) { 51 | let local = self.head_local.clone(); 52 | let local = Some(Rc::new(RefCell::new(Local::new(local)))); 53 | self.head_local = local; 54 | } 55 | 56 | pub fn pop_local(&mut self) { 57 | if let Some(local) = &self.head_local { 58 | let parent = local.borrow().parent.clone(); 59 | self.head_local = parent; 60 | } 61 | } 62 | } 63 | 64 | impl Default for Environment 65 | where 66 | T: Clone, 67 | { 68 | fn default() -> Self { 69 | let mut env = Environment { 70 | head_local: None, 71 | lambda_local: None, 72 | }; 73 | env.push_local(); 74 | env 75 | } 76 | } 77 | 78 | impl Environment 79 | where 80 | T: Clone + std::fmt::Debug, 81 | { 82 | #[allow(dead_code)] 83 | pub fn dump_local(&self, dump_root: bool) { 84 | let local = self.head_local.as_ref().unwrap().borrow(); 85 | printlnuw("--- Locals ---"); 86 | local.dump(dump_root); 87 | } 88 | } 89 | 90 | /// Reference of Local. `None` represents the root. 91 | pub type LocalRef = Option>>>; 92 | 93 | /// A Local has mappings for local variables. 94 | /// 95 | /// The Environment has a local as currently evaluation. 96 | /// A lambda also has a local to realize closures. 97 | /// 98 | /// Locals chains like this. 99 | /// 100 | /// ```text 101 | /// Environment.head_local -> local1 -> local2 ... -> None (root) 102 | /// ``` 103 | #[derive(PartialEq, Debug)] 104 | pub struct Local { 105 | pub variables: FxHashMap, 106 | parent: LocalRef, 107 | } 108 | type LocalKey = String; 109 | 110 | impl Local 111 | where 112 | T: Clone, 113 | { 114 | fn new(parent: LocalRef) -> Local { 115 | Local { 116 | variables: HashMap::default(), 117 | parent, 118 | } 119 | } 120 | 121 | pub fn find_var(&mut self, id: LocalKey) -> Option { 122 | if let Some(value) = self.variables.get(&id) { 123 | Some(value.clone()) 124 | } else if let Some(parent) = &self.parent { 125 | parent.borrow_mut().find_var(id) 126 | } else { 127 | None 128 | } 129 | } 130 | 131 | pub fn update_var(&mut self, id: LocalKey, value: &T) -> bool { 132 | if self.variables.get(&id).is_some() { 133 | self.variables.insert(id, value.clone()); 134 | true 135 | } else if let Some(parent) = &self.parent { 136 | parent.borrow_mut().update_var(id, value) 137 | } else { 138 | false 139 | } 140 | } 141 | } 142 | 143 | impl Local 144 | where 145 | T: Clone + std::fmt::Debug, 146 | { 147 | fn dump(&self, dump_root: bool) { 148 | if let Some(parent) = &self.parent { 149 | // Don't print root local. 150 | printlnuw(&format!("{:#?}", self.variables)); 151 | parent.borrow_mut().dump(dump_root) 152 | } else if dump_root { 153 | printlnuw(&format!("{:#?}", self.variables)); 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/lispi/error.rs: -------------------------------------------------------------------------------- 1 | use std::{error, fmt::Display}; 2 | use thiserror; 3 | 4 | use super::{typer::Type, Location, LocationRange, TokenLocation}; 5 | 6 | #[derive(PartialEq, Debug, Clone, thiserror::Error)] 7 | pub enum Error { 8 | #[error("IO error: {0}")] 9 | Io(String), 10 | #[error("Tokenize error: {0}")] 11 | Tokenize(String), 12 | #[error("Parse error: {0}")] 13 | Parse(String), 14 | #[error("Evaluation error: {0}")] 15 | Eval(String), 16 | #[error("Type error: {0}")] 17 | Type(String), 18 | #[error("Types {0} and {1} are not matched")] 19 | TypeNotMatched(Type, Type, TokenLocation, TokenLocation), 20 | #[error("Compile error: {0}")] 21 | CompileError(String), 22 | 23 | #[error("Undefined variable: `{0}` at {1}")] 24 | UndefinedVariable(String, &'static str), 25 | 26 | #[error("Bug: {message:?} at {file:?}:{line:?}")] 27 | Bug { 28 | message: String, 29 | file: &'static str, 30 | line: u32, 31 | }, 32 | 33 | // For non-local exists 34 | #[error("")] 35 | DoNothing, 36 | } 37 | 38 | impl Error { 39 | pub fn with_location(self, location: TokenLocation) -> ErrorWithLocation { 40 | ErrorWithLocation { 41 | err: self, 42 | location, 43 | } 44 | } 45 | 46 | pub fn with_single_location(self, loc: Location) -> ErrorWithLocation { 47 | let end = Location { 48 | line: loc.line, 49 | column: loc.column + 1, 50 | }; 51 | self.with_location(TokenLocation::Range(LocationRange::new(loc, end))) 52 | } 53 | 54 | pub fn with_null_location(self) -> ErrorWithLocation { 55 | ErrorWithLocation { 56 | err: self, 57 | location: TokenLocation::Null, 58 | } 59 | } 60 | } 61 | 62 | #[derive(PartialEq, Debug, Clone)] 63 | pub struct ErrorWithLocation { 64 | pub err: Error, 65 | pub location: TokenLocation, 66 | } 67 | 68 | impl Display for ErrorWithLocation { 69 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 70 | write!(f, "{}", self.err) 71 | } 72 | } 73 | 74 | impl error::Error for ErrorWithLocation {} 75 | -------------------------------------------------------------------------------- /src/lispi/include_expander.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use itertools::Itertools; 3 | 4 | use super::{ast::*, common::read_lines, parser as p, tokenizer as t}; 5 | 6 | fn expand_includes_ast(ast: AnnotatedAst) -> Result { 7 | let AnnotatedAst { ast, location, ty } = ast; 8 | 9 | let prog = match ast { 10 | Ast::Include(path) => { 11 | // TODO: Interpret path as a relative path from the program location 12 | let lines = read_lines(path)?; 13 | let tokens = t::tokenize(lines)?; 14 | let program = p::parse(tokens)?; 15 | expand_includes(program)? 16 | } 17 | _ => vec![AnnotatedAst { ast, location, ty }], 18 | }; 19 | 20 | Ok(prog) 21 | } 22 | 23 | pub fn expand_includes(asts: p::Program) -> Result { 24 | let asts = asts 25 | .into_iter() 26 | .map(expand_includes_ast) 27 | .collect::>>()? 28 | .into_iter() 29 | .flatten() 30 | .collect_vec(); 31 | 32 | Ok(asts) 33 | } 34 | -------------------------------------------------------------------------------- /src/lispi/ir.rs: -------------------------------------------------------------------------------- 1 | pub mod basic_block; 2 | pub mod compiler; 3 | pub mod instruction; 4 | pub mod register_allocation; 5 | pub mod removing_phi_instructions; 6 | pub mod tag; 7 | 8 | use id_arena::Arena; 9 | 10 | use self::basic_block::BasicBlock; 11 | 12 | #[derive(Default)] 13 | pub struct IrContext { 14 | pub bb_arena: Arena, 15 | } 16 | -------------------------------------------------------------------------------- /src/lispi/ir/basic_block.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use colored::Colorize; 3 | use itertools::Itertools; 4 | use std::{collections::VecDeque, fmt::Display, fs::File, io::Write, path::Path, slice::Iter}; 5 | 6 | use id_arena::{Arena, Id}; 7 | use rustc_hash::FxHashSet; 8 | 9 | use super::instruction::{self as i, AnnotatedInstr}; 10 | use crate::lispi::{ 11 | ty::{StructDefinitions, Type}, 12 | SymbolValue, 13 | }; 14 | 15 | #[derive(Clone, PartialEq, Debug)] 16 | pub struct BasicBlock { 17 | pub label: String, 18 | pub insts: Vec, 19 | 20 | /// Basic blocks where the control flow goes to 21 | pub destination_bbs: FxHashSet>, 22 | 23 | /// Basic blocks where the control flow comes from 24 | pub source_bbs: FxHashSet>, 25 | 26 | pub preceding_bb: Option>, 27 | } 28 | 29 | impl BasicBlock { 30 | pub fn new(label: String, preceding_bb: Option>) -> Self { 31 | Self { 32 | label, 33 | insts: Vec::new(), 34 | destination_bbs: FxHashSet::default(), 35 | source_bbs: FxHashSet::default(), 36 | preceding_bb, 37 | } 38 | } 39 | 40 | pub fn push_inst(&mut self, inst: i::AnnotatedInstr) { 41 | self.insts.push(inst); 42 | } 43 | } 44 | 45 | pub trait BasicBlockIdExtension { 46 | /// Find BasicBlock by BFS 47 | fn find_forward<'a, F>(&self, arena: &'a Arena, pred: F) -> Option<&'a BasicBlock> 48 | where 49 | F: FnMut(&'a BasicBlock) -> bool; 50 | } 51 | 52 | impl BasicBlockIdExtension for Id { 53 | fn find_forward<'a, F>( 54 | &self, 55 | arena: &'a Arena, 56 | mut pred: F, 57 | ) -> Option<&'a BasicBlock> 58 | where 59 | F: FnMut(&'a BasicBlock) -> bool, 60 | { 61 | let mut que = VecDeque::new(); 62 | 63 | que.push_back(self); 64 | 65 | let mut visited_bbs = FxHashSet::default(); 66 | 67 | while let Some(bb) = que.pop_back() { 68 | if visited_bbs.contains(bb) { 69 | continue; 70 | } 71 | visited_bbs.insert(bb); 72 | 73 | let bb = arena.get(*bb).unwrap(); 74 | 75 | if pred(bb) { 76 | return Some(bb); 77 | } 78 | 79 | for dbb in &bb.destination_bbs { 80 | que.push_back(dbb); 81 | } 82 | } 83 | 84 | None 85 | } 86 | } 87 | 88 | pub type BasicBlocks = Vec>; 89 | 90 | #[derive(Clone, Debug)] 91 | pub struct Function { 92 | pub name: String, 93 | pub args: Vec<(String, Type)>, 94 | pub free_vars: Vec, 95 | pub ty: Type, 96 | pub is_lambda: bool, 97 | 98 | pub basic_blocks: BasicBlocks, 99 | } 100 | 101 | impl Function { 102 | pub fn new( 103 | name: String, 104 | args: Vec<(String, Type)>, 105 | free_vars: Vec, 106 | ty: Type, 107 | is_lambda: bool, 108 | basic_blocks: BasicBlocks, 109 | ) -> Self { 110 | Self { 111 | name, 112 | args, 113 | free_vars, 114 | ty, 115 | is_lambda, 116 | basic_blocks, 117 | } 118 | } 119 | 120 | pub fn walk_instructions<'a>( 121 | &'a self, 122 | arena: &'a Arena, 123 | ) -> WalkingInstructionIterator<'a> { 124 | let cur_bb = self.basic_blocks.iter(); 125 | WalkingInstructionIterator::new(arena, cur_bb) 126 | } 127 | 128 | pub fn dump(&self, arena: &Arena) { 129 | print!("{}", self.display(true, arena)); 130 | } 131 | 132 | pub fn display<'a>(&'a self, colored: bool, arena: &'a Arena) -> FunctionDisplay { 133 | FunctionDisplay { 134 | func: self, 135 | colored, 136 | arena, 137 | } 138 | } 139 | } 140 | 141 | pub struct WalkingInstructionIterator<'a> { 142 | arena: &'a Arena, 143 | cur_bb: Iter<'a, Id>, 144 | cur_inst: Option>, 145 | 146 | finished: bool, 147 | } 148 | 149 | impl<'a> WalkingInstructionIterator<'a> { 150 | fn new(arena: &'a Arena, cur_bb: Iter<'a, Id>) -> Self { 151 | Self { 152 | arena, 153 | cur_bb, 154 | cur_inst: None, 155 | finished: false, 156 | } 157 | } 158 | 159 | fn load_next_bb(&mut self) { 160 | let next_bb = self.cur_bb.next(); 161 | match next_bb { 162 | Some(next_bb) => { 163 | let next_bb = self.arena.get(*next_bb).unwrap(); 164 | self.cur_inst = Some(next_bb.insts.iter()); 165 | } 166 | None => { 167 | self.finished = true; 168 | } 169 | } 170 | } 171 | } 172 | 173 | impl<'a> Iterator for WalkingInstructionIterator<'a> { 174 | type Item = &'a AnnotatedInstr; 175 | 176 | fn next(&mut self) -> Option { 177 | if self.finished { 178 | None 179 | } else { 180 | match self.cur_inst.as_mut() { 181 | None => { 182 | self.load_next_bb(); 183 | self.next() 184 | } 185 | Some(iter) => { 186 | let ret = iter.next(); 187 | match ret { 188 | Some(_) => ret, 189 | None => { 190 | self.load_next_bb(); 191 | self.next() 192 | } 193 | } 194 | } 195 | } 196 | } 197 | } 198 | } 199 | 200 | #[derive(Clone, Debug)] 201 | pub struct FunctionDisplay<'a> { 202 | func: &'a Function, 203 | colored: bool, 204 | arena: &'a Arena, 205 | } 206 | 207 | impl Display for FunctionDisplay<'_> { 208 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 209 | write!(f, "function {} (", self.func.name)?; 210 | for (id, ty) in &self.func.args { 211 | write!(f, "%{}: {}, ", id, ty)?; 212 | } 213 | write!(f, ") (")?; 214 | for id in &self.func.free_vars { 215 | write!(f, "%{}, ", id)?; 216 | } 217 | writeln!( 218 | f, 219 | "): {} {{ // {}", 220 | self.func.ty, 221 | if self.func.is_lambda { "lambda" } else { "" } 222 | )?; 223 | 224 | for bb in &self.func.basic_blocks { 225 | let bb = self.arena.get(*bb).unwrap(); 226 | if self.colored { 227 | writeln!(f, " {}:", bb.label.cyan())?; 228 | } else { 229 | writeln!(f, " {}:", bb.label)?; 230 | } 231 | for inst in &bb.insts { 232 | writeln!(f, " {}", inst.display(self.colored))?; 233 | } 234 | } 235 | 236 | writeln!(f, "}}")?; 237 | 238 | Ok(()) 239 | } 240 | } 241 | 242 | pub struct IrProgram { 243 | pub funcs: Vec, 244 | pub structs: StructDefinitions, 245 | } 246 | 247 | impl IrProgram { 248 | pub fn map_fun(self, fun: F) -> Result 249 | where 250 | F: FnMut(Function) -> Result, 251 | { 252 | let IrProgram { funcs, structs } = self; 253 | Ok(IrProgram { 254 | funcs: funcs.into_iter().map(fun).collect::>>()?, 255 | structs, 256 | }) 257 | } 258 | } 259 | 260 | fn connect_bbs( 261 | arena: &mut Arena, 262 | source_id: &Id, 263 | dest_id: &Id, 264 | ) { 265 | let source = arena.get_mut(*source_id).unwrap(); 266 | source.destination_bbs.insert(*dest_id); 267 | 268 | let dest = arena.get_mut(*dest_id).unwrap(); 269 | dest.source_bbs.insert(*source_id); 270 | } 271 | 272 | pub fn build_connections_between_bbs(arena: &mut Arena, funcs: &[Function]) { 273 | for func in funcs { 274 | // Connect consecutive basic blocks 275 | for (forward_id, back_id) in func.basic_blocks.iter().tuple_windows() { 276 | let forward_bb = arena.get(*forward_id).unwrap(); 277 | let is_terminal = forward_bb 278 | .insts 279 | .last() 280 | .map_or(false, |inst| inst.inst.is_terminal()); 281 | 282 | if !is_terminal { 283 | connect_bbs(arena, forward_id, back_id); 284 | } 285 | } 286 | 287 | // Connect non-consecutive (e.g. jump) basic blocks 288 | for curr_id in &func.basic_blocks { 289 | let curr_bb = arena.get(*curr_id).unwrap(); 290 | 291 | let insts = curr_bb.insts.clone(); 292 | for inst in insts { 293 | match &inst.inst { 294 | i::Instruction::Branch { 295 | then_bb: then_id, 296 | else_bb: else_id, 297 | .. 298 | } => { 299 | connect_bbs(arena, curr_id, then_id); 300 | connect_bbs(arena, curr_id, else_id); 301 | } 302 | i::Instruction::Jump(_, dest_id) => connect_bbs(arena, curr_id, dest_id), 303 | _ => {} 304 | } 305 | } 306 | } 307 | } 308 | } 309 | 310 | pub fn dump_functions

(arena: &mut Arena, funcs: &[Function], path: P) -> Result<()> 311 | where 312 | P: AsRef, 313 | { 314 | let mut out = File::create(path)?; 315 | 316 | for func in funcs { 317 | writeln!(out, "# {}", func.name)?; 318 | 319 | let contents = func.display(false, arena).to_string(); 320 | writeln!(out, "{}", contents)?; 321 | } 322 | 323 | Ok(()) 324 | } 325 | 326 | pub fn dump_functions_as_dot

( 327 | arena: &mut Arena, 328 | funcs: &[Function], 329 | path: P, 330 | ) -> Result<()> 331 | where 332 | P: AsRef, 333 | { 334 | let mut out = File::create(path)?; 335 | 336 | writeln!( 337 | out, 338 | "digraph cfg {{ 339 | node [shape=box, nojustify=true]" 340 | )?; 341 | 342 | for func in funcs { 343 | let mut que = VecDeque::new(); 344 | 345 | for bb_id in &func.basic_blocks { 346 | let bb = arena.get(*bb_id).unwrap(); 347 | let insts = bb 348 | .insts 349 | .iter() 350 | .map(|inst| inst.display(false).to_string()) 351 | .join("\\l"); 352 | writeln!( 353 | out, 354 | " {} [label=\"{}:\\l{}\\l\"]", 355 | bb.label, bb.label, insts 356 | )?; 357 | } 358 | 359 | if let Some(bb) = func.basic_blocks.first() { 360 | que.push_back(bb); 361 | } 362 | 363 | let mut visited_bbs = FxHashSet::default(); 364 | 365 | while let Some(bb) = que.pop_back() { 366 | if visited_bbs.contains(bb) { 367 | continue; 368 | } 369 | visited_bbs.insert(bb); 370 | 371 | let bb = arena.get(*bb).unwrap(); 372 | 373 | for dbb in &bb.destination_bbs { 374 | que.push_back(dbb); 375 | 376 | let dbb = arena.get(*dbb).unwrap(); 377 | 378 | writeln!(out, " {} -> {};", bb.label, dbb.label)?; 379 | } 380 | } 381 | } 382 | 383 | writeln!(out, "}}")?; 384 | 385 | Ok(()) 386 | } 387 | -------------------------------------------------------------------------------- /src/lispi/ir/register_allocation.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use id_arena::Id; 3 | use itertools::Itertools; 4 | use rustc_hash::{FxHashMap, FxHashSet}; 5 | 6 | use crate::{ 7 | bug, 8 | data_structure::graph::UndirectedGraph, 9 | lispi::{ 10 | cli_option::CliOption, 11 | error::Error, 12 | ir::{ 13 | basic_block::{BasicBlock, Function, IrProgram}, 14 | instruction::{AnnotatedInstr, Instruction, Variable}, 15 | IrContext, 16 | }, 17 | ty, 18 | unique_generator::UniqueGenerator, 19 | }, 20 | }; 21 | 22 | use super::tag::Tag; 23 | 24 | pub type RegisterMap = FxHashMap; 25 | 26 | type VariableSet<'a> = FxHashSet<&'a Variable>; 27 | type AllInOuts<'a> = FxHashMap, Vec<(VariableSet<'a>, VariableSet<'a>)>>; 28 | 29 | fn print_var_set(var_set: &FxHashSet<&Variable>) { 30 | print!(" {{"); 31 | for v in var_set { 32 | print!("{}, ", v); 33 | } 34 | println!("}}, "); 35 | } 36 | 37 | fn calculate_lifetime<'a>( 38 | func: &Function, 39 | ir_ctx: &'a IrContext, 40 | opt: &CliOption, 41 | ) -> AllInOuts<'a> { 42 | let mut def_uses = FxHashMap::default(); 43 | 44 | let mut exclude_vars = Vec::new(); 45 | 46 | // Scan DontAllocateRegister 47 | for bb_id in &func.basic_blocks { 48 | let bb = ir_ctx.bb_arena.get(*bb_id).unwrap(); 49 | 50 | for annot_inst in &bb.insts { 51 | if annot_inst.has_tag(Tag::DontAllocateRegister) { 52 | exclude_vars.push(&annot_inst.result); 53 | } 54 | } 55 | } 56 | 57 | for bb_id in &func.basic_blocks { 58 | let bb = ir_ctx.bb_arena.get(*bb_id).unwrap(); 59 | 60 | let mut def_uses_bb = Vec::new(); 61 | 62 | for annot_inst in &bb.insts { 63 | let used_vars = annot_inst.inst.collect_vars(); 64 | // get_vars(&annot_inst.inst, &mut used_vars); 65 | let used_vars = used_vars 66 | .into_iter() 67 | .filter(|uv| !exclude_vars.iter().any(|v| v == uv)) 68 | .collect_vec(); 69 | 70 | let mut def_vars = FxHashSet::default(); 71 | // if !annot_inst.inst.is_terminal() { 72 | if annot_inst.has_result() { 73 | def_vars.insert(&annot_inst.result); 74 | }; 75 | 76 | def_uses_bb.push((def_vars, FxHashSet::from_iter(used_vars.into_iter()))); 77 | } 78 | 79 | def_uses.insert(*bb_id, def_uses_bb); 80 | } 81 | 82 | // To make this immutable 83 | let def_uses = def_uses; 84 | 85 | let mut prev_all_in_outs = FxHashMap::default(); 86 | let mut all_in_outs_result = FxHashMap::default(); 87 | 88 | let mut all_in_outs: FxHashMap, Vec<_>> = FxHashMap::default(); 89 | 90 | let mut prev_in_vars = FxHashSet::default(); 91 | 92 | for _ in 0..10 { 93 | let mut def_uses = def_uses.clone(); 94 | 95 | for bb_id in func.basic_blocks.iter().rev() { 96 | let bb = ir_ctx.bb_arena.get(*bb_id).unwrap(); 97 | 98 | let mut def_uses_bb = def_uses.remove(bb_id).unwrap(); 99 | 100 | let mut in_outs = Vec::new(); 101 | 102 | for i in 0..bb.insts.len() { 103 | let (defs, uses) = def_uses_bb.pop().unwrap(); 104 | 105 | let is_last_inst = i == 0; 106 | let mut out_vars = FxHashSet::default(); 107 | if is_last_inst { 108 | for dest_bb in &bb.destination_bbs { 109 | if let Some(in_outs) = all_in_outs.get(dest_bb) { 110 | if let Some((inn, _)) = in_outs.first() { 111 | for v in inn { 112 | let v: &&Variable = v; 113 | out_vars.insert(*v); 114 | } 115 | } 116 | } 117 | } 118 | } else { 119 | out_vars = FxHashSet::from_iter(prev_in_vars.drain()); 120 | } 121 | 122 | let uses = FxHashSet::from_iter(uses.into_iter()); 123 | let diff = FxHashSet::from_iter(out_vars.difference(&defs).copied()); 124 | let in_vars = FxHashSet::from_iter(uses.union(&diff).copied()); 125 | 126 | prev_in_vars = in_vars.clone(); 127 | 128 | if opt.dump_register_allocation { 129 | println!("{}", bb.insts[bb.insts.len() - 1 - i].display(true)); 130 | print_var_set(&in_vars); 131 | print_var_set(&out_vars); 132 | print_var_set(&defs); 133 | print_var_set(&uses); 134 | println!(); 135 | } 136 | 137 | in_outs.push((in_vars, out_vars)); 138 | } 139 | 140 | in_outs.reverse(); 141 | all_in_outs.insert(*bb_id, in_outs); 142 | } 143 | 144 | if all_in_outs == prev_all_in_outs { 145 | all_in_outs_result = all_in_outs; 146 | break; 147 | } 148 | 149 | prev_all_in_outs = FxHashMap::from_iter(all_in_outs.clone()); 150 | } 151 | 152 | all_in_outs_result 153 | } 154 | 155 | fn build_inference_graph( 156 | func: &Function, 157 | all_in_outs: &AllInOuts, 158 | ir_ctx: &IrContext, 159 | ) -> UndirectedGraph { 160 | let mut inter_graph: UndirectedGraph = UndirectedGraph::default(); 161 | 162 | for bb_id in &func.basic_blocks { 163 | let bb = ir_ctx.bb_arena.get(*bb_id).unwrap(); 164 | 165 | for curr_inst_idx in 0..bb.insts.len() { 166 | if curr_inst_idx < bb.insts.len() - 1 { 167 | // In this case, "out" of curr-inst and "in" of next-inst are self-evidently equal. 168 | let in_outs = all_in_outs.get(bb_id).unwrap(); 169 | let (ins, outs) = &in_outs[curr_inst_idx]; 170 | 171 | fn in_args(func: &Function, var: &Variable) -> bool { 172 | func.args.iter().any(|(name, _)| &var.name == name) 173 | } 174 | 175 | for v in ins { 176 | if in_args(func, v) { 177 | continue; 178 | } 179 | inter_graph.add_node(v); 180 | } 181 | 182 | for v in outs { 183 | if in_args(func, v) { 184 | continue; 185 | } 186 | inter_graph.add_node(v); 187 | } 188 | 189 | for (a, b) in ins.iter().tuple_combinations() { 190 | if in_args(func, a) || in_args(func, b) { 191 | continue; 192 | } 193 | inter_graph.connect(a, b); 194 | } 195 | 196 | for (a, b) in outs.iter().tuple_combinations() { 197 | if in_args(func, a) || in_args(func, b) { 198 | continue; 199 | } 200 | inter_graph.connect(a, b); 201 | } 202 | } else { 203 | // Take from next bb 204 | for dest_bb in &bb.destination_bbs { 205 | let _dest_bb = ir_ctx.bb_arena.get(*dest_bb).unwrap(); 206 | } 207 | } 208 | } 209 | } 210 | 211 | inter_graph 212 | } 213 | 214 | fn spill_variable(spilled_var: &Variable, fun: &Function, ir_ctx: &mut IrContext) { 215 | let ptr_var = Variable { 216 | name: format!("{}-ptr", spilled_var.name), 217 | }; 218 | let mut gen = UniqueGenerator::default(); 219 | 220 | let mut already_spilled = false; 221 | 222 | for bb in fun.basic_blocks.iter() { 223 | let mut result = Vec::new(); 224 | 225 | let bb = ir_ctx.bb_arena.get_mut(*bb).unwrap(); 226 | 227 | for AnnotatedInstr { 228 | result: var, 229 | inst, 230 | ty, 231 | tags, 232 | } in bb.insts.clone().into_iter() 233 | { 234 | let mut replace_var_map = FxHashMap::default(); 235 | // TODO: Allocate the globally unique name. 236 | let new_var = Variable { 237 | name: format!("{}-{}", spilled_var.name, gen.gen()), 238 | }; 239 | replace_var_map.insert(spilled_var.clone(), new_var.clone()); 240 | let replaced_inst = inst.clone().replace_var(&replace_var_map); 241 | if replaced_inst != inst { 242 | result.push(AnnotatedInstr::new( 243 | new_var, 244 | Instruction::LoadElement { 245 | addr: ptr_var.clone().into(), 246 | // TODO: Adjust type 247 | ty: ty::Type::Int, 248 | index: 0.into(), 249 | }, 250 | ty::Type::Int, 251 | )); 252 | } 253 | result.push(AnnotatedInstr::new(var.clone(), replaced_inst, ty).with_tags(tags)); 254 | 255 | if &var == spilled_var { 256 | if !already_spilled { 257 | result.push( 258 | AnnotatedInstr::new( 259 | ptr_var.clone(), 260 | Instruction::Alloca { 261 | // TODO: Adjust type 262 | ty: ty::Type::Int, 263 | count: 4.into(), 264 | }, 265 | ty::Type::Reference(Box::new(ty::Type::Int)), 266 | ) 267 | .with_tags(vec![Tag::DontAllocateRegister]), 268 | ); 269 | 270 | already_spilled = true; 271 | } 272 | 273 | result.push(AnnotatedInstr::new( 274 | Variable::empty(), 275 | Instruction::StoreElement { 276 | addr: ptr_var.clone().into(), 277 | // TODO: Adjust type 278 | ty: ty::Type::Int, 279 | index: 0.into(), 280 | value: spilled_var.clone().into(), 281 | }, 282 | ty::Type::Void, 283 | )); 284 | } 285 | } 286 | 287 | bb.insts = result; 288 | } 289 | } 290 | 291 | /// Order is the second vector of the result is the same as `InProgram.funcs`. 292 | pub fn create_interference_graph( 293 | program: IrProgram, 294 | ir_ctx: &mut IrContext, 295 | opt: &CliOption, 296 | ) -> Result<(IrProgram, Vec)> { 297 | // TODO: Take the number from outside 298 | let num_of_registers = 7; 299 | 300 | let mut register_maps = Vec::new(); 301 | 302 | let program = program.map_fun(|func| { 303 | for _ in 0..3 { 304 | let all_in_outs = calculate_lifetime(&func, ir_ctx, opt); 305 | 306 | let mut inter_graph = build_inference_graph(&func, &all_in_outs, ir_ctx); 307 | 308 | if opt.dump_register_allocation { 309 | println!("{}", inter_graph); 310 | } 311 | 312 | let vars = inter_graph.values(); 313 | 314 | // A vector of (IGID, connected IGIDs). 315 | let mut removed_vars = Vec::new(); 316 | let mut spill_list = Vec::new(); 317 | 318 | // 319 | // Remove variables from inter_graph or decide spilled variables by following steps. 320 | // 1. Split variables into two list by register pressure. 321 | // * low_pressure_vars: Register pressure is less than and equals number of registers. 322 | // * high_pressure_vars: Otherwise 323 | // 2. Remove variables in low_pressure_vars from inter_graph. 324 | // 3. Remove variables in high_pressure_vars from inter_graph if its register pressure is less than and equals number of registers. 325 | // Otherwise, add the variable to spill_list. 326 | // 327 | 328 | let mut vars_sorted_by_register_pressure = vars 329 | .iter() 330 | .map(|(var, id)| { 331 | let register_pressure = inter_graph.get_connected_vars(var).len(); 332 | (var, id, register_pressure) 333 | }) 334 | .sorted_by_key(|(_, _, register_pressure)| *register_pressure); 335 | 336 | let low_pressure_vars = vars_sorted_by_register_pressure 337 | .take_while_ref(|(_, _, rp)| *rp <= num_of_registers) 338 | .collect_vec(); 339 | 340 | let high_pressure_vars = vars_sorted_by_register_pressure.rev(); 341 | 342 | for (var, id, _) in low_pressure_vars { 343 | let connected_var = inter_graph.get_connected_vars(var); 344 | removed_vars.push((id, connected_var)); 345 | inter_graph.remove(var); 346 | } 347 | 348 | for (var, id, _) in high_pressure_vars { 349 | let connected_var = inter_graph.get_connected_vars(var); 350 | let register_pressure = connected_var.len(); 351 | 352 | if register_pressure <= num_of_registers { 353 | removed_vars.push((id, connected_var)); 354 | } else { 355 | spill_list.push(var); 356 | } 357 | inter_graph.remove(var); 358 | } 359 | 360 | if spill_list.is_empty() { 361 | // A mapping for IGID and register ID. 362 | let mut allocation_map = FxHashMap::default(); 363 | 364 | for (&var, others) in removed_vars.iter().rev() { 365 | let mut allocated = [false].repeat(num_of_registers); 366 | for other in others { 367 | if let Some(®_id) = allocation_map.get(other) { 368 | allocated[reg_id] = true; 369 | } 370 | } 371 | 372 | let reg_id = allocated 373 | .iter() 374 | .enumerate() 375 | .find(|(_, &alloc)| !alloc) 376 | .map(|(id, _)| id); 377 | 378 | if let Some(reg_id) = reg_id { 379 | allocation_map.insert(var, reg_id); 380 | } else { 381 | return Err(bug!("Register cannot be allocated!")); 382 | } 383 | } 384 | 385 | let mut result = FxHashMap::default(); 386 | 387 | for (var, reg) in allocation_map { 388 | let (var, _) = vars.iter().find(|(_, &id)| id == var).unwrap(); 389 | result.insert(var.to_owned(), reg); 390 | } 391 | 392 | register_maps.push(result); 393 | 394 | return Ok(func); 395 | } else { 396 | if opt.dump_register_allocation { 397 | println!("{:#?}", spill_list); 398 | } 399 | 400 | for sv in spill_list { 401 | spill_variable(sv, &func, ir_ctx); 402 | } 403 | 404 | if opt.dump_register_allocation { 405 | func.dump(&ir_ctx.bb_arena); 406 | println!(); 407 | } 408 | } 409 | } 410 | 411 | Err(Error::CompileError("Cannot allocate registers".to_string()).into()) 412 | })?; 413 | 414 | Ok((program, register_maps)) 415 | } 416 | -------------------------------------------------------------------------------- /src/lispi/ir/removing_phi_instructions.rs: -------------------------------------------------------------------------------- 1 | use rustc_hash::FxHashMap; 2 | 3 | use super::super::ir::{basic_block as bb, instruction as i, IrContext}; 4 | 5 | pub fn remove_phi_instructions(program: &bb::IrProgram, ir_ctx: &mut IrContext) { 6 | for fun in &program.funcs { 7 | let bb::Function { basic_blocks, .. } = fun; 8 | 9 | let mut assign_map = FxHashMap::default(); 10 | 11 | for bb in basic_blocks { 12 | let bb = ir_ctx.bb_arena.get_mut(*bb).unwrap(); 13 | 14 | for i::AnnotatedInstr { 15 | result, 16 | inst, 17 | ty, 18 | tags: _, 19 | } in &bb.insts 20 | { 21 | if let i::Instruction::Phi(nodes) = &inst { 22 | for (node, label) in nodes { 23 | if !assign_map.contains_key(&label.name) { 24 | assign_map.insert(label.name.clone(), Vec::new()); 25 | } 26 | 27 | if let Some(entries) = assign_map.get_mut(&label.name) { 28 | entries.push((result.clone(), ty.clone(), node.clone())); 29 | } 30 | } 31 | } 32 | } 33 | } 34 | 35 | for bb in basic_blocks { 36 | let bb = ir_ctx.bb_arena.get_mut(*bb).unwrap(); 37 | 38 | let mut insts = Vec::new(); 39 | insts.append(&mut bb.insts); 40 | 41 | let mut new_insts = Vec::new(); 42 | 43 | for i::AnnotatedInstr { 44 | result, 45 | inst, 46 | ty, 47 | tags: _, 48 | } in insts.into_iter().rev() 49 | { 50 | match &inst { 51 | i::Instruction::Phi(_) => { /* Remove phi instruction */ } 52 | _ => { 53 | if !inst.is_terminal() { 54 | if let Some(entries) = assign_map.remove(&bb.label) { 55 | for (result, ty, operand) in entries { 56 | new_insts.push(i::AnnotatedInstr::new( 57 | result, 58 | i::Instruction::Operand(operand), 59 | ty, 60 | )); 61 | } 62 | } 63 | } 64 | 65 | new_insts.push(i::AnnotatedInstr::new(result, inst, ty)); 66 | } 67 | } 68 | } 69 | 70 | new_insts.reverse(); 71 | bb.insts = new_insts; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/lispi/ir/tag.rs: -------------------------------------------------------------------------------- 1 | use super::instruction::{Label, Variable}; 2 | 3 | #[derive(Clone, PartialEq, Debug)] 4 | pub enum Tag { 5 | /// Indicates the head of the loop 6 | LoopHeader { label: String }, 7 | /// Indicates that it will be replaced by phi function 8 | LoopPhiFunctionSite(LoopPhiFunctionSite), 9 | /// Indicates that the result variable don't need to be allocated a register. 10 | DontAllocateRegister, 11 | } 12 | 13 | impl Tag { 14 | pub fn is_match_with(&self, tag: &Tag) -> bool { 15 | use Tag::*; 16 | matches!( 17 | (self, tag), 18 | (LoopHeader { .. }, LoopHeader { .. }) 19 | | (LoopPhiFunctionSite(_), LoopPhiFunctionSite(_)) 20 | | (DontAllocateRegister, DontAllocateRegister) 21 | ) 22 | } 23 | } 24 | 25 | #[derive(Clone, PartialEq, Debug)] 26 | pub struct LoopPhiFunctionSite { 27 | pub label: String, 28 | pub index: LoopPhiFunctionSiteIndex, 29 | pub header_label: Label, 30 | } 31 | 32 | #[derive(Clone, PartialEq, Debug)] 33 | pub enum LoopPhiFunctionSiteIndex { 34 | Loop(usize), 35 | FreeVar(Variable), 36 | } 37 | -------------------------------------------------------------------------------- /src/lispi/macro_expander.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use super::{ast::*, environment::Environment, evaluator::*, parser::*}; 4 | use anyhow::Result; 5 | 6 | type MacroEnv = HashMap; 7 | 8 | pub fn expand_macros_asts( 9 | asts: Vec, 10 | menv: &mut MacroEnv, 11 | ) -> Result> { 12 | asts.into_iter() 13 | .map(|ast| expand_macros_ast(ast, menv)) 14 | .collect::>>() 15 | } 16 | 17 | pub fn expand_macros_ast(ast: AnnotatedAst, menv: &mut MacroEnv) -> Result { 18 | let AnnotatedAst { ast, location, ty } = ast; 19 | 20 | let result = match ast { 21 | Ast::DefineMacro(ref mac) => { 22 | menv.insert(mac.id.to_owned(), mac.clone()); 23 | ast 24 | } 25 | Ast::Define(mut def) => { 26 | def.init = Box::new(expand_macros_ast(*def.init, menv)?); 27 | Ast::Define(def) 28 | } 29 | Ast::DefineFunction(DefineFunction { 30 | id, 31 | lambda: 32 | Lambda { 33 | args, 34 | arg_types, 35 | body, 36 | }, 37 | lambda_type, 38 | }) => { 39 | let body = body 40 | .into_iter() 41 | .map(|ast| expand_macros_ast(ast, menv)) 42 | .collect::>>()?; 43 | Ast::DefineFunction(DefineFunction { 44 | id, 45 | lambda: Lambda { 46 | args, 47 | arg_types, 48 | body, 49 | }, 50 | lambda_type, 51 | }) 52 | } 53 | Ast::Assign(mut assign) => { 54 | assign.value = Box::new(expand_macros_ast(*assign.value, menv)?); 55 | Ast::Assign(assign) 56 | } 57 | Ast::IfExpr(mut if_expr) => { 58 | if_expr.cond = Box::new(expand_macros_ast(*if_expr.cond, menv)?); 59 | if_expr.then_ast = Box::new(expand_macros_ast(*if_expr.then_ast, menv)?); 60 | if let Some(else_ast) = if_expr.else_ast { 61 | if_expr.else_ast = Some(Box::new(expand_macros_ast(*else_ast, menv)?)) 62 | } 63 | 64 | Ast::IfExpr(if_expr) 65 | } 66 | Ast::Cond(mut cond) => { 67 | cond.clauses = cond 68 | .clauses 69 | .into_iter() 70 | .map(|CondClause { cond, body }| { 71 | Ok(CondClause { 72 | cond: Box::new(expand_macros_ast(*cond, menv)?), 73 | body: expand_macros_asts(body, menv)?, 74 | }) 75 | }) 76 | .collect::>>()?; 77 | 78 | Ast::Cond(cond) 79 | } 80 | Ast::Let(Let { 81 | sequential, 82 | proc_id, 83 | inits, 84 | body, 85 | }) => { 86 | let inits = inits 87 | .into_iter() 88 | .map(|(k, v)| { 89 | let v = expand_macros_ast(v, menv)?; 90 | Ok((k, v)) 91 | }) 92 | .collect::>>()?; 93 | let body = body 94 | .into_iter() 95 | .map(|body| expand_macros_ast(body, menv)) 96 | .collect::>>()?; 97 | 98 | Ast::Let(Let { 99 | sequential, 100 | proc_id, 101 | inits, 102 | body, 103 | }) 104 | } 105 | Ast::Begin(Begin { body }) => { 106 | let body = body 107 | .into_iter() 108 | .map(|body| expand_macros_ast(body, menv)) 109 | .collect::>>()?; 110 | Ast::Begin(Begin { body }) 111 | } 112 | Ast::Loop(Loop { inits, label, body }) => { 113 | let inits = inits 114 | .into_iter() 115 | .map(|(k, v)| { 116 | let v = expand_macros_ast(v, menv)?; 117 | Ok((k, v)) 118 | }) 119 | .collect::>>()?; 120 | let body = body 121 | .into_iter() 122 | .map(|body| expand_macros_ast(body, menv)) 123 | .collect::>>()?; 124 | Ast::Loop(Loop { inits, label, body }) 125 | } 126 | Ast::ListLiteral(values) => { 127 | let values = values 128 | .into_iter() 129 | .map(|value| expand_macros_ast(value, menv)) 130 | .collect::>>()?; 131 | Ast::ListLiteral(values) 132 | } 133 | Ast::ArrayLiteral(values, is_fixed) => { 134 | let values = values 135 | .into_iter() 136 | .map(|value| expand_macros_ast(value, menv)) 137 | .collect::>>()?; 138 | Ast::ArrayLiteral(values, is_fixed) 139 | } 140 | Ast::List(vs) => { 141 | if let Some(( 142 | AnnotatedAst { 143 | ast: Ast::Symbol(name), 144 | location: name_loc, 145 | ty: name_ty, 146 | }, 147 | args, 148 | )) = vs.split_first() 149 | { 150 | let mut args = args 151 | .iter() 152 | .map(|arg| expand_macros_ast(arg.clone(), menv)) 153 | .collect::>>()?; 154 | 155 | if let Some(mac) = menv.get(name) { 156 | let mut env = Environment::default(); 157 | let mut ty_env = Environment::default(); 158 | 159 | init_env(&mut env, &mut ty_env); 160 | 161 | for (name, value) in mac.args.iter().zip(args) { 162 | env.insert_var(name.clone(), Value::RawAst(value.clone())); 163 | } 164 | 165 | let result = eval_asts(&mac.body, &mut env); 166 | 167 | env.pop_local(); 168 | 169 | let result = get_last_result(result?); 170 | Ast::from_value(result)? 171 | } else { 172 | let name = AnnotatedAst { 173 | ast: Ast::Symbol(name.clone()), 174 | location: *name_loc, 175 | ty: name_ty.clone(), 176 | }; 177 | 178 | let mut vs = vec![name]; 179 | vs.append(&mut args); 180 | Ast::List(vs) 181 | } 182 | } else { 183 | let vs = vs 184 | .into_iter() 185 | .map(|v| expand_macros_ast(v, menv)) 186 | .collect::>>()?; 187 | Ast::List(vs) 188 | } 189 | } 190 | Ast::Lambda(Lambda { 191 | args, 192 | arg_types, 193 | body, 194 | }) => { 195 | let body = body 196 | .into_iter() 197 | .map(|ast| expand_macros_ast(ast, menv)) 198 | .collect::>>()?; 199 | Ast::Lambda(Lambda { 200 | args, 201 | arg_types, 202 | body, 203 | }) 204 | } 205 | Ast::As(expr, ty) => Ast::As(Box::new(expand_macros_ast(*expr, menv)?), ty), 206 | Ast::Ref(expr) => Ast::Ref(Box::new(expand_macros_ast(*expr, menv)?)), 207 | Ast::Symbol(_) 208 | | Ast::SymbolWithType(_, _) 209 | | Ast::Quoted(_) 210 | | Ast::Integer(_) 211 | | Ast::Float(_) 212 | | Ast::Boolean(_) 213 | | Ast::Char(_) 214 | | Ast::String(_) 215 | | Ast::Nil 216 | | Ast::Continue(_) 217 | | Ast::Include(_) 218 | | Ast::DefineStruct(_) => ast, 219 | }; 220 | Ok(AnnotatedAst { 221 | ast: result, 222 | location, 223 | ty, 224 | }) 225 | } 226 | 227 | pub fn expand_macros(asts: Program) -> Result { 228 | let mut menv = MacroEnv::new(); 229 | 230 | let asts = asts 231 | .into_iter() 232 | .map(|ast| expand_macros_ast(ast, &mut menv)) 233 | .collect::>>()?; 234 | 235 | Ok(asts) 236 | } 237 | -------------------------------------------------------------------------------- /src/lispi/pass.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | pub mod constant_folding; 4 | pub mod context_folding; 5 | pub mod immediate_unfolding; 6 | pub mod placing_on_memory; 7 | pub mod removing_duplicated_assignments; 8 | pub mod removing_redundant_assignments; 9 | pub mod removing_ref_and_deref; 10 | pub mod removing_uncalled_functions; 11 | pub mod tail_recursion; 12 | 13 | #[derive(Eq, PartialEq, Hash)] 14 | pub enum Optimize { 15 | ConstantFolding, 16 | ImmediateUnfolding, 17 | RemovingRedundantAssignments, 18 | TailRecursion, 19 | } 20 | 21 | impl Optimize { 22 | pub fn all() -> HashSet { 23 | use Optimize::*; 24 | HashSet::from([ 25 | ConstantFolding, 26 | ImmediateUnfolding, 27 | RemovingRedundantAssignments, 28 | TailRecursion, 29 | ]) 30 | } 31 | 32 | pub fn minimum() -> HashSet { 33 | use Optimize::*; 34 | HashSet::from([ImmediateUnfolding, TailRecursion]) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/lispi/pass/constant_folding.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use anyhow::Result; 4 | use rustc_hash::FxHashMap; 5 | 6 | use crate::lispi::ir::IrContext; 7 | 8 | use super::super::ir::basic_block::*; 9 | use super::super::ir::instruction::*; 10 | 11 | use Instruction as I; 12 | 13 | struct Context { 14 | env: FxHashMap, 15 | } 16 | 17 | fn remove_deadcode(fun: &Function, ir_ctx: &mut IrContext) -> Result<()> { 18 | fn register_as_used(used_vars: &mut HashSet, op: &Operand) { 19 | if let Operand::Variable(var) = op { 20 | used_vars.insert(var.clone()); 21 | } 22 | } 23 | 24 | let mut used_vars = HashSet::new(); 25 | 26 | // Look phi functions first because they reference back variables. 27 | for bb in fun.basic_blocks.iter().rev() { 28 | let bb = ir_ctx.bb_arena.get_mut(*bb).unwrap(); 29 | for AnnotatedInstr { inst, .. } in &bb.insts { 30 | if let Instruction::Phi(nodes) = inst { 31 | for (op, _) in nodes { 32 | register_as_used(&mut used_vars, op); 33 | } 34 | } 35 | } 36 | } 37 | 38 | for bb in fun.basic_blocks.iter().rev() { 39 | let mut result = Vec::new(); 40 | 41 | let bb = ir_ctx.bb_arena.get_mut(*bb).unwrap(); 42 | 43 | for AnnotatedInstr { 44 | result: var, 45 | inst, 46 | ty, 47 | tags: _, 48 | } in bb.insts.clone().into_iter().rev() 49 | { 50 | if let Instruction::Phi(_) = &inst { 51 | // Variables added in front. 52 | } else { 53 | for v in inst.collect_vars() { 54 | used_vars.insert(v.clone()); 55 | } 56 | } 57 | 58 | let used = inst.is_label() || inst.is_terminal() || used_vars.contains(&var); 59 | 60 | if !inst.is_removable() || used { 61 | result.push(AnnotatedInstr::new(var, inst, ty)); 62 | } 63 | } 64 | 65 | result.reverse(); 66 | bb.insts = result; 67 | } 68 | 69 | Ok(()) 70 | } 71 | 72 | fn fold_imm(ctx: &mut Context, op: Operand) -> Operand { 73 | match op { 74 | Operand::Variable(var) => { 75 | if let Some(imm) = ctx.env.get(&var.name) { 76 | Operand::Immediate(imm.clone()) 77 | } else { 78 | Operand::Variable(var) 79 | } 80 | } 81 | Operand::Immediate(_) => op, 82 | } 83 | } 84 | 85 | fn insert_imm(ctx: &mut Context, op: &Operand, var: &Variable) { 86 | match op { 87 | Operand::Immediate(imm) => { 88 | ctx.env.insert(var.name.clone(), imm.clone()); 89 | } 90 | Operand::Variable(_) => {} 91 | } 92 | } 93 | 94 | fn fold_constants_arith( 95 | ctx: &mut Context, 96 | var: &Variable, 97 | left: Operand, 98 | right: Operand, 99 | if_int: F1, 100 | if_else: F2, 101 | ) -> Instruction 102 | where 103 | F1: Fn(i32, i32) -> i32, 104 | F2: Fn(Operand, Operand) -> Instruction, 105 | { 106 | let folded_left = fold_imm(ctx, left); 107 | let folded_right = fold_imm(ctx, right); 108 | 109 | if let ( 110 | Operand::Immediate(Immediate::Integer(left)), 111 | Operand::Immediate(Immediate::Integer(right)), 112 | ) = (&folded_left, &folded_right) 113 | { 114 | let val = if_int(*left, *right); 115 | 116 | let op = Operand::Immediate(Immediate::Integer(val)); 117 | insert_imm(ctx, &op, var); 118 | I::Operand(op) 119 | } else { 120 | if_else(folded_left, folded_right) 121 | } 122 | } 123 | 124 | /// TODO: Commonalize with fold_constants_arith 125 | fn fold_constants_logical( 126 | ctx: &mut Context, 127 | var: &Variable, 128 | left: Operand, 129 | right: Operand, 130 | if_int: F1, 131 | if_else: F2, 132 | ) -> Instruction 133 | where 134 | F1: Fn(bool, bool) -> bool, 135 | F2: Fn(Operand, Operand) -> Instruction, 136 | { 137 | let folded_left = fold_imm(ctx, left); 138 | let folded_right = fold_imm(ctx, right); 139 | 140 | if let ( 141 | Operand::Immediate(Immediate::Boolean(left)), 142 | Operand::Immediate(Immediate::Boolean(right)), 143 | ) = (&folded_left, &folded_right) 144 | { 145 | let val = if_int(*left, *right); 146 | 147 | let op = Operand::Immediate(Immediate::Boolean(val)); 148 | insert_imm(ctx, &op, var); 149 | I::Operand(op) 150 | } else { 151 | if_else(folded_left, folded_right) 152 | } 153 | } 154 | 155 | fn fold_constants_insts(fun: &Function, ctx: &mut Context, ir_ctx: &mut IrContext) -> Result<()> { 156 | for bb in &fun.basic_blocks { 157 | let bb = ir_ctx.bb_arena.get_mut(*bb).unwrap(); 158 | 159 | let mut result = Vec::new(); 160 | 161 | for AnnotatedInstr { 162 | result: var, 163 | inst, 164 | ty, 165 | tags: _, 166 | } in bb.insts.clone() 167 | { 168 | let inst = match inst { 169 | I::Operand(Operand::Immediate(imm)) => { 170 | ctx.env.insert(var.name.clone(), imm.clone()); 171 | Some(I::Operand(Operand::Immediate(imm))) 172 | } 173 | I::Operand(op @ Operand::Variable(_)) => { 174 | let op = fold_imm(ctx, op); 175 | insert_imm(ctx, &op, &var); 176 | Some(I::Operand(op)) 177 | } 178 | 179 | I::Branch { 180 | cond, 181 | then_label, 182 | else_label, 183 | then_bb, 184 | else_bb, 185 | } => { 186 | let cond = fold_imm(ctx, cond); 187 | Some(I::Branch { 188 | cond, 189 | then_label, 190 | else_label, 191 | then_bb, 192 | else_bb, 193 | }) 194 | } 195 | 196 | I::Alloca { ty, count } => Some(I::Alloca { 197 | ty, 198 | count: fold_imm(ctx, count), 199 | }), 200 | 201 | I::Add(left, right) => Some(fold_constants_arith( 202 | ctx, 203 | &var, 204 | left, 205 | right, 206 | |l, r| l + r, 207 | I::Add, 208 | )), 209 | I::Sub(left, right) => Some(fold_constants_arith( 210 | ctx, 211 | &var, 212 | left, 213 | right, 214 | |l, r| l - r, 215 | I::Sub, 216 | )), 217 | I::Mul(left, right) => Some(fold_constants_arith( 218 | ctx, 219 | &var, 220 | left, 221 | right, 222 | |l, r| l * r, 223 | I::Mul, 224 | )), 225 | I::Div(left, right) => Some(fold_constants_arith( 226 | ctx, 227 | &var, 228 | left, 229 | right, 230 | |l, r| l / r, 231 | I::Div, 232 | )), 233 | I::Mod(left, right) => Some(fold_constants_arith( 234 | ctx, 235 | &var, 236 | left, 237 | right, 238 | |l, r| l % r, 239 | I::Mod, 240 | )), 241 | I::And(left, right) => Some(fold_constants_logical( 242 | ctx, 243 | &var, 244 | left, 245 | right, 246 | |l, r| l & r, 247 | I::And, 248 | )), 249 | I::Or(left, right) => Some(fold_constants_logical( 250 | ctx, 251 | &var, 252 | left, 253 | right, 254 | |l, r| l | r, 255 | I::Or, 256 | )), 257 | I::Shift(op, left, right) => Some(fold_constants_arith( 258 | ctx, 259 | &var, 260 | left, 261 | right, 262 | |l, r| { 263 | // TODO: The behavior of shift may be difference between host machine and target machine. 264 | // Take care this. 265 | match &op { 266 | ShiftOperator::LogicalLeft => l << r, 267 | ShiftOperator::LogicalRight => { 268 | let ul = l as u32; 269 | let ur = r; 270 | (ul >> ur) as i32 271 | } 272 | } 273 | }, 274 | |l, r| I::Shift(op, l, r), 275 | )), 276 | I::Store(addr, value) => { 277 | let addr = fold_imm(ctx, addr); 278 | let value = fold_imm(ctx, value); 279 | Some(I::Store(addr, value)) 280 | } 281 | I::LoadElement { addr, ty, index } => Some(I::LoadElement { 282 | addr: fold_imm(ctx, addr), 283 | ty, 284 | index: fold_imm(ctx, index), 285 | }), 286 | I::StoreElement { 287 | addr, 288 | ty, 289 | index, 290 | value, 291 | } => Some(I::StoreElement { 292 | addr: fold_imm(ctx, addr), 293 | ty, 294 | index: fold_imm(ctx, index), 295 | value, 296 | }), 297 | I::Not(op) => { 298 | let op = fold_imm(ctx, op); 299 | if let Operand::Immediate(imm) = &op { 300 | match imm { 301 | Immediate::Boolean(op) => { 302 | let not_op = Operand::Immediate(Immediate::Boolean(!op)); 303 | insert_imm(ctx, ¬_op, &var); 304 | Some(I::Operand(not_op)) 305 | } 306 | Immediate::Integer(op) => Some(I::Operand((op == &0).into())), 307 | _ => Some(I::Not(op)), 308 | } 309 | } else { 310 | Some(I::Not(op)) 311 | } 312 | } 313 | I::Cmp(op, left, right) => { 314 | let left = fold_imm(ctx, left); 315 | let right = fold_imm(ctx, right); 316 | 317 | if let ( 318 | Operand::Immediate(Immediate::Integer(left)), 319 | Operand::Immediate(Immediate::Integer(right)), 320 | ) = (&left, &right) 321 | { 322 | let val = match op { 323 | CmpOperator::Eq => left == right, 324 | CmpOperator::SGE => left >= right, 325 | CmpOperator::SLE => left <= right, 326 | CmpOperator::SGT => left < right, 327 | CmpOperator::SLT => left > right, 328 | }; 329 | 330 | let op = Operand::Immediate(Immediate::Boolean(val)); 331 | insert_imm(ctx, &op, &var); 332 | Some(I::Operand(op)) 333 | } else { 334 | Some(I::Cmp(op, left, right)) 335 | } 336 | } 337 | I::Call { fun, args } => { 338 | let fun = fold_imm(ctx, fun); 339 | let args = args.into_iter().map(|arg| fold_imm(ctx, arg)).collect(); 340 | 341 | Some(I::Call { fun, args }) 342 | } 343 | I::SysCall { number, args } => { 344 | let number = fold_imm(ctx, number); 345 | let args = args.into_iter().map(|arg| fold_imm(ctx, arg)).collect(); 346 | Some(I::SysCall { number, args }) 347 | } 348 | I::Ret(op) => Some(I::Ret(fold_imm(ctx, op))), 349 | 350 | I::Reference(op) => Some(I::Reference(fold_imm(ctx, op))), 351 | I::Dereference(op) => Some(I::Dereference(fold_imm(ctx, op))), 352 | 353 | I::Jump(_, _) | I::Phi(_) | I::Label(_) | I::Nop => Some(inst), 354 | }; 355 | 356 | if let Some(inst) = inst { 357 | result.push(AnnotatedInstr::new(var, inst, ty)); 358 | } 359 | } 360 | 361 | bb.insts = result; 362 | } 363 | 364 | Ok(()) 365 | } 366 | 367 | pub fn optimize(program: &IrProgram, ir_ctx: &mut IrContext) -> Result<()> { 368 | let mut ctx = Context { 369 | env: FxHashMap::default(), 370 | }; 371 | for fun in &program.funcs { 372 | fold_constants_insts(fun, &mut ctx, ir_ctx)?; 373 | } 374 | 375 | for fun in &program.funcs { 376 | remove_deadcode(fun, ir_ctx)?; 377 | } 378 | 379 | Ok(()) 380 | } 381 | -------------------------------------------------------------------------------- /src/lispi/pass/context_folding.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! This step has only memo and is not implemented. 3 | //! 4 | //! This step removes contextual arguments of closures. 5 | //! For example, let's consider the following code. 6 | //! 7 | //! ```lisp 8 | //! (define f (lambda () 42)) 9 | //! (define g (lambda () (f)) 10 | //! (g) 11 | //! ``` 12 | //! 13 | //! Note that functions `f` and `g` are defined at the root context, and function `g` don't know function `f`. 14 | //! This code is compiled to, 15 | //! 16 | //! ```text 17 | //! function f()() { ret 42 } 18 | //! 19 | //! function g()(ff) { 20 | //! %var = call %ff 21 | //! ret %var 22 | //! } 23 | //! 24 | //! function main()() { 25 | //! call g, f 26 | //! } 27 | //! ``` 28 | //! 29 | //! Function `g` takes an contextual argument `f` to know outer variables. 30 | //! The value `f` is constant and can be folded in this step. 31 | //! 32 | //! ```text 33 | //! function f()() { ret 42 } 34 | //! 35 | //! function g()() { 36 | //! %var = call f 37 | //! ret %var 38 | //! } 39 | //! 40 | //! function main()() { 41 | //! call g 42 | //! } 43 | //! ``` 44 | //! 45 | //! Now, values satisfying the following conditions are folded. 46 | //! 47 | //! 1. Value is a function label. 48 | //! 2. All of values passed to functions are equaled. 49 | //! 50 | -------------------------------------------------------------------------------- /src/lispi/pass/immediate_unfolding.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use crate::lispi::{ 4 | ir::{basic_block::*, instruction::*, IrContext}, 5 | ty::Type, 6 | unique_generator::UniqueGenerator, 7 | }; 8 | 9 | use Instruction as I; 10 | 11 | enum ImmediateUnfoldingMode { 12 | None, 13 | Left, 14 | Both, 15 | } 16 | 17 | #[derive(Default)] 18 | struct Context { 19 | var_gen: UniqueGenerator, 20 | } 21 | 22 | fn unfold_immediate(op: Operand, insts: &mut Vec, ctx: &mut Context) -> Operand { 23 | match op { 24 | Operand::Variable(_) => op, 25 | Operand::Immediate(_) => { 26 | let var = Variable { 27 | // Set unique ID in entire program 28 | name: format!("var{}.uf", ctx.var_gen.gen()), 29 | }; 30 | insts.push(AnnotatedInstr { 31 | result: var.clone(), 32 | inst: I::Operand(op), 33 | ty: Type::None, 34 | tags: Vec::new(), 35 | }); 36 | Operand::Variable(var) 37 | } 38 | } 39 | } 40 | 41 | fn unfold_immediate_arith( 42 | left: Operand, 43 | right: Operand, 44 | constructor: F, 45 | insts: &mut Vec, 46 | mode: ImmediateUnfoldingMode, 47 | ctx: &mut Context, 48 | ) -> Instruction 49 | where 50 | F: Fn(Operand, Operand) -> Instruction, 51 | { 52 | use ImmediateUnfoldingMode as m; 53 | 54 | match mode { 55 | m::None => constructor(left, right), 56 | m::Left => { 57 | let unfolded_left = unfold_immediate(left, insts, ctx); 58 | constructor(unfolded_left, right) 59 | } 60 | m::Both => { 61 | let unfolded_right = unfold_immediate(right, insts, ctx); 62 | let unfolded_left = unfold_immediate(left, insts, ctx); 63 | constructor(unfolded_left, unfolded_right) 64 | } 65 | } 66 | } 67 | 68 | pub fn optimize( 69 | program: &IrProgram, 70 | ir_ctx: &mut IrContext, 71 | unfolding_for_riscv: bool, 72 | ) -> Result<()> { 73 | let mut ctx = Context::default(); 74 | 75 | for fun in &program.funcs { 76 | for bb in &fun.basic_blocks { 77 | let bb = ir_ctx.bb_arena.get_mut(*bb).unwrap(); 78 | 79 | let mut insts = Vec::new(); 80 | 81 | for AnnotatedInstr { 82 | result, 83 | inst, 84 | ty, 85 | tags, 86 | } in bb.insts.drain(..) 87 | { 88 | let mode = if unfolding_for_riscv { 89 | ImmediateUnfoldingMode::Left 90 | } else { 91 | ImmediateUnfoldingMode::None 92 | }; 93 | 94 | let inst = match inst { 95 | I::Operand(_) 96 | | I::Alloca { .. } 97 | | I::Jump(_, _) 98 | | I::Call { .. } 99 | | I::SysCall { .. } 100 | | I::Phi(_) 101 | | I::Label(_) 102 | | I::Nop => Some(inst), 103 | I::Branch { 104 | cond, 105 | then_label, 106 | else_label, 107 | then_bb, 108 | else_bb, 109 | } => { 110 | let cond = if unfolding_for_riscv { 111 | unfold_immediate(cond, &mut insts, &mut ctx) 112 | } else { 113 | cond 114 | }; 115 | Some(I::Branch { 116 | cond, 117 | then_label, 118 | else_label, 119 | then_bb, 120 | else_bb, 121 | }) 122 | } 123 | 124 | I::Add(left, right) => Some(unfold_immediate_arith( 125 | left, 126 | right, 127 | I::Add, 128 | &mut insts, 129 | mode, 130 | &mut ctx, 131 | )), 132 | I::Sub(left, right) => Some(unfold_immediate_arith( 133 | left, 134 | right, 135 | I::Sub, 136 | &mut insts, 137 | ImmediateUnfoldingMode::Both, 138 | &mut ctx, 139 | )), 140 | I::Mul(left, right) => Some(unfold_immediate_arith( 141 | left, 142 | right, 143 | I::Mul, 144 | &mut insts, 145 | ImmediateUnfoldingMode::Both, 146 | &mut ctx, 147 | )), 148 | I::Div(left, right) => Some(unfold_immediate_arith( 149 | left, 150 | right, 151 | I::Div, 152 | &mut insts, 153 | ImmediateUnfoldingMode::Both, 154 | &mut ctx, 155 | )), 156 | I::Mod(left, right) => Some(unfold_immediate_arith( 157 | left, 158 | right, 159 | I::Mod, 160 | &mut insts, 161 | ImmediateUnfoldingMode::Both, 162 | &mut ctx, 163 | )), 164 | I::And(left, right) => Some(unfold_immediate_arith( 165 | left, 166 | right, 167 | I::And, 168 | &mut insts, 169 | mode, 170 | &mut ctx, 171 | )), 172 | I::Or(left, right) => Some(unfold_immediate_arith( 173 | left, 174 | right, 175 | I::Or, 176 | &mut insts, 177 | mode, 178 | &mut ctx, 179 | )), 180 | 181 | I::Shift(op, left, right) => Some(unfold_immediate_arith( 182 | left, 183 | right, 184 | |l, r| I::Shift(op, l, r), 185 | &mut insts, 186 | mode, 187 | &mut ctx, 188 | )), 189 | 190 | I::Store(addr, value) => { 191 | if unfolding_for_riscv { 192 | let addr = unfold_immediate(addr, &mut insts, &mut ctx); 193 | let value = unfold_immediate(value, &mut insts, &mut ctx); 194 | Some(I::Store(addr, value)) 195 | } else { 196 | Some(I::Store(addr, value)) 197 | } 198 | } 199 | I::LoadElement { addr, ty, index } => Some(I::LoadElement { 200 | addr: unfold_immediate(addr, &mut insts, &mut ctx), 201 | ty, 202 | index, 203 | }), 204 | I::StoreElement { 205 | addr, 206 | ty, 207 | index, 208 | value, 209 | } => Some(I::StoreElement { 210 | addr: unfold_immediate(addr, &mut insts, &mut ctx), 211 | ty, 212 | index, 213 | value: unfold_immediate(value, &mut insts, &mut ctx), 214 | }), 215 | // The argument of Not must be an operand. 216 | I::Not(_) => Some(inst), 217 | I::Cmp(op, left, right) => Some(unfold_immediate_arith( 218 | left, 219 | right, 220 | |l, r| I::Cmp(op, l, r), 221 | &mut insts, 222 | ImmediateUnfoldingMode::Both, 223 | &mut ctx, 224 | )), 225 | I::Ret(op) => { 226 | if unfolding_for_riscv { 227 | Some(I::Ret(unfold_immediate(op, &mut insts, &mut ctx))) 228 | } else { 229 | Some(I::Ret(op)) 230 | } 231 | } 232 | I::Reference(op) => { 233 | Some(I::Reference(unfold_immediate(op, &mut insts, &mut ctx))) 234 | } 235 | I::Dereference(op) => { 236 | Some(I::Dereference(unfold_immediate(op, &mut insts, &mut ctx))) 237 | } 238 | }; 239 | 240 | if let Some(inst) = inst { 241 | insts.push(AnnotatedInstr { 242 | result, 243 | inst, 244 | ty, 245 | tags, 246 | }) 247 | } 248 | } 249 | 250 | bb.insts = insts; 251 | } 252 | } 253 | 254 | Ok(()) 255 | } 256 | -------------------------------------------------------------------------------- /src/lispi/pass/placing_on_memory.rs: -------------------------------------------------------------------------------- 1 | //! Place `ref`erenced variables on the stack memory. 2 | //! And replace `deref` to `loadelement`. 3 | //! 4 | //! ## Example of `ref` 5 | //! 6 | //! Translate: 7 | //! ```txt 8 | //! ; A: Initialize 9 | //! %var1 = inst 10 | //! ; B: Reference the variable 11 | //! %var2 = add %var1, %var1 12 | //! ; C: Get address of the variable 13 | //! %var3 = ref %var1 14 | //! ``` 15 | //! to: 16 | //! ```txt 17 | //! %var1-pom0 = inst 18 | //! %var1 = alloca Int, 1 19 | //! store %var1, %var1-pom 20 | //! 21 | //! %var1-pom1 = load %var1 22 | //! %var2 = add %var1-pom1, %var1-pom1 23 | //! 24 | //! %var3 = %var1 25 | //! ``` 26 | //! 27 | //! ## Example of `deref` 28 | //! 29 | //! Translate: 30 | //! ```txt 31 | //! %var1 = deref %var0 32 | //! ``` 33 | //! 34 | //! to: 35 | //! ```txt 36 | //! %var1 = loadelement %var0, (Type of dereferenced %var0), 0 37 | //! ``` 38 | //! 39 | 40 | use anyhow::Result; 41 | use rustc_hash::{FxHashMap, FxHashSet}; 42 | 43 | use crate::lispi::{ 44 | ir::{ 45 | basic_block::IrProgram, 46 | instruction::{AnnotatedInstr, Instruction, Operand, Variable}, 47 | IrContext, 48 | }, 49 | ty::Type, 50 | unique_generator::UniqueGenerator, 51 | }; 52 | 53 | pub fn optimize(program: &IrProgram, ctx: &mut IrContext) -> Result<()> { 54 | for fun in &program.funcs { 55 | let mut vars_on_memory = FxHashSet::default(); 56 | for inst in fun.walk_instructions(&ctx.bb_arena) { 57 | if let Instruction::Reference(op) = &inst.inst { 58 | if let Operand::Variable(var) = op { 59 | vars_on_memory.insert(var.clone()); 60 | } else { 61 | panic!("Cannot get address of immediate now.") 62 | } 63 | } 64 | } 65 | 66 | let mut gen = UniqueGenerator::new("pom".to_string()); 67 | 68 | // 69 | // Processing of `ref` 70 | // 71 | for bb in &fun.basic_blocks { 72 | let bb = ctx.bb_arena.get_mut(*bb).unwrap(); 73 | 74 | let mut result = Vec::new(); 75 | 76 | for AnnotatedInstr { 77 | result: result_var, 78 | inst, 79 | ty, 80 | tags: _, 81 | } in bb.insts.clone() 82 | { 83 | if vars_on_memory.contains(&result_var) { 84 | // 85 | // A: Initialize 86 | // 87 | let tmp_var = result_var.clone().with_suffix(&gen.gen_string()); 88 | result.push(AnnotatedInstr::new(tmp_var.clone(), inst, ty)); 89 | 90 | result.push(AnnotatedInstr::new( 91 | result_var.clone(), 92 | Instruction::Alloca { 93 | // TODO: Set type 94 | ty: Type::Int, 95 | count: 1.into(), 96 | }, 97 | Type::None, 98 | )); 99 | 100 | result.push(AnnotatedInstr::new( 101 | Variable::empty(), 102 | Instruction::Store(result_var.into(), tmp_var.into()), 103 | Type::None, 104 | )); 105 | } else { 106 | let referenced_var = 107 | if let Instruction::Reference(Operand::Variable(var)) = &inst { 108 | if vars_on_memory.contains(var) { 109 | Some(var) 110 | } else { 111 | None 112 | } 113 | } else { 114 | None 115 | }; 116 | 117 | if let Some(ref_var) = referenced_var { 118 | // 119 | // C: Get address of the variable 120 | // 121 | result.push(AnnotatedInstr::new( 122 | result_var, 123 | Instruction::Operand(ref_var.clone().into()), 124 | ty, 125 | )); 126 | } else { 127 | // 128 | // B: Reference the variable 129 | // 130 | 131 | let mut vmap = FxHashMap::default(); 132 | 133 | for var in inst.collect_vars() { 134 | if !vars_on_memory.contains(var) { 135 | continue; 136 | } 137 | 138 | let tmp_var = result_var.clone().with_suffix(&gen.gen_string()); 139 | 140 | result.push(AnnotatedInstr::new( 141 | tmp_var.clone(), 142 | Instruction::LoadElement { 143 | addr: var.clone().into(), 144 | ty: Type::Int, 145 | index: 0.into(), 146 | }, 147 | Type::None, 148 | )); 149 | 150 | vmap.insert(var.clone(), tmp_var); 151 | } 152 | 153 | result.push(AnnotatedInstr::new(result_var, inst.replace_var(&vmap), ty)); 154 | } 155 | } 156 | } 157 | 158 | bb.insts = result; 159 | } 160 | 161 | // 162 | // Processing of `deref` 163 | // 164 | for bb in &fun.basic_blocks { 165 | let bb = ctx.bb_arena.get_mut(*bb).unwrap(); 166 | 167 | let mut result = Vec::new(); 168 | 169 | for AnnotatedInstr { 170 | result: result_var, 171 | inst, 172 | ty, 173 | tags: _, 174 | } in bb.insts.clone() 175 | { 176 | let inst = if let Instruction::Dereference(op) = inst { 177 | Instruction::LoadElement { 178 | addr: op, 179 | ty: ty.clone(), 180 | index: 0.into(), 181 | } 182 | } else { 183 | inst 184 | }; 185 | result.push(AnnotatedInstr::new(result_var, inst, ty)); 186 | } 187 | 188 | bb.insts = result; 189 | } 190 | } 191 | 192 | Ok(()) 193 | } 194 | -------------------------------------------------------------------------------- /src/lispi/pass/removing_duplicated_assignments.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use rustc_hash::FxHashMap; 3 | 4 | use crate::lispi::ir::IrContext; 5 | 6 | use super::super::ir::basic_block::*; 7 | use super::super::ir::instruction::*; 8 | 9 | use Instruction as I; 10 | 11 | struct Context { 12 | assign_var_map: FxHashMap, 13 | assign_imm_map: FxHashMap, 14 | 15 | replace_var_map: FxHashMap, 16 | } 17 | 18 | fn remove_duplicated_assignments(fun: &Function, ir_ctx: &mut IrContext) -> Result<()> { 19 | let mut ctx = Context { 20 | assign_imm_map: FxHashMap::default(), 21 | assign_var_map: FxHashMap::default(), 22 | 23 | replace_var_map: FxHashMap::default(), 24 | }; 25 | 26 | // Create var/imm and var mapping 27 | 28 | for bb in &fun.basic_blocks { 29 | let bb = ir_ctx.bb_arena.get_mut(*bb).unwrap(); 30 | 31 | for AnnotatedInstr { 32 | result: result_var, 33 | inst, 34 | ty: _, 35 | tags: _, 36 | } in &bb.insts 37 | { 38 | match inst { 39 | I::Operand(Operand::Variable(var)) => { 40 | if let Some(rvar) = ctx.assign_var_map.get(var) { 41 | ctx.replace_var_map.insert(result_var.clone(), rvar.clone()); 42 | } else { 43 | ctx.assign_var_map.insert(var.clone(), result_var.clone()); 44 | } 45 | } 46 | I::Operand(Operand::Immediate(imm)) => { 47 | if let Some(rvar) = ctx.assign_imm_map.get(imm) { 48 | ctx.replace_var_map.insert(result_var.clone(), rvar.clone()); 49 | } else { 50 | ctx.assign_imm_map.insert(imm.clone(), result_var.clone()); 51 | } 52 | } 53 | 54 | _ => {} 55 | }; 56 | } 57 | } 58 | 59 | // Replace variables 60 | 61 | for bb in &fun.basic_blocks { 62 | let bb = ir_ctx.bb_arena.get_mut(*bb).unwrap(); 63 | 64 | let mut result = Vec::new(); 65 | 66 | for AnnotatedInstr { 67 | result: result_var, 68 | inst, 69 | ty, 70 | tags: _, 71 | } in bb.insts.clone() 72 | { 73 | if ctx.replace_var_map.contains_key(&result_var) { 74 | continue; 75 | } 76 | 77 | let inst = inst.replace_var(&ctx.replace_var_map); 78 | result.push(AnnotatedInstr::new(result_var, inst, ty)); 79 | } 80 | 81 | bb.insts = result; 82 | } 83 | 84 | Ok(()) 85 | } 86 | 87 | pub fn optimize(program: &IrProgram, ir_ctx: &mut IrContext) -> Result<()> { 88 | for fun in &program.funcs { 89 | remove_duplicated_assignments(fun, ir_ctx)?; 90 | } 91 | 92 | Ok(()) 93 | } 94 | -------------------------------------------------------------------------------- /src/lispi/pass/removing_redundant_assignments.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! This step removes redundant assignments such as `%var1 = %var2`. 3 | //! It propagates assigned variables to the following instructions. 4 | //! 5 | 6 | use anyhow::Result; 7 | use itertools::Itertools; 8 | use rustc_hash::FxHashMap; 9 | use rustc_hash::FxHashSet; 10 | 11 | use crate::lispi::ir::IrContext; 12 | 13 | use super::super::ir::basic_block::*; 14 | use super::super::ir::instruction::*; 15 | 16 | use Instruction as I; 17 | 18 | struct Context { 19 | replace_var_map: FxHashMap, 20 | } 21 | 22 | fn remove_redundant_assignments(fun: &Function, ir_ctx: &mut IrContext) -> Result<()> { 23 | let mut ctx = Context { 24 | replace_var_map: FxHashMap::default(), 25 | }; 26 | 27 | let mut excluded_vars = FxHashSet::default(); 28 | 29 | // Don't remove variables in phi instructions. 30 | for inst in fun.walk_instructions(&ir_ctx.bb_arena) { 31 | if let I::Phi(nodes) = &inst.inst { 32 | for (op, _) in nodes { 33 | if let Operand::Variable(var) = op { 34 | excluded_vars.insert(var.clone()); 35 | } 36 | } 37 | } 38 | } 39 | 40 | for bb in &fun.basic_blocks { 41 | let bb = ir_ctx.bb_arena.get_mut(*bb).unwrap(); 42 | 43 | bb.insts = bb 44 | .insts 45 | .drain(..) 46 | .filter_map( 47 | |AnnotatedInstr { 48 | result: result_var, 49 | inst, 50 | ty, 51 | tags, 52 | }| { 53 | if let I::Operand(Operand::Variable(var)) = &inst { 54 | let var = ctx.replace_var_map.get(var).unwrap_or(var); 55 | 56 | // Don't remove loading arguments. 57 | let in_args = fun.args.iter().any(|(name, _)| name == &var.name); 58 | if !in_args && !excluded_vars.contains(&result_var) { 59 | ctx.replace_var_map.insert(result_var, var.clone()); 60 | return None; 61 | } 62 | } 63 | 64 | Some(AnnotatedInstr { 65 | result: result_var, 66 | inst: inst.replace_var(&ctx.replace_var_map), 67 | ty, 68 | tags, 69 | }) 70 | }, 71 | ) 72 | .collect_vec(); 73 | } 74 | 75 | Ok(()) 76 | } 77 | 78 | pub fn optimize(program: &IrProgram, ir_ctx: &mut IrContext) -> Result<()> { 79 | for fun in &program.funcs { 80 | remove_redundant_assignments(fun, ir_ctx)?; 81 | } 82 | 83 | Ok(()) 84 | } 85 | -------------------------------------------------------------------------------- /src/lispi/pass/removing_ref_and_deref.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Removes reference and dereference instructions for the same variable. 3 | //! This also removes loadelement and reference for the same variable. 4 | //! 5 | //! For example, following instructions: 6 | //! ``` 7 | //! %var1 = ref %var0 8 | //! ; some instructions... 9 | //! %var2 = deref %var1 10 | //! ```` 11 | //! 12 | //! Are converted to: 13 | //! ``` 14 | //! ; some instructions... 15 | //! %var2 = %var0 16 | //! ``` 17 | //! 18 | //! Following instructions: 19 | //! ``` 20 | //! %var1 = loadelement %var0, int, 4 21 | //! ; some instructions... 22 | //! %var2 = deref %var1 23 | //! ```` 24 | //! 25 | //! Are converted to: 26 | //! ``` 27 | //! ; some instructions... 28 | //! %var2 = addi %var0, 4 29 | //! ``` 30 | //! 31 | 32 | use anyhow::Result; 33 | use itertools::Itertools; 34 | use rustc_hash::FxHashMap; 35 | 36 | use crate::lispi::ir::{ 37 | basic_block::{Function, IrProgram}, 38 | instruction::{AnnotatedInstr, Instruction, Operand}, 39 | IrContext, 40 | }; 41 | 42 | pub fn remove_ref_and_deref(fun: &Function, ir_ctx: &mut IrContext) -> Result<()> { 43 | let mut ref_mapping_candidates = FxHashMap::default(); 44 | let mut le_mapping_candidates = FxHashMap::default(); 45 | 46 | for inst in fun.walk_instructions(&ir_ctx.bb_arena) { 47 | if let Instruction::Reference(Operand::Variable(op)) = &inst.inst { 48 | ref_mapping_candidates.insert(inst.result.clone(), op.clone()); 49 | } else if let Instruction::LoadElement { 50 | addr: Operand::Variable(addr), 51 | ty: _, 52 | index, 53 | } = &inst.inst 54 | { 55 | le_mapping_candidates.insert(inst.result.clone(), (addr.clone(), index.clone())); 56 | } 57 | } 58 | 59 | let mut ref_mapping = FxHashMap::default(); 60 | let mut le_mapping = FxHashMap::default(); 61 | 62 | for inst in fun.walk_instructions(&ir_ctx.bb_arena) { 63 | if let Instruction::Dereference(Operand::Variable(op)) = &inst.inst { 64 | if let Some(referenced_var) = ref_mapping_candidates.get(&op) { 65 | ref_mapping.insert(op.clone(), referenced_var.clone()); 66 | } 67 | } else if let Instruction::Reference(Operand::Variable(op)) = &inst.inst { 68 | if let Some(referenced_var) = le_mapping_candidates.get(&op) { 69 | le_mapping.insert(op.clone(), referenced_var.clone()); 70 | } 71 | } 72 | } 73 | 74 | for bb in &fun.basic_blocks { 75 | let bb = ir_ctx.bb_arena.get_mut(*bb).unwrap(); 76 | 77 | bb.insts = bb 78 | .insts 79 | .drain(..) 80 | .filter_map( 81 | |AnnotatedInstr { 82 | result: result_var, 83 | inst, 84 | ty, 85 | tags, 86 | }| { 87 | // Remove ref and deref 88 | 89 | match inst { 90 | Instruction::Dereference(Operand::Variable(var)) => { 91 | if let Some(referenced_var) = ref_mapping.get(&var) { 92 | Some(AnnotatedInstr { 93 | result: result_var, 94 | inst: Instruction::Operand(Operand::Variable( 95 | referenced_var.clone(), 96 | )), 97 | ty, 98 | tags, 99 | }) 100 | } else { 101 | Some(AnnotatedInstr { 102 | result: result_var, 103 | inst: Instruction::Dereference(Operand::Variable(var)), 104 | ty, 105 | tags, 106 | }) 107 | } 108 | } 109 | Instruction::Reference(Operand::Variable(var)) => { 110 | if ref_mapping.contains_key(&result_var) { 111 | None 112 | } else { 113 | Some(AnnotatedInstr { 114 | result: result_var, 115 | inst: Instruction::Reference(Operand::Variable(var)), 116 | ty, 117 | tags, 118 | }) 119 | } 120 | } 121 | _ => Some(AnnotatedInstr { 122 | result: result_var, 123 | inst, 124 | ty, 125 | tags, 126 | }), 127 | } 128 | }, 129 | ) 130 | .filter_map( 131 | |AnnotatedInstr { 132 | result: result_var, 133 | inst, 134 | ty, 135 | tags, 136 | }| { 137 | // Remove loadelement and ref 138 | 139 | match inst { 140 | Instruction::Reference(Operand::Variable(var)) => { 141 | if let Some((referenced_var, offset)) = le_mapping.get(&var) { 142 | Some(AnnotatedInstr { 143 | result: result_var, 144 | inst: Instruction::Add( 145 | Operand::Variable(referenced_var.clone()), 146 | offset.clone(), 147 | ), 148 | ty, 149 | tags, 150 | }) 151 | } else { 152 | Some(AnnotatedInstr { 153 | result: result_var, 154 | inst: Instruction::Reference(Operand::Variable(var)), 155 | ty, 156 | tags, 157 | }) 158 | } 159 | } 160 | Instruction::LoadElement { 161 | addr, 162 | ty: ety, 163 | index, 164 | } => { 165 | if le_mapping.contains_key(&result_var) { 166 | None 167 | } else { 168 | Some(AnnotatedInstr { 169 | result: result_var, 170 | inst: Instruction::LoadElement { 171 | addr, 172 | ty: ety, 173 | index, 174 | }, 175 | ty, 176 | tags, 177 | }) 178 | } 179 | } 180 | _ => Some(AnnotatedInstr { 181 | result: result_var, 182 | inst, 183 | ty, 184 | tags, 185 | }), 186 | } 187 | }, 188 | ) 189 | .collect_vec(); 190 | } 191 | 192 | Ok(()) 193 | } 194 | 195 | pub fn optimize(program: &IrProgram, ir_ctx: &mut IrContext) -> Result<()> { 196 | for fun in &program.funcs { 197 | remove_ref_and_deref(fun, ir_ctx)?; 198 | } 199 | 200 | Ok(()) 201 | } 202 | -------------------------------------------------------------------------------- /src/lispi/pass/removing_uncalled_functions.rs: -------------------------------------------------------------------------------- 1 | use rustc_hash::FxHashMap; 2 | 3 | use crate::lispi::ir::{ 4 | basic_block::IrProgram, 5 | instruction::{Immediate, Instruction, Operand}, 6 | IrContext, 7 | }; 8 | 9 | fn is_called_from_main( 10 | calling_relations: &FxHashMap>, 11 | func_name: &String, 12 | ) -> bool { 13 | fn is_called_internal( 14 | calling_relations: &FxHashMap>, 15 | func_name: &String, 16 | current: &String, 17 | ) -> bool { 18 | if current == func_name { 19 | true 20 | } else if let Some(funcs) = calling_relations.get(current) { 21 | // TODO: Fix not to check again functions that is checked once to avoid infinite loop. 22 | funcs 23 | .iter() 24 | .any(|f| is_called_internal(calling_relations, func_name, f)) 25 | } else { 26 | false 27 | } 28 | } 29 | 30 | is_called_internal(calling_relations, func_name, &"main".to_string()) 31 | } 32 | 33 | pub fn optimize(program: IrProgram, ctx: &mut IrContext) -> IrProgram { 34 | // Map caller function to called functions 35 | let mut calling_relations: FxHashMap> = FxHashMap::default(); 36 | 37 | for func in &program.funcs { 38 | for inst in func.walk_instructions(&ctx.bb_arena) { 39 | if let Instruction::Call { 40 | fun: Operand::Immediate(Immediate::Label(name)), 41 | .. 42 | } = &inst.inst 43 | { 44 | let name = name.name.clone(); 45 | if let Some(called_funcs) = calling_relations.get_mut(&func.name) { 46 | called_funcs.push(name); 47 | } else { 48 | calling_relations.insert(func.name.clone(), vec![name]); 49 | } 50 | } 51 | } 52 | } 53 | 54 | let IrProgram { funcs, structs } = program; 55 | IrProgram { 56 | funcs: funcs 57 | .into_iter() 58 | .filter(|func| { 59 | // Don't remove lambdas conservatively 60 | func.is_lambda || is_called_from_main(&calling_relations, &func.name) 61 | }) 62 | .collect(), 63 | structs, 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/lispi/pass/tail_recursion.rs: -------------------------------------------------------------------------------- 1 | //! Try to optimize tail recursion for body. 2 | //! 3 | //! This removes tail recursion such as following forms. 4 | //! 5 | //! ```lisp 6 | //! (let loop ((i 0)) (if (< i 1000 7 | //! (begin 8 | //! (loop (+ i 1))))) 9 | //! ``` 10 | //! 11 | //! This is converted to like following expressions. 12 | //! 13 | //! ```lisp 14 | //! (define i 0) 15 | //! (internal-loop (if (< i 1000 16 | //! (begin 17 | //! (set! i (+ i 1)) 18 | //! continue)))) 19 | //! ``` 20 | //! 21 | //! A `internal-loop` behaves like `while(true)`. However it breaks without continue. 22 | //! A `continue` is special value used only in this interpreter. 23 | //! If the evaluator meets `continue`, the process goes a head of `internal-loop`. 24 | //! 25 | //! This optimization is followed by . 26 | //! 27 | 28 | use anyhow::Result; 29 | 30 | use super::super::{ast::*, parser::*, SymbolValue, TokenLocation}; 31 | 32 | // TOOD: Support body what the function is assigned other variable 33 | fn optimize_tail_recursion( 34 | func_name: &String, 35 | locals: &[SymbolValue], 36 | body: &[AnnotatedAst], 37 | ) -> Option> { 38 | fn _optimize_tail_recursion( 39 | func_name: &String, 40 | locals: &[SymbolValue], 41 | ast: &AnnotatedAst, 42 | ) -> Option { 43 | match &ast.ast { 44 | Ast::List(vs) => { 45 | if let Some((name_ast, args)) = vs.split_first() { 46 | if let Ast::Symbol(name) = &name_ast.ast { 47 | let name = name.as_str(); 48 | let mut args = { 49 | let not_in_args = 50 | args.iter().all(|arg| !includes_symbol(func_name, &arg.ast)); 51 | 52 | if name == func_name && not_in_args { 53 | let updates = args 54 | .iter() 55 | .zip(locals) 56 | .map(|(arg, sym)| { 57 | Ast::Assign(Assign { 58 | var: sym.clone(), 59 | var_loc: TokenLocation::Null, 60 | value: Box::new(arg.clone()), 61 | }) 62 | .with_null_location() 63 | }) 64 | .collect::>(); 65 | 66 | return Some( 67 | Ast::Continue(Continue { 68 | label: name.to_string(), 69 | updates, 70 | }) 71 | .with_null_location(), 72 | ); 73 | } else { 74 | None 75 | } 76 | }; 77 | args.as_mut().map(|args| { 78 | let mut whole = vec![name_ast.clone()]; 79 | whole.append(args); 80 | Ast::List(whole).with_null_location() 81 | }) 82 | } else { 83 | // This is not valid ast 84 | None 85 | } 86 | } else { 87 | Some(ast.clone()) 88 | } 89 | } 90 | Ast::Assign(assign) => Some(ast.clone().with_new_ast(Ast::Assign(Assign { 91 | value: Box::new(_optimize_tail_recursion( 92 | func_name, 93 | locals, 94 | assign.value.as_ref(), 95 | )?), 96 | ..assign.clone() 97 | }))), 98 | Ast::IfExpr(if_expr) => { 99 | if includes_symbol(func_name, &if_expr.cond.ast) { 100 | return None; 101 | } 102 | 103 | let cond = if_expr.cond.clone(); 104 | let then_ast = Box::new(_optimize_tail_recursion( 105 | func_name, 106 | locals, 107 | if_expr.then_ast.as_ref(), 108 | )?); 109 | 110 | let if_expr = if let Some(else_ast) = &if_expr.else_ast { 111 | let else_ast = Some(Box::new(_optimize_tail_recursion( 112 | func_name, 113 | locals, 114 | else_ast.as_ref(), 115 | )?)); 116 | IfExpr { 117 | cond, 118 | then_ast, 119 | else_ast, 120 | } 121 | } else { 122 | IfExpr { 123 | cond, 124 | then_ast, 125 | else_ast: None, 126 | } 127 | }; 128 | 129 | Some(ast.clone().with_new_ast(Ast::IfExpr(if_expr))) 130 | } 131 | Ast::As(expr, ty) => Some(ast.clone().with_new_ast(Ast::As( 132 | Box::new(_optimize_tail_recursion(func_name, locals, expr)?), 133 | ty.to_owned(), 134 | ))), 135 | Ast::Let(Let { 136 | sequential, 137 | proc_id, 138 | inits, 139 | body, 140 | }) => { 141 | let sequential = *sequential; 142 | let proc_id = proc_id.clone(); 143 | 144 | let includes_sym_in_inits = inits 145 | .iter() 146 | .any(|(_k, v)| includes_symbol(func_name, &v.ast)); 147 | 148 | if includes_sym_in_inits { 149 | return None; 150 | } 151 | 152 | let body = optimize_tail_recursion(func_name, locals, body)?; 153 | 154 | Some(ast.clone().with_new_ast(Ast::Let(Let { 155 | sequential, 156 | proc_id, 157 | inits: inits.clone(), 158 | body, 159 | }))) 160 | } 161 | Ast::Begin(Begin { body }) => { 162 | let body = optimize_tail_recursion(func_name, locals, body)?; 163 | Some(ast.clone().with_new_ast(Ast::Begin(Begin { body }))) 164 | } 165 | Ast::Loop(Loop { inits, label, body }) => { 166 | let includes_sym_in_inits = inits 167 | .iter() 168 | .any(|(_k, v)| includes_symbol(func_name, &v.ast)); 169 | 170 | if includes_sym_in_inits { 171 | return None; 172 | } 173 | 174 | let body = optimize_tail_recursion(func_name, locals, body)?; 175 | Some(ast.clone().with_new_ast(Ast::Loop(Loop { 176 | inits: inits.clone(), 177 | label: label.to_string(), 178 | body, 179 | }))) 180 | } 181 | Ast::ListLiteral(vs) => { 182 | let vs = optimize_tail_recursion(func_name, locals, vs)?; 183 | Some(ast.clone().with_new_ast(Ast::ListLiteral(vs))) 184 | } 185 | Ast::ArrayLiteral(vs, is_fixed) => { 186 | let vs = optimize_tail_recursion(func_name, locals, vs)?; 187 | Some(ast.clone().with_new_ast(Ast::ArrayLiteral(vs, *is_fixed))) 188 | } 189 | Ast::Cond(Cond { clauses }) => { 190 | let clauses = clauses 191 | .iter() 192 | .map(|CondClause { cond, body }| { 193 | Some(CondClause { 194 | cond: Box::new(_optimize_tail_recursion(func_name, locals, cond)?), 195 | body: optimize_tail_recursion(func_name, locals, body)?, 196 | }) 197 | }) 198 | .collect::>>()?; 199 | Some(ast.clone().with_new_ast(Ast::Cond(Cond { clauses }))) 200 | } 201 | Ast::Quoted(v) => _optimize_tail_recursion(func_name, locals, v), 202 | Ast::Ref(expr) => Some(ast.clone().with_new_ast(Ast::Ref(Box::new( 203 | _optimize_tail_recursion(func_name, locals, expr)?, 204 | )))), 205 | Ast::Symbol(_) 206 | | Ast::SymbolWithType(_, _) 207 | | Ast::Integer(_) 208 | | Ast::Float(_) 209 | | Ast::Boolean(_) 210 | | Ast::Char(_) 211 | | Ast::String(_) 212 | | Ast::Nil 213 | | Ast::Include(_) 214 | | Ast::DefineMacro(_) 215 | | Ast::DefineStruct(_) 216 | | Ast::Define(_) 217 | | Ast::DefineFunction(_) 218 | | Ast::Lambda(_) 219 | | Ast::Continue(_) => Some(ast.clone()), 220 | } 221 | } 222 | 223 | fn includes_symbol(sym: &String, ast: &Ast) -> bool { 224 | match ast { 225 | Ast::List(vs) => vs.iter().any(|v| includes_symbol(sym, &v.ast)), 226 | Ast::Quoted(v) => includes_symbol(sym, &v.ast), 227 | Ast::Symbol(v) => v == sym, 228 | Ast::SymbolWithType(v, _) => v == sym, 229 | Ast::Assign(assign) => &assign.var == sym || includes_symbol(sym, &assign.value.ast), 230 | Ast::IfExpr(IfExpr { 231 | cond, 232 | then_ast, 233 | else_ast, 234 | }) => { 235 | includes_symbol(sym, &cond.ast) 236 | || includes_symbol(sym, &then_ast.ast) 237 | || else_ast 238 | .as_ref() 239 | .map(|else_ast| includes_symbol(sym, &else_ast.ast)) 240 | .unwrap_or(false) 241 | } 242 | Ast::As(expr, _) => includes_symbol(sym, &expr.ast), 243 | Ast::Let(Let { inits, body, .. }) => { 244 | inits.iter().any(|(_k, v)| includes_symbol(sym, &v.ast)) 245 | | body.iter().any(|b| includes_symbol(sym, &b.ast)) 246 | } 247 | Ast::Begin(Begin { body }) => body.iter().any(|b| includes_symbol(sym, &b.ast)), 248 | Ast::Loop(Loop { inits, body, .. }) => { 249 | inits.iter().any(|(_k, v)| includes_symbol(sym, &v.ast)) 250 | | body.iter().any(|b| includes_symbol(sym, &b.ast)) 251 | } 252 | Ast::ListLiteral(vs) => vs.iter().any(|v| includes_symbol(sym, &v.ast)), 253 | Ast::ArrayLiteral(vs, _) => vs.iter().any(|v| includes_symbol(sym, &v.ast)), 254 | Ast::Cond(Cond { clauses }) => clauses.iter().any(|CondClause { cond, body }| { 255 | includes_symbol(sym, &cond.ast) || body.iter().any(|b| includes_symbol(sym, &b.ast)) 256 | }), 257 | Ast::Ref(expr) => includes_symbol(sym, &expr.ast), 258 | Ast::Integer(_) 259 | | Ast::Float(_) 260 | | Ast::Boolean(_) 261 | | Ast::Char(_) 262 | | Ast::String(_) 263 | | Ast::Nil 264 | | Ast::Include(_) 265 | | Ast::DefineMacro(_) 266 | | Ast::DefineStruct(_) 267 | | Ast::Define(_) 268 | | Ast::DefineFunction(_) 269 | | Ast::Lambda(_) 270 | | Ast::Continue(_) => false, 271 | } 272 | } 273 | 274 | let len = body.len(); 275 | match len { 276 | 0 => None, 277 | _ => { 278 | let (last, body) = body.split_last().unwrap(); 279 | for ast in body { 280 | if includes_symbol(func_name, &ast.ast) { 281 | return None; 282 | } 283 | } 284 | _optimize_tail_recursion(func_name, locals, last).map(|last| { 285 | let mut body = body.to_vec(); 286 | body.push(last); 287 | body 288 | }) 289 | } 290 | } 291 | } 292 | 293 | fn opt_tail_recursion_ast(ast: AnnotatedAst, _ctx: &mut ()) -> Result { 294 | let AnnotatedAst { ast, location, ty } = ast; 295 | 296 | match ast { 297 | Ast::Let(Let { 298 | sequential, 299 | proc_id, 300 | inits, 301 | body, 302 | }) => { 303 | let inits = inits 304 | .into_iter() 305 | .map(|(id, expr)| Ok((id, opt_tail_recursion_ast(expr, _ctx)?))) 306 | .collect::>>()?; 307 | 308 | let body = body 309 | .into_iter() 310 | .map(|ast| opt_tail_recursion_ast(ast, _ctx)) 311 | .collect::>>()?; 312 | 313 | if let Some(proc_id) = &proc_id { 314 | // named let 315 | 316 | let args = inits.iter().map(|(id, _)| id.clone()).collect::>(); 317 | if let Some(optimized) = optimize_tail_recursion(proc_id, &args, &body) { 318 | return Ok(AnnotatedAst { 319 | ast: Ast::Loop(Loop { 320 | inits, 321 | label: proc_id.clone(), 322 | body: optimized, 323 | }), 324 | location, 325 | ty, 326 | }); 327 | } 328 | } 329 | 330 | Ok(AnnotatedAst { 331 | ast: Ast::Let(Let { 332 | sequential, 333 | proc_id, 334 | inits, 335 | body, 336 | }), 337 | location, 338 | ty, 339 | }) 340 | } 341 | _ => { 342 | let annot = AnnotatedAst { ast, location, ty }; 343 | annot.traverse(&mut (), opt_tail_recursion_ast) 344 | } 345 | } 346 | } 347 | 348 | pub fn optimize(asts: Program) -> Result { 349 | asts.into_iter() 350 | .map(|ast| opt_tail_recursion_ast(ast, &mut ())) 351 | .collect::>>() 352 | } 353 | -------------------------------------------------------------------------------- /src/lispi/riscv.rs: -------------------------------------------------------------------------------- 1 | pub mod cmp_translator; 2 | pub mod code_generator; 3 | pub mod stack_frame; 4 | 5 | #[derive(Eq, PartialEq, Hash)] 6 | pub enum Spec { 7 | /// Base Integer Instruction Set, Version 2.1 8 | Integer32, 9 | /// Standard Extension for Integer Multiplication and Division, Version 2.0 10 | Multiplication, 11 | } 12 | -------------------------------------------------------------------------------- /src/lispi/riscv/cmp_translator.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use crate::lispi::{ 4 | ir::{basic_block::*, instruction::*, IrContext}, 5 | ty::Type, 6 | }; 7 | 8 | use Instruction as I; 9 | 10 | pub fn translate(program: &IrProgram, ir_ctx: &mut IrContext) -> Result<()> { 11 | for fun in &program.funcs { 12 | for bb in &fun.basic_blocks { 13 | let bb = ir_ctx.bb_arena.get_mut(*bb).unwrap(); 14 | 15 | let mut insts = Vec::new(); 16 | 17 | for AnnotatedInstr { 18 | result, 19 | inst, 20 | ty, 21 | tags, 22 | } in bb.insts.drain(..) 23 | { 24 | if let I::Cmp(op, left, right) = &inst { 25 | use CmpOperator::*; 26 | 27 | match op { 28 | // left == right <=> !(left - right) 29 | Eq => { 30 | let iresult = Variable { 31 | name: format!("{}-ct", result.name), 32 | }; 33 | insts.push( 34 | AnnotatedInstr::new( 35 | iresult.clone(), 36 | I::Sub(left.clone(), right.clone()), 37 | ty, 38 | ) 39 | .with_tags(tags), 40 | ); 41 | insts.push(AnnotatedInstr::new( 42 | result, 43 | I::Not(iresult.into()), 44 | Type::Boolean, 45 | )); 46 | } 47 | // left >= right <=> !(left < right) 48 | SGE => { 49 | let iresult = Variable { 50 | name: format!("{}-ct", result.name), 51 | }; 52 | insts.push( 53 | AnnotatedInstr::new( 54 | iresult.clone(), 55 | I::Cmp(SLT, left.clone(), right.clone()), 56 | ty, 57 | ) 58 | .with_tags(tags), 59 | ); 60 | insts.push(AnnotatedInstr::new( 61 | result, 62 | I::Not(iresult.into()), 63 | Type::Boolean, 64 | )); 65 | } 66 | // left <= right <=> !(right < left) 67 | SLE => { 68 | let iresult = Variable { 69 | name: format!("{}-ct", result.name), 70 | }; 71 | insts.push( 72 | AnnotatedInstr::new( 73 | iresult.clone(), 74 | I::Cmp(SLT, right.clone(), left.clone()), 75 | ty, 76 | ) 77 | .with_tags(tags), 78 | ); 79 | insts.push(AnnotatedInstr::new( 80 | result, 81 | I::Not(iresult.into()), 82 | Type::Boolean, 83 | )); 84 | } 85 | // left > right <=> right < left 86 | SGT => { 87 | insts.push( 88 | AnnotatedInstr::new( 89 | result, 90 | I::Cmp(SLT, right.clone(), left.clone()), 91 | ty, 92 | ) 93 | .with_tags(tags), 94 | ); 95 | } 96 | SLT => insts.push(AnnotatedInstr { 97 | result, 98 | inst, 99 | ty, 100 | tags, 101 | }), 102 | } 103 | } else { 104 | insts.push(AnnotatedInstr { 105 | result, 106 | inst, 107 | ty, 108 | tags, 109 | }) 110 | } 111 | } 112 | 113 | bb.insts = insts; 114 | } 115 | } 116 | 117 | Ok(()) 118 | } 119 | -------------------------------------------------------------------------------- /src/lispi/riscv/stack_frame.rs: -------------------------------------------------------------------------------- 1 | //! Stack frame 2 | //! 3 | //! ```text 4 | //! Higher 5 | //! 6 | //! Caller frame 7 | //! ========================= FP 8 | //! Saved Registers 9 | //! * Used registers 10 | //! * aX for arguments 11 | //! * tX 12 | //! * fp(s0) 13 | //! * ra 14 | //! ------------------------- 15 | //! Local Variables 16 | //! ========================= SP 17 | //! Callee frame 18 | //! 19 | //! Lower 20 | //! ``` 21 | //! 22 | 23 | use itertools::Itertools; 24 | use rustc_hash::FxHashMap; 25 | use rv32_asm::instruction::*; 26 | 27 | use crate::lispi::ir::{instruction::Variable, register_allocation::RegisterMap}; 28 | 29 | pub struct StackFrame<'a> { 30 | register_map: &'a RegisterMap, 31 | callee_saved_registers: Vec, 32 | 33 | num_of_used_a_register: usize, 34 | 35 | /// Size of the region for local variable in bytes 36 | local_var_size: usize, 37 | local_var_map: FxHashMap, 38 | } 39 | 40 | impl<'a> StackFrame<'a> { 41 | pub fn new(register_map: &'a RegisterMap) -> Self { 42 | Self { 43 | register_map, 44 | callee_saved_registers: vec![ 45 | // Register 'ra' is caller-saved. 46 | // However it is treated as callee-saved because it is not used in function body. 47 | Register::ra(), 48 | Register::fp(), 49 | Register::s(1), 50 | // Used for temporary register 51 | Register::s(2), 52 | ], 53 | num_of_used_a_register: 10, 54 | local_var_size: 16 * 4, 55 | local_var_map: FxHashMap::default(), 56 | } 57 | } 58 | 59 | /// TODO: Manage local variable statically 60 | pub fn allocate_local_var(&mut self, var: &Variable, size: usize) -> usize { 61 | assert!(self.get_local_vars_size() < self.local_var_size); 62 | 63 | let idx = 4 64 | * (self.callee_saved_registers.len() 65 | + self.register_map.values().unique().count() 66 | + self.num_of_used_a_register) 67 | + self.get_local_vars_size(); 68 | self.local_var_map.insert(var.clone(), (idx, size)); 69 | idx 70 | } 71 | 72 | /// Returns the address and size of var. 73 | pub fn get_local_var(&self, var: &Variable) -> Option<(usize, usize)> { 74 | self.local_var_map.get(var).copied() 75 | } 76 | 77 | pub fn get_local_vars_size(&self) -> usize { 78 | self.local_var_map.values().map(|(_, size)| size).sum() 79 | } 80 | 81 | pub fn generate_fun_header(&self) -> Vec { 82 | let frame_size = 4 83 | * (self.callee_saved_registers.len() 84 | + self.register_map.values().unique().count() 85 | + self.num_of_used_a_register 86 | + self.local_var_size) as i32; 87 | 88 | let mut insts = Vec::new(); 89 | 90 | insts.push(Instruction::addi(Register::sp(), Register::sp(), -frame_size).into()); 91 | 92 | for (i, reg) in self.callee_saved_registers.iter().enumerate() { 93 | insts.push(Instruction::sw(*reg, Register::sp(), Immediate::new(i as i32 * 4)).into()); 94 | } 95 | 96 | insts.push(Instruction::addi(Register::fp(), Register::sp(), frame_size).into()); 97 | 98 | insts.push(Instruction::mv(Register::s(1), Register::sp()).into()); 99 | 100 | insts 101 | } 102 | 103 | pub fn generate_fun_footer(&self) -> Vec { 104 | let mut insts = Vec::new(); 105 | 106 | insts.push(Instruction::mv(Register::t(0), Register::fp()).into()); 107 | 108 | for (i, reg) in self.callee_saved_registers.iter().enumerate() { 109 | insts.push(Instruction::lw(*reg, Register::s(1), Immediate::new(i as i32 * 4)).into()); 110 | } 111 | 112 | insts.push(Instruction::mv(Register::sp(), Register::t(0)).into()); 113 | 114 | insts 115 | } 116 | 117 | /// Generate insts to save and restore caller-saved registers. 118 | pub fn generate_insts_for_call( 119 | &self, 120 | args_count: usize, 121 | result_reg: Option<&Register>, 122 | preserve_result_reg: bool, 123 | ) -> ( 124 | Vec, 125 | Vec, 126 | Vec, 127 | ) { 128 | let used_regs = self 129 | .register_map 130 | .values() 131 | .unique() 132 | .map(|id| Register::t(*id as u32)) 133 | .chain((0..args_count).map(|i| Register::a(i as u32))) 134 | .filter(|reg| { 135 | if preserve_result_reg { 136 | true 137 | } else { 138 | Some(reg) != result_reg 139 | } 140 | }) 141 | .collect_vec(); 142 | 143 | let mut save = Vec::new(); 144 | // Save caller-saved registers for temporary and function arguments registers now 145 | for (i, reg) in used_regs.iter().enumerate() { 146 | save.push( 147 | Instruction::sw(*reg, Register::s(1), Immediate::new((i as i32 + 3) * 4)).into(), 148 | ); 149 | } 150 | 151 | let mut restore_preserved_result_reg = Vec::new(); 152 | 153 | let mut restore = Vec::new(); 154 | for (i, reg) in used_regs.into_iter().enumerate() { 155 | if preserve_result_reg && Some(®) == result_reg { 156 | restore_preserved_result_reg.push( 157 | Instruction::lw(reg, Register::s(1), Immediate::new((i as i32 + 3) * 4)).into(), 158 | ); 159 | } else { 160 | restore.push( 161 | Instruction::lw(reg, Register::s(1), Immediate::new((i as i32 + 3) * 4)).into(), 162 | ); 163 | } 164 | } 165 | 166 | (save, restore_preserved_result_reg, restore) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/lispi/tokenizer.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use std::fmt::Display; 3 | use std::num::{ParseFloatError, ParseIntError}; 4 | 5 | use super::error::*; 6 | use super::*; 7 | 8 | impl From for Error { 9 | fn from(_err: ParseIntError) -> Self { 10 | Error::Tokenize("Parse int error".to_string()) 11 | } 12 | } 13 | 14 | impl From for Error { 15 | fn from(_err: ParseFloatError) -> Self { 16 | Error::Tokenize("Parse float error".to_string()) 17 | } 18 | } 19 | 20 | #[derive(Debug, PartialEq)] 21 | pub enum Token { 22 | LeftParen, 23 | RightParen, 24 | LeftSquareBracket, 25 | RightSquareBracket, 26 | Quote, 27 | Colon, 28 | Ampersand, 29 | IntegerLiteral(i32), 30 | FloatLiteral(f32), 31 | Identifier(String), 32 | BooleanLiteral(bool), 33 | CharLiteral(char), 34 | StringLiteral(String), 35 | 36 | /// Unused token such as new line, space 37 | Other, 38 | EOF, 39 | } 40 | 41 | impl Token { 42 | fn with_location(self, begin: Location, end: Location) -> TokenWithLocation { 43 | TokenWithLocation { 44 | token: self, 45 | location: LocationRange { begin, end }, 46 | } 47 | } 48 | 49 | fn push_front_to_identifier(self, str: String) -> Token { 50 | match self { 51 | Token::Identifier(id) => { 52 | let id = str + &id; 53 | Token::Identifier(id) 54 | } 55 | _ => self, 56 | } 57 | } 58 | } 59 | 60 | #[derive(PartialEq, Debug)] 61 | pub struct TokenWithLocation { 62 | pub token: Token, 63 | pub location: LocationRange, 64 | } 65 | 66 | impl Display for TokenWithLocation { 67 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 68 | let LocationRange { begin, end } = &self.location; 69 | let loc = format!("@{}-{}", begin, end); 70 | write!(f, "{:?}{}", self.token, loc.dimmed()) 71 | } 72 | } 73 | 74 | trait CharExt { 75 | fn is_identifier_head(&self) -> bool; 76 | fn is_identifier(&self) -> bool; 77 | } 78 | 79 | impl CharExt for char { 80 | fn is_identifier_head(&self) -> bool { 81 | match *self { 82 | c if c.is_ascii_alphabetic() => true, 83 | '!' | '$' | '%' | '*' | '/' | '<' | '=' | '>' | '?' | '@' | '^' | '_' | '~' | '+' 84 | | '-' | '.' => true, 85 | _ => false, 86 | } 87 | } 88 | 89 | fn is_identifier(&self) -> bool { 90 | self.is_identifier_head() || self.is_ascii_digit() 91 | } 92 | } 93 | 94 | fn take_while( 95 | program: &[String], 96 | line: &mut Vec, 97 | loc: &mut Location, 98 | pred: fn(char) -> bool, 99 | ) -> Vec { 100 | let mut buf = Vec::new(); 101 | while let Ok(ch) = current_char(line, loc) { 102 | if !pred(ch) { 103 | break; 104 | } 105 | buf.push(ch.to_string()); 106 | 107 | succ(program, line, loc); 108 | } 109 | buf 110 | } 111 | 112 | fn take_expected( 113 | program: &[String], 114 | line: &mut Vec, 115 | loc: &mut Location, 116 | expected: char, 117 | ) -> Result<()> { 118 | let c = current_char(line, loc)?; 119 | if c == expected { 120 | succ(program, line, loc); 121 | Ok(()) 122 | } else { 123 | Err(Error::Tokenize(format!("Unexpected {}", c)) 124 | .with_single_location(*loc) 125 | .into()) 126 | } 127 | } 128 | 129 | fn current_char(line: &[char], loc: &Location) -> Result { 130 | if let Some(ch) = line.get(loc.column) { 131 | Ok(*ch) 132 | } else { 133 | Err(Error::Tokenize("Unexpected EOF".to_string()) 134 | .with_single_location(*loc) 135 | .into()) 136 | } 137 | } 138 | 139 | fn move_to_next_line<'a>(program: &'a [String], line: &'a mut Vec, loc: &mut Location) { 140 | loc.newline(); 141 | if let Some(new_line) = program.get(loc.line) { 142 | *line = new_line.chars().collect(); 143 | } else { 144 | *line = Vec::new(); 145 | } 146 | } 147 | 148 | /// Move loc to next location. 149 | /// 150 | /// Returned value is moved location, however it is not next line when the column reaches the end of line. 151 | fn succ<'a>(program: &'a [String], line: &'a mut Vec, loc: &mut Location) -> Location { 152 | loc.column += 1; 153 | 154 | let result = *loc; 155 | 156 | let c = current_char(line, loc); 157 | let nl = if let Ok(c) = c { 158 | c == '\r' || c == '\n' 159 | } else { 160 | true 161 | }; 162 | 163 | if nl { 164 | move_to_next_line(program, line, loc); 165 | } 166 | 167 | result 168 | } 169 | 170 | fn tokenize_number( 171 | program: &[String], 172 | line: &mut Vec, 173 | loc: &mut Location, 174 | begin: Location, 175 | sign: bool, 176 | ) -> Result { 177 | let head = current_char(line, loc)?; 178 | 179 | match head { 180 | '0' => { 181 | // hex or binary 182 | succ(program, line, loc); 183 | let head = current_char(line, loc)?; 184 | match head { 185 | 'x' | 'b' => { 186 | succ(program, line, loc); 187 | 188 | let int = take_while(program, line, loc, |c| c.is_ascii_hexdigit()).join(""); 189 | let radix = if head == 'x' { 16 } else { 2 }; 190 | let int = i32::from_str_radix(int.as_str(), radix)?; 191 | let int = if sign { int } else { -int }; 192 | 193 | Ok(Token::IntegerLiteral(int).with_location(begin, *loc)) 194 | } 195 | _ => { 196 | // Just 0 197 | Ok(Token::IntegerLiteral(0).with_location(begin, *loc)) 198 | } 199 | } 200 | } 201 | _ => { 202 | // ordinary integer 203 | let int = take_while(program, line, loc, |c| c.is_ascii_digit()).join(""); 204 | 205 | if let Ok('.') = current_char(line, loc) { 206 | succ(program, line, loc); 207 | let decimal = take_while(program, line, loc, |c| c.is_ascii_digit()).join(""); 208 | let float = (int + "." + &decimal).parse::()?; 209 | let float = if sign { float } else { -float }; 210 | Ok(Token::FloatLiteral(float).with_location(begin, *loc)) 211 | } else { 212 | let int = int.parse::()?; 213 | let int = if sign { int } else { -int }; 214 | Ok(Token::IntegerLiteral(int).with_location(begin, *loc)) 215 | } 216 | } 217 | } 218 | } 219 | 220 | fn tokenize_identifier( 221 | program: &[String], 222 | line: &mut Vec, 223 | loc: &mut Location, 224 | begin: Location, 225 | ) -> Result { 226 | let chars = take_while(program, line, loc, |c| c.is_identifier()); 227 | 228 | let value = chars.join(""); 229 | Ok(Token::Identifier(value).with_location(begin, *loc)) 230 | } 231 | 232 | /// Get a single token from program and move loc to the location of next token 233 | fn tokenize_single<'a>( 234 | program: &'a Vec, 235 | line: &'a mut Vec, 236 | loc: &mut Location, 237 | ) -> Result> { 238 | if let Ok(ch) = current_char(line, loc) { 239 | let begin = *loc; 240 | let result = match ch { 241 | '(' => { 242 | let end = succ(program, line, loc); 243 | Token::LeftParen.with_location(begin, end) 244 | } 245 | ')' => { 246 | let end = succ(program, line, loc); 247 | Token::RightParen.with_location(begin, end) 248 | } 249 | '[' => { 250 | succ(program, line, loc); 251 | Token::LeftSquareBracket.with_location(begin, *loc) 252 | } 253 | ']' => { 254 | succ(program, line, loc); 255 | Token::RightSquareBracket.with_location(begin, *loc) 256 | } 257 | '\'' => { 258 | succ(program, line, loc); 259 | Token::Quote.with_location(begin, *loc) 260 | } 261 | '#' => { 262 | succ(program, line, loc); 263 | let ret = match current_char(line, loc)? { 264 | 't' => Token::BooleanLiteral(true), 265 | 'f' => Token::BooleanLiteral(false), 266 | '\\' => { 267 | succ(program, line, loc); 268 | let c = current_char(line, loc)?; 269 | Token::CharLiteral(c) 270 | } 271 | c => Err(Error::Tokenize(format!("Unexpected charactor {}", c)) 272 | .with_single_location(*loc))?, 273 | }; 274 | succ(program, line, loc); 275 | ret.with_location(begin, *loc) 276 | } 277 | '\\' => { 278 | succ(program, line, loc); 279 | let ch = current_char(line, loc)?; 280 | succ(program, line, loc); 281 | 282 | Token::CharLiteral(ch).with_location(begin, *loc) 283 | } 284 | ';' => { 285 | move_to_next_line(program, line, loc); 286 | return Ok(None); 287 | } 288 | ':' => { 289 | succ(program, line, loc); 290 | Token::Colon.with_location(begin, *loc) 291 | } 292 | '"' => { 293 | succ(program, line, loc); 294 | 295 | let value = take_while(program, line, loc, |c| c != '"'); 296 | let value = value.join(""); 297 | let value = value.replace("\\r", "\r"); 298 | let value = value.replace("\\n", "\n"); 299 | let value = value.replace("\\t", "\t"); 300 | 301 | take_expected(program, line, loc, '"')?; 302 | 303 | Token::StringLiteral(value).with_location(begin, *loc) 304 | } 305 | // Unary operator 306 | '+' | '-' => { 307 | let op = current_char(line, loc).unwrap(); 308 | let end = succ(program, line, loc); 309 | 310 | if let Ok(ch) = current_char(line, loc) { 311 | if ch.is_ascii_digit() { 312 | let sign = op == '+'; 313 | tokenize_number(program, line, loc, begin, sign)? 314 | } else { 315 | let mut token = tokenize_identifier(program, line, loc, begin)?; 316 | token.token = token.token.push_front_to_identifier(op.to_string()); 317 | token 318 | } 319 | } else { 320 | Token::Identifier(op.to_string()).with_location(begin, end) 321 | } 322 | } 323 | '&' => { 324 | succ(program, line, loc); 325 | Token::Ampersand.with_location(begin, *loc) 326 | } 327 | c if c.is_ascii_digit() => tokenize_number(program, line, loc, begin, true)?, 328 | c if c.is_identifier_head() => tokenize_identifier(program, line, loc, begin)?, 329 | ' ' | '\n' | '\r' => { 330 | succ(program, line, loc); 331 | Token::Other.with_location(Location::head(), Location::head()) 332 | } 333 | c => { 334 | return Err(Error::Tokenize(format!("Unexpected charactor `{:?}`", c)) 335 | .with_location(TokenLocation::Range(LocationRange::new(begin, *loc))) 336 | .into()) 337 | } 338 | }; 339 | Ok(Some(result)) 340 | } else { 341 | let token = if loc.line < program.len() - 1 { 342 | succ(program, line, loc); 343 | Token::Other 344 | } else { 345 | Token::EOF 346 | }; 347 | 348 | Ok(Some( 349 | token.with_location(Location::head(), Location::head()), 350 | )) 351 | } 352 | } 353 | 354 | /// Get tokens with its location from program. 355 | /// 356 | /// This step is to ease parsing step. 357 | pub fn tokenize(program: Vec) -> Result> { 358 | let mut result = Vec::new(); 359 | 360 | if let Some(line) = program.get(0) { 361 | let mut line: Vec = line.chars().collect(); 362 | let mut loc = Location::head(); 363 | loop { 364 | let token = tokenize_single(&program, &mut line, &mut loc)?; 365 | if let Some(token) = token { 366 | match token.token { 367 | Token::Other => {} 368 | Token::EOF => break, 369 | _ => result.push(token), 370 | } 371 | } 372 | } 373 | Ok(result) 374 | } else { 375 | Ok(Vec::new()) 376 | } 377 | } 378 | 379 | pub fn show_tokens(tokens: Vec) -> Result> { 380 | for TokenWithLocation { token, location } in &tokens { 381 | println!("{:?} @ {}", token, location); 382 | } 383 | Ok(tokens) 384 | } 385 | 386 | #[cfg(test)] 387 | mod tests { 388 | use super::*; 389 | 390 | fn tok(value: &str) -> Token { 391 | tokenize(vec![value.to_string()]) 392 | .unwrap() 393 | .into_iter() 394 | .next() 395 | .unwrap() 396 | .token 397 | } 398 | 399 | #[test] 400 | fn test_tokenize_number_float() { 401 | #[allow(clippy::approx_constant)] 402 | let pi = 3.14; 403 | assert_eq!(tok("3.14"), Token::FloatLiteral(pi)); 404 | assert_eq!(tok("3.0"), Token::FloatLiteral(3.0)); 405 | } 406 | 407 | #[test] 408 | fn test_tokenize_number_sign() { 409 | assert_eq!(tok("+3"), Token::IntegerLiteral(3)); 410 | assert_eq!(tok("-3"), Token::IntegerLiteral(-3)); 411 | } 412 | 413 | #[test] 414 | fn test_tokenize_number_hex() { 415 | assert_eq!(tok("0xFF"), Token::IntegerLiteral(255)); 416 | assert_eq!(tok("0xff"), Token::IntegerLiteral(255)); 417 | 418 | assert_eq!(tok("-0xff"), Token::IntegerLiteral(-255)); 419 | } 420 | 421 | #[test] 422 | fn test_tokenize_number_bin() { 423 | assert_eq!(tok("0b0110"), Token::IntegerLiteral(6)); 424 | assert_eq!(tok("-0b0110"), Token::IntegerLiteral(-6)); 425 | } 426 | 427 | #[test] 428 | fn test_tokenize_identifier() { 429 | assert_eq!(tok("+test"), Token::Identifier("+test".to_string())); 430 | assert_eq!(tok("-test"), Token::Identifier("-test".to_string())); 431 | assert_eq!(tok("+"), Token::Identifier("+".to_string())); 432 | assert_eq!(tok("-"), Token::Identifier("-".to_string())); 433 | } 434 | 435 | #[test] 436 | fn test_tokenize_char() { 437 | assert_eq!(tok("#\\a"), Token::CharLiteral('a')); 438 | assert_eq!(tok("#\\あ"), Token::CharLiteral('あ')); 439 | assert_eq!(tok("\\a"), Token::CharLiteral('a')); 440 | } 441 | } 442 | -------------------------------------------------------------------------------- /src/lispi/unique_generator.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, PartialEq, Debug, Default)] 2 | pub struct UniqueGenerator { 3 | prefix: String, 4 | count: u32, 5 | } 6 | 7 | impl UniqueGenerator { 8 | pub fn new(prefix: String) -> Self { 9 | Self { prefix, count: 0 } 10 | } 11 | 12 | pub fn gen(&mut self) -> u32 { 13 | let id = self.count; 14 | self.count += 1; 15 | id 16 | } 17 | 18 | pub fn gen_string(&mut self) -> String { 19 | format!("{}{}", self.prefix.clone(), self.gen()) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | use crossterm::cursor; 4 | use crossterm::event; 5 | use crossterm::event::{read, Event, KeyEvent}; 6 | use crossterm::terminal; 7 | use crossterm::ExecutableCommand; 8 | use lisp_rs::lispi::interpret_with_env; 9 | use std::collections::VecDeque; 10 | use std::io::stdout; 11 | use std::process::exit; 12 | 13 | use lisp_rs::lispi::cli_option::CliOption; 14 | use lisp_rs::lispi::common::read_lines; 15 | use lisp_rs::lispi::error::ErrorWithLocation; 16 | use lisp_rs::lispi::*; 17 | use lisp_rs::lispi::{console as c, environment as env, error::Error, evaluator as e, pass}; 18 | use lisp_rs::lispi::{Location, LocationRange, TokenLocation}; 19 | 20 | fn main() -> Result<()> { 21 | let cli = CliOption::parse(); 22 | 23 | if let Some(filename) = &cli.filename { 24 | let filename = filename.to_owned(); 25 | 26 | // Run the program from file 27 | let lines = read_lines(&filename)?; 28 | 29 | if cli.compile { 30 | let applied_opts = if cli.without_opts { 31 | pass::Optimize::minimum() 32 | } else { 33 | pass::Optimize::all() 34 | }; 35 | let result = compile(lines.clone(), &cli, applied_opts); 36 | match result { 37 | Ok(_) => {} 38 | Err(err) => { 39 | show_error(err, filename, lines); 40 | exit(1); 41 | } 42 | } 43 | } else { 44 | let result = interpret(lines.clone(), &cli); 45 | match result { 46 | Ok(result) => { 47 | if let Some((result, ty)) = result.last() { 48 | println!("{}: {}", result, ty); 49 | } 50 | } 51 | Err(err) => { 52 | show_error(err, filename, lines); 53 | exit(1); 54 | } 55 | } 56 | } 57 | } else { 58 | // REPL mode 59 | 60 | terminal::enable_raw_mode()?; 61 | 62 | let prompt = "(lisp-rs) > "; 63 | 64 | c::print(prompt)?; 65 | 66 | let mut history: VecDeque> = VecDeque::new(); 67 | history.push_front(Vec::new()); 68 | 69 | // 0 is the position where a user is inputing. 70 | let mut history_pos = 0; 71 | 72 | let mut cursor_pos = 0; 73 | 74 | let set_program = |prog: String, cursor_pos: &mut usize| -> std::io::Result<()> { 75 | stdout() 76 | .execute(cursor::MoveToColumn(1))? 77 | .execute(terminal::Clear(terminal::ClearType::CurrentLine))?; 78 | c::print(prompt)?; 79 | c::print(&prog)?; 80 | 81 | *cursor_pos = prog.len(); 82 | 83 | Ok(()) 84 | }; 85 | 86 | let mut env = env::Environment::default(); 87 | let mut ty_env = env::Environment::default(); 88 | e::init_env(&mut env, &mut ty_env); 89 | 90 | loop { 91 | let buffer = &mut history[history_pos]; 92 | 93 | match read()? { 94 | Event::Key(KeyEvent { code, modifiers }) => match code { 95 | event::KeyCode::Char(ch) => { 96 | if ch == 'c' && modifiers == event::KeyModifiers::CONTROL { 97 | c::newline()?; 98 | c::print(prompt)?; 99 | 100 | if history_pos == 0 { 101 | buffer.clear(); 102 | } 103 | 104 | history_pos = 0; 105 | cursor_pos = 0; 106 | 107 | continue; 108 | } 109 | 110 | if ch == 'd' && modifiers == event::KeyModifiers::CONTROL { 111 | terminal::disable_raw_mode()?; 112 | return Ok(()); 113 | } 114 | 115 | if cursor_pos < buffer.len() { 116 | buffer.insert(cursor_pos, ch.to_string()); 117 | } else { 118 | buffer.push(ch.to_string()); 119 | } 120 | 121 | let left_shift = buffer.len() - cursor_pos - 1; 122 | 123 | set_program(buffer.join(""), &mut cursor_pos)?; 124 | 125 | cursor_pos -= left_shift; 126 | stdout().execute(cursor::MoveLeft(left_shift.try_into().unwrap()))?; 127 | } 128 | event::KeyCode::Backspace => { 129 | if cursor_pos > 0 { 130 | buffer.remove(cursor_pos - 1); 131 | 132 | let left_shift = buffer.len() + 1 - cursor_pos; 133 | 134 | set_program(buffer.join(""), &mut cursor_pos)?; 135 | 136 | cursor_pos -= left_shift; 137 | stdout().execute(cursor::MoveLeft(left_shift.try_into().unwrap()))?; 138 | } 139 | } 140 | event::KeyCode::Enter => { 141 | c::newline()?; 142 | 143 | if !buffer.is_empty() { 144 | let lines = vec![buffer.join("")]; 145 | 146 | let results = interpret_with_env(lines.clone(), &mut env, &mut ty_env); 147 | match results { 148 | Ok(results) => { 149 | if let Some((result, ty)) = results.iter().last() { 150 | c::printlnuw(&format!("{}: {}", result, ty)); 151 | } 152 | } 153 | Err(err) => show_error(err, "".to_string(), lines), 154 | } 155 | } 156 | 157 | c::print(prompt)?; 158 | 159 | if history_pos >= 2 { 160 | history[0] = buffer.clone(); 161 | } 162 | 163 | if !history[0].is_empty() { 164 | history.push_front(Vec::new()); 165 | } 166 | 167 | history_pos = 0; 168 | cursor_pos = 0; 169 | } 170 | event::KeyCode::Right => { 171 | if cursor_pos < buffer.len() { 172 | stdout().execute(cursor::MoveRight(1))?; 173 | cursor_pos += 1; 174 | } 175 | } 176 | event::KeyCode::Left => { 177 | if cursor_pos > 0 { 178 | stdout().execute(cursor::MoveLeft(1))?; 179 | cursor_pos -= 1; 180 | } 181 | } 182 | event::KeyCode::Up => { 183 | if history_pos < history.len() - 1 { 184 | history_pos += 1; 185 | 186 | let program = history[history_pos].join(""); 187 | set_program(program, &mut cursor_pos)?; 188 | } 189 | } 190 | event::KeyCode::Down => { 191 | if history_pos > 0 { 192 | history_pos -= 1; 193 | 194 | let program = history[history_pos].join(""); 195 | set_program(program, &mut cursor_pos)?; 196 | } 197 | } 198 | _ => {} 199 | }, 200 | Event::Mouse(_) => todo!(), 201 | Event::Resize(_, _) => todo!(), 202 | } 203 | } 204 | } 205 | 206 | Ok(()) 207 | } 208 | 209 | /// Show an error as human readable format (like rustc) 210 | fn show_error(err: anyhow::Error, filename: String, lines: Vec) { 211 | if let Some(ErrorWithLocation { err, location }) = err.downcast_ref::() { 212 | match err { 213 | Error::TypeNotMatched(t0, t1, loc0, loc1) => { 214 | let (expected_ty, actual_ty, loc) = match (loc0, loc1) { 215 | (&TokenLocation::Null, &TokenLocation::Null) => { 216 | c::printlnuw(&err); 217 | return; 218 | } 219 | (loc, &TokenLocation::Null) => (t1, t0, loc), 220 | (&TokenLocation::Null, loc) => (t0, t1, loc), 221 | (loc0, loc1) => { 222 | if loc0 != loc1 { 223 | c::printlnuw(&format!( 224 | "Type error: {} and {} are not matched at {}", 225 | t0, t1, filename, 226 | )); 227 | c::printlnuw(&format!("First type: {}", t0)); 228 | show_error_location(loc0, &lines); 229 | c::printlnuw(&format!("Second type: {}", t1)); 230 | show_error_location(loc1, &lines); 231 | return; 232 | } else { 233 | (t1, t0, loc0) 234 | } 235 | } 236 | }; 237 | c::printlnuw(&format!( 238 | "Type error: {} is expected but {} is taken at {}:{}", 239 | expected_ty, actual_ty, filename, loc 240 | )); 241 | show_error_location(loc, &lines); 242 | } 243 | _ => { 244 | match location { 245 | TokenLocation::Range(_) | TokenLocation::EOF => { 246 | c::printlnuw(&format!("{} at {}:{}", err, filename, location)) 247 | } 248 | TokenLocation::Null => c::printlnuw(&format!("{} at {}", err, filename)), 249 | } 250 | show_error_location(location, &lines); 251 | } 252 | } 253 | } else { 254 | c::printlnuw(&err); 255 | } 256 | } 257 | 258 | fn show_error_location(location: &TokenLocation, lines: &[String]) { 259 | let range = match location { 260 | TokenLocation::Range(range) => Some(*range), 261 | TokenLocation::EOF => { 262 | let last_line = lines 263 | .iter() 264 | .rev() 265 | .enumerate() 266 | .find(|(_, line)| !line.is_empty()); 267 | let (line, column) = last_line 268 | .map(|(lineno, line)| (if lineno >= 1 { lineno - 1 } else { 0 }, line.len())) 269 | .unwrap_or((0, 0)); 270 | Some(LocationRange { 271 | begin: Location { line, column }, 272 | end: Location { 273 | line, 274 | column: column + 1, 275 | }, 276 | }) 277 | } 278 | TokenLocation::Null => None, 279 | }; 280 | if let Some(LocationRange { begin, end }) = range { 281 | let Location { 282 | line: bline, 283 | column: bcol, 284 | } = begin.humanize(); 285 | let Location { 286 | line: eline, 287 | column: ecol, 288 | } = end.humanize(); 289 | 290 | if bline == eline { 291 | let lineno = bline.to_string(); 292 | let left = " ".repeat(lineno.len()) + " |"; 293 | c::printlnuw(&left); 294 | let underline = " ".repeat(bcol - 1) + "^".repeat(ecol - bcol).as_str(); 295 | 296 | c::printlnuw(&format!( 297 | "{} | {}", 298 | lineno, 299 | lines 300 | .get(bline - 1) 301 | .map(|l| l.to_string()) 302 | .unwrap_or_else(|| "".to_string()) 303 | )); 304 | c::printlnuw(&format!("{} {}", left, underline)); 305 | } else { 306 | let max_lineno_len = eline.to_string().len(); 307 | let left = " ".repeat(max_lineno_len) + " |"; 308 | c::printlnuw(&left); 309 | 310 | for line in bline..=eline { 311 | let lineno = line.to_string(); 312 | c::printlnuw(&format!( 313 | "{}{} | > {}", 314 | lineno, 315 | " ".repeat(max_lineno_len - lineno.len()), 316 | lines 317 | .get(line - 1) 318 | .map(|l| l.to_string()) 319 | .unwrap_or_else(|| "".to_string()) 320 | )); 321 | } 322 | c::printlnuw(&left); 323 | } 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! assert_error { 3 | ( $value:expr, $p:pat ) => { 4 | let has_error = match $value { 5 | Err($p) => true, 6 | _ => false, 7 | }; 8 | assert!( 9 | has_error, 10 | "{} must have an error {}", 11 | stringify!($value), 12 | stringify!($p) 13 | ); 14 | }; 15 | } 16 | 17 | #[allow(dead_code)] 18 | pub static EPS: f32 = 0.0001; 19 | 20 | #[macro_export] 21 | macro_rules! assert_eq_eps { 22 | ( $expected:expr, $actual:expr) => { 23 | if let Ok(Value::Float(v)) = $actual { 24 | assert!( 25 | (v - $expected).abs() <= $crate::common::EPS, 26 | "|{} - {}| <= {}", 27 | v, 28 | $expected, 29 | $crate::common::EPS 30 | ); 31 | } else { 32 | assert!(false, "{:?} must be a float value", $actual); 33 | } 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /tests/interpret_test.rs: -------------------------------------------------------------------------------- 1 | use std::stringify; 2 | 3 | mod common; 4 | 5 | use lisp_rs::lispi::{ 6 | cli_option::CliOption, 7 | error::{Error, ErrorWithLocation}, 8 | evaluator::*, 9 | interpret, 10 | }; 11 | 12 | fn interp(program: &str) -> Result { 13 | let lines = program.split('\n').map(|l| l.to_string()).collect(); 14 | 15 | let opt = CliOption::default(); 16 | 17 | let result = interpret(lines, &opt); 18 | match result { 19 | Ok(result) => Ok(result.last().unwrap().0.clone()), 20 | Err(err) => { 21 | if let Some(err) = err.downcast_ref::() { 22 | Err(err.err.clone()) 23 | } else { 24 | // Must not reach here 25 | Err(Error::DoNothing) 26 | } 27 | } 28 | } 29 | } 30 | 31 | fn build_list(vs: Vec) -> Value { 32 | let vs = vs.iter().map(|v| Value::Integer(*v)).collect(); 33 | Value::List(vs) 34 | } 35 | 36 | #[test] 37 | fn literal_test() { 38 | assert_eq!(Ok(Value::Integer(3)), interp("3")); 39 | assert_eq!(Ok(Value::Float(3.15)), interp("3.15")); 40 | assert_eq!(Ok(Value::Integer(3)), interp("+3")); 41 | assert_eq!(Ok(Value::Integer(-3)), interp("-3")); 42 | 43 | assert_eq!( 44 | Ok(Value::String("Hello World! こんにちは".to_string())), 45 | interp("\"Hello World! こんにちは\"") 46 | ); 47 | 48 | assert_eq!(Ok(Value::Symbol("hello".to_string())), interp("'hello")); 49 | 50 | assert_error!(interp("'(1 2 3)"), Error::Type(_)); 51 | } 52 | 53 | #[test] 54 | fn arithmetic_test() { 55 | assert_eq!(Ok(Value::Integer(3)), interp("(+ 1 2)")); 56 | assert_eq!(Ok(Value::Integer(-1)), interp("(- 1 2)")); 57 | assert_eq!(Ok(Value::Integer(6)), interp("(* 2 3)")); 58 | // assert_eq!(Ok(Value::Integer(60)), interp("(+ 10 20 30)")); 59 | assert_eq!(Ok(Value::Integer(1)), interp("(+ (* 1 2) (- 3 4))")); 60 | 61 | // assert_eq!(Ok(Value::Integer(0)), interp("(+)")); 62 | // assert_eq_eps!(2.2, interp("(+ 1 1.2)")); 63 | // assert_eq_eps!(6.0, interp("(+ 1 2 3.0)")); 64 | // assert_eq_eps!(3.3, interp("(+ 1.1 2.2)")); 65 | 66 | // assert_eq!(Ok(Value::Integer(-1)), interp("(- 1)")); 67 | // assert_eq_eps!(-0.2, interp("(- 1 1.2)")); 68 | // assert_eq_eps!(-4.0, interp("(- 1 2 3.0)")); 69 | // assert_eq_eps!(-1.1, interp("(- 1.1 2.2)")); 70 | 71 | // assert_eq!(Ok(Value::Integer(1)), interp("(*)")); 72 | assert_eq!(Ok(Value::Integer(2)), interp("(* 1 2)")); 73 | // assert_eq_eps!(2.0, interp("(* 1 2.0)")); 74 | 75 | // assert_eq_eps!(0.5, interp("(/ 2.0)")); 76 | assert_eq!(Ok(Value::Integer(0)), interp("(/ 1 2)")); 77 | // assert_eq_eps!(0.5, interp("(/ 1 2.0)")); 78 | } 79 | 80 | #[test] 81 | fn map_test() { 82 | // assert_eq!( 83 | // Ok(build_list(vec![11, 22, 33])), 84 | // interp("(map + '(1 2 3) '(10 20 30))") 85 | // ); 86 | 87 | assert_eq!( 88 | Ok(build_list(vec![1, 9, 16])), 89 | interp( 90 | r#" 91 | (define square (lambda (x) (* x x))) 92 | (map square (list 1 3 4)) 93 | "# 94 | ) 95 | ); 96 | } 97 | 98 | #[test] 99 | fn undefined_function_test() { 100 | assert_error!(&interp("(x 1 2)"), Error::UndefinedVariable(_, "typing")); 101 | assert_error!(&interp("(** 1 2)"), Error::UndefinedVariable(_, "typing")); 102 | } 103 | 104 | #[test] 105 | fn variable_test() { 106 | assert_eq!( 107 | Ok(Value::Integer(3)), 108 | interp( 109 | r#" 110 | (define x 1) 111 | (define y 2) 112 | (+ x y)"# 113 | ) 114 | ); 115 | } 116 | 117 | #[test] 118 | fn type_test() { 119 | assert_error!(&interp("(car 1)"), Error::TypeNotMatched(_, _, _, _)); 120 | assert_error!(&interp("(car '(1 2) 1)"), Error::Type(_)); 121 | } 122 | 123 | #[test] 124 | fn define_error_test() { 125 | assert_error!(&interp("(define 1 2)"), Error::Parse(_)); 126 | assert_error!(&interp("(define x 2 'err)"), Error::Parse(_)); 127 | } 128 | 129 | #[test] 130 | fn if_test() { 131 | // assert_eq!(Ok(Value::Integer(3)), interp("(if 1 (+ 1 2) (+ 3 4))")); 132 | assert_eq!(Ok(Value::Integer(3)), interp("(if #t (+ 1 2) (+ 3 4))")); 133 | assert_eq!(Ok(Value::Integer(7)), interp("(if #f (+ 1 2) (+ 3 4))")); 134 | 135 | // assert_eq!(Ok(Value::Integer(2)), interp("(if 1 2 3)")); 136 | assert_eq!(Ok(Value::Integer(2)), interp("(if (even? 2) 2 3)")); 137 | assert_eq!(Ok(Value::Integer(3)), interp("(if (even? 1) 2 3)")); 138 | 139 | // assert_eq!(Ok(Value::Integer(3)), interp("(if 1 (+ 1 2))")); 140 | assert_eq!(Ok(Value::nil()), interp("(if #f (+ 1 2))")); 141 | } 142 | 143 | #[test] 144 | fn cond_test() { 145 | assert_eq!( 146 | Ok(Value::List( 147 | ["zero", "one", "other"] 148 | .iter() 149 | .map(|s| Value::String(s.to_string())) 150 | .collect() 151 | )), 152 | interp( 153 | r#" 154 | (define f (lambda (x) 155 | (cond 156 | ((= x 0) "zero") 157 | ((= x 1) "one") 158 | (#t "other")))) 159 | 160 | (list (f 0) (f 1) (f 2)) 161 | "# 162 | ) 163 | ); 164 | } 165 | 166 | #[test] 167 | fn list_test() { 168 | assert_eq!(Ok(build_list(vec![1, 2, 3])), interp("(list 1 2 3)")); 169 | assert_error!( 170 | interp("(list 1 \"2\" 3)"), 171 | Error::TypeNotMatched(_, _, _, _) 172 | ); 173 | 174 | assert_eq!( 175 | Ok(build_list(vec![1, 2, 3])), 176 | interp( 177 | r#" 178 | (define xs (list 1 2 3)) 179 | xs"# 180 | ) 181 | ); 182 | assert_eq!(Ok(Value::Integer(1)), interp("(car (list 1 2))")); 183 | assert_eq!(Ok(build_list(vec![2]),), interp("(cdr (list 1 2))")); 184 | 185 | assert_eq!(Ok(Value::nil()), interp("(cdr (list 0))")); 186 | } 187 | 188 | #[test] 189 | fn function_test() { 190 | assert_eq!( 191 | Ok(Value::Integer(25)), 192 | interp( 193 | r#" 194 | (define square (lambda (x) (* x x))) 195 | (square 5)"# 196 | ) 197 | ); 198 | 199 | assert_eq!( 200 | Ok(Value::Integer(25)), 201 | interp( 202 | r#" 203 | (define x 10) 204 | (define square (lambda (x) (* x x))) 205 | (square 5)"# 206 | ) 207 | ); 208 | 209 | assert_eq!( 210 | Ok(Value::Integer(10)), 211 | interp( 212 | r#" 213 | (define x 10) 214 | (define square (lambda (x) (* x x))) 215 | (square 5) 216 | x"# 217 | ) 218 | ); 219 | 220 | assert_eq!( 221 | Ok(Value::Integer(10)), 222 | interp( 223 | r#" 224 | (define y 10) 225 | (define set-y (lambda (x) 226 | (define y 0) 227 | (set! y x))) 228 | (set-y 5) 229 | y 230 | "# 231 | ) 232 | ); 233 | 234 | assert_eq!( 235 | Ok(Value::Integer(24)), 236 | interp( 237 | r#" 238 | (let fact ((x 4)) 239 | (if (= x 0) 240 | 1 241 | (* x (fact (- x 1))))) 242 | "# 243 | ) 244 | ); 245 | } 246 | 247 | #[test] 248 | fn lambda_test() { 249 | assert_eq!(Ok(Value::Integer(25)), interp("((lambda (x) (* x x)) 5)")); 250 | 251 | assert_eq!( 252 | Ok(Value::Integer(25)), 253 | interp( 254 | r#" 255 | (define x 10) 256 | ((lambda (x) (* x x)) 5)"# 257 | ) 258 | ); 259 | } 260 | 261 | #[test] 262 | fn closure_test() { 263 | assert_eq!( 264 | Ok(build_list(vec![1, 2, 1, 3])), 265 | interp( 266 | r#" 267 | (define make-counter (lambda () 268 | (define c 0) 269 | (lambda () 270 | (set! c (+ c 1)) 271 | c))) 272 | 273 | (define c1 (make-counter)) 274 | (define c2 (make-counter)) 275 | 276 | (list (c1) (c1) (c2) (c1))"# 277 | ) 278 | ); 279 | } 280 | 281 | #[test] 282 | fn macro_test() { 283 | assert_eq!( 284 | Ok(build_list(vec![20, 10])), 285 | interp( 286 | r#" 287 | (define-macro my-push (item place) 288 | (list 'set! 289 | place 290 | (list 'cons item place))) 291 | 292 | (define stack (list)) 293 | (my-push 10 stack) 294 | (my-push 20 stack) 295 | stack"# 296 | ) 297 | ); 298 | } 299 | 300 | #[test] 301 | fn let_test() { 302 | assert_eq!(Ok(Value::Integer(3)), interp("(let ((a 1) (b 2)) (+ a b))")); 303 | 304 | assert_eq!( 305 | Ok(Value::Integer(0)), 306 | interp( 307 | r#" 308 | (define a 0) 309 | (let ((a 1) (b 2)) 310 | (+ a b) 311 | (set! a 9)) 312 | a"# 313 | ) 314 | ); 315 | 316 | assert_error!( 317 | interp("(let ((a 1) (b a)) (+ a b))"), 318 | Error::UndefinedVariable(_, "typing") 319 | ); 320 | } 321 | 322 | #[test] 323 | fn let_star_test() { 324 | assert_eq!( 325 | Ok(Value::Integer(3)), 326 | interp("(let* ((a 1) (b 2)) (+ a b))") 327 | ); 328 | 329 | assert_eq!( 330 | Ok(Value::Integer(0)), 331 | interp( 332 | r#" 333 | (define a 0) 334 | (let* ((a 1) (b 2)) 335 | (+ a b) 336 | (set! a 9)) 337 | a"# 338 | ) 339 | ); 340 | 341 | assert_eq!( 342 | Ok(Value::Integer(2)), 343 | interp("(let* ((a 1) (b a)) (+ a b))") 344 | ); 345 | } 346 | 347 | #[test] 348 | fn named_let_test() { 349 | assert_eq!( 350 | Ok(Value::Integer(10)), 351 | interp( 352 | r#" 353 | (define a 0) 354 | (let loop ((i 0)) (if (< i 10) 355 | (begin 356 | (set! a (+ a 1)) 357 | (loop (+ i 1))))) 358 | a"# 359 | ) 360 | ); 361 | } 362 | 363 | #[test] 364 | fn optimizing_tail_recursion_test() { 365 | assert_eq!( 366 | Ok(Value::Integer(1000)), 367 | interp( 368 | r#" 369 | (define a 0) 370 | (let loop ((i 0)) (if (< i 1000) 371 | (begin 372 | (set! a (+ a 1)) 373 | (loop (+ i 1))))) 374 | a"# 375 | ) 376 | ); 377 | } 378 | -------------------------------------------------------------------------------- /tests/type_test.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use lisp_rs::lispi::{ 4 | cli_option::CliOption, 5 | error::{Error, ErrorWithLocation}, 6 | frontend, pass, 7 | typer::Type, 8 | }; 9 | 10 | fn typing(program: &str) -> Result { 11 | let lines = program.split('\n').map(|l| l.to_string()).collect(); 12 | 13 | let opt = CliOption::default(); 14 | 15 | let result = frontend(lines, &opt, &pass::Optimize::all()); 16 | match result { 17 | Ok((asts, _, _)) => Ok(asts.last().unwrap().ty.clone()), 18 | Err(err) => { 19 | if let Some(err) = err.downcast_ref::() { 20 | Err(err.err.clone()) 21 | } else { 22 | // Must not reach here 23 | Err(Error::DoNothing) 24 | } 25 | } 26 | } 27 | } 28 | 29 | #[test] 30 | fn literal_test() { 31 | assert_eq!( 32 | typing("(array 1 2 3)"), 33 | Ok(Type::Array(Box::new(Type::Int))) 34 | ); 35 | } 36 | 37 | #[test] 38 | fn list_test() { 39 | assert_error!(&typing("(car 1)"), Error::TypeNotMatched(_, _, _, _)); 40 | assert_error!(&typing("(car '(1 2) 1)"), Error::Type(_)); 41 | } 42 | 43 | #[test] 44 | fn type_annot_lambda_test() { 45 | assert!(typing( 46 | r#" 47 | (define not (lambda (x: bool) 48 | (if x #f #t) 49 | )) 50 | "# 51 | ) 52 | .is_ok()); 53 | 54 | assert_error!( 55 | &typing( 56 | r#" 57 | (define invalid-not (lambda (x: int) 58 | (if x #f #t) 59 | )) 60 | "# 61 | ), 62 | Error::TypeNotMatched(_, _, _, _) 63 | ); 64 | } 65 | --------------------------------------------------------------------------------