Skip to content

Commit d8bb665

Browse files
authored
Merge pull request #27 from OlivierNicole/sourcemaps
Sourcemap support for wasm
2 parents 8d259a2 + 628d4e0 commit d8bb665

25 files changed

Lines changed: 373 additions & 189 deletions

compiler/bin-wasm_of_ocaml/cmd_arg.ml

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type t =
2828
; runtime_files : string list
2929
; output_file : string * bool
3030
; input_file : string
31+
; enable_source_maps : bool
3132
; params : (string * string) list
3233
}
3334

@@ -50,11 +51,11 @@ let options =
5051
Arg.(value & opt (some (enum profile)) None & info [ "opt" ] ~docv:"NUM" ~doc)
5152
in
5253
let no_sourcemap =
53-
let doc = "Currently ignored (for compatibility with Js_of_ocaml)." in
54+
let doc = "Disable sourcemap output." in
5455
Arg.(value & flag & info [ "no-sourcemap"; "no-source-map" ] ~doc)
5556
in
5657
let sourcemap =
57-
let doc = "Currently ignored (for compatibility with Js_of_ocaml)." in
58+
let doc = "Output source locations in a separate sourcemap file." in
5859
Arg.(value & flag & info [ "sourcemap"; "source-map" ] ~doc)
5960
in
6061
let sourcemap_inline_in_js =
@@ -69,24 +70,42 @@ let options =
6970
& opt_all (list (pair ~sep:'=' (enum all) string)) []
7071
& info [ "set" ] ~docv:"PARAM=VALUE" ~doc)
7172
in
72-
let build_t common set_param profile _ _ _ output_file input_file runtime_files =
73+
let build_t
74+
common
75+
set_param
76+
profile
77+
sourcemap
78+
no_sourcemap
79+
_
80+
output_file
81+
input_file
82+
runtime_files =
7383
let chop_extension s = try Filename.chop_extension s with Invalid_argument _ -> s in
7484
let output_file =
7585
match output_file with
7686
| Some s -> s, true
7787
| None -> chop_extension input_file ^ ".js", false
7888
in
7989
let params : (string * string) list = List.flatten set_param in
80-
`Ok { common; params; profile; output_file; input_file; runtime_files }
90+
let enable_source_maps = (not no_sourcemap) && sourcemap in
91+
`Ok
92+
{ common
93+
; params
94+
; profile
95+
; output_file
96+
; input_file
97+
; runtime_files
98+
; enable_source_maps
99+
}
81100
in
82101
let t =
83102
Term.(
84103
const build_t
85104
$ Jsoo_cmdline.Arg.t
86105
$ set_param
87106
$ profile
88-
$ no_sourcemap
89107
$ sourcemap
108+
$ no_sourcemap
90109
$ sourcemap_inline_in_js
91110
$ output_file
92111
$ input_file

compiler/bin-wasm_of_ocaml/cmd_arg.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type t =
2626
; runtime_files : string list
2727
; output_file : string * bool
2828
; input_file : string
29+
; enable_source_maps : bool
2930
; params : (string * string) list
3031
}
3132

compiler/bin-wasm_of_ocaml/compile.ml

Lines changed: 75 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,19 @@ let common_binaryen_options () =
8484
in
8585
if Config.Flag.pretty () then "-g" :: l else l
8686

87-
let link runtime_files input_file output_file =
87+
let link ~enable_source_maps runtime_files input_file output_file =
8888
command
8989
("wasm-merge"
9090
:: (common_binaryen_options ()
9191
@ List.flatten
9292
(List.map
9393
~f:(fun runtime_file -> [ Filename.quote runtime_file; "env" ])
9494
runtime_files)
95-
@ [ Filename.quote input_file; "exec"; "-o"; Filename.quote output_file ]))
95+
@ [ Filename.quote input_file; "exec"; "-o"; Filename.quote output_file ]
96+
@
97+
if enable_source_maps
98+
then [ "--output-source-map"; Filename.quote (output_file ^ ".map") ]
99+
else []))
96100

97101
let generate_dependencies primitives =
98102
Yojson.Basic.to_string
@@ -120,7 +124,7 @@ let filter_unused_primitives primitives usage_file =
120124
with End_of_file -> ());
121125
!s
122126

