精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

編譯優化|LLVM代碼生成技術詳解及在數據庫中的應用

開發
隨著IT基礎設施的發展,現代的數據處理系統需要處理更多的數據、支持更為復雜的算法。數據量的增長和算法的復雜化,為數據分析系統帶來了嚴峻的性能挑戰。近年來,我們可以在數據庫、大數據系統和AI平臺等領域看到很多性能優化的技術,技術涵蓋體系結構、編譯技術和高性能計算等領域。作為編譯優化技術的代表,本文主要介紹基于LLVM的代碼生成技術(簡稱Codeden)。

 1. 前言

隨著IT基礎設施的發展,現代的數據處理系統需要處理更多的數據、支持更為復雜的算法。數據量的增長和算法的復雜化,為數據分析系統帶來了嚴峻的性能挑戰。近年來,我們可以在數據庫、大數據系統和AI平臺等領域看到很多性能優化的技術,技術涵蓋體系結構、編譯技術和高性能計算等領域。作為編譯優化技術的代表,本文主要介紹基于LLVM的代碼生成技術(簡稱Codeden)。

LLVM是一款非常流行的開源編譯器框架,支持多種語言和底層硬件。開發者可以基于LLVM搭建自己的編譯框架并進行二次開發,將不同的語言或者邏輯編譯成運行在多種硬件上的可執行文件。對于Codegen技術來說,我們主要關注LLVM IR的格式以及生成LLVM IR的API。在本文的如下部分,我們首先對LLVM IR進行介紹,然后介紹Codegen技術的原理和使用場景,最后我們介紹在阿里云自研的云原生數據倉庫產品AnalyticDB PostgreSQL中,Codegen的典型應用場景。

2. LLVM IR簡介及上手教程

在編譯器理論與實踐中,IR是非常重要的一環。IR的全稱叫做Intermediate Representation,翻譯過來叫“中間表示”。 對于一個編譯器來說,從上層抽象的高級語言到底層的匯編語言,要經歷很多個環節(pass),經歷不同的表現形式。而編譯優化技術有很多種,每種技術作用的編譯環節不同。但是IR是一個明顯的分水嶺。IR以上的編譯優化,不需要關心底層硬件的細節,比如硬件的指令集、寄存器文件大小等。IR以下的編譯優化,需要和硬件打交道。LLVM最為著名是它的IR的設計。得益于巧妙地IR設計,LLVM向上可以支持不同的語言,向下可以支持不同的硬件,而且不同的語言可以復用IR層的優化算法。

上圖展示了LLVM的一個框架圖。LLVM把整個編譯過程分為三步:(1)前端,把高級語言轉換為IR。(2)中端,在IR層做優化。(3) 后端,把IR轉化為對應的硬件平臺的匯編語言。因此LLVM的擴展性很好。比如你要實現一個名為toyc的語言、希望運行在ARM平臺上,你只需要實現一個toyc->LLVM IR的前端,其他部分調LLVM的模塊就可以了。或者你要搞一個新的硬件平臺,那么只需要搞定LLVM IR->新硬件這一階段,然后該硬件就可以支持很多種現存的語言。因此,IR是LLVM最有競爭力的地方,同時也是學習使用LLVM Codegen的最核心的地方。

2.1 LLVM IR基本知識

LLVM的IR格式非常像匯編,對于學習過匯編語言的同學來說,學會使用LLVM IR進行編程非常容易。對于沒學過匯編語言的同學,也不用擔心,匯編其實并不難。匯編難的不是學會,而是工程實現。因為匯編語言的開發難度,會隨著工程復雜度的提升呈指數級上升。接下來我們需要了解IR中最重要的三部分,指令格式、Basic Block & CFG,還有SSA。完整的LLVM IR信息請參考https://llvm.org/docs/LangRef.html。

指令格式。LLVM IR提供了一種類似于匯編語言的三地址碼式的指令格式。下面的代碼片段是一個非常簡單的用LLVM IR實現的函數,該函數的輸入是5個i32類型(int32)的整數,函數的功能是計算這5個數的和并返回。LLVM IR是支持一些基本的數據類型的,比如i8、i32、浮點數等。LLVM IR中得變量的命名是以 "%"開頭,默認%0是函數的第一個參數、%1是第二個參數,依次類推。機器生成的變量一般是以數字進行命名,如果是手寫的話,可以根據自己的喜好選擇合適的命名方法。LLVM IR的指令格式包括操作符、類型、輸入、返回值。例如 "%6 = add i32 %0, %1"的操作符號是"add"、類型是"i32"、輸入是"%0"和“%1”、返回值是"%6"??偟膩碚f,IR支持一些基本的指令,然后編譯器通過這些基本指令的來完成一些復雜的運算。例如,我們在C中寫一個形如“A * B + C”的表達式在LLVM IR中是通過一條乘法和一條加法指令來完成的,另外可能也包括一些類型轉換指令。

