Eterm
catch表達式
1> catch 1+2.
3
2> catch 1+a.
{'EXIT',{badarith,[...]}}
3> catch throw(hello).
hello
源碼分析
-module(test). -compile(export_all). t() -> catch erlang:now(). |
{function, t, 0, 2}. {label,1}. {line,[{location,"test.erl",4}]}. {func_info,{atom,test},{atom,t},0}. {label,2}. {allocate,1,0}. {'catch',{y,0},{f,3}}. {line,[{location,"test.erl",5}]}. {call_ext,0,{extfunc,erlang,now,0}}. {label,3}. {catch_end,{y,0}}. {deallocate,1}. return. |
1> c(test). {ok,test} 2> erts_debug:df(test). ok |
04B55938: i_func_info_IaaI 0 test t 0 04B5594C: allocate_tt 1 0 04B55954: catch_yf y(0) f(0000871B) 04B55960: call_bif_e erlang:now/0 04B55968: catch_end_y y(0) 04B55970: deallocate_return_Q 1 |
// beam_emu.c
OpCase(catch_yf):
c_p->catches++; // catches數量加1
yb(Arg(0)) = Arg(1); // 把catch指針地址存入進程棧,即f(0000871B)
Next(2); // 執行下一條指令
// beam_emu.c
OpCase(catch_end_y): {
c_p->catches--; // 進程 catches數減1
make_blank(yb(Arg(0))); // 將catch立即數的值置NIL,數據將會丟掉
if (is_non_value(r(0))) { // 如果異常出現
if (x(1) == am_throw) { // 如果是 throw(Term),返回 Term
r(0) = x(2);
} else {
if (x(1) == am_error) { // 如果是 error(Term), 再帶上當前堆棧的信息
SWAPOUT;
x(2) = add_stacktrace(c_p, x(2), x(3));
SWAPIN;
}
/* only x(2) is included in the rootset here */
if (E - HTOP < 3 || c_p->mbuf) { /* Force GC in case add_stacktrace()
* created heap fragments */
// 檢查進程堆空間不足,執行gc避免出現堆外數據
SWAPOUT;
PROCESS_MAIN_CHK_LOCKS(c_p);
FCALLS -= erts_garbage_collect(c_p, 3, reg+2, 1);
ERTS_VERIFY_UNUSED_TEMP_ALLOC(c_p);
PROCESS_MAIN_CHK_LOCKS(c_p);
SWAPIN;
}
r(0) = TUPLE2(HTOP, am_EXIT, x(2));
HTOP += 3;
}
}
CHECK_TERM(r(0));
Next(1); // 執行下一條指令
}
//beam_hot.h
OpCase(deallocate_return_Q):
{
DeallocateReturn(Arg(0));//釋放分配的棧空間,返回上一個CP指令地址(注:CP是返回地址指針)
}
DeallocateReturn實際是個宏,代碼如下:#define DeallocateReturn(Deallocate) \
do { \
int words_to_pop = (Deallocate); \
SET_I((BeamInstr *) cp_val(*E)); \ // 解析當前棧的指令地址,即獲取上一個CP指令地址
E = ADD_BYTE_OFFSET(E, words_to_pop); \
CHECK_TERM(r(0)); \
Goto(*I); \//執行的指令
} while (0)
// erl_term.h
#define make_catch(x) (((x) << _TAG_IMMED2_SIZE) | _TAG_IMMED2_CATCH) // 轉成catch立即樹
#define is_catch(x) (((x) & _TAG_IMMED2_MASK) == _TAG_IMMED2_CATCH) // 是否catch立即數
這兩個是 catch立即數的生成和判定,後面的代碼會提到這兩個宏的使用。// beam_load.c 加載beam過程,有刪節
static void final_touch(LoaderState* stp)
{
int i;
int on_load = stp->on_load;
unsigned catches;
Uint index;
BeamInstr* code = stp->code;
Module* modp;
/*
* 申請catch索引,填補catch_yf指令
* 前面的f(0000871B)就在這裏產生的,指向了beam_catches結構數據
* 因爲一個catch立即數放不了整個beam_catches數據,就只放了指針
*/
index = stp->catches;
catches = BEAM_CATCHES_NIL;
while (index != 0) { //遍歷所有的catch_yf指令
BeamInstr next = code[index];
code[index] = BeamOpCode(op_catch_yf); // 指向catch_yf指令的opcode地址
// 獲取 catch_end 指令地址,構造beam_catches結構數據
catches = beam_catches_cons((BeamInstr *)code[index+2], catches);
code[index+2] = make_catch(catches); // 將beam_catches索引位置轉成 catch立即數
index = next;
}
modp = erts_put_module(stp->module);
modp->curr.catches = catches;
/*
* ....
*/
}
再來看下什麼時候會執行到 這裏的代碼。// 執行匿名函數
OpCase(i_apply_fun): {
BeamInstr *next;
SWAPOUT;
next = apply_fun(c_p, r(0), x(1), reg);
SWAPIN;
if (next != NULL) {
r(0) = reg[0];
SET_CP(c_p, I+1);
SET_I(next);
Dispatchfun();
}
goto find_func_info; // 遇到錯誤走這裏
}
// 數學運算錯誤,或檢查錯誤就會走這裏
lb_Cl_error: {
if (Arg(0) != 0) { // 如果帶了 label地址,就執行 jump指令
OpCase(jump_f): { // 這裏就是 jump實現代碼
jump_f:
SET_I((BeamInstr *) Arg(0));
Goto(*I);
}
}
ASSERT(c_p->freason != BADMATCH || is_value(c_p->fvalue));
goto find_func_info; // 遇到錯誤走這裏
}
// 等待消息超時
OpCase(i_wait_error): {
c_p->freason = EXC_TIMEOUT_VALUE;
goto find_func_info; // 遇到錯誤走這裏
}
好了,再看下 find_func_info 究竟是什麼神通?/* Fall through here */
find_func_info: {
reg[0] = r(0);
SWAPOUT;
I = handle_error(c_p, I, reg, NULL); // 獲取異常錯誤指令地址
goto post_error_handling;
}
post_error_handling:
if (I == 0) { // 等待下次調度 erl_exit(),拋出異常中斷
goto do_schedule;
} else {
r(0) = reg[0];
ASSERT(!is_value(r(0)));
if (c_p->mbuf) { // 存在堆外消息數據,執行gc
erts_garbage_collect(c_p, 0, reg+1, 3);
}
SWAPIN;
Goto(*I); // 執行指令
}
}
然後,簡單看下 handle_error函數。// erl_emu.c VM處理異常函數
static BeamInstr* handle_error(Process* c_p, BeamInstr* pc, Eterm* reg, BifFunction bf)
{
Eterm* hp;
Eterm Value = c_p->fvalue;
Eterm Args = am_true;
c_p->i = pc; /* In case we call erl_exit(). */
ASSERT(c_p->freason != TRAP); /* Should have been handled earlier. */
/*
* Check if we have an arglist for the top level call. If so, this
* is encoded in Value, so we have to dig out the real Value as well
* as the Arglist.
*/
if (c_p->freason & EXF_ARGLIST) {
Eterm* tp;
ASSERT(is_tuple(Value));
tp = tuple_val(Value);
Value = tp[1];
Args = tp[2];
}
/*
* Save the stack trace info if the EXF_SAVETRACE flag is set. The
* main reason for doing this separately is to allow throws to later
* become promoted to errors without losing the original stack
* trace, even if they have passed through one or more catch and
* rethrow. It also makes the creation of symbolic stack traces much
* more modular.
*/
if (c_p->freason & EXF_SAVETRACE) {
save_stacktrace(c_p, pc, reg, bf, Args);
}
/*
* Throws that are not caught are turned into 'nocatch' errors
*/
if ((c_p->freason & EXF_THROWN) && (c_p->catches <= 0) ) {
hp = HAlloc(c_p, 3);
Value = TUPLE2(hp, am_nocatch, Value);
c_p->freason = EXC_ERROR;
}
/* Get the fully expanded error term */
Value = expand_error_value(c_p, c_p->freason, Value);
/* Save final error term and stabilize the exception flags so no
further expansion is done. */
c_p->fvalue = Value;
c_p->freason = PRIMARY_EXCEPTION(c_p->freason);
/* Find a handler or die */
if ((c_p->catches > 0 || IS_TRACED_FL(c_p, F_EXCEPTION_TRACE))
&& !(c_p->freason & EXF_PANIC)) {
BeamInstr *new_pc;
/* The Beam handler code (catch_end or try_end) checks reg[0]
for THE_NON_VALUE to see if the previous code finished
abnormally. If so, reg[1], reg[2] and reg[3] should hold the
exception class, term and trace, respectively. (If the
handler is just a trap to native code, these registers will
be ignored.) */
reg[0] = THE_NON_VALUE;
reg[1] = exception_tag[GET_EXC_CLASS(c_p->freason)];
reg[2] = Value;
reg[3] = c_p->ftrace;
if ((new_pc = next_catch(c_p, reg))) { // 從進程棧上找到最近的 catch
c_p->cp = 0; /* To avoid keeping stale references. */
return new_pc; // 返回 catch end 指令地址
}
if (c_p->catches > 0) erl_exit(1, "Catch not found");
}
ERTS_SMP_UNREQ_PROC_MAIN_LOCK(c_p);
terminate_proc(c_p, Value);
ERTS_SMP_REQ_PROC_MAIN_LOCK(c_p);
return NULL; // 返回0,就是執行 erl_exit()
}
問題討論
爲什麼catch要放在進程棧,然後利用立即數實現。1、異常中斷處理
2、catch多層嵌套
問題延伸
進程堆與進程棧
首先,erlang VM的基本調度單位是erlang進程。如果執行某段代碼,就要有運行erlang進程來執行。爲什麼我們可以在shell下肆無忌憚地運行代碼,實際上我們看到的是由shell實現進程執行後返回給我們的結果。
try-catch尾遞歸
t() -> try do_something(), t() catch _:_ -> ok end. |
erlang:hibernate
hibernate(M, F, A) when is_atom(M), is_atom(F), is_list(A) -> erlang:hibernate(?MODULE, wake_up, [M, F, A]). wake_up(M, F, A) when is_atom(M), is_atom(F), is_list(A) -> try apply(M, F, A) catch _Class:Reason -> exit(Reason) end. |