ROSE 編譯器框架/內聯器
ROSE 內聯器在函式呼叫點行內函數。
行內函數是指編譯器直接將函式定義中的程式碼複製到呼叫函式的程式碼中,而不是在記憶體中建立一個單獨的指令集。這樣,就可以直接用函式體修改後的副本替換函式呼叫,從而避免了函式呼叫的效能開銷。inline 關鍵字只是向編譯器建議可以進行內聯擴充套件;編譯器可以自由地忽略此建議。
您必須啟用 EDG 5.0 來內聯 C++11 程式碼
- --enable-edg_version=5.0
按照 https://github.com/rose-compiler/rose/wiki 中的說明配置和構建 ROSE
inlineEverything -c [選項] input.c
這是一個程式轉換工具,用於在您的 C/C++ 或 Fortran 程式碼中行內函數呼叫。
用法:inlineEverything -c [選項] input.c
可選選項包括
- -skip-postprocessing:跳過清理程式碼的後處理
- -process-headers:處理標頭檔案中的呼叫
- -verbose:列印除錯資訊
- -limit N:內聯最多 N 個函式,然後停止
- -main-only:僅內聯從 main() 可到達的函式
API 和實現
// main API bool doInline(SgFunctionCallExp*, bool)
該工具的原始碼
- https://github.com/rose-compiler/rose/tree/develop/tests/nonsmoke/functional/roseTests/astInliningTests/inlineEverything.C
- 命令列處理在此原始檔中進行
216 // Main inliner code. Accepts a function call as a parameter, and inlines 217 // only that single function call. Returns true if it succeeded, and false 218 // otherwise. The function call must be to a named function, static member 219 // function, or non-virtual non-static member function, and the function 220 // must be known (not through a function pointer or member function 221 // pointer). Also, the body of the function must already be visible. 222 // Recursive procedures are handled properly (when allowRecursion is set), by 223 // inlining one copy of the procedure into itself. Any other restrictions on 224 // what can be inlined are bugs in the inliner code. 225 bool 226 doInline(SgFunctionCallExp* funcall, bool allowRecursion)
- 資格檢查:跳過無法內聯的內容
- 如果函式呼叫用作表示式運算元:例如 a = func1() + func2();
- 生成一個臨時變數以獲取返回值:例如 temp = func1();
- 將函式呼叫表示式替換為臨時變數。例如 a = temp + temp;
- 一個輕微的最佳化:如果函式呼叫是唯一的表示式運算元:例如 a= func1()。不需要臨時變數(a 可以直接使用,無需另一個臨時變數作為中間變數。)
- 獲取實際引數列表
- 複製要內聯的函式的函式體
- 重新命名行內函數定義中的標籤。指向它們的 goto 語句將被更新。
- 在函式體內
- 為每個形式引數建立一個區域性變數,用實際引數初始化每個區域性變數
- 構建一個 paramMap:將形式引數(SgInitializedName)對映到新的區域性變數(SgVariableSymbol)
- this 指標的處理方式類似:建立一個區域性變數,用呼叫者的 this 指標初始化
- 將函式體中的變數引用替換為實際引數 // ReplaceParameterUseVisitor(paramMap).traverse(funbody_copy, postorder);
- 插入一個標籤以指示行內函數體末尾 // rose_inline_end__
該演算法的侷限性不是非常乾淨
- 它生成新的區域性變數和標籤。
可以內聯的內容
- 一個命名函式,
- 靜態成員函式,或
- 限定名稱不以 "::std:: " 開頭 // 跳過 std:: 函式
- 非虛擬非靜態成員函式 // 跳過虛擬函式,靜態成員函式無法訪問 this->data(非靜態資料)。這就是我們檢查非靜態以獲取 this 指標情況的原因。
- 該函式必須已知(不是透過函式指標或成員函式指標)。// 空函式引用表示式
- 該函式的函式體必須已經在當前 AST 中可見。// 跳過函式體為空的函式
inlineEverything.C 有一個清理步驟,可以使概述的程式碼更完善
- cleanupInlinedCode(sageProject);
- 刪除未使用的標籤:SageInterface::removeUnusedLabels(top);
- 刪除跳轉到下一條語句:SageInterface::removeJumpsToNextStatement(top);
- simpleCopyAndConstantPropagation() // 在程式碼中,例如 "int foo = bar",其中 foo 和 bar 未修改,用 "bar" 替換 "foo" 並刪除宣告
- 刪除空語句:RemoveNullStatementsVisitor().traverse(top, postorder);
- 將宣告移動到首次使用:MoveDeclarationsToFirstUseVisitor().traverse(top, postorder); 例如 int x__2 =7; w= x_2 +3; ==> w=7+3;
- doSubexpressionExpansionSmart () // 用其初始化表示式替換變數的所有使用情況。要求 initname 具有賦值初始化程式 用其初始化表示式的副本替換 initname 作用域中 initname 的所有使用情況。然後刪除 initname。
- changeAllMembersToPublic(sageProject);
此步驟做了很多事情,很容易引發一些錯誤。
- 更糟糕的是,清理步驟作用於整個 AST,包括 C++ 標頭檔案。
// Post-inline AST normalizations
// DQ (6/12/2015): These functions first renames all variable (a bit heavy handed for my tastes)
// and then (second) removes the blocks that are otherwise added to support the inlining. The removal
// of the blocks is the motivation for renaming the variables, but the variable renaming is
// done evarywhere instead of just where the functions are inlined. I think the addition of
// the blocks is a better solution than the overly agressive renaming of variables in the whole
// program. So the best solution is to comment out both of these functions. All test codes
// pass (including the token-based unparsing tests).
// renameVariables(sageProject);
// flattenBlocks(sageProject);
cleanupInlinedCode(sageProject);
// In code with declarations such as "int foo = bar", where foo and bar are
// not modified, replace "foo" with "bar" and remove the declaration
void simpleCopyAndConstantPropagation(SgNode* top) {
FindReferenceVariablesVisitor().traverse(top, preorder);
FindCopiesVisitor().traverse(top, preorder);
FindUsedDeclarationsVisitor vis;
vis.traverse(top, preorder);
RemoveUnusedDeclarationsVisitor(vis.used_decls, set<SgFunctionDeclaration*>()).traverse(top, postorder);
}
帶有示例轉換器和測試輸入檔案的測試目錄
檢視 Makefile.am,示例轉換器的原始碼將在您的構建樹中生成一個名為 "inlineEverything" 的可執行檔案。
這是您可以嘗試內聯示例程式碼的工具。
- inlineEverything
相同的 Makefile.am 中的 make check 規則包含用於使用該工具的示例命令列。
要測試單個輸入檔案(例如 template_functions.C),請鍵入
- make inlineEverything_template_functions.C.passed // TODO: 更新為觸發測試的新方法
inlineEverything --help
---------------------Tool-Specific Help----------------------------------- This is a program transformation tool to inline function calls in your C/C++ or Fortran code. Usage: inlineEverything -c [options] input.c The optional options include: -skip-postprocessing: Skip postprocessing which cleanups code -process-headers: Process calls within header files -verbose: Printout debugging information
--------------input----------------
bash-4.2$ cat specimen25_1.C
template<typename T>
void swap(T& x, T& y)
{
T tmp = x;
x = y;
y = tmp;
}
int foo (int a, int b)
{
swap(a,b);
}
int main()
{
}
----- command line -------------
bash-4.2$ inlineEverything -c specimen26_1.C
-----------output: with postprocessing (cleanup) --------------
bash-4.2$ cat rose_specimen25_1.C
template < typename T >
void swap ( T & x, T & y )
{
T tmp = x;
x = y;
y = tmp;
}
int foo(int a,int b)
{
{
int tmp = a;
a = b;
b = tmp;
}
}
int main()
{
}
// output without postprocessing: cleanup
template < typename T >
void swap ( T & x, T & y )
{
T tmp = x;
x = y;
y = tmp;
}
int foo(int a,int b)
{
{
int &x__2 = a;
int &y__3 = b;
int tmp = x__2;
x__2 = y__3;
y__3 = tmp;
rose_inline_end__4:
;
}
}
int main()
{
}
make check 只檢查行內函數的數量是否與預期相同。
# Note: must use the name convention of specimenXX_N.C , in which N is the number of function calls inlined.
# The specimens are named so that the number between the "_" and next "." is the number of function calls that
# we expect this specimen to inline.
inlineEverything_specimens = \
specimen01_1.C ..
inlineEverything_test_targets = $(addprefix inlineEverything_, $(addsuffix .passed, $(inlineEverything_specimens)))
TEST_TARGETS += $(inlineEverything_test_targets)
$(inlineEverything_test_targets): inlineEverything_%.passed: % inlineEverything inlineEverything.conf
@$(RTH_RUN) \
TITLE="inlineEverything $< [$@]" \
SPECIMEN="$(abspath $<)" \
NINLINE="$$(echo $(notdir $<) |sed --regexp-extended 's/specimen[0-9]+_([0-9]+).*/\1/')" \
TRANSLATOR="$$(pwd)/inlineEverything" \
$(srcdir)/inlineEverything.conf $@
cat inlineEverything.conf
# Test configuration file (see "scripts/rth_run.pl --help" for details)
# Tests the inliner
# Run the tests in subdirectories for ease of cleanup.
subdir = yes
# Run the test and then make sure the output contains a certain string
cmd = ${VALGRIND} ${TRANSLATOR} -rose:verbose 0 ${SPECIMEN} -o a.out |tee ${TEMP_FILE_0}
cmd = grep "Test inlined ${NINLINE} function" ${TEMP_FILE_0}
cmd = cat -n rose_*
cmd = ./a.out
# Extra stuff that might be useful to specify in the makefile
title = ${TITLE}
disabled = ${DISABLED}
timeout = ${TIMEOUT}
我們使用一系列越來越複雜的示例來解釋所使用的內聯演算法。
更多示例輸入和輸出檔案可在此處獲得:
extern int x;
void incrementX()
{
x++;
}
int main()
{
incrementX();
return x;
}
//----------output, without postprocessing --------
extern int x;
void incrementX()
{
x++;
}
int main()
{
// the function body is copied here
{
x++;
rose_inline_end__2: // a label for the end of a function is generated.
;
}
return x;
}
//-----------output, with postprocessing for clean up
// unused label and empty statement are removed
extern int x;
void incrementX()
{
x++;
}
int main()
{
// the function body is copied here
{
x++;
}
return x;
}
// a function with a return
extern int x;
int incrementX()
{
x++;
return x;
}
int main()
{
incrementX();
return x;
}
//---------- output without postprocessing
// a function with a return
extern int x;
int incrementX()
{
x++;
return x;
}
int main()
{
{
x++;
{ // a return statement is translated into a block, go to the exit point in the end
x;
goto rose_inline_end__2;
}
rose_inline_end__2: // a label for the end of the function: the exit point
;
}
return x;
}
//-------- with postprocessing, the code look the same
// a function with a return
extern int x;
int incrementX()
{
x++;
return x;
}
int main()
{
{
x++;
{
goto rose_inline_end__2;
}
rose_inline_end__2:
;
}
return x;
}
// input code -----------------------
int foo(int i) {
return 5+i;
}
int main(int, char**) {
int w;
w = foo(1)+ foo(2);
return w;
}
//--------------after inlining-----------------
// You can see that a temparory variable is used to capture the returned value of a function call.
// Then the temp variable is used to replace the original function call expression
int foo(int i)
{
return 5 + i;
}
int main(int ,char **)
{
int w;
int rose_temp__4;
{
int i__2 = 1;
{
rose_temp__4 = 5 + i__2;
goto rose_inline_end__3;
}
rose_inline_end__3:
;
}
int rose_temp__8;
{
int i__6 = 2;
{
rose_temp__8 = 5 + i__6;
goto rose_inline_end__7;
}
rose_inline_end__7:
;
}
w = rose_temp__4 + rose_temp__8;
return w;
}
//----- postprocessing does not simplify the code any further
int foo(int i)
{
return 5 + i;
}
int main(int ,char **)
{
int rose_temp__4;
{
{
rose_temp__4 = 5 + 1;
goto rose_inline_end__3;
}
rose_inline_end__3:
;
}
int rose_temp__8;
{
{
rose_temp__8 = 5 + 2;
goto rose_inline_end__7;
}
rose_inline_end__7:
;
}
int w = rose_temp__4 + rose_temp__8;
return w;
}
最佳化的轉換
- 不要盲目生成一個臨時變數來捕獲函式呼叫的值。
- 而是直接在函式體內重複使用 lhs 變數的原始宣告。
int foo(int i) {
return 5+i;
}
int main(int, char**) {
int w;
w = foo(1);
return w;
}
//-------------after inlining ----------
int foo(int i)
{
return 5 + i;
}
int main(int ,char **)
{
int w;
{
int i__2 = 1;
{
w = 5 + i__2;
goto rose_inline_end__3;
}
rose_inline_end__3:
;
}
return w;
}
//postprocessing does not simplify the code further.
int foo(int i)
{
return 5 + i;
}
int main(int ,char **)
{
int w;
{
{
w = 5 + 1;
goto rose_inline_end__3;
}
rose_inline_end__3:
;
}
return w;
}
程式碼已標準化。
#include <stdlib.h>
int foo() {
exit (1);
return 0;
}
int main(int, char**) {
int w, x = 7;
w = x == 8 ? foo() : 0;
return w;
}
//----------- after inlining ---------------
#include <stdlib.h>
int foo()
{
exit(1);
return 0;
}
int main(int ,char **)
{
int w;
int x = 7;
if (x == 8) {
int rose_temp__4;
{
exit(1);
{
rose_temp__4 = 0;
goto rose_inline_end__2;
}
rose_inline_end__2:
;
}
w = rose_temp__4;
}
else {
w = 0;
}
return w;
}
#include <vector>
typedef int Index_t ;
struct Domain
{
public:
// non-reference type
Index_t numNode() { return m_numNode ; }
void AllocateNodeElemIndexes()
{
Index_t numNode = this->numNode() ;
}
#if 0 // the best inline result should look like the following
void AllocateNodeElemIndexes_inlined()
{
Index_t numNode = m_numNode; // call site 1 inlined
}
#endif
private:
Index_t m_numNode ;
} domain;
//----------------------------after inlining ----------------
#include <vector>
typedef int Index_t;
struct Domain {
// non-reference type
inline Index_t numNode()
{
return (this) -> m_numNode;
}
inline void AllocateNodeElemIndexes()
{
//x. split declaration + initializer into two parts
// a temporary variable to transfer value of initializer
Index_t rose_temp__3;
//x. a new code block to embed the function body
{
struct Domain *this__1 = this__1;
{
rose_temp__3 = this__1 -> m_numNode;
//x. goto the label after function call
goto rose_inline_end__2;
}
//x. label after the function call
rose_inline_end__2:
;
}
Index_t numNode = rose_temp__3;
}
Index_t m_numNode;
} domain;
--------------input----------------
bash-4.2$ cat specimen25_1.C
template<typename T>
void swap(T& x, T& y)
{
T tmp = x;
x = y;
y = tmp;
}
int foo (int a, int b)
{
swap(a,b);
}
int main()
{
}
----- command line -------------
bash-4.2$ inlineEverything -c -skip-postprocessing specimen26_1.C
-----------output: with postprocessing (cleanup) --------------
// output without postprocessing: cleanup
template < typename T >
void swap ( T & x, T & y )
{
T tmp = x;
x = y;
y = tmp;
}
int foo(int a,int b)
{
{
int &x__2 = a; // local variables for each formal arguments, initialized with actual arguments
int &y__3 = b;
int tmp = x__2; // variable references are replace with the local variables
x__2 = y__3;
y__3 = tmp;
rose_inline_end__4: // a label to indicate the end of the outlined function body.
;
}
}
int main()
{
}
//------------input
int foo(int x) {
return x + 3;
}
int bar(int y) {
return foo(y) + foo(2);
}
int main(int, char**) {
int w;
w = bar(1);
return 0;
}
//--------------- output, no postprocessing ------------
int
foo (int x)
{
return x + 3;
}
int
bar (int y)
{
int rose_temp__4;
{
int x__2 = y;
{
rose_temp__4 = x__2 + 3;
goto rose_inline_end__3;
}
rose_inline_end__3:
;
}
int rose_temp__8;
{
int x__6 = 2;
{
rose_temp__8 = x__6 + 3;
goto rose_inline_end__7;
}
rose_inline_end__7:
;
}
return rose_temp__4 + rose_temp__8;
}
int
main (int, char **)
{
int w;
{
int y__10 = 1;
int rose_temp__4;
{
int x__2 = y__10;
{
rose_temp__4 = x__2 + 3;
goto rose_inline_end__3__1;
}
rose_inline_end__3__1:
;
}
int rose_temp__8;
{
int x__6 = 2;
{
rose_temp__8 = x__6 + 3;
goto rose_inline_end__7__2;
}
rose_inline_end__7__2:
;
}
{
w = rose_temp__4 + rose_temp__8;
goto rose_inline_end__11;
}
rose_inline_end__11:
;
}
return 0;
}
有關如何呼叫內聯 API 的官方文件是
- 第 36 章“呼叫 ROSE 的內聯器”教程:http://rosecompiler.org/ROSE_Tutorial/ROSE-Tutorial.pdf
TEST inlineEverything ../../../../../../sourcetree/tests/nonsmoke/functional/roseTests/astInliningTests/template_functions.C [inlineEverything_template_functions.C.passed]
inlineEverything_template_functions.C [out]: 注意:使用 EDG 4.9 配置和 GNU 編譯器 4.9 及更高版本(使用 EDG 4.12 配置 ROSE)不支援 C++11 輸入檔案到 ROSE。