3atv精品不卡视频,97人人超碰国产精品最新,中文字幕av一区二区三区人妻少妇,久久久精品波多野结衣,日韩一区二区三区精品

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

使用 LLVM 实现一个简单编译器

發布時間:2024/2/28 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用 LLVM 实现一个简单编译器 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者:tomoyazhang,騰訊 PCG 后臺開發工程師

1. 目標

這個系列來自 LLVM 的Kaleidoscope 教程,增加了我對代碼的注釋以及一些理解,修改了部分代碼。現在開始我們要使用 LLVM 實現一個編譯器,完成對如下代碼的編譯運行。

#?斐波那契數列函數定義 def?fib(x)if?x?<?3?then1elsefib(x?-?1)?+?fib(x?-?2)fib(40)#?函數聲明 extern?sin(arg) extern?cos(arg) extern?atan2(arg1?arg2)#?聲明后的函數可調用 atan2(sin(.4),?cos(42))

這個語言稱為 Kaleidoscope, 從代碼可以看出,Kaleidoscope 支持函數、條件分支、數值計算等語言特性。為了方便,Kaleidoscope 唯一支持的數據類型為 float64, 所以示例中的所有數值都是 float64。

2. Lex

編譯的第一個步驟稱為 Lex, 詞法分析,其功能是將文本輸入轉為多個 tokens, 比如對于如下代碼:

atan2(sin(.4),?cos(42))

就應該轉為:

tokens?=?["atan2",?"(",?"sin",?"(",?.4,?")",?",",?"cos",?"(",?42,?")",?")"]

接下來我們使用 C++來寫這個 Lexer, 由于這是教程代碼,所以并沒有使用工程項目應有的設計:

//?如果不是以下5種情況,Lexer返回[0-255]的ASCII值,否則返回以下枚舉值 enum?Token?{TOKEN_EOF?=?-1,?????????//?文件結束標識符TOKEN_DEF?=?-2,?????????//?關鍵字defTOKEN_EXTERN?=?-3,??????//?關鍵字externTOKEN_IDENTIFIER?=?-4,??//?名字TOKEN_NUMBER?=?-5???????//?數值 };std::string?g_identifier_str;??//?Filled?in?if?TOKEN_IDENTIFIER double?g_number_val;???????????//?Filled?in?if?TOKEN_NUMBER//?從標準輸入解析一個Token并返回 int?GetToken()?{static?int?last_char?=?'?';//?忽略空白字符while?(isspace(last_char))?{last_char?=?getchar();}//?識別字符串if?(isalpha(last_char))?{g_identifier_str?=?last_char;while?(isalnum((last_char?=?getchar())))?{g_identifier_str?+=?last_char;}if?(g_identifier_str?==?"def")?{return?TOKEN_DEF;}?else?if?(g_identifier_str?==?"extern")?{return?TOKEN_EXTERN;}?else?{return?TOKEN_IDENTIFIER;}}//?識別數值if?(isdigit(last_char)?||?last_char?==?'.')?{std::string?num_str;do?{num_str?+=?last_char;last_char?=?getchar();}?while?(isdigit(last_char)?||?last_char?==?'.');g_number_val?=?strtod(num_str.c_str(),?nullptr);return?TOKEN_NUMBER;}//?忽略注釋if?(last_char?==?'#')?{do?{last_char?=?getchar();}?while?(last_char?!=?EOF?&amp;&amp;?last_char?!=?'\n'?&amp;&amp;?last_char?!=?'\r');if?(last_char?!=?EOF)?{return?GetToken();}}//?識別文件結束if?(last_char?==?EOF)?{return?TOKEN_EOF;}//?直接返回ASCIIint?this_char?=?last_char;last_char?=?getchar();return?this_char; }

使用 Lexer 對之前的代碼處理結果為(使用空格分隔 tokens):

def?fib?(?x?)?if?x?<?3?then?1?else?fib?(?x?-?1?)?+?fib?(?x?-?2?)?fib?(?40?)?extern?sin?(?arg?) extern?cos?(?arg?)?extern?atan2?(?arg1?arg2?)?atan2?(?sin?(?0.4?)?,?cos?(?42?)?)

Lexer 的輸入是代碼文本,輸出是有序的一個個 Token。

3. Parser

編譯的第二個步驟稱為 Parse, 其功能是將 Lexer 輸出的 tokens 轉為 AST (Abstract Syntax Tree)。我們首先定義表達式的 AST Node:

//?所有?`表達式`?節點的基類 class?ExprAST?{public:virtual?~ExprAST()?{} };//?字面值表達式 class?NumberExprAST?:?public?ExprAST?{public:NumberExprAST(double?val)?:?val_(val)?{}private:double?val_; };//?變量表達式 class?VariableExprAST?:?public?ExprAST?{public:VariableExprAST(const?std::string&amp;?name)?:?name_(name)?{}private:std::string?name_; };//?二元操作表達式 class?BinaryExprAST?:?public?ExprAST?{public:BinaryExprAST(char?op,?std::unique_ptr<ExprAST>?lhs,std::unique_ptr<ExprAST>?rhs):?op_(op),?lhs_(std::move(lhs)),?rhs_(std::move(rhs))?{}private:char?op_;std::unique_ptr<ExprAST>?lhs_;std::unique_ptr<ExprAST>?rhs_; };//?函數調用表達式 class?CallExprAST?:?public?ExprAST?{public:CallExprAST(const?std::string&amp;?callee,std::vector<std::unique_ptr<ExprAST>>?args):?callee_(callee),?args_(std::move(args))?{}private:std::string?callee_;std::vector<std::unique_ptr<ExprAST>>?args_; };

為了便于理解,關于條件表達式的內容放在后面,這里暫不考慮。接著我們定義函數聲明和函數的 AST Node:

//?函數接口 class?PrototypeAST?{public:PrototypeAST(const?std::string&amp;?name,?std::vector<std::string>?args):?name_(name),?args_(std::move(args))?{}const?std::string&amp;?name()?const?{?return?name_;?}private:std::string?name_;std::vector<std::string>?args_; };//?函數 class?FunctionAST?{public:FunctionAST(std::unique_ptr<PrototypeAST>?proto,std::unique_ptr<ExprAST>?body):?proto_(std::move(proto)),?body_(std::move(body))?{}private:std::unique_ptr<PrototypeAST>?proto_;std::unique_ptr<ExprAST>?body_; };

接下來我們要進行 Parse, 在正式 Parse 前,定義如下函數方便后續處理:

int?g_current_token;??//?當前待處理的Token int?GetNextToken()?{return?g_current_token?=?GetToken(); }

首先我們處理最簡單的字面值:

//?numberexpr?::=?number std::unique_ptr<ExprAST>?ParseNumberExpr()?{auto?result?=?std::make_unique<NumberExprAST>(g_number_val);GetNextToken();return?std::move(result); }

這段程序非常簡單,當前 Token 為 TOKEN_NUMBER 時被調用,使用 g_number_val,創建一個 NumberExprAST, 因為當前 Token 處理完畢,讓 Lexer 前進一個 Token, 最后返回。接著我們處理圓括號操作符、變量、函數調用:

//?parenexpr?::=?(?expression?) std::unique_ptr<ExprAST>?ParseParenExpr()?{GetNextToken();??//?eat?(auto?expr?=?ParseExpression();GetNextToken();??//?eat?)return?expr; }///?identifierexpr ///???::=?identifier ///???::=?identifier?(?expression,?expression,?...,?expression?) std::unique_ptr<ExprAST>?ParseIdentifierExpr()?{std::string?id?=?g_identifier_str;GetNextToken();if?(g_current_token?!=?'(')?{return?std::make_unique<VariableExprAST>(id);}?else?{GetNextToken();??//?eat?(std::vector<std::unique_ptr<ExprAST>>?args;while?(g_current_token?!=?')')?{args.push_back(ParseExpression());if?(g_current_token?==?')')?{break;}?else?{GetNextToken();??//?eat?,}}GetNextToken();??//?eat?)return?std::make_unique<CallExprAST>(id,?std::move(args));} }

上面代碼中的 ParseExpression 與 ParseParenExpr 等存在循環依賴,這里按照其名字理解意思即可,具體實現在后面。我們將 NumberExpr、ParenExpr、IdentifierExpr 視為 PrimaryExpr, 封裝 ParsePrimary 方便后續調用:

///?primary ///???::=?identifierexpr ///???::=?numberexpr ///???::=?parenexpr std::unique_ptr<ExprAST>?ParsePrimary()?{switch?(g_current_token)?{case?TOKEN_IDENTIFIER:?return?ParseIdentifierExpr();case?TOKEN_NUMBER:?return?ParseNumberExpr();case?'(':?return?ParseParenExpr();default:?return?nullptr;} }

接下來我們考慮如何處理二元操作符,為了方便,Kaleidoscope 只支持 4 種二元操作符,優先級為:

'<'?<?'+'?=?'-'?<?'*'

即'<'的優先級最低,而'*'的優先級最高,在代碼中實現為:

//?定義優先級 const?std::map<char,?int>?g_binop_precedence?=?{{'<',?10},?{'+',?20},?{'-',?20},?{'*',?40}};//?獲得當前Token的優先級 int?GetTokenPrecedence()?{auto?it?=?g_binop_precedence.find(g_current_token);if?(it?!=?g_binop_precedence.end())?{return?it->second;}?else?{return?-1;} }

對于帶優先級的二元操作符的解析,我們會將其分成多個片段。比如一個表達式:

a?+?b?+?(c?+?d)?*?e?*?f?+?g

首先解析 a, 然后處理多個二元組:

[+,?b],?[+,?(c+d)],?[*,?e],?[*,?f],?[+,?g]

即,復雜表達式可以抽象為一個 PrimaryExpr 跟著多個[binop, PrimaryExpr]二元組,注意由于圓括號屬于 PrimaryExpr, 所以這里不需要考慮怎么特殊處理(c+d),因為會被 ParsePrimary 自動處理。

//?parse //???lhs?[binop?primary]?[binop?primary]?... //?如遇到優先級小于min_precedence的操作符,則停止 std::unique_ptr<ExprAST>?ParseBinOpRhs(int?min_precedence,std::unique_ptr<ExprAST>?lhs)?{while?(true)?{int?current_precedence?=?GetTokenPrecedence();if?(current_precedence?<?min_precedence)?{//?如果當前token不是二元操作符,current_precedence為-1,?結束任務//?如果遇到優先級更低的操作符,也結束任務return?lhs;}int?binop?=?g_current_token;GetNextToken();??//?eat?binopauto?rhs?=?ParsePrimary();//?現在我們有兩種可能的解析方式//????*?(lhs?binop?rhs)?binop?unparsed//????*?lhs?binop?(rhs?binop?unparsed)int?next_precedence?=?GetTokenPrecedence();if?(current_precedence?<?next_precedence)?{//?將高于current_precedence的右邊的操作符處理掉返回rhs?=?ParseBinOpRhs(current_precedence?+?1,?std::move(rhs));}lhs?=std::make_unique<BinaryExprAST>(binop,?std::move(lhs),?std::move(rhs));//?繼續循環} }//?expression //???::=?primary?[binop?primary]?[binop?primary]?... std::unique_ptr<ExprAST>?ParseExpression()?{auto?lhs?=?ParsePrimary();return?ParseBinOpRhs(0,?std::move(lhs)); }

最復雜的部分完成后,按部就班把 function 寫完:

//?prototype //???::=?id?(?id?id?...?id) std::unique_ptr<PrototypeAST>?ParsePrototype()?{std::string?function_name?=?g_identifier_str;GetNextToken();std::vector<std::string>?arg_names;while?(GetNextToken()?==?TOKEN_IDENTIFIER)?{arg_names.push_back(g_identifier_str);}GetNextToken();??//?eat?)return?std::make_unique<PrototypeAST>(function_name,?std::move(arg_names)); }//?definition?::=?def?prototype?expression std::unique_ptr<FunctionAST>?ParseDefinition()?{GetNextToken();??//?eat?defauto?proto?=?ParsePrototype();auto?expr?=?ParseExpression();return?std::make_unique<FunctionAST>(std::move(proto),?std::move(expr)); }//?external?::=?extern?prototype std::unique_ptr<PrototypeAST>?ParseExtern()?{GetNextToken();??//?eat?externreturn?ParsePrototype(); }

最后,我們為頂層的代碼實現匿名 function:

//?toplevelexpr?::=?expression std::unique_ptr<FunctionAST>?ParseTopLevelExpr()?{auto?expr?=?ParseExpression();auto?proto?=?std::make_unique<PrototypeAST>("",?std::vector<std::string>());return?std::make_unique<FunctionAST>(std::move(proto),?std::move(expr)); }

頂層代碼的意思是放在全局而不放在 function 內定義的一些執行語句比如變量賦值,函數調用等。編寫一個 main 函數:

int?main()?{GetNextToken();while?(true)?{switch?(g_current_token)?{case?TOKEN_EOF:?return?0;case?TOKEN_DEF:?{ParseDefinition();std::cout?<<?"parsed?a?function?definition"?<<?std::endl;break;}case?TOKEN_EXTERN:?{ParseExtern();std::cout?<<?"parsed?a?extern"?<<?std::endl;break;}default:?{ParseTopLevelExpr();std::cout?<<?"parsed?a?top?level?expr"?<<?std::endl;break;}}}return?0; }

編譯:

clang++?main.cpp?`llvm-config?--cxxflags?--ldflags?--libs`

輸入如下代碼進行測試:

def?foo(x?y)x?+?foo(y,?4)def?foo(x?y)x?+?yyextern?sin(a)

得到輸出:

parsed?a?function?definition parsed?a?function?definition parsed?a?top?level?expr parsed?a?extern

至此成功將 Lexer 輸出的 tokens 轉為 AST。

4. Code Generation to LLVM IR

終于開始 codegen 了,首先我們 include 一些 LLVM 頭文件,定義一些全局變量:

#include?"llvm/ADT/APFloat.h" #include?"llvm/ADT/STLExtras.h" #include?"llvm/IR/BasicBlock.h" #include?"llvm/IR/Constants.h" #include?"llvm/IR/DerivedTypes.h" #include?"llvm/IR/Function.h" #include?"llvm/IR/IRBuilder.h" #include?"llvm/IR/LLVMContext.h" #include?"llvm/IR/LegacyPassManager.h" #include?"llvm/IR/Module.h" #include?"llvm/IR/Type.h" #include?"llvm/IR/Verifier.h" #include?"llvm/Support/TargetSelect.h" #include?"llvm/Target/TargetMachine.h" #include?"llvm/Transforms/InstCombine/InstCombine.h" #include?"llvm/Transforms/Scalar.h" #include?"llvm/Transforms/Scalar/GVN.h"//?記錄了LLVM的核心數據結構,比如類型和常量表,不過我們不太需要關心它的內部 llvm::LLVMContext?g_llvm_context; //?用于創建LLVM指令 llvm::IRBuilder<>?g_ir_builder(g_llvm_context); //?用于管理函數和全局變量,可以粗淺地理解為類c++的編譯單元(單個cpp文件) llvm::Module?g_module("my?cool?jit",?g_llvm_context); //?用于記錄函數的變量參數 std::map<std::string,?llvm::Value*>?g_named_values;

然后給每個 AST Class 增加一個 CodeGen 接口:

//?所有?`表達式`?節點的基類 class?ExprAST?{public:virtual?~ExprAST()?{}virtual?llvm::Value*?CodeGen()?=?0; };//?字面值表達式 class?NumberExprAST?:?public?ExprAST?{public:NumberExprAST(double?val)?:?val_(val)?{}llvm::Value*?CodeGen()?override;private:double?val_; };

首先實現 NumberExprAST 的 CodeGen:

llvm::Value*?NumberExprAST::CodeGen()?{return?llvm::ConstantFP::get(g_llvm_context,?llvm::APFloat(val_)); }

由于 Kaleidoscope 只有一種數據類型 FP64, 所以直接調用 ConstantFP 傳入即可,APFloat 是 llvm 內部的數據結構,用于存儲 Arbitrary Precision Float. 在 LLVM IR 中,所有常量是唯一且共享的,所以這里使用的 get 而不是 new/create。

然后實現 VariableExprAST 的 CodeGen:

llvm::Value*?VariableExprAST::CodeGen()?{return?g_named_values.at(name_); }

由于 Kaleidoscope 的 VariableExpr 只存在于函數內對函數參數的引用,我們假定函數參數已經被注冊到 g_name_values 中,所以 VariableExpr 直接查表返回即可。

接著實現 BinaryExprAST, 分別 codegen lhs, rhs 然后創建指令處理 lhs, rhs 即可:

