国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

編譯器開發語言選擇:Rust 還是 OCaml?

譯文 精選
開發 前端
這篇文章詳細比較了 Rust 和 OCaml 在編譯器開發中的優勢和劣勢。作者通過對兩種語言的特性、性能、生態系統等方面的深入分析,為讀者提供了一個全面的視角來理解這兩種語言在編譯器開發中的應用。

譯者 | 劉汪洋

審校 | 重樓

關于如何選擇最合適的編程語言來開發編譯器,這個話題在編程語言愛好者中經常引起熱議。具體可參考以下討論:鏈接 1鏈接 2 鏈接 3。遺憾的是,許多人的回答要么局限于自己偏愛的語言,沒有提供合理解釋,要么給出模糊的解釋卻缺乏具體的例證。這兩種回答對提問者來說幾乎沒有任何幫助。在本文中,我將嘗試通過比較兩種語言——Rust 和 OCaml,來對這個話題提供更詳細的闡述。

CPS 轉換

在闡述我的具體觀點之前,我會展示一個非常簡單的語言的 CPS(延續傳遞風格)轉換的兩個相似實現,并不做任何結論性陳述。這一通用方法源于 Andrew W. Appel 的“Compiling with Continuations”。即使你對這個概念不太了解,也不必擔心;你只需要關注 Rust 和 OCaml 中是如何具體實現這個理念的。

以下是用 Rust 編寫的 CPS 轉換:

use std::cell::RefCell;
use std::ops::Deref;
use std::rc::Rc;

// lambda 語言的變量標識符 `Term`。
type Var = String;

// lambda語言;直接風格。
type Term = Rc<TermTree>;

enum TermTree {
    Var(Var),
    Fix(Vec<(Var, Vec<Var>, Term)>, Term),
    Appl(Term, Vec<Term>),
    Record(Vec<Term>),
    Select(Term, u32),
}

use TermTree::*;

#[derive(Clone)]
enum CpsVar {
    // 在 CPS 轉換過程中從 lambda 項中獲取。
    CLamVar(Var),
    // 在 CPS 轉換過程中唯一生成。
    CGenVar(u32),
}

use CpsVar::*;

// 結果的 CPS 項。
enum CpsTerm {
    CFix(Vec<(CpsVar, Vec<CpsVar>, CpsTerm)>, Box<CpsTerm>),
    CAppl(CpsVar, Vec<CpsVar>),
    CRecord(Vec<CpsVar>, Binder),
    CSelect(CpsVar, u32, Binder),
    CHalt(CpsVar),
}

use CpsTerm::*;

// 在 `CpsTerm` 內部綁定唯一的 `CpsVar`。
type Binder = (CpsVar, Box<CpsTerm>);

// 根據當前的`i` 生成一個唯一的 CPS 變量。
fn gensym(i: RefCell<u32>) -> CpsVar {
    let x = CGenVar(i.clone().into_inner());
    i.replace_with(|&mut i| i + 1);
    x
}

// 將`Term`轉換為`CpsTerm`,并將`finish`應用于結果的CPS變量。
fn convert(gen: RefCell<u32>, finish: impl FnOnce(CpsVar) -> CpsTerm, term: Term) -> CpsTerm {
    match term.deref() {
        Var(x) => finish(CLamVar(x.to_string())),
        Fix(defs, m) => CFix(
            defs.iter()
            .map(|def| convert_def(gen.clone(), def.clone()))
            .collect(),
            Box::new(convert(gen, finish, m.clone())),
        ),
        Appl(f, args) => {
            let ret_k = gensym(gen.clone());
            let ret_k_x = gensym(gen.clone());
            CFix(
                vec![(ret_k.clone(), vec![ret_k_x.clone()], finish(ret_k_x))],
                Box::new(convert(
                    gen.clone(),
                    |f_cps| {
                        convert_list(
                            gen,
                            |args_cps| {
                                CAppl(f_cps, args_cps.into_iter().chain(vec![ret_k]).collect())
                            },
                            args,
                        )
                    },
                    f.clone(),
                )),
            )
        }
        Record(fields) => convert_list(
            gen.clone(),
            |fields_cps| {
                let x = gensym(gen);
                CRecord(fields_cps, (x.clone(), Box::new(finish(x))))
            },
            fields,
        ),
        Select(m, i) => convert(
            gen.clone(),
            |m_cps| {
                let x = gensym(gen);
                CSelect(m_cps, *i, (x.clone(), Box::new(finish(x))))
            },
            m.clone(),
        ),
    }
}

