erlang catch和try 底层实现剖析

跟try有关的外部通用指令有四条,分别是

{"try", 2, -1, 0, 7521}, //特定指令id为-1,指令转换操作的地址偏移为7521
{"try_end", 1, 461, 1, -1}, //对应特定指令id为461 
{"try_case", 1, 459, 1, -1}, //对应特定指令id为459 
{"try_case_end", 1, 460, 1, -1}, //对应特定指令id为460

我们发现try指令对应的特定指令为-1,也就是说try指令不是直接转换为特殊指令的,而是经由加载器进行一系列操作 ,操作的地址偏移为7521。其对应的操作如下:

TOP_rename, 62 //TOP_rename 的实现在beam_load.c中: 
case TOP_rename: 
	instr->op = op = *pc++; //op 赋值为62 
	instr->arity = gen_opc[op].arity;//gen_opc[62]  为 {"catch", 2, 36, 1, -1}, 
	return TE_OK;

也就是说,try外部通用指令会被转换为catch指令。
跟catch有关的外部通用指令有两条,分别是:

{"catch", 2, 36, 1, -1},   //对应的特定指令id为36 
{"catch_end", 1, 37, 1, -1}, //对应的特定指令id为37

catch特定指令的源码实现如下:

#define yb(N) (*ADD_BYTE_OFFSET(E, N))// E 为  c_p->stop 即进程堆栈空间的结束位置 
#define ADD_BYTE_OFFSET(ptr, offset)  ((Eterm *) (((unsigned char *)ptr) + (offset))) 

OpCase(catch_yf)://有删减 
{   
	BeamInstr next_pf = BeamCodeAddr(I[3]); //获取下一条指令的地址   
	c_p->catches++;//cp是当前进程的结构,包含当前进程的各种信息(见 erl_process.h process定义)   
	yb(I[1]) = I[2];//I[1] 和 I[2] 参见下文的final_touch函数执行完毕后的 codev[ci+1],codev[ci+2]   
	GotoPF(next_pf);//执行下一条指令 
}

catch_end特定指令的源码实现如下:

OpCase(catch_end_y)://有删减 
{   
	BeamInstr next_pf = BeamCodeAddr(I[1]);  //获取下一条指令的地址   
	c_p->catches--; //cp对catch的记录减一   
	make_blank(yb(BeamExtraData(I[0])));;//记录当前 catch_end地址的配对的catch指令的记录置空   
	if (is_non_value(r(0))) {//如果上条指令执行的结果,不是正常的返回值(也就是抛错了)     
		if (x(1) == am_throw) {       
			r(0) = x(2);//如果是 throw 的抛错,则返回值为 throw(Info) 中的 Info     
		} else {       
			if (x(1) == am_error) {//如果是 error 的抛错         
				x(2) = add_stacktrace(c_p, x(2), x(3)); //记录错误到cp的跟踪堆栈中       
			}       
		//此处省略了剩余空间判断,以及空间不足时进行垃圾回收的逻辑       
		//生成 {'EXIT',当前进程跟踪堆栈信息},作为返回值       
		r(0) = TUPLE2(HTOP, am_EXIT, x(2));     
		}   
	}   
GotoPF(next_pf);//执行下一条指令 
}

try_end特定指令的源码实现如下:

OpCase(try_end_y)://有删减 
{   
	BeamInstr next_pf = BeamCodeAddr(I[1]);//获取下一条指令的地址   
	c_p->catches--;//进程结构里的catch记录减一 
	make_blank(yb(BeamExtraData(I[0])));//记录当前 catch_end地址的配对的catch指令的记录置空   
	GotoPF(next_pf);//执行下一条指令 
}

try_case特定指令的源码实现如下:

//try catch语句
try Exprs of
    Pattern1 [when GuardSeq1] ->
        Body1;
    ...;
    PatternN [when GuardSeqN] ->
        BodyN
catch
    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
        ExceptionBody1;
    ...;
    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
        ExceptionBodyN
after
    AfterBody
