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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
|
(***********************************************************************)
(* *)
(* OCaml *)
(* *)
(* Jeremie Dimino, Jane Street Europe *)
(* *)
(* Copyright 2015 Institut National de Recherche en Informatique et *)
(* en Automatique. All rights reserved. This file is distributed *)
(* under the terms of the Q Public License version 1.0. *)
(* *)
(***********************************************************************)
open StdLabels
open Bigarray
type 'a typ =
| Int : int typ
| Int32 : int32 typ
| Int64 : int64 typ
| Nativeint : nativeint typ
| Float : float typ
type 'a proto =
| Ret : 'a typ -> 'a proto
| Abs : 'a typ * 'b proto -> ('a -> 'b) proto
let ( ** ) x y = Abs (x, y)
(* This form is easier to process programmatically. We don't expose it as
ocamlopt takes a really really long time to compile a constant list
of these. *)
type simplified_test = Test : string * 'a * 'a proto -> simplified_test
type test =
| T1 : string * ('a -> 'b) * 'a typ * 'b typ -> test
| T2 : string * ('a -> 'b -> 'c) * 'a typ * 'b typ * 'c typ -> test
| T3 : string * ('a -> 'b -> 'c -> 'd) *
'a typ * 'b typ * 'c typ * 'd typ -> test
| T4 : string * ('a -> 'b -> 'c -> 'd -> 'e) *
'a typ * 'b typ * 'c typ * 'd typ * 'e typ -> test
| T5 : string * ('a -> 'b -> 'c -> 'd -> 'e -> 'f) *
'a typ * 'b typ * 'c typ * 'd typ * 'e typ * 'f typ -> test
| T6 : string * ('a -> 'b -> 'c -> 'd -> 'e -> 'f -> 'g) *
'a typ * 'b typ * 'c typ * 'd typ * 'e typ * 'f typ * 'g typ -> test
| T : string * 'a * 'a proto -> test
let expand_test = function
| T1 (s, fn, a, b) -> Test (s, fn, a ** Ret b)
| T2 (s, fn, a, b, c) -> Test (s, fn, a ** b ** Ret c)
| T3 (s, fn, a, b, c, d) -> Test (s, fn, a ** b ** c ** Ret d)
| T4 (s, fn, a, b, c, d, e) -> Test (s, fn, a ** b ** c ** d ** Ret e)
| T5 (s, fn, a, b, c, d, e, f) ->
Test (s, fn, a ** b ** c ** d ** e ** Ret f)
| T6 (s, fn, a, b, c, d, e, f, g) ->
Test (s, fn, a ** b ** c ** d ** e ** f ** Ret g)
| T (s, fn, p) -> Test (s, fn, p)
let string_of : type a. a typ -> a -> string = function
| Int -> string_of_int
| Int32 -> Printf.sprintf "%ldl"
| Int64 -> Printf.sprintf "%LdL"
| Nativeint -> Printf.sprintf "%ndn"
| Float ->
fun f -> Printf.sprintf "float_of_bits 0x%LxL" (Int64.bits_of_float f)
let rec arity : type a. a proto -> int = function
| Ret _ -> 0
| Abs (_, p) -> 1 + arity p
module Buffer = struct
type t = (char, int8_unsigned_elt, c_layout) Array1.t
let arg_size = 8
let create ~arity : t =
Array1.create char c_layout ((arity + 1) * arg_size)
let clear (t : t) = Array1.fill t '\000'
let length : t -> int = Array1.dim
external init_c_side : ocaml_buffer:t -> c_buffer:t -> unit
= "test_set_buffers"
external get_int32 : t -> int -> int32 = "%caml_bigstring_get32"
external get_int64 : t -> int -> int64 = "%caml_bigstring_get64"
external set_int32 : t -> int -> int32 -> unit = "%caml_bigstring_set32"
external set_int64 : t -> int -> int64 -> unit = "%caml_bigstring_set64"
let get_int32 t ~arg = get_int32 t (arg * arg_size)
let get_int64 t ~arg = get_int64 t (arg * arg_size)
let set_int32 t ~arg x = set_int32 t (arg * arg_size) x
let set_int64 t ~arg x = set_int64 t (arg * arg_size) x
let get_nativeint, set_nativeint =
match Sys.word_size with
| 32 -> ((fun t ~arg -> get_int32 t ~arg |> Nativeint.of_int32),
(fun t ~arg x -> set_int32 t ~arg (Nativeint.to_int32 x)))
| 64 -> ((fun t ~arg -> get_int64 t ~arg |> Int64.to_nativeint),
(fun t ~arg x -> set_int64 t ~arg (Int64.of_nativeint x)))
| n -> Printf.ksprintf failwith "unknown word size (%d)" n
let get_int =
if Sys.word_size = 32 then
fun buf ~arg -> get_int32 buf ~arg |> Int32.to_int
else
fun buf ~arg -> get_int64 buf ~arg |> Int64.to_int
let set_int =
if Sys.word_size = 32 then
fun buf ~arg x -> set_int32 buf ~arg (Int32.of_int x)
else
fun buf ~arg x -> set_int64 buf ~arg (Int64.of_int x)
let get_float buf ~arg = get_int64 buf ~arg |> Int64.float_of_bits
let set_float buf ~arg x = set_int64 buf ~arg (Int64.bits_of_float x)
let get : type a. a typ -> t -> arg:int -> a = function
| Int -> get_int
| Int32 -> get_int32
| Int64 -> get_int64
| Nativeint -> get_nativeint
| Float -> get_float
let set : type a. a typ -> t -> arg:int -> a -> unit = function
| Int -> set_int
| Int32 -> set_int32
| Int64 -> set_int64
| Nativeint -> set_nativeint
| Float -> set_float
(* This is almost a memcpy except that we use get/set which should
ensure that the values in [dst] don't overflow. *)
let copy_args ~src ~dst proto =
let rec loop : type a. a proto -> int -> unit = fun proto arg ->
match proto with
| Ret typ ->
set typ dst ~arg (get typ src ~arg)
| Abs (typ, rest) ->
set typ dst ~arg (get typ src ~arg);
loop rest (arg + 1)
in
loop proto 0
end
let exec proto f ~ocaml_buffer ~c_buffer =
let rec loop : type a. a proto -> a -> int -> unit = fun proto f arg ->
match proto with
| Ret typ ->
Buffer.set typ c_buffer ~arg f
| Abs (typ, rest) ->
let x = Buffer.get typ ocaml_buffer ~arg in
loop rest (f x) (arg + 1)
in
loop proto f 0
let strings_of_test_instance name proto buffer =
let rec loop : type a. a proto -> int -> string list -> string list * string =
fun proto arg acc ->
match proto with
| Ret typ ->
(List.rev acc, string_of typ (Buffer.get typ buffer ~arg))
| Abs (typ, rest) ->
let s = string_of typ (Buffer.get typ buffer ~arg) in
loop rest (arg + 1) (s :: acc)
in
loop proto 0 []
let typ_size : type a. a typ -> int = function
| Int -> Sys.word_size / 8
| Int32 -> 4
| Int64 -> 8
| Nativeint -> Sys.word_size / 8
| Float -> 8
let rec sizes : type a. a proto -> int list = function
| Ret typ -> [typ_size typ]
| Abs (typ, rest) -> typ_size typ :: sizes rest
let print_hex ~sizes ~arity buffer =
let printf = Printf.printf in
printf "(";
for i = 0 to arity do
if i = arity then
printf ") -> "
else if i > 0 then
printf ", ";
for ofs = i * Buffer.arg_size to i * Buffer.arg_size + sizes.(i) - 1 do
printf "%02x" (Char.code buffer.{ofs});
done;
done
let printed_mismatches = ref 0
let print_mismatch name proto ~ocaml_buffer ~c_buffer =
let printf = Printf.printf in
printf "Mismatch for %s\n" name;
let o_args, o_res = strings_of_test_instance name proto ocaml_buffer in
let c_args, c_res = strings_of_test_instance name proto c_buffer in
let o_args, c_args =
(* Align arguments *)
List.map2 o_args c_args ~f:(fun a b ->
let len_a = String.length a and len_b = String.length b in
let len = max len_a len_b in
(Printf.sprintf "%*s" len a,
Printf.sprintf "%*s" len b))
|> List.split
in
printf "ocaml side : (%s) -> %s\n" (String.concat ~sep:", " o_args) o_res;
printf "c side : (%s) -> %s\n" (String.concat ~sep:", " c_args) c_res;
let sizes = sizes proto |> Array.of_list in
let arity = arity proto in
printf "ocaml side : "; print_hex ~sizes ~arity ocaml_buffer; printf "\n";
printf "c side : "; print_hex ~sizes ~arity c_buffer; printf "\n";
incr printed_mismatches;
if !printed_mismatches >= 1000 then begin
printf "Output truncated at 1000 failures.";
exit 0
end
external cleanup_normal
: int -> int -> int -> int -> int -> int -> int -> int
-> int -> int -> int -> int -> int -> int -> int -> int
-> int -> int -> int -> int -> int -> int -> int -> int
-> int -> int -> int -> int -> int -> int -> int -> int
-> int -> int -> int -> int -> int -> int -> int -> int
-> int -> int -> int -> int -> int -> int -> int -> int
-> int -> int -> int -> int -> int -> int -> int -> int
-> int -> int -> int -> int -> int -> int -> int -> int
-> int -> int -> int -> int -> int -> int -> int -> int
-> int -> int -> int -> int -> int -> int -> int -> int
-> int -> int -> int -> int -> int -> int -> int -> int
-> int -> int -> int -> int -> int -> int -> int -> int
-> int -> int -> int -> int -> int -> int -> int -> int
-> int -> int -> int -> int -> int -> int -> int -> int
-> int -> int -> int -> int -> int -> int -> int -> int
-> int -> int -> int -> int -> int -> int -> int -> int
-> int = "" "noalloc" "test_cleanup_normal"
external cleanup_float
: float -> float -> float -> float -> float -> float -> float -> float
-> float -> float -> float -> float -> float -> float -> float -> float
-> float -> float -> float -> float -> float -> float -> float -> float
-> float -> float -> float -> float -> float -> float -> float -> float
-> float = "" "noalloc" "test_cleanup_normal" "float"
let cleanup_args_and_stack () =
let _ : int =
cleanup_normal
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
in
let _ : float =
cleanup_float
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
in
()
let run_test ~random_data ~ocaml_buffer ~c_buffer (Test (name, f, proto)) =
Buffer.clear ocaml_buffer;
Buffer.clear c_buffer;
Buffer.copy_args ~src:random_data ~dst:ocaml_buffer proto;
cleanup_args_and_stack ();
exec proto f ~ocaml_buffer ~c_buffer;
let success = ocaml_buffer = c_buffer in
if not success then print_mismatch name proto ~ocaml_buffer ~c_buffer;
success
let run_tests tests =
let tests = List.map tests ~f:expand_test in
let max_args =
List.fold_left tests ~init:0 ~f:(fun acc (Test (_, _, p)) ->
max acc (arity p))
in
let ocaml_buffer = Buffer.create ~arity:max_args
and c_buffer = Buffer.create ~arity:max_args in
Buffer.init_c_side ~ocaml_buffer ~c_buffer;
let random_data = Buffer.create ~arity:max_args in
let new_random_data () =
for i = 0 to Buffer.length random_data - 1 do
random_data.{i} <- char_of_int (Random.int 256)
done
in
let failure = ref false in
for i = 1 to 1000 do
new_random_data ();
List.iter tests ~f:(fun test ->
if not (run_test ~random_data ~ocaml_buffer ~c_buffer test) then
failure := true)
done;
exit (if !failure then 1 else 0)
|