#assembly #register #compiler #instructions #portable #tiny #complex

bin+lib lasm

为复杂编译器设计的微型且可移植的汇编语言

1 个不稳定版本

0.1.0 2019年12月18日

#78 in #portable

Apache-2.0 协议

1MB
961 行代码(不含注释)

包含(WOFF 字体,190KB)doc/FiraSans-Medium.woff,(WOFF 字体,185KB)doc/FiraSans-Regular.woff,(WOFF 字体,94KB)doc/SourceSerifPro-Bold.ttf.woff,(WOFF 字体,89KB)doc/SourceSerifPro-Regular.ttf.woff,(WOFF 字体,56KB)doc/SourceCodePro-Regular.woff,(WOFF 字体,56KB)doc/SourceCodePro-Semibold.woff以及其他1项

lasm

为复杂编译器设计的微型且可移植的汇编语言

安装

cargo install -f lasm

文档

文档可以在这里找到。


lib.rs:

lasm,一个最小化和可移植的汇编语言

这个crate的精神是尽可能地制作最小和最正确的汇编语言。简化的指令集被放在首位。如果可能,速度也是一个值得称赞的特性。

目的

编写编译器是非常困难的。其中很多困难来自于尝试管理内存以及尝试用低级指令来表示高级概念。

因此,考虑到这些问题,我编写了这种汇编语言。

特性

最高级特性是无限数量的寄存器。这允许编译器更容易地声明和使用变量。上一次我写编译器时,最困难的部分是管理变量的分配和释放。因此,我编写了这种汇编语言来处理这个问题!

过程

另一个高级特性是管理过程声明。当汇编被解析时,在检查语义错误之前,每个过程定义都会被定义。因此,过程可以以任何顺序定义。

可移植性

最后一个,也是最好的特性是可移植性。lasm 非常紧凑:lasm 指令集的整个 C 实现接近 150 行。编写 lasm 的实现非常简单,因此编译到 lasm 允许编译器针对多种不同的编程语言和平台。

基本指令

堆栈指令 描述
pushLITERAL 将 LITERAL 参数推入堆栈。LITERAL 参数必须是一个字符或浮点数
pop 从堆栈中弹出一个值到 ACC 寄存器
ldREGISTER 将存储在 REGISTER 中的值推入堆栈。要加载的 REGISTER 必须在加载之前定义
stREGISTER 从堆栈中弹出一个值到 REGISTER。要存储的 REGISTER 必须在存储之前声明
dup 复制堆栈顶部的项目
指针指令 描述
referREGISTER 将指向 REGISTER 的指针推入堆栈
解除引用 从栈中弹出一个指针,并将指针所指向的值压入栈中。这只会将单个单元格压入栈中,不会超过一个单元格。
解除引用存储 从栈中弹出指针和一个单元格,并将单元格存储在指针所指向的位置。
分配REGISTER 从栈中弹出一个 SIZE 值,并将 SIZE 个空闲单元的地址存储在寄存器中。
释放REGISTER 从栈中弹出一个 SIZE 值,并释放寄存器中存储的指针所指向的内存。
数学指令 描述
从栈中弹出两个单元格,并将它们的和压入栈中。
从栈中弹出两个单元格,并将第一个单元格的值减去第二个单元格的值后压入栈中。
从栈中弹出两个单元格,并将它们的乘积压入栈中。
从栈中弹出两个单元格,并将第一个单元格的值除以第二个单元格的值后压入栈中。
比较 从栈中弹出两个单元格,如果第一个单元格小于第二个单元格则压入 -1,如果相等则压入 0,否则压入 1。
输入/输出指令 描述
输出字符 从栈中弹出单元格,并将其作为字符打印。
输出浮点数 从栈中弹出单元格,并将其作为浮点数打印。
输入字符 从 STDIN 获取字符并压入栈中。
输入浮点数 从 STDIN 获取浮点数并压入栈中。
控制指令 描述
循环 标记循环的开始。在每次迭代的开始,从栈中弹出一个测试值。当值不为零时,循环继续。否则,循环跳转到匹配的 endloop
循环结束 标记循环的结束。

示例

这种汇编语言比大多数其他汇编语言都要简单,因为可移植性和紧凑性是两个最大的目标。因此,示例相对简单。

斐波那契数列

这个示例通过在三个变量 abc 上执行算术运算来实现斐波那契数列。为了简化输出数字,定义了一些辅助过程。

// comments are C-style
// The `stack_size` flag can ONLY be used at the top of the file.
// Anywhere else, this flag will show up as a syntax error.
// The purpose of the flag is to set the size of memory used
// outside of the statically determined memory. Any loads,
// pushes, allocs, etc. require a bit of memory on the stack.

// If this flag is not present, 256 cells are used by default.
stack_size 1024

// The start procedure is the entry point
proc start
    // Declare the registers we will use
    define a, 1
    // Push 0 and store it in 'a'
    push 0 st a
    define b, 1
    // Push 1 and store it in 'b'
    push 1 st b
    define c, 1
    // Push 0 and store it in 'c'
    push 0 st c

    // This will determine the number of times to iterate
    define n, 1
    // Push 10 and store it in 'n'
    push 10 st n

    // loop while n is not zero
    ld n
    loop
        ld a st c // c = a
        ld b st a // a = b
        ld a call print_num // print a
        ld c ld b add st b // b = c + b

        push 1
        ld n
        // subtract 1 from n
        sub
        // store the result in n again
        st n

        // Load n again for the loop test
        ld n
    endloop
endproc


proc print_num
    // the define keyword takes two arguments,
    // the name of the register and the size of
    // the newly created register.

    // This simply tells the assembler to allocate permanent
    // space for a register with a given size. It also tells
    // the assembler how many cells to pop off of the stack when
    // storing a value in this register.
    define n, 1
    
    // When we call print_num, we expect a single argument on the
    // stack. So, we store this argument in the register n for later
    // usage.
    st n

    // Now we load the value stored in n back onto the stack
    // and print the value as a number
    ld n outn

    // Now we print a newline using the newline procedure
    call nl
endproc


proc nl
    // 10 (the character code for '\n') is pushed onto the stack
    // and printed out as a character
    push 10 outc
endproc

实现

lasm 的实现非常简单:当针对新的编程语言进行实现时,要实现的指令非常少。此外,lasm 的结构在低级语言中实现起来非常简单。

对于 lasm 的实现有一些 非常重要 的注意事项

  1. lasm 的内存使用双精度浮点数组或 64 位浮点数实现。
  2. lasm 跟踪内存数组中每个单独单元格的分配和释放。这最简单的方法是使用与数据带长度相同的布尔数组。
  3. 分配超过可用内存量的行为是未定义的(如果可能,这应该导致程序退出)。
  4. 实现应始终将预留的寄存器内存标记为已分配(这样分配就不会返回寄存器内存的指针)。
  5. 预留的寄存器内存始终位于栈的 立即 之前。
  6. 累加器寄存器始终位于地址 0
  7. 栈指针寄存器始终位于地址 1
  8. 用户定义的寄存器位于栈指针寄存器和栈之间。
  9. inninc 指令在遇到 EOF 和其他输入错误时返回 0

依赖项

~2MB
~29K SLoC