end
OpCase(try_case_y)://有删减 
{   
	BeamInstr next_pf = BeamCodeAddr(I[1]);//获取下一条指令的地址   
	c_p->catches--;//进程结构里的catch记录减一   
	make_blank(yb(BeamExtraData(I[0])));;//记录当前 catch_end地址的配对的catch指令的记录置空   
	//x(1)的值为抛错类型: error throw exit,即上述结构中的 ClassN   
	//x(2)的值为抛错内容,如 throw(Info) 中的Info,即上述结构中的  Exception   
	//x(3)的值为构建Stacktrace的编码信息,Stacktrace = build_stacktrace(c_p,  x(3))   
	r(0) = x(1);//r(0)为x(0),此三条命令调整返回值顺序   
	x(1) = x(2);   
	x(2) = x(3);   
	GotoPF(next_pf);//执行下一条指令 
}

try_case_end特定指令的源码实现如下:

OpCase(try_case_end_s): //当 try catch 语句的 Pattern 全都匹配不上时,执行的操作
{
  Eterm targ1;
  GetSource(I[1], targ1); //获取x(I[1])或y(I[1])中的数据赋值给targ1
  {
    c_p->fvalue = targ1;//cp进程结构 记录错误信息
    c_p->freason = EXC_TRY_CLAUSE;//cp进程结构 记录错误原因
    goto find_func_info;//执行 find_func_info 指令
  }
}

加载器在加载到catch指令时,会做一些额外的处理:

//code为虚拟机执行的指令数组,可以理解为代码区 
case op_catch_yf: 
	//此时codev内容为: 
	//codev[ci-3]:catch_yf指令或catch_yf指令地址 
	//codev[ci-2]:catch配对的结束指令地址存储在y(X),中的X 
	//codev[ci-1]:catch配对的结束指令的label值,即存储在y(X)中的数据     
	code[ci-3] = stp->catches; // stp->catches记录了上一条catch指令在code中的偏移     
	stp->catches = ci-3; // 将当前catch在code中的偏移记录到stp->catches 
	//此时codev内容为: 
	//codev[ci-3]:上一条catch指令在code中的偏移 
	//codev[ci-2],codev[ci-1]:同上     
	break;

之后加载器在执行freeze_code函数的逻辑时,会调整 codev[ci-1] 的值为 catch结束指令和catch开始指令的地址差值
最后加载器在加载完毕时调用 erts_finish_loading 函数 → 调用 final_touch 函数,其中有逻辑:

index = stp->catches;//获取最后一条catch指令,在codev中的偏移 
catches = BEAM_CATCHES_NIL; 
while (index != 0) { 
	//此时codev[index]:上一条catch指令在code中的偏移 
	BeamInstr next = codev[index]; 
	codev[index] = ((BeamInstr)(op_catch_yf));// 向 code[偏移] 写入catch指令 
	BeamInstr* abs_addr; 
	//此时codev内容为: 
	//codev[index]:((BeamInstr)(op_catch_yf)) 
	//codev[index+1]:catch配对的结束指令地址存储在y(X),中的X 
	//codev[index+2]:catch结束指令和catch开始指令的地址差值     
	abs_addr = (BeamInstr *)codev + index + codev[index+2]; //得到catch结束指令地址 
	catches = beam_catches_cons(abs_addr, catches); 
	//beam_catches_cons 将 abs_addr和catches 记录在一个全局数组中,并返回数组序号 
	codev[index+2] = make_catch(catches);//将catches左移六位后,低6位赋值为011011,即标记当前Eterm为catch类型 
	//此时codev[ci+2]:标记为catch的Eterm, 其值右移6位后,得到值是一个索引,根据该索引可查询到code[ci]对应的catch结束语句所在的位置 
	index = next; 
} 
inst_p->catches = catches;

当程序抛错时,查询catch结束位置的核心逻辑如下:

next_catch(Process* c_p, Eterm *reg) { 
	int active_catches = c_p->catches > 0; //判断当前进程记录的catch数是否大于0 
	Eterm *ptr, *prev = NULL; 
	ptr = c_p->stop;//注: y[0] 即是 c_p->stop[0] 
	while (ptr < c_p->hend) {//注:进程初始化时, hend = heap+sz, stop = hend -1, htop = heap 
		if (is_catch(*ptr)) {//遍历栈区,获取第一个类型位catch的Eterm 
			if (active_catches) beam_catches_car(catch_val((*ptr)));//根据该Eterm,查询abs_addr(见上文) 
			ptr++;
		} 
		else ptr++; 
	} 
return NULL;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值