define i32 @ir_add(i32, i32, i32, i32, i32){ %6 = add i32 %0, %1 %7 = add i32 %6, %2 %8 = add i32 %7, %3 %9 = add i32 %8, %4 ret i32 %9}

Basic Block & CFG。了解了IR的指令格式以后,接下來我們需要了解兩個概念:Basic Block(基本塊,簡稱BB)和Control Flow Graph(控制流圖,CFG)。下圖(左)展示了一個簡單的C語言函數,下圖(中)是使用clang編譯出來的對應的LLVM IR,下圖(右)是使用graphviz畫出來的CFG。結合這張圖,我們解釋下Basic Block和CFG的概念。

在我們平時接觸到的高級語言中,每種語言都會有很多分支跳轉語句,比如C語言中有for, while, if等關鍵字,這些關鍵字都代表著分支跳轉。開發者通過分支跳轉來實現不同的邏輯運算。匯編語言通常通過有條件跳轉和無條件跳轉兩種跳轉指令來實現邏輯運算,LLVM IR同理。比如在LLVM IR中"br label %7"意味著無論如何都跳轉到名為%7的label那里,這是一條無條件跳轉指令。"br i1 %10, label %11, label %22"是有條件跳轉,意味著這如果%10是true則跳轉到名為%11的label,否則跳轉到名為%22的label。

在了解了跳轉指令這個概念后,我們介紹Basic Block的概念。一個Basic Block是指一段串行執行的指令流,除了最后一句之外不會有跳轉指令,Basic Block入口的第一條指令叫做“Leading instruction”。除了第一個Basic Block之外,每個Basic Block都會有一個名字(label)。第一個Basic Block也可以有,只是有時候沒必要。例如在這段代碼當中一共有5個Basic Block。Basic Block的概念,解決了控制邏輯的問題。通過Basic Block, 我們可以把代碼劃分成不同的代碼塊,在編譯優化中,有的優化是針對單個Basic Block的,有些是針對多個Basic Block的。

CFG(Control Flow Graph, 控制流圖)其實就是由Basic Block以及Basic Block之間的跳轉關系組成的一個圖。例如上圖所示的代碼,一共有5個Basic Block,箭頭列出了Basic Block之間的跳轉關系,共同組成了一個CFG。如果一個Basic Block只有一個箭頭指向別的Block,那么這個跳轉就是無條件跳轉,否則是有條件跳轉。CFG是編譯理論中一個比較簡單而且很基礎的概念,CFG更進一步是DFG(Data Flow Graph,數據流圖),很多進階的編譯優化算法都是基于DFG的。對于使用LLVM進行Codegen開發的同學,理解CFG的概念即可。

SSA。SSA的全稱是Static Single Assignment(靜態單賦值),這是編譯技術中非?;A的一個理念。SSA是學習LLVM IR必須熟悉的概念,同時也是最難理解的一個概念。細心的讀者在觀察上面列出的IR代碼時會發現,每個“變量”只會被賦值一次,這就是SSA的核心思想。因為從編譯器的角度來看,編譯器不關心“變量”,編譯器是以“數據”為中心進行設計的。每個“變量”的每次寫入,都生成了一個新的數據版本,編譯器的優化是圍繞數據版本展開的。接下來我們用如下的C語言代碼來解釋這一思想。

上圖(左)展示了一段簡單的C代碼,上圖(右)是這段代碼的SSA版本,也就是“編譯器眼中的代碼”。在C語言中,我們知道數據都是用變量來存儲的,因此數據操作的核心是變量,開發者需要關心變量的生存時間、何時被賦值、何時被使用。但是編譯器只關心數據的流向,因此每次賦值操作都會生成一個新的左值。例如左邊代碼只有一個a, 但是在右邊的代碼有4個變量,因為a里面的數據一共有4個版本。除了每次賦值操作會生成一個新的變量,最后的一個phi節點會生成一個新的變量。在SSA中,每個變量都代表數據的一個版本。也就是說,高級語言以變量為核心,而SSA格式以數據為核心。SSA中每次賦值操作都會生成一個版本的數據,因此在寫IR的時候,時刻牢記IR的變量和高層語言不同,一個IR的變量代表數據的一個版本。Phi節點是SSA中的一個重要概念。在這個例子當中,a_4的取值取決于之前執行了哪個分支,如果執行了第一個分支,那么a_4 = a_1, 依次類推。Phi節點通過判斷這段代碼是從哪個Basic Block跳轉而來,選擇合適的數據版本。LLVM IR自然也是需要開發者寫Phi節點的,在循環、條件分支跳轉的地方,往往需要手寫很多phi節點,這是寫LLVM IR時邏輯上比較難處理的地方。

