package repr

  1. Overview
  2. Docs

Source file type_size.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
(*
 * Copyright (c) 2016-2017 Thomas Gazagnaire <thomas@gazagnaire.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *)

open Type_core
module Sizer = Size.Sizer
module Bin = Binary

let rec t : type a. a t -> a Sizer.t = function
  | Self s -> fst (self s)
  | Custom c -> c.size_of
  | Map b -> map ~boxed:true b
  | Prim t -> prim ~boxed:true t
  | Attributes { attr_type; _ } -> t attr_type
  | Boxed b -> t b
  | List l -> Bin.List.sizer l.len (t l.v)
  | Array a -> Bin.Array.sizer a.len (t a.v)
  | Tuple t -> tuple t
  | Option x -> Bin.Option.sizer (t x)
  | Record r -> record r
  | Variant v -> variant v
  | Var v -> raise (Unbound_type_variable v)

and unboxed : type a. a t -> a Sizer.t = function
  | Self s -> snd (self s)
  | Custom c -> c.unboxed_size_of
  | Map b -> map ~boxed:false b
  | Prim t -> prim ~boxed:false t
  | Attributes { attr_type = t; _ } -> unboxed t
  | Boxed b -> t b
  | List l -> Bin.List.sizer l.len (t l.v)
  | Array a -> Bin.Array.sizer a.len (t a.v)
  | Tuple t -> tuple t
  | Option x -> Bin.Option.sizer (t x)
  | Record r -> record r
  | Variant v -> variant v
  | Var v -> raise (Unbound_type_variable v)

and self : type a. a self -> a Sizer.t * a Sizer.t =
  (* The resulting sizer may be any of [Unknown], [Static] or [Dynamic]. In the
     latter case, we must be able to recurse back to this definition at size
     computation time.

     We unroll with 'stub' dynamic values that initially [assert false] but will
     be backpatched with the parent derivation (iff it does actually turn out to
     be dynamic). *)
  let stub _ = assert false in
  let backpatch stubref = function
    | Size.Dynamic f -> stubref := f
    | Size.Static _ -> ()
    | Size.Unknown -> ()
  in
  fun { self_unroll; _ } ->
    let of_value = ref stub
    and of_encoding = ref stub
    and unboxed_of_value = ref stub
    and unboxed_of_encoding = ref stub in
    let unrolled =
      let size_of =
        Sizer.dynamic
          ~of_value:(fun a -> !of_value a)
          ~of_encoding:(fun buf off -> !of_encoding buf off)
      in
      let unboxed_size_of =
        Sizer.dynamic
          ~of_value:(fun a -> !unboxed_of_value a)
          ~of_encoding:(fun buf off -> !unboxed_of_encoding buf off)
      in
      self_unroll (partial ~size_of ~unboxed_size_of ())
    in
    let t = t unrolled and unboxed = unboxed unrolled in
    backpatch of_value t.of_value;
    backpatch of_encoding t.of_encoding;
    backpatch unboxed_of_value unboxed.of_value;
    backpatch unboxed_of_encoding unboxed.of_encoding;
    (t, unboxed)

and tuple : type a. a tuple -> a Sizer.t = function
  | Pair (x, y) -> Bin.Pair.sizer (t x) (t y)
  | Triple (x, y, z) -> Bin.Triple.sizer (t x) (t y) (t z)
  | Quad (w, x, y, z) -> Bin.Quad.sizer (t w) (t x) (t y) (t z)

and map : type a b. boxed:bool -> (a, b) map -> b Sizer.t =
 fun ~boxed { x; g; _ } -> Sizer.using g (if boxed then t x else unboxed x)

and prim : type a. boxed:bool -> a prim -> a Sizer.t =
 fun ~boxed -> function
  | Unit -> Bin.Unit.sizer
  | Bool -> Bin.Bool.sizer
  | Char -> Bin.Char.sizer
  | Int -> Bin.Varint.sizer
  | Int32 -> Bin.Int32.sizer
  | Int64 -> Bin.Int64.sizer
  | Float -> Bin.Float.sizer
  | String n -> (if boxed then Bin.String.sizer else Bin.String_unboxed.sizer) n
  | Bytes n -> (if boxed then Bin.Bytes.sizer else Bin.Bytes_unboxed.sizer) n

and record : type a. a record -> a Sizer.t =
 fun r ->
  fields r
  |> List.map (fun (Field f) -> Sizer.using f.fget (t f.ftype))
  |> ListLabels.fold_left ~init:(Sizer.static 0) ~f:Sizer.( <+> )

and variant : type a. a variant -> a Sizer.t =
 fun v ->
  let static_varint_size n =
    match Bin.Varint.sizer.of_value with
    | Unknown | Static _ -> assert false
    | Dynamic f -> f n
  in
  let case_lengths : (int * a Sizer.t) array =
    ArrayLabels.map v.vcases ~f:(function
      | C0 { ctag0; _ } -> (static_varint_size ctag0, Sizer.static 0)
      | C1 { ctag1; ctype1; cwit1 = expected; _ } ->
          let tag_length = static_varint_size ctag1 in
          let arg_length =
            match t ctype1 with
            | ({ of_value = Static _; _ } | { of_value = Unknown; _ }) as t -> t
            | { of_value = Dynamic of_value; of_encoding } ->
                let of_value a =
                  match v.vget a with
                  | CV0 _ -> assert false
                  | CV1 ({ cwit1 = received; _ }, args) -> (
                      match Witness.cast received expected args with
                      | Some v -> of_value v
                      | None -> assert false)
                in
                { of_value = Dynamic of_value; of_encoding }
          in
          (tag_length, arg_length))
  in
  (* If all cases have [size = Static n], then so does the variant.
     If any case has [size = Unknown], then so does the variant. *)
  let non_dynamic_length =
    let rec aux static_so_far = function
      | -1 -> Option.map (fun n -> Sizer.static n) static_so_far
      | i -> (
          match case_lengths.(i) with
          | _, { of_value = Unknown; _ } -> Some Sizer.unknown
          | _, { of_value = Dynamic _; _ } -> None
          | tag_len, { of_value = Static arg_len; _ } -> (
              let len = tag_len + arg_len in
              match static_so_far with
              | None -> aux (Some len) (i - 1)
              | Some len' when len = len' -> aux static_so_far (i - 1)
              | Some _ -> None))
    in
    aux None (Array.length case_lengths - 1)
  in
  match non_dynamic_length with
  | Some x -> x
  | None ->
      (* Otherwise, the variant size is [Dynamic] over the tag *)
      let of_value a =
        let tag =
          match v.vget a with
          | CV0 { ctag0; _ } -> ctag0
          | CV1 ({ ctag1; _ }, _) -> ctag1
        in
        let tag_length, arg_length = case_lengths.(tag) in
        let arg_length =
          match arg_length.of_value with
          | Dynamic f -> f a
          | Static n -> n
          | Unknown ->
              (* [Unknown] arg lengths discounted above *)
              assert false
        in
        tag_length + arg_length
      in
      let of_encoding buf (Size.Offset off) =
        let off = ref off in
        let tag = Bin.Varint.decode buf off in
        match case_lengths.(tag) with
        | _, { of_encoding = Static n; _ } -> Size.Offset (!off + n)
        | _, { of_encoding = Dynamic f; _ } -> f buf (Size.Offset !off)
        | _, { of_encoding = _; _ } -> assert false
      in
      Sizer.dynamic ~of_value ~of_encoding