继上一篇【一个编程语言的设计笔记(一)】
每一种编程语言都有类似的逻辑:先提供一个底层框架,然后在这个框架上面发挥,形成各种各样的东西
而不同底层框架的编程语言也会有不同的功用
oir的目的是的作为一个底层语言的同时,也可以作为高阶语言使用
也就是oir的底层框架需要能够表示底层逻辑,也能表示高级语言的高级特性
高层和底层公用一套底层逻辑的好处就是每次ir的处理都不必完全重新生成一遍ir,仅仅需要在原有的基础上增量修改即可
暴露堆栈虽然能极限性地增加性能,但是栈的暴露意味着编译器难以自动分配变量,变量是放在栈还是放在寄存器这个问题会变得难解,因为栈已经被用户自定义了
oir之前是这样设计的 https://blog.csdn.net/qq_49757011/article/details/108239672
后来发现这种结构让编译器分配变量实在是太复杂了
虽然话是这样说,但是也要保留一定可以考虑的余地,分配虽然复杂但是不是没可能,oir有强大的可扩展性,这种功能的加入不是不可以
编译目标语言的流程:读ir,然后根据ir生成目标语言
优化ir代码的流程:读ir,根据ir修改ir
高层ir翻译成低层ir的流程:读ir,根据ir修改ir
综上可知:
每次对ir的处理都需要读取ir对ir的处理可能修改ir
因为ir是增量存储的,故不能像普通的对象那样直接读取
ir的读取者需要一套统一的接口来读取ir,提供这个统一接口的就可以是一种读取器,这种读取器需要根据一个读取目标来决定应该把那张错综复杂的ir增量网络里的哪种状态的网络呈现给读取者
同样因为ir是增量存储的,ir的修改也需要修改器一类的东西代理操作
有些ir的修改完全没有必要保留原版,这种情况就可以采用直接修改的方案而不是增量修改来节约计算机资源
修改器可以设置修改目标和修改方案
为了让ir的元编程能力变强,元编程可以直接执行ir指令来修改、生成其他ir,而且很多操作都是编码期即时完成的
比如我需要一个vector<int>,我就可以在ide里面直接把int当作参数塞进vector函数的参数里,然后点一下按钮他就会弹出来一个叫vector<int>的返回值
ir的基本组成就是ir各种指令和值之类的东西应该如何以何种模式组织起来,形成哪种对象的引用关系
这个目的更偏向于让读者理解,此时这个读者可能是正在写代码的程序员,也可能是需要优化代码的ir优化器,还可能是编译期一类的东西
考虑到读者更希望一眼就能看出ir中各种零件的依赖、引用、包含等逻辑关系,ir就需要以一种清晰的对象形式摆在内存里,最终让人类和机器都能很快地理解
对于人类,经常会面对把一个函数中的某个部分分离出去作为另一个函数的工作,这时候理清各种代码的逻辑关系就显得重要了起来
一种关系是并行和串行的关系,根据代码的依赖关系把代码分离成并行和串行的代码两种不同的结构,虽然大部分的这类代码实际上都编译成串行执行,但是却给了更多让编译器能够更容易理解代码行为并产生相应优化的机会。
这时候,线程的产生和线程的等待就成了语言的内置内容
除了理清小程序里的逻辑关系外,这种功能还可以拿来控制程序的大型线程,不同线程间不同内容之间的关系清晰明了,对多线程开放极其友好
众所周知,llvm是不支持这种异步的结构的,为了编译成llvm的ir,满足oir底层的代码不一定对于其他代码是底层的,还是需要“ir处理”来翻译的,翻译之后,一般流程无法串行化的异步代码就会被编译成基于线程池的异步代码
记得在本人的上一篇笔记【一个编程语言的设计笔记(一)】中我已经提到过使用节点结构表示指令,例如:
…… -> [ 指令1 ] -> [ 指令2 ] -> [ 指令3 ] ->……
如今细入研究并串时,如果纯考指令链组织程序的话,在获取某个指令时,快速获取其上层的并串模式就和变得极其困难:需要去回溯指令链,寻找产生这个线程的分支点。
解决方法就是在分支点后的每个指令上都保存上上个分支点的引用,告诉访问者这个指令的线程是由那条指令产生的。但是这样仍然会在每条指令的末端产生大量的List以保存<>可能为一个以上的分支指令,造成大量的资源浪费。
有一个方法可以同时解决这两个问题:把分支的全部指令都抽象成一个指令:
这样能明显解决并串行含糊不清的问题,但是清晰度的增加也让特殊结构难以实现,比如大型线程的管理就需要另辟蹊径。但是不能不承认并行代码指令化确实让ir的条理更清楚了,因为明确了各种异步链的起始点和终点。同时这样做也符合利于读者解读的设计目的。