llvm::Value*?BinaryExprAST::CodeGen()?{llvm::Value*?lhs?=?lhs_->CodeGen();llvm::Value*?rhs?=?rhs_->CodeGen();switch?(op_)?{case?'<':?{llvm::Value*?tmp?=?g_ir_builder.CreateFCmpULT(lhs,?rhs,?"cmptmp");//?把?0/1?轉為?0.0/1.0return?g_ir_builder.CreateUIToFP(tmp,?llvm::Type::getDoubleTy(g_llvm_context),?"booltmp");}case?'+':?return?g_ir_builder.CreateFAdd(lhs,?rhs,?"addtmp");case?'-':?return?g_ir_builder.CreateFSub(lhs,?rhs,?"subtmp");case?'*':?return?g_ir_builder.CreateFMul(lhs,?rhs,?"multmp");default:?return?nullptr;} }

實現 CallExprAST:

llvm::Value*?CallExprAST::CodeGen()?{//?g_module中存儲了全局變量/函數等llvm::Function*?callee?=?g_module.getFunction(callee_);std::vector<llvm::Value*>?args;for?(std::unique_ptr<ExprAST>&amp;?arg_expr?:?args_)?{args.push_back(arg_expr->CodeGen());}return?g_ir_builder.CreateCall(callee,?args,?"calltmp"); }

實現 ProtoTypeAST:

llvm::Value*?PrototypeAST::CodeGen()?{//?創建kaleidoscope的函數類型?double?(doube,?double,?...,?double)std::vector<llvm::Type*>?doubles(args_.size(),llvm::Type::getDoubleTy(g_llvm_context));//?函數類型是唯一的,所以使用get而不是new/createllvm::FunctionType*?function_type?=?llvm::FunctionType::get(llvm::Type::getDoubleTy(g_llvm_context),?doubles,?false);//?創建函數,?ExternalLinkage意味著函數可能不在當前module中定義,在當前module//?即g_module中注冊名字為name_,?后面可以使用這個名字在g_module中查詢llvm::Function*?func?=?llvm::Function::Create(function_type,?llvm::Function::ExternalLinkage,?name_,?&amp;g_module);//?增加IR可讀性,設置function的argument?nameint?index?=?0;for?(auto&amp;?arg?:?func->args())?{arg.setName(args_[index++]);}return?func; }

實現 FunctionAST:

llvm::Value*?FunctionAST::CodeGen()?{//?檢查函數聲明是否已完成codegen(比如之前的extern聲明),?如果沒有則執行codegenllvm::Function*?func?=?g_module.getFunction(proto_->name());if?(func?==?nullptr)?{func?=?proto_->CodeGen();}//?創建一個Block并且設置為指令插入位置。//?llvm?block用于定義control?flow?graph,?由于我們暫不實現control?flow,?創建//?一個單獨的block即可llvm::BasicBlock*?block?=llvm::BasicBlock::Create(g_llvm_context,?"entry",?func);g_ir_builder.SetInsertPoint(block);//?將函數參數注冊到g_named_values中,讓VariableExprAST可以codegeng_named_values.clear();for?(llvm::Value&amp;?arg?:?func->args())?{g_named_values[arg.getName()]?=?&amp;arg;}//?codegen?body然后returnllvm::Value*?ret_val?=?body_->CodeGen();g_ir_builder.CreateRet(ret_val);llvm::verifyFunction(*func);return?func; }

至此,所有 codegen 都已完成,修改 main:

int?main()?{GetNextToken();while?(true)?{switch?(g_current_token)?{case?TOKEN_EOF:?return?0;case?TOKEN_DEF:?{auto?ast?=?ParseDefinition();std::cout?<<?"parsed?a?function?definition"?<<?std::endl;ast->CodeGen()->print(llvm::errs());std::cerr?<<?std::endl;break;}case?TOKEN_EXTERN:?{auto?ast?=?ParseExtern();std::cout?<<?"parsed?a?extern"?<<?std::endl;ast->CodeGen()->print(llvm::errs());std::cerr?<<?std::endl;break;}default:?{auto?ast?=?ParseTopLevelExpr();std::cout?<<?"parsed?a?top?level?expr"?<<?std::endl;ast->CodeGen()->print(llvm::errs());std::cerr?<<?std::endl;break;}}}return?0; }

輸入測試:

4?+?5def?foo(a?b)a*a?+?2*a*b?+?b*bfoo(2,?3)def?bar(a)foo(a,?4)?+?bar(31337)extern?cos(x)cos(1.234)

得到輸出:

parsed?a?top?level?expr define?double?@0()?{ entry:ret?double?9.000000e+00 }parsed?a?function?definition define?double?@foo(double?%a,?double?%b)?{ entry:%multmp?=?fmul?double?%a,?%a%multmp1?=?fmul?double?2.000000e+00,?%a%multmp2?=?fmul?double?%multmp1,?%b%addtmp?=?fadd?double?%multmp,?%multmp2%multmp3?=?fmul?double?%b,?%b%addtmp4?=?fadd?double?%addtmp,?%multmp3ret?double?%addtmp4 }parsed?a?top?level?expr define?double?@1()?{ entry:%calltmp?=?call?double?@foo(double?2.000000e+00,?double?3.000000e+00)ret?double?%calltmp }parsed?a?function?definition define?double?@bar(double?%a)?{ entry:%calltmp?=?call?double?@foo(double?%a,?double?4.000000e+00)%calltmp1?=?call?double?@bar(double?3.133700e+04)%addtmp?=?fadd?double?%calltmp,?%calltmp1ret?double?%addtmp }parsed?a?extern declare?double?@cos(double)parsed?a?top?level?expr define?double?@2()?{ entry:%calltmp?=?call?double?@cos(double?1.234000e+00)ret?double?%calltmp }

至此,我們已成功將 Parser 輸出的 AST 轉為 LLVM IR。

5. Optimizer

我們使用上一節的程序處理如下代碼:

def?test(x)1?+?2?+?x

可以得到:

parsed?a?function?definition define?double?@test(double?%x)?{ entry:%addtmp?=?fadd?double?3.000000e+00,?%xret?double?%addtmp }

可以看到,生成的指令直接是 1+2 的結果,而沒有 1 + 2 的指令,這種自動把常量計算完畢而不是生成加法指令的優化稱為 Constant Folding。

在大部分時候僅有這個優化仍然不夠,比如如下代碼:

def?test(x)(1?+?2?+?x)?*?(x?+?(1?+?2))

可以得到編譯結果:

parsed?a?function?definition define?double?@test(double?%x)?{ entry:%addtmp?=?fadd?double?3.000000e+00,?%x%addtmp1?=?fadd?double?%x,?3.000000e+00%multmp?=?fmul?double?%addtmp,?%addtmp1ret?double?%multmp }

生成了兩個加法指令,但最優做法只需要一個加法即可,因為乘法的兩邊 lhs 和 rhs 是相等的。

這需要其他的優化技術,llvm 以"passes"的形式提供,llvm 中的 passes 可以選擇是否啟用,可以設置 passes 的順序。

這里我們對每個函數單獨做優化,定義 g_fpm, 增加幾個 passes:

llvm::legacy::FunctionPassManager?g_fpm(&amp;g_module);int?main()?{g_fpm.add(llvm::createInstructionCombiningPass());g_fpm.add(llvm::createReassociatePass());g_fpm.add(llvm::createGVNPass());g_fpm.add(llvm::createCFGSimplificationPass());g_fpm.doInitialization();... }

在 FunctionAST 的 CodeGen 中增加一句:

llvm::Value*?ret_val?=?body_->CodeGen();g_ir_builder.CreateRet(ret_val);llvm::verifyFunction(*func);g_fpm.run(*func);?//?增加這句return?func;

即啟動了對每個 function 的優化,接下來測試之前的代碼:

parsed?a?function?definition define?double?@test(double?%x)?{ entry:%addtmp?=?fadd?double?%x,?3.000000e+00%multmp?=?fmul?double?%addtmp,?%addtmpret?double?%multmp }

可以看到,和我們期望的一樣,加法指令減少到一個。

6. Adding a JIT Compiler

由于 JIT 模式中我們需要反復創建新的 module, 所以我們將全局變量 g_module 改為 unique_ptr。

//?用于管理函數和全局變量,可以粗淺地理解為類c++的編譯單元(單個cpp文件) std::unique_ptr<llvm::Module>?g_module?=std::make_unique<llvm::Module>("my?cool?jit",?g_llvm_context);

為了專注于 JIT,我們可以把優化的 passes 刪掉。

修改 ParseTopLevelExpr,給 PrototypeAST 命名為__anon_expr, 讓我們后面可以通過這個名字找到它。

//?toplevelexpr?::=?expression std::unique_ptr<FunctionAST>?ParseTopLevelExpr()?{auto?expr?=?ParseExpression();auto?proto?=std::make_unique<PrototypeAST>("__anon_expr",?std::vector<std::string>());return?std::make_unique<FunctionAST>(std::move(proto),?std::move(expr)); }

然后我們從 llvm-project 中拷貝一份代碼 llvm/examples/Kaleidoscope/include/KaleidoscopeJIT.h 到本地再 include, 其定義了 KaleidoscopeJIT 類,關于這個類,在后面會做解讀,這里先不管。

定義全局變量 g_jit, 并使用 InitializeNativeTarget*函數初始化環境。

#include?"KaleidoscopeJIT.h"std::unique_ptr<llvm::orc::KaleidoscopeJIT>?g_jit;int?main()?{llvm::InitializeNativeTarget();llvm::InitializeNativeTargetAsmPrinter();llvm::InitializeNativeTargetAsmParser();g_jit.reset(new?llvm::orc::KaleidoscopeJIT);g_module->setDataLayout(g_jit->getTargetMachine().createDataLayout());... }

修改 main 處理 top level expr 的代碼為:

auto?ast?=?ParseTopLevelExpr();std::cout?<<?"parsed?a?top?level?expr"?<<?std::endl;ast->CodeGen()->print(llvm::errs());std::cout?<<?std::endl;auto?h?=?g_jit->addModule(std::move(g_module));//?重新創建g_module在下次使用g_module?=std::make_unique<llvm::Module>("my?cool?jit",?g_llvm_context);g_module->setDataLayout(g_jit->getTargetMachine().createDataLayout());//?通過名字找到編譯的函數符號auto?symbol?=?g_jit->findSymbol("__anon_expr");//?強轉為C函數指針double?(*fp)()?=?(double?(*)())(symbol.getAddress().get());//?執行輸出std::cout?<<?fp()?<<?std::endl;g_jit->removeModule(h);break;

輸入:

4?+?5def?foo(a?b)a*a?+?2*a*b?+?b*bfoo(2,?3)

得到輸出:

parsed?a?top?level?expr define?double?@__anon_expr()?{ entry:ret?double?9.000000e+00 }9 parsed?a?function?definition define?double?@foo(double?%a,?double?%b)?{ entry:%multmp?=?fmul?double?%a,?%a%multmp1?=?fmul?double?2.000000e+00,?%a%multmp2?=?fmul?double?%multmp1,?%b%addtmp?=?fadd?double?%multmp,?%multmp2%multmp3?=?fmul?double?%b,?%b%addtmp4?=?fadd?double?%addtmp,?%multmp3ret?double?%addtmp4 }parsed?a?top?level?expr define?double?@__anon_expr()?{ entry:%calltmp?=?call?double?@foo(double?2.000000e+00,?double?3.000000e+00)ret?double?%calltmp }25

可以看到代碼已經順利執行,但現在的實現仍然是有問題的,比如上面的輸入,foo 函數的定義和調用是被歸在同一個 module 中,當第一次調用完成后,由于我們 removeModule, 第二次調用 foo 會失敗。

在解決這個問題之前,我們先把 main 函數內對不同 TOKEN 的處理拆成多個函數,如下:

void?ReCreateModule()?{g_module?=?std::make_unique<llvm::Module>("my?cool?jit",?g_llvm_context);g_module->setDataLayout(g_jit->getTargetMachine().createDataLayout()); }void?ParseDefinitionToken()?{auto?ast?=?ParseDefinition();std::cout?<<?"parsed?a?function?definition"?<<?std::endl;ast->CodeGen()->print(llvm::errs());std::cerr?<<?std::endl; }void?ParseExternToken()?{auto?ast?=?ParseExtern();std::cout?<<?"parsed?a?extern"?<<?std::endl;ast->CodeGen()->print(llvm::errs());std::cerr?<<?std::endl; }void?ParseTopLevel()?{auto?ast?=?ParseTopLevelExpr();std::cout?<<?"parsed?a?top?level?expr"?<<?std::endl;ast->CodeGen()->print(llvm::errs());std::cout?<<?std::endl;auto?h?=?g_jit->addModule(std::move(g_module));//?重新創建g_module在下次使用ReCreateModule();//?通過名字找到編譯的函數符號auto?symbol?=?g_jit->findSymbol("__anon_expr");//?強轉為C函數指針double?(*fp)()?=?(double?(*)())(symbol.getAddress().get());//?執行輸出std::cout?<<?fp()?<<?std::endl;g_jit->removeModule(h); }int?main()?{llvm::InitializeNativeTarget();llvm::InitializeNativeTargetAsmPrinter();llvm::InitializeNativeTargetAsmParser();g_jit.reset(new?llvm::orc::KaleidoscopeJIT);g_module->setDataLayout(g_jit->getTargetMachine().createDataLayout());GetNextToken();while?(true)?{switch?(g_current_token)?{case?TOKEN_EOF:?return?0;case?TOKEN_DEF:?ParseDefinitionToken();?break;case?TOKEN_EXTERN:?ParseExternToken();?break;default:?ParseTopLevel();?break;}}return?0; }

為了解決第二次調用 foo 失敗的問題,我們需要讓 function 和 top level expr 處于不同的 Module, 而處于不同 Module 的話,CallExprAST 的 CodeGen 在當前 module 會找不到 function, 所以需要自動在 CallExprAST 做 CodeGen 時在當前 Module 聲明這個函數,即自動地增加 extern, 也就是在當前 Module 自動做對應 PrototypeAST 的 CodeGen.

首先,增加一個全局變量存儲從函數名到函數接口的映射,并增加一個查詢函數。

std::map<std::string,?std::unique_ptr<PrototypeAST>>?name2proto_ast;llvm::Function*?GetFunction(const?std::string&amp;?name)?{llvm::Function*?callee?=?g_module->getFunction(name);if?(callee?!=?nullptr)?{??//?當前module存在函數定義return?callee;}?else?{//?聲明函數return?name2proto_ast.at(name)->CodeGen();} }

更改 CallExprAST 的 CodeGen, 讓其使用上面定義的 GetFuntion:

llvm::Value*?CallExprAST::CodeGen()?{llvm::Function*?callee?=?GetFunction(callee_);std::vector<llvm::Value*>?args;for?(std::unique_ptr<ExprAST>&amp;?arg_expr?:?args_)?{args.push_back(arg_expr->CodeGen());}return?g_ir_builder.CreateCall(callee,?args,?"calltmp"); }

更改 FunctionAST 的 CodeGen, 讓其將結果寫入 name2proto_ast:

llvm::Value*?FunctionAST::CodeGen()?{PrototypeAST&amp;?proto?=?*proto_;name2proto_ast[proto.name()]?=?std::move(proto_);??//?transfer?ownershipllvm::Function*?func?=?GetFunction(proto.name());//?創建一個Block并且設置為指令插入位置。//?llvm?block用于定義control?flow?graph,?由于我們暫不實現control?flow,?創建//?一個單獨的block即可llvm::BasicBlock*?block?=llvm::BasicBlock::Create(g_llvm_context,?"entry",?func);g_ir_builder.SetInsertPoint(block);//?將函數參數注冊到g_named_values中,讓VariableExprAST可以codegeng_named_values.clear();for?(llvm::Value&amp;?arg?:?func->args())?{g_named_values[arg.getName()]?=?&amp;arg;}//?codegen?body然后returnllvm::Value*?ret_val?=?body_->CodeGen();g_ir_builder.CreateRet(ret_val);llvm::verifyFunction(*func);return?func; }

修改 ParseExternToken 將結果寫入 name2proto_ast:

void?ParseExternToken()?{auto?ast?=?ParseExtern();std::cout?<<?"parsed?a?extern"?<<?std::endl;ast->CodeGen()->print(llvm::errs());std::cerr?<<?std::endl;name2proto_ast[ast->name()]?=?std::move(ast); }

修改 ParseDefinitionToken 讓其使用獨立 Module:

void?ParseDefinitionToken()?{auto?ast?=?ParseDefinition();std::cout?<<?"parsed?a?function?definition"?<<?std::endl;ast->CodeGen()->print(llvm::errs());std::cerr?<<?std::endl;g_jit->addModule(std::move(g_module));ReCreateModule(); }

修改完畢,輸入測試:

def?foo(x)x?+?1foo(2)def?foo(x)x?+?2foo(2)extern?sin(x) extern?cos(x)sin(1.0)def?foo(x)sin(x)?*?sin(x)?+?cos(x)?*?cos(x)foo(4) foo(3)

得到輸出:

parsed?a?function?definition define?double?@foo(double?%x)?{ entry:%addtmp?=?fadd?double?%x,?1.000000e+00ret?double?%addtmp }parsed?a?top?level?expr define?double?@__anon_expr()?{ entry:%calltmp?=?call?double?@foo(double?2.000000e+00)ret?double?%calltmp }3 parsed?a?function?definition define?double?@foo(double?%x)?{ entry:%addtmp?=?fadd?double?%x,?2.000000e+00ret?double?%addtmp }parsed?a?top?level?expr define?double?@__anon_expr()?{ entry:%calltmp?=?call?double?@foo(double?2.000000e+00)ret?double?%calltmp }4 parsed?a?extern declare?double?@sin(double)parsed?a?extern declare?double?@cos(double)parsed?a?top?level?expr define?double?@__anon_expr()?{ entry:%calltmp?=?call?double?@sin(double?1.000000e+00)ret?double?%calltmp }0.841471 parsed?a?function?definition define?double?@foo(double?%x)?{ entry:%calltmp?=?call?double?@sin(double?%x)%calltmp1?=?call?double?@sin(double?%x)%multmp?=?fmul?double?%calltmp,?%calltmp1%calltmp2?=?call?double?@cos(double?%x)%calltmp3?=?call?double?@cos(double?%x)%multmp4?=?fmul?double?%calltmp2,?%calltmp3%addtmp?=?fadd?double?%multmp,?%multmp4ret?double?%addtmp }parsed?a?top?level?expr define?double?@__anon_expr()?{ entry:%calltmp?=?call?double?@foo(double?4.000000e+00)ret?double?%calltmp }1 parsed?a?top?level?expr define?double?@__anon_expr()?{ entry:%calltmp?=?call?double?@foo(double?3.000000e+00)ret?double?%calltmp }1

成功運行,執行正確!代碼可以正確解析 sin, cos 的原因在 KaleidoscopeJIT.h 中,截取其尋找符號的代碼。

JITSymbol?findMangledSymbol(const?std::string?&amp;Name)?{ #ifdef?_WIN32//?The?symbol?lookup?of?ObjectLinkingLayer?uses?the?SymbolRef::SF_Exported//?flag?to?decide?whether?a?symbol?will?be?visible?or?not,?when?we?call//?IRCompileLayer::findSymbolIn?with?ExportedSymbolsOnly?set?to?true.////?But?for?Windows?COFF?objects,?this?flag?is?currently?never?set.//?For?a?potential?solution?see:?https://reviews.llvm.org/rL258665//?For?now,?we?allow?non-exported?symbols?on?Windows?as?a?workaround.const?bool?ExportedSymbolsOnly?=?false; #elseconst?bool?ExportedSymbolsOnly?=?true; #endif//?Search?modules?in?reverse?order:?from?last?added?to?first?added.//?This?is?the?opposite?of?the?usual?search?order?for?dlsym,?but?makes?more//?sense?in?a?REPL?where?we?want?to?bind?to?the?newest?available?definition.for?(auto?H?:?make_range(ModuleKeys.rbegin(),?ModuleKeys.rend()))if?(auto?Sym?=?CompileLayer.findSymbolIn(H,?Name,?ExportedSymbolsOnly))return?Sym;//?If?we?can't?find?the?symbol?in?the?JIT,?try?looking?in?the?host?process.if?(auto?SymAddr?=?RTDyldMemoryManager::getSymbolAddressInProcess(Name))return?JITSymbol(SymAddr,?JITSymbolFlags::Exported);#ifdef?_WIN32//?For?Windows?retry?without?"_"?at?beginning,?as?RTDyldMemoryManager?uses//?GetProcAddress?and?standard?libraries?like?msvcrt.dll?use?names//?with?and?without?"_"?(for?example?"_itoa"?but?"sin").if?(Name.length()?>?2?&amp;&amp;?Name[0]?==?'_')if?(auto?SymAddr?=RTDyldMemoryManager::getSymbolAddressInProcess(Name.substr(1)))return?JITSymbol(SymAddr,?JITSymbolFlags::Exported); #endifreturn?null

可以看到,在之前定義的 Module 找不到后會在 host process 中尋找這個符號。

7. SSA

繼續給我們的 Kaleidoscope 添加功能之前,需要先介紹 SSA, Static Single Assignment,考慮下面代碼:

y?:=?1 y?:=?2 x?:=?y

我們可以發現第一個賦值是不必須的,而且第三行使用的 y 來自第二行的賦值,改成 SSA 格式為

y_1?=?1 y_2?=?2 x_1?=?y_2

改完可以方便編譯器進行優化,比如把第一個賦值刪去,于是我們可以給出 SSA 的定義:

  • 每個變量僅且必須被賦值一次,原本代碼中的多次變量賦值會被賦予版本號然后視為不同變量;

  • 每個變量在被使用之前必須被定義。

考慮如下 Control Flow Graph:

加上版本號:

可以看到,這里遇到一個問題,最下面的 block 里面的 y 應該使用 y1 還是 y2, 為了解決這個問題,插入一個特殊語句稱為 phi function, 其會根據 control flow 從 y1 和 y2 中選擇一個值作為 y3, 如下:

可以看到,對于 x 不需要 phi function, 因為兩個分支到最后的都是 x2。

8. Control Flow

我們現在實現的 Kaleidoscope 還不夠完善,缺少 if else 控制流,比如不支持如下代碼:

def?fib(x)if?x?<?3?then1elsefib(x?-?1)?+?fib(x?-?2)

首先讓我們的 Lexer 能識別 if then else 三個關鍵字,增加 TOKEN 類型:

TOKEN_IF?=?-6,??????????//?ifTOKEN_THEN?=?-7,????????//?thenTOKEN_ELSE?=?-8,????????//?else

增加識別規則:

//?識別字符串if?(isalpha(last_char))?{g_identifier_str?=?last_char;while?(isalnum((last_char?=?getchar())))?{g_identifier_str?+=?last_char;}if?(g_identifier_str?==?"def")?{return?TOKEN_DEF;}?else?if?(g_identifier_str?==?"extern")?{return?TOKEN_EXTERN;}?else?if?(g_identifier_str?==?"if")?{return?TOKEN_IF;}?else?if?(g_identifier_str?==?"then")?{return?TOKEN_THEN;}?else?if?(g_identifier_str?==?"else")?{return?TOKEN_ELSE;}?else?{return?TOKEN_IDENTIFIER;}}

增加 IfExprAST:

//?if?then?else class?IfExprAST?:?public?ExprAST?{public:IfExprAST(std::unique_ptr<ExprAST>?cond,?std::unique_ptr<ExprAST>?then_expr,std::unique_ptr<ExprAST>?else_expr):?cond_(std::move(cond)),then_expr_(std::move(then_expr)),else_expr_(std::move(else_expr))?{}llvm::Value*?CodeGen()?override;private:std::unique_ptr<ExprAST>?cond_;std::unique_ptr<ExprAST>?then_expr_;std::unique_ptr<ExprAST>?else_expr_; };

增加對 IfExprAST 的解析:

std::unique_ptr<ExprAST>?ParseIfExpr()?{GetNextToken();??//?eat?ifstd::unique_ptr<ExprAST>?cond?=?ParseExpression();GetNextToken();??//?eat?thenstd::unique_ptr<ExprAST>?then_expr?=?ParseExpression();GetNextToken();??//?eat?elsestd::unique_ptr<ExprAST>?else_expr?=?ParseExpression();return?std::make_unique<IfExprAST>(std::move(cond),?std::move(then_expr),std::move(else_expr)); }

增加到 ParsePrimary 中:

//?primary //???::=?identifierexpr //???::=?numberexpr //???::=?parenexpr std::unique_ptr<ExprAST>?ParsePrimary()?{switch?(g_current_token)?{case?TOKEN_IDENTIFIER:?return?ParseIdentifierExpr();case?TOKEN_NUMBER:?return?ParseNumberExpr();case?'(':?return?ParseParenExpr();case?TOKEN_IF:?return?ParseIfExpr();default:?return?nullptr;} }

完成了 lex 和 parse,接下來是最有意思的 codegen:

llvm::Value*?IfExprAST::CodeGen()?{llvm::Value*?cond_value?=?cond_->CodeGen();//?創建fcmp?one指令,?cond_value?=?(cond_value?!=?0.0)//?轉為1bit?(bool)類型cond_value?=?g_ir_builder.CreateFCmpONE(cond_value,?llvm::ConstantFP::get(g_llvm_context,?llvm::APFloat(0.0)),"ifcond");//?在每個function內我們會創建一個block,?這里一定在這個block內,根據block得到//?對應的上層functionllvm::Function*?func?=?g_ir_builder.GetInsertBlock()->getParent();//?為then?else以及最后的final創建blockllvm::BasicBlock*?then_block?=llvm::BasicBlock::Create(g_llvm_context,?"then",?func);llvm::BasicBlock*?else_block?=llvm::BasicBlock::Create(g_llvm_context,?"else");llvm::BasicBlock*?final_block?=llvm::BasicBlock::Create(g_llvm_context,?"ifcont");//?創建跳轉指令,根據cond_value選擇then_block/else_blockg_ir_builder.CreateCondBr(cond_value,?then_block,?else_block);//?codegen?then_block,?增加跳轉final_block指令g_ir_builder.SetInsertPoint(then_block);llvm::Value*?then_value?=?then_expr_->CodeGen();g_ir_builder.CreateBr(final_block);//?then語句內可能會有嵌套的if/then/else,?在嵌套的codegen時,會改變當前的//?InsertBlock,?我們需要有最終結果的那個block作為這里的then_blockthen_block?=?g_ir_builder.GetInsertBlock();//?在這里才加入是為了讓這個block位于上面的then里嵌套block的后面func->getBasicBlockList().push_back(else_block);//?與then類似g_ir_builder.SetInsertPoint(else_block);llvm::Value*?else_value?=?else_expr_->CodeGen();g_ir_builder.CreateBr(final_block);else_block?=?g_ir_builder.GetInsertBlock();//?codegen?finalfunc->getBasicBlockList().push_back(final_block);g_ir_builder.SetInsertPoint(final_block);llvm::PHINode*?pn?=?g_ir_builder.CreatePHI(llvm::Type::getDoubleTy(g_llvm_context),?2,?"iftmp");pn->addIncoming(then_value,?then_block);pn->addIncoming(else_value,?else_block);return?pn; }

這里使用了上一節 SSA 中提到的 phi function,輸入:

def?foo(x)if?x?<?3?then1elsefoo(x?-?1)?+?foo(x?-?2)foo(1) foo(2) foo(3) foo(4)

得到輸出:

parsed?a?function?definition define?double?@foo(double?%x)?{ entry:%cmptmp?=?fcmp?ult?double?%x,?3.000000e+00%booltmp?=?uitofp?i1?%cmptmp?to?double%ifcond?=?fcmp?one?double?%booltmp,?0.000000e+00br?i1?%ifcond,?label?%then,?label?%elsethen:?????????????????????????????????????????????;?preds?=?%entrybr?label?%ifcontelse:?????????????????????????????????????????????;?preds?=?%entry%subtmp?=?fsub?double?%x,?1.000000e+00%calltmp?=?call?double?@foo(double?%subtmp)%subtmp1?=?fsub?double?%x,?2.000000e+00%calltmp2?=?call?double?@foo(double?%subtmp1)%addtmp?=?fadd?double?%calltmp,?%calltmp2br?label?%ifcontifcont:???????????????????????????????????????????;?preds?=?%else,?%then%iftmp?=?phi?double?[?1.000000e+00,?%then?],?[?%addtmp,?%else?]ret?double?%iftmp }parsed?a?top?level?expr define?double?@__anon_expr()?{ entry:%calltmp?=?call?double?@foo(double?1.000000e+00)ret?double?%calltmp }1 parsed?a?top?level?expr define?double?@__anon_expr()?{ entry:%calltmp?=?call?double?@foo(double?2.000000e+00)ret?double?%calltmp }1 parsed?a?top?level?expr define?double?@__anon_expr()?{ entry:%calltmp?=?call?double?@foo(double?3.000000e+00)ret?double?%calltmp }2 parsed?a?top?level?expr define?double?@__anon_expr()?{ entry:%calltmp?=?call?double?@foo(double?4.000000e+00)ret?double?%calltmp }3

成功完成了斐波那契數列的計算,接下來我們需要增加循環的支持,在此之前我們實現一個 printd 函數:

extern?"C"?double?printd(double?x)?{printf("%lf\n",?x);return?0.0; }

編譯:

clang++?-g?main.cpp?\`llvm-config?--cxxflags?--ldflags?--libs\`?-Wl,-no-as-needed?-rdynamic

輸入:

extern?printd(x)printd(12)

得到輸出:

parsed?a?extern declare?double?@printd(double)parsed?a?top?level?expr define?double?@__anon_expr()?{ entry:%calltmp?=?call?double?@printd(double?1.200000e+01)ret?double?%calltmp }12.000000 0

可以看到,我們成功給 Kaleiscope 添加了 printd 函數,接下來看我們需要實現的循環語法, 使用 C++代碼作為注釋:

def?printstar(n):for?i?=?1,?i?<?n,?1.0?in?#?for?(double?i?=?1.0;?i?<?n;?i?+=?1.0)printd(n)

同樣,我們增加 for 和 in 的 TOKEN:

enum?Token?{TOKEN_EOF?=?-1,?????????//?文件結束標識符TOKEN_DEF?=?-2,?????????//?關鍵字defTOKEN_EXTERN?=?-3,??????//?關鍵字externTOKEN_IDENTIFIER?=?-4,??//?名字TOKEN_NUMBER?=?-5,??????//?數值TOKEN_IF?=?-6,??????????//?ifTOKEN_THEN?=?-7,????????//?thenTOKEN_ELSE?=?-8,????????//?elseTOKEN_FOR?=?-9,?????????//?forTOKEN_IN?=?-10??????????//?in };

增加 TOKEN 的識別:

//?識別字符串if?(isalpha(last_char))?{g_identifier_str?=?last_char;while?(isalnum((last_char?=?getchar())))?{g_identifier_str?+=?last_char;}if?(g_identifier_str?==?"def")?{return?TOKEN_DEF;}?else?if?(g_identifier_str?==?"extern")?{return?TOKEN_EXTERN;}?else?if?(g_identifier_str?==?"if")?{return?TOKEN_IF;}?else?if?(g_identifier_str?==?"then")?{return?TOKEN_THEN;}?else?if?(g_identifier_str?==?"else")?{return?TOKEN_ELSE;}?else?if?(g_identifier_str?==?"for")?{return?TOKEN_FOR;}?else?if?(g_identifier_str?==?"in")?{return?TOKEN_IN;}?else?{return?TOKEN_IDENTIFIER;}}

增加 ForExprAST:

//?for?in class?ForExprAST?:?public?ExprAST?{public:ForExprAST(const?std::string&amp;?var_name,?std::unique_ptr<ExprAST>?start_expr,std::unique_ptr<ExprAST>?end_expr,std::unique_ptr<ExprAST>?step_expr,std::unique_ptr<ExprAST>?body_expr):?var_name_(var_name),start_expr_(std::move(start_expr)),end_expr_(std::move(end_expr)),step_expr_(std::move(step_expr)),body_expr_(std::move(body_expr))?{}llvm::Value*?CodeGen()?override;private:std::string?var_name_;std::unique_ptr<ExprAST>?start_expr_;std::unique_ptr<ExprAST>?end_expr_;std::unique_ptr<ExprAST>?step_expr_;std::unique_ptr<ExprAST>?body_expr_; };

添加到 Primary 的解析中:

//?forexpr?::=?for?var_name?=?start_expr,?end_expr,?step_expr?in?body_expr std::unique_ptr<ExprAST>?ParseForExpr()?{GetNextToken();??//?eat?forstd::string?var_name?=?g_identifier_str;GetNextToken();??//?eat?var_nameGetNextToken();??//?eat?=std::unique_ptr<ExprAST>?start_expr?=?ParseExpression();GetNextToken();??//?eat?,std::unique_ptr<ExprAST>?end_expr?=?ParseExpression();GetNextToken();??//?eat?,std::unique_ptr<ExprAST>?step_expr?=?ParseExpression();GetNextToken();??//?eat?instd::unique_ptr<ExprAST>?body_expr?=?ParseExpression();return?std::make_unique<ForExprAST>(var_name,?std::move(start_expr),std::move(end_expr),?std::move(step_expr),std::move(body_expr)); } //?primary //???::=?identifierexpr //???::=?numberexpr //???::=?parenexpr std::unique_ptr<ExprAST>?ParsePrimary()?{switch?(g_current_token)?{case?TOKEN_IDENTIFIER:?return?ParseIdentifierExpr();case?TOKEN_NUMBER:?return?ParseNumberExpr();case?'(':?return?ParseParenExpr();case?TOKEN_IF:?return?ParseIfExpr();case?TOKEN_FOR:?return?ParseForExpr();default:?return?nullptr;} }

開始 codegen:

llvm::Value*?ForExprAST::CodeGen()?{//?codegen?startllvm::Value*?start_val?=?start_expr_->CodeGen();//?獲取當前functionllvm::Function*?func?=?g_ir_builder.GetInsertBlock()->getParent();//?保存當前的blockllvm::BasicBlock*?pre_block?=?g_ir_builder.GetInsertBlock();//?新增一個loop?block到當前functionllvm::BasicBlock*?loop_block?=llvm::BasicBlock::Create(g_llvm_context,?"loop",?func);//?為當前block增加到loop_block的跳轉指令g_ir_builder.CreateBr(loop_block);//?開始在loop_block內增加指令g_ir_builder.SetInsertPoint(loop_block);llvm::PHINode*?var?=?g_ir_builder.CreatePHI(llvm::Type::getDoubleTy(g_llvm_context),?2,?var_name_.c_str());//?如果來自pre_block的跳轉,則取start_val的值var->addIncoming(start_val,?pre_block);//?現在我們新增了一個變量var,因為可能會被后面的代碼引用,所以要注冊到//?g_named_values中,其可能會和函數參數重名,但我們這里為了方便不管//?這個特殊情況,直接注冊到g_named_values中,g_named_values[var_name_]?=?var;//?在loop_block中增加body的指令body_expr_->CodeGen();//?codegen?step_exprllvm::Value*?step_value?=?step_expr_->CodeGen();//?next_var?=?var?+?step_valuellvm::Value*?next_value?=?g_ir_builder.CreateFAdd(var,?step_value,?"nextvar");//?codegen?end_exprllvm::Value*?end_value?=?end_expr_->CodeGen();//?end_value?=?(end_value?!=?0.0)end_value?=?g_ir_builder.CreateFCmpONE(end_value,?llvm::ConstantFP::get(g_llvm_context,?llvm::APFloat(0.0)),"loopcond");//?和if/then/else一樣,這里的block可能會發生變化,保存當前的blockllvm::BasicBlock*?loop_end_block?=?g_ir_builder.GetInsertBlock();//?創建循環結束后的blockllvm::BasicBlock*?after_block?=llvm::BasicBlock::Create(g_llvm_context,?"afterloop",?func);//?根據end_value選擇是再來一次loop_block還是進入after_blockg_ir_builder.CreateCondBr(end_value,?loop_block,?after_block);//?給after_block增加指令g_ir_builder.SetInsertPoint(after_block);//?如果是再次循環,取新的值var->addIncoming(next_value,?loop_end_block);//?循環結束,避免被再次引用g_named_values.erase(var_name_);//?return?0return?llvm::Constant::getNullValue(llvm::Type::getDoubleTy(g_llvm_context)); }

輸入:

extern?printd(x)def?foo(x)if?x?<?3?then1elsefoo(x?-?1)?+?foo(x?-?2)for?i?=?1,?i?<?10,?1.0?inprintd(foo(i))

輸出:

parsed?a?extern declare?double?@printd(double)parsed?a?function?definition define?double?@foo(double?%x)?{ entry:%cmptmp?=?fcmp?ult?double?%x,?3.000000e+00%booltmp?=?uitofp?i1?%cmptmp?to?double%ifcond?=?fcmp?one?double?%booltmp,?0.000000e+00br?i1?%ifcond,?label?%then,?label?%elsethen:?????????????????????????????????????????????;?preds?=?%entrybr?label?%ifcontelse:?????????????????????????????????????????????;?preds?=?%entry%subtmp?=?fsub?double?%x,?1.000000e+00%calltmp?=?call?double?@foo(double?%subtmp)%subtmp1?=?fsub?double?%x,?2.000000e+00%calltmp2?=?call?double?@foo(double?%subtmp1)%addtmp?=?fadd?double?%calltmp,?%calltmp2br?label?%ifcontifcont:???????????????????????????????????????????;?preds?=?%else,?%then%iftmp?=?phi?double?[?1.000000e+00,?%then?],?[?%addtmp,?%else?]ret?double?%iftmp }parsed?a?top?level?expr define?double?@__anon_expr()?{ entry:br?label?%looploop:?????????????????????????????????????????????;?preds?=?%loop,?%entry%i?=?phi?double?[?1.000000e+00,?%entry?],?[?%nextvar,?%loop?]%calltmp?=?call?double?@foo(double?%i)%calltmp1?=?call?double?@printd(double?%calltmp)%nextvar?=?fadd?double?%i,?1.000000e+00%cmptmp?=?fcmp?ult?double?%i,?1.000000e+01%booltmp?=?uitofp?i1?%cmptmp?to?double%loopcond?=?fcmp?one?double?%booltmp,?0.000000e+00br?i1?%loopcond,?label?%loop,?label?%afterloopafterloop:????????????????????????????????????????;?preds?=?%loopret?double?0.000000e+00 }1.000000 1.000000 2.000000 3.000000 5.000000 8.000000 13.000000 21.000000 34.000000 55.000000 0

成功遍歷了斐波那契數列。

9. User-Defined Operators

在 C++中,用戶可以重載操作符而不能增加操作符。在這里,我們將給 Kaleidoscope 增加一個功能,讓用戶可以增加二元操作符。

#?新增二元操作符?`>`,?優先級等于內置的?`<` def?binary>?10?(LHS?RHS)RHS?<?LHS#?新增二元操作符?`|`,?優先級為5 def?binary|?5?(LHS?RHS)if?LHS?then1else?if?RHS?then1else0#?新增二元操作符?`=`,優先級為9,這個操作符類似C++的?`==` def?binary=?9?(LHS?RHS)!(LHS?<?RHS?|?LHS?>?RHS)

增加 TOKEN 的類型:

enum?Token?{...TOKEN_BINARY?=?-11,?????//?binary };

增加 TOKEN 的識別:

//?從標準輸入解析一個Token并返回 int?GetToken()?{...//?識別字符串if?(isalpha(last_char))?{...if?(g_identifier_str?==?"def")?{return?TOKEN_DEF;}?else?if?(g_identifier_str?==?"extern")?{return?TOKEN_EXTERN;}?else?if?(g_identifier_str?==?"if")?{return?TOKEN_IF;}?else?if?(g_identifier_str?==?"then")?{return?TOKEN_THEN;}?else?if?(g_identifier_str?==?"else")?{return?TOKEN_ELSE;}?else?if?(g_identifier_str?==?"for")?{return?TOKEN_FOR;}?else?if?(g_identifier_str?==?"in")?{return?TOKEN_IN;}?else?if?(g_identifier_str?==?"binary")?{return?TOKEN_BINARY;}?else?{return?TOKEN_IDENTIFIER;}}... }

我們把新增的二元操作符視為一個函數,所以不需要新增 AST,但是需要修改 PrototypeAST。

//?函數接口 class?PrototypeAST?{public:PrototypeAST(const?std::string&amp;?name,?std::vector<std::string>?args,bool?is_operator?=?false,?int?op_precedence?=?0):?name_(name),args_(std::move(args)),is_operator_(is_operator),op_precedence_(op_precedence)?{}llvm::Function*?CodeGen();const?std::string&amp;?name()?const?{?return?name_;?}int?op_precedence()?const?{?return?op_precedence_;?}bool?IsUnaryOp()?const?{?return?is_operator_?&amp;&amp;?args_.size()?==?1;?}bool?IsBinaryOp()?const?{?return?is_operator_?&amp;&amp;?args_.size()?==?2;?}//?like?`|`?in?`binary|`char?GetOpName()?{?return?name_[name_.size()?-?1];?}private:std::string?name_;std::vector<std::string>?args_;bool?is_operator_;int?op_precedence_; };

修改 parse 部分:

//?prototype //???::=?id?(?id?id?...?id) //???::=?binary?binop?precedence?(id?id) std::unique_ptr<PrototypeAST>?ParsePrototype()?{std::string?function_name;bool?is_operator?=?false;int?precedence?=?0;switch?(g_current_token)?{case?TOKEN_IDENTIFIER:?{function_name?=?g_identifier_str;is_operator?=?false;GetNextToken();??//?eat?idbreak;}case?TOKEN_BINARY:?{GetNextToken();??//?eat?binaryfunction_name?=?"binary";function_name?+=?(char)(g_current_token);is_operator?=?true;GetNextToken();??//?eat?binopprecedence?=?g_number_val;GetNextToken();??//?eat?precedencebreak;}}std::vector<std::string>?arg_names;while?(GetNextToken()?==?TOKEN_IDENTIFIER)?{arg_names.push_back(g_identifier_str);}GetNextToken();??//?eat?)return?std::make_unique<PrototypeAST>(function_name,?arg_names,?is_operator,precedence); }

修改 BinaryExprAST 的 CodeGen 處理自定義 Operator, 增加函數調用指令:

llvm::Value*?BinaryExprAST::CodeGen()?{llvm::Value*?lhs?=?lhs_->CodeGen();llvm::Value*?rhs?=?rhs_->CodeGen();switch?(op_)?{case?'<':?{llvm::Value*?tmp?=?g_ir_builder.CreateFCmpULT(lhs,?rhs,?"cmptmp");//?把?0/1?轉為?0.0/1.0return?g_ir_builder.CreateUIToFP(tmp,?llvm::Type::getDoubleTy(g_llvm_context),?"booltmp");}case?'+':?return?g_ir_builder.CreateFAdd(lhs,?rhs,?"addtmp");case?'-':?return?g_ir_builder.CreateFSub(lhs,?rhs,?"subtmp");case?'*':?return?g_ir_builder.CreateFMul(lhs,?rhs,?"multmp");default:?{//?user?defined?operatorllvm::Function*?func?=?GetFunction(std::string("binary")?+?op_);llvm::Value*?operands[2]?=?{lhs,?rhs};return?g_ir_builder.CreateCall(func,?operands,?"binop");}} }

在 FunctionAST 的 CodeGen 時,注冊操作符優先級,從而讓自定義操作符被識別為操作符。

llvm::Value*?FunctionAST::CodeGen()?{PrototypeAST&amp;?proto?=?*proto_;name2proto_ast[proto.name()]?=?std::move(proto_);??//?transfer?ownershipllvm::Function*?func?=?GetFunction(proto.name());if?(proto.IsBinaryOp())?{g_binop_precedence[proto.GetOpName()]?=?proto.op_precedence();}//?創建一個Block并且設置為指令插入位置。//?llvm?block用于定義control?flow?graph,?由于我們暫不實現control?flow,?創建//?一個單獨的block即可llvm::BasicBlock*?block?=llvm::BasicBlock::Create(g_llvm_context,?"entry",?func);g_ir_builder.SetInsertPoint(block);//?將函數參數注冊到g_named_values中,讓VariableExprAST可以codegeng_named_values.clear();for?(llvm::Value&amp;?arg?:?func->args())?{g_named_values[arg.getName()]?=?&amp;arg;}//?codegen?body然后returnllvm::Value*?ret_val?=?body_->CodeGen();g_ir_builder.CreateRet(ret_val);llvm::verifyFunction(*func);return?func; }

輸入:

#?新增二元操作符?`>`,?優先級等于內置的?`<` def?binary>?10?(LHS?RHS)RHS?<?LHS1?>?2 2?>?1#?新增二元操作符?`|`,?優先級為5 def?binary|?5?(LHS?RHS)if?LHS?then1else?if?RHS?then1else01?|?0 0?|?1 0?|?0 1?|?1

得到輸出:

parsed?a?function?definition define?double?@"binary>"(double?%LHS,?double?%RHS)?{ entry:%cmptmp?=?fcmp?ult?double?%RHS,?%LHS%booltmp?=?uitofp?i1?%cmptmp?to?doubleret?double?%booltmp }parsed?a?top?level?expr define?double?@__anon_expr()?{ entry:%binop?=?call?double?@"binary>"(double?1.000000e+00,?double?2.000000e+00)ret?double?%binop }0 parsed?a?top?level?expr define?double?@__anon_expr()?{ entry:%binop?=?call?double?@"binary>"(double?2.000000e+00,?double?1.000000e+00)ret?double?%binop }1 parsed?a?function?definition define?double?@"binary|"(double?%LHS,?double?%RHS)?{ entry:%ifcond?=?fcmp?one?double?%LHS,?0.000000e+00br?i1?%ifcond,?label?%then,?label?%elsethen:?????????????????????????????????????????????;?preds?=?%entrybr?label?%ifcont4else:?????????????????????????????????????????????;?preds?=?%entry%ifcond1?=?fcmp?one?double?%RHS,?0.000000e+00br?i1?%ifcond1,?label?%then2,?label?%else3then2:????????????????????????????????????????????;?preds?=?%elsebr?label?%ifcontelse3:????????????????????????????????????????????;?preds?=?%elsebr?label?%ifcontifcont:???????????????????????????????????????????;?preds?=?%else3,?%then2%iftmp?=?phi?double?[?1.000000e+00,?%then2?],?[?0.000000e+00,?%else3?]br?label?%ifcont4ifcont4:??????????????????????????????????????????;?preds?=?%ifcont,?%then%iftmp5?=?phi?double?[?1.000000e+00,?%then?],?[?%iftmp,?%ifcont?]ret?double?%iftmp5 }parsed?a?top?level?expr define?double?@__anon_expr()?{ entry:%binop?=?call?double?@"binary|"(double?1.000000e+00,?double?0.000000e+00)ret?double?%binop }1 parsed?a?top?level?expr define?double?@__anon_expr()?{ entry:%binop?=?call?double?@"binary|"(double?0.000000e+00,?double?1.000000e+00)ret?double?%binop }1 parsed?a?top?level?expr define?double?@__anon_expr()?{ entry:%binop?=?call?double?@"binary|"(double?0.000000e+00,?double?0.000000e+00)ret?double?%binop }0 parsed?a?top?level?expr define?double?@__anon_expr()?{ entry:%binop?=?call?double?@"binary|"(double?1.000000e+00,?double?1.000000e+00)ret?double?%binop }1

10. Mutable Variables

本節我們將讓 Kaleidoscope 支持可變變量,首先我們看如下 C 代碼:

int?G,?H; int?test(_Bool?Condition)?{int?X;if?(Condition)X?=?G;elseX?=?H;return?X; }

由于變量 X 的值依賴于程序的執行路徑,會加入一個 phi node 來選取分支結果。上面代碼的 LLVM IR 如下:

@G?=?weak?global?i32?0???;?type?of?@G?is?i32* @H?=?weak?global?i32?0???;?type?of?@H?is?i32*define?i32?@test(i1?%Condition)?{ entry:br?i1?%Condition,?label?%cond_true,?label?%cond_falsecond_true:%X.0?=?load?i32*?@Gbr?label?%cond_nextcond_false:%X.1?=?load?i32*?@Hbr?label?%cond_nextcond_next:%X.2?=?phi?i32?[?%X.1,?%cond_false?],?[?%X.0,?%cond_true?]ret?i32?%X.2 }

上面的 X 是符合 SSA 格式的,但是這里真正的難題是給可變變量賦值時怎么自動添加 phi node。我們先了解一些信息,LLVM 要求寄存器變量是 SSA 格式,但卻不允許內存對象是 SSA 格式。比如上面的例子中,G 和 H 就沒有版本號。在 LLVM 中,所有內存訪問都是顯示的 load/store 指令,并且不存在取內存地址的操作。注意上面的例子中,即使@G/@H 全局變量定義時用的 i32, 但其類型仍然是 i32*, 表示在全局數據區存放 i32 的空間地址。

現在假設我們想創建一個類似@G 但是在棧上的內存變量,基本指令如下:

define?i32?@example()?{entry:%X?=?alloca?i32???????????;?type?of?%X?is?i32*....%tmp?=?load?i32*?%X???????;?load?the?stack?value?%X?from?the?stack.%tmp2?=?add?i32?%tmp,?1???;?increment?itstore?i32?%tmp2,?i32*?%X??;?store?it?back...

于是我們可以把上面使用 phi node 的 LLVM IR 改寫為使用棧上變量:

@G?=?weak?global?i32?0???;?type?of?@G?is?i32* @H?=?weak?global?i32?0???;?type?of?@H?is?i32*define?i32?@test(i1?%Condition)?{ entry:%X?=?alloca?i32???????????;?type?of?%X?is?i32*.br?i1?%Condition,?label?%cond_true,?label?%cond_falsecond_true:%X.0?=?load?i32*?@Gstore?i32?%X.0,?i32*?%X???;?Update?Xbr?label?%cond_nextcond_false:%X.1?=?load?i32*?@Hstore?i32?%X.1,?i32*?%X???;?Update?Xbr?label?%cond_nextcond_next:%X.2?=?load?i32*?%X???????;?Read?Xret?i32?%X.2 }

于是我們找到了一個處理任意可變變量而且不需要創建 phi node 的辦法:

  • 每個可變變量在棧上創建

  • 變量讀取變為 load from stack

  • 變量更新變為 store to stack

  • 使用棧上地址作為變量地址

  • 但是這會帶來一個新的問題,因為內存速度不如寄存器,大量使用棧會有性能問題。不過,LLVM 優化器有一個 pass 稱為"mem2reg", 專門將 stack 的使用自動地盡可能轉為使用 phi node, 下面為自動優化的結果:

    @G?=?weak?global?i32?0 @H?=?weak?global?i32?0define?i32?@test(i1?%Condition)?{ entry:br?i1?%Condition,?label?%cond_true,?label?%cond_falsecond_true:%X.0?=?load?i32*?@Gbr?label?%cond_nextcond_false:%X.1?=?load?i32*?@Hbr?label?%cond_nextcond_next:%X.01?=?phi?i32?[?%X.1,?%cond_false?],?[?%X.0,?%cond_true?]ret?i32?%X.01}

    mem2reg 實現了一個稱為"iterated dominance frontier"的標準算法來自動創建 SSA 格式。對 mem2reg 的使用需要注意:

  • mem2reg 只能優化棧上變量,不會優化全局變量和堆上變量;

  • mem2reg 只優化 entry block 中的棧上變量創建, 因為在 entry block 中就意味著只創建一次;

  • 如果對棧上變量有 load 和 store 之外的操作, mem2reg 也不會優化;

  • mem2reg 只能優化基本類型的棧上變量,比如指針,數值和數組。其中數組的大小必須為 1. 對于結構體和數組等的優化需要另一個稱為"sroa"的 pass。

  • 因為我們后面需要啟用 mem2reg,我們先把優化器加回來,修改全局定義:

    std::unique_ptr<llvm::Module>?g_module; std::unique_ptr<llvm::legacy::FunctionPassManager>?g_fpm;

    修改 ReCreateModule:

    void?ReCreateModule()?{g_module?=?std::make_unique<llvm::Module>("my?cool?jit",?g_llvm_context);g_module->setDataLayout(g_jit->getTargetMachine().createDataLayout());g_fpm?=?std::make_unique<llvm::legacy::FunctionPassManager>(g_module.get());g_fpm->add(llvm::createInstructionCombiningPass());g_fpm->add(llvm::createReassociatePass());g_fpm->add(llvm::createGVNPass());g_fpm->add(llvm::createCFGSimplificationPass());g_fpm->doInitialization(); }

    在 FunctionAST::CodeGen 中執行優化器:

    g_ir_builder.CreateRet(ret_val); llvm::verifyFunction(*func); g_fpm->run(*func);

    修改 main:

    int?main()?{llvm::InitializeNativeTarget();llvm::InitializeNativeTargetAsmPrinter();llvm::InitializeNativeTargetAsmParser();g_jit.reset(new?llvm::orc::KaleidoscopeJIT);ReCreateModule();... }

    我們有兩種類型的變量,分別是函數參數以及 for 循環的變量,這里我們將這兩種變量也修改為使用內存,再讓 mem2reg 進行優化。因為所有的變量都會使用內存,修改 g_named_value 存儲的類型為 AllocaInst*:

    std::map<std::string,?llvm::AllocaInst*>?g_named_values;

    編寫一個函數 CreateEntryBlockAlloca,簡化后續工作,其功能是往函數的 EntryBlock 的最開始的地方添加分配內存指令:

    llvm::AllocaInst*?CreateEntryBlockAlloca(llvm::Function*?func,const?std::string&amp;?var_name)?{llvm::IRBuilder<>?ir_builder(&amp;(func->getEntryBlock()),func->getEntryBlock().begin());return?ir_builder.CreateAlloca(llvm::Type::getDoubleTy(g_llvm_context),?0,var_name.c_str()); }

    修改 VariableExprAST::CodeGen, 由于我們所有變量都放在內存你上,所以增加 load 指令:

    llvm::Value*?VariableExprAST::CodeGen()?{llvm::AllocaInst*?val?=?g_named_values.at(name_);return?g_ir_builder.CreateLoad(val,?name_.c_str()); }

    接下來我們修改 for 循環里變量的 CodeGen:

    llvm::Value*?ForExprAST::CodeGen()?{//?獲取當前functionllvm::Function*?func?=?g_ir_builder.GetInsertBlock()->getParent();//?將變量創建為棧上變量,不再是phi?nodellvm::AllocaInst*?var?=?CreateEntryBlockAlloca(func,?var_name_);//?codegen?startllvm::Value*?start_val?=?start_expr_->CodeGen();//?將初始值賦給varg_ir_builder.CreateStore(start_val,?var);//?新增一個loop?block到當前functionllvm::BasicBlock*?loop_block?=llvm::BasicBlock::Create(g_llvm_context,?"loop",?func);//?為當前block增加到loop_block的跳轉指令g_ir_builder.CreateBr(loop_block);//?開始在loop_block內增加指令g_ir_builder.SetInsertPoint(loop_block);//?現在我們新增了一個變量var,因為可能會被后面的代碼引用,所以要注冊到//?g_named_values中,其可能會和函數參數重名,但我們這里為了方便不管//?這個特殊情況,直接注冊到g_named_values中,g_named_values[var_name_]?=?var;//?在loop_block中增加body的指令body_expr_->CodeGen();//?codegen?step_exprllvm::Value*?step_value?=?step_expr_->CodeGen();//?var?=?var?+?step_valuellvm::Value*?cur_value?=?g_ir_builder.CreateLoad(var);llvm::Value*?next_value?=g_ir_builder.CreateFAdd(cur_value,?step_value,?"nextvar");g_ir_builder.CreateStore(next_value,?var);//?codegen?end_exprllvm::Value*?end_value?=?end_expr_->CodeGen();//?end_value?=?(end_value?!=?0.0)end_value?=?g_ir_builder.CreateFCmpONE(end_value,?llvm::ConstantFP::get(g_llvm_context,?llvm::APFloat(0.0)),"loopcond");//?和if/then/else一樣,這里的block可能會發生變化,保存當前的blockllvm::BasicBlock*?loop_end_block?=?g_ir_builder.GetInsertBlock();//?創建循環結束后的blockllvm::BasicBlock*?after_block?=llvm::BasicBlock::Create(g_llvm_context,?"afterloop",?func);//?根據end_value選擇是再來一次loop_block還是進入after_blockg_ir_builder.CreateCondBr(end_value,?loop_block,?after_block);//?給after_block增加指令g_ir_builder.SetInsertPoint(after_block);//?循環結束,避免被再次引用g_named_values.erase(var_name_);//?return?0return?llvm::Constant::getNullValue(llvm::Type::getDoubleTy(g_llvm_context)); }

    修改 FunctionAST::codegen()使得參數可變:

    llvm::Value*?FunctionAST::CodeGen()?{PrototypeAST&amp;?proto?=?*proto_;name2proto_ast[proto.name()]?=?std::move(proto_);??//?transfer?ownershipllvm::Function*?func?=?GetFunction(proto.name());if?(proto.IsBinaryOp())?{g_binop_precedence[proto.GetOpName()]?=?proto.op_precedence();}//?創建一個Block并且設置為指令插入位置。//?llvm?block用于定義control?flow?graph,?由于我們暫不實現control?flow,?創建//?一個單獨的block即可llvm::BasicBlock*?block?=llvm::BasicBlock::Create(g_llvm_context,?"entry",?func);g_ir_builder.SetInsertPoint(block);//?將函數參數注冊到g_named_values中,讓VariableExprAST可以codegeng_named_values.clear();for?(llvm::Value&amp;?arg?:?func->args())?{//?為每個參數創建一個棧上變量,并賦初值,修改g_named_values使得后面的引用//?會引用這個棧上變量llvm::AllocaInst*?var?=?CreateEntryBlockAlloca(func,?arg.getName());g_ir_builder.CreateStore(&amp;arg,?var);g_named_values[arg.getName()]?=?var;}//?codegen?body然后returnllvm::Value*?ret_val?=?body_->CodeGen();g_ir_builder.CreateRet(ret_val);llvm::verifyFunction(*func);g_fpm->run(*func);return?func; }

    輸入:

    extern?printd(x)def?foo(x)if?x?<?3?then1elsefoo(x?-?1)?+?foo(x?-?2)for?i?=?1,?i?<?10,?1.0?inprintd(foo(i))

    輸出:

    parsed?a?extern?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????[13/48988] declare?double?@printd(double)parsed?a?function?definition define?double?@foo(double?%x)?{ entry:%x1?=?alloca?double,?align?8store?double?%x,?double*?%x1,?align?8%cmptmp?=?fcmp?ult?double?%x,?3.000000e+00br?i1?%cmptmp,?label?%ifcont,?label?%elseelse:?????????????????????????????????????????????;?preds?=?%entry%subtmp?=?fadd?double?%x,?-1.000000e+00%calltmp?=?call?double?@foo(double?%subtmp)%subtmp5?=?fadd?double?%x,?-2.000000e+00%calltmp6?=?call?double?@foo(double?%subtmp5)%addtmp?=?fadd?double?%calltmp,?%calltmp6br?label?%ifcontifcont:???????????????????????????????????????????;?preds?=?%entry,?%else%iftmp?=?phi?double?[?%addtmp,?%else?],?[?1.000000e+00,?%entry?]ret?double?%iftmp }parsed?a?top?level?expr define?double?@__anon_expr()?{ entry:%i?=?alloca?double,?align?8store?double?1.000000e+00,?double*?%i,?align?8br?label?%looploop:?????????????????????????????????????????????;?preds?=?%loop,?%entry%i1?=?phi?double?[?%nextvar,?%loop?],?[?1.000000e+00,?%entry?]%calltmp?=?call?double?@foo(double?%i1)%calltmp2?=?call?double?@printd(double?%calltmp)%nextvar?=?fadd?double?%i1,?1.000000e+00store?double?%nextvar,?double*?%i,?align?8%cmptmp?=?fcmp?ult?double?%nextvar,?1.000000e+01br?i1?%cmptmp,?label?%loop,?label?%afterloopafterloop:????????????????????????????????????????;?preds?=?%loopret?double?0.000000e+00 }1.000000 1.000000 2.000000 3.000000 5.000000 8.000000 13.000000 21.000000 34.000000 0

    可以看到,新版本的 IR 中已經沒有了 phi node, 接下來我們加入優化器:

    g_fpm->add(llvm::createPromoteMemoryToRegisterPass());g_fpm->add(llvm::createInstructionCombiningPass());g_fpm->add(llvm::createReassociatePass());

    再次得到輸出:

    parsed?a?extern declare?double?@printd(double)parsed?a?function?definition define?double?@foo(double?%x)?{ entry:%cmptmp?=?fcmp?ult?double?%x,?3.000000e+00br?i1?%cmptmp,?label?%ifcont,?label?%elseelse:?????????????????????????????????????????????;?preds?=?%entry%subtmp?=?fadd?double?%x,?-1.000000e+00%calltmp?=?call?double?@foo(double?%subtmp)%subtmp5?=?fadd?double?%x,?-2.000000e+00%calltmp6?=?call?double?@foo(double?%subtmp5)%addtmp?=?fadd?double?%calltmp,?%calltmp6br?label?%ifcontifcont:???????????????????????????????????????????;?preds?=?%entry,?%else%iftmp?=?phi?double?[?%addtmp,?%else?],?[?1.000000e+00,?%entry?]ret?double?%iftmp }parsed?a?top?level?expr define?double?@__anon_expr()?{ entry:br?label?%looploop:?????????????????????????????????????????????;?preds?=?%loop,?%entry%i1?=?phi?double?[?%nextvar,?%loop?],?[?1.000000e+00,?%entry?]%calltmp?=?call?double?@foo(double?%i1)%calltmp2?=?call?double?@printd(double?%calltmp)%nextvar?=?fadd?double?%i1,?1.000000e+00%cmptmp?=?fcmp?ult?double?%nextvar,?1.000000e+01br?i1?%cmptmp,?label?%loop,?label?%afterloopafterloop:????????????????????????????????????????;?preds?=?%loopret?double?0.000000e+00 }1.000000 1.000000 2.000000 3.000000 5.000000 8.000000 13.000000 21.000000 34.000000 0

    可以看到,棧上變量自動地變為寄存器變量,且 phi node 自動地被添加。

    11. 完整代碼與參考資料

    完整代碼見:

    https://zhuanlan.zhihu.com/p/336929719

    參考:

    • https://en.wikipedia.org/wiki/Static_single_assignment_form

    • https://llvm.org/docs/tutorial/MyFirstLanguageFrontend/index.html

    歡迎大家多多交流,共同進步。

    總結

    以上是生活随笔為你收集整理的使用 LLVM 实现一个简单编译器的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    国内精品久久久久久中文字幕 | 亚洲 另类 在线 欧美 制服 | 暴力强奷在线播放无码 | 国产精品高潮呻吟av久久4虎 | 国产成人亚洲综合无码 | 中文字幕无线码 | 给我免费的视频在线观看 | 亚洲大尺度无码无码专区 | 亚洲色在线无码国产精品不卡 | 亚洲欧美日韩综合久久久 | 乱人伦中文视频在线观看 | 娇妻被黑人粗大高潮白浆 | 国产精品沙发午睡系列 | 国产亚洲精品久久久久久国模美 | 色一情一乱一伦一视频免费看 | 99久久精品国产一区二区蜜芽 | 精品无码av一区二区三区 | 性色欲网站人妻丰满中文久久不卡 | 无码人妻久久一区二区三区不卡 | 国产农村乱对白刺激视频 | 76少妇精品导航 | 国产9 9在线 | 中文 | 国产亚洲人成a在线v网站 | 又粗又大又硬毛片免费看 | 美女毛片一区二区三区四区 | 荫蒂被男人添的好舒服爽免费视频 | 精品久久久无码人妻字幂 | a在线观看免费网站大全 | 亚洲国产午夜精品理论片 | 四虎影视成人永久免费观看视频 | 久久综合狠狠综合久久综合88 | 亚洲色欲色欲天天天www | 色综合久久中文娱乐网 | 人妻中文无码久热丝袜 | 无码帝国www无码专区色综合 | 国产三级精品三级男人的天堂 | 图片小说视频一区二区 | 美女极度色诱视频国产 | 色一情一乱一伦一视频免费看 | 帮老师解开蕾丝奶罩吸乳网站 | 国产乱子伦视频在线播放 | 亚洲色欲久久久综合网东京热 | 色诱久久久久综合网ywww | 久久国内精品自在自线 | 成人影院yy111111在线观看 | 人妻有码中文字幕在线 | 精品aⅴ一区二区三区 | 国精产品一品二品国精品69xx | 色综合天天综合狠狠爱 | 亚洲人成网站免费播放 | 婷婷丁香六月激情综合啪 | 精品久久久无码中文字幕 | 在线a亚洲视频播放在线观看 | 影音先锋中文字幕无码 | 欧美人与牲动交xxxx | 午夜精品一区二区三区的区别 | 麻豆国产人妻欲求不满 | 两性色午夜视频免费播放 | 97久久超碰中文字幕 | 国产激情一区二区三区 | 久久人人爽人人人人片 | 97se亚洲精品一区 | 夫妻免费无码v看片 | 日韩欧美群交p片內射中文 | 搡女人真爽免费视频大全 | 久久久久久久人妻无码中文字幕爆 | 88国产精品欧美一区二区三区 | 亚洲乱码国产乱码精品精 | 成人影院yy111111在线观看 | 精品国产精品久久一区免费式 | 精品国产乱码久久久久乱码 | 色综合久久88色综合天天 | 久久久久久久人妻无码中文字幕爆 | 亚洲人交乣女bbw | 18禁止看的免费污网站 | 99久久精品国产一区二区蜜芽 | 国产在线aaa片一区二区99 | 狠狠亚洲超碰狼人久久 | 久久伊人色av天堂九九小黄鸭 | 无码中文字幕色专区 | 国产精品福利视频导航 | 欧美乱妇无乱码大黄a片 | 日韩av无码中文无码电影 | 中文无码精品a∨在线观看不卡 | 久久综合色之久久综合 | 啦啦啦www在线观看免费视频 | 色综合久久中文娱乐网 | 欧美大屁股xxxxhd黑色 | 亚洲日韩乱码中文无码蜜桃臀网站 | 国产精品99爱免费视频 | 欧美丰满熟妇xxxx | 日本饥渴人妻欲求不满 | 精品国产一区av天美传媒 | 日本www一道久久久免费榴莲 | 免费无码的av片在线观看 | 亚洲成a人一区二区三区 | 亚洲中文字幕无码中字 | 色偷偷av老熟女 久久精品人妻少妇一区二区三区 | 在教室伦流澡到高潮hnp视频 | 丝袜 中出 制服 人妻 美腿 | 欧美日本免费一区二区三区 | 国产精品亚洲综合色区韩国 | 无码人妻av免费一区二区三区 | 色婷婷香蕉在线一区二区 | 亚洲码国产精品高潮在线 | 色偷偷人人澡人人爽人人模 | 国产香蕉尹人综合在线观看 | 精品无人国产偷自产在线 | 色狠狠av一区二区三区 | 丰满人妻一区二区三区免费视频 | 欧美精品免费观看二区 | 又大又硬又爽免费视频 | 久久综合网欧美色妞网 | 日本爽爽爽爽爽爽在线观看免 | 小sao货水好多真紧h无码视频 | 亚洲国产欧美国产综合一区 | 久久精品国产一区二区三区肥胖 | 麻豆人妻少妇精品无码专区 | 日韩 欧美 动漫 国产 制服 | 香港三级日本三级妇三级 | 国产精品亚洲综合色区韩国 | 日韩精品无码一本二本三本色 | 76少妇精品导航 | 欧美日韩在线亚洲综合国产人 | 在线天堂新版最新版在线8 | 成人免费无码大片a毛片 | 青青青手机频在线观看 | 亚洲中文字幕成人无码 | 人妻有码中文字幕在线 | 欧美日韩久久久精品a片 | 1000部夫妻午夜免费 | 国产真实伦对白全集 | 男女下面进入的视频免费午夜 | 亚洲精品一区二区三区四区五区 | www国产精品内射老师 | 精品少妇爆乳无码av无码专区 | 少妇一晚三次一区二区三区 | 国产香蕉97碰碰久久人人 | 色综合久久久无码网中文 | 欧美野外疯狂做受xxxx高潮 | 久久久中文字幕日本无吗 | 性生交片免费无码看人 | 免费无码av一区二区 | 国产精品无码一区二区三区不卡 | 日韩精品无码一区二区中文字幕 | 乌克兰少妇性做爰 | 强开小婷嫩苞又嫩又紧视频 | 国产精品18久久久久久麻辣 | 伊人久久婷婷五月综合97色 | 奇米影视888欧美在线观看 | 国产成人无码午夜视频在线观看 | 人妻少妇精品无码专区动漫 | 日欧一片内射va在线影院 | 久久国产精品萌白酱免费 | 亚洲一区av无码专区在线观看 | 国产精品对白交换视频 | www一区二区www免费 | 精品人人妻人人澡人人爽人人 | 丰满诱人的人妻3 | а天堂中文在线官网 | 黑人巨大精品欧美一区二区 | 国产97在线 | 亚洲 | 亚洲国产精品久久久久久 | 亚洲精品一区国产 | 国产成人无码专区 | 男女猛烈xx00免费视频试看 | 国产亚洲精品久久久闺蜜 | 亚洲男人av天堂午夜在 | 亚洲精品午夜无码电影网 | 亚洲一区二区三区偷拍女厕 | 免费男性肉肉影院 | 欧美丰满熟妇xxxx性ppx人交 | 狂野欧美性猛xxxx乱大交 | 亚洲欧美色中文字幕在线 | 国产精品久免费的黄网站 | 精品人妻人人做人人爽 | 精品无码一区二区三区的天堂 | 国产激情综合五月久久 | 日产精品99久久久久久 | 国产猛烈高潮尖叫视频免费 | 色综合久久久久综合一本到桃花网 | 国产热a欧美热a在线视频 | 久热国产vs视频在线观看 | 成在人线av无码免观看麻豆 | 免费乱码人妻系列无码专区 | 性色欲情网站iwww九文堂 | 自拍偷自拍亚洲精品被多人伦好爽 | 久久人人爽人人人人片 | 免费国产黄网站在线观看 | 日本一卡2卡3卡4卡无卡免费网站 国产一区二区三区影院 | 亚洲日韩乱码中文无码蜜桃臀网站 | 无码一区二区三区在线 | 丁香花在线影院观看在线播放 | 国语自产偷拍精品视频偷 | 国产免费久久精品国产传媒 | 欧美黑人乱大交 | 久久精品人妻少妇一区二区三区 | 中文字幕无码免费久久99 | 99精品无人区乱码1区2区3区 | 久久国语露脸国产精品电影 | 午夜理论片yy44880影院 | 中文字幕无码av波多野吉衣 | 中文字幕无码免费久久99 | 亚洲中文字幕va福利 | 牲欲强的熟妇农村老妇女视频 | 精品久久久无码人妻字幂 | 成年女人永久免费看片 | 成人av无码一区二区三区 | 精品无码国产一区二区三区av | 日韩精品无码一本二本三本色 | 国产精品理论片在线观看 | 成熟人妻av无码专区 | 国产精品第一国产精品 | 亚洲午夜无码久久 | 露脸叫床粗话东北少妇 | 色妞www精品免费视频 | 99久久婷婷国产综合精品青草免费 | 国产午夜精品一区二区三区嫩草 | 亚洲国产午夜精品理论片 | 国产亚洲欧美日韩亚洲中文色 | 免费乱码人妻系列无码专区 | 东京热无码av男人的天堂 | 永久免费观看国产裸体美女 | 国产无遮挡吃胸膜奶免费看 | 日韩精品成人一区二区三区 | 欧美国产日韩久久mv | 久久久久免费精品国产 | 久久综合给合久久狠狠狠97色 | 无码国内精品人妻少妇 | 久久人人97超碰a片精品 | 99久久亚洲精品无码毛片 | 综合激情五月综合激情五月激情1 | 蜜桃av抽搐高潮一区二区 | 久久久精品456亚洲影院 | 黑人巨大精品欧美一区二区 | 亚洲欧洲中文日韩av乱码 | 亚洲成av人片在线观看无码不卡 | 亚洲男女内射在线播放 | 国产午夜福利亚洲第一 | 国产真实夫妇视频 | 成人免费无码大片a毛片 | √8天堂资源地址中文在线 | 免费播放一区二区三区 | 欧美精品国产综合久久 | 亚洲男女内射在线播放 | 亚洲自偷自偷在线制服 | 欧美老熟妇乱xxxxx | 无人区乱码一区二区三区 | 天干天干啦夜天干天2017 | 亚洲热妇无码av在线播放 | 国产午夜精品一区二区三区嫩草 | 人妻少妇精品久久 | 天堂а√在线中文在线 | 精品 日韩 国产 欧美 视频 | 欧美午夜特黄aaaaaa片 | 欧美人与禽zoz0性伦交 | 欧美丰满老熟妇xxxxx性 | 亚洲色在线无码国产精品不卡 | 人人妻人人澡人人爽欧美一区 | 亚洲综合精品香蕉久久网 | 色一情一乱一伦一视频免费看 | 亚洲狠狠婷婷综合久久 | 综合激情五月综合激情五月激情1 | 亚洲一区二区三区国产精华液 | 日韩欧美成人免费观看 | 欧洲vodafone精品性 | 无码av中文字幕免费放 | 亚洲欧洲日本综合aⅴ在线 | aⅴ在线视频男人的天堂 | 粉嫩少妇内射浓精videos | 国产内射爽爽大片视频社区在线 | 亚洲精品美女久久久久久久 | www一区二区www免费 | 老熟妇乱子伦牲交视频 | 性色av无码免费一区二区三区 | 婷婷六月久久综合丁香 | 中文字幕无码日韩欧毛 | 无码成人精品区在线观看 | 亚洲精品成人福利网站 | 俺去俺来也在线www色官网 | 久久国内精品自在自线 | 日产精品高潮呻吟av久久 | 300部国产真实乱 | 久久国产精品萌白酱免费 | 欧美野外疯狂做受xxxx高潮 | 国产在线一区二区三区四区五区 | 久久久成人毛片无码 | 国产人成高清在线视频99最全资源 | 任你躁国产自任一区二区三区 | 日日天干夜夜狠狠爱 | 丰满人妻精品国产99aⅴ | 久久久久久久久蜜桃 | 免费网站看v片在线18禁无码 | 国产香蕉97碰碰久久人人 | 日韩av无码一区二区三区 | 国产在线一区二区三区四区五区 | 无码国产色欲xxxxx视频 | 性生交大片免费看l | 亚洲乱码日产精品bd | 亚洲国产综合无码一区 | 久久久www成人免费毛片 | 久久精品中文字幕大胸 | 国产婷婷色一区二区三区在线 | 在线观看免费人成视频 | 波多野结衣一区二区三区av免费 | 久久99精品国产麻豆蜜芽 | 午夜无码人妻av大片色欲 | 亚洲狠狠色丁香婷婷综合 | 欧洲vodafone精品性 | 日本在线高清不卡免费播放 | 大屁股大乳丰满人妻 | 久久久久亚洲精品男人的天堂 | 啦啦啦www在线观看免费视频 | 欧美兽交xxxx×视频 | 人妻与老人中文字幕 | 无码人妻黑人中文字幕 | 5858s亚洲色大成网站www | 成熟妇人a片免费看网站 | 日韩欧美中文字幕在线三区 | 久久久亚洲欧洲日产国码αv | 精品无码一区二区三区的天堂 | 成人免费无码大片a毛片 | 日本精品人妻无码免费大全 | 一区二区三区乱码在线 | 欧洲 | a在线观看免费网站大全 | 人人妻人人澡人人爽人人精品 | 久久综合香蕉国产蜜臀av | 国产成人精品久久亚洲高清不卡 | 正在播放老肥熟妇露脸 | 国产麻豆精品一区二区三区v视界 | 亚洲国产精品美女久久久久 | 最新国产麻豆aⅴ精品无码 | 无码精品国产va在线观看dvd | 亚洲乱码国产乱码精品精 | 亚洲成av人片天堂网无码】 | 国产成人无码av一区二区 | 久久午夜无码鲁丝片秋霞 | 日韩av无码一区二区三区不卡 | 99久久精品无码一区二区毛片 | 国产亚洲精品久久久久久久久动漫 | 国产av人人夜夜澡人人爽麻豆 | 高清国产亚洲精品自在久久 | 欧美亚洲日韩国产人成在线播放 | 国产亚洲精品久久久久久国模美 | 2019nv天堂香蕉在线观看 | 日韩精品无码免费一区二区三区 | 免费观看又污又黄的网站 | 性欧美大战久久久久久久 | 玩弄人妻少妇500系列视频 | 精品乱码久久久久久久 | 国产成人无码午夜视频在线观看 | 丰腴饱满的极品熟妇 | 99久久精品日本一区二区免费 | 欧美日韩综合一区二区三区 | 大地资源网第二页免费观看 | 色综合久久中文娱乐网 | 无人区乱码一区二区三区 | 精品无人国产偷自产在线 | 亚洲熟妇色xxxxx欧美老妇y | 狠狠色噜噜狠狠狠7777奇米 | 99re在线播放 | 欧美日韩视频无码一区二区三 | 国产成人精品视频ⅴa片软件竹菊 | 丰满人妻精品国产99aⅴ | 欧美熟妇另类久久久久久不卡 | 久久99热只有频精品8 | 76少妇精品导航 | 欧美日韩综合一区二区三区 | 精品久久久久香蕉网 | 国精品人妻无码一区二区三区蜜柚 | 综合网日日天干夜夜久久 | 亚洲伊人久久精品影院 | 久久久精品国产sm最大网站 | 青青草原综合久久大伊人精品 | 久久久精品456亚洲影院 | 亚洲一区二区三区国产精华液 | 一区二区传媒有限公司 | 亚洲国产成人a精品不卡在线 | 无码国模国产在线观看 | 98国产精品综合一区二区三区 | 无码播放一区二区三区 | 亚洲伊人久久精品影院 | 亚洲国产欧美在线成人 | 国内少妇偷人精品视频免费 | 人人妻人人澡人人爽精品欧美 | 国产高清av在线播放 | 天堂亚洲2017在线观看 | 日本精品人妻无码免费大全 | 国产成人无码午夜视频在线观看 | 国产特级毛片aaaaaaa高清 | 夜先锋av资源网站 | 成人无码视频免费播放 | 综合网日日天干夜夜久久 | 久久精品国产一区二区三区 | 麻豆av传媒蜜桃天美传媒 | 日本一区二区更新不卡 | 四虎影视成人永久免费观看视频 | 午夜福利不卡在线视频 | 亚洲熟熟妇xxxx | 亚洲综合无码一区二区三区 | 无人区乱码一区二区三区 | 18精品久久久无码午夜福利 | 免费播放一区二区三区 | 成人影院yy111111在线观看 | 熟妇女人妻丰满少妇中文字幕 | 爆乳一区二区三区无码 | 久久人人爽人人人人片 | 久久国内精品自在自线 | 精品欧洲av无码一区二区三区 | 亚洲狠狠婷婷综合久久 | 女高中生第一次破苞av | www国产亚洲精品久久久日本 | 欧美丰满熟妇xxxx性ppx人交 | 亚洲中文字幕无码一久久区 | 亚洲七七久久桃花影院 | 色综合视频一区二区三区 | 亚洲精品一区二区三区在线 | 麻豆果冻传媒2021精品传媒一区下载 | 日韩无码专区 | 乌克兰少妇性做爰 | 玩弄中年熟妇正在播放 | 亚洲精品国产品国语在线观看 | 99久久亚洲精品无码毛片 | 99在线 | 亚洲 | 在线欧美精品一区二区三区 | 欧美人与动性行为视频 | 久久久精品欧美一区二区免费 | 麻豆国产人妻欲求不满谁演的 | 午夜精品一区二区三区的区别 | 国产精品亚洲lv粉色 | 自拍偷自拍亚洲精品被多人伦好爽 | 亚洲熟妇色xxxxx欧美老妇 | 国产成人精品无码播放 | 日日摸夜夜摸狠狠摸婷婷 | 狠狠亚洲超碰狼人久久 | 一二三四在线观看免费视频 | 国产成人无码一二三区视频 | 精品久久8x国产免费观看 | 黑人粗大猛烈进出高潮视频 | 国产精品久久久久久亚洲影视内衣 | 精品偷自拍另类在线观看 | 欧美日韩人成综合在线播放 | 中文字幕日韩精品一区二区三区 | 久久99久久99精品中文字幕 | 老熟妇仑乱视频一区二区 | 国产精品.xx视频.xxtv | 无人区乱码一区二区三区 | 久久久www成人免费毛片 | 51国偷自产一区二区三区 | 黑人大群体交免费视频 | 在线а√天堂中文官网 | 国产精品无码mv在线观看 | 大色综合色综合网站 | 欧美精品免费观看二区 | 在线天堂新版最新版在线8 | 午夜精品久久久久久久 | 国产精品久久福利网站 | 少妇太爽了在线观看 | 久久无码人妻影院 | 好爽又高潮了毛片免费下载 | yw尤物av无码国产在线观看 | 亚洲无人区午夜福利码高清完整版 | 无码纯肉视频在线观看 | 国精品人妻无码一区二区三区蜜柚 | 久久久精品成人免费观看 | 无遮挡国产高潮视频免费观看 | 草草网站影院白丝内射 | 麻豆国产人妻欲求不满谁演的 | 麻豆国产丝袜白领秘书在线观看 | 日韩亚洲欧美中文高清在线 | 国产乱人无码伦av在线a | 波多野结衣aⅴ在线 | 国产精品久久久久无码av色戒 | 国产特级毛片aaaaaa高潮流水 | 国产人妖乱国产精品人妖 | 国产三级精品三级男人的天堂 | 好爽又高潮了毛片免费下载 | 国内精品人妻无码久久久影院 | 日韩精品久久久肉伦网站 | 久青草影院在线观看国产 | 国产97在线 | 亚洲 | 成人av无码一区二区三区 | 麻豆国产丝袜白领秘书在线观看 | 亚洲成色www久久网站 | 国产另类ts人妖一区二区 | 久久婷婷五月综合色国产香蕉 | 精品成人av一区二区三区 | 学生妹亚洲一区二区 | 日本护士毛茸茸高潮 | 亚洲 日韩 欧美 成人 在线观看 | 久青草影院在线观看国产 | 日韩精品a片一区二区三区妖精 | 日韩精品无码免费一区二区三区 | 中文字幕 人妻熟女 | 日韩精品久久久肉伦网站 | 老熟妇仑乱视频一区二区 | 高清不卡一区二区三区 | 18黄暴禁片在线观看 | 日本一卡2卡3卡四卡精品网站 | 97精品国产97久久久久久免费 | 国产人成高清在线视频99最全资源 | 国产精品美女久久久久av爽李琼 | 午夜福利试看120秒体验区 | 国产精品久久国产精品99 | 999久久久国产精品消防器材 | 国产三级久久久精品麻豆三级 | 久久亚洲精品成人无码 | 乱码午夜-极国产极内射 | 西西人体www44rt大胆高清 | av香港经典三级级 在线 | 精品人人妻人人澡人人爽人人 | 亚洲成av人影院在线观看 | 免费国产成人高清在线观看网站 | 亚洲成a人片在线观看无码 | 精品人妻人人做人人爽 | 97久久国产亚洲精品超碰热 | 久久精品中文字幕大胸 | 国产又爽又黄又刺激的视频 | 国产亚洲精品久久久久久大师 | 牲欲强的熟妇农村老妇女视频 | 色婷婷久久一区二区三区麻豆 | 亚洲精品中文字幕久久久久 | 精品亚洲成av人在线观看 | 黑人巨大精品欧美黑寡妇 | 国内精品一区二区三区不卡 | 麻豆精品国产精华精华液好用吗 | 伊人久久婷婷五月综合97色 | 国产午夜福利100集发布 | 国产三级精品三级男人的天堂 | 网友自拍区视频精品 | 99久久精品午夜一区二区 | 日本精品高清一区二区 | 久久婷婷五月综合色国产香蕉 | 一本色道婷婷久久欧美 | 色一情一乱一伦一视频免费看 | 激情内射日本一区二区三区 | 久久久国产精品无码免费专区 | 欧美丰满老熟妇xxxxx性 | 人人爽人人澡人人人妻 | 日韩少妇白浆无码系列 | 亚洲综合色区中文字幕 | 玩弄中年熟妇正在播放 | 欧美freesex黑人又粗又大 | 东京一本一道一二三区 | 在线观看免费人成视频 | 国产亚洲精品久久久久久久久动漫 | aa片在线观看视频在线播放 | 午夜丰满少妇性开放视频 | 99久久婷婷国产综合精品青草免费 | 亚洲人交乣女bbw | 少妇久久久久久人妻无码 | 丰满人妻精品国产99aⅴ | 国产无av码在线观看 | 国产办公室秘书无码精品99 | 亚洲人成网站色7799 | 全黄性性激高免费视频 | 亚洲精品午夜国产va久久成人 | 强奷人妻日本中文字幕 | 国产午夜福利100集发布 | 成人影院yy111111在线观看 | 天堂а√在线地址中文在线 | 波多野结衣一区二区三区av免费 | 大色综合色综合网站 | 亚洲爆乳精品无码一区二区三区 | 亚洲精品国偷拍自产在线观看蜜桃 | 国产精品久久久久久久9999 | 国产精品嫩草久久久久 | 扒开双腿吃奶呻吟做受视频 | 奇米影视7777久久精品 | 亚洲熟熟妇xxxx | 日本丰满熟妇videos | 野外少妇愉情中文字幕 | 欧洲vodafone精品性 | 国产极品美女高潮无套在线观看 | 欧美国产日韩亚洲中文 | a片在线免费观看 | 在线观看欧美一区二区三区 | 无码人妻出轨黑人中文字幕 | 亚洲啪av永久无码精品放毛片 | 免费中文字幕日韩欧美 | 久久精品99久久香蕉国产色戒 | 国产舌乚八伦偷品w中 | 丝袜 中出 制服 人妻 美腿 | 熟妇人妻无码xxx视频 | 未满成年国产在线观看 | 女人被男人爽到呻吟的视频 | 真人与拘做受免费视频一 | 澳门永久av免费网站 | 亚洲七七久久桃花影院 | 午夜精品久久久久久久久 | 蜜臀aⅴ国产精品久久久国产老师 | 国产乱人偷精品人妻a片 | 亚洲熟女一区二区三区 | 亚洲欧美国产精品久久 | 18无码粉嫩小泬无套在线观看 | 欧美日韩人成综合在线播放 | 最近免费中文字幕中文高清百度 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 欧美freesex黑人又粗又大 | 亚洲色大成网站www | 久久久亚洲欧洲日产国码αv | 丰满肥臀大屁股熟妇激情视频 | 亚洲熟悉妇女xxx妇女av | 国产色精品久久人妻 | 一本一道久久综合久久 | 久久亚洲中文字幕精品一区 | yw尤物av无码国产在线观看 | 亚洲人亚洲人成电影网站色 | 牲欲强的熟妇农村老妇女视频 | 乱中年女人伦av三区 | 又粗又大又硬又长又爽 | 亚无码乱人伦一区二区 | 久久久久国色av免费观看性色 | 东京热无码av男人的天堂 | 午夜福利不卡在线视频 | 麻豆国产人妻欲求不满 | 麻豆成人精品国产免费 | 色综合久久久无码中文字幕 | 无码免费一区二区三区 | 麻豆蜜桃av蜜臀av色欲av | 国产情侣作爱视频免费观看 | 欧美丰满熟妇xxxx性ppx人交 | 三上悠亚人妻中文字幕在线 | 免费人成网站视频在线观看 | 中文字幕精品av一区二区五区 | 国产精品18久久久久久麻辣 | 国产一精品一av一免费 | 亚洲综合久久一区二区 | 97精品人妻一区二区三区香蕉 | 午夜熟女插插xx免费视频 | 撕开奶罩揉吮奶头视频 | 日本爽爽爽爽爽爽在线观看免 | 俺去俺来也在线www色官网 | 亚洲精品一区二区三区四区五区 | 精品无码av一区二区三区 | 亚洲成在人网站无码天堂 | 亚洲精品国产第一综合99久久 | 久久久中文字幕日本无吗 | 久久 国产 尿 小便 嘘嘘 | 国产另类ts人妖一区二区 | 无码一区二区三区在线 | 国产sm调教视频在线观看 | 麻豆md0077饥渴少妇 | 国内少妇偷人精品视频免费 | 国产精华av午夜在线观看 | 午夜精品久久久久久久 | 18精品久久久无码午夜福利 | 国产性生交xxxxx无码 | 亚洲精品午夜国产va久久成人 | 国产成人久久精品流白浆 | 水蜜桃色314在线观看 | 国产内射老熟女aaaa | 老熟女重囗味hdxx69 | 亚洲精品无码国产 | 精品人人妻人人澡人人爽人人 | 久青草影院在线观看国产 | 西西人体www44rt大胆高清 | 日本一区二区三区免费播放 | 无码免费一区二区三区 | 久在线观看福利视频 | 午夜精品一区二区三区在线观看 | 天天拍夜夜添久久精品大 | 亚洲熟妇色xxxxx亚洲 | 日韩无码专区 | 亚洲中文字幕无码中文字在线 | 国产亚洲美女精品久久久2020 | 男女爱爱好爽视频免费看 | 久久久av男人的天堂 | 欧美35页视频在线观看 | 理论片87福利理论电影 | 亚洲区欧美区综合区自拍区 | 欧美人与牲动交xxxx | 欧美兽交xxxx×视频 | 一本色道久久综合亚洲精品不卡 | a片在线免费观看 | 少妇厨房愉情理9仑片视频 | 国产精品鲁鲁鲁 | 4hu四虎永久在线观看 | 亚洲日韩一区二区三区 | 成人性做爰aaa片免费看 | 四虎影视成人永久免费观看视频 | 久久亚洲a片com人成 | 国产av剧情md精品麻豆 | 高清不卡一区二区三区 | 国产综合在线观看 | 国产成人亚洲综合无码 | 久久久久久亚洲精品a片成人 | 国产香蕉尹人视频在线 | 人人妻人人澡人人爽欧美一区九九 | 麻豆国产97在线 | 欧洲 | 青春草在线视频免费观看 | 午夜时刻免费入口 | 人妻夜夜爽天天爽三区 | 乌克兰少妇xxxx做受 | 亚洲色欲色欲欲www在线 | 天天av天天av天天透 | 国产69精品久久久久app下载 | 亚洲熟妇自偷自拍另类 | 成在人线av无码免费 | 无码人妻少妇伦在线电影 | 偷窥日本少妇撒尿chinese | 少妇无码一区二区二三区 | 国产精品亚洲专区无码不卡 | 草草网站影院白丝内射 | 亚洲最大成人网站 | 55夜色66夜色国产精品视频 | 波多野结衣一区二区三区av免费 | 人妻少妇精品无码专区二区 | 久久天天躁狠狠躁夜夜免费观看 | 国产精品亚洲专区无码不卡 | 色偷偷av老熟女 久久精品人妻少妇一区二区三区 | 精品国产av色一区二区深夜久久 | 在线а√天堂中文官网 | 午夜无码区在线观看 | 永久免费观看美女裸体的网站 | 亚洲欧美综合区丁香五月小说 | 无人区乱码一区二区三区 | 日韩人妻系列无码专区 | 国模大胆一区二区三区 | 成人免费无码大片a毛片 | 波多野结衣乳巨码无在线观看 | 亚洲国产日韩a在线播放 | 99久久精品国产一区二区蜜芽 | 精品亚洲韩国一区二区三区 | 国产舌乚八伦偷品w中 | 国产日产欧产精品精品app | 激情内射亚州一区二区三区爱妻 | 无遮挡国产高潮视频免费观看 | 强奷人妻日本中文字幕 | 国产偷自视频区视频 | 国产亚洲视频中文字幕97精品 | 黑人玩弄人妻中文在线 | 久久99精品国产.久久久久 | 中文毛片无遮挡高清免费 | 欧美乱妇无乱码大黄a片 | 久久伊人色av天堂九九小黄鸭 | 久久国产精品萌白酱免费 | 国产熟妇高潮叫床视频播放 | 国产乱码精品一品二品 | 亚洲国产精品久久人人爱 | 人妻少妇精品无码专区动漫 | 十八禁视频网站在线观看 | 俺去俺来也在线www色官网 | 日本大香伊一区二区三区 | 久久亚洲中文字幕无码 | 国产精品成人av在线观看 | 无人区乱码一区二区三区 | 人人澡人人妻人人爽人人蜜桃 | 无码一区二区三区在线观看 | 国产精品怡红院永久免费 | 日本乱人伦片中文三区 | 日本在线高清不卡免费播放 | 丰满诱人的人妻3 | 久久综合狠狠综合久久综合88 | 一本无码人妻在中文字幕免费 | 欧美xxxxx精品 | 欧美亚洲日韩国产人成在线播放 | 亚洲中文字幕无码一久久区 | 中文字幕无码视频专区 | 国产在热线精品视频 | 无码中文字幕色专区 | 亚洲午夜福利在线观看 | 久久国产劲爆∧v内射 | 国产av无码专区亚洲a∨毛片 | 无码免费一区二区三区 | 亚洲精品综合五月久久小说 | 亚洲国产精品成人久久蜜臀 | 粗大的内捧猛烈进出视频 | 国产内射老熟女aaaa | 国产成人无码av一区二区 | 国内少妇偷人精品视频 | 在线亚洲高清揄拍自拍一品区 | 131美女爱做视频 | 欧美精品一区二区精品久久 | 无码乱肉视频免费大全合集 | 亚洲 另类 在线 欧美 制服 | 亚洲精品综合一区二区三区在线 | 蜜桃无码一区二区三区 | 乱码午夜-极国产极内射 | 久久亚洲中文字幕无码 | 婷婷五月综合缴情在线视频 | 亚洲精品国偷拍自产在线麻豆 | 红桃av一区二区三区在线无码av | 国产午夜无码视频在线观看 | 国产女主播喷水视频在线观看 | 久久熟妇人妻午夜寂寞影院 | 熟女少妇在线视频播放 | 久久久久国色av免费观看性色 | 亚洲色大成网站www | 国产内射爽爽大片视频社区在线 | 久久亚洲中文字幕精品一区 | 久久无码专区国产精品s | 中文字幕 人妻熟女 | 青青青爽视频在线观看 | 日韩人妻系列无码专区 | 精品欧美一区二区三区久久久 | 一本久久a久久精品vr综合 | 98国产精品综合一区二区三区 | 久久久亚洲欧洲日产国码αv | 东北女人啪啪对白 | 欧美自拍另类欧美综合图片区 | 亚洲 激情 小说 另类 欧美 | 露脸叫床粗话东北少妇 | 国产精品va在线观看无码 | 夜夜躁日日躁狠狠久久av | 国产午夜精品一区二区三区嫩草 | 久久综合狠狠综合久久综合88 | 国内揄拍国内精品人妻 | 久久久久亚洲精品中文字幕 | 日韩亚洲欧美精品综合 | 久久综合久久自在自线精品自 | 激情爆乳一区二区三区 | 中国大陆精品视频xxxx | 男女猛烈xx00免费视频试看 | 丰满妇女强制高潮18xxxx | 亚洲国产成人a精品不卡在线 | 亚洲色欲色欲天天天www | 国产一区二区三区精品视频 | 国产av一区二区精品久久凹凸 | 九九热爱视频精品 | 欧美色就是色 | 久久久中文字幕日本无吗 | 国产熟女一区二区三区四区五区 | 国产精品-区区久久久狼 | 日韩无套无码精品 | 无码人妻精品一区二区三区下载 | 日日摸夜夜摸狠狠摸婷婷 | 婷婷五月综合激情中文字幕 | 国产成人久久精品流白浆 | 亚洲成a人一区二区三区 | 麻豆国产人妻欲求不满谁演的 | 成人综合网亚洲伊人 | 在线播放免费人成毛片乱码 | 玩弄少妇高潮ⅹxxxyw | 人妻中文无码久热丝袜 | 国产美女极度色诱视频www | 黑人大群体交免费视频 | 国产精品99久久精品爆乳 | 性啪啪chinese东北女人 | 欧美兽交xxxx×视频 | 国产乱人伦偷精品视频 | 国产精品内射视频免费 | 亚洲乱码国产乱码精品精 | 国产内射老熟女aaaa | 狠狠cao日日穞夜夜穞av | 3d动漫精品啪啪一区二区中 | 亚洲国产精品成人久久蜜臀 | 精品无码国产一区二区三区av | 最近免费中文字幕中文高清百度 | 亚洲综合在线一区二区三区 | 亚洲精品欧美二区三区中文字幕 | 亚洲中文字幕在线观看 | 小泽玛莉亚一区二区视频在线 | 一本久久a久久精品亚洲 | 在线精品国产一区二区三区 | 天堂无码人妻精品一区二区三区 | 国产精品毛多多水多 | 亚洲精品成a人在线观看 | 国产在线aaa片一区二区99 | 免费人成在线观看网站 | 色 综合 欧美 亚洲 国产 | 国产三级精品三级男人的天堂 | 亚洲国产精品一区二区美利坚 | 国产亚洲美女精品久久久2020 | а√资源新版在线天堂 | 日日橹狠狠爱欧美视频 | 水蜜桃亚洲一二三四在线 | 色 综合 欧美 亚洲 国产 | 丝袜美腿亚洲一区二区 | 国产精品亚洲综合色区韩国 | 四虎国产精品免费久久 | 欧美日韩一区二区三区自拍 | 国产一区二区三区日韩精品 | 日韩av无码中文无码电影 | 中文字幕av无码一区二区三区电影 | 中文字幕无码热在线视频 | 日本熟妇乱子伦xxxx | 成人无码视频在线观看网站 | 日韩欧美中文字幕在线三区 | 国产精品理论片在线观看 | 亚洲七七久久桃花影院 | 亚洲精品国产精品乱码视色 | 一本久久伊人热热精品中文字幕 | 国产av一区二区三区最新精品 | 色综合久久久无码中文字幕 | 久久99精品久久久久婷婷 | av无码不卡在线观看免费 | 天天综合网天天综合色 | 午夜免费福利小电影 | 国产成人无码a区在线观看视频app | 午夜嘿嘿嘿影院 | 成人一区二区免费视频 | 无遮挡啪啪摇乳动态图 | 久久精品国产精品国产精品污 | 国产精品内射视频免费 | 欧美日韩亚洲国产精品 | 久久久久久久久888 | 久久久久av无码免费网 | 99久久久无码国产精品免费 | 色欲综合久久中文字幕网 | 国产精品美女久久久网av | 国产97人人超碰caoprom | 午夜精品一区二区三区的区别 | 水蜜桃亚洲一二三四在线 | 97无码免费人妻超级碰碰夜夜 | 无码免费一区二区三区 | 人妻体内射精一区二区三四 | 亚洲国产欧美日韩精品一区二区三区 | 永久免费精品精品永久-夜色 | 日本在线高清不卡免费播放 | 天天拍夜夜添久久精品大 | 奇米综合四色77777久久 东京无码熟妇人妻av在线网址 | 国产av剧情md精品麻豆 | 久久国语露脸国产精品电影 | 香蕉久久久久久av成人 | 国产亚洲精品精品国产亚洲综合 | 国内揄拍国内精品少妇国语 | 国产成人无码午夜视频在线观看 | 免费国产黄网站在线观看 | 性欧美熟妇videofreesex | 亚洲熟妇色xxxxx欧美老妇y | 熟妇人妻激情偷爽文 | 亚洲日本在线电影 | 熟妇人妻激情偷爽文 | 四虎国产精品一区二区 | 国产精品毛多多水多 | 成人无码精品一区二区三区 | 国产 精品 自在自线 | 中文无码精品a∨在线观看不卡 | 国产精品亚洲一区二区三区喷水 | 亚洲狠狠色丁香婷婷综合 | 自拍偷自拍亚洲精品被多人伦好爽 | 国产va免费精品观看 | 国产成人人人97超碰超爽8 | 色五月五月丁香亚洲综合网 | 久久久中文字幕日本无吗 | 成熟女人特级毛片www免费 | 久久午夜无码鲁丝片 | 无套内谢的新婚少妇国语播放 | 国产内射爽爽大片视频社区在线 | 国产香蕉尹人视频在线 | 国产精品丝袜黑色高跟鞋 | 国产成人一区二区三区在线观看 | 国产人妻人伦精品 | 国产精品无码永久免费888 | 欧美人与禽zoz0性伦交 | 精品日本一区二区三区在线观看 | 亚洲熟熟妇xxxx | 久久无码专区国产精品s | 亚洲熟妇色xxxxx亚洲 | 中文字幕人妻丝袜二区 | 丰满人妻精品国产99aⅴ | 亚洲天堂2017无码 | 无码人妻出轨黑人中文字幕 | 精品久久久久久人妻无码中文字幕 | 久久午夜夜伦鲁鲁片无码免费 | 亚洲区欧美区综合区自拍区 | 精品人妻人人做人人爽 | 乌克兰少妇xxxx做受 | 动漫av网站免费观看 | a片免费视频在线观看 | 一本色道婷婷久久欧美 | 国产精品久久久久无码av色戒 | 亚洲天堂2017无码中文 | 国产成人久久精品流白浆 | 欧美放荡的少妇 | 日韩av无码一区二区三区 | 日韩精品无码一区二区中文字幕 | 色婷婷av一区二区三区之红樱桃 | 国产熟妇高潮叫床视频播放 | 人人妻人人澡人人爽人人精品浪潮 | 国产真实伦对白全集 | 久久成人a毛片免费观看网站 | 麻豆人妻少妇精品无码专区 | 伊在人天堂亚洲香蕉精品区 | 久久亚洲a片com人成 | 色婷婷久久一区二区三区麻豆 | 人人超人人超碰超国产 | 精品国精品国产自在久国产87 | 扒开双腿疯狂进出爽爽爽视频 | 久久国产精品萌白酱免费 | 日韩av无码一区二区三区 | 亚洲欧洲中文日韩av乱码 | 国产精品va在线观看无码 | 最新国产乱人伦偷精品免费网站 | 国产成人精品一区二区在线小狼 | 人人妻人人澡人人爽欧美一区九九 | 亚洲区小说区激情区图片区 | 久久久久99精品成人片 | 中文字幕无线码 | 亚洲日韩av一区二区三区中文 | 亚洲欧美国产精品久久 | 成在人线av无码免费 | 日日天干夜夜狠狠爱 | 亚洲精品成a人在线观看 | 国产舌乚八伦偷品w中 | 亚洲欧美国产精品久久 | 四十如虎的丰满熟妇啪啪 | 亚洲成av人在线观看网址 | 一本大道久久东京热无码av | 久久综合狠狠综合久久综合88 | 日日鲁鲁鲁夜夜爽爽狠狠 | 亚洲熟熟妇xxxx | 亚洲啪av永久无码精品放毛片 | aⅴ在线视频男人的天堂 | 欧美怡红院免费全部视频 | 亚洲精品一区二区三区在线观看 | 国产亚洲精品久久久久久大师 | 国产热a欧美热a在线视频 | 无码午夜成人1000部免费视频 | 国产美女极度色诱视频www | 亚洲精品午夜国产va久久成人 | 亚洲日韩中文字幕在线播放 | 日韩 欧美 动漫 国产 制服 | 九九热爱视频精品 | 少妇一晚三次一区二区三区 | 欧美阿v高清资源不卡在线播放 | 国产精品视频免费播放 | 中文字幕乱码人妻二区三区 | 18黄暴禁片在线观看 | 亚洲人成网站色7799 | а天堂中文在线官网 | 成年美女黄网站色大免费全看 | 男女作爱免费网站 | 午夜理论片yy44880影院 | 国产精品久久久久久亚洲毛片 | 久久99精品久久久久久 | 国产精品亚洲专区无码不卡 | 成人无码视频免费播放 | 国产精品手机免费 | av在线亚洲欧洲日产一区二区 | 欧美丰满熟妇xxxx性ppx人交 | 粉嫩少妇内射浓精videos | 亚洲精品综合五月久久小说 | 亚洲 欧美 激情 小说 另类 | 国产精品第一国产精品 | 无码帝国www无码专区色综合 | 人妻少妇精品久久 | 精品日本一区二区三区在线观看 | 18禁黄网站男男禁片免费观看 | 亚洲无人区午夜福利码高清完整版 | 未满成年国产在线观看 | 国产无遮挡又黄又爽又色 | 夜精品a片一区二区三区无码白浆 | 国产成人精品优优av | 色欲久久久天天天综合网精品 | 300部国产真实乱 | 日韩 欧美 动漫 国产 制服 | 亚洲国产欧美国产综合一区 | 亚洲成a人片在线观看无码3d | 久久99精品国产.久久久久 | 九月婷婷人人澡人人添人人爽 | 无码福利日韩神码福利片 | 久久99精品久久久久久动态图 | 国产精品美女久久久久av爽李琼 | 全黄性性激高免费视频 | 日本乱偷人妻中文字幕 | 激情综合激情五月俺也去 | 成人一区二区免费视频 | 色一情一乱一伦 | 97夜夜澡人人双人人人喊 | 国产精品欧美成人 | 成人无码视频免费播放 | 日日鲁鲁鲁夜夜爽爽狠狠 | 爆乳一区二区三区无码 | 无码国产激情在线观看 | 久久国产劲爆∧v内射 | 久久久久人妻一区精品色欧美 | 性史性农村dvd毛片 | 无码福利日韩神码福利片 | 亚洲小说春色综合另类 | 又色又爽又黄的美女裸体网站 | 欧美午夜特黄aaaaaa片 | 香蕉久久久久久av成人 | 18精品久久久无码午夜福利 | 亚洲热妇无码av在线播放 | 国产sm调教视频在线观看 | 天天摸天天透天天添 | 亚洲第一无码av无码专区 | 高潮毛片无遮挡高清免费视频 | 亚洲七七久久桃花影院 | 国产精品亚洲一区二区三区喷水 | 欧美丰满熟妇xxxx | 亚洲日韩中文字幕在线播放 | 亚洲一区二区三区无码久久 | 精品人妻人人做人人爽 | 自拍偷自拍亚洲精品被多人伦好爽 | 久久精品国产99久久6动漫 | 扒开双腿疯狂进出爽爽爽视频 | 精品人妻人人做人人爽夜夜爽 | 国产在线精品一区二区三区直播 | 亚洲人成网站在线播放942 | 亚洲精品www久久久 | 超碰97人人做人人爱少妇 | 午夜福利电影 | 国色天香社区在线视频 | 偷窥村妇洗澡毛毛多 | 中文字幕无码人妻少妇免费 | 国产精华av午夜在线观看 | 5858s亚洲色大成网站www | 未满成年国产在线观看 | 亚洲熟妇色xxxxx欧美老妇 | 亚洲s码欧洲m码国产av | 亚洲精品午夜国产va久久成人 | 日本饥渴人妻欲求不满 | 少妇被粗大的猛进出69影院 | 国产乱码精品一品二品 | 亚洲中文无码av永久不收费 | 日本肉体xxxx裸交 | 综合人妻久久一区二区精品 | 国产一精品一av一免费 | 无码一区二区三区在线观看 | 国产精品对白交换视频 | 曰本女人与公拘交酡免费视频 | 高清无码午夜福利视频 | 国产高潮视频在线观看 | 成熟妇人a片免费看网站 | 精品无码国产自产拍在线观看蜜 | 中文无码精品a∨在线观看不卡 | 人妻无码αv中文字幕久久琪琪布 | 精品少妇爆乳无码av无码专区 | 国产超碰人人爽人人做人人添 | 亚洲s码欧洲m码国产av | 国产精品久免费的黄网站 | 日韩精品无码一区二区中文字幕 | 青草青草久热国产精品 | 无套内谢的新婚少妇国语播放 | 99视频精品全部免费免费观看 | 精品国产麻豆免费人成网站 | 中文字幕 亚洲精品 第1页 | 日韩少妇白浆无码系列 | 国产va免费精品观看 | 国产熟妇高潮叫床视频播放 | 亚洲熟妇色xxxxx欧美老妇y | 国产va免费精品观看 | 国产精品亚洲lv粉色 | 网友自拍区视频精品 | 噜噜噜亚洲色成人网站 | 中文亚洲成a人片在线观看 | 久久成人a毛片免费观看网站 | 一个人看的www免费视频在线观看 | 亚洲国产成人av在线观看 | 四十如虎的丰满熟妇啪啪 | 色五月五月丁香亚洲综合网 | 中国女人内谢69xxxxxa片 | 人妻与老人中文字幕 | 亚洲精品一区二区三区婷婷月 | 国产激情无码一区二区 | 3d动漫精品啪啪一区二区中 | a在线亚洲男人的天堂 | 青青青手机频在线观看 | 九月婷婷人人澡人人添人人爽 | 麻花豆传媒剧国产免费mv在线 | 久久亚洲国产成人精品性色 | 国产黄在线观看免费观看不卡 | 午夜熟女插插xx免费视频 | 人人妻人人澡人人爽人人精品浪潮 | 人人妻人人澡人人爽欧美一区九九 | 国产女主播喷水视频在线观看 | 国产人妻精品一区二区三区 | 人人爽人人澡人人高潮 | 日本丰满护士爆乳xxxx | 任你躁在线精品免费 | 狂野欧美性猛交免费视频 | 国产欧美精品一区二区三区 | 在线播放免费人成毛片乱码 | 狠狠色丁香久久婷婷综合五月 | 亚洲综合无码一区二区三区 | 人妻少妇精品无码专区动漫 | 国产精品怡红院永久免费 | 亚欧洲精品在线视频免费观看 | 无码av免费一区二区三区试看 | 亚洲成av人影院在线观看 | 亚洲中文字幕无码一久久区 | 亚洲国产高清在线观看视频 | 麻豆国产97在线 | 欧洲 | 国产在线精品一区二区高清不卡 | 国产高潮视频在线观看 | 丰满少妇人妻久久久久久 | 国产 精品 自在自线 | 亲嘴扒胸摸屁股激烈网站 | 97无码免费人妻超级碰碰夜夜 | 亚洲色欲色欲天天天www | 欧美 日韩 亚洲 在线 | 亚洲日韩精品欧美一区二区 | 黄网在线观看免费网站 | 欧美阿v高清资源不卡在线播放 | 内射白嫩少妇超碰 | 18禁黄网站男男禁片免费观看 | 久久久久国色av免费观看性色 | 成人三级无码视频在线观看 | 午夜理论片yy44880影院 | 久久综合网欧美色妞网 | 欧洲精品码一区二区三区免费看 | 成人片黄网站色大片免费观看 | 爱做久久久久久 | 国产精品18久久久久久麻辣 | 亚洲va中文字幕无码久久不卡 | 亚洲成av人片在线观看无码不卡 | 欧美老妇交乱视频在线观看 | 东北女人啪啪对白 | 亚洲精品午夜无码电影网 | 国精产品一品二品国精品69xx | 久久久国产一区二区三区 | 日韩亚洲欧美精品综合 | 精品久久久无码人妻字幂 | 伊人久久大香线焦av综合影院 | 精品国产aⅴ无码一区二区 | 亚洲中文字幕在线无码一区二区 | 正在播放老肥熟妇露脸 | 久久精品视频在线看15 | 色综合天天综合狠狠爱 | 波多野结衣aⅴ在线 | 曰韩少妇内射免费播放 | 国产熟妇另类久久久久 | 亚洲熟妇色xxxxx欧美老妇y | 帮老师解开蕾丝奶罩吸乳网站 | 麻豆国产人妻欲求不满谁演的 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | 红桃av一区二区三区在线无码av | 成 人 免费观看网站 | 少妇性俱乐部纵欲狂欢电影 | 亚洲成熟女人毛毛耸耸多 | 久久综合色之久久综合 | 麻豆果冻传媒2021精品传媒一区下载 | 国产亚洲视频中文字幕97精品 | 妺妺窝人体色www在线小说 | 曰韩无码二三区中文字幕 | 男女作爱免费网站 | 久久视频在线观看精品 | 精品人人妻人人澡人人爽人人 | 日日躁夜夜躁狠狠躁 | 又湿又紧又大又爽a视频国产 | 日本精品久久久久中文字幕 | 男女爱爱好爽视频免费看 | 4hu四虎永久在线观看 | 又大又黄又粗又爽的免费视频 | 色综合视频一区二区三区 | 粗大的内捧猛烈进出视频 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | 熟女少妇在线视频播放 | 午夜精品久久久久久久久 | 久久国内精品自在自线 | 久久成人a毛片免费观看网站 | 日日麻批免费40分钟无码 | 超碰97人人射妻 | 亚洲一区二区三区偷拍女厕 | 久久精品人人做人人综合试看 | 久久精品女人天堂av免费观看 | 国产精品二区一区二区aⅴ污介绍 | 国产无套内射久久久国产 | 日日天干夜夜狠狠爱 | 国产片av国语在线观看 | 真人与拘做受免费视频 | 日韩精品成人一区二区三区 | 大肉大捧一进一出视频出来呀 | 精品国产麻豆免费人成网站 | 麻豆精产国品 | 无码人妻av免费一区二区三区 | 巨爆乳无码视频在线观看 | 领导边摸边吃奶边做爽在线观看 | 欧美高清在线精品一区 | 内射老妇bbwx0c0ck | 黑森林福利视频导航 | 国产精品久久久久9999小说 | 国产莉萝无码av在线播放 | 精品国偷自产在线视频 | 无遮无挡爽爽免费视频 | 久久国语露脸国产精品电影 | 亚洲第一网站男人都懂 | 波多野结衣av一区二区全免费观看 | 国产一区二区三区四区五区加勒比 | 国产极品美女高潮无套在线观看 | 亚洲精品中文字幕乱码 | 国内少妇偷人精品视频 | 亚洲色欲久久久综合网东京热 | 高清无码午夜福利视频 | 亚洲中文字幕av在天堂 | 人人妻人人澡人人爽人人精品 | 亚洲精品国产a久久久久久 | 亚洲а∨天堂久久精品2021 | 国产成人精品优优av | 狠狠色噜噜狠狠狠7777奇米 | 成熟人妻av无码专区 | 亚洲精品中文字幕久久久久 | 红桃av一区二区三区在线无码av | 中文字幕无线码 | 国产黑色丝袜在线播放 | 日本熟妇人妻xxxxx人hd | 国产精品福利视频导航 | 久久久国产精品无码免费专区 | 亚洲一区二区三区 | 啦啦啦www在线观看免费视频 | 中文亚洲成a人片在线观看 | 国产乡下妇女做爰 | 中文字幕无码乱人伦 | 六月丁香婷婷色狠狠久久 | 国产一区二区不卡老阿姨 | 国产福利视频一区二区 | 性色av无码免费一区二区三区 | 亚洲精品国偷拍自产在线麻豆 | 国产农村妇女aaaaa视频 撕开奶罩揉吮奶头视频 | 成人免费无码大片a毛片 | 正在播放东北夫妻内射 | 午夜成人1000部免费视频 | 女人被爽到呻吟gif动态图视看 | 十八禁视频网站在线观看 | 高潮喷水的毛片 | 亚洲综合在线一区二区三区 | 亚洲精品美女久久久久久久 | 88国产精品欧美一区二区三区 | 精品日本一区二区三区在线观看 | 亚洲午夜福利在线观看 | 未满小14洗澡无码视频网站 | 国产超级va在线观看视频 | 久久精品99久久香蕉国产色戒 | 亚洲中文字幕成人无码 | 亚洲 日韩 欧美 成人 在线观看 | 国产精品鲁鲁鲁 | 精品久久久久久人妻无码中文字幕 | 99久久精品国产一区二区蜜芽 | 国产激情无码一区二区app | 欧美兽交xxxx×视频 | 国产亚洲精品久久久久久国模美 | 丰满少妇女裸体bbw | 精品无人国产偷自产在线 | 久久无码中文字幕免费影院蜜桃 | 色偷偷人人澡人人爽人人模 | 丝袜 中出 制服 人妻 美腿 | 国产激情无码一区二区 | 中文字幕乱码亚洲无线三区 | 狂野欧美性猛交免费视频 | 亚洲成色在线综合网站 | 亚洲s码欧洲m码国产av | 婷婷丁香五月天综合东京热 | 精品偷拍一区二区三区在线看 | 亚洲中文字幕无码中字 | 亚洲 另类 在线 欧美 制服 | 亚洲精品综合一区二区三区在线 | 国产精品久久久久久亚洲影视内衣 | 日本大香伊一区二区三区 | 午夜肉伦伦影院 | 免费看少妇作爱视频 | 欧美一区二区三区 | 久久久久久久人妻无码中文字幕爆 | 亚洲精品综合一区二区三区在线 | 免费无码av一区二区 | 日本护士xxxxhd少妇 | 午夜理论片yy44880影院 | 国产精品丝袜黑色高跟鞋 | 55夜色66夜色国产精品视频 | 午夜福利一区二区三区在线观看 | 精品无码一区二区三区爱欲 | 波多野结衣乳巨码无在线观看 | 精品久久久中文字幕人妻 | 色五月五月丁香亚洲综合网 | 国产色在线 | 国产 | 国产精品久久久久久久9999 | 国产精品久久久久9999小说 | 免费无码av一区二区 | 亚洲日本一区二区三区在线 | 亚洲а∨天堂久久精品2021 | 日日摸日日碰夜夜爽av | 久久精品视频在线看15 | 欧美激情综合亚洲一二区 | 亚洲国产日韩a在线播放 | 亚洲一区二区三区在线观看网站 | 77777熟女视频在线观看 а天堂中文在线官网 | 激情人妻另类人妻伦 | 麻花豆传媒剧国产免费mv在线 | 亚洲日韩av片在线观看 | 精品乱码久久久久久久 | 国产精品久久久久久久影院 | 骚片av蜜桃精品一区 | 精品aⅴ一区二区三区 | 人妻熟女一区 | 九九久久精品国产免费看小说 | 成人毛片一区二区 | 精品国精品国产自在久国产87 | 成人性做爰aaa片免费看 | 亚洲成a人片在线观看日本 | 亚洲精品综合五月久久小说 | 夜夜夜高潮夜夜爽夜夜爰爰 | 樱花草在线播放免费中文 | 亚洲国产精品一区二区美利坚 | 亚洲精品午夜国产va久久成人 | 美女张开腿让人桶 | 377p欧洲日本亚洲大胆 | 亚洲 日韩 欧美 成人 在线观看 | 亚洲综合伊人久久大杳蕉 | 亚洲国产成人a精品不卡在线 | 亚洲国产av精品一区二区蜜芽 | 亚洲国产精品无码一区二区三区 | 久热国产vs视频在线观看 | 天堂一区人妻无码 | 久久久国产一区二区三区 | 久久zyz资源站无码中文动漫 | av无码不卡在线观看免费 | 少妇人妻偷人精品无码视频 | 亚洲国产午夜精品理论片 | 午夜丰满少妇性开放视频 | 狂野欧美激情性xxxx | 色婷婷av一区二区三区之红樱桃 | а天堂中文在线官网 | 中文字幕无码热在线视频 | 日日摸天天摸爽爽狠狠97 | 亚洲日韩乱码中文无码蜜桃臀网站 | 国产超级va在线观看视频 | 精品国产精品久久一区免费式 | 99re在线播放 | 国产精品igao视频网 | 鲁大师影院在线观看 | 男女超爽视频免费播放 | √天堂资源地址中文在线 | 日日天日日夜日日摸 | 久久久精品欧美一区二区免费 | 日韩av激情在线观看 | 伊人久久婷婷五月综合97色 | 999久久久国产精品消防器材 | 亚洲综合伊人久久大杳蕉 | 国产办公室秘书无码精品99 | 国产精品18久久久久久麻辣 | 亚洲性无码av中文字幕 | 高潮喷水的毛片 | 2020久久超碰国产精品最新 | 亚洲精品国偷拍自产在线观看蜜桃 | 亚洲精品一区国产 | 亚洲国产高清在线观看视频 | 亚洲色在线无码国产精品不卡 | 日本一卡2卡3卡4卡无卡免费网站 国产一区二区三区影院 | 亚洲乱码国产乱码精品精 | 美女毛片一区二区三区四区 | 又湿又紧又大又爽a视频国产 | 亚洲中文字幕乱码av波多ji | 国产疯狂伦交大片 | 玩弄人妻少妇500系列视频 | 亚洲国产高清在线观看视频 | 国产肉丝袜在线观看 | 狠狠色噜噜狠狠狠7777奇米 | 久久www免费人成人片 | 日韩欧美群交p片內射中文 | 国产网红无码精品视频 | 亚洲 a v无 码免 费 成 人 a v | 欧美亚洲国产一区二区三区 | 亚洲日本一区二区三区在线 | 精品乱码久久久久久久 | 熟妇激情内射com | 男人和女人高潮免费网站 | 久久综合给合久久狠狠狠97色 | 国产午夜亚洲精品不卡下载 | 人妻天天爽夜夜爽一区二区 | 国产精品鲁鲁鲁 | 丰满少妇熟乱xxxxx视频 | 国产真实乱对白精彩久久 | 女人被男人爽到呻吟的视频 | 久久精品中文闷骚内射 | aa片在线观看视频在线播放 | 久久久久av无码免费网 | 欧洲美熟女乱又伦 | 天堂一区人妻无码 | 国产成人亚洲综合无码 | 老司机亚洲精品影院 | 精品久久久久香蕉网 | 久久久久国色av免费观看性色 | 国产成人一区二区三区在线观看 | 国产无遮挡吃胸膜奶免费看 | 欧美成人免费全部网站 | 欧美一区二区三区 | 丰满人妻精品国产99aⅴ | 爽爽影院免费观看 | 亚洲 高清 成人 动漫 | 国产精品亚洲专区无码不卡 | 精品国产一区av天美传媒 | 中文字幕乱妇无码av在线 | 国产精品理论片在线观看 | 18黄暴禁片在线观看 | 久久综合色之久久综合 | 在线成人www免费观看视频 | 国产偷国产偷精品高清尤物 | 色婷婷av一区二区三区之红樱桃 | 欧美黑人乱大交 | 午夜丰满少妇性开放视频 | 欧美性生交活xxxxxdddd | 久久久久久av无码免费看大片 | 国产成人av免费观看 | 久久99精品久久久久久 | 激情五月综合色婷婷一区二区 | 一本色道久久综合亚洲精品不卡 | 伊在人天堂亚洲香蕉精品区 | 欧美熟妇另类久久久久久多毛 | 伊人久久婷婷五月综合97色 | 国产猛烈高潮尖叫视频免费 | 麻豆国产人妻欲求不满谁演的 | 四虎永久在线精品免费网址 | 欧美日韩一区二区免费视频 | 亚洲色大成网站www | 在线a亚洲视频播放在线观看 | 久久精品视频在线看15 | 亚洲精品成人福利网站 | 国产精品福利视频导航 | 成 人 免费观看网站 | 日韩欧美中文字幕在线三区 | 国产精品a成v人在线播放 | 妺妺窝人体色www在线小说 | 狂野欧美性猛xxxx乱大交 | 国产精品办公室沙发 | 亚洲狠狠色丁香婷婷综合 | 精品人妻人人做人人爽夜夜爽 | 精品国产青草久久久久福利 | 日韩av无码中文无码电影 | 窝窝午夜理论片影院 | 亚洲日本在线电影 | 日韩精品无码免费一区二区三区 | 久久无码人妻影院 | 亚洲精品无码人妻无码 | 免费无码一区二区三区蜜桃大 | 成人性做爰aaa片免费看不忠 | 久久成人a毛片免费观看网站 | 久9re热视频这里只有精品 | 欧美日韩久久久精品a片 | 亚洲国产精品一区二区美利坚 | 沈阳熟女露脸对白视频 | 亚洲国产精华液网站w | 成人无码影片精品久久久 | 精品国产麻豆免费人成网站 | 人人妻人人澡人人爽人人精品浪潮 | 日本丰满护士爆乳xxxx | 国产精品第一区揄拍无码 | 欧美人与物videos另类 | 成人性做爰aaa片免费看不忠 | 色综合久久久无码网中文 | 久久国内精品自在自线 | 美女毛片一区二区三区四区 | 中文字幕乱码中文乱码51精品 | 强辱丰满人妻hd中文字幕 | 日本丰满护士爆乳xxxx | 国内老熟妇对白xxxxhd | 久久亚洲a片com人成 | 国产特级毛片aaaaaa高潮流水 | 免费看少妇作爱视频 | 精品一二三区久久aaa片 | 亚洲一区二区三区国产精华液 | 奇米影视7777久久精品人人爽 | 欧美丰满熟妇xxxx | 日本又色又爽又黄的a片18禁 | 成人精品视频一区二区三区尤物 | 久久精品国产精品国产精品污 | 亚洲综合精品香蕉久久网 | 国产色在线 | 国产 | 无码av中文字幕免费放 | 亚洲а∨天堂久久精品2021 | 大胆欧美熟妇xx | 免费中文字幕日韩欧美 | 无码人妻精品一区二区三区不卡 | 久久综合香蕉国产蜜臀av | 76少妇精品导航 | 亚洲一区二区三区播放 | 亚洲日本va中文字幕 | 亚洲色大成网站www国产 | 欧美黑人性暴力猛交喷水 | 亚洲精品中文字幕乱码 | 熟妇人妻无码xxx视频 | 最近中文2019字幕第二页 | 国产一区二区三区影院 | 无码国产色欲xxxxx视频 | 狂野欧美性猛交免费视频 | 欧洲美熟女乱又伦 | 国产小呦泬泬99精品 | 亚洲中文字幕无码一久久区 | 国产精品久久久久9999小说 | 国产片av国语在线观看 | 中文无码伦av中文字幕 | 中文字幕日韩精品一区二区三区 | 久久久久久久久蜜桃 | 天堂亚洲免费视频 | 午夜福利一区二区三区在线观看 | 亚洲精品一区三区三区在线观看 | 国产一区二区三区四区五区加勒比 | 狠狠cao日日穞夜夜穞av | 一本色道久久综合亚洲精品不卡 | 亚洲人成影院在线观看 | 久久精品国产99精品亚洲 | v一区无码内射国产 | 久久精品国产99精品亚洲 | 中文字幕 亚洲精品 第1页 | 日本丰满熟妇videos | 日韩精品久久久肉伦网站 | 无套内射视频囯产 | 成人精品天堂一区二区三区 | 久久精品国产一区二区三区肥胖 | 99麻豆久久久国产精品免费 | 波多野结衣乳巨码无在线观看 | 99久久人妻精品免费二区 | 桃花色综合影院 | 久久国产自偷自偷免费一区调 | 性色欲网站人妻丰满中文久久不卡 | 亚洲国精产品一二二线 | 亚洲综合另类小说色区 | 无套内谢老熟女 | 国产精品爱久久久久久久 | а√天堂www在线天堂小说 | 国产疯狂伦交大片 | 日韩亚洲欧美精品综合 | av在线亚洲欧洲日产一区二区 | 亚洲 a v无 码免 费 成 人 a v | 中文字幕乱码亚洲无线三区 | 国产成人一区二区三区在线观看 | 亚洲 另类 在线 欧美 制服 | 免费观看黄网站 | 久久99久久99精品中文字幕 | 三上悠亚人妻中文字幕在线 | 精品亚洲成av人在线观看 | 色爱情人网站 | 精品久久久中文字幕人妻 | 少妇性俱乐部纵欲狂欢电影 | 娇妻被黑人粗大高潮白浆 | 国产舌乚八伦偷品w中 | 欧美高清在线精品一区 | 无码吃奶揉捏奶头高潮视频 | 无码中文字幕色专区 | 国产香蕉97碰碰久久人人 | 色一情一乱一伦一区二区三欧美 | 偷窥日本少妇撒尿chinese | 99久久精品日本一区二区免费 | 亚洲精品久久久久avwww潮水 | 三上悠亚人妻中文字幕在线 | 国产综合色产在线精品 | 一区二区三区乱码在线 | 欧洲 | 国产av久久久久精东av | 99精品无人区乱码1区2区3区 | 日本一区二区三区免费高清 | 小sao货水好多真紧h无码视频 | 日韩精品久久久肉伦网站 | 亚洲一区二区三区在线观看网站 | 国产婷婷色一区二区三区在线 | 噜噜噜亚洲色成人网站 | 成在人线av无码免费 | 久久这里只有精品视频9 | 色欲久久久天天天综合网精品 | 国产97色在线 | 免 | 成人无码精品一区二区三区 | 国产在线精品一区二区三区直播 | 狠狠色噜噜狠狠狠7777奇米 | 午夜性刺激在线视频免费 | 日韩精品a片一区二区三区妖精 | 内射后入在线观看一区 | 国产精品久久久久久无码 | 夜夜高潮次次欢爽av女 | 日韩av无码一区二区三区不卡 | 秋霞成人午夜鲁丝一区二区三区 | 精品偷拍一区二区三区在线看 | 性欧美熟妇videofreesex | 一本色道久久综合狠狠躁 | 国产香蕉97碰碰久久人人 | 18精品久久久无码午夜福利 | 婷婷五月综合缴情在线视频 | 日本护士xxxxhd少妇 | 国产av无码专区亚洲a∨毛片 | 国产精品久久久久久亚洲毛片 | 无码任你躁久久久久久久 | 国产女主播喷水视频在线观看 | 国产特级毛片aaaaaaa高清 | 青青草原综合久久大伊人精品 | 国产精品久久久久久久影院 | 亚洲国产精品美女久久久久 | 无码人妻少妇伦在线电影 | 少妇邻居内射在线 | 日韩少妇白浆无码系列 | 免费人成在线观看网站 | 国产精品亚洲一区二区三区喷水 | 国产av一区二区三区最新精品 | 天天做天天爱天天爽综合网 | 中文字幕无码人妻少妇免费 | 男女猛烈xx00免费视频试看 | 六十路熟妇乱子伦 | 大乳丰满人妻中文字幕日本 |