首页 > 其他分享 >编译实践学习 Part3

编译实践学习 Part3

时间:2024-03-24 19:44:06浏览次数:23  
标签:std ast 实践 class BinaryExpAST 编译 Part3 template public

License: CC BY-SA 4.0

闲话

看了半天文档终于懂了 Bison 里怎么处理带 | 的语法了。

为什么 info 要做成 Emacs 格式啊?Vimer 无能狂怒(

Lv 3.1

一元表达式

首先当然是设计 AST 了。

这里我用 std::varient,不知道有没有更优雅的写法。

class PrimaryExpAST: public BaseAST{
public:
	std::variant<std::unique_ptr<BaseAST>, int> inside_exp;
    // ...
};

然后就是修改 sysy.y.

PrimaryExp:
	"(" Exp ")" { 
		...
	} | Number {
		...
	};

生成 Koopa IR

一开始生成的是这样的东西:

fun @main():i32{
  %entry:
    %0 = 6
    %1 = ne 0, %0
    %2 = sub 0, %1
    %3 = sub 0, %2
    %4 = %3
    ret %4
}

然后报错了.

发现 koopa ir 中不能出现 %4=%3 这样的废话。

查询 Koopa IR 规范, 你会发现, Koopa IR 并不支持一元运算, 而只支持如下的二元运算:

...

这是因为, 目前已知有意义的一元操作均可用二元操作表示:

...

所以,万事开头难,中间难,结尾也难(

解决方法就是在生成 IR 时避免这样的废话。

本来想用 %4 = add 0, 3 这样的废话的,但是感觉不大优雅,就换成了 class 包装的值,用 std::stack 保存。

发现不知道为什么重载不了 operator<<,于是大力上了 template.

class Koopa_val{
private:
	int val;
	bool is_im;
public:
	Koopa_val(bool typ, int x){
		is_im = typ;
		val = x;
	}
	std::string get_str(){
		if(is_im){
			return std::to_string(val);
		} else {
			return std::string("%") + std::to_string(val);
		}
	}
	template<typename T>
	friend T &operator<<(T &outstr, const Koopa_val& me){
		if(me.is_im){
			outstr << me.val;
		} else {
			outstr << "%" << me.val;
		}
		return outstr;
	}
};

重构 AST

感觉之前写的 AST 都是 std::shared_ptr<BaseAST>,容易搞不清楚 BaseAST 到底是哪种。

于是将 .hpp 中的内容拆了一部分(output)到 .cpp 里。

然后一通查找+替换。

输出汇编

你需要注意:

在 C/C++ 中, 并没有 %0 = ...%1 = ... 这样的结构. 前一条 eq 指令和后一条 sub 指令的指针会被按照顺序, 先后存放在 %entry 基本块中的指令列表中. sub 指令中出现的 %0, 表示在内存形式中实际上就是一个指向前一条 eq 指令的指针.

参考了 Gold_stein 的文章,用 unordered_map 记录值。

写到一半发现 IDE 报错了。

为什么不支持 C++20 的特性?哦原来是我 CMakeLists.txt 里写了用 C++17. 改了.

但是 LunarVim IDE 里还是会报错,看着很烦,一通搜索后发现只要在项目目录里加一个 compile_flags.txt 然后里面写 -std=c++20 就行了。

最后还是多用了两个编译器。不想修了,留到 lv4 吧(

Lv 3.2

设计 AST

感觉后面还会有一大堆的二元运算……

它们的代码都是相似的,能不能稍微偷点懒呢?

template 大法好!

template<const int level>
class BinaryExpAST: public BaseAST{
public:
	std::optional<std::unique_ptr<BinaryExpAST<level>>> now_level;
	std::optional<std::unique_ptr<BinaryOpAST>> binary_op;
	std::unique_ptr<BinaryExpAST<level-1>> nxt_level;
	void output(Ost &outstr, std::string prefix) const override;
};

别忘了给最后一级做个特化。

template<>
class BinaryExpAST<0>: public BaseAST{
public:
	std::optional<std::unique_ptr<BinaryExpAST<0>>> now_level;
	std::optional<std::unique_ptr<BinaryOpAST>> binary_op;
	std::unique_ptr<UnaryExpAST> nxt_level;
	void output(Ost &outstr, std::string prefix) const override;
};

然后发现编译不了了。

/usr/bin/ld: CMakeFiles/compiler.dir/sysy.tab.cpp.o:(.rodata._ZTVN8Ast_Base8Ast_Defs12BinaryExpASTILi1EEE[_ZTVN8Ast_Base8Ast_Defs12BinaryExpASTILi1EEE]+0x20): undefined reference to `Ast_Base::Ast_Defs::BinaryExpAST<1>::output(std::__cxx11::basic_ostringstream<char, std::char_traits<char>, std::allocator<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) const'

一通 STFW & RTFM 后发现 Bison 里调用模板并不会(在 cpp 中)生成对应的模板代码,因此编译器找不到 BinaryExpAST<1> 对应的 output 函数,然后就报错了。

原先的解决方法很暴力:

BinaryExpAST<1> foo;

然后又是一通 STFW 后发现:

template class BinaryExpAST<1>;

我就说 cpp 不可能只有一种解决方案

然后发现每个模板都要写一次……

问 ChatGPT 后得到了如下的做法。

template<int level> class BinaryExpAST: 
public BinaryExpAST_Base<BinaryExpAST<level>, BinaryExpAST<level-1>>{ 
public:
	static void instantiate(){
		BinaryExpAST<level> sth;
		BinaryExpAST<level-1>::instantiate();
	}
};

template<> class BinaryExpAST<0>: 
public BinaryExpAST_Base<BinaryExpAST<0>, UnaryExpAST>{ 
public:
	static void instantiate(){ }
};

template class BinaryExpAST_Base<BinaryExpAST<0>, UnaryExpAST>;

auto init_hook = []() {
    BinaryExpAST<3>::instantiate();
    return 0;
}();

至少能跑了

Bison 中“或”的语法

这节出现了这样的东西:

AddExp      ::= MulExp | AddExp ("+" | "-") MulExp

很显然 Bison 是不支持直接用括号的。怎么办呢?

AddOp: ADD	{ $$ = new BinaryOpAST(OP_ADD); } ;
SubOp: SUB	{ $$ = new BinaryOpAST(OP_SUB); } ;
MulOp: MUL	{ $$ = new BinaryOpAST(OP_MUL); } ;
DivOp: DIV	{ $$ = new BinaryOpAST(OP_DIV); } ;
ModOp: MOD	{ $$ = new BinaryOpAST(OP_MOD); } ;

Lv1Op: AddOp | SubOp ;

AddExp:
	MulExp
	| AddExp Lv1Op MulExp {
		auto ast = new BinaryExpAST<1>();
		ast->now_level = cast_ast<BinaryExpAST<1>>($1);
		ast->binary_op = cast_ast<BinaryOpAST>($2);
		ast->nxt_level = cast_ast<BinaryExpAST<0>>($3);
		$$ = ast;
	} 
	;

Lv 3.3

前面打好基础后后面应该就容易了(

还是老流程:设计 AST,更新 Lexer & Parser,输出 Koopa IR,输出 RISC-V.

设计 AST

定义了几个别名:

using MulExp = BinaryExpAST<0>;
using AddExp = BinaryExpAST<1>;
using RelExp = BinaryExpAST<2>;
using EqExp = BinaryExpAST<3>;
using LAndExp = BinaryExpAST<4>;
using LOrExp = BinaryExpAST<5>;

然后没了。

更新 Lexer & Parser

Bison 里没找到用宏的方式,只能手打了……

输出 Koopa IR

注意逻辑运算符的输出只能是 \(0\) 或 \(1\).

实现方法就是加一个 is_logic_op() 函数,然后特判。

输出汇编

不出意外的话把几个 KOOPA_RBO_ 开头的指令加进去就行了。

不出意外的话应该是出意外了。在 -riscv 上 WA+AE.

首先就是由于偷懒没有用 x0 寄存器而导致在 test 06,27 上寄存器超标,然后还有 19,20 WA 了.

WA 的两个点到 /opt/bin/testcases 里找到了数据点,发现汇编生成错了.

超标的那个就留到 lv4 吧(咕咕咕

标签:std,ast,实践,class,BinaryExpAST,编译,Part3,template,public
From: https://www.cnblogs.com/x383494/p/18092891

相关文章

  • 【Linux应用开发】gcc编译过程
            gcc是一个c编译器,​可以将源代码转换为可执行程序。编译过程包括了预处理、编译、汇编和链接这四个阶段。预处理(Preprocessing):在预处理阶段,源代码会经过预处理器的处理,包括展开宏定义、包含头文件、条件编译等操作。预处理器会生成一个经过预处理的中间文件......
  • gcc编译步骤与常用参数
    1.gcc编译步骤与常用参数1.1.编译步骤源码hello.c只有寥寥几行代码#include<stdio.h>intmain(void){printf("hello\n");}执行-E预处理,得到hello.i,生成了很长的.i文件-S编译helloc.s,这一步是最重要的,得到的反汇编文件,可以看出很多问题:-c汇编得到hello.......
  • FFmpeg开发笔记(八)Linux交叉编译Android的FFmpeg库
    ​《FFmpeg开发实战:从零基础到短视频上线》一书的“12.1.2 交叉编译Android需要的so库”介绍了如何在Windows环境交叉编译Android所需FFmpeg的so库,接下来介绍如何在Linux环境交叉编译Android所需FFmpeg的so库。1、下载Linux版本的android-ndk-r21e登录Linux服务器(比如华为云的......
  • langchain Chatchat 学习实践(二)——实现对Ollama的支持
    1、采用Langchain的Ollama库,新建get_BaseChatModel方法,按照名称返回ChatOllama或ChatOpenAI实例;2、在model_config.py.example中添加了ollama相关配置,用于设置ollama模型名称和ollama部署发布地址;3、在chat.py,knowledge_base_chat.py,file_chat.py,search_engine_chat.py,ag......
  • 深入剖析Java中的“==”与“equals”:不同之处及实践
    引言比较在任何编程语言中都是基本操作,Java提供了“==”运算符和“equals()”方法进行比较,它们在比较对象时有着本质的区别。“==”:引用数据类型与基本数据类型比较对于基本数据类型,如int、double等“==”比较的是值本身。对于引用数据类型,如String、Arrays等,它比较的......
  • LaTex学习实践(简易快速LaTex上手例子)
    目录前言正文完全参考前言这篇博客完全是博客https://blog.csdn.net/NSJim/article/details/109066847?spm=1001.2014.3001.5506的实践产物因为写的太好了,所以我进行了实践(overleaf平台)所有的代码和图片我已上传,下载后,上传到自己的overleaf平台即可编......
  • vue3 动态编译组件失败:Component provided template option but runtime compilation
    根据vue3官方文档路由,写了如下一个简单的页面来模拟路由的实现。为了减少*.vue文件的个数,在这个但页面中,使用defineComponent通过object定义组件。<scriptsetup>import{ref,computed,defineComponent}from'vue'constHome=defineComponent({template:`......
  • 政安晨:【深度学习实践】【使用 TensorFlow 和 Keras 为结构化数据构建和训练神经网络
    政安晨的个人主页:政安晨欢迎 ......
  • 自己编译RustDesk,并将自建ID服务器和key信息写入客户端
    前言:搭建RustDesk编译环境    今天总算是把编译环境给折腾清楚了,编译出来了至少能用,但说不上好用,问题还不少,官方的客户端就是要手工填写ID服务器地址和key才可以用,而且还容易被别人白嫖你搭建的服务器,当然如果拿到你编译后的客户端,也是存在被白嫖的可能。这方面还没......
  • ros2:手动编译包
    首先需要colcon库支持sudoaptinstallpython3-colcon-common-extensions github上拉个包(这里使用示例程序)gitclonehttps://github.com/ros2/examplessrc/examples-bfoxy其中-bfoxy代表选择foxy版本分支 编译colconbuild 进入包所在目录cd/src/ex......