2.2 學會使用LLVM IR寫程序

熟悉LLVM IR最好的辦法就是使用IR寫幾個程序。在開始寫之前,建議先花30分鐘-1個小時再粗略閱讀下官方手冊(https://llvm.org/docs/LangRef.html),熟悉下都有哪些指令的類型。接下來我們通過兩個簡單的case熟悉下LLVM IR編程的全部流程。

下面是一個循環加法的函數片段。這個函數一共包含三個Basic Block,loop、loop_body和final。其中loop是整個函數的開始,loop_body是函數的循環體,final是函數的結尾。在第5行和第6行,我們使用phi節點來實現結果和循環變量。

  1. define i32 @ir_loopadd_phi(i32*, i32){ br label %loop loop: %i = phi i32 [0,%2], [%newi,%loop_body] %res = phi i32[0,%2], [%new_res, %loop_body] %break_flag = icmp sge i32 %i, %1 br i1 %break_flag, label %final, label %loop_body loop_body: %addr = getelementptr inbounds i32, i32* %0, i32 %i %val = load i32, i32* %addr, align 4 %new_res = add i32 %res, %val %newi = add i32 %i, 1 br label %loopfinal: ret i32 %res;} 

下面是一個數組冒泡排序的函數片段。這個函數包含兩個循環體。LLVM IR實現循環本身就比較復雜,兩個循環嵌套會更加復雜。如果能夠用LLVM IR實現一個冒泡算法,基本上就理解了LLVM的整個邏輯了。

  1. define void @ir_bubble(i32*, i32) { %r_flag_addr = alloca i32, align 4 %j = alloca i32, align 4 %r_flag_ini = add i32 %1, -1 store i32 %r_flag_ini, i32* %r_flag_addr, align 4 br label %out_loop_headout_loop_head: ;check break store i32 0, i32* %j, align 4 %tmp_r_flag = load i32, i32* %r_flag_addr, align 4 %out_break_flag = icmp sle i32 %tmp_r_flag, 0 br i1 %out_break_flag, label %final, label %in_loop_head in_loop_head: ;check break %tmpj_1 = load i32, i32* %j, align 4 %in_break_flag = icmp sge i32 %tmpj_1, %tmp_r_flag br i1 %in_break_flag, label %out_loop_tail, label %in_loop_body in_loop_body: ;read & swap %tmpj_left = load i32, i32* %j, align 4 %tmpj_right = add i32 %tmpj_left, 1 %left_addr = getelementptr inbounds i32, i32* %0, i32 %tmpj_left %right_addr = getelementptr inbounds i32, i32* %0, i32 %tmpj_right %left_val = load i32, i32* %left_addr, align 4 %right_val = load i32, i32* %right_addr, align 4 ;swap check %swap_flag = icmp sge i32 %left_val, %right_val %left_res = select i1 %swap_flag, i32 %right_val, i32 %left_val %right_res = select i1 %swap_flag, i32 %left_val, i32 %right_val store i32 %left_res, i32* %left_addr, align 4 store i32 %right_res, i32* %right_addr, align 4 br label %in_loop_end in_loop_end: ;update j %tmpj_2 = load i32, i32* %j, align 4 %newj = add i32 %tmpj_2, 1 store i32 %newj, i32* %j, align 4 br label %in_loop_headout_loop_tail: ;update r_flag %tmp_r_flag_1 = load i32, i32* %r_flag_addr, align 4 %new_r_flag = sub i32 %tmp_r_flag_1, 1 store i32 %new_r_flag, i32* %r_flag_addr, align 4 br label %out_loop_headfinal: ret void

我們把如上的LLVM IR用clang編譯器編譯成object文件,然后和C語言寫的程序鏈接到一起,即可正常調用。在上面提到的case中,我們只使用了i32、i64等基本數據類型,LLVM IR中支持struct等高級數據類型,可以實現更為復雜的功能。

2.3 使用LLVM API實現Codegen

編譯器本質上就是調用各種各樣的API,根據輸入去生成對應的代碼,LLVM Codegen也不例外。在LLVM內部,一個函數是一個class,一個Basic Block試一個class, 一條指令、一個變量都是一個class。用LLVM API實現codegen就是根據需求,用LLVM內部的數據結構去實現相應的IR。

  1. Value *constant = Builder.getInt32(16); Value *Arg1 = fooFunc->arg_begin(); Value *val = createArith(Builder, Arg1, constant); Value *val2 = Builder.getInt32(100); Value *Compare = Builder.CreateICmpULT(val, val2, "cmptmp"); Value *Condition = Builder.CreateICmpNE(Compare, Builder.getInt1(0), "ifcond"); ValList VL; VL.push_back(Condition); VL.push_back(Arg1); BasicBlock *ThenBB = createBB(fooFunc, "then"); BasicBlock *ElseBB = createBB(fooFunc, "else"); BasicBlock *MergeBB = createBB(fooFunc, "ifcont"); BBList List; List.push_back(ThenBB); List.push_back(ElseBB); List.push_back(MergeBB); Value *v = createIfElse(Builder, List, VL); 

如上是一個用LLVM API實現codegen的例子。其實這就是個用C++寫IR的過程,如果知道如何寫IR的話,只需要熟悉下這套API就可以了。這套API提供了一些基本的數據結構,比如指令、函數、基本塊、llvm builder等,然后我們只需要調用相應的函數去生成這些對象即可。一般來說,首先我們先生成函數的原型,包括函數名字、參數列表、返回類型等。然后我們在根據函數的功能,確定都需要有哪些Basic Block以及Basic Block之間的跳轉關系,然后生成相應的Basic。最后我們再按照一定的順序去給每個Basic Block填充指令。邏輯是,這個流程和用LLVM IR寫代碼是相仿的。

3. Codegen技術分析

如果我們用上文所描述的方法,生成一些簡單的函數,并且用C寫出對應的版本進行性能對比,我們就會發現,LLVM IR的性能并不會比C快。一方面,計算機底層執行的是匯編,C語言本身和匯編是非常接近的,了解底層的程序員往往能夠從C代碼中推測出大概會生成什么樣的匯編。另一方面,現代編譯器往往做了很多優化,一些大大減輕了程序員的優化負擔。因此,使用LLVM IR進行Codegen并不會獲得比手寫C更好的性能,而且使用LLVM Codegen有一些明顯的缺點。想要真正用好LLVM,我們還需要熟悉LLVM的特點。

3.1 缺點分析

缺點1:開發難。實際開發中幾乎不會有工程使用匯編作為主要開發語言,因為開發難度太大了,有興趣的小伙伴可以試著寫個快排感受一下。即使是數據庫、操作系統這樣的基礎軟件,往往也只是在少數的地方會用到匯編。使用LLVM IR開發會有類似的問題。比如上文展示的最復雜例子是冒泡算法。開發者用C寫個冒泡只需要幾分鐘,但是用LLVM IR寫個冒泡可能要一個小時。另外,LLVM IR很難處理復雜的數據結構,比如結構體、類。除了LLVM IR中的那些基本數據結構外,新增一個復雜的數據結構非常難。因此在實際的開發當中,采用Codegen會導致開發難度指數級上升。

缺點2:調試難。開發者通常通過單步跟蹤的方式去調試代碼,但是LLVM IR是不支持的。一旦代碼出問題,只能是人肉一遍一遍看LLVM IR。如果懂匯編的話,可以通過單步跟蹤生成的匯編進行調試,但是匯編語言和IR之間并不是簡單的映射關系,因此只能一定程度上降低調試難度,并不完全解決調試的問題。

缺點3: 運行成本。生成LLVM IR往往很快,但是生成的IR需要調用LLVM 中的工具進行優化、以及編譯成二進制文件,這個過程是需要時間的(請聯想一下GCC編譯的速度)。在數據庫的開發過程中,我們的經驗值是每個函數大約需要10ms-100ms的codegen成本。大部分的時間花在了優化IR和IR到匯編這兩步。

3.2 適用場景

了解了LLVM Codegen的缺點,我們才能去分析其優點、選擇合適場景。下面這部分是團隊在開發過程中總結的適合使用LLVM Codegen的場景。

場景1:Java/python等語言。上文中提到過LLVM IR并不會比C快,但是會比Java/python等語言快啊。例如在Java中,有時候為了提升性能,會通過JNI調用一些C的函數提升性能。同理,Java也可以調用LLVM IR生成的函數提升性能。

場景2:硬件和語言不兼容。LLVM支持多種后端,比如X86、ARM和GPU。對于一些硬件與語言不兼容的場景,可以利用LLVM實現兼容。例如如果我們的系統是用Java語言開發、想要調用GPU,可以考慮用LLVM IR生成GPU代碼,然后通過JNI的方法進行調用。這套方案不僅支持NVIDIA的GPU,也支持AMD的GPU,而且對應生成的IR也可以在CPU上執行。

場景3:邏輯簡化。以數據庫為例,數據庫執行引擎在執行過程中需要做大量的數據類型、算法邏輯相關的判斷。這主要是由于SQL中的數據類型和邏輯,很多是在數據庫開發時無法確定的,只能在運行時決定。這一部分過程,也被稱為“解釋執行”。我們可以利用LLVM在運行時生成代碼,由于這個時候數據類型和邏輯已經確定,我們可以在LLVM IR中刪除那些不必要的判斷操作,從而實現性能的提升。

4. LLVM在數據庫中的應用

在數據庫當中,團隊是用LLVM來進行表達式的處理,接下來我們以PostgreSQL數據庫和云原生數據倉庫AnalyticDB PostgreSQL為對比,解釋LLVM的應用方法。

PostgreSQL為了實現表達式的解釋執行,采用了一套“拼函數”的方案。PostgreSQL中實現了大量C函數,比如加減法、大小比較等,不同類型的都有。SQL在生成執行計劃階段會根據表達式符號的類型和數據類型選擇相應的函數、把指針存下來,等執行的時候再調用。因此對于 "a > 10 and b < 5"這樣的過濾條件,假設a和b都是int32,PostgreSQL實際上調用了“Int8AndOp(Int32GT(a, 10), Int32LT(b, 5))”這樣一個函數組合,就像搭積木一樣。這樣的方案有兩個明顯的性能問題。一方面這種方案會帶來比較多次數的函數調用,函數調用本身是有成本的。另一方面,這種方案必須要實現一個統一的函數接口,函數內部和外部都需要做一些類型轉換,這也是額外的性能開銷。Odyssey使用LLVM 進行codegen,可以實現最小化的代碼。因為在SQL下發以后,數據庫是知道表達式的符號和輸入數據的類型的,因此只需要根據需求選取相應的IR指令就可以了。因此只需要三條IR指令,就可以實現這個表達式,然后我們把表達式封裝成一個函數,就可以在執行的時候調用了。這次操作,把多次函數調用簡化成了一次函數調用,大大減少了指令的總數量。

// 樣例SQLselect count(*) from table where a > 10 and b < 5;// PostgreSQL解釋執行方案:多次函數調用result = Int8AndOp(Int32GT(a, 10), Int32LT(b, 5));// AnalyticDB PostgreSQL方案:使用LLVM codegen生成最小化底層代碼%res1 = icmp ugt i32 %a, 10;%res2 = icmp ult i32 %b, 5; %res = and i8 %res1, %res2;
在數據庫中,表達式主要出現在幾個場景。一類是過濾條件,通常出現在where條件中。一類是輸出列表,一般跟在select之后。有些算子,比如join、agg等,它的判斷條件中也可能會出現一些比較復雜的表達式。因此表達式的處理是會出現在數據庫執行引擎的各個模塊的。在AnalyticDB PostgreSQL版中,開發團隊抽象出了一個表達式處理框架,通過LLVM Codegen來處理這些表達式,從而提高了執行引擎的整體性能。

5. 總結

LLVM作為一個流行的開源編譯框架,近年來被用于數據庫、AI等系統的性能加速。由于編譯器理論本身門檻較高,因此LLVM的學習有一定的難度。而且從工程上,還需要對LLVM的工程特點和性能特征有比較準確的理解,才能找到合適的加速場景。阿里云數據庫團隊的云原生數據倉庫產品AnalyticDB PostgreSQL版基于LLVM實現了一套運行時的表達式處理框架,能夠有效地提高系統在進行復雜數據分析時地性能。

責任編輯:梁菲 來源: 阿里云云棲號
相關推薦

2021-06-25 15:46:02

代碼數據庫技術

2009-10-27 16:36:07

Oracle如何解鎖

2024-11-13 15:15:46

2009-07-22 11:45:43

2023-03-03 08:00:00

重采樣數據集

2011-05-19 10:29:40

數據庫查詢

2011-04-12 13:44:17

CachéOracle數據庫

2014-06-10 15:07:19

Oracle數據庫優化

2011-03-04 10:03:45

EJB數據庫應用

2018-05-17 23:07:12

2010-10-09 10:29:29

MySQL外鍵

2011-05-18 09:39:19

Oracle數據庫性能優化

2011-04-02 14:50:58

數據庫代碼

2009-03-19 08:56:58

pureXMLDB2數據結構

2011-05-17 15:02:15

ORACLE數據庫備份

2023-03-07 16:21:26

2010-04-09 16:51:24

Oracle數據庫

2011-08-17 17:29:32

Windows編譯MySQL

2024-02-04 09:41:51

人工智能

2024-09-29 08:40:34

點贊
收藏

51CTO技術棧公眾號

国产全是老熟女太爽了| 蜜臀精品一区二区三区在线观看| 激情综合色综合久久| 日韩美女视频一区二区在线观看| 日韩av男人的天堂| 成人久久久精品乱码一区二区三区| 色狠狠综合天天综合综合| 亚洲一区二区三区sesese| 国产一区二区三区四区五区六区 | 国内成人免费视频| 欧美激情综合色综合啪啪五月| 激情五月亚洲色图| 五月色婷婷综合| 欧美精品99| 9191成人精品久久| 亚洲国产一区二区三区在线| 性无码专区无码| 国产伦理久久久久久妇女| 亚洲精品免费播放| 91精品国产综合久久男男| 国产ts在线播放| 国产高清亚洲| 亚洲视频 欧洲视频| 国产色综合天天综合网| 久久精品无码人妻| 精品无码人妻一区二区三区| 国产日韩久久久| 欧美网色网址| 五月婷婷激情综合| 国产欧美一区二区三区四区| 免费观看一级视频| 希岛爱理一区二区三区| 制服丝袜av成人在线看| 精品人妻一区二区三区四区在线 | 欧美在线一区二区三区四区| 黄色在线观看国产| 午夜精品影院| 欧美精品一区二区三区高清aⅴ| 麻豆传媒网站在线观看| 亚洲第一色视频| 在线日韩中文| 亚洲欧美激情四射在线日| 亚洲精品怡红院| 日本不卡不卡| 久久社区一区| 日韩一区二区三区四区| 久久这里只有精品8| 特级黄色录像片| 黑人操日本美女| 亚洲精品一区二区口爆| 中文字幕资源网在线观看免费| 国产精品一区二区在线看| 欧美成aaa人片免费看| 亚洲精品鲁一鲁一区二区三区 | 伊人免费在线观看高清版| 久久密一区二区三区| 亚洲欧美日韩成人| 中文字幕av网址| 欧美黄色a视频| 亚洲一区日韩精品中文字幕| 麻豆成人在线播放| 91丨porny丨在线中文 | 日韩精品在线视频免费观看| 日韩在线观看视频一区| 日韩电影免费在线观看网站| 久久香蕉国产线看观看av| 亚洲成人福利视频| 亚洲国产精品免费视频| 狠狠88综合久久久久综合网| 国产有码在线一区二区视频| 亚洲无人区一区| 奇米在线7777在线精品| 日韩av一区二区在线播放| 亚洲国产第一区| 成av人片在线观看www| 国产日韩欧美a| 91青青草免费观看| 久久精品视频5| 91精品推荐| 久久综合久久美利坚合众国| 青娱乐国产在线视频| 国产永久精品大片wwwapp| 日韩亚洲欧美高清| 国产人成视频在线观看| 色999韩欧美国产综合俺来也| 亚洲福利视频一区| 久久免费视频2| 国产精品二线| 97se亚洲国产综合自在线不卡| 成人免费直播live| 人妻av无码专区| 潘金莲激情呻吟欲求不满视频| 黄色在线免费看| 久久亚洲一区二区三区明星换脸| 亚洲一区二区三区乱码aⅴ蜜桃女 亚洲一区二区三区乱码aⅴ | 99热在线观看免费精品| 性欧美69xoxoxoxo| 欧美激情综合亚洲一二区| 国产成人在线播放视频| 中文字幕一区二区三区在线视频| 色哟哟亚洲精品一区二区| 一区二区视频观看| 韩国精品福利一区二区三区| 亚洲免费小视频| 小嫩苞一区二区三区| 亚洲精品偷拍| 成人性生交大片免费看视频直播| 日韩一区二区三区不卡| 国产精品天美传媒| 日本最新一区二区三区视频观看| 免费国产精品视频| 国产日韩av一区二区| 69精品丰满人妻无码视频a片| 麻豆电影在线播放| 在线免费av导航| 日韩精品国产精品| 2022国产精品| 国产特黄在线| 午夜精品久久久久久不卡8050| 亚洲无吗一区二区三区| 激情小说亚洲色图| 精品福利一区二区三区免费视频| 国产黄色大片免费看| 蜜桃精品噜噜噜成人av| 亚洲欧美日韩网| 久久久久久久久久久网| 开心九九激情九九欧美日韩精美视频电影| 国产精品久久久av久久久| 精品无码一区二区三区的天堂| 久久婷婷一区| 国产精品免费一区豆花| 91中文字幕在线播放| 久久亚洲春色中文字幕久久久| 成人在线免费高清视频| 粉嫩91精品久久久久久久99蜜桃| 精品污污网站免费看| 女同激情久久av久久| 女厕嘘嘘一区二区在线播放| 97国产精品免费视频| 亚洲GV成人无码久久精品| 国产成人av一区二区三区在线| 在线一区二区视频| 亚洲一区二区福利| 免费黄色片网站| 99热免费精品在线观看| 国产成人精品国内自产拍免费看| 最近中文字幕在线视频| 久久这里只有精品6| 欧美在线一区视频| 成人网ww555视频免费看| 欧美巨大另类极品videosbest| 潘金莲一级淫片aaaaa| 日韩理论在线| 欧美精品第一页在线播放| 国产乱色精品成人免费视频 | 最新中文字幕日本| 午夜精品影院| 国产精品视频一区二区三区经| 欧美成人免费| 国产精品久久久久国产精品日日| 男人的天堂视频在线| av日韩久久| 日韩精品黄色网| 黄色录像一级片| 精品一区二区三区在线播放视频| 国产一区二区高清视频| 午夜不卡视频| 国产精品免费不| 亚洲无线码一区二区三区| avtt中文字幕| 亚洲精品一二| 欧美三级网色| 影音先锋在线视频| 精品日产卡一卡二卡麻豆| 免费看黄色的视频| 日韩在线观看一区二区| 一区不卡字幕| 自拍视频在线看| 国产亚洲精品91在线| 亚洲天堂视频在线| 91美女精品福利| www.18av.com| 欧美激情极品| 国产精品一区久久久| 婷婷国产在线| 久久精品国产亚洲5555| 亚洲精品毛片| 91精品麻豆日日躁夜夜躁| 超碰手机在线观看| 欧美日韩亚洲一区二区三区在线| 国产精品一区二区av| 日韩av超清在线观看| 欧美成人在线直播| 久热这里只有精品6| 欧美国产视频在线| 男人天堂999| 911亚洲精选| 亚洲www啪成人一区二区| 中文字幕亚洲无线码a| 婷婷激情五月网| 国产精品理论在线观看| 色七七在线观看| 性插视频在线观看| 精品国产99久久久久久宅男i| 99re久久精品国产| 久久高清免费视频| 国产精品成人aaaa在线| 国产第一页在线| 久久久精品国产免费观看同学| 99re8这里只有精品| 亚洲女娇小黑人粗硬| 国模吧一区二区三区| 亚洲av综合色区无码一二三区| 欧美日韩中文字幕在线| 懂色av粉嫩av蜜乳av| 狠狠色丁香久久婷婷综| 激情综合网婷婷| 视频一区中文| 国产精品麻豆免费版| 亚洲精品第一| 国产精品白嫩初高中害羞小美女 | 超碰91人人草人人干| 亚洲精品www久久久久久| 1000精品久久久久久久久| 男女视频一区二区三区| 亚洲性视频h| 精品国产区在线| 男人av在线播放| 日韩电影在线观看永久视频免费网站| 亚洲在线观看av| 日本丶国产丶欧美色综合| 日本少妇性生活| 夜夜嗨av一区二区三区中文字幕| 动漫av在线免费观看| 精品一区免费av| 在线免费观看视频黄| 最新黄网在线观看| 国产精品久久久久久久久动漫 | 97成人超碰视| 日批在线观看视频| 亚洲日本成人| 久久人人爽人人爽人人av| 午夜精品电影| 久久手机在线视频| 欧美黑人xx片| 国产精品欧美一区二区三区| ass精品国模裸体欣赏pics| zzijzzij亚洲日本少妇熟睡| 国产成人无码一二三区视频| 亚洲高清不卡| 亚洲中文字幕无码专区| 大胆日韩av| 国产成人免费观看| 日韩欧美不卡视频| 成人黄色网址在线观看| 精品一区二区中文字幕| 99亚洲视频| 国产在线青青草| 九九热在线免费观看| 国产精品18久久久久久久久| 日韩av高清在线看片| 亚洲国产激情| 日韩精品―中文字幕| 久久久蜜桃一区二区人| 亚洲小视频在线播放| 91精品婷婷色在线观看| 韩国无码av片在线观看网站| 亚洲高清二区| 国产v亚洲v天堂无码久久久| 青青青伊人色综合久久| 在线播放黄色av| 日韩国产成人精品| 一级黄色特级片| 在线一区欧美| 日本成人在线免费视频| 黄色精品网站| 奇米精品一区二区三区| 久久夜色精品| 中文字幕一区久久| 日韩国产欧美在线视频| 视频二区在线播放| 国产福利精品导航| 国产老熟女伦老熟妇露脸| 国产婷婷色一区二区三区| 岛国片在线免费观看| 久久亚洲二区三区| 久久精品亚洲a| 亚洲大型综合色站| 波多野结衣mp4| 日韩亚洲欧美一区| 精品亚洲综合| 日韩精品在线观看网站| 丰满人妻一区二区三区免费视频| 精品视频1区2区3区| 欧美国产日本高清在线| 国产99久久九九精品无码免费| 欧美大胆一级视频| 搞黄视频免费在线观看| 欧美高清视频免费观看| 蜜桃精品在线| 粉嫩av一区二区三区免费观看 | 欧美一区2区三区4区公司二百| 国产精品不卡| 伊人狠狠色丁香综合尤物| 国产剧情一区| 狠狠噜天天噜日日噜| 免费日本视频一区| 午夜精品一区二区三区视频免费看 | 亚洲欧美色图片| 麻豆传媒视频在线观看免费| 91精品91久久久久久| 久久精品女同亚洲女同13| 丰满熟女一区二区三区| 在线观看视频一区二区| 国产高清免费在线观看| 在线日韩精品视频| 国产精品久久久久久久久毛片| 国产成人午夜电影网| 在线播放国产一区二区三区| 神宫寺奈绪一区二区三区| 7777女厕盗摄久久久| 免费一级在线观看播放网址| 欧美黑人狂野猛交老妇| v8888av| 91亚洲欧美| 国产激情在线| 欧美在线色视频| 亚洲第一免费视频| 日韩在线视频国产| 日本精品不卡| 国产精品99久久99久久久二8| 亚洲精品18| 欧美少妇在线观看| 精品在线播放免费| 无罩大乳的熟妇正在播放| 国产男人搡女人免费视频| 免费一级欧美在线大片| 欧美在线观看一区| 国产精品无码粉嫩小泬| 日韩乱码在线视频| 超黄网站在线观看| 亚洲最大福利视频网站| 66视频精品| 性chinese极品按摩| 国产女人18毛片水真多成人如厕 | 亚洲精品国产精| 99久久视频| 亚洲综合日韩中文字幕v在线| 日韩大片在线| www.99在线| 久久久精品一品道一区| 国产精品xxxx喷水欧美| 精品欧美一区二区在线观看| 性欧美猛交videos| 久热99视频在线观看| 在线观看一二三区| 国产精品久久久久久久| 成年人午夜免费视频| 成人精品一区二区三区中文字幕| 九九久久免费视频| 精品国产一区二区亚洲人成毛片| 国产探花在线观看| 国新精品乱码一区二区三区18| 亚洲日韩视频| a级大片在线观看| 欧美亚洲国产bt| 很黄的网站在线观看| 99视频免费观看| 亚洲三级网址| 九一精品在线观看| 国产精品成人在线观看| 国产成人精品a视频| 韩国精品久久久999| 亚洲精品456| 天天干天天操天天做| 悠悠色在线精品| 日韩中文字幕综合| 国产精品av免费在线观看| 久久精品国内一区二区三区水蜜桃| 爱情岛论坛亚洲自拍| 国产精品网曝门| 国产99久久九九精品无码免费| 69久久夜色精品国产69| 欧美好骚综合网| 欧产日产国产精品98| 欧美综合一区二区| 黄页在线观看免费| 日韩免费中文专区| 成人综合在线观看| japanese国产在线观看| 欧美国产在线视频| 禁果av一区二区三区| 日本wwwwwww| 欧美丝袜自拍制服另类| 国产高清在线a视频大全| 亚洲激情一区二区| thepron国产精品| 亚洲最大成人在线视频| 欧美一级电影免费在线观看| 国产厕拍一区|