Repository: mransan/ocaml-protoc
Pull request #214 (2023-09-10 18:57:40)

typecheck and codegen for services
this needs serious testing, and relies on #213 .

A question is: do we want to ship various runtime transports for the services, or is that best left to other libraries? For example, do we want a `pbrt-twirp-cohttp-lwt` library that takes any `('a, 'b) Pbrt_service.Client.rpc` and gives back a `'a -> ('b, Pbrt_service.rpc_error) result Lwt.t` (ie it encodes the query, sends it, reads the result, deserializes the result, and return that).

c-cube: There is no more question: we now generate code that will work with any concrete RPC implementation. I'm working on one in a private repo, and there's a toy Twirp implementation in #215, and a toy ZMQ implementation in #218 .

Patch:
diff --git a/benchs/benchs.ml b/benchs/benchs.ml
index 2177d71..4a27da3 100644
--- a/benchs/benchs.ml
+++ b/benchs/benchs.ml
@@ -1,5 +1,6 @@
 module B = Benchmark
 
+let spf = Printf.sprintf
 let mk_t name f = name, f, ()
 
 module Enc = struct
@@ -108,7 +109,7 @@ module Dec = struct
   let of_bytes source = { source; offset = 0; limit = Bytes.length source }
   let of_string source = of_bytes (Bytes.unsafe_of_string source)
 
-  let byte d =
+  let[@inline] byte d =
     if d.offset >= d.limit then raise (Failure Incomplete);
     let byte = int_of_char (Bytes.get d.source d.offset) in
     d.offset <- d.offset + 1;
@@ -192,7 +193,7 @@ module Dec = struct
       (let dec = of_string s in
        for _i = 0 to n do
          let _n = varint_imp dec in
-         ()
+         Sys.opaque_identity (ignore _n)
        done)
 
   let test_imp2 n (s : string) =
@@ -201,7 +202,7 @@ module Dec = struct
       (let dec = of_string s in
        for _i = 0 to n do
          let _n = varint_imp_noinline dec in
-         ()
+         Sys.opaque_identity (ignore _n)
        done)
 
   let test_rec n (s : string) =
@@ -210,7 +211,7 @@ module Dec = struct
       (let dec = of_string s in
        for _i = 0 to n do
          let _n = varint_rec dec in
-         ()
+         Sys.opaque_identity (ignore _n)
        done)
 
   let test_rec2 n (s : string) =
@@ -219,7 +220,7 @@ module Dec = struct
       (let dec = of_string s in
        for _i = 0 to n do
          let _n = varint_rec_noinline dec in
-         ()
+         Sys.opaque_identity (ignore _n)
        done)
 
   (* sanity check *)
@@ -262,6 +263,299 @@ let () =
          "enc" @>>> [ test_enc 5; test_enc 10; test_enc 50; test_enc 1000 ];
        ]
 
