# simple-rvcc **Repository Path**: MuMuNan/simple-rvcc ## Basic Information - **Project Name**: simple-rvcc - **Description**: simple riscv compiler - follow https://github.com/sunshaoce/rvcc-course - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-04-02 - **Last Updated**: 2023-04-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 算数表达式的解析基本成型 词法分析形成tokens链表 语法分析形成语法树 最终遍历语法树生成代码 优先级和结合性其实已经暗含在文法和具体实现代码之中,这就是神奇的地方,不需要专门花费精力处理 \ \ \ makefile使用 make rvcc会生成riscvv汇编代码(直接gcc编译运行) - 所以这个rvcc就是个编译器! (./rvcc "1+2-3" 查看是否正确生成代码) RISCV=~riscv make test命令会测试这段汇编代码 - 测试编译生成的代码是否正确 main.c就是生成汇编,test.sh测试汇编代码 04课改进报错信息,可以多./rvcc expr尝试些错误的表达式,会指出错误的地方 实际应用的两个接口 errorAt & errorTok,前者用于token还未生成的时候 局限性:通过空格偏移的方式,只适用于ASCII码,对Unicode不一定能正确指向错误位置 效果 ./rvcc 1++1 1++1 ^ expect a number 06课加上一元的+和-,不是靠词法分析,而是靠语法分析的文法 10课加上变量,语法分析加上的文法简单,加上个assign就行 语义分析就要考虑变量保存在哪/怎么获取等问题 parse阶段所有变量先都保存成一个链表,最终codegen阶段先计算出所有local variables数量 然后在栈上预先分配空间,还是通过地址来读取变量内容 12课加上return语句,其实就是加上个标签,遇到return就直接跳转(词法解析就加上关键字解析,语法解析加上对return语句的识别,代码生成加上一个跳转标签) 本节课引入Function结构体,可以认为目前整个程序就是个匿名函数,为之后支持函数做准备 13课引入block语句块 不过暂时不处理作用域问题,局部变量仍然还是一并存储(只是单纯文法上加上{}) 16课 支持for语句 之前几个自以为是的设定这里集中暴露出问题来了 比如空语句的问题,我的设计是直接跳过,之后改成返回个空语句 但是在for语句的匹配中就问题很大 像作者那样直接让ExprStmt中能匹配空语句,这样for语句的匹配就变得很容易 不过这样经历也好,就能感受到其中设计的精妙 目前空语句可以用ND_EMPTY_STMT表示,也可以就是Node*为NULL(genStmt遇到不生成任何代码) 19课 每个Node也都加入对应终结符,提升报错质量 类似有while if这样开头的就用这些终结符进行报错位置提示 20课 支持一元的&和×运算符 文法上修改简单,unary中加入两种新操作符即可 生成代码也简单,已经有genAddr,加上根据addr获取变量即可 居然想了很久才自己想明白,目前设计的原因, ND_VAR能够直接&取地址,ND_DEREF能够和ND_ADDR抵消,但是ND_ADDR不能直接&取地址(是当前变量存储设计的问题) 多多画图看看,更清晰! 21课 指针运算 上节课中的指针加减其实还不是真正指针运算,要真正实现指针运算需要加上个类型系统 之前设计的Node存储格式和原作者不一样,这里递归程序写起来好像就有问题了 主要修改就是修改对+ -的处理,原先只考虑两个整数之间,现在可能有指针介入指针 其实也不难,就是ptr+1转换成纯加法ptr+sizeof(elem),所以parse阶段就搞定了,之后仍是正常加减运算,没有指针的概念了 22课 int定义变量 这节课开始文法应该会有点区别,因为crafting interpreters是动态类型,变量都是'var'定义 其实主要也就是处理下可能的指针变量的定义 目前int a=..=.., b=..=.., c=..=..; 整体形成一个blockStmt,每个逗号分隔的形成一个exprStmt 23课 调用外部定义的函数 codegen部分改动其实简单,直接记录函数名,然后call 函数名即可(栈上多保存了一个ra) 注意函数调用是作为一个expr出现的,不管是parse还是codegen都注意(所以parse时要解析成一个exprStmt) 24课 调用最多6个参数的函数 codegen部分也简单,就是把参数放在a0-a5这6个寄存器中,然后call fname 其他文法上修改简单 25课 支持零参数函数的定义 如今整个Program是一系列函数 主要是文法上的改动 codegen部分改动不大,原先只生成一个main函数,现在改成生成多个函数段的代码 作者还专门弄了个T_FUNC类型,不过我这没用上也能运行了,看看到底有啥用 26课 支持最多6个参数的函数定义 函数参数传递原理:通过a0-a5几个寄存器传递参数,所以函数在编译时参数也要加入局部变量列表 最终函数调用时,把a0-a5的数值传递给相应的局部变量即可(相关局部变量和其他局部变量一样都已经在栈上分配了位置) 我的实现还是没用上T_FUNC类型 感觉作者的匹配文法有点怪 感觉需要梳理下了,整体感觉有点乱了 把语法树的结构大概梳理下,代码生成大概思路梳理下 现在统一规定,所有语句块全都放在Body中(for-body func-body block-body),所以addType中对语句块Body要遍历 而exprStmt/returnStmt/&语句/deref语句这些的sub-expr都放在Lhs 变量和成员大写开头,函数用驼峰命名 之后复习可以看着简单笔记,看看能不能把整个过程回忆起来 我发现好像还没有语义分析这个阶段 注意: 好像编译器课程很多都是从解析算数表达式开始 注意这里其实没有释放分配的内存,因为编译器运行时间不长, 可以结束后让操作系统回收内存,不释放对性能也帮助,并且有助于程序的简单性 发现很容易犯一个错,老忘记str(n)cmp是==0才表示相等