GNU C 編譯器內部/函式呼叫 4 1
檔案中的函式用於在檔案 cgraphunit.c 中生成一個呼叫圖。兩個相關的函式是 cgraph_finalize_compilation_unit(),它在解析完檔案後從函式 pop_file_scope() 呼叫,以及 cgraph_finalize_function(),它從 finish_function() 呼叫。
每個函式的效果取決於編譯模式。一次一個單元模式指示編譯器僅在解析完每個函式後才構建呼叫圖。當此選項不存在時,函式將在解析後立即轉換為 RTL。
cgraph_finalize_function() 呼叫 cgraph_analyze_function(),後者將其轉換為 RTL。否則,該函式將在 cgraph_nodes_queue 中排隊。最後,cgraph_finalize_compilation_unit() 處理佇列。cgraph_nodes 是表示呼叫圖的全域性變數。函式 dump_cgraph 允許列印呼叫圖。
在本章中,我們將瞭解函式如何相互呼叫。通常,函式在進行呼叫時會傳遞多個引數。當函式開始時會建立一個棧幀。但是,前一個函式的棧幀可能會被重用。這種型別的函式呼叫稱為兄弟呼叫。當函式體不夠大時,設定棧幀的執行時開銷過高。在這種情況下,被呼叫函式將被內聯到父函式中。
函式 expand_call() 接收 CALL_EXPR 樹並生成 RTL 表示式。它必須決定引數傳遞模式。struct arg_data 包含每個引數的必要資訊。
| 欄位名稱 | 解釋 |
| tree tree_value | 此引數的樹節點 |
| enum machine_mode mode | 值的模式 |
| rtx value | 引數的當前 RTL 值,如果未預計算則為 0 |
| rtx initial_value | 引數的初始計算 RTL 值;僅適用於 const 函式。 |
| rtx reg | 用於傳遞此引數的暫存器,如果在棧上傳遞則為 0 |
| rtx tail_call_reg | 在生成尾呼叫序列時用於傳遞此引數的暫存器 |
| rtx parallel_value | 如果 REG 是 PARALLEL,則這是 VALUE 的副本,被拉入 emit_group_move 的正確形式。 |
| int unsignedp | 如果 REG 從引數表示式的實際模式提升而來,則表示提升是符號擴充套件還是零擴充套件 |
| int partial | 要放入暫存器的位元組數。0 表示將整個區域放入暫存器或不放入暫存器。 |
| int pass_on_stack | 如果引數必須在棧上傳遞則不為零。請注意,即使 pass_on_stack 為零,某些引數也可能在棧上傳遞,僅僅因為 FUNCTION_ARG 指示這樣做 |
| struct locate_and_pad_arg_data locate | 為 locate_and_pad_parm 打包的一些欄位 |
| rtx stack | 應該儲存引數的棧上的位置 |
| rtx stack_slot | 此引數槽的起始位置的棧上的位置 |
| rtx save_area | 如果需要,此棧區域已被儲存的位置 |
| rtx *aligned_regs | 如果引數的對齊方式不允許直接複製到暫存器,則將較小尺寸的部分複製到偽暫存器。這些儲存在由此欄位指向的塊中。 |
| int n_aligned_regs | 表示我們建立了多少個字長偽暫存器 |
在沒有某些機器特定的資訊的情況下,生成函式呼叫是不可能的,例如不同型別的硬體暫存器的數量。在每個體系結構的 .h 檔案中定義的許多宏負責將中端與後端連線起來
| 宏名稱 | 解釋 |
| INIT_CUMULATIVE_ARGS | 為呼叫資料型別為 FNTYPE 的函式初始化 CUMULATIVE_ARGS 資料結構。 |
| FUNCTION_ARG | 定義將引數放到哪裡。值為零表示將引數壓入棧,或將引數儲存其中的一個硬體暫存器。 |
| FUNCTION_ARG_ADVANCE | 更新 CUM 中的資料以在引數上進行前進。 |
檔案 i386.c 中的函式 init_cumulative_args() 處理 x86 體系結構的情況。它考慮了使用者可能指定的函式屬性 regparm 和 fastcall,在這種情況下,可用暫存器的數量將相應地設定。但是,如果函式接受可變數量的引數,則所有引數都將透過棧傳遞。
引數的位置是在函式 initialize_argument_information() 中決定的。機器特定的 function_arg() 函式將返回引數的 rtl,如果它進入暫存器
ret = gen_rtx_REG (mode, regno);
引數可能會在棧和暫存器中傳遞,例如,如果引數型別是可定址的。
根據某些條件,除了正常鏈之外,還會生成兄弟呼叫指令鏈。讓我們考慮只生成正常鏈的情況。變數 rtx argblock 是為棧引數預分配的空間的地址(在沒有 push 指令的機器上),或者如果未預分配空間則為 0。
許多機器特定的變數決定了棧的形狀。ACCUMULATE_OUTOING_ARGS 指示編譯器在函式前導中為任何函式呼叫的所有引數預分配足夠的位元組數。之後,函式引數將儲存在該區域中,而不會修改棧幀的大小。ACCUMULATE_OUTOING_ARGS 取決於變數 target_flags。它取決於機器配置和命令列選項。在 ACCUMULATE_OUTOING_ARGS 的情況下,i386 特定的變數
const int x86_accumulate_outgoing_args = m_ATHLON_K8 | m_PENT4 | m_NOCONA | m_PPRO;
和一個命令列選項 -maccumulate-outgoing-args 使能了此功能。這意味著它在 Pentium4 上啟用,並且不使用 push/pop 指令來傳遞函式引數。
如果我們預分配了棧空間,請計算每個引數的地址並將其儲存到 ARGS 陣列中。
根據需要預計算函式呼叫的引數。此例程將填充每個預計算引數的 INITIAL_VALUE 和 VALUE 欄位。precompute_arguments()
給定 FNDECL 和 EXP,返回一個適合用作呼叫指令中目標地址的 rtx
funexp = rtx_for_function_call (fndecl, addr);
預計算所有暫存器引數。一旦我們開始填充任何特定的硬體暫存器,計算任何東西就不安全了。precompute_register_parameters (num_actuals, args, ®_parm_seen);
現在儲存(如果需要,還計算)所有非暫存器引數。這些引數位於暫存器引數之前,因為它們可能需要塊移動,這可能會破壞用於暫存器引數的暫存器。部分暫存器引數不會在這裡儲存,但如果它們需要,我們會在此處預分配空間。
store_one_arg (&args[i], argblock, flags,
adjusted_args_size.var != 0,
reg_parm_stack_space)
對任何完全暫存器引數或在棧和暫存器中傳遞的引數執行所需的暫存器載入。它們的表示式已經計算出來了。
load_register_parameters (args, num_actuals, &call_fusage, flags,
pass == 0, &sibcall_failure);
最後,emit_call_1() 生成指令來呼叫函式 FUNEXP,並可選地彈出結果。CALL_INSN 是生成的第一個指令。
當決定引數的位置時,變數 struct args_size args_size 會儲存棧引數的總大小。它將一系列引數的大小記錄為樹表示式和常數的總和。樹部分對於處理可變大小的引數(例如,在編譯時大小未知的陣列引數)是必需的。C 語言不允許可變大小的引數。
有人可能會想知道被呼叫函式是如何找到引數到達位置的。它也使用呼叫者使用的機器特定資訊。在被呼叫者中重新執行 INIT_CUMULATIVE_ARGS、FUNCTION_ARG 和 FUNCTION_ARG_ADVANCE 可以決定引數是否應該在暫存器或棧中到達,與 expand_call 相同。