+module Dec_bits64 = struct
+  open Pbrt.Decoder
+  open! Dec
+
+  (* put the int64 integers from 0 to n in a dec *)
+  let mk_buf_n n : string =
+    let enc = Pbrt.Encoder.create () in
+    for i = 0 to n do
+      Pbrt.Encoder.int_as_bits64 i enc
+    done;
+    Pbrt.Encoder.to_string enc
+
+  (** Read 8 bytes at once, return offset of the first one. *)
+  let get8 (self : t) : int =
+    if self.offset + 8 > self.limit then raise (Failure Incomplete);
+    let n = self.offset in
+    self.offset <- self.offset + 8;
+    n
+
+  let bits64_unrolled (d : t) =
+    let b1 = byte d in
+    let b2 = byte d in
+    let b3 = byte d in
+    let b4 = byte d in
+    let b5 = byte d in
+    let b6 = byte d in
+    let b7 = byte d in
+    let b8 = byte d in
+    Int64.(
+      add
+        (shift_left (of_int b8) 56)
+        (add
+           (shift_left (of_int b7) 48)
+           (add
+              (shift_left (of_int b6) 40)
+              (add
+                 (shift_left (of_int b5) 32)
+                 (add
+                    (shift_left (of_int b4) 24)
+                    (add
+                       (shift_left (of_int b3) 16)
+                       (add (shift_left (of_int b2) 8) (of_int b1))))))))
+
+  let bits64_unrolled_single_read (d : t) =
+    let off = get8 d in
+    let b1 = int_of_char @@ Bytes.unsafe_get d.source off in
+    let b2 = int_of_char @@ Bytes.unsafe_get d.source (off + 1) in
+    let b3 = int_of_char @@ Bytes.unsafe_get d.source (off + 2) in
+    let b4 = int_of_char @@ Bytes.unsafe_get d.source (off + 3) in
+    let b5 = int_of_char @@ Bytes.unsafe_get d.source (off + 4) in
+    let b6 = int_of_char @@ Bytes.unsafe_get d.source (off + 5) in
+    let b7 = int_of_char @@ Bytes.unsafe_get d.source (off + 6) in
+    let b8 = int_of_char @@ Bytes.unsafe_get d.source (off + 7) in
+    Int64.(
+      add
+        (shift_left (of_int b8) 56)
+        (add
+           (shift_left (of_int b7) 48)
+           (add
+              (shift_left (of_int b6) 40)
+              (add
+                 (shift_left (of_int b5) 32)
+                 (add
+                    (shift_left (of_int b4) 24)
+                    (add
+                       (shift_left (of_int b3) 16)
+                       (add (shift_left (of_int b2) 8) (of_int b1))))))))
+
+  let bits64_loop (d : t) =
+    let res = ref 0L in
+    for i = 0 to 7 do
+      let byte = byte d in
+      res := Int64.(logor !res (shift_left (of_int byte) (8 * i)))
+    done;
+    !res
+
+  let bits64_from_stdlib (d : t) : int64 =
+    let off = get8 d in
+    Bytes.get_int64_le d.source off
+
+  let test_unrolled s n =
+    mk_t "dec-bits64-unrolled" @@ fun () ->
+    Sys.opaque_identity
+      (let dec = of_string s in
+       for _i = 0 to n do
+         let _n = bits64_unrolled dec in
+         Sys.opaque_identity (ignore _n)
+       done)
+
+  let test_unrolled_single_read s n =
+    mk_t "dec-bits64-unrolled-single-read" @@ fun () ->
+    Sys.opaque_identity
+      (let dec = of_string s in
+       for _i = 0 to n do
+         let _n = bits64_unrolled_single_read dec in
+         Sys.opaque_identity (ignore _n)
+       done)
+
+  let test_loop s n =
+    mk_t "dec-bits64-loop" @@ fun () ->
+    Sys.opaque_identity
+      (let dec = of_string s in
+       for _i = 0 to n do
+         let _n = bits64_loop dec in
+         Sys.opaque_identity (ignore _n)
+       done)
+
+  let test_stdlib s n =
+    mk_t "dec-bits64-stdlib" @@ fun () ->
+    Sys.opaque_identity
+      (let dec = of_string s in
+       for _i = 0 to n do
+         let _n = bits64_from_stdlib dec in
+         Sys.opaque_identity (ignore _n)
+       done)
+
+  (* sanity check *)
+  let () =
+    let n = 5 in
+    let s = mk_buf_n n in
+
+    let dec_to_l f =
+      let dec = of_string s in
+      let l = ref [] in
+      for _i = 0 to n do
+        let n = f dec in
+        l := Int64.to_int n :: !l
+      done;
+      List.rev !l
+    in
+    assert (dec_to_l bits64_unrolled = [ 0; 1; 2; 3; 4; 5 ]);
+    assert (dec_to_l bits64_unrolled_single_read = [ 0; 1; 2; 3; 4; 5 ]);
+    assert (dec_to_l bits64_loop = [ 0; 1; 2; 3; 4; 5 ]);
+    assert (dec_to_l bits64_from_stdlib = [ 0; 1; 2; 3; 4; 5 ]);
+    ()
+end
+
+let test_dec_bits64 n =
+  let open B.Tree in
+  let s = Dec_bits64.mk_buf_n n in
+  Printf.sprintf "%d" n
+  @> lazy
+       (B.throughputN ~repeat:3 4
+          [
+            Dec_bits64.test_unrolled s n;
+            Dec_bits64.test_unrolled_single_read s n;
+            Dec_bits64.test_loop s n;
+            Dec_bits64.test_stdlib s n;
+          ])
+
+let () =
+  let open B.Tree in
+  register @@ "bits64"
+  @>>> [
+         "dec"
+         @>>> [
+                test_dec_bits64 5;
+                test_dec_bits64 10;
+                test_dec_bits64 50;
+                test_dec_bits64 1000;
+              ];
+         (* "enc" @>>> [ test_enc 5; test_enc 10; test_enc 50; test_enc 1000 ]; *)
+       ]
+
+module Varint_size = struct
+  type run_loop = n:int -> unit
+
+  module While_inline = struct
+    (** Number of bytes to encode [i] *)
+    let[@inline] varint_size (i : int64) : int =
+      let i = ref i in
+      let n = ref 0 in
+      let continue = ref true in
+      while !continue do
+        incr n;
+        let cur = Int64.(logand !i 0x7fL) in
+        if cur = !i then
+          continue := false
+        else
+          i := Int64.shift_right_logical !i 7
+      done;
+      !n
+
+    let loop ~n =
+      for i = 1 to n do
+        ignore (Sys.opaque_identity (varint_size (Int64.of_int i)) : int)
+      done
+  end
+
+  module While_noinline = struct
+    let[@inline never] varint_size (i : int64) : int =
+      let i = ref i in
+      let n = ref 0 in
+      let continue = ref true in
+      while !continue do
+        incr n;
+        let cur = Int64.(logand !i 0x7fL) in
+        if cur = !i then
+          continue := false
+        else
+          i := Int64.shift_right_logical !i 7
+      done;
+      !n
+
+    let loop ~n =
+      for i = 1 to n do
+        ignore (Sys.opaque_identity (varint_size (Int64.of_int i)) : int)
+      done
+  end
+
+  module For_loop = struct
+    external int_of_bool : bool -> int = "%identity"
+
+    let[@inline] varint_size (i : int64) : int =
+      let i = ref i in
+      let n = ref 0 in
+      for _j = 0 to 10 do
+        n := !n + int_of_bool (not (Int64.equal !i 0L));
+        i := Int64.shift_right_logical !i 7
+      done;
+      !n
+
+    let loop ~n =
+      for i = 1 to n do
+        ignore (Sys.opaque_identity (varint_size (Int64.of_int i)) : int)
+      done
+  end
+
+  module C_while = struct
+    external varint_size : (int64[@unboxed]) -> int
+      = "caml_pbrt_varint_size_byte" "caml_pbrt_varint_size"
+      [@@noalloc]
+
+    let loop ~n =
+      for i = 1 to n do
+        ignore (Sys.opaque_identity (varint_size (Int64.of_int i)) : int)
+      done
+  end
+
+  (* sanity checks *)
+  let () =
+    List.iter
+      (fun i ->
+        let i = Int64.of_int i in
+        let c1 = While_inline.varint_size i in
+        let c2 = While_noinline.varint_size i in
+        let c3 = For_loop.varint_size i in
+        let c4 = C_while.varint_size i in
+        assert (c1 = c2);
+        assert (c1 = c3);
+        assert (c1 = c4))
+      [
+        1;
+        2;
+        3;
+        10;
+        15;
+        20;
+        21;
+        22;
+        30;
+        50;
+        100;
+        300;
+        1000;
+        2000;
+        100_000;
+        1_000_000_000;
+        max_int - 10;
+        max_int;
+      ]
+end
+
+let test_varint_size n =
+  let open B.Tree in
+  let mkbench name (run : Varint_size.run_loop) =
+    mk_t (spf "varint-size-%s" name) @@ fun () -> Sys.opaque_identity (run ~n)
+  in
+
+  spf "%d" n
+  @> lazy
+       (B.throughputN ~repeat:4 3
+          [
+            mkbench "while-inline" Varint_size.While_inline.loop;
+            mkbench "while-noinline" Varint_size.While_noinline.loop;
+            mkbench "for-loop" Varint_size.For_loop.loop;
+            mkbench "c-while" Varint_size.C_while.loop;
+          ])
+
+let () =
+  let open B.Tree in
+  register @@ "varint-size" @>>> List.map test_varint_size [ 1000; 100_000 ]
+
 module Nested = struct
   type person = Foo.person = {
     name: string;
@@ -277,6 +571,7 @@ module Nested = struct
   type company = Foo.company = {
     name: string;
     stores: store list;
+    subsidiaries: company list;
   }
 
   type payload_kind = Pbrt.payload_kind =
@@ -315,29 +610,38 @@ module Nested = struct
       E.list (fun p e -> E.key 3 Bytes (E.nested (enc_person p)) e) st.clients e;
       ()
 
-    let enc_company (c : company) (e : E.t) : unit =
+    let rec enc_company (c : company) (e : E.t) : unit =
       E.key 1 Bytes (E.string c.name) e;
       E.list (fun st e -> E.key 2 Bytes (E.nested (enc_store st)) e) c.stores e;
+      E.list
+        (fun st c -> E.key 3 Bytes (E.nested (enc_company st)) c)
+        c.subsidiaries e;
       ()
   end
 
-  let spf = Printf.sprintf
-
-  let mk_company n =
+  (* company, with [n] stores and [2^depth] subsidiaries *)
+  let rec mk_company ~n ~depth : company =
     {
       name = "bigcorp";
+      subsidiaries =
+        (if depth = 0 then
+          []
+        else (
+          let c = mk_company ~n ~depth:(depth - 1) in
+          [ c; c ]
+        ));
       stores =
         List.init n (fun i ->
             {
               address = spf "%d foobar street" i;
               clients =
-                List.init 30 (fun j ->
+                List.init 2 (fun j ->
                     {
                       name = spf "client_%d_%d" i j;
                       age = Int64.of_int ((j mod 30) + 15);
                     });
               employees =
-                List.init 5 (fun j ->
+                List.init 2 (fun j ->
                     {
                       name = spf "employee_%d_%d" i j;
                       age = Int64.of_int ((j mod 30) + 18);
@@ -349,11 +653,15 @@ module Nested = struct
     include Make_enc (E)
 
     let bench company =
-      let enc = E.create () in
-      mk_t (spf "nested-enc-%s" E.name_of_enc) @@ fun () ->
-      Sys.opaque_identity
-        (E.clear enc;
-         enc_company company enc)
+      mk_t (spf "nenc-%s" E.name_of_enc) @@ fun () ->
+      for _i = 1 to 10 do
+        let enc = E.create () in
+        for _j = 1 to 10 do
+          Sys.opaque_identity
+            (E.clear enc;
+             enc_company company enc)
+        done
+      done
 
     let string_of_company c =
       let e = E.create () in
@@ -503,58 +811,288 @@ module Nested = struct
       self.b <- b';
       self.start <- newcap - n
 
-    let[@inline never] grow_ self =
-      assert (self.start = 0);
+    let next_cap_ (self : t) : int =
       let n = cap self in
-      let newcap = n + (n lsr 1) + 3 in
+      n + (n lsr 1) + 3
+
+    let[@inline never] grow_reserve_n (self : t) n : unit =
+      let newcap = max (cap self + n) (next_cap_ self) in
       grow_to_ self newcap;
-      assert (self.start > 0)
+      assert (self.start >= n)
 
-    let[@inline] add_char (self : t) (c : char) : unit =
-      if self.start = 0 then grow_ self;
-      self.start <- self.start - 1;
-      Bytes.unsafe_set self.b self.start c
+    (** Reserve [n] bytes, return the offset at which we can write them. *)
+    let[@inline] reserve_n (self : t) (n : int) : int =
+      if self.start < n then grow_reserve_n self n;
+      self.start <- self.start - n;
+      self.start
 
     let add_bytes (self : t) (b : bytes) =
       let n = Bytes.length b in
-      if self.start - n <= 0 then grow_to_ self (cap self + n + (n lsr 1) + 1);
+      if self.start <= n then grow_to_ self (cap self + n + (n lsr 1) + 1);
       Bytes.blit b 0 self.b (self.start - n) n;
       self.start <- self.start - n;
       ()
 
-    let varint i (e : t) =
-      let[@unroll 2] rec write i =
-        let cur = Int64.(logand i 0x7fL) in
-        if cur = i then
-          add_char e (Char.unsafe_chr Int64.(to_int cur))
+    (** Number of bytes to encode [i] *)
+    let[@inline] varint_size (i : int64) : int =
+      let i = ref i in
+      let n = ref 0 in
+      let continue = ref true in
+      while !continue do
+        incr n;
+        let cur = Int64.(logand !i 0x7fL) in
+        if cur = !i then
+          continue := false
+        else
+          i := Int64.shift_right_logical !i 7
+      done;
+      !n
+
+    let[@inline] varint (i : int64) (e : t) : unit =
+      let n_bytes = varint_size i in
+      let start = reserve_n e n_bytes in
+
+      let i = ref i in
+      for j = 0 to n_bytes - 1 do
+        let cur = Int64.(logand !i 0x7fL) in
+        if j = n_bytes - 1 then
+          Bytes.set e.b (start + j) (Char.unsafe_chr Int64.(to_int cur))
         else (
-          write (Int64.shift_right_logical i 7);
-          add_char e (Char.unsafe_chr Int64.(to_int (logor 0x80L cur)))
+          Bytes.set e.b (start + j)
+            (Char.unsafe_chr Int64.(to_int (logor 0x80L cur)));
+          i := Int64.shift_right_logical !i 7
         )
+      done
+
+    let int64_as_varint = varint
+    let[@inline] int_as_varint i e = varint (Int64.of_int i) e
+
+    let[@inline] key k pk f e =
+      let pk' =
+        match pk with
+        | Varint -> 0
+        | Bits64 -> 1
+        | Bytes -> 2
+        | Bits32 -> 5
       in
-      write i
-
-    (* TODO: can we do this in a loop?
-       let varint (i:int64) (e:t) =
-         let i = ref i in
-         let continue = ref true in
-         while !continue do
-           let cur = Int64.(logand !i 0x7fL) in
-           if cur = !i
-           then (
-             continue := false;
-             add_char e (Char.unsafe_chr Int64.(to_int cur))
-           ) else (
-             add_char e
-               (Char.unsafe_chr Int64.( to_int (logor 0x80L cur)
-             ));
-             i := Int64.shift_right_logical !i 7;
-           )
-         done
-    *)
+      f e;
+      int_as_varint (pk' lor (k lsl 3)) e;
+      (* write this after the data *)
+      ()
+
+    let bytes b (e : t) =
+      add_bytes e b;
+      int_as_varint (Bytes.length b) e;
+      ()
+
+    let string s e = bytes (Bytes.unsafe_of_string s) e
+
+    (* encode lists in reverse order *)
+    let list f l e =
+      let rec loop = function
+        | [] -> ()
+        | [ x ] -> f x e
+        | x :: tl ->
+          loop tl;
+          f x e
+      in
+      loop l
+
+    let nested f (e : t) =
+      let s0 = length e in
+      f e;
+      let size = length e - s0 in
+      int_as_varint size e
+  end)
+
+  module From_back_noinline = Make_bench (struct
+    let name_of_enc = "write-backward-noinline"
+
+    type t = from_back_end
+
+    let create () : t = { b = Bytes.create 16; start = 16 }
+    let[@inline] clear self = self.start <- Bytes.length self.b
+    let[@inline] cap self = Bytes.length self.b
+    let[@inline] length self = cap self - self.start
+
+    let to_string self : string =
+      Bytes.sub_string self.b self.start (length self)
+
+    let grow_to_ self newcap =
+      let n = length self in
+      let b' = Bytes.create newcap in
+      Bytes.blit self.b self.start b' (newcap - n) n;
+      self.b <- b';
+      self.start <- newcap - n
+
+    let next_cap_ (self : t) : int =
+      let n = cap self in
+      n + (n lsr 1) + 3
+
+    let[@inline never] grow_reserve_n (self : t) n : unit =
+      let newcap = max (cap self + n) (next_cap_ self) in
+      grow_to_ self newcap;
+      assert (self.start >= n)
+
+    (** Reserve [n] bytes, return the offset at which we can write them. *)
+    let[@inline] reserve_n (self : t) (n : int) : int =
+      if self.start < n then grow_reserve_n self n;
+      self.start <- self.start - n;
+      self.start
+
+    let add_bytes (self : t) (b : bytes) =
+      let n = Bytes.length b in
+      if self.start <= n then grow_to_ self (cap self + n + (n lsr 1) + 1);
+      Bytes.blit b 0 self.b (self.start - n) n;
+      self.start <- self.start - n;
+      ()
+
+    let[@inline never] varint_size (i : int64) : int =
+      let i = ref i in
+      let n = ref 0 in
+      let continue = ref true in
+      while !continue do
+        incr n;
+        let cur = Int64.(logand !i 0x7fL) in
+        if cur = !i then
+          continue := false
+        else
+          i := Int64.shift_right_logical !i 7
+      done;
+      !n
+
+    let[@inline never] varint (i : int64) (e : t) : unit =
+      let n_bytes = varint_size i in
+      let start = reserve_n e n_bytes in
+
+      let i = ref i in
+      for j = 0 to n_bytes - 1 do
+        let cur = Int64.(logand !i 0x7fL) in
+        if j = n_bytes - 1 then
+          Bytes.set e.b (start + j) (Char.unsafe_chr Int64.(to_int cur))
+        else (
+          Bytes.set e.b (start + j)
+            (Char.unsafe_chr Int64.(to_int (logor 0x80L cur)));
+          i := Int64.shift_right_logical !i 7
+        )
+      done
 
     let int64_as_varint = varint
-    let int_as_varint i e = varint (Int64.of_int i) e
+    let[@inline] int_as_varint i e = varint (Int64.of_int i) e
+
+    let[@inline] key k pk f e =
+      let pk' =
+        match pk with
+        | Varint -> 0
+        | Bits64 -> 1
+        | Bytes -> 2
+        | Bits32 -> 5
+      in
+      f e;
+      int_as_varint (pk' lor (k lsl 3)) e;
+      (* write this after the data *)
+      ()
+
+    let bytes b (e : t) =
+      add_bytes e b;
+      int_as_varint (Bytes.length b) e;
+      ()
+
+    let string s e = bytes (Bytes.unsafe_of_string s) e
+
+    (* encode lists in reverse order *)
+    let list f l e =
+      let rec loop = function
+        | [] -> ()
+        | [ x ] -> f x e
+        | x :: tl ->
+          loop tl;
+          f x e
+      in
+      loop l
+
+    let nested f (e : t) =
+      let s0 = length e in
+      f e;
+      let size = length e - s0 in
+      int_as_varint size e
+  end)
+
+  module From_back_c = Make_bench (struct
+    let name_of_enc = "write-backward-c"
+
+    type t = from_back_end
+
+    let create () : t = { b = Bytes.create 16; start = 16 }
+    let[@inline] clear self = self.start <- Bytes.length self.b
+    let[@inline] cap self = Bytes.length self.b
+    let[@inline] length self = cap self - self.start
+
+    let to_string self : string =
+      Bytes.sub_string self.b self.start (length self)
+
+    let grow_to_ self newcap =
+      let n = length self in
+      let b' = Bytes.create newcap in
+      Bytes.blit self.b self.start b' (newcap - n) n;
+      self.b <- b';
+      self.start <- newcap - n
+
+    let next_cap_ (self : t) : int =
+      let n = cap self in
+      n + (n lsr 1) + 3
+
+    let[@inline never] grow_reserve_n (self : t) n : unit =
+      let newcap = max (cap self + n) (next_cap_ self) in
+      grow_to_ self newcap;
+      assert (self.start >= n)
+
+    (** Reserve [n] bytes, return the offset at which we can write them. *)
+    let[@inline] reserve_n (self : t) (n : int) : int =
+      if self.start < n then grow_reserve_n self n;
+      self.start <- self.start - n;
+      self.start
+
+    let add_bytes (self : t) (b : bytes) =
+      let n = Bytes.length b in
+      if self.start <= n then grow_to_ self (cap self + n + (n lsr 1) + 1);
+      Bytes.blit b 0 self.b (self.start - n) n;
+      self.start <- self.start - n;
+      ()
+
+    (*
+    external varint_size : (int64[@unboxed]) -> int
+      = "caml_pbrt_varint_size_byte" "caml_pbrt_varint_size"
+      [@@noalloc]
+      *)
+
+    (* keep this in OCaml because the C overhead is non trivial *)
+    let[@inline] varint_size (i : int64) : int =
+      let i = ref i in
+      let n = ref 0 in
+      let continue = ref true in
+      while !continue do
+        incr n;
+        let cur = Int64.(logand !i 0x7fL) in
+        if cur = !i then
+          continue := false
+        else
+          i := Int64.shift_right_logical !i 7
+      done;
+      !n
+
+    external varint_slice :
+      bytes -> (int[@untagged]) -> (int64[@unboxed]) -> unit
+      = "caml_pbrt_varint_byte" "caml_pbrt_varint"
+      [@@noalloc]
+
+    let[@inline] varint (i : int64) (e : t) : unit =
+      let n_bytes = varint_size i in
+      let start = reserve_n e n_bytes in
+      varint_slice e.b start i
+
+    let int64_as_varint = varint
+    let[@inline] int_as_varint i e = varint (Int64.of_int i) e
 
     let[@inline] key k pk f e =
       let pk' =
@@ -597,16 +1135,34 @@ module Nested = struct
   let bench_basic = Basic.bench
   let bench_buffers_nested = Buffers_nested.bench
   let bench_from_back = From_back.bench
+  let bench_from_back_noinline = From_back_noinline.bench
+  let bench_from_back_c = From_back_c.bench
+
+  let pp_size ~n ~depth =
+    Printf.printf "bench nested enc: length for n=%d, depth=%d is %d B\n" n
+      depth
+      (String.length (Basic.string_of_company @@ mk_company ~n ~depth))
 
   (* sanity check *)
-  let () =
-    let s_basic = Basic.string_of_company (mk_company 1) in
-    let s_buffers_nested = Buffers_nested.string_of_company (mk_company 1) in
-    let s_from_back = From_back.string_of_company (mk_company 1) in
+  let check ~n ~depth () =
+    let s_basic = Basic.string_of_company (mk_company ~n ~depth) in
+    let s_buffers_nested =
+      Buffers_nested.string_of_company (mk_company ~n ~depth)
+    in
+    let s_from_back = From_back.string_of_company (mk_company ~n ~depth) in
+    let s_from_back2 =
+      From_back_noinline.string_of_company (mk_company ~n ~depth)
+    in
+    let s_from_backc = From_back_c.string_of_company (mk_company ~n ~depth) in
     (*
-    Printf.printf "basic: (len=%d) %S\n" (String.length s_basic) s_basic;
-    Printf.printf "from_back: (len=%d) %S\n" (String.length s_from_back) s_from_back;
-       *)
+       Printf.printf "basic:\n(len=%d) %S\n" (String.length s_basic) s_basic;
+       Printf.printf "from_back:\n(len=%d) %S\n"
+         (String.length s_from_back)
+         s_from_back;
+       Printf.printf "from_back_c:\n(len=%d) %S\n"
+         (String.length s_from_backc)
+         s_from_backc;
+    *)
     let dec_s s =
       Pbrt.Decoder.(
         let dec = of_string s in
@@ -615,25 +1171,37 @@ module Nested = struct
     let c_basic = dec_s s_basic in
     let c_buffers_nested = dec_s s_buffers_nested in
     let c_from_back = dec_s s_from_back in
+    let c_from_back2 = dec_s s_from_back2 in
+    let c_from_backc = dec_s s_from_backc in
     (*
     Format.printf "c_basic=%a@." Foo_pp.pp_company c_basic;
     Format.printf "c_from_back=%a@." Foo_pp.pp_company c_from_back;
        *)
     assert (c_basic = c_buffers_nested);
     assert (c_basic = c_from_back);
+    assert (c_basic = c_from_back2);
+    assert (c_basic = c_from_backc);
     ()
+
+  let () =
+    List.iter
+      (fun (n, depth) -> check ~n ~depth ())
+      [ 1, 3; 2, 4; 10, 1; 20, 2 ]
 end
 
-let test_nested_enc n =
+let test_nested_enc ~n ~depth =
   let open B.Tree in
-  let company = Nested.mk_company n in
-  Printf.sprintf "%d" n
+  let company = Nested.mk_company ~n ~depth in
+  Printf.sprintf "n=%d,depth=%d" n depth
   @> lazy
-       (B.throughputN ~repeat:4 3
+       (Nested.pp_size ~n ~depth;
+        B.throughputN ~repeat:4 3
           [
             Nested.bench_basic company;
             Nested.bench_buffers_nested company;
             Nested.bench_from_back company;
+            Nested.bench_from_back_noinline company;
+            Nested.bench_from_back_c company;
           ])
 
 let () =
@@ -641,14 +1209,35 @@ let () =
   register @@ "nested"
   @>>> [
          "enc"
-         @>>> [
-                test_nested_enc 2;
-                test_nested_enc 5;
-                test_nested_enc 10;
-                test_nested_enc 20;
-                test_nested_enc 50;
-                test_nested_enc 100;
-              ];
+         @>>> List.map
+                (fun (n, depth) -> test_nested_enc ~n ~depth)
+                [
+                  1, 1;
+                  1, 1;
+                  1, 4;
+                  1, 6;
+                  1, 10;
+                  2, 1;
+                  2, 4;
+                  2, 6;
+                  2, 10;
+                  5, 1;
+                  5, 4;
+                  5, 6;
+                  10, 1;
+                  10, 2;
+                  10, 3;
+                  10, 4;
+                  20, 1;
+                  50, 1;
+                  20, 3;
+                  20, 4;
+                  50, 1;
+                  50, 3;
+                  50, 4;
+                  100, 1;
+                  100, 3;
+                ];
        ]
 
 let () = B.Tree.run_global ()
diff --git a/benchs/dune b/benchs/dune
index 8a8d064..207e4ef 100644
--- a/benchs/dune
+++ b/benchs/dune
@@ -1,6 +1,7 @@
 (executable
  (name benchs)
  (ocamlopt_flags :standard -inline 100)
+ (foreign_stubs (language c) (flags :standard -std=c99 -O2) (names stubs))
  (libraries ocaml-protoc benchmark))
 
 (rule
diff --git a/benchs/foo.proto b/benchs/foo.proto
index ba1e202..13a07d4 100644
--- a/benchs/foo.proto
+++ b/benchs/foo.proto
@@ -15,4 +15,5 @@ message Store {
 message Company {
   string name = 1;
   repeated Store stores = 2;
+  repeated Company subsidiaries = 3;
 }
diff --git a/benchs/stubs.c b/benchs/stubs.c
new file mode 100644
index 0000000..ce2398a
--- /dev/null
+++ b/benchs/stubs.c
@@ -0,0 +1,80 @@
+
+#include <caml/alloc.h>
+#include <caml/memory.h>
+#include <caml/mlvalues.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+inline int pbrt_varint_size(int64_t i) {
+  int n = 0;
+  while (1) {
+    n++;
+    int64_t cur = i & 0x7f;
+    if (cur == i)
+      break;
+    i = i >> 7;
+  }
+  return n;
+}
+
+// number of bytes for i
+CAMLprim value caml_pbrt_varint_size(int64_t i) {
+  int res = pbrt_varint_size(i);
+  return Val_int(res);
+}
+
+CAMLprim value caml_pbrt_varint_size_byte(value v_i) {
+  CAMLparam1(v_i);
+
+  int64_t i = Int64_val(v_i);
+  int res = pbrt_varint_size(i);
+  CAMLreturn(Val_int(res));
+}
+
+// write i at str[idx…]
+inline void pbrt_varint(unsigned char *str, int64_t i) {
+  while (true) {
+    int64_t cur = i & 0x7f;
+    if (cur == i) {
+      *str = (unsigned char)cur;
+      break;
+    } else {
+      *str = (unsigned char)(cur | 0x80);
+      i = i >> 7;
+      ++str;
+    }
+  }
+}
+
+//    let[@inline] varint (i : int64) (e : t) : unit =
+//      let n_bytes = varint_size i in
+//      let start = reserve_n e n_bytes in
+//
+//      let i = ref i in
+//      for j = 0 to n_bytes - 1 do
+//        let cur = Int64.(logand !i 0x7fL) in
+//        if j = n_bytes - 1 then
+//          Bytes.set e.b (start + j) (Char.unsafe_chr Int64.(to_int cur))
+//        else (
+//          Bytes.set e.b (start + j)
+//            (Char.unsafe_chr Int64.(to_int (logor 0x80L cur)));
+//          i := Int64.shift_right_logical !i 7
+//        )
+//      done
+
+// write `i` starting at `idx`
+CAMLprim value caml_pbrt_varint(value _str, intnat idx, int64_t i) {
+  CAMLparam1(_str);
+  char *str = Bytes_val(_str);
+  pbrt_varint(str + idx, i);
+  CAMLreturn(Val_unit);
+}
+
+CAMLprim value caml_pbrt_varint_bytes(value _str, value _idx, value _i) {
+  CAMLparam3(_str, _idx, _i);
+  char *str = Bytes_val(_str);
+  int idx = Int_val(_idx);
+  int64_t i = Int64_val(_idx);
+  pbrt_varint(str + idx, i);
+  CAMLreturn(Val_unit);
+}
diff --git a/dune-project b/dune-project
index 057276d..ec9ff44 100644
--- a/dune-project
+++ b/dune-project
@@ -3,8 +3,8 @@
 (generate_opam_files true)
 (version 2.4)
 
-(maintainers "Maxime Ransan <maxime.ransan@gmail.com>")
-(authors "Maxime Ransan <maxime.ransan@gmail.com>")
+(maintainers "Maxime Ransan <maxime.ransan@gmail.com>" "Simon Cruanes")
+(authors "Maxime Ransan <maxime.ransan@gmail.com>" "Simon Cruanes")
 (source (github mransan/ocaml-protoc))
 (license MIT)
 
@@ -20,7 +20,7 @@
 (package
   (name pbrt)
   (synopsis "Runtime library for Protobuf tooling")
-  (depends 
+  (depends
     stdlib-shims
     (odoc :with-doc)
     (ocaml (>= 4.03)))
@@ -29,11 +29,19 @@
 (package
   (name pbrt_yojson)
   (synopsis "Runtime library for ocaml-protoc to support JSON encoding/decoding")
-  (depends 
+  (depends
     (ocaml (>= 4.03))
     (odoc :with-doc)
     (yojson (>= 1.6))
     base64)
   (tags (protobuf encode decode)))
 
+(package
+  (name pbrt_services)
+  (synopsis "Runtime library for ocaml-protoc to support RPC services")
+  (depends
+    (ocaml (>= 4.03))
+    (pbrt (= :version))
+    (pbrt_yojson (= :version)))
+  (tags (protobuf encode decode services rpc)))
 
diff --git a/ocaml-protoc.opam b/ocaml-protoc.opam
index 24c8e06..618cb62 100644
--- a/ocaml-protoc.opam
+++ b/ocaml-protoc.opam
@@ -2,8 +2,8 @@
 opam-version: "2.0"
 version: "2.4"
 synopsis: "Pure OCaml compiler for .proto files"
-maintainer: ["Maxime Ransan <maxime.ransan@gmail.com>"]
-authors: ["Maxime Ransan <maxime.ransan@gmail.com>"]
+maintainer: ["Maxime Ransan <maxime.ransan@gmail.com>" "Simon Cruanes"]
+authors: ["Maxime Ransan <maxime.ransan@gmail.com>" "Simon Cruanes"]
 license: "MIT"
 tags: ["protoc" "protobuf" "codegen"]
 homepage: "https://github.com/mransan/ocaml-protoc"
diff --git a/pbrt.opam b/pbrt.opam
index 46b3953..b23272a 100644
--- a/pbrt.opam
+++ b/pbrt.opam
@@ -2,8 +2,8 @@
 opam-version: "2.0"
 version: "2.4"
 synopsis: "Runtime library for Protobuf tooling"
-maintainer: ["Maxime Ransan <maxime.ransan@gmail.com>"]
-authors: ["Maxime Ransan <maxime.ransan@gmail.com>"]
+maintainer: ["Maxime Ransan <maxime.ransan@gmail.com>" "Simon Cruanes"]
+authors: ["Maxime Ransan <maxime.ransan@gmail.com>" "Simon Cruanes"]
 license: "MIT"
 tags: ["protobuf" "encode" "decode"]
 homepage: "https://github.com/mransan/ocaml-protoc"
diff --git a/pbrt_services.opam b/pbrt_services.opam
new file mode 100644
index 0000000..6fd466f
--- /dev/null
+++ b/pbrt_services.opam
@@ -0,0 +1,31 @@
+# This file is generated by dune, edit dune-project instead
+opam-version: "2.0"
+version: "2.4"
+synopsis: "Runtime library for ocaml-protoc to support RPC services"
+maintainer: ["Maxime Ransan <maxime.ransan@gmail.com>" "Simon Cruanes"]
+authors: ["Maxime Ransan <maxime.ransan@gmail.com>" "Simon Cruanes"]
+license: "MIT"
+tags: ["protobuf" "encode" "decode" "services" "rpc"]
+homepage: "https://github.com/mransan/ocaml-protoc"
+bug-reports: "https://github.com/mransan/ocaml-protoc/issues"
+depends: [
+  "dune" {>= "2.0"}
+  "ocaml" {>= "4.03"}
+  "pbrt" {= version}
+  "pbrt_yojson" {= version}
+]
+build: [
+  ["dune" "subst"] {pinned}
+  [
+    "dune"
+    "build"
+    "-p"
+    name
+    "-j"
+    jobs
+    "@install"
+    "@runtest" {with-test}
+    "@doc" {with-doc}
+  ]
+]
+dev-repo: "git+https://github.com/mransan/ocaml-protoc.git"
diff --git a/pbrt_yojson.opam b/pbrt_yojson.opam
index 6fde790..6fb7865 100644
--- a/pbrt_yojson.opam
+++ b/pbrt_yojson.opam
@@ -3,8 +3,8 @@ opam-version: "2.0"
 version: "2.4"
 synopsis:
   "Runtime library for ocaml-protoc to support JSON encoding/decoding"
-maintainer: ["Maxime Ransan <maxime.ransan@gmail.com>"]
-authors: ["Maxime Ransan <maxime.ransan@gmail.com>"]
+maintainer: ["Maxime Ransan <maxime.ransan@gmail.com>" "Simon Cruanes"]
+authors: ["Maxime Ransan <maxime.ransan@gmail.com>" "Simon Cruanes"]
 license: "MIT"
 tags: ["protobuf" "encode" "decode"]
 homepage: "https://github.com/mransan/ocaml-protoc"
diff --git a/src/compilerlib/dune b/src/compilerlib/dune
index 3064fcb..65a2328 100644
--- a/src/compilerlib/dune
+++ b/src/compilerlib/dune
@@ -8,9 +8,9 @@
  (synopsis "Compiler library for ocaml-protoc, to turn .proto files into OCaml code")
  (wrapped true)
  (modules pb_codegen_all pb_codegen_backend pb_codegen_decode_binary pb_codegen_decode_bs
-   pb_codegen_decode_yojson pb_codegen_default pb_codegen_encode_binary
+   pb_codegen_decode_yojson pb_codegen_default pb_codegen_make pb_codegen_encode_binary
    pb_codegen_encode_bs pb_codegen_encode_yojson pb_codegen_formatting
-   pb_codegen_ocaml_type pb_codegen_pp pb_codegen_plugin pb_codegen_types
+   pb_codegen_ocaml_type pb_codegen_pp pb_codegen_plugin pb_codegen_types pb_codegen_services
    pb_codegen_util pb_exception pb_field_type pb_location pb_logger pb_option
    pb_parsing pb_parsing_lexer pb_parsing_parser pb_parsing_parse_tree
    pb_parsing_util pb_typing_graph pb_typing pb_typing_recursion
diff --git a/src/compilerlib/pb_codegen_all.ml b/src/compilerlib/pb_codegen_all.ml
index 2f2f5f0..c88392c 100644
--- a/src/compilerlib/pb_codegen_all.ml
+++ b/src/compilerlib/pb_codegen_all.ml
@@ -1,8 +1,8 @@
 (*
   The MIT License (MIT)
-  
+
   Copyright (c) 2016 Maxime Ransan <maxime.ransan@gmail.com>
-  
+
   Permission is hereby granted, free of charge, to any person obtaining a copy
   of this software and associated documentation files (the "Software"), to deal
   in the Software without restriction, including without limitation the rights
@@ -29,6 +29,7 @@ module F = Pb_codegen_formatting
 module Plugin = Pb_codegen_plugin
 
 type codegen_f = Plugin.codegen_f
+type codegen_service_f = Ot.service -> F.scope -> unit
 
 type ocaml_mod = {
   ml: F.scope;
@@ -88,16 +89,32 @@ let generate_for_all_types (ocaml_types : Ot.type_ list list) (sc : F.scope)
       ())
     ocaml_types
 
+let generate_for_all_services (services : Ot.service list) (sc : F.scope)
+    (f : codegen_service_f) ocamldoc_title : unit =
+  (match ocamldoc_title with
+  | None -> ()
+  | Some ocamldoc_title ->
+    F.empty_line sc;
+    F.linep sc "(** {2 %s} *)" ocamldoc_title;
+    F.empty_line sc);
+
+  List.iter (fun (service : Ot.service) -> f service sc) services
+
 let generate_type_and_default (self : ocaml_mod) ocaml_types : unit =
   generate_for_all_types ocaml_types self.ml Pb_codegen_types.gen_struct None;
   generate_for_all_types ocaml_types self.ml Pb_codegen_default.gen_struct None;
-
   generate_for_all_types ocaml_types self.mli Pb_codegen_types.gen_sig
     (Some Pb_codegen_types.ocamldoc_title);
   generate_for_all_types ocaml_types self.mli Pb_codegen_default.gen_sig
     (Some Pb_codegen_default.ocamldoc_title);
   ()
 
+let generate_make (self : ocaml_mod) ocaml_types : unit =
+  generate_for_all_types ocaml_types self.ml Pb_codegen_make.gen_struct
+    (Some Pb_codegen_make.ocamldoc_title);
+  generate_for_all_types ocaml_types self.mli Pb_codegen_make.gen_sig
+    (Some Pb_codegen_make.ocamldoc_title)
+
 let generate_mutable_records (self : ocaml_mod) ocaml_types : unit =
   let ocaml_types = List.flatten ocaml_types in
   List.iter
@@ -111,6 +128,17 @@ let generate_mutable_records (self : ocaml_mod) ocaml_types : unit =
       | _ -> ())
     ocaml_types
 
+let generate_service_struct service sc : unit =
+  Pb_codegen_services.gen_service_struct service sc
+
+let generate_service_sig service sc : unit =
+  Pb_codegen_services.gen_service_sig service sc
+
+let generate_services (self : ocaml_mod) services : unit =
+  generate_for_all_services services self.ml generate_service_struct None;
+  generate_for_all_services services self.mli generate_service_sig
+    (Some "Services")
+
 let generate_plugin (self : ocaml_mod) ocaml_types (p : Plugin.t) : unit =
   let (module P) = p in
   F.line self.ml "[@@@ocaml.warning \"-27-30-39\"]";
@@ -119,11 +147,15 @@ let generate_plugin (self : ocaml_mod) ocaml_types (p : Plugin.t) : unit =
   generate_for_all_types ocaml_types self.mli P.gen_sig (Some P.ocamldoc_title);
   ()
 
-let codegen ocaml_types ~proto_file_options ~proto_file_name
-    (plugins : Plugin.t list) : ocaml_mod =
+let codegen (proto : Ot.proto) ~generate_make:gen_make ~proto_file_options
+    ~proto_file_name (plugins : Plugin.t list) : ocaml_mod =
   let self = new_ocaml_mod ~proto_file_options ~proto_file_name () in
-  generate_type_and_default self ocaml_types;
+  generate_type_and_default self proto.proto_types;
   if List.exists Pb_codegen_plugin.requires_mutable_records plugins then
-    generate_mutable_records self ocaml_types;
-  List.iter (generate_plugin self ocaml_types) plugins;
+    generate_mutable_records self proto.proto_types;
+  if gen_make then generate_make self proto.proto_types;
+  List.iter (generate_plugin self proto.proto_types) plugins;
+
+  (* services come last, they need binary and json *)
+  generate_services self proto.proto_services;
   self
diff --git a/src/compilerlib/pb_codegen_all.mli b/src/compilerlib/pb_codegen_all.mli
index 6394720..55fcc66 100644
--- a/src/compilerlib/pb_codegen_all.mli
+++ b/src/compilerlib/pb_codegen_all.mli
@@ -10,7 +10,8 @@ type ocaml_mod = {
 }
 
 val codegen :
-  Ot.type_ list list ->
+  Ot.proto ->
+  generate_make:bool ->
   proto_file_options:Pb_option.set ->
   proto_file_name:string ->
   Plugin.t list ->
diff --git a/src/compilerlib/pb_codegen_backend.ml b/src/compilerlib/pb_codegen_backend.ml
index 10243c8..84f12e2 100644
--- a/src/compilerlib/pb_codegen_backend.ml
+++ b/src/compilerlib/pb_codegen_backend.ml
@@ -91,7 +91,7 @@ let module_prefix_of_file_name file_name =
   | dot_index -> module_name @@ String.sub file_name 0 dot_index
   | exception Not_found -> E.invalid_file_name file_name
 
-let type_name message_scope name =
+let type_name message_scope name : string =
   let module S = String in
   let all_names = message_scope @ [ name ] in
   let all_names =
@@ -132,7 +132,7 @@ let wrapper_type_of_type_name = function
     with the module name. (This is essentially expecting (rightly) a sub module
     with the same name.
  *)
-let user_defined_type_of_id all_types file_name i =
+let user_defined_type_of_id ~(all_types : _ list) ~file_name i : Ot.field_type =
   let module_prefix = module_prefix_of_file_name file_name in
   match Typing_util.type_of_id all_types i with
   | exception Not_found -> E.programmatic_error E.No_type_found_for_id
@@ -169,7 +169,7 @@ let user_defined_type_of_id all_types file_name i =
       )
     )
 
-let encoding_info_of_field_type all_types field_type =
+let encoding_info_of_field_type ~all_types field_type : Ot.payload_kind =
   match field_type with
   | `Double -> Ot.Pk_bits64
   | `Float -> Ot.Pk_bits32
@@ -191,7 +191,7 @@ let encoding_info_of_field_type all_types field_type =
     | { Tt.spec = Tt.Enum _; _ } -> Ot.Pk_varint false
     | { Tt.spec = Tt.Message _; _ } -> Ot.Pk_bytes)
 
-let encoding_of_field all_types (field : (Pb_field_type.resolved, 'a) Tt.field)
+let encoding_of_field ~all_types (field : (Pb_field_type.resolved, 'a) Tt.field)
     =
   let packed =
     match Typing_util.field_option field "packed" with
@@ -201,12 +201,12 @@ let encoding_of_field all_types (field : (Pb_field_type.resolved, 'a) Tt.field)
   in
 
   let pk =
-    encoding_info_of_field_type all_types (Typing_util.field_type field)
+    encoding_info_of_field_type ~all_types (Typing_util.field_type field)
   in
   pk, Typing_util.field_number field, packed, Typing_util.field_default field
 
-let compile_field_type ~unsigned_tag all_types file_options field_options
-    file_name field_type =
+let compile_field_type ~unsigned_tag ~(all_types : _ Tt.proto_type list)
+    file_options field_options file_name field_type : Ot.field_type =
   let ocaml_type =
     match Pb_option.get field_options "ocaml_type" with
     | Some Pb_option.(Scalar_value (Constant_literal "int_t")) -> `Int_t
@@ -285,7 +285,7 @@ let compile_field_type ~unsigned_tag all_types file_options field_options
   | `Bool, _ -> Ot.(Ft_basic_type Bt_bool)
   | `String, _ -> Ot.(Ft_basic_type Bt_string)
   | `Bytes, _ -> Ot.(Ft_basic_type Bt_bytes)
-  | `User_defined id, _ -> user_defined_type_of_id all_types file_name id
+  | `User_defined id, _ -> user_defined_type_of_id ~all_types ~file_name id
 
 let is_mutable ?field_name field_options =
   match Pb_option.get field_options "ocaml_mutable" with
@@ -301,19 +301,19 @@ let ocaml_container field_options =
   | Some _ -> None
 
 let variant_of_oneof ?include_oneof_name ~outer_message_names ~unsigned_tag
-    all_types file_options file_name oneof_field =
+    ~all_types file_options file_name oneof_field : Ot.variant =
   let v_constructors =
     List.map
       (fun field ->
         let pbtt_field_type = Typing_util.field_type field in
         let field_type =
-          compile_field_type ~unsigned_tag all_types file_options
+          compile_field_type ~unsigned_tag ~all_types file_options
             (Typing_util.field_options field)
             file_name pbtt_field_type
         in
 
         let vc_payload_kind, vc_encoding_number, _, _ =
-          encoding_of_field all_types field
+          encoding_of_field ~all_types field
         in
 
         let vc_constructor = constructor_name (Typing_util.field_name field) in
@@ -339,30 +339,30 @@ let variant_of_oneof ?include_oneof_name ~outer_message_names ~unsigned_tag
   Ot.{ v_name; v_constructors }
 
 (*
- * Notes on type level PPX extension handling.
- *
- * ocaml-protoc supports 2 custom options for defining type level ppx
- * extensions:
- * a) message option called ocaml_type_ppx
- * b) file option called ocaml_all_types_ppx
- *
- * 'ocaml_type_ppx' has priority over 'ocaml_all_types_ppx' extension.
- * This means that if a message contains 'ocaml_type_ppx' extension then the
- * associated string will be used for the OCaml generated type ppx extension.
- *
- * 'ocaml_all_types_ppx' is a file option which is a convenient workflow when
- * the ppx extensions are the same for all types. (Most likely the case).
- *
+   Notes on type level PPX extension handling.
+
+   ocaml-protoc supports 2 custom options for defining type level ppx
+   extensions:
+   a) message option called ocaml_type_ppx
+   b) file option called ocaml_all_types_ppx
+
+   'ocaml_type_ppx' has priority over 'ocaml_all_types_ppx' extension.
+   This means that if a message contains 'ocaml_type_ppx' extension then the
+   associated string will be used for the OCaml generated type ppx extension.
+
+   'ocaml_all_types_ppx' is a file option which is a convenient workflow when
+   the ppx extensions are the same for all types. (Most likely the case).
+
  *)
 
-(* utility function to return the string value from a sring option
+(** utility function to return the string value from a sring option
  *)
 let string_of_string_option message_name = function
   | None -> None
   | Some Pb_option.(Scalar_value (Constant_string s)) -> Some s
   | _ -> E.invalid_ppx_extension_option message_name
 
-(* utility function to implement the priority logic defined in the notes above.
+(** utility function to implement the priority logic defined in the notes above.
  *)
 let process_all_types_ppx_extension file_name file_options
     type_level_ppx_extension =
@@ -373,9 +373,9 @@ let process_all_types_ppx_extension file_name file_options
     |> string_of_string_option file_name
 
 let compile_message ~(unsigned_tag : bool) (file_options : Pb_option.set)
-    (all_types : Pb_field_type.resolved Tt.proto) (file_name : string)
-    (scope : Tt.type_scope) (message : Pb_field_type.resolved Tt.message) :
-    Ot.type_ list =
+    ~(all_types : Pb_field_type.resolved Tt.proto_type list)
+    (file_name : string) (scope : Tt.type_scope)
+    (message : Pb_field_type.resolved Tt.message) : Ot.type_ list =
   let module_prefix = module_prefix_of_file_name file_name in
 
   (* TODO maybe module_ should be resolved before `compile_message` since
@@ -408,8 +408,8 @@ let compile_message ~(unsigned_tag : bool) (file_options : Pb_option.set)
   | Tt.Message_oneof_field f :: [] ->
     let outer_message_names = message_names @ [ message_name ] in
     let variant =
-      variant_of_oneof ~unsigned_tag ~outer_message_names all_types file_options
-        file_name f
+      variant_of_oneof ~unsigned_tag ~outer_message_names ~all_types
+        file_options file_name f
     in
     [ Ot.{ module_prefix; spec = Variant variant; type_level_ppx_extension } ]
   | _ ->
@@ -418,7 +418,7 @@ let compile_message ~(unsigned_tag : bool) (file_options : Pb_option.set)
         (fun (variants, fields) -> function
           | Tt.Message_field field ->
             let pk, encoding_number, packed, _ =
-              encoding_of_field all_types field
+              encoding_of_field ~all_types field
             in
 
             let field_name = Typing_util.field_name field in
@@ -428,7 +428,7 @@ let compile_message ~(unsigned_tag : bool) (file_options : Pb_option.set)
             let field_type = Typing_util.field_type field in
 
             let ocaml_field_type =
-              compile_field_type ~unsigned_tag all_types file_options
+              compile_field_type ~unsigned_tag ~all_types file_options
                 field_options file_name field_type
             in
 
@@ -440,17 +440,17 @@ let compile_message ~(unsigned_tag : bool) (file_options : Pb_option.set)
               match Typing_util.field_label field with
               | `Nolabel ->
                 (* From proto3 section on default value:
-                 * https://goo.gl/NKt9Cc
-                 *
-                 * --
-                 * For message fields, the field is not set. Its exact value is
-                 * language-dependent. See the generated code guide for details.
-                 * --
-                 *
-                 * Since we must support the face that the message won't be sent
-                 * we always make such a field an OCaml option. It's the
-                 * responsability of the application to check for [None] and
-                 * perform any error handling if required. *)
+                   https://goo.gl/NKt9Cc
+
+                   --
+                   For message fields, the field is not set. Its exact value is
+                   language-dependent. See the generated code guide for details.
+                   --
+
+                   Since we must support the face that the message won't be sent
+                   we always make such a field an OCaml option. It's the
+                   responsability of the application to check for [None] and
+                   perform any error handling if required. *)
                 let is_message =
                   match ocaml_field_type with
                   | Ot.Ft_user_defined_type { Ot.udt_type = `Message; _ } ->
@@ -493,7 +493,7 @@ let compile_message ~(unsigned_tag : bool) (file_options : Pb_option.set)
             let outer_message_names = message_names @ [ message_name ] in
             let variant =
               variant_of_oneof ~unsigned_tag ~include_oneof_name:()
-                ~outer_message_names all_types file_options file_name field
+                ~outer_message_names ~all_types file_options file_name field
             in
 
             let record_field =
@@ -538,11 +538,11 @@ let compile_message ~(unsigned_tag : bool) (file_options : Pb_option.set)
             in
 
             let key_type =
-              compile_field_type ~unsigned_tag all_types file_options
+              compile_field_type ~unsigned_tag ~all_types file_options
                 map_options file_name map_key_type
             in
 
-            let key_pk = encoding_info_of_field_type all_types map_key_type in
+            let key_pk = encoding_info_of_field_type ~all_types map_key_type in
 
             let key_type =
               match key_type with
@@ -551,12 +551,12 @@ let compile_message ~(unsigned_tag : bool) (file_options : Pb_option.set)
             in
 
             let value_type =
-              compile_field_type ~unsigned_tag all_types file_options
+              compile_field_type ~unsigned_tag ~all_types file_options
                 map_options file_name map_value_type
             in
 
             let value_pk =
-              encoding_info_of_field_type all_types map_value_type
+              encoding_info_of_field_type ~all_types map_value_type
             in
 
             let associative_type =
@@ -636,12 +636,50 @@ let compile_enum file_options file_name scope enum =
       type_level_ppx_extension;
     }
 
-let compile ~unsigned_tag all_types = function
+let compile_rpc ~(file_name : string) ~all_types
+    (rpc : Pb_field_type.resolved Tt.rpc) : Ot.rpc =
+  let compile_ty ~stream (ty : int) : Ot.rpc_type =
+    let ty = user_defined_type_of_id ~all_types ~file_name ty in
+    if stream then
+      Ot.Rpc_stream ty
+    else
+      Ot.Rpc_scalar ty
+  in
+
+  {
+    Ot.rpc_name = rpc.rpc_name;
+    rpc_req = compile_ty ~stream:rpc.rpc_req_stream rpc.rpc_req;
+    rpc_res = compile_ty ~stream:rpc.rpc_res_stream rpc.rpc_res;
+  }
+
+let compile_service ~all_types (service : Pb_field_type.resolved Tt.service) :
+    Ot.service =
+  {
+    Ot.service_name = service.service_name;
+    service_packages = service.service_packages;
+    service_body =
+      List.map
+        (compile_rpc ~file_name:service.service_file_name ~all_types)
+        service.service_body;
+  }
+
+let compile1 ~unsigned_tag ~all_types : _ -> Ot.type_ list = function
   | { Tt.spec = Tt.Message m; file_name; file_options; scope; _ } ->
-    compile_message ~unsigned_tag file_options all_types file_name scope m
+    compile_message ~unsigned_tag file_options ~all_types file_name scope m
   | { Tt.spec = Tt.Enum e; file_name; scope; file_options; _ } ->
     [ compile_enum file_options file_name scope e ]
 
+let compile ~unsigned_tag ~all_types (proto : Pb_field_type.resolved Tt.proto) :
+    Ot.proto =
+  let tys =
+    List.map
+      (fun t -> List.flatten @@ List.map (compile1 ~unsigned_tag ~all_types) t)
+      proto.proto_types
+  in
+
+  let services = List.map (compile_service ~all_types) proto.proto_services in
+  { Ot.proto_services = services; proto_types = tys }
+
 module Internal = struct
   let is_mutable = is_mutable
   let constructor_name = constructor_name
diff --git a/src/compilerlib/pb_codegen_backend.mli b/src/compilerlib/pb_codegen_backend.mli
index fc0e1bf..13c41c4 100644
--- a/src/compilerlib/pb_codegen_backend.mli
+++ b/src/compilerlib/pb_codegen_backend.mli
@@ -38,9 +38,9 @@ module Ot = Pb_codegen_ocaml_type
 
 val compile :
   unsigned_tag:bool ->
+  all_types:Pb_field_type.resolved Tt.proto_type list ->
   Pb_field_type.resolved Tt.proto ->
-  Pb_field_type.resolved Tt.proto_type ->
-  Ot.type_ list
+  Ot.proto
 
 (** Internal helpers.
 
@@ -60,7 +60,7 @@ module Internal : sig
     ?include_oneof_name:unit ->
     outer_message_names:string list ->
     unsigned_tag:bool ->
-    'a Tt.proto ->
+    all_types:'a Tt.proto_type list ->
     Pb_option.set ->
     string ->
     int Tt.oneof ->
diff --git a/src/compilerlib/pb_codegen_decode_binary.ml b/src/compilerlib/pb_codegen_decode_binary.ml
index b13bdef..f9e9822 100644
--- a/src/compilerlib/pb_codegen_decode_binary.ml
+++ b/src/compilerlib/pb_codegen_decode_binary.ml
@@ -39,7 +39,7 @@ let runtime_function_for_wrapper_type { Ot.wt_type; wt_pk } =
   | Ot.Bt_bytes, Ot.Pk_bytes -> "Pbrt.Decoder.wrapper_bytes_value"
   | _ -> assert false
 
-let decode_field_expression field_type pk =
+let decode_field_expression field_type pk : string =
   match field_type with
   | Ot.Ft_user_defined_type t ->
     let f_name =
diff --git a/src/compilerlib/pb_codegen_decode_yojson.ml b/src/compilerlib/pb_codegen_decode_yojson.ml
index e011937..00c3a00 100644
--- a/src/compilerlib/pb_codegen_decode_yojson.ml
+++ b/src/compilerlib/pb_codegen_decode_yojson.ml
@@ -3,29 +3,27 @@ module F = Pb_codegen_formatting
 
 let sp = Pb_codegen_util.sp
 
-(* Function which returns all the possible pattern match for reading a JSON
+(** Function which returns all the possible pattern match for reading a JSON
    value into an OCaml value. The protobuf JSON encoding rules
    are defined here:
    https://developers.google.com/protocol-buffers/docs/proto3#json *)
 let field_pattern_match ~r_name ~rf_label field_type =
   match field_type with
   | Ot.Ft_basic_type bt ->
-    let decode runtime_f =
-      sp "Pbrt_yojson.%s json_value \"%s\" \"%s\"" runtime_f r_name rf_label
-    in
-    let exp =
+    let runtime_f =
       match bt with
-      | Ot.Bt_string -> decode "string"
-      | Ot.Bt_float -> decode "float"
-      | Ot.Bt_int -> decode "int"
-      | Ot.Bt_int32 -> decode "int32"
-      | Ot.Bt_int64 -> decode "int64"
-      | Ot.Bt_uint32 -> sp "`unsigned (%s)" (decode "int32")
-      | Ot.Bt_uint64 -> sp "`unsigned (%s)" (decode "int64")
-      | Ot.Bt_bool -> decode "bool"
-      | Ot.Bt_bytes -> decode "bytes"
+      | Ot.Bt_string -> "string"
+      | Ot.Bt_float -> "float"
+      | Ot.Bt_int -> "int"
+      | Ot.Bt_int32 -> "int32"
+      | Ot.Bt_int64 -> "int64"
+      | Ot.Bt_uint32 -> "[`unsigned of int32]"
+      | Ot.Bt_uint64 -> "[`unsigned of int64]"
+      | Ot.Bt_bool -> "bool"
+      | Ot.Bt_bytes -> "bytes"
     in
-    ("json_value", exp)
+    ( "json_value",
+      sp "Pbrt_yojson.%s json_value \"%s\" \"%s\"" runtime_f r_name rf_label )
   | Ot.Ft_unit ->
     "json_value", sp "Pbrt_yojson.unit json_value \"%s\" \"%s\"" r_name rf_label
   (* TODO Wrapper: add similar one for wrapper type (with different
@@ -99,49 +97,6 @@ let gen_rft_variant_field sc ~r_name ~rf_label { Ot.v_constructors; _ } =
         F.linep sc "  v.%s <- %s (%s)" rf_label vc_constructor exp)
     v_constructors
 
-let gen_rft_assoc_field sc ~r_name ~rf_label ~assoc_type ~key_type ~value_type =
-  let json_label = Pb_codegen_util.camel_case_of_label rf_label in
-  F.linep sc "| (\"%s\", `Assoc assoc) ->" json_label;
-  F.sub_scope sc (fun sc ->
-    let value_name, value_exp = field_pattern_match ~r_name ~rf_label value_type in
-    let key_name = "key" in
-    let key_exp =
-      match key_type with
-      | Ot.Bt_string -> "key"
-      | Ot.Bt_int -> "(Int.of_string key)"
-      | Ot.Bt_int32 -> "(Int32.of_string key)"
-      | Ot.Bt_int64 -> "(Int64.of_string key)"
-      | Ot.Bt_uint32 -> "(`unsigned (Int32.of_string key))"
-      | Ot.Bt_uint64 -> "(`unsigned (Int64.of_string key))"
-      | Ot.Bt_bool -> "(Bool.of_string key)"
-      | Ot.Bt_float ->
-        Printf.eprintf "float cannot be used as a map key type";
-        exit 1
-      | Ot.Bt_bytes ->
-        Printf.eprintf "bytes cannot be used as a map key type";
-        exit 1
-    in
-    F.line sc "let assoc =";
-    F.sub_scope sc (fun sc ->
-      F.line sc "assoc";
-      F.linep sc "|> List.map (fun (%s, %s) -> (%s, %s)) "
-        key_name
-        value_name
-        key_exp
-        value_exp;
-      F.line sc "|> List.to_seq";
-      (* Passing through [Hashtbl.of_seq] even in the [At_list] case ensures that if there
-         is a repeated key we take the last value associated with it. *)
-      F.line sc "|> Hashtbl.of_seq");
-    F.line sc "in";
-    let assoc_exp =
-      match assoc_type with
-      | Ot.At_hashtable -> "assoc"
-      | Ot.At_list -> "assoc |> Hashtbl.to_seq |> List.of_seq"
-    in
-    F.linep sc "v.%s <- %s" rf_label assoc_exp);
-;;
-
 (* Generate decode function for a record *)
 let gen_record ?and_ { Ot.r_name; r_fields } sc =
   let mutable_record_name = Pb_codegen_util.mutable_record_name r_name in
@@ -174,8 +129,9 @@ let gen_record ?and_ { Ot.r_name; r_fields } sc =
               | Ot.Rft_required _ ->
                 Printf.eprintf "Only proto3 syntax supported in JSON encoding";
                 exit 1
-              | Ot.Rft_associative (assoc_type, _, (key_type, _), (value_type, _)) ->
-                gen_rft_assoc_field sc ~r_name ~rf_label ~assoc_type ~key_type ~value_type)
+              | Ot.Rft_associative _ ->
+                Printf.eprintf "Map field are not currently supported for JSON";
+                exit 1)
             r_fields;
 
           (* Unknown fields are simply ignored *)
diff --git a/src/compilerlib/pb_codegen_decode_yojson.mli b/src/compilerlib/pb_codegen_decode_yojson.mli
index fcff7c2..3cd0c82 100644
--- a/src/compilerlib/pb_codegen_decode_yojson.mli
+++ b/src/compilerlib/pb_codegen_decode_yojson.mli
@@ -2,4 +2,11 @@
 
 include Pb_codegen_plugin.S
 
+val field_pattern_match :
+  r_name:string ->
+  rf_label:string ->
+  Pb_codegen_ocaml_type.field_type ->
+  string * string
+(** How to decode a field type *)
+
 val plugin : Pb_codegen_plugin.t
diff --git a/src/compilerlib/pb_codegen_default.mli b/src/compilerlib/pb_codegen_default.mli
index 67b71e8..8d20e08 100644
--- a/src/compilerlib/pb_codegen_default.mli
+++ b/src/compilerlib/pb_codegen_default.mli
@@ -2,5 +2,10 @@
 
 include Pb_codegen_plugin.S
 
+val record_field_default_info :
+  Pb_codegen_ocaml_type.record_field -> string * string * string
+(** This function returns [(field_name, field_default_value, field_type)] for
+   a record field. *)
+
 val gen_record_mutable :
   Pb_codegen_ocaml_type.record -> Pb_codegen_formatting.scope -> unit
diff --git a/src/compilerlib/pb_codegen_encode_yojson.ml b/src/compilerlib/pb_codegen_encode_yojson.ml
index a5e6474..d0f80fb 100644
--- a/src/compilerlib/pb_codegen_encode_yojson.ml
+++ b/src/compilerlib/pb_codegen_encode_yojson.ml
@@ -16,15 +16,9 @@ let runtime_function_for_basic_type json_label basic_type pk =
   (* Int32 *)
   | Ot.Bt_int32, Ot.Pk_varint _ | Ot.Bt_int32, Ot.Pk_bits32 ->
     "make_int", Some "Int32.to_int"
-  (* Uint32 *)
-  | Ot.Bt_uint32, Ot.Pk_varint _ | Ot.Bt_uint32, Ot.Pk_bits32 ->
-    "make_int", Some "(fun (`unsigned x) -> Int32.to_int x)"
   (* Int64 *)
   | Ot.Bt_int64, Ot.Pk_varint _ | Ot.Bt_int64, Ot.Pk_bits64 ->
     "make_string", Some "Int64.to_string"
-  (* Uint64 *)
-  | Ot.Bt_uint64, Ot.Pk_varint _ | Ot.Bt_uint64, Ot.Pk_bits64 ->
-    "make_string", Some "(fun (`unsigned x) -> Int64.to_string x)"
   (* 64 bit integer are always encoded as string since
      only support up to 51 bits integer. An improvement
      could be to check for value > 2^51 and use int *)
@@ -42,7 +36,7 @@ let runtime_function_for_basic_type json_label basic_type pk =
    return the runtime function name which accepts an option field
    and return the YoJson value (ie Null when value is None *)
 
-let gen_field var_name json_label field_type pk =
+let gen_field var_name json_label field_type pk : string option =
   match field_type, pk with
   | Ot.Ft_unit, _ -> None
   (* Basic types *)
@@ -148,73 +142,6 @@ let gen_rft_variant sc rf_label { Ot.v_constructors; _ } =
 
   F.linep sc "in (* match v.%s *)" rf_label
 
-let gen_rft_assoc
-      sc
-      ~rf_label
-      ~assoc_type
-      ~key_type
-      ~value_field:(value_type, value_pk)
-  =
-  let var_name = sp "v.%s" rf_label in
-  let json_label = Pb_codegen_util.camel_case_of_label rf_label in
-  let key_pat, key_exp =
-    match key_type with
-    | Ot.Bt_string -> "key", "key"
-    | Ot.Bt_int -> "key", "(Int.to_string key)"
-    | Ot.Bt_int32 -> "key", "(Int32.to_string key)"
-    | Ot.Bt_int64 -> "key", "(Int64.to_string key)"
-    | Ot.Bt_uint32 -> "(`unsigned key)", "(Int32.to_string key)"
-    | Ot.Bt_uint64 -> "(`unsigned key)", "(Int64.to_string key)"
-    | Ot.Bt_bool -> "key", "(Bool.to_string key)"
-    | Ot.Bt_float ->
-      Printf.eprintf "float cannot be used as a map key type";
-      exit 1
-    | Ot.Bt_bytes ->
-      Printf.eprintf "bytes cannot be used as a map key type";
-      exit 1
-  in
-  let write_assoc_field ~fn ~var_name =
-    F.line sc "let assoc_field =";
-    F.sub_scope sc (fun sc ->
-      F.linep sc "%s" var_name;
-      (match assoc_type with
-       | Ot.At_list -> ()
-       | Ot.At_hashtable ->
-         F.line sc "|> Hashtbl.to_seq |> List.of_seq");
-      F.linep sc "|> List.map (fun (%s, value) -> %s, %s value)" key_pat key_exp fn);
-    F.line sc "in";
-  in
-  F.line sc "let assoc =";
-  F.sub_scope sc (fun sc ->
-    (match value_type with
-     | Ot.Ft_unit -> unsupported json_label
-     | Ot.Ft_basic_type basic_type ->
-       let runtime_f, map_function =
-         runtime_function_for_basic_type json_label basic_type value_pk
-       in
-       (match map_function with
-        | None -> write_assoc_field ~fn:("Pbrt_yojson." ^ runtime_f) ~var_name
-        | Some map_function ->
-          let fn =
-            Printf.sprintf
-              "(fun value -> value |> %s |> Pbrt_yojson.%s)"
-              map_function
-              runtime_f
-          in
-          write_assoc_field ~fn ~var_name)
-     (* TODO Wrapper: add similar case for Ft_wrapper_type *)
-     (* User defined *)
-     | Ot.Ft_user_defined_type udt ->
-       let fn =
-         let function_prefix = "encode_json" in
-         Pb_codegen_util.function_name_of_user_defined ~function_prefix udt
-       in
-       write_assoc_field ~fn ~var_name;
-     | _ -> unsupported json_label);
-    F.linep sc "(\"%s\", `Assoc assoc_field) :: assoc " json_label);
-  F.line sc "in"
-;;
-
 let gen_record ?and_ { Ot.r_name; r_fields } sc =
   let rn = r_name in
   F.linep sc "%s encode_json_%s (v:%s) = "
@@ -238,8 +165,9 @@ let gen_record ?and_ { Ot.r_name; r_fields } sc =
           | Ot.Rft_required _ ->
             Printf.eprintf "Only proto3 syntax supported in JSON encoding";
             exit 1
-          | Ot.Rft_associative (assoc_type, _, (key_type, _), value_field) ->
-            gen_rft_assoc sc ~rf_label ~assoc_type ~key_type ~value_field)
+          | Ot.Rft_associative _ ->
+            Printf.eprintf "Map field are not currently supported for JSON";
+            exit 1)
         r_fields (* List.iter *);
       F.line sc "`Assoc assoc")
 
diff --git a/src/compilerlib/pb_codegen_make.ml b/src/compilerlib/pb_codegen_make.ml
new file mode 100644
index 0000000..1744787
--- /dev/null
+++ b/src/compilerlib/pb_codegen_make.ml
@@ -0,0 +1,90 @@
+module Ot = Pb_codegen_ocaml_type
+module F = Pb_codegen_formatting
+open Pb_codegen_util
+
+(** Is this field optional enough that we give it a default value? *)
+let field_is_optional (r_field : Ot.record_field) : bool =
+  match r_field.rf_field_type with
+  | Rft_optional _ -> true
+  | _ -> false
+
+(** Obtain information about the fields *)
+let fields_of_record { Ot.r_fields; _ } :
+    (string * string * [ `Optional of _ | `Required ]) list =
+  List.map
+    (fun r_field ->
+      let fname, fdefault, ftype =
+        Pb_codegen_default.record_field_default_info r_field
+      in
+      if field_is_optional r_field then
+        fname, ftype, `Optional fdefault
+      else
+        fname, ftype, `Required)
+    r_fields
+
+let gen_record ?and_ ({ Ot.r_name; _ } as r) sc : unit =
+  let fields = fields_of_record r in
+
+  F.linep sc "%s make_%s " (let_decl_of_and and_) r_name;
+
+  F.sub_scope sc (fun sc ->
+      List.iter
+        (fun (fname, ftype, d) ->
+          match d with
+          | `Required -> F.linep sc "~(%s:%s)" fname ftype
+          | `Optional fvalue ->
+            F.linep sc "?%s:((%s:%s) = %s)" fname fname ftype fvalue)
+        fields;
+      F.linep sc "() : %s  = {" r_name);
+
+  F.sub_scope sc (fun sc ->
+      List.iter (fun (fname, _, _) -> F.linep sc "%s;" fname) fields);
+
+  F.line sc "}"
+
+let gen_struct ?and_ t sc =
+  let { Ot.spec; _ } = t in
+
+  let has_encoded =
+    match spec with
+    | Ot.Record r ->
+      gen_record ?and_ r sc;
+      true
+    | Ot.Const_variant _ | Ot.Variant _ | Ot.Unit _ ->
+      (* nothing for variants *)
+      false
+  in
+  has_encoded
+
+let gen_sig_record sc ({ Ot.r_name; _ } as r) =
+  F.linep sc "val make_%s : " r_name;
+
+  let fields : _ list = fields_of_record r in
+
+  F.sub_scope sc (fun sc ->
+      List.iter
+        (fun (field_name, field_type, d) ->
+          match d with
+          | `Optional _ -> F.linep sc "?%s:%s ->" field_name field_type
+          | `Required -> F.linep sc "%s:%s ->" field_name field_type)
+        fields;
+      F.line sc "unit ->";
+      F.line sc r_name);
+  let rn = r_name in
+  F.linep sc "(** [make_%s … ()] is a builder for type [%s] *)" rn rn
+
+let gen_sig ?and_:_ t sc =
+  let { Ot.spec; _ } = t in
+
+  let has_encoded =
+    match spec with
+    | Ot.Record r ->
+      gen_sig_record sc r;
+      true
+    | Ot.Variant _ | Ot.Const_variant _ | Ot.Unit _ -> false
+  in
+
+  has_encoded
+
+let ocamldoc_title = "Make functions"
+let requires_mutable_records = false
diff --git a/src/compilerlib/pb_codegen_make.mli b/src/compilerlib/pb_codegen_make.mli
new file mode 100644
index 0000000..573153b
--- /dev/null
+++ b/src/compilerlib/pb_codegen_make.mli
@@ -0,0 +1,3 @@
+(** Code generator for the [make] functions (i.e builders, but stricter than [default]) *)
+
+include Pb_codegen_plugin.S
diff --git a/src/compilerlib/pb_codegen_ocaml_type.ml b/src/compilerlib/pb_codegen_ocaml_type.ml
index de7edec..6d88397 100644
--- a/src/compilerlib/pb_codegen_ocaml_type.ml
+++ b/src/compilerlib/pb_codegen_ocaml_type.ml
@@ -65,10 +65,10 @@ type field_type =
   | Ft_unit
   | Ft_basic_type of basic_type
   | Ft_user_defined_type of user_defined_type
-  (* New wrapper type which indicates that the corresponding ocaml
+  | Ft_wrapper_type of wrapper_type
+      (** New wrapper type which indicates that the corresponding ocaml
      Type should be an `option` along with the fact that it is encoded with
      special rules *)
-  | Ft_wrapper_type of wrapper_type
 
 type default_value = Pb_option.constant option
 
@@ -87,7 +87,7 @@ type is_packed = bool
 
 type record_field_type =
   | Rft_nolabel of (field_type * encoding_number * payload_kind)
-    (* no default values in proto3 no label fields *)
+      (** no default values in proto3 no label fields *)
   | Rft_required of
       (field_type * encoding_number * payload_kind * default_value)
   | Rft_optional of
@@ -155,3 +155,29 @@ type type_ = {
   spec: type_spec;
   type_level_ppx_extension: string option;
 }
+
+(** RPC argument or return type. We require message types in RPC. *)
+type rpc_type =
+  | Rpc_scalar of field_type
+  | Rpc_stream of field_type
+
+type rpc = {
+  rpc_name: string;
+  rpc_req: rpc_type;
+  rpc_res: rpc_type;
+}
+(** A RPC specification, ie the signature for one remote procedure. *)
+
+type service = {
+  service_name: string;
+  service_packages: string list;  (** Package in which this belongs *)
+  service_body: rpc list;
+}
+(** A service, composed of multiple RPCs. *)
+
+type proto = {
+  proto_types: type_ list list;
+      (** List of strongly connected type definitions *)
+  proto_services: service list;
+}
+(** A proto file is composed of a list of types and a list of services. *)
diff --git a/src/compilerlib/pb_codegen_services.ml b/src/compilerlib/pb_codegen_services.ml
new file mode 100644
index 0000000..1979eaf
--- /dev/null
+++ b/src/compilerlib/pb_codegen_services.ml
@@ -0,0 +1,239 @@
+module Ot = Pb_codegen_ocaml_type
+module F = Pb_codegen_formatting
+
+let spf = Printf.sprintf
+
+let ocaml_type_of_rpc_type (rpc : Ot.rpc_type) : string * string =
+  match rpc with
+  | Rpc_scalar ty -> Pb_codegen_util.string_of_field_type ty, "unary"
+  | Rpc_stream ty -> Pb_codegen_util.string_of_field_type ty, "stream"
+
+let string_of_server_rpc (req : Ot.rpc_type) (res : Ot.rpc_type) : string =
+  let req, req_mode = ocaml_type_of_rpc_type req in
+  let res, res_mode = ocaml_type_of_rpc_type res in
+  spf "(%s, %s, %s, %s) Server.rpc" req req_mode res res_mode
+
+let function_name_encode_json ~service_name ~rpc_name (ty : Ot.rpc_type) :
+    string =
+  let f ty =
+    match ty with
+    | Ot.Ft_unit -> "(fun () -> `Assoc [])"
+    | Ot.Ft_user_defined_type udt ->
+      let function_prefix = "encode_json" in
+      Pb_codegen_util.function_name_of_user_defined ~function_prefix udt
+    | _ ->
+      Printf.eprintf "cannot json-encode request for %s in service %s\n%!"
+        rpc_name service_name;
+      exit 1
+  in
+  match ty with
+  | Ot.Rpc_scalar ty | Ot.Rpc_stream ty -> f ty
+
+let function_name_decode_json ~service_name ~rpc_name (ty : Ot.rpc_type) :
+    string =
+  let f ty =
+    match ty with
+    | Ot.Ft_unit -> "(fun _ -> ())"
+    | Ot.Ft_user_defined_type udt ->
+      let function_prefix = "decode_json" in
+      Pb_codegen_util.function_name_of_user_defined ~function_prefix udt
+    | _ ->
+      Printf.eprintf "cannot decode json request for %s in service %s\n%!"
+        rpc_name service_name;
+      exit 1
+  in
+  match ty with
+  | Ot.Rpc_scalar ty | Ot.Rpc_stream ty -> f ty
+
+let function_name_encode_pb ~service_name ~rpc_name (ty : Ot.rpc_type) : string
+    =
+  let f ty =
+    match ty with
+    | Ot.Ft_unit -> "(fun () enc -> Pbrt.Encoder.empty_nested enc)"
+    | Ot.Ft_user_defined_type udt ->
+      let function_prefix = "encode_pb" in
+      Pb_codegen_util.function_name_of_user_defined ~function_prefix udt
+    | _ ->
+      Printf.eprintf "cannot binary-encode request for %s in service %s\n%!"
+        rpc_name service_name;
+      exit 1
+  in
+  match ty with
+  | Ot.Rpc_scalar ty | Ot.Rpc_stream ty -> f ty
+
+let function_name_decode_pb ~service_name ~rpc_name (ty : Ot.rpc_type) : string
+    =
+  let f ty =
+    match ty with
+    | Ot.Ft_unit -> "(fun d -> Pbrt.Decoder.empty_nested d)"
+    | Ot.Ft_user_defined_type udt ->
+      let function_prefix = "decode_pb" in
+      Pb_codegen_util.function_name_of_user_defined ~function_prefix udt
+    | _ ->
+      Printf.eprintf "cannot decode binary request for %s in service %s\n%!"
+        rpc_name service_name;
+      exit 1
+  in
+  match ty with
+  | Ot.Rpc_scalar ty | Ot.Rpc_stream ty -> f ty
+
+let mod_name_for_client (service : Ot.service) : string =
+  String.capitalize_ascii service.service_name
+
+let string_list_of_package (path : string list) : string =
+  spf "[%s]" (String.concat ";" @@ List.map (fun s -> spf "%S" s) path)
+
+let gen_service_client_struct (service : Ot.service) sc : unit =
+  let service_name = service.service_name in
+  F.line sc "module Client = struct";
+  let gen_rpc sc (rpc : Ot.rpc) =
+    let rpc_name = rpc.rpc_name in
+    let req, req_mode = ocaml_type_of_rpc_type rpc.rpc_req in
+    let req_mode_witness = String.capitalize_ascii req_mode in
+    let res, res_mode = ocaml_type_of_rpc_type rpc.rpc_res in
+    let res_mode_witness = String.capitalize_ascii res_mode in
+    F.empty_line sc;
+    F.linep sc "let %s : (%s, %s, %s, %s) Client.rpc ="
+      (Pb_codegen_util.function_name_of_rpc rpc)
+      req req_mode res res_mode;
+    F.linep sc "  (Client.mk_rpc ";
+    F.linep sc "    ~package:%s"
+      (string_list_of_package service.service_packages);
+    F.linep sc "    ~service_name:%S ~rpc_name:%S" service.service_name
+      rpc.rpc_name;
+    F.linep sc "    ~req_mode:Client.%s" req_mode_witness;
+    F.linep sc "    ~res_mode:Client.%s" res_mode_witness;
+    F.linep sc "    ~encode_json_req:%s"
+      (function_name_encode_json ~service_name ~rpc_name rpc.rpc_req);
+    F.linep sc "    ~encode_pb_req:%s"
+      (function_name_encode_pb ~service_name ~rpc_name rpc.rpc_req);
+    F.linep sc "    ~decode_json_res:%s"
+      (function_name_decode_json ~service_name ~rpc_name rpc.rpc_res);
+    F.linep sc "    ~decode_pb_res:%s"
+      (function_name_decode_pb ~service_name ~rpc_name rpc.rpc_res);
+    let req, req_mode = ocaml_type_of_rpc_type rpc.rpc_req in
+    let res, res_mode = ocaml_type_of_rpc_type rpc.rpc_res in
+    F.linep sc "    () : (%s, %s, %s, %s) Client.rpc)" req req_mode res res_mode
+  in
+  F.sub_scope sc (fun sc ->
+      F.linep sc "open Pbrt_services";
+      List.iter (gen_rpc sc) service.service_body);
+  F.line sc "end"
+
+let gen_service_server_struct (service : Ot.service) sc : unit =
+  let service_name = service.service_name in
+
+  (* generate rpc descriptions for the server side *)
+  let gen_rpc sc (rpc : Ot.rpc) =
+    F.empty_line sc;
+    let rpc_name = rpc.rpc_name in
+    let name = Pb_codegen_util.function_name_of_rpc rpc in
+    let req, req_mode = ocaml_type_of_rpc_type rpc.rpc_req in
+    let res, res_mode = ocaml_type_of_rpc_type rpc.rpc_res in
+    let req_mode_witness = String.capitalize_ascii req_mode in
+    let res_mode_witness = String.capitalize_ascii res_mode in
+
+    F.linep sc "let _rpc_%s : (%s,%s,%s,%s) Server.rpc = " name req req_mode res
+      res_mode;
+    F.linep sc "  (Server.mk_rpc ~name:%S" rpc.rpc_name;
+    F.linep sc "    ~req_mode:Server.%s" req_mode_witness;
+    F.linep sc "    ~res_mode:Server.%s" res_mode_witness;
+    F.linep sc "    ~encode_json_res:%s"
+      (function_name_encode_json ~service_name ~rpc_name rpc.rpc_res);
+    F.linep sc "    ~encode_pb_res:%s"
+      (function_name_encode_pb ~service_name ~rpc_name rpc.rpc_res);
+    F.linep sc "    ~decode_json_req:%s"
+      (function_name_decode_json ~service_name ~rpc_name rpc.rpc_req);
+    F.linep sc "    ~decode_pb_req:%s"
+      (function_name_decode_pb ~service_name ~rpc_name rpc.rpc_req);
+    F.linep sc "    () : _ Server.rpc)"
+  in
+
+  let gen_server sc =
+    F.line sc "open Pbrt_services";
+    List.iter (gen_rpc sc) service.service_body;
+
+    (* now generate a function from the module type to a [Service_server.t] *)
+    F.empty_line sc;
+    F.linep sc "let make";
+    List.iter
+      (fun (rpc : Ot.rpc) ->
+        let name = Pb_codegen_util.function_name_of_rpc rpc in
+        F.linep sc "  ~%s" name)
+      service.service_body;
+    F.line sc "  () : _ Server.t =";
+    F.linep sc "  { Server.";
+    F.linep sc "    service_name=%S;" service_name;
+    F.linep sc "    package=%s;"
+      (string_list_of_package service.service_packages);
+    F.line sc "    handlers=[";
+    List.iter
+      (fun (rpc : Ot.rpc) ->
+        let f = Pb_codegen_util.function_name_of_rpc rpc in
+        F.linep sc "       (%s %s);" f (spf "_rpc_%s" f))
+      service.service_body;
+    F.line sc "    ];";
+    F.line sc "  }"
+  in
+
+  F.empty_line sc;
+  F.line sc "module Server = struct";
+  F.sub_scope sc gen_server;
+  F.line sc "end";
+  F.empty_line sc
+
+let gen_service_struct (service : Ot.service) sc : unit =
+  F.linep sc "module %s = struct" (mod_name_for_client service);
+  F.sub_scope sc (fun sc ->
+      F.linep sc "open Pbrt_services.Value_mode";
+
+      gen_service_client_struct service sc;
+
+      (* now the server side *)
+      gen_service_server_struct service sc);
+
+  F.line sc "end";
+  F.empty_line sc
+
+let gen_service_sig (service : Ot.service) sc : unit =
+  F.linep sc "(** %s service *)" service.service_name;
+  F.linep sc "module %s : sig" (mod_name_for_client service);
+  F.sub_scope sc (fun sc ->
+      F.linep sc "open Pbrt_services";
+      F.linep sc "open Pbrt_services.Value_mode";
+
+      (* client *)
+      let gen_client_rpc sc (rpc : Ot.rpc) =
+        F.empty_line sc;
+        let req, req_mode = ocaml_type_of_rpc_type rpc.rpc_req in
+        let res, res_mode = ocaml_type_of_rpc_type rpc.rpc_res in
+        F.linep sc "val %s : (%s, %s, %s, %s) Client.rpc"
+          (Pb_codegen_util.function_name_of_rpc rpc)
+          req req_mode res res_mode
+      in
+
+      F.empty_line sc;
+      F.line sc "module Client : sig";
+      F.sub_scope sc (fun sc ->
+          List.iter (gen_client_rpc sc) service.service_body);
+      F.line sc "end";
+
+      (* server *)
+      F.empty_line sc;
+      F.line sc "module Server : sig";
+      F.sub_scope sc (fun sc ->
+          F.line sc "(** Produce a server implementation from handlers *)";
+          F.linep sc "val make : ";
+          List.iter
+            (fun (rpc : Ot.rpc) ->
+              F.linep sc "  %s:(%s -> 'handler) ->"
+                (Pb_codegen_util.function_name_of_rpc rpc)
+                (string_of_server_rpc rpc.rpc_req rpc.rpc_res))
+            service.service_body;
+          F.linep sc "  unit -> 'handler Pbrt_services.Server.t");
+      F.line sc "end";
+
+      ());
+
+  F.line sc "end";
+  F.empty_line sc
diff --git a/src/compilerlib/pb_codegen_services.mli b/src/compilerlib/pb_codegen_services.mli
new file mode 100644
index 0000000..997b4c5
--- /dev/null
+++ b/src/compilerlib/pb_codegen_services.mli
@@ -0,0 +1,5 @@
+val gen_service_sig :
+  Pb_codegen_ocaml_type.service -> Pb_codegen_formatting.scope -> unit
+
+val gen_service_struct :
+  Pb_codegen_ocaml_type.service -> Pb_codegen_formatting.scope -> unit
diff --git a/src/compilerlib/pb_codegen_types.mli b/src/compilerlib/pb_codegen_types.mli
index f191201..5c748b1 100644
--- a/src/compilerlib/pb_codegen_types.mli
+++ b/src/compilerlib/pb_codegen_types.mli
@@ -1,4 +1,4 @@
-(** Code generator for the OCaml type *)
+(** Code generator for the OCaml types *)
 
 include Pb_codegen_plugin.S
 
diff --git a/src/compilerlib/pb_codegen_util.ml b/src/compilerlib/pb_codegen_util.ml
index d11a318..79c8752 100644
--- a/src/compilerlib/pb_codegen_util.ml
+++ b/src/compilerlib/pb_codegen_util.ml
@@ -84,6 +84,14 @@ let function_name_of_user_defined ~function_prefix = function
   | { Ot.udt_module_prefix = None; Ot.udt_type_name; _ } ->
     sp "%s_%s" function_prefix udt_type_name
 
+let module_type_name_of_service_client (service : Ot.service) : string =
+  String.uppercase_ascii service.service_name ^ "_CLIENT"
+
+let module_type_name_of_service_server (service : Ot.service) : string =
+  String.uppercase_ascii service.service_name ^ "_SERVER"
+
+let function_name_of_rpc (rpc : Ot.rpc) = String.uncapitalize_ascii rpc.rpc_name
+
 let caml_file_name_of_proto_file_name ~proto_file_name =
   let splitted = Pb_util.rev_split_by_char '.' proto_file_name in
   if List.length splitted < 2 || List.hd splitted <> "proto" then
diff --git a/src/compilerlib/pb_codegen_util.mli b/src/compilerlib/pb_codegen_util.mli
index fa52b10..97fcaf9 100644
--- a/src/compilerlib/pb_codegen_util.mli
+++ b/src/compilerlib/pb_codegen_util.mli
@@ -30,6 +30,15 @@ val function_name_of_user_defined :
     user defined field type.
  *)
 
+val module_type_name_of_service_client : Pb_codegen_ocaml_type.service -> string
+(** Name of the module type for this service (client) *)
+
+val module_type_name_of_service_server : Pb_codegen_ocaml_type.service -> string
+(** Name of the module type for this service (server) *)
+
+val function_name_of_rpc : Pb_codegen_ocaml_type.rpc -> string
+(** Name of the function for this RPC *)
+
 val caml_file_name_of_proto_file_name : proto_file_name:string -> string
 (** [caml_file_name_of_proto_file_name filename] returns the OCaml file name from
     the protobuf file name
diff --git a/src/compilerlib/pb_exception.ml b/src/compilerlib/pb_exception.ml
index 8cf2de4..29255d3 100644
--- a/src/compilerlib/pb_exception.ml
+++ b/src/compilerlib/pb_exception.ml
@@ -92,6 +92,8 @@ type error =
   | Invalid_first_enum_value_proto3 of string * string option
   | Invalid_key_type_for_map of string
   | Unsupported_wrapper_type of string
+  | Invalid_rpc_req_type of string * string
+  | Invalid_rpc_res_type of string * string
 
 exception Compilation_error of error
 (** Exception raised when a compilation error occurs *)
@@ -158,6 +160,16 @@ let string_of_error = function
       ("Invalid field label for field: %s in message: %s. "
      ^^ "[Required|Optional] are not supported.")
       field_name message_name
+  | Invalid_rpc_req_type (service_name, rpc_name) ->
+    P.sprintf
+      ("Invalid type for RPC request: %s in service: %s. "
+     ^^ "The type must be user-defined..")
+      service_name rpc_name
+  | Invalid_rpc_res_type (service_name, rpc_name) ->
+    P.sprintf
+      ("Invalid type for RPC response: %s in service: %s. "
+     ^^ "The type must be user-defined..")
+      service_name rpc_name
   | Default_field_option_not_supported (field_name, message_name) ->
     P.sprintf
       ("Explicit default values are not allowed in proto3. "
@@ -252,6 +264,12 @@ let invalid_proto3_field_label ~field_name ~message_name =
   raise
     (Compilation_error (Invalid_proto3_field_label (field_name, message_name)))
 
+let invalid_rpc_req_type ~service_name ~rpc_name () =
+  raise (Compilation_error (Invalid_rpc_req_type (service_name, rpc_name)))
+
+let invalid_rpc_res_type ~service_name ~rpc_name () =
+  raise (Compilation_error (Invalid_rpc_res_type (service_name, rpc_name)))
+
 let default_field_option_not_supported ~field_name ~message_name =
   raise
     (Compilation_error
diff --git a/src/compilerlib/pb_exception.mli b/src/compilerlib/pb_exception.mli
index 1faa82c..50c755c 100644
--- a/src/compilerlib/pb_exception.mli
+++ b/src/compilerlib/pb_exception.mli
@@ -69,6 +69,8 @@ val protoc_parsing_error : error -> Pb_location.t -> string -> 'a
 val unknown_parsing_error : msg:string -> context:string -> Pb_location.t -> 'a
 val invalid_protobuf_syntax : string -> 'a
 val invalid_proto3_field_label : field_name:string -> message_name:string -> 'a
+val invalid_rpc_req_type : service_name:string -> rpc_name:string -> unit -> 'a
+val invalid_rpc_res_type : service_name:string -> rpc_name:string -> unit -> 'a
 
 val default_field_option_not_supported :
   field_name:string -> message_name:string -> 'a
diff --git a/src/compilerlib/pb_typing.ml b/src/compilerlib/pb_typing.ml
index c572574..f185b27 100644
--- a/src/compilerlib/pb_typing.ml
+++ b/src/compilerlib/pb_typing.ml
@@ -23,16 +23,23 @@
 
 *)
 
+module Pt = Pb_parsing_parse_tree
 module Tt = Pb_typing_type_tree
 
-let perform_typing protos =
-  let typed_protos =
+let perform_typing (protos : Pt.proto list) : Pb_field_type.resolved Tt.proto =
+  let validated_types, validated_services =
     List.fold_left
-      (fun typed_protos proto ->
-        typed_protos @ Pb_typing_validation.validate proto)
-      [] protos
+      (fun (typed_protos, services) proto ->
+        let val_proto = Pb_typing_validation.validate proto in
+        ( typed_protos @ List.flatten val_proto.proto_types,
+          services @ val_proto.proto_services ))
+      ([], []) protos
   in
 
-  let typed_protos = Pb_typing_resolution.resolve_types typed_protos in
+  let t, types = Pb_typing_resolution.resolve_types validated_types in
+  let services = Pb_typing_resolution.resolve_services t validated_services in
 
-  List.rev @@ Pb_typing_recursion.group typed_protos
+  {
+    Tt.proto_types = List.rev @@ Pb_typing_recursion.group types;
+    proto_services = services;
+  }
diff --git a/src/compilerlib/pb_typing.mli b/src/compilerlib/pb_typing.mli
index a84db4a..8bec414 100644
--- a/src/compilerlib/pb_typing.mli
+++ b/src/compilerlib/pb_typing.mli
@@ -37,8 +37,7 @@
 module Tt = Pb_typing_type_tree
 
 val perform_typing :
-  Pb_parsing_parse_tree.proto list ->
-  Pb_field_type.resolved Tt.proto_type list list
+  Pb_parsing_parse_tree.proto list -> Pb_field_type.resolved Tt.proto
 (** [perform_typing parsed_tree] returned the type tree organized in groups
     of fully resolved types. Each group contains all the mutually recursive
     types and the type group by reverse dependency order. *)
diff --git a/src/compilerlib/pb_typing_recursion.mli b/src/compilerlib/pb_typing_recursion.mli
index 6e1891e..8afbdd5 100644
--- a/src/compilerlib/pb_typing_recursion.mli
+++ b/src/compilerlib/pb_typing_recursion.mli
@@ -39,7 +39,8 @@
 module Tt = Pb_typing_type_tree
 
 val group :
-  Pb_field_type.resolved Tt.proto -> Pb_field_type.resolved Tt.proto list
+  Pb_field_type.resolved Tt.proto_type list ->
+  Pb_field_type.resolved Tt.proto_type list list
 (** [group types] returns the list of all the mutually recursive group
       of types in reverse order of dependency. In other the last group of
       types of the returned list don't depend on any other types. *)
diff --git a/src/compilerlib/pb_typing_resolution.ml b/src/compilerlib/pb_typing_resolution.ml
index 370460e..85fb75b 100644
--- a/src/compilerlib/pb_typing_resolution.ml
+++ b/src/compilerlib/pb_typing_resolution.ml
@@ -89,11 +89,11 @@ end
 (* Types_by_scope *)
 
 (* this function returns the type path of a message which is the
- * packages followed by the enclosing message names and eventually
- * the message name of the given type.
- *
- * If the type is an enum then [Failure] is raised.
- * TODO: change [Failure] to a [Pb_exception.Compilation_error] *)
+   packages followed by the enclosing message names and eventually
+   the message name of the given type.
+
+   If the type is an enum then [Failure] is raised.
+   TODO: change [Failure] to a [Pb_exception.Compilation_error] *)
 let type_path_of_type { Tt.scope; spec; _ } =
   match spec with
   | Tt.Enum _ -> assert false
@@ -102,15 +102,15 @@ let type_path_of_type { Tt.scope; spec; _ } =
     packages @ message_names @ [ message_name ]
 
 (* this function returns all the scope to search for a type starting
- * by the most innner one first.
- *
- * If [message_scope] = ['Msg1'; 'Msg2'] and [field_scope] = ['Msg3'] then
- * the following scopes will be returned:
- * [
- *   ['Msg1'; 'Msg2'; 'Msg3'];  // This would be the scope of the current msg
- *   ['Msg1'; 'Msg3'; ];        // Outer message scope
- *   ['Msg3'; ]                 // Top level scope
- * ] *)
+   by the most innner one first.
+
+   If [message_scope] = ['Msg1'; 'Msg2'] and [field_scope] = ['Msg3'] then
+   the following scopes will be returned:
+   [
+     ['Msg1'; 'Msg2'; 'Msg3'];  // This would be the scope of the current msg
+     ['Msg1'; 'Msg3'; ];        // Outer message scope
+     ['Msg3'; ]                 // Top level scope
+   ] *)
 let compute_search_type_paths unresolved_field_type message_type_path =
   let { Pb_field_type.type_path; type_name = _; from_root } =
     unresolved_field_type
@@ -126,14 +126,14 @@ let compute_search_type_paths unresolved_field_type message_type_path =
     List.rev @@ loop [] message_type_path
   )
 
-(* this function ensure that the default value of the field is correct
- * with respect to its type when this latter is a builtin one.
- *
- * in case the default value is invalid then an
- * [Pb_exception.Compilation_error] is raised.
- *
- * Note that this function also does type coersion when the default value
- * is an int and the builtin type is a float or double. *)
+(** this function ensure that the default value of the field is correct
+   with respect to its type when this latter is a builtin one.
+
+   in case the default value is invalid then an
+   [Pb_exception.Compilation_error] is raised.
+
+   Note that this function also does type coersion when the default value
+   is an int and the builtin type is a float or double. *)
 let resolve_builtin_type_field_default field_name builtin_type field_default =
   match field_default with
   | None -> None
@@ -180,16 +180,16 @@ let resolve_builtin_type_field_default field_name builtin_type field_default =
       E.invalid_default_value ~field_name
         ~info:"default value not supported for bytes" ())
 
-(* This function verifies that the default value for a used defined
- * field is correct.
- *
- * In protobuf, only field which type is [enum] can have a default
- * value. Field of type [message] can't.
- *
- * In the case the field is an enum then the default value must be
- * a litteral value which is one of the enum value.
- *
- * If the validation fails then [Pb_exception.Compilation_error] is raised *)
+(** This function verifies that the default value for a used defined
+   field is correct.
+
+   In protobuf, only field which type is [enum] can have a default
+   value. Field of type [message] can't.
+
+   In the case the field is an enum then the default value must be
+   a litteral value which is one of the enum value.
+
+   If the validation fails then [Pb_exception.Compilation_error] is raised *)
 let resolve_enum_field_default field_name type_ field_default =
   match field_default with
   | None -> None
@@ -221,14 +221,32 @@ let resolve_enum_field_default field_name type_ field_default =
     E.invalid_default_value ~field_name
       ~info:"default value not supported for message" ()
 
-(* this function resolves both the type and the defaut value of a field
- * type. Note that it is necessary to verify both at the same time since
- * the default value must be of the same type as the field type in order
- * to be valid.
- *
- * For builtin the type the validation is trivial while for user defined
- * type a search must be done for all the possible scopes the type
- * might be in. *)
+let resolve_user_defined_type t field_name
+    (unresolved_field_type : Pb_field_type.unresolved) field_default
+    message_type_path : int * _ =
+  let { Pb_field_type.type_name; _ } = unresolved_field_type in
+  let rec aux = function
+    | [] -> raise Not_found
+    | type_path :: tl ->
+      (match Types_by_scope.find t type_path type_name with
+      | type_ ->
+        let id = type_.Tt.id in
+        let field_default =
+          resolve_enum_field_default field_name type_ field_default
+        in
+        id, field_default
+      | exception Not_found -> aux tl)
+  in
+  aux (compute_search_type_paths unresolved_field_type message_type_path)
+
+(** this function resolves both the type and the defaut value of a field
+   type. Note that it is necessary to verify both at the same time since
+   the default value must be of the same type as the field type in order
+   to be valid.
+
+   For builtin the type the validation is trivial while for user defined
+   type a search must be done for all the possible scopes the type
+   might be in. *)
 let resolve_field_type_and_default t field_name field_type field_default
     message_type_path =
   match field_type with
@@ -238,20 +256,11 @@ let resolve_field_type_and_default t field_name field_type field_default
     in
     builtin_type, field_default
   | `User_defined unresolved_field_type ->
-    let { Pb_field_type.type_name; _ } = unresolved_field_type in
-    let rec aux = function
-      | [] -> raise Not_found
-      | type_path :: tl ->
-        (match Types_by_scope.find t type_path type_name with
-        | type_ ->
-          let id = type_.Tt.id in
-          let field_default =
-            resolve_enum_field_default field_name type_ field_default
-          in
-          `User_defined id, field_default
-        | exception Not_found -> aux tl)
+    let id, default =
+      resolve_user_defined_type t field_name unresolved_field_type field_default
+        message_type_path
     in
-    aux (compute_search_type_paths unresolved_field_type message_type_path)
+    `User_defined id, default
 
 (** this function resolves all the field type of the given type *)
 let resolve_type t type_ : int Tt.proto_type =
@@ -327,6 +336,35 @@ let resolve_type t type_ : int Tt.proto_type =
     in
     { Tt.scope; id; file_name; file_options; spec }
 
-let resolve_types types =
+let resolve_types types : Types_by_scope.t * _ list =
   let t = List.fold_left Types_by_scope.add Types_by_scope.empty types in
-  List.map (resolve_type t) types
+  t, List.map (resolve_type t) types
+
+let resolve_service t (service : _ Tt.service) : _ Tt.service =
+  let resolve_ty ~rpc_name ~name ty : Pb_field_type.resolved =
+    let rpc_type, _field_default =
+      let do_resolve () =
+        resolve_user_defined_type t name ty None service.service_packages
+      in
+      match do_resolve () with
+      | ret -> ret
+      | exception Not_found ->
+        E.unresolved_type ~field_name:name ~type_:"" ~message_name:rpc_name ()
+    in
+    rpc_type
+  in
+
+  let resolve_rpc (rpc : _ Tt.rpc) : _ Tt.rpc =
+    let rpc_name = rpc.rpc_name in
+    {
+      rpc with
+      Tt.rpc_req = resolve_ty ~rpc_name ~name:"req" rpc.rpc_req;
+      rpc_res = resolve_ty ~rpc_name ~name:"res" rpc.rpc_res;
+    }
+  in
+
+  { service with Tt.service_body = List.map resolve_rpc service.service_body }
+
+let resolve_services (t : Types_by_scope.t) (services : _ Tt.service list) :
+    _ Tt.service list =
+  List.map (resolve_service t) services
diff --git a/src/compilerlib/pb_typing_resolution.mli b/src/compilerlib/pb_typing_resolution.mli
index ca292a3..17e5f79 100644
--- a/src/compilerlib/pb_typing_resolution.mli
+++ b/src/compilerlib/pb_typing_resolution.mli
@@ -69,7 +69,12 @@ end
 
 val resolve_types :
   Pb_field_type.unresolved Tt.proto_type list ->
-  Pb_field_type.resolved Tt.proto_type list
+  Types_by_scope.t * Pb_field_type.resolved Tt.proto_type list
 (** [resolve_types types] resolves all the field types for all the [types].
     If a field cannot be resolved then [Pb_exception.Compilation_error] is
     raised. *)
+
+val resolve_services :
+  Types_by_scope.t ->
+  Pb_field_type.unresolved Tt.service list ->
+  Pb_field_type.resolved Tt.service list
diff --git a/src/compilerlib/pb_typing_type_tree.ml b/src/compilerlib/pb_typing_type_tree.ml
index 97cd4ed..000cb29 100644
--- a/src/compilerlib/pb_typing_type_tree.ml
+++ b/src/compilerlib/pb_typing_type_tree.ml
@@ -108,4 +108,27 @@ type 'a proto_type = {
   spec: 'a proto_type_spec;
 }
 
-type 'a proto = 'a proto_type list
+type 'a rpc = {
+  rpc_name: string;
+  rpc_options: Pb_option.set;
+  rpc_req_stream: bool;
+  rpc_req: 'a;
+  rpc_res_stream: bool;
+  rpc_res: 'a;
+}
+(** A RPC specification. *)
+
+type 'a service = {
+  service_name: string;
+  service_file_name: string;
+  service_packages: string list;  (** Package in which this belongs *)
+  service_body: 'a rpc list;
+}
+(** A service, composed of multiple RPCs. *)
+
+type 'a proto = {
+  proto_types: 'a proto_type list list;
+      (** List of strongly connected type definitions *)
+  proto_services: 'a service list;
+}
+(** A proto file is composed of a list of types and a list of services. *)
diff --git a/src/compilerlib/pb_typing_util.mli b/src/compilerlib/pb_typing_util.mli
index 2f79c8f..9872447 100644
--- a/src/compilerlib/pb_typing_util.mli
+++ b/src/compilerlib/pb_typing_util.mli
@@ -56,7 +56,7 @@ val field_option : ('a, 'b) Tt.field -> string -> Pb_option.value option
     is returned.
   *)
 
-val type_of_id : 'a Tt.proto -> int -> 'a Tt.proto_type
+val type_of_id : 'a Tt.proto_type list -> int -> 'a Tt.proto_type
 (** [type_of_id all_types id] returns the type associated with the given id,
     raise [Not_found] if the type is not in the all_types.
   *)
diff --git a/src/compilerlib/pb_typing_validation.ml b/src/compilerlib/pb_typing_validation.ml
index 4dd017f..2ca0577 100644
--- a/src/compilerlib/pb_typing_validation.ml
+++ b/src/compilerlib/pb_typing_validation.ml
@@ -265,8 +265,62 @@ let rec validate_message ?(parent_options = Pb_option.empty) file_name
   acc.Acc.all_types
   @ [ make_proto_type ~file_name ~file_options ~id ~scope:message_scope ~spec ]
 
-let validate (proto : Pt.proto) : _ Tt.proto_type list =
-  let { Pt.package; Pt.proto_file_name; messages; enums; file_options; _ } =
+let validate_service (scope : Tt.type_scope) ~file_name (service : Pt.service) :
+    _ Tt.service =
+  let { Pt.service_name; service_body } = service in
+  let service_body =
+    List.filter_map
+      (function
+        | Pt.Service_option _ -> None
+        | Pt.Service_rpc
+            {
+              rpc_name;
+              rpc_options;
+              rpc_req_stream;
+              rpc_req;
+              rpc_res_stream;
+              rpc_res;
+            } ->
+          let rpc_req =
+            match rpc_req with
+            | `User_defined ty -> ty
+            | _ -> E.invalid_rpc_req_type ~service_name ~rpc_name ()
+          in
+          let rpc_res =
+            match rpc_res with
+            | `User_defined ty -> ty
+            | _ -> E.invalid_rpc_res_type ~service_name ~rpc_name ()
+          in
+          let rpc =
+            {
+              Tt.rpc_name;
+              rpc_options;
+              rpc_req_stream;
+              rpc_req;
+              rpc_res_stream;
+              rpc_res;
+            }
+          in
+          Some rpc)
+      service_body
+  in
+  {
+    Tt.service_packages = scope.packages;
+    service_file_name = file_name;
+    service_name;
+    service_body;
+  }
+
+let validate (proto : Pt.proto) : _ Tt.proto =
+  let {
+    Pt.package;
+    Pt.proto_file_name;
+    messages;
+    enums;
+    file_options;
+    services;
+    _;
+  } =
     proto
   in
 
@@ -276,11 +330,17 @@ let validate (proto : Pt.proto) : _ Tt.proto_type list =
   let pbtt_msgs =
     List.fold_right
       (fun e pbtt_msgs ->
-        compile_enum_p1 file_name file_options scope e :: pbtt_msgs)
+        [ compile_enum_p1 file_name file_options scope e ] :: pbtt_msgs)
       enums []
   in
 
-  List.fold_left
-    (fun pbtt_msgs pbpt_msg ->
-      pbtt_msgs @ validate_message file_name file_options scope pbpt_msg)
-    pbtt_msgs messages
+  let proto_types =
+    List.fold_left
+      (fun pbtt_msgs pbpt_msg ->
+        let tys = validate_message file_name file_options scope pbpt_msg in
+        tys :: pbtt_msgs)
+      pbtt_msgs messages
+  in
+
+  let proto_services = List.map (validate_service scope ~file_name) services in
+  { Tt.proto_types; proto_services }
diff --git a/src/compilerlib/pb_typing_validation.mli b/src/compilerlib/pb_typing_validation.mli
index b2b53dc..796a104 100644
--- a/src/compilerlib/pb_typing_validation.mli
+++ b/src/compilerlib/pb_typing_validation.mli
@@ -50,4 +50,4 @@ val validate_message :
   (* file options *)
   Tt.type_scope ->
   Pt.message ->
-  Pb_field_type.unresolved Tt.proto
+  Pb_field_type.unresolved Tt.proto_type list
diff --git a/src/compilerlib/pb_util.ml b/src/compilerlib/pb_util.ml
index e45840e..2163d46 100644
--- a/src/compilerlib/pb_util.ml
+++ b/src/compilerlib/pb_util.ml
@@ -127,6 +127,15 @@ module List = struct
       (match f hd with
       | None -> filter_map f tl
       | Some x -> x :: filter_map f tl)
+
+  let find_opt f l = try Some (List.find f l) with Not_found -> None
+
+  let rec find_map f = function
+    | [] -> None
+    | x :: tl ->
+      (match f x with
+      | Some _ as r -> r
+      | None -> find_map f tl)
 end
 
 module Int_map = Map.Make (struct
diff --git a/src/compilerlib/pb_util.mli b/src/compilerlib/pb_util.mli
index ac88b69..4320c7a 100644
--- a/src/compilerlib/pb_util.mli
+++ b/src/compilerlib/pb_util.mli
@@ -81,6 +81,9 @@ module List : sig
   (** [filter_map f l] returns the list of element [x] for which [f] returned
       [Some x]. The length of the returned list will be less or equal than
       the length of the input list [l]. *)
+
+  val find_opt : ('a -> bool) -> 'a list -> 'a option
+  val find_map : ('a -> 'b option) -> 'a list -> 'b option
 end
 
 module Str_map : Map.S with type key = string
diff --git a/src/examples/calculator.proto b/src/examples/calculator.proto
new file mode 100644
index 0000000..3082757
--- /dev/null
+++ b/src/examples/calculator.proto
@@ -0,0 +1,29 @@
+syntax = "proto3";
+
+message DivByZero {}
+
+message I32 {
+  int32 value = 0;
+}
+
+message AddReq {
+  int32 a = 1;
+  int32 b = 2;
+}
+
+message AddAllReq {
+  repeated int32 ints = 1;
+}
+
+message Empty {}
+
+service Calculator {
+  rpc add(AddReq) returns (I32);
+
+  rpc add_all(AddAllReq) returns (I32);
+
+  rpc ping(Empty) returns (Empty);
+
+  rpc get_pings(Empty) returns (I32);
+}
+
diff --git a/src/examples/dune b/src/examples/dune
index 882a7fa..f4919db 100644
--- a/src/examples/dune
+++ b/src/examples/dune
@@ -41,3 +41,27 @@
  (name example05)
  (modules t_example05 example05)
  (libraries pbrt))
+
+(rule
+ (targets calculator.ml calculator.mli)
+ (deps calculator.proto)
+ (action
+  (run ocaml-protoc --binary --pp --yojson --services --ml_out ./ %{deps})))
+
+(test
+ (name calculator)
+ (modules t_calculator calculator)
+ (package pbrt_services)
+ (libraries pbrt pbrt_yojson pbrt_services))
+
+(rule
+ (targets file_server.ml file_server.mli)
+ (deps file_server.proto)
+ (action
+  (run ocaml-protoc --binary --pp --yojson --services --ml_out ./ %{deps})))
+
+(test
+ (name file_server)
+ (modules file_server) ; just check that it compiles
+ (package pbrt_services)
+ (libraries pbrt pbrt_yojson pbrt_services))
diff --git a/src/examples/file_server.proto b/src/examples/file_server.proto
new file mode 100644
index 0000000..4cea46c
--- /dev/null
+++ b/src/examples/file_server.proto
@@ -0,0 +1,39 @@
+
+// test that streaming variants all compile
+
+syntax = "proto3";
+
+message FileChunk {
+  string path = 1;
+  bytes data = 2;
+  int32 crc = 3;
+}
+
+message FilePath {
+  string path = 1;
+} 
+
+message FileCrc {
+  /// CRC of the entire file
+  int32 crc = 1;
+}
+
+message Empty {}
+
+message Ping {}
+
+message Pong {}
+
+service FileServer {
+  rpc touch_file(FilePath) returns (Empty);
+
+  /// Upload a file
+  rpc upload_file(stream FileChunk) returns (FileCrc);
+
+  /// Download a file
+  rpc download_file(FilePath) returns (stream FileChunk);
+
+  // keepalive
+  rpc ping_pong(stream Ping) returns (stream Pong);
+}
+
diff --git a/src/examples/t_calculator.ml b/src/examples/t_calculator.ml
new file mode 100644
index 0000000..306831a
--- /dev/null
+++ b/src/examples/t_calculator.ml
@@ -0,0 +1,1 @@
+let () = ()
diff --git a/src/ocaml-protoc/ocaml_protoc.ml b/src/ocaml-protoc/ocaml_protoc.ml
index 09deb01..e89fb84 100644
--- a/src/ocaml-protoc/ocaml_protoc.ml
+++ b/src/ocaml-protoc/ocaml_protoc.ml
@@ -46,11 +46,11 @@ let () =
       File_options.to_file_options cmdline.Cmdline.cmd_line_file_options
     in
 
-    let ocaml_types, proto_file_options =
+    let ocaml_proto, proto_file_options =
       Compilation.compile cmdline cmd_line_file_options
     in
 
-    Generation.generate_code ocaml_types ~proto_file_options cmdline
+    Generation.generate_code ocaml_proto ~proto_file_options cmdline
   with exn ->
     Printf.eprintf "%s\n" (Printexc.to_string exn);
     exit 1
diff --git a/src/ocaml-protoc/ocaml_protoc_cmdline.ml b/src/ocaml-protoc/ocaml_protoc_cmdline.ml
index 4002a6e..52f3894 100644
--- a/src/ocaml-protoc/ocaml_protoc_cmdline.ml
+++ b/src/ocaml-protoc/ocaml_protoc_cmdline.ml
@@ -110,6 +110,8 @@ module Cmdline = struct
     yojson: bool ref;  (** whether yojson encoding is enabled *)
     bs: bool ref;  (** whether BuckleScript encoding is enabled *)
     pp: bool ref;  (** whether pretty printing is enabled *)
+    services: bool ref;  (** whether services code generation is enabled *)
+    make: bool ref;  (** whether to generate "make" functions *)
     mutable cmd_line_file_options: File_options.t;
         (** file options override from the cmd line *)
     unsigned_tag: bool ref;
@@ -127,6 +129,8 @@ module Cmdline = struct
       yojson = ref false;
       bs = ref false;
       pp = ref false;
+      services = ref false;
+      make = ref false;
       cmd_line_file_options = File_options.make ();
       unsigned_tag = ref false;
     }
@@ -137,6 +141,9 @@ module Cmdline = struct
       "--bs", Arg.Set t.bs, " generate BuckleScript encoding";
       "--binary", Arg.Set t.binary, " generate binary encoding";
       "--pp", Arg.Set t.pp, " generate pretty print functions";
+      ( "--services",
+        Arg.Set t.services,
+        " generate code for services (requires json+binary)" );
       ( "-I",
         Arg.String (fun s -> t.include_dirs <- s :: t.include_dirs),
         " include directories" );
@@ -147,6 +154,7 @@ module Cmdline = struct
       ( "--unsigned",
         Arg.Set t.unsigned_tag,
         " tag uint32 and uint64 types with `unsigned" );
+      "--make", Arg.Set t.make, " generate `make` functions";
     ]
     @ File_options.cmd_line_args t.cmd_line_file_options
 
@@ -160,6 +168,11 @@ module Cmdline = struct
       t.pp := true
     );
 
+    if !(t.services) then (
+      t.binary := true;
+      t.yojson := true
+    );
+
     if t.proto_file_name = "" then
       failwith "Missing proto file name from command line argument";
 
diff --git a/src/ocaml-protoc/ocaml_protoc_compilation.ml b/src/ocaml-protoc/ocaml_protoc_compilation.ml
index d34c339..f6ab4eb 100644
--- a/src/ocaml-protoc/ocaml_protoc_compilation.ml
+++ b/src/ocaml-protoc/ocaml_protoc_compilation.ml
@@ -57,7 +57,7 @@ let find_imported_file include_dirs file_name =
     | Some file_name -> file_name
   )
 
-let compile cmdline cmd_line_files_options =
+let compile cmdline cmd_line_files_options : Ot.proto * _ =
   let { Cmdline.include_dirs; proto_file_name; unsigned_tag; _ } = cmdline in
 
   (* parsing *)
@@ -87,34 +87,27 @@ let compile cmdline cmd_line_files_options =
   in
 
   (* typing *)
-  let grouped_protos = Pb_typing.perform_typing protos in
-  let all_typed_protos = List.flatten grouped_protos in
+  let typed_proto = Pb_typing.perform_typing protos in
+  let all_typed_protos = List.flatten typed_proto.proto_types in
 
-  (* Only get the types which are part of the given proto file 
-   * (compilation unit) *)
-  let grouped_proto =
-    List.filter
-      (function
-        | { Tt.file_name; _ } :: _ when file_name = proto_file_name -> true
-        | _ -> false)
-      grouped_protos
+  (* Only get the types which are part of the given proto file
+     (compilation unit) *)
+  let typed_proto =
+    {
+      typed_proto with
+      Tt.proto_types =
+        List.filter
+          (function
+            | { Tt.file_name; _ } :: _ when file_name = proto_file_name -> true
+            | _ -> false)
+          typed_proto.proto_types;
+    }
   in
 
   (* -- OCaml Backend -- *)
   let module BO = Pb_codegen_backend in
-  let ocaml_types =
-    List.rev
-    @@ List.fold_left
-         (fun ocaml_types types ->
-           let l =
-             List.flatten
-             @@ List.map
-                  (fun t ->
-                    BO.compile ~unsigned_tag:!unsigned_tag all_typed_protos t)
-                  types
-           in
-           l :: ocaml_types)
-         [] grouped_proto
+  let ocaml_proto =
+    BO.compile ~unsigned_tag:!unsigned_tag ~all_types:all_typed_protos
+      typed_proto
   in
-
-  ocaml_types, proto_file_options
+  ocaml_proto, proto_file_options
diff --git a/src/ocaml-protoc/ocaml_protoc_generation.ml b/src/ocaml-protoc/ocaml_protoc_generation.ml
index 55ba78b..73b4694 100644
--- a/src/ocaml-protoc/ocaml_protoc_generation.ml
+++ b/src/ocaml-protoc/ocaml_protoc_generation.ml
@@ -53,19 +53,19 @@ let open_files cmdline (f : ml:out_channel -> mli:out_channel -> 'a) : 'a =
     (fun () -> f ~ml:oc_ml ~mli:oc_mli)
 
 let generate_code ocaml_types ~proto_file_options cmdline : unit =
-  let plugins =
+  let plugins : Pb_codegen_plugin.t list =
     List.flatten
       [
-        (if !(cmdline.Cmdline.yojson) then
-          [ Pb_codegen_encode_yojson.plugin; Pb_codegen_decode_yojson.plugin ]
+        (if !(cmdline.Cmdline.pp) then
+          [ Pb_codegen_pp.plugin ]
         else
           []);
         (if !(cmdline.Cmdline.binary) then
           [ Pb_codegen_encode_binary.plugin; Pb_codegen_decode_binary.plugin ]
         else
           []);
-        (if !(cmdline.Cmdline.pp) then
-          [ Pb_codegen_pp.plugin ]
+        (if !(cmdline.Cmdline.yojson) then
+          [ Pb_codegen_encode_yojson.plugin; Pb_codegen_decode_yojson.plugin ]
         else
           []);
         (if !(cmdline.Cmdline.bs) then
@@ -76,8 +76,8 @@ let generate_code ocaml_types ~proto_file_options cmdline : unit =
   in
 
   let ocaml_mod : CG_all.ocaml_mod =
-    CG_all.codegen ocaml_types ~proto_file_options
-      ~proto_file_name:cmdline.proto_file_name plugins
+    CG_all.codegen ocaml_types ~generate_make:!(cmdline.make)
+      ~proto_file_options ~proto_file_name:cmdline.proto_file_name plugins
   in
 
   (* now write the files *)
diff --git a/src/ocaml-protoc/ocaml_protoc_generation.mli b/src/ocaml-protoc/ocaml_protoc_generation.mli
index 1586458..9a4295f 100644
--- a/src/ocaml-protoc/ocaml_protoc_generation.mli
+++ b/src/ocaml-protoc/ocaml_protoc_generation.mli
@@ -4,4 +4,4 @@ module Ot = Pb_codegen_ocaml_type
 module Cmdline = Ocaml_protoc_cmdline.Cmdline
 
 val generate_code :
-  Ot.type_ list list -> proto_file_options:Pb_option.set -> Cmdline.t -> unit
+  Ot.proto -> proto_file_options:Pb_option.set -> Cmdline.t -> unit
diff --git a/src/runtime-services/dune b/src/runtime-services/dune
new file mode 100644
index 0000000..77e192e
--- /dev/null
+++ b/src/runtime-services/dune
@@ -0,0 +1,7 @@
+
+(library
+ (name pbrt_services)
+ (public_name pbrt_services)
+ (wrapped true)
+ (synopsis "Runtime library for services generated by ocaml-protoc")
+ (libraries pbrt pbrt_yojson yojson))
diff --git a/src/runtime-services/pbrt_services.ml b/src/runtime-services/pbrt_services.ml
new file mode 100644
index 0000000..59b5179
--- /dev/null
+++ b/src/runtime-services/pbrt_services.ml
@@ -0,0 +1,113 @@
+(** Runtime for Protobuf services. *)
+
+(** Whether there's a single value or a stream of them *)
+module Value_mode = struct
+  type unary
+  type stream
+end
+
+module Push_stream = Push_stream
+
+(** Service stubs, client side *)
+module Client = struct
+  type _ mode =
+    | Unary : Value_mode.unary mode
+    | Stream : Value_mode.stream mode
+
+  type ('req, 'req_mode, 'res, 'res_mode) rpc = {
+    service_name: string;
+    package: string list;  (** Package for the service *)
+    rpc_name: string;
+    req_mode: 'req_mode mode;
+    res_mode: 'res_mode mode;
+    encode_json_req: 'req -> Yojson.Basic.t;
+    encode_pb_req: 'req -> Pbrt.Encoder.t -> unit;
+    decode_json_res: Yojson.Basic.t -> 'res;
+    decode_pb_res: Pbrt.Decoder.t -> 'res;
+  }
+  (** A RPC description. You need a transport library
+      that knows where to send the bytes to actually use it. *)
+
+  let mk_rpc :
+      ?package:string list ->
+      service_name:string ->
+      rpc_name:string ->
+      req_mode:'req_mode mode ->
+      res_mode:'res_mode mode ->
+      encode_json_req:('req -> Yojson.Basic.t) ->
+      encode_pb_req:('req -> Pbrt.Encoder.t -> unit) ->
+      decode_json_res:(Yojson.Basic.t -> 'res) ->
+      decode_pb_res:(Pbrt.Decoder.t -> 'res) ->
+      unit ->
+      ('req, 'req_mode, 'res, 'res_mode) rpc =
+   fun ?(package = []) ~service_name ~rpc_name ~req_mode ~res_mode
+       ~encode_json_req ~encode_pb_req ~decode_json_res ~decode_pb_res () :
+       _ rpc ->
+    {
+      service_name;
+      package;
+      rpc_name;
+      req_mode;
+      res_mode;
+      encode_pb_req;
+      encode_json_req;
+      decode_pb_res;
+      decode_json_res;
+    }
+end
+
+(** Service stubs, server side *)
+module Server = struct
+  type 'm mode = 'm Client.mode =
+    | Unary : Value_mode.unary mode
+    | Stream : Value_mode.stream mode
+
+  type ('req, 'req_mode, 'res, 'res_mode) rpc = {
+    name: string;
+    req_mode: 'req_mode mode;
+    res_mode: 'res_mode mode;
+    encode_json_res: 'res -> Yojson.Basic.t;
+    encode_pb_res: 'res -> Pbrt.Encoder.t -> unit;
+    decode_json_req: Yojson.Basic.t -> 'req;
+    decode_pb_req: Pbrt.Decoder.t -> 'req;
+  }
+  (** A single RPC method, alongside encoders and decoders for
+        input and output types. . *)
+
+  (** A RPC endpoint. *)
+  type any_rpc = RPC : ('req, 'req_mode, 'res, 'res_mode) rpc -> any_rpc
+  [@@unboxed]
+
+  (** Helper to build a RPC *)
+  let mk_rpc :
+      name:string ->
+      req_mode:'req_mode mode ->
+      res_mode:'res_mode mode ->
+      encode_json_res:('res -> Yojson.Basic.t) ->
+      encode_pb_res:('res -> Pbrt.Encoder.t -> unit) ->
+      decode_json_req:(Yojson.Basic.t -> 'req) ->
+      decode_pb_req:(Pbrt.Decoder.t -> 'req) ->
+      unit ->
+      ('req, 'req_mode, 'res, 'res_mode) rpc =
+   fun ~name ~req_mode ~res_mode ~encode_json_res ~encode_pb_res
+       ~decode_json_req ~decode_pb_req () ->
+    {
+      name;
+      req_mode;
+      res_mode;
+      decode_pb_req;
+      decode_json_req;
+      encode_pb_res;
+      encode_json_res;
+    }
+
+  type 'h t = {
+    service_name: string;  (** Name of the service *)
+    package: string list;
+        (** The package this belongs in (e.g. "bigco.auth.secretpasswordstash"),
+         split along "." *)
+    handlers: 'h list;  (** A list of handlers *)
+  }
+  (** A service with fixed set of methods, which depends on the concrete RPC
+      implementation. Each method is a handler of some type ['h]. *)
+end
diff --git a/src/runtime-services/push_stream.ml b/src/runtime-services/push_stream.ml
new file mode 100644
index 0000000..7487943
--- /dev/null
+++ b/src/runtime-services/push_stream.ml
@@ -0,0 +1,7 @@
+type 'a t = {
+  push: 'a -> unit;
+  close: unit -> unit;
+}
+
+let push self x = self.push x
+let close self = self.close ()
diff --git a/src/runtime-services/push_stream.mli b/src/runtime-services/push_stream.mli
new file mode 100644
index 0000000..d62e8c1
--- /dev/null
+++ b/src/runtime-services/push_stream.mli
@@ -0,0 +1,10 @@
+(** Producer end of a stream, into which we can push values *)
+
+type 'a t = {
+  push: 'a -> unit;
+  close: unit -> unit;
+}
+(** Stream of outgoing values, we can push new ones until we close it *)
+
+val push : 'a t -> 'a -> unit
+val close : _ t -> unit
diff --git a/src/runtime-yojson/dune b/src/runtime-yojson/dune
index 7f53410..c7e629d 100644
--- a/src/runtime-yojson/dune
+++ b/src/runtime-yojson/dune
@@ -2,4 +2,4 @@
 (library
   (public_name pbrt_yojson)
   (wrapped false)
-  (libraries yojson base64))
+  (libraries (re_export yojson) base64))
diff --git a/src/runtime/pbrt.ml b/src/runtime/pbrt.ml
index 73f1d56..f6c39f5 100644
--- a/src/runtime/pbrt.ml
+++ b/src/runtime/pbrt.ml
@@ -96,11 +96,14 @@ module Decoder = struct
   let unexpected_payload field_name pk =
     raise (Failure (Unexpected_payload (field_name, pk)))
 
-  let missing_field field_name = raise (Failure (Missing_field field_name))
+  let[@inline never] missing_field field_name =
+    raise (Failure (Missing_field field_name))
+
+  let[@inline never] incomplete () = raise (Failure Incomplete)
   let at_end d = d.limit = d.offset
 
-  let byte d =
-    if d.offset >= d.limit then raise (Failure Incomplete);
+  let[@inline] byte d =
+    if d.offset >= d.limit then incomplete ();
     let byte = int_of_char (Bytes.get d.source d.offset) in
     d.offset <- d.offset + 1;
     byte
@@ -756,7 +759,6 @@ module Repeated_field = struct
 
   let to_list t = map_to_list identity t
 end
-(* Repeated_field*)
 
 module Pp = struct
   module F = Format
@@ -816,4 +818,3 @@ module Pp = struct
   let pp_brk pp_record (fmt : F.formatter) r : unit =
     F.fprintf fmt "@[<hv2>{ %a@;<1 -2>@]}" pp_record r
 end
-(* Pp *)
diff --git a/src/runtime/pbrt.mli b/src/runtime/pbrt.mli
index 2425f51..a6197b9 100644
--- a/src/runtime/pbrt.mli
+++ b/src/runtime/pbrt.mli
@@ -362,10 +362,11 @@ module Encoder : sig
   val wrapper_bytes_value : bytes option -> t -> unit
 end
 
+(** Optimized representation for repeated fields *)
 module Repeated_field : sig
   type 'a t
   (** optimized data structure for fast inserts so that decoding
-      can be efficient
+      of repeated fields can be efficient.
 
       Type can be constructed at no cost from an existing array.
     *)
@@ -510,4 +511,3 @@ module Pp : sig
   val pp_brk : (formatter -> 'a -> unit) -> formatter -> 'a -> unit
   (** [pp_brk fmt r] formats record value [r] with curly brakets.  *)
 end
-(* Pp *)


Test patch:
diff --git a/src/tests/google_unittest/dune b/src/tests/google_unittest/dune
index 6291319..b56b15c 100644
--- a/src/tests/google_unittest/dune
+++ b/src/tests/google_unittest/dune
@@ -1,5 +1,6 @@
 (test
   (name google_unittest)
+  (flags :standard -w -11)
   (libraries pbrt))
 
 (rule
diff --git a/src/tests/integration-tests/dune b/src/tests/integration-tests/dune
index 1ac2574..dee780f 100644
--- a/src/tests/integration-tests/dune
+++ b/src/tests/integration-tests/dune
@@ -324,3 +324,16 @@
  (name test_proto3_optional)
  (libraries pbrt)
  (modules test_proto3_optional_ml test_proto3_optional))
+
+(rule
+ (targets test_make.ml test_make.mli)
+ (deps
+  (:proto test_make.proto)
+  ../../include/ocaml-protoc/ocamloptions.proto)
+ (action
+  (run ocaml-protoc --binary --make --ml_out ./ %{proto})))
+
+(executable
+ (name test_make)
+ (libraries pbrt)
+ (modules test_make))
diff --git a/src/tests/integration-tests/test_make.proto b/src/tests/integration-tests/test_make.proto
new file mode 100644
index 0000000..7928842
--- /dev/null
+++ b/src/tests/integration-tests/test_make.proto
@@ -0,0 +1,21 @@
+syntax = "proto3";
+
+enum FooEnum {
+  A = 0;
+  B = 1;
+}
+
+message FooOuter {
+  string x = 1;
+  optional int32 y = 2;
+  repeated FooEnum enums = 3;
+
+  message FooInner {
+    string inner_x = 1;
+    optional int64 inner_y = 2;
+    repeated float inner_z = 3;
+    repeated float inner_z2 = 4 [packed=true];
+  }
+
+  repeated FooInner foos = 10;
+}
diff --git a/src/tests/unit-tests/test_typing.ml b/src/tests/unit-tests/test_typing.ml
index c963f2d..6fa4149 100644
--- a/src/tests/unit-tests/test_typing.ml
+++ b/src/tests/unit-tests/test_typing.ml
@@ -17,7 +17,8 @@ let () =
   let t =
     List.fold_left
       (fun t type_ -> Pb_typing_resolution.Types_by_scope.add t type_)
-      Pb_typing_resolution.Types_by_scope.empty proto
+      Pb_typing_resolution.Types_by_scope.empty
+      (List.flatten proto.proto_types)
   in
   assert (is_found t [ "foo"; "bar" ] "M1");
   assert (is_found t [ "foo"; "bar" ] "M2");