File: wat_preprocess.ml

package info (click to toggle)
js-of-ocaml 6.2.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 37,932 kB
  • sloc: ml: 135,957; javascript: 58,364; ansic: 437; makefile: 422; sh: 12; perl: 4
file content (643 lines) | stat: -rw-r--r-- 19,164 bytes parent folder | download
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
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
open Stdlib

exception Error of (Lexing.position * Lexing.position) * string

let report_error loc msg =
  let location = Lexing.range_to_string loc in
  Format.eprintf "%s%s%!" location msg;
  exit 1

(****)

(*
See the WebAssembly Text Format Specification:
https://webassembly.github.io/spec/core/text/index.html

We use custom annotations to extend the syntax
(https://github.com/WebAssembly/annotations).
*)

let digit = [%sedlex.regexp? '0' .. '9']

let hexdigit = [%sedlex.regexp? '0' .. '9' | 'a' .. 'f' | 'A' .. 'F']

let num = [%sedlex.regexp? digit, Star (Opt '_', digit)]

let hexnum = [%sedlex.regexp? hexdigit, Star (Opt '_', hexdigit)]

let uN = [%sedlex.regexp? num | "0x", hexnum]

let idchar =
  [%sedlex.regexp?
    ( '0' .. '9'
    | 'A' .. 'Z'
    | 'a' .. 'z'
    | '!'
    | '#'
    | '$'
    | '%'
    | '&'
    | '\''
    | '*'
    | '+'
    | '-'
    | '.'
    | '/'
    | ':'
    | '<'
    | '='
    | '>'
    | '?'
    | '@'
    | '\\'
    | '^'
    | '_'
    | '`'
    | '|'
    | '~' )]

let id = [%sedlex.regexp? '$', Plus idchar]

let linechar = [%sedlex.regexp? Sub (any, (10 | 13))]

let newline = [%sedlex.regexp? 10 | 13 | 13, 10]

let linecomment = [%sedlex.regexp? ";;", Star linechar, (newline | eof)]

let keyword = [%sedlex.regexp? Plus idchar]

let rec comment start_pos lexbuf =
  match%sedlex lexbuf with
  | ";)" -> ()
  | "(;" ->
      comment (Sedlexing.lexing_positions lexbuf) lexbuf;
      comment start_pos lexbuf
  | ';' | '(' | Plus (Sub (any, (';' | '('))) -> comment start_pos lexbuf
  | _ -> raise (Error (start_pos, Printf.sprintf "Unclosed comment.\n"))

let string_buffer = Buffer.create 256

let rec string lexbuf =
  match%sedlex lexbuf with
  | '"' ->
      Buffer.add_string string_buffer (Sedlexing.Utf8.lexeme lexbuf);
      let s = Buffer.contents string_buffer in
      Buffer.clear string_buffer;
      s
  | Plus
      ( Sub (any, (0 .. 31 | 0x7f | '"' | '\\'))
      | "\\t" | "\\n" | "\\r" | "\\'" | "\\\"" | "\\\\"
      | '\\', hexdigit, hexdigit
      | "\\u{", hexnum, "}" ) ->
      Buffer.add_string string_buffer (Sedlexing.Utf8.lexeme lexbuf);
      string lexbuf
  | _ ->
      raise
        (Error (Sedlexing.lexing_positions lexbuf, Printf.sprintf "Malformed string.\n"))

type pos =
  { loc : Lexing.position
  ; byte_loc : int
  }

type token =
  | LPAREN
  | RPAREN
  | ATOM of string
  | EOF

let locs lexbuf =
  let loc, loc' = Sedlexing.lexing_positions lexbuf in
  let byte_loc, byte_loc' = Sedlexing.bytes_loc lexbuf in
  { loc; byte_loc }, { loc = loc'; byte_loc = byte_loc' }

let position_of_loc (pos, pos') = pos.loc, pos'.loc

let rec token lexbuf =
  match%sedlex lexbuf with
  | '(' -> LPAREN, locs lexbuf
  | ')' -> RPAREN, locs lexbuf
  | keyword -> ATOM (Sedlexing.Utf8.lexeme lexbuf), locs lexbuf
  | '"' ->
      let string_start =
        { loc = Sedlexing.lexing_position_start lexbuf
        ; byte_loc = Sedlexing.lexeme_bytes_start lexbuf
        }
      in
      Buffer.add_char string_buffer '"';
      let str = string lexbuf in
      ( ATOM str
      , ( string_start
        , { loc = Sedlexing.lexing_position_curr lexbuf
          ; byte_loc = Sedlexing.lexeme_bytes_end lexbuf
          } ) )
  | Plus (' ' | '\t' | newline | linecomment) -> token lexbuf
  | "(;" ->
      comment (Sedlexing.lexing_positions lexbuf) lexbuf;
      token lexbuf
  | ";)" ->
      raise
        (Error
           ( Sedlexing.lexing_positions lexbuf
           , Printf.sprintf "Unmatched closing comment.\n" ))
  | eof -> EOF, locs lexbuf
  | _ ->
      raise (Error (Sedlexing.lexing_positions lexbuf, Printf.sprintf "Syntax error.\n"))

type t =
  { loc : pos * pos
  ; desc : desc
  }

and desc =
  | Atom of string
  | List of t list

let rec parse_list lexbuf toplevel start_loc acc =
  let tok, loc = token lexbuf in
  match tok with
  | LPAREN ->
      let lst, loc = parse_list lexbuf false loc [] in
      parse_list lexbuf toplevel start_loc ({ desc = List lst; loc } :: acc)
  | RPAREN ->
      if toplevel
      then
        raise
          (Error
             ( Sedlexing.lexing_positions lexbuf
             , Printf.sprintf "Unexpected closing parenthesis.\n" ));
      List.rev acc, (fst start_loc, snd loc)
  | EOF ->
      if not toplevel
      then
        raise
          (Error (position_of_loc start_loc, Printf.sprintf "Unclosed parenthesis.\n"));
      List.rev acc, (fst start_loc, snd loc)
  | ATOM s -> parse_list lexbuf toplevel start_loc ({ loc; desc = Atom s } :: acc)

let parse lexbuf =
  let pos =
    { loc = Sedlexing.lexing_position_start lexbuf
    ; byte_loc = Sedlexing.lexeme_bytes_end lexbuf
    }
  in
  parse_list lexbuf true (pos, pos) []

let is_unsigned_integer s =
  let lexbuf = Sedlexing.Utf8.from_string s in
  match%sedlex lexbuf with
  | uN, eof -> true
  | _ -> false

let hexdigit c =
  match c with
  | '0' .. '9' -> Char.code c - Char.code '0'
  | 'a' .. 'f' -> Char.code c - (Char.code 'a' - 10)
  | 'A' .. 'F' -> Char.code c - (Char.code 'A' - 10)
  | _ -> assert false

let rec parse_string_contents loc lexbuf =
  match%sedlex lexbuf with
  | eof ->
      let s = Buffer.contents string_buffer in
      Buffer.clear string_buffer;
      s
  | Plus (Sub (any, (0 .. 31 | 0x7f | '"' | '\\'))) ->
      Buffer.add_string string_buffer (Sedlexing.Utf8.lexeme lexbuf);
      parse_string_contents loc lexbuf
  | "\\t" ->
      Buffer.add_char string_buffer '\t';
      parse_string_contents loc lexbuf
  | "\\n" ->
      Buffer.add_char string_buffer '\n';
      parse_string_contents loc lexbuf
  | "\\r" ->
      Buffer.add_char string_buffer '\r';
      parse_string_contents loc lexbuf
  | "\\'" ->
      Buffer.add_char string_buffer '\'';
      parse_string_contents loc lexbuf
  | "\\\"" ->
      Buffer.add_char string_buffer '"';
      parse_string_contents loc lexbuf
  | "\\\\" ->
      Buffer.add_char string_buffer '\\';
      parse_string_contents loc lexbuf
  | '\\', hexdigit, hexdigit ->
      let s = Sedlexing.Utf8.lexeme lexbuf in
      assert (String.length s = 3);
      Buffer.add_char string_buffer (Char.chr ((hexdigit s.[1] * 16) + hexdigit s.[2]));
      parse_string_contents loc lexbuf
  | "\\u{", hexnum, "}" -> (
      match
        let s = Sedlexing.Utf8.lexeme lexbuf in
        int_of_string ("0x" ^ String.sub s ~pos:3 ~len:(String.length s - 4))
      with
      | c when Uchar.is_valid c ->
          Buffer.add_utf_8_uchar string_buffer (Uchar.of_int c);
          parse_string_contents loc lexbuf
      | _ | (exception Failure _) ->
          Buffer.clear string_buffer;
          raise
            (Error
               (position_of_loc loc, Printf.sprintf "Invalid Unicode escape sequences.\n"))
      )
  | _ -> assert false

let parse_string loc s =
  parse_string_contents
    loc
    (Sedlexing.Utf8.from_string (String.sub s ~pos:1 ~len:(String.length s - 2)))

let is_string s = String.length s > 0 && Char.equal s.[0] '"'

let is_keyword s =
  let lexbuf = Sedlexing.Utf8.from_string s in
  match%sedlex lexbuf with
  | keyword, eof -> true
  | _ -> false

let is_id s =
  let lexbuf = Sedlexing.Utf8.from_string s in
  match%sedlex lexbuf with
  | id, eof -> true
  | _ -> false

(****)

module StringMap = Map.Make (String)

type typ =
  | Bool
  | String
  | Version

type value =
  | Bool of bool
  | String of string
  | Version of int * int * int

let value_equal (a : value) b = Poly.equal a b

let value_compare (a : value) b = Poly.compare a b

type st =
  { text : string
  ; mutable pos : pos
  ; variables : value StringMap.t
  ; buf : Buffer.t
  }

let value_type v : typ =
  match v with
  | Bool _ -> Bool
  | String _ -> String
  | Version _ -> Version

let type_name (t : typ) =
  match t with
  | Bool -> "boolean"
  | String -> "string"
  | Version -> "version"

let variable_is_set st nm =
  match StringMap.find_opt nm st.variables with
  | Some (Bool true) -> true
  | _ -> false

let check_type ?typ expr actual_typ =
  match typ with
  | None -> ()
  | Some typ ->
      if not (Poly.equal actual_typ typ)
      then
        raise
          (Error
             ( position_of_loc expr.loc
             , Printf.sprintf
                 "Expected a %s but this is a %s.\n"
                 (type_name typ)
                 (type_name actual_typ) ))

let rec eval ?typ st expr =
  match expr with
  | { desc = Atom s; loc } when is_string s ->
      check_type ?typ expr String;
      String (parse_string loc s)
  | { desc = Atom s; loc } when is_keyword s ->
      if not (StringMap.mem s st.variables)
      then
        raise (Error (position_of_loc loc, Printf.sprintf "Unknown variable '%s'.\n" s));
      let res = StringMap.find s st.variables in
      check_type ?typ expr (value_type res);
      res
  | { desc =
        List
          [ { desc = Atom major; _ }
          ; { desc = Atom minor; _ }
          ; { desc = Atom patchlevel; _ }
          ]
    ; _
    }
    when is_unsigned_integer major
         && is_unsigned_integer minor
         && is_unsigned_integer patchlevel ->
      check_type ?typ expr Version;
      Version (int_of_string major, int_of_string minor, int_of_string patchlevel)
  | { desc = List ({ desc = Atom "and"; _ } :: lst); _ } ->
      check_type ?typ expr Bool;
      Bool (List.for_all ~f:(fun expr' -> eval_bool st expr') lst)
  | { desc = List ({ desc = Atom "or"; _ } :: lst); _ } ->
      check_type ?typ expr Bool;
      Bool (List.exists ~f:(fun expr' -> eval_bool st expr') lst)
  | { desc = List [ { desc = Atom "not"; _ }; expr' ]; _ } ->
      check_type ?typ expr Bool;
      Bool (not (eval_bool st expr'))
  | { desc =
        List ({ desc = Atom (("=" | "<" | ">" | "<=" | ">=" | "<>") as op); _ } :: args)
    ; loc
    } -> bin_op st ?typ loc op args
  | { loc; _ } -> raise (Error (position_of_loc loc, Printf.sprintf "Syntax error.\n"))

and eval_bool st expr =
  match eval ~typ:Bool st expr with
  | Bool b -> b
  | _ -> assert false

and bin_op st ?typ loc op args =
  match args with
  | [ expr; expr' ] ->
      check_type ?typ expr Bool;
      let v = eval st expr in
      let v' = eval ~typ:(value_type v) st expr' in
      Bool
        (let op =
           match op with
           | "=" -> ( = )
           | "<" -> ( < )
           | ">" -> ( > )
           | "<=" -> ( <= )
           | ">=" -> ( >= )
           | "<>" -> ( <> )
           | _ -> assert false
         in
         op (value_compare v v') 0)
  | _ -> raise (Error (position_of_loc loc, Printf.sprintf "Syntax error.\n"))

(****)

let write st pos' =
  Buffer.add_substring st.buf st.text st.pos.byte_loc (pos'.byte_loc - st.pos.byte_loc);
  st.pos <- pos'

let skip st (pos' : pos) =
  let lines = pos'.loc.pos_lnum - st.pos.loc.pos_lnum in
  let cols =
    pos'.loc.pos_cnum
    - pos'.loc.pos_bol
    - if lines > 0 then 0 else st.pos.loc.pos_cnum - st.pos.loc.pos_bol
  in
  Buffer.add_string st.buf (String.make (max 0 lines) '\n');
  Buffer.add_string st.buf (String.make (max 0 cols) ' ');
  st.pos <- pos'

let insert st s =
  Buffer.add_string st.buf s;
  let n = String.length s in
  st.pos <-
    { loc = { st.pos.loc with pos_cnum = st.pos.loc.pos_cnum + n }
    ; byte_loc = st.pos.byte_loc - 1
    }

let pred_position { loc; byte_loc } =
  { loc = { loc with pos_cnum = loc.pos_cnum - 1 }; byte_loc = byte_loc - 1 }

let rec rewrite_list st l = List.iter ~f:(rewrite st) l

and rewrite st elt =
  match elt with
  | { desc =
        List
          [ { desc = Atom "@if"; _ }
          ; expr
          ; { desc = List ({ desc = Atom "@then"; loc = _, pos_after_then } :: then_body)
            ; loc = _, pos_after_then_body
            }
          ]
    ; loc = pos, pos'
    } ->
      write st pos;
      if eval_bool st expr
      then (
        skip st pos_after_then;
        rewrite_list st then_body;
        write st (pred_position pos_after_then_body);
        skip st pos')
      else skip st pos'
  | { desc =
        List
          [ { desc = Atom "@if"; _ }
          ; expr
          ; { desc = List ({ desc = Atom "@then"; loc = _, pos_after_then } :: then_body)
            ; loc = _, pos_after_then_body
            }
          ; { desc = List ({ desc = Atom "@else"; loc = _, pos_after_else } :: else_body)
            ; loc = _, pos_after_else_body
            }
          ]
    ; loc = pos, pos'
    } ->
      write st pos;
      if eval_bool st expr
      then (
        skip st pos_after_then;
        rewrite_list st then_body;
        write st (pred_position pos_after_then_body);
        skip st pos')
      else (
        skip st pos_after_else;
        rewrite_list st else_body;
        write st (pred_position pos_after_else_body);
        skip st pos')
  | { desc =
        List
          ({ desc = Atom "@if"; _ }
          :: _
          :: { desc = List ({ desc = Atom "@then"; _ } :: _); _ }
          :: { desc = List ({ desc = Atom "@else"; _ } :: _); _ }
          :: { loc; _ }
          :: _)
    ; _
    } ->
      raise
        (Error (position_of_loc loc, Printf.sprintf "Expecting closing parenthesis.\n"))
  | { desc =
        List
          ({ desc = Atom "@if"; _ }
          :: _
          :: { desc = List ({ desc = Atom "@then"; _ } :: _); _ }
          :: { loc; _ }
          :: _)
    ; _
    } ->
      raise
        (Error
           ( position_of_loc loc
           , Printf.sprintf "Expecting @else clause or closing parenthesis.\n" ))
  | { desc = List ({ desc = Atom "@if"; _ } :: _ :: { loc = pos, pos'; _ } :: _); _ }
  | { desc = List [ { desc = Atom "@if"; _ }; { loc = _, pos; _ } ]; loc = _, pos' } ->
      raise (Error ((pos.loc, pos'.loc), Printf.sprintf "Expecting @then clause.\n"))
  | { desc = List [ { desc = Atom "@if"; loc = _, pos } ]; loc = _, pos' } ->
      raise (Error ((pos.loc, pos'.loc), Printf.sprintf "Expecting condition.\n"))
  | { desc = List ({ desc = Atom (("@then" | "@else") as nm); loc } :: _); _ } ->
      raise
        (Error
           ( position_of_loc loc
           , Printf.sprintf "Unexpected %s clause. Maybe you forgot a parenthesis.\n" nm
           ))
  | { desc =
        List
          [ { desc = Atom "@string"; _ }
          ; { desc = Atom name; loc = loc_name }
          ; { desc = Atom value; loc = loc_value }
          ]
    ; loc = pos, pos'
    } ->
      if not (is_id name) then raise (Error (position_of_loc loc_name, "Expecting an id"));
      if not (is_string value)
      then raise (Error (position_of_loc loc_value, "Expecting a string"));
      let s = parse_string loc_value value in
      write st pos;
      insert
        st
        (Format.asprintf
           "(global %s (ref eq) (array.new_fixed $bytes %d%a))"
           name
           (String.length s)
           (fun f s ->
             String.iter ~f:(fun c -> Format.fprintf f " (i32.const %d)" (Char.code c)) s)
           s);
      skip st pos'
  | { desc = List [ { desc = Atom "@string"; _ }; { desc = Atom value; loc = loc_value } ]
    ; loc = pos, pos'
    } ->
      if not (is_string value)
      then raise (Error (position_of_loc loc_value, "Expecting a string"));
      let s = parse_string loc_value value in
      write st pos;
      insert
        st
        (Format.asprintf
           "(array.new_fixed $bytes %d%a)"
           (String.length s)
           (fun f s ->
             String.iter ~f:(fun c -> Format.fprintf f " (i32.const %d)" (Char.code c)) s)
           s);
      skip st pos'
  | { desc = List [ { desc = Atom "@string"; loc = _, pos } ]; loc = _, pos' } ->
      raise (Error ((pos.loc, pos'.loc), Printf.sprintf "Expecting an id or a string.\n"))
  | { desc = List ({ desc = Atom "@string"; _ } :: _ :: _ :: { loc; _ } :: _); _ } ->
      raise
        (Error (position_of_loc loc, Printf.sprintf "Expecting a closing parenthesis.\n"))
  | { desc = List [ { desc = Atom "@char"; _ }; { desc = Atom value; loc = loc_value } ]
    ; loc = pos, pos'
    } ->
      if
        (not (is_string value))
        ||
        let s = parse_string loc_value value in
        String.length s <> 1 || Char.code s.[0] > 127
      then raise (Error (position_of_loc loc_value, "Expecting an ASCII character"));
      let s = parse_string loc_value value in
      write st pos;
      insert st (Format.asprintf "(i32.const %d)" (Char.code s.[0]));
      skip st pos'
  | { desc = List [ { desc = Atom "@char"; loc = _, pos } ]; loc = _, pos' } ->
      raise (Error ((pos.loc, pos'.loc), Printf.sprintf "Expecting a string.\n"))
  | { desc = List ({ desc = Atom "@char"; _ } :: _ :: _ :: { loc; _ } :: _); _ } ->
      raise
        (Error (position_of_loc loc, Printf.sprintf "Expecting a closing parenthesis.\n"))
  | { desc =
        List
          ({ desc = Atom "func"; loc = _, pos }
          :: { desc =
                 List
                   [ { desc = Atom "export"; _ }
                   ; { desc = Atom export_name; loc = export_loc }
                   ]
             ; loc = pos', _
             }
          :: l)
    ; _
    }
    when variable_is_set st "name-wasm-functions"
         && is_id ("$" ^ parse_string export_loc export_name) ->
      write st pos;
      insert st (Printf.sprintf " $%s " (parse_string export_loc export_name));
      skip st pos';
      rewrite_list st l
  | { desc = List l; _ } -> rewrite_list st l
  | _ -> ()

(****)

let ocaml_version =
  Scanf.sscanf Sys.ocaml_version "%d.%d.%d" (fun major minor patchlevel ->
      Version (major, minor, patchlevel))

let default_settings = [ "name-wasm-functions", Bool true ]

let f ~variables ~filename ~contents:text =
  let variables =
    List.fold_left
      ~f:(fun m (k, v) -> StringMap.add k v m)
      ~init:StringMap.empty
      (default_settings @ variables)
  in
  let variables = StringMap.add "ocaml_version" ocaml_version variables in
  let lexbuf = Sedlexing.Utf8.from_string text in
  Sedlexing.set_filename lexbuf filename;
  try
    let t, (pos, end_pos) = parse lexbuf in
    let st = { text; pos; variables; buf = Buffer.create (String.length text) } in
    rewrite_list st t;
    write st end_pos;
    Buffer.contents st.buf
  with Error (loc, msg) -> report_error loc msg

type source =
  | Binary
  | File
  | Contents of string

type input =
  { module_name : string
  ; file : string
  ; source : source
  }

let with_preprocessed_files ~variables ~inputs action =
  List.fold_left
    ~f:(fun cont { module_name; file; source } inputs ->
      match
        match source with
        | Binary -> None
        | File ->
            if Link.Wasm_binary.check_file ~file then None else Some (Fs.read_file file)
        | Contents contents -> Some contents
      with
      | None -> cont ({ Binaryen.module_name; file; source_map_file = None } :: inputs)
      | Some contents ->
          let source_file = file in
          Fs.with_intermediate_file (Filename.temp_file module_name ".wat")
          @@ fun file ->
          Fs.write_file
            ~name:file
            ~contents:
              (if Link.Wasm_binary.check ~contents
               then contents
               else f ~variables ~filename:source_file ~contents);
          cont ({ Binaryen.module_name; file; source_map_file = None } :: inputs))
    ~init:action
    inputs
    []