123-
let dead_code_elimination in_file out_file =
127+
let dead_code_elimination ~enable_source_maps in_file out_file =
124128
with_intermediate_file (Filename.temp_file "deps" ".json")
125129
@@ fun deps_file ->
126130
with_intermediate_file (Filename.temp_file "usage" ".txt")
@@ -130,14 +134,15 @@ let dead_code_elimination in_file out_file =
130134
command
131135
("wasm-metadce"
132136
:: (common_binaryen_options ()
133-
@ [ "--graph-file"
134-
; Filename.quote deps_file
135-
; Filename.quote in_file
136-
; "-o"
137-
; Filename.quote out_file
138-
; ">"
139-
; Filename.quote usage_file
140-
]));
137+
@ [ "--graph-file"; Filename.quote deps_file; Filename.quote in_file ]
138+
@ (if enable_source_maps
139+
then [ "--input-source-map"; Filename.quote (in_file ^ ".map") ]
140+
else [])
141+
@ [ "-o"; Filename.quote out_file ]
142+
@ (if enable_source_maps
143+
then [ "--output-source-map"; Filename.quote (out_file ^ ".map") ]
144+
else [])
145+
@ [ ">"; Filename.quote usage_file ]));
141146
filter_unused_primitives primitives usage_file
142147

143148
let optimization_options =
@@ -146,29 +151,62 @@ let optimization_options =
146151
; [ "-O3"; "--traps-never-happen" ]
147152
|]
148153

149-
let optimize ~profile in_file out_file =
154+
let optimize ~profile ?sourcemap_file in_file out_file =
150155
let level =
151156
match profile with
152157
| None -> 1
153158
| Some p -> fst (List.find ~f:(fun (_, p') -> Poly.equal p p') Driver.profiles)
154159
in
155160
command
156161
("wasm-opt"
157-
:: (common_binaryen_options ()
158-
@ optimization_options.(level - 1)
159-
@ [ Filename.quote in_file; "-o"; Filename.quote out_file ]))
162+
:: (common_binaryen_options ()
163+
@ optimization_options.(level - 1)
164+
@ [ Filename.quote in_file; "-o"; Filename.quote out_file ])
165+
@
166+
match sourcemap_file with
167+
| Some sourcemap_file ->
168+
[ "--input-source-map"
169+
; Filename.quote (in_file ^ ".map")
170+
; "--output-source-map"
171+
; Filename.quote sourcemap_file
172+
; "--output-source-map-url"
173+
; Filename.quote sourcemap_file
174+
]
175+
| None -> [])
160176

161-
let link_and_optimize ~profile runtime_wasm_files wat_file output_file =
177+
let link_and_optimize ~profile ?sourcemap_file runtime_wasm_files wat_file output_file =
178+
let sourcemap_file =
179+
(* Check that Binaryen supports the necessary sourcemaps options (requires
180+
version >= 118) *)
181+
match sourcemap_file with
182+
| Some _ when Sys.command "wasm-merge -osm foo 2> /dev/null" <> 0 -> None
183+
| Some _ | None -> sourcemap_file
184+
in
185+
let enable_source_maps = Option.is_some sourcemap_file in
162186
with_intermediate_file (Filename.temp_file "runtime" ".wasm")
163187
@@ fun runtime_file ->
164188
write_file runtime_file Wa_runtime.wasm_runtime;
165189
with_intermediate_file (Filename.temp_file "wasm-merged" ".wasm")
166190
@@ fun temp_file ->
167-
link (runtime_file :: runtime_wasm_files) wat_file temp_file;
191+
link ~enable_source_maps (runtime_file :: runtime_wasm_files) wat_file temp_file;
168192
with_intermediate_file (Filename.temp_file "wasm-dce" ".wasm")
169193
@@ fun temp_file' ->
170-
let primitives = dead_code_elimination temp_file temp_file' in
171-
optimize ~profile temp_file' output_file;
194+
let primitives = dead_code_elimination ~enable_source_maps temp_file temp_file' in
195+
optimize ~profile ?sourcemap_file temp_file' output_file;
196+
(* Add source file contents to source map *)
197+
Option.iter sourcemap_file ~f:(fun sourcemap_file ->
198+
let open Source_map in
199+
let source_map, mappings = Source_map_io.of_file_no_mappings sourcemap_file in
200+
assert (List.is_empty (Option.value source_map.sources_content ~default:[]));
201+
let sources_content =
202+
Some
203+
(List.map source_map.sources ~f:(fun file ->
204+
if Sys.file_exists file && not (Sys.is_directory file)
205+
then Some (Fs.read_file file)
206+
else None))
207+
in
208+
let source_map = { source_map with sources_content } in
209+
Source_map_io.to_file ?mappings source_map ~file:sourcemap_file);
172210
primitives
173211

174212
let escape_string s =
@@ -276,7 +314,15 @@ let build_js_runtime primitives (strings, fragments) wasm_file output_file =
276314
^ trim_semi (Buffer.contents fragment_buffer)
277315
^ String.sub s ~pos:(l + 9) ~len:(String.length s - l - 9))
278316