// 將`Vec<Term>`轉換為`Vec<CpsVar>`并 調用`finish`
fn convert_list(
    gen: RefCell<u32>,
    finish: impl FnOnce(Vec<CpsVar>) -> CpsTerm,
    terms: &[Term],
) -> CpsTerm {
    fn go(
        gen: RefCell<u32>,
        finish: impl FnOnce(Vec<CpsVar>) -> CpsTerm,
        mut acc: Vec<CpsVar>,
        terms: &[Term],
    ) -> CpsTerm {
        match terms.split_first() {
            None => finish(acc),
            Some((x, xs)) => convert(
                gen.clone(),
                |x_cps| {
                    acc.push(x_cps);
                    go(gen, finish, acc, xs)
                },
                x.clone(),
            ),
        }
    }
    let acc = Vec::with_capacity(terms.len());
    go(gen, finish, acc, terms)
}


// 將單個函數定義轉換為其 CPS 形式。
fn convert_def(
    gen: RefCell<u32>,
    (f, params, m): (Var, Vec<Var>, Term),
) -> (CpsVar, Vec<CpsVar>, CpsTerm) {
    let k = gensym(gen.clone());
    (
        CLamVar(f),
        params
            .into_iter()
            .map(CLamVar)
            .chain(std::iter::once(k.clone()))
            .collect(),
        convert(gen, |m_cps| CAppl(k, vec![m_cps]), m),
    )
}

代碼包括注釋和空行共 145 行。

同樣的算法用地道的 OCaml 編寫:

(* lambda語言中的變量標識符[term]。 *)
type var = string

(* lambda語言;直接風格。 *)
type term =
  | Var of var
  | Fix of (var * var list * term) list * term
  | Appl of term * term list
  | Record of term list
  | Select of term * int

type cps_var =
  (* 在CPS轉換過程中從lambda項中提取。 *)
  | CLamVar of var
  (* 在CPS轉換過程中獨特生成。 *)
  | CGenVar of int

(* 生成的CPS項。 *)
type cps_term =
  | CFix of (cps_var * cps_var list * cps_term) list * cps_term
  | CAppl of cps_var * cps_var list
  | CRecord of cps_var list * binder
  | CSelect of cps_var * int * binder
  | CHalt of cps_var

(* 在[cps_term]內部綁定唯一的[cps_var]。 *)
and binder = cps_var * cps_term

(* 根據當前的[i]生成唯一的CPS變量。 *)
let gensym i =
  let x = CGenVar !i in
  i := !i + 1;
  x

(* 將[term]轉換為[cps_term],并將[finish]應用于生成的CPS變量。 *)
let rec convert gen finish = function
  | Var x -> finish (CLamVar x)
  | Fix (defs, m) -> CFix (List.map (convert_def gen) defs, convert gen finish m)
  | Appl (f, args) ->
      let ret_k = gensym gen in
      let ret_k_x = gensym gen in
      CFix
        ( [ (ret_k, [ ret_k_x ], finish ret_k_x) ],
          f
          |> convert gen (fun f_cps ->
                 args
                 |> convert_list gen (fun args_cps ->
                        CAppl (f_cps, args_cps @ [ ret_k ]))) )
  | Record fields ->
      fields
      |> convert_list gen (fun fields_cps ->
             let x = gensym gen in
             CRecord (fields_cps, (x, finish x)))
  | Select (m, i) ->
      m
      |> convert gen (fun m_cps ->
             let x = gensym gen in
             CSelect (m_cps, i, (x, finish x)))


(* 將[term list]轉換為[cps_var list]并將[finish]應用于它。 *)
and convert_list gen finish =
  let rec go acc = function
    | [] -> finish (List.rev acc)
    | x :: xs -> x |> convert gen (fun x_cps -> go (x_cps :: acc) xs)
  in
  go []

(* 將單個函數定義轉換為其CPS形式。 *)
and convert_def gen (f, params, m) =
  let k = gensym gen in
  ( CLamVar f,
    List.map (fun x -> CLamVar x) params @ [ k ],
    m |> convert gen (fun m_cps -> CAppl (k, [ m_cps ])) )

代碼包括注釋和空行總共有 74 行。這比 Rust 版本短了大概一半。

比較兩種實現

開發的核心特點涵蓋了:

  1. 大量使用遞歸定義的數據結構。
  2. 復雜的數據轉換流程。

下面簡要概述 Rust 和 OCaml 在這兩方面的處理差異:

1.遞歸數據結構:

  • OCaml:直接支持遞歸數據結構。
  • Rust:遞歸數據結構的實現需要借助Rc 和 Box將TermTree和CpsTerm的遞歸結構進行封裝。

2.復雜數據轉換:

  • OCaml
  • 遞歸廣泛應用,擁有尾調用和“ Tail Modulo Constructor (TMC )”優化。
  • 模式匹配的實現便捷高效,無需額外縮進和復雜的參數描述。
  • 標準數據結構主要為不可變類型,有助于代碼理解。
  • Rust
  • 遞歸使用較少,且并不保證尾遞歸。
  • 模式匹配的實現相對復雜,涉及額外的縮進和參數詳述。
  • 標準數據結構大多為可變類型,傾向于使用命令式風格,需要進行迭代操作才能實現流水線編程。

與 OCaml 相比,Rust 的語法更冗長。作為一門無垃圾回收的語言,它要求開發者必須精確處理內存管理。這確實增加了對執行過程的透明度,但對理解算法本身的幫助卻不明顯。

在 Rust 中,修改變量或數據結構的值也相對復雜:

fn gensym(i: RefCell<u32>) -> CpsVar {
    let x = CGenVar(i.clone().into_inner());
    i.replace_with(|&mut i| i + 1);
    x
}

與之相比,在 OCaml 中比較簡單:

let gensym i =
  let x = CGenVar !i in
  i := !i + 1;
  x

為何選擇RefCell<u32>而非&mut u32?因為 Rust 強制在任何時刻只允許一個可變引用指向特定值。盡管在多線程代碼中這是合理的,但在單線程的算法中,我們需用RefCell來繞過這個限制。

另外,Rust 中convert_list的實現也值得注意。由于fn本質上只是代碼指針,所以我們每次調用go都必須明確傳遞gen和finish,導致了變量類型的重復聲明。與之相對,OCaml則會自動捕獲gen和finish。

雖然上述算法不算特別復雜,但已經足以體現 ML 家族語言在編程方面的便利性。下面,我們將深入探討兩種語言類型系統的更多細節。

類型安全性:GADTs

與 Rust 相比,OCaml 的類型系統通常更具表現力,并且在資源管理之外具有更多優勢。例如,OCaml 支持廣義代數數據類型(GADTs),以強化數據結構的某些不變性。考慮一種包含布爾值、整數及其操作的對象語言,其描述如下:

enum Term {
    Bool(bool),
    Not(Box<Term>),
    And(Box<Term>, Box<Term>),
    Int(i32),
    Neg(Box<Term>),
    Add(Box<Term>, Box<Term>),
}

enum Value {
    Bool(bool),
    Int(i32),
}

那么,我們該如何編寫該語言的求值器呢?以下是一個可能的解決方案:

fn eval(term: &Term) -> Value {
    use Term::*;

    match term {
        Bool(b) => Value::Bool(*b),
        Not(m) => match eval(m) {
            Value::Bool(b) => Value::Bool(!b),
            _ => panic!("`Not`運算符應用于非布爾值"),
        },
        And(m, n) => match (eval(m), eval(n)) {
            (Value::Bool(b1), Value::Bool(b2)) => Value::Bool(b1 && b2),
            _ => panic!("`And`運算符應用于非布爾值"),
        },
        Int(i) => Value::Int(*i),
        Neg(m) => match eval(m) {
            Value::Int(i) => Value::Int(-i),
            _ => panic!("`Neg`運算符應用于非整數值"),
        },
        Add(m, n) => match (eval(m), eval(n)) {
            (Value::Int(i1), Value::Int(i2)) => Value::Int(i1 + i2),
            _ => panic!("`Add`運算符應用于非整數值"),
        },
    }
}

雖然該解決方案相對簡單,但并不夠穩健。如果對eval的輸入類型不合適會怎樣呢?以下示例說明了問題:

fn main() {
    use Term::*;
    let term = Not(Box::new(And(Box::new(Bool(true)), Box::new(Int(42)))));
    dbg!(eval(&term));
}

程序會因為“And運算符的第二操作數必須為布爾值”而出現問題。

為了避免此類錯誤,我們可以在 OCaml 中采用 GADTs :

type _ term =
  | Bool : bool -> bool term
  | Not : bool term -> bool term
  | And : bool term * bool term -> bool term
  | Int : int -> int term
  | Neg : int term -> int term
  | Add : int term * int term -> int term

let rec eval : type a. a term -> a = function
  | Bool b -> b
  | Not m -> not (eval m)
  | And (m, n) ->
      let b1, b2 = (eval m, eval n) in
      b1 && b2
  | Int i -> i
  | Neg m -> -eval m
  | Add (m, n) ->
      let i1, i2 = (eval m, eval n) in
      i1 + i2

現在,嘗試構造一個不合適的類型會是什么情況呢?

let () =
  let _term = Not (And (Bool true, Int 42)) in
  ()

類型檢查不會通過!

File "bin/main.ml", line 22, characters 35-41:
22 |   let _term = Not (And (Bool true, Int 42)) in
                                        ^^^^^^
Error: This expression has type int term
       but an expression was expected of type bool term
       Type int is not compatible with type bool

之所以會這樣,是因為我們在term的定義中實質上編碼了對象語言的類型系統。OCaml 知道And只接受布爾類型的項,而不是整數類型。在實際應用場景中,我們可以有一個無限制的、類似 Rust 的Term項,該項是解析生成的,并可進一步詳細闡述為合適的 GADT 風格的term。無論采用eval還是compile,后者均可被處理。

類型靈活性:First-Class Modules

OCaml 中具備一項 Rust 所不具備的獨特功能:First-Class Modules。First-Class Modules允許模塊存儲于變量、作為參數傳遞,甚至可以從常規函數返回。假設你的目標語言涵蓋了各種固定大小的整數,如i8/u8、i16/u16等,那么你可以通過 OCaml 中的(常規)模塊來表示這些類型:

fixed_ints.mli

(* [u8], [u16]等由我們定義。*)

module type S = sig
  type t

  val add : t -> t -> t
  val sub : t -> t -> t
  val mul : t -> t -> t
  val div : t -> t -> t
  val rem : t -> t -> t

  (* 這里還有一些操作。*)
end

module U8 : S with type t = u8
module U16 : S with type t = u16
module U32 : S with type t = u32
module U64 : S with type t = u64
module U128 : S with type t = u128
module I8 : S with type t = i8
module I16 : S with type t = i16
module I32 : S with type t = i32
module I64 : S with type t = i64
module I128 : S with type t = i128

在抽象語法樹(AST)中,整數值可以按照以下方式表示:

type generic =
  | U8 of u8
  | U16 of u16
  | U32 of u32
  | U64 of u64
  | U128 of u128
  | I8 of i8
  | I16 of i16
  | I32 of i32
  | I64 of i64
  | I128 of i128

那么,在存在諸多算術運算符add/sub/mul/div/rem和多種類型的操作數時,該如何高效地實現評估呢?

解決方案如下:

  1. 定義函數pair_exn,將兩個generic映射為一個一等模塊Pair。
  2. 為特定的整數對定義模塊Pair,并實現S。
  3. 定義函數do_int_bin_op,接收Pair作為參數,并執行整數對上的操作op。
  4. 從eval中調用do_int_bin_op。

在 OCaml 中:

fixed_ints.mli

module type Pair = sig
  type t

  include S with type t := t

  val pair : t * t
end

val pair_exn : generic * generic -> (module Pair)

pair的實現如下:

fixed_ints.ml

let pair_exn =
  let make (type a) (module S : S with type t = a) (x, y) =
    (module struct
      include S

      let pair = x, y
    end : Pair)
  in
  function
  | U8 x, U8 y -> make (module U8) (x, y)
  | U16 x, U16 y -> make (module U16) (x, y)
  | U32 x, U32 y -> make (module U32) (x, y)
  | U64 x, U64 y -> make (module U64) (x, y)
  | U128 x, U128 y -> make (module U128) (x, y)
  | I8 x, I8 y -> make (module I8) (x, y)
  | I16 x, I16 y -> make (module I16) (x, y)
  | I32 x, I32 y -> make (module I32) (x, y)
  | I64 x, I64 y -> make (module I64) (x, y)
  | I128 x, I128 y -> make (module I128) (x, y)
  | _, _ -> raise (invalid_arg "Fixed_ints.pair_exn")
;;

現在,我們可以按如下方式編寫 eval:

(* 在 [eval] 的定義中的某處。*)
| IntBinOp (op, ty, m, n) ->
  let x = extract_int_exn (eval m) in
  let y = extract_int_exn (eval n) in
  let (module Pair) = Fixed_ints.pair_exn (x, y) in
  do_int_bin_op op (module Pair)

extract_int_exn 取出一個值,并提取一個整型 generic,如果該值不是整數則拋出異常。

最后,do_int_bin_op 定義如下:

let do_int_bin_op op (module S : Fixed_ints.Pair) =
  let x, y = S.pair in
  match op with
  | Add -> S.add x y |> S.to_value
  | Sub -> S.sub x y |> S.to_value
  | Mul -> S.mul x y |> S.to_value
  | Div -> S.div x y |> S.to_value
  | Rem -> S.rem x y |> S.to_value
;;

S.to_value 將類型化的整數轉換回持有 generic 的值。

通過借助 First-Class Modules,我們能夠在無需過多樣板代碼的前提下實現固定大小整數的評估。而在 Rust 中的最佳實踐則是使用macro_rules!。然而,該方法因其難以理解的語法、與編程語言其他部分集成不深入,以及較差的 IDE 支持而備受詬病。

結束語

雖然 Rust 在資源管理方面表現優秀,但 OCaml 更適合于編譯器的開發。這其中涉及許多引人注目的特性,如多態變體自定義綁定操作符Effect Handlers。得益于完全靜態且靈活的類型系統,OCaml 在歷史上已廣泛應用于眾多項目,包括  Frama-C 工具鏈Coq 定理證明器,以及 Rust 編譯器的早期版本。

然而,OCaml 也不是完美的。 OCaml 的標準庫和整體生態系統與 Rust 相比顯得略遜一籌。在 OCaml 中直接使用 Rust 的完整固定大小整數集合并不可行。不過,通過整合原生 OCaml 整數、Int32、Int64模塊和 C FFI,可以達到同樣的效果。(專業提示:避免使用[ocaml-stdint],截至 2023 年 8 月 6 日,該庫未維護且存在多個錯誤。[ocaml-integers]是更穩定的選擇,但缺乏Int8、Int16和 128 位整數的支持,并在文檔方面有所不足。)

隨著 Rust 的日益普及,更多的開發者開始在 GitHub 上使用它來啟動編譯器項目。我認為,如果你試圖借助 Rust 編寫大量編譯器來深入學習 Rust ,或者確切了解自己的目標,這可能是明智之舉。 如果你主要關注的是編譯器開發,那么 OCaml 將能夠為你節省大量時間和精力。

其他值得考慮的選項包括 Haskell 和各類 Lisp 方言。 如果你已經熟練掌握了 Haskell(對此我同時表示祝賀和哀悼),那么僅為了編寫編譯器而學習 OCaml 可能是不必要的。如果你尚未掌握 Haskell,OCaml 可能是更容易上手的選擇。盡管 Lisps 極具靈活性, 但由于它們通常缺少靜態類型安全性,運行時錯誤可能成為一個棘手問題。

參考文獻

  1. CPS 是編譯器 Standard ML of New Jersey 的核心表示形式。
  2. 代碼和附帶的測試可在此處訪問。
  3. 選擇 Rc 是為了減輕在某些地方昂貴的克隆 TermTree 的負擔。
  4. 從嚴格意義上講,OCaml 中的所有函數都是柯里化(Currying)的,因此 function 只是定義了一個單一參數的函數,并在其上進行模式匹配。
  5. 在此處閉包未能提供解決方案,因為它們不能遞歸調用,至少在未進行一些復雜操作之前不能調用。

廣大網友激烈討論