279-
let run { Cmd_arg.common; profile; runtime_files; input_file; output_file; params } =
317+
let run
318+
{ Cmd_arg.common
319+
; profile
320+
; runtime_files
321+
; input_file
322+
; output_file
323+
; enable_source_maps
324+
; params
325+
} =
280326
Jsoo_cmdline.Arg.eval common;
281327
Wa_generate.init ();
282328
let output_file = fst output_file in
@@ -316,7 +362,7 @@ let run { Cmd_arg.common; profile; runtime_files; input_file; output_file; param
316362
let need_debug = Config.Flag.debuginfo () in
317363
let output (one : Parse_bytecode.one) ~standalone ch =
318364
let code = one.code in
319-
let live_vars, in_cps, p =
365+
let live_vars, in_cps, p, debug =
320366
Driver.f
321367
~target:Wasm
322368
~standalone
@@ -326,7 +372,7 @@ let run { Cmd_arg.common; profile; runtime_files; input_file; output_file; param
326372
one.debug
327373
code
328374
in
329-
let strings = Wa_generate.f ch ~live_vars ~in_cps p in
375+
let strings = Wa_generate.f ch ~debug ~live_vars ~in_cps p in
330376
if times () then Format.eprintf "compilation: %a@." Timer.print t;
331377
strings
332378
in
@@ -367,7 +413,13 @@ let run { Cmd_arg.common; profile; runtime_files; input_file; output_file; param
367413
@@ fun tmp_wasm_file ->
368414
let strings = output_gen wat_file (output code ~standalone:true) in
369415
let primitives =
370-
link_and_optimize ~profile runtime_wasm_files wat_file tmp_wasm_file
416+
link_and_optimize
417+
~profile
418+
?sourcemap_file:
419+
(if enable_source_maps then Some (wasm_file ^ ".map") else None)
420+
runtime_wasm_files
421+
wat_file
422+
tmp_wasm_file
371423
in
372424
build_js_runtime primitives strings wasm_file output_file
373425
| `Cmo _ | `Cma _ -> assert false);

compiler/bin-wasm_of_ocaml/dune

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,4 @@
4343
(install
4444
(section man)
4545
(package wasm_of_ocaml-compiler)
46-
(files
47-
wasm_of_ocaml.1))
46+
(files wasm_of_ocaml.1))

compiler/lib/driver.ml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,9 @@ let configure formatter =
576576

577577
type 'a target =
578578
| JavaScript : Pretty_print.t -> Source_map.t option target
579-
| Wasm : (Deadcode.variable_uses * Effects.in_cps * Code.program) target
579+
| Wasm
580+
: (Deadcode.variable_uses * Effects.in_cps * Code.program * Parse_bytecode.Debug.t)
581+
target
580582

581583
let target_flag (type a) (t : a target) =
582584
match t with
@@ -631,7 +633,7 @@ let full
631633
source_map
632634
| Wasm ->
633635
let (p, live_vars), _, in_cps = r in
634-
live_vars, in_cps, p
636+
live_vars, in_cps, p, d
635637

636638
let full_no_source_map ~formatter ~standalone ~wrap_with_fun ~profile ~linkall d p =
637639
let (_ : Source_map.t option) =

compiler/lib/driver.mli

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ type profile
2222

2323
type 'a target =
2424
| JavaScript : Pretty_print.t -> Source_map.t option target
25-
| Wasm : (Deadcode.variable_uses * Effects.in_cps * Code.program) target
25+
| Wasm
26+
: (Deadcode.variable_uses * Effects.in_cps * Code.program * Parse_bytecode.Debug.t)
27+
target
2628

2729
val f :
2830
target:'result target

compiler/lib/generate.ml

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -341,11 +341,14 @@ let bool e = J.ECond (e, one, zero)
341341

342342
(****)
343343

344-
let source_location ctx ?force (pc : Code.loc) =
345-
match Parse_bytecode.Debug.find_loc ctx.Ctx.debug ?force pc with
344+
let source_location debug ?force (pc : Code.loc) =
345+
match Parse_bytecode.Debug.find_loc debug ?force pc with
346346
| Some pi -> J.Pi pi
347347
| None -> J.N
348348

349+
let source_location_ctx ctx ?force (pc : Code.loc) =
350+
source_location ctx.Ctx.debug ?force pc
351+
349352
(****)
350353

351354
let float_const f = J.ENum (J.Num.of_float f)
@@ -1240,13 +1243,13 @@ let rec translate_expr ctx queue loc x e level : _ * J.statement_list =
12401243
let (px, cx), queue = access_queue queue x in
12411244
(Mlvalue.Block.field cx n, or_p px mutable_p, queue), []
12421245
| Closure (args, ((pc, _) as cont)) ->
1243-
let loc = source_location ctx ~force:After (After pc) in
1246+
let loc = source_location_ctx ctx ~force:After (After pc) in
12441247
let clo = compile_closure ctx cont in
12451248
let clo =
12461249
match clo with
12471250
| (st, x) :: rem ->
12481251
let loc =
1249-
match x, source_location ctx (Before pc) with
1252+
match x, source_location_ctx ctx (Before pc) with
12501253
| (J.U | J.N), (J.U | J.N) -> J.U
12511254
| x, (J.U | J.N) -> x
12521255
| (J.U | J.N), x -> x
@@ -1495,14 +1498,14 @@ and translate_instr ctx expr_queue instr =
14951498
let instr, pc = instr in
14961499
match instr with
14971500
| Assign (x, y) ->
1498-
let loc = source_location ctx pc in
1501+
let loc = source_location_ctx ctx pc in
14991502
let (_py, cy), expr_queue = access_queue expr_queue y in
15001503
flush_queue
15011504
expr_queue
15021505
mutator_p
15031506
[ J.Expression_statement (J.EBin (J.Eq, J.EVar (J.V x), cy)), loc ]
15041507
| Let (x, e) -> (
1505-
let loc = source_location ctx pc in
1508+
let loc = source_location_ctx ctx pc in
15061509
let (ce, prop, expr_queue), instrs = translate_expr ctx expr_queue loc x e 0 in
15071510
let keep_name x =
15081511
match Code.Var.get_name x with
@@ -1533,23 +1536,23 @@ and translate_instr ctx expr_queue instr =
15331536
prop
15341537
(instrs @ [ J.variable_declaration [ J.V x, (ce, loc) ], loc ]))
15351538
| Set_field (x, n, y) ->
1536-
let loc = source_location ctx pc in
1539+
let loc = source_location_ctx ctx pc in
15371540
let (_px, cx), expr_queue = access_queue expr_queue x in
15381541
let (_py, cy), expr_queue = access_queue expr_queue y in
15391542
flush_queue
15401543
expr_queue
15411544
mutator_p
15421545
[ J.Expression_statement (J.EBin (J.Eq, Mlvalue.Block.field cx n, cy)), loc ]
15431546
| Offset_ref (x, 1) ->
1544-
let loc = source_location ctx pc in
1547+
let loc = source_location_ctx ctx pc in
15451548
(* FIX: may overflow.. *)
15461549
let (_px, cx), expr_queue = access_queue expr_queue x in
15471550
flush_queue
15481551
expr_queue
15491552
mutator_p
15501553
[ J.Expression_statement (J.EUn (J.IncrA, Mlvalue.Block.field cx 0)), loc ]
15511554
| Offset_ref (x, n) ->
1552-
let loc = source_location ctx pc in
1555+
let loc = source_location_ctx ctx pc in
15531556
(* FIX: may overflow.. *)
15541557
let (_px, cx), expr_queue = access_queue expr_queue x in
15551558
flush_queue
@@ -1558,7 +1561,7 @@ and translate_instr ctx expr_queue instr =
15581561
[ J.Expression_statement (J.EBin (J.PlusEq, Mlvalue.Block.field cx 0, int n)), loc
15591562
]
15601563
| Array_set (x, y, z) ->
1561-
let loc = source_location ctx pc in
1564+
let loc = source_location_ctx ctx pc in
15621565
let (_px, cx), expr_queue = access_queue expr_queue x in
15631566
let (_py, cy), expr_queue = access_queue expr_queue y in
15641567
let (_pz, cz), expr_queue = access_queue expr_queue z in
@@ -1619,7 +1622,7 @@ and compile_block st queue (pc : Addr.t) loop_stack frontier interm =
16191622
else (
16201623
if debug () then Format.eprintf "break;@;}@]@,";
16211624
body @ [ J.Break_statement None, J.N ])) )
1622-
, source_location st.ctx (Code.location_of_pc pc) )
1625+
, source_location_ctx st.ctx (Code.location_of_pc pc) )
16231626
in
16241627
let label = if !lab_used then Some lab else None in
16251628
let for_loop =
@@ -1854,7 +1857,7 @@ and compile_conditional st queue last loop_stack backs frontier interm =
18541857
| Stop -> Format.eprintf "stop;@;"
18551858
| Cond (x, _, _) -> Format.eprintf "@[<hv 2>cond(%a){@;" Code.Var.print x
18561859
| Switch (x, _, _) -> Format.eprintf "@[<hv 2>switch(%a){@;" Code.Var.print x);
1857-
let loc = source_location st.ctx pc in
1860+
let loc = source_location_ctx st.ctx pc in
18581861
let res =
18591862
match last with
18601863
| Return x ->

0 commit comments

Comments
 (0)