網友們圍繞 Rust 和 OCaml 的優劣以及如何選擇展開了激烈討論。
關于 Rust和 OCaml 優劣對比。有些網友認為 Rust 的優點在于其內存安全性和性能,這使得它在系統編程和高性能計算中非常有用。然而,Rust 的學習曲線相對較陡,可能需要更多的時間和精力去掌握。有網友表示,OCaml 在函數式編程和類型推斷方面非常強大,它的語法簡潔,易于學習。然而,OCaml 的生態系統相對較小,可能沒有 Rust 那么多的庫和工具可供選擇。也有網友認為,Rust 和 OCaml 都有一些獨特的特性,如 Rust 的所有權系統和 OCaml 的模式匹配,這些特性在編譯器開發中可能非常有用。
關于如何選擇。有網友認為,如果項目的主要目標是快速開發和原型設計,那么 OCaml 可能是更好的選擇。如果項目需要處理復雜的并發和內存管理問題,那么 Rust 可能更適合。也有網友認為,Rust 和 OCaml 都是優秀的編程語言,但在編譯器開發中,它們各有優勢和劣勢,選擇編程語言不僅僅是技術問題,還涉及到團隊動力、項目管理和人力資源等多個方面。因此,選擇哪種語言需要綜合考慮多個因素。

譯者介紹

劉汪洋,51CTO社區編輯,昵稱:明明如月,一個擁有 5 年開發經驗的某大廠高級 Java 工程師,擁有多個主流技術博客平臺博客專家稱號。

標題:Compiler Development: Rust or OCaml?,作者:Hirrolot

責任編輯:華軒 來源: 51CTO
相關推薦

2023-07-31 07:33:04

Rust編譯器內存

2022-09-16 07:40:17

CloudWeGo開源Rust

2020-12-01 10:56:37

Rust編程AWS

2010-05-28 14:14:37

Linux開發工具

2019-10-17 09:00:00

GoRust編程語言

2023-11-13 22:03:34

IDEC語言編譯器

2025-03-13 07:32:26

2024-01-04 07:36:24

PythonIDE編譯器

2010-01-21 09:11:38

C++編譯器

2010-01-18 10:34:21

C++編譯器

2010-03-23 11:17:16

Python 動態編譯

2017-03-20 18:01:55

編譯器匯編

2009-08-10 17:12:54

C#編譯器

2013-03-29 10:02:37

編譯器語言編譯開發

2020-01-29 09:20:47

CRust編程語言

2010-10-20 13:43:37

C++編譯器

2019-08-06 08:20:07

編譯器工具開發者

2022-08-22 07:38:01

Go語言函數

2020-10-26 11:33:45

編程語言編譯器軟件

2009-07-06 12:49:33

JSP編譯器
點贊
收藏

51CTO技術棧公眾號

人妻无码一区二区三区四区| 日本一区二区在线播放| 国产精品一区二区小说| 国产成人av自拍| 精品国产免费一区二区三区| 亚洲国产专区| 日本不卡一区| 第一社区sis001原创亚洲| 午夜精品久久久久久久 | 亚洲国产女人aaa毛片在线| 亚洲一品av免费观看| 你懂的av在线| 日本中文字幕在线播放| 久久国产精品色| 日韩激情第一页| 91免费看网站| 欧美精选一区二区三区| 在线视频不卡一区二区三区| 热久久天天拍国产| 欧美大片在线看| 97成人超碰| 欧美综合久久久| 日韩中文字幕免费在线| 99热精品一区二区| 别急慢慢来1978如如2| 久久超级碰视频| 欧美夫妻性生活视频| 成人a在线观看| 哺乳挤奶一区二区三区免费看 | 亚洲女人天堂在线| 国产精品久久夜| 欧美黄网站在线观看| 成人动漫精品一区二区| 免费视频爱爱太爽了| 狠狠色综合播放一区二区| 亚洲精品一区二区毛豆| 奇米精品一区二区三区四区| 污视频在线免费观看一区二区三区| 亚洲承认在线| 国产精品伊人日日| 黄色影院一级片| 欧美丝袜一区二区| 国产美女视频一区二区三区| 少妇久久久久| 亚洲男人天堂2024| 97影院秋霞午夜在线观看| 欧美一区二区久久| 一级片a一级片| 亚洲无人区一区| 亚洲社区在线| 亚洲一区二区三区三| 香蕉视频免费在线| 一本色道久久加勒比精品| 国产一级免费在线观看| 色94色欧美sute亚洲线路一久| 国产精品麻豆一区二区三区 | 日韩精品一区二区视频| 不卡在线观看av| 99精品国产高清一区二区| 中文字幕一区二区三区乱码图片| 国产精品一区二区三区观看| 亚洲专区一区二区三区| 在线观看国产一区| 国产高清精品久久久久| 久久精品国产精品亚洲色婷婷| 中文字幕乱码亚洲精品一区| avtt天堂资源网| 欧美视频裸体精品| 欧美午夜大胆人体| 日韩视频免费在线观看| 啪啪激情综合网| 国产精品夜夜夜一区二区三区尤| 久久午夜激情| 成人一对一视频| 一级做a爱片久久| 99久久精品免费观看国产| 中文字幕一区电影| 欧美精品羞羞答答| 色999日韩自偷自拍美女| 99久久99久久精品免费看蜜桃| 日本中文字幕视频| 99re8这里有精品热视频8在线| 四虎永久在线精品免费一区二区| 另类小说视频一区二区| 日韩av图片| 91免费视频观看| 欧美vide| 日韩大陆欧美高清视频区| 高清日韩中文字幕| 国产一区自拍视频| 岛国精品在线播放| 在线国产福利网站| 亚洲欧美日韩直播| 一区二区小说| aaa免费在线观看| 亚洲精品美国一| 最新国产在线拍揄自揄视频| 欧美激情中文字幕乱码免费| 欧美一区二区三区久久精品| 久久av高潮av| 日韩欧美aⅴ综合网站发布| 粉嫩av一区二区三区四区五区| 国产精品永久免费视频| 国产一区亚洲一区| 理论视频在线| 韩日欧美一区二区| 久久成人综合网| 精品电影在线| 久久人人97超碰精品888| 免费看黄色91| 亚洲有码转帖| 久久久日本电影| 日本怡春院一区二区| 传媒视频在线| 色七七影院综合| 久久久天天操| 在线小视频网址| 欧美国产激情18| 国产一区二区剧情av在线| 国产色a在线| 茄子视频成人在线| 成人av资源站| hd国产人妖ts另类视频| 91成人免费看| 亚洲欧美日韩国产综合在线| 成人涩涩视频| 日韩免费av电影| 色婷婷亚洲婷婷| 国产亚洲一区| 一本岛在线视频| 日韩日本欧美亚洲| 国产精品一区2区| 在线中文字幕-区二区三区四区| 国产精品视频午夜| 国产精品美女久久久久av爽李琼| 一二区成人影院电影网| 欧美日韩在线精品一区二区三区| 精品欧美aⅴ在线网站| av成人app永久免费| 超碰人人爱人人| 精品处破学生在线二十三| 最新日韩av| 高清美女视频一区| 91传媒免费看| 日韩欧美极品在线观看| 色狮一区二区三区四区视频| 97福利网站在线观看视频| 国产成人精品视频| 亚洲黄色小视频| 中文字幕亚洲在线观看| 91精品国产91久久综合| 久草精品在线| 精品日本12videosex| 国产96在线 | 亚洲| 亚洲精品国产精品国自产在线| 国产亚洲欧洲| 日本在线视频网| 国产在线观看一区| 欧美色图第一页| 好看的av在线不卡观看| 三级黄视频在线观看| 91中文字幕在线| 一道本成人在线| 日韩图片一区| 俄罗斯一级**毛片在线播放| 日本不卡一区二区三区四区| 国产一区二区三区在线看| 99精品一区二区三区| 国产精品玖玖玖在线资源| 成人福利网址| 日本一本a高清免费不卡| 精品成人国产在线观看男人呻吟| 五月婷婷六月综合| 亚洲搞黄视频| 一区二区三区免费看| 亚洲日韩欧美视频一区| 91在线一区二区三区| 日韩精品导航| 国产福利在线看| 亚洲第一综合| 久久久精品999| 亚洲精品成a人| 亚洲免费大片| 成人爽a毛片免费啪啪| 国内外免费激情视频| 国产精品久久久久福利| 欧美性高清videossexo| 国产自产高清不卡| 国产视频一区二| 在线国产一区二区三区| 极品日韩久久| 日韩在线播放av| 日韩免费高清在线| 影音成人av| 黄色永久免费网站| 国产日韩欧美在线| 欧美va亚洲va香蕉在线| av在线播放成人| 日韩精品免费一区二区三区| fc2ppv国产精品久久|