Skip to content

Commit b78e277

Browse files
committed
Optimize sourcemap processing during linking
1 parent 9fea74c commit b78e277

File tree

1 file changed

+108
-56
lines changed

1 file changed

+108
-56
lines changed

compiler/lib/link_js.ml

Lines changed: 108 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ module Line_reader : sig
3535

3636
val peek : t -> string option
3737

38-
val drop : t -> unit
38+
val drop : t -> drop_action:(unit -> unit) -> unit
39+
(** [drop_action] is the function to call if a line was effectively dropped
40+
(if EOF is reached, this function may return without dropping a line). *)
3941

4042
val close : t -> unit
4143

@@ -78,15 +80,17 @@ end = struct
7880
Some s
7981
with End_of_file -> None)
8082

81-
let drop t =
83+
let drop t ~drop_action =
8284
match t.next with
8385
| Some _ ->
8486
t.next <- None;
85-
t.lnum <- t.lnum + 1
87+
t.lnum <- t.lnum + 1;
88+
drop_action ()
8689
| None -> (
8790
try
8891
let (_ : string) = input_line t.ic in
89-
t.lnum <- t.lnum + 1
92+
t.lnum <- t.lnum + 1;
93+
drop_action ()
9094
with End_of_file -> ())
9195

9296
let lnum t = t.lnum
@@ -99,9 +103,15 @@ module Line_writer : sig
99103

100104
val of_channel : out_channel -> t
101105

102-
val write : ?source:Line_reader.t -> t -> string -> unit
106+
val write : ?source:Line_reader.t -> t -> add:(int -> unit) -> string -> unit
107+
(** [write ~source t s ~add] writes [s], followed by a newline, and calls
108+
[edit], giving it in argument the number of "line number" pragma lines
109+
emitted before writing [s]. *)
103110

104-
val write_lines : ?source:Line_reader.t -> t -> string -> unit
111+
val write_lines : ?source:Line_reader.t -> t -> total:(int -> unit) -> string -> unit
112+
(** [write_lines ~source t s ~total] writes all lines in [s], ensures that the last
113+
line ends with a newline, and calls [total], giving it in argument the total number
114+
of lines written, including "line number" pragma lines. *)
105115

106116
val lnum : t -> int
107117
end = struct
@@ -113,7 +123,7 @@ end = struct
113123

114124
let of_channel oc = { oc; source = None; lnum = 0 }
115125

116-
let write ?source t s =
126+
let write ?source t ~add s =
117127
let source =
118128
match source with
119129
| None -> None
@@ -130,21 +140,24 @@ end = struct
130140
| Some (fname1, lnum1), Some (fname2, lnum2) ->
131141
if String.equal fname1 fname2 && lnum1 + 1 = lnum2 then 0 else emit fname2 lnum2
132142
in
143+
add lnum_off;
133144
output_string t.oc s;
134145
output_string t.oc "\n";
135146
let lnum_off = lnum_off + 1 in
136147
t.source <- source;
137148
t.lnum <- t.lnum + lnum_off
138149

139-
let write_lines ?source t lines =
150+
let write_lines ?source t ~total lines =
140151
let l = String.split_on_char ~sep:'\n' lines in
152+
let lcount = ref 0 in
141153
let rec w = function
142154
| [ "" ] | [] -> ()
143155
| s :: xs ->
144-
write ?source t s;
156+
let () = write ?source t s ~add:(fun n -> lcount := !lcount + n + 1) in
145157
w xs
146158
in
147-
w l
159+
w l;
160+
total !lcount
148161

149162
let lnum t = t.lnum
150163
end
@@ -194,41 +207,41 @@ let action ~resolve_sourcemap_url ~drop_source_map file line =
194207
Source_map (rule_out_index_map (Source_map.of_string content))
195208

196209
module Units : sig
197-
val read : Line_reader.t -> Unit_info.t -> Unit_info.t
210+
val read : Line_reader.t -> drop_action:(unit -> unit) -> Unit_info.t -> Unit_info.t
198211

199212
val scan_file : string -> Build_info.t option * Unit_info.t list
200213
end = struct
201-
let rec read ic uinfo =
214+
let rec read ic ~drop_action uinfo =
202215
match Line_reader.peek ic with
203216
| None -> uinfo
204217
| Some line -> (
205218
match Unit_info.parse uinfo line with
206219
| None -> uinfo
207220
| Some uinfo ->
208-
Line_reader.drop ic;
209-
read ic uinfo)
221+
Line_reader.drop ~drop_action ic;
222+
read ic ~drop_action uinfo)
210223

211-
let find_unit_info ic =
224+
let find_unit_info ~drop_action ic =
212225
let rec find_next ic =
213226
match Line_reader.peek ic with
214227
| None -> None
215228
| Some line -> (
216229
match prefix_kind line with
217230
| `Json_base64 _ | `Url _ | `Other | `Build_info _ ->
218-
Line_reader.drop ic;
231+
Line_reader.drop ~drop_action ic;
219232
find_next ic
220-
| `Unit -> Some (read ic Unit_info.empty))
233+
| `Unit -> Some (read ic ~drop_action Unit_info.empty))
221234
in
222235
find_next ic
223236

224-
let find_build_info ic =
237+
let find_build_info ~drop_action ic =
225238
let rec find_next ic =
226239
match Line_reader.peek ic with
227240
| None -> None
228241
| Some line -> (
229242
match prefix_kind line with
230243
| `Json_base64 _ | `Url _ | `Other ->
231-
Line_reader.drop ic;
244+
Line_reader.drop ~drop_action ic;
232245
find_next ic
233246
| `Build_info bi -> Some bi
234247
| `Unit -> None)
@@ -237,12 +250,13 @@ end = struct
237250

238251
let scan_file file =
239252
let ic = Line_reader.open_ file in
253+
let drop_action () = () in
240254
let rec scan_all ic acc =
241-
match find_unit_info ic with
255+
match find_unit_info ~drop_action ic with
242256
| None -> List.rev acc
243257
| Some x -> scan_all ic (x :: acc)
244258
in
245-
let build_info = find_build_info ic in
259+
let build_info = find_build_info ~drop_action ic in
246260
let units = scan_all ic [] in
247261
Line_reader.close ic;
248262
build_info, units
@@ -323,12 +337,28 @@ let link ~output ~linkall ~mklib ~toplevel ~files ~resolve_sourcemap_url ~source
323337
in
324338
let sm_for_file = ref None in
325339
let ic = Line_reader.open_ file in
326-
let skip ic = Line_reader.drop ic in
327-
let reloc = ref [] in
340+
let old_line_count = Line_writer.lnum oc in
341+
let edits = ref [] in
342+
let emit_drop_action edits () = edits := Source_map.Line_edits.Drop :: !edits in
343+
let skip ic = Line_reader.drop ~drop_action:(emit_drop_action edits) ic in
328344
let copy ic oc =
329345
let line = Line_reader.next ic in
330-
Line_writer.write ~source:ic oc line;
331-
reloc := (Line_reader.lnum ic, Line_writer.lnum oc) :: !reloc
346+
Line_writer.write
347+
~source:ic
348+
~add:(fun count -> edits := Add { count } :: !edits)
349+
oc
350+
line;
351+
(* Note: line actions are in reverse order compared to the actual generated
352+
lines *)
353+
edits := Source_map.Line_edits.Keep :: !edits
354+
in
355+
let write_line oc str =
356+
Line_writer.write oc str ~add:(fun count ->
357+
edits := Source_map.Line_edits.(Add { count = count + 1 }) :: !edits)
358+
in
359+
let write_lines oc str =
360+
Line_writer.write_lines oc str ~total:(fun count ->
361+
edits := Source_map.Line_edits.(Add { count }) :: !edits)
332362
in
333363
let rec read () =
334364
match Line_reader.peek ic with
@@ -347,11 +377,13 @@ let link ~output ~linkall ~mklib ~toplevel ~files ~resolve_sourcemap_url ~source
347377
if not !build_info_emitted
348378
then (
349379
let bi = Build_info.with_kind bi (if mklib then `Cma else `Unknown) in
350-
Line_writer.write_lines oc (Build_info.to_string bi);
380+
write_lines oc (Build_info.to_string bi);
351381
build_info_emitted := true)
352382
| Drop -> skip ic
353383
| Unit ->
354-
let u = Units.read ic Unit_info.empty in
384+
let u =
385+
Units.read ic ~drop_action:(emit_drop_action edits) Unit_info.empty
386+
in
355387
if StringSet.cardinal (StringSet.inter u.Unit_info.provides to_link) > 0
356388
then (
357389
if u.effects_without_cps && not !warn_effects
@@ -363,7 +395,7 @@ let link ~output ~linkall ~mklib ~toplevel ~files ~resolve_sourcemap_url ~source
363395
(if mklib
364396
then
365397
let u = if linkall then { u with force_link = true } else u in
366-
Line_writer.write_lines oc (Unit_info.to_string u));
398+
write_lines oc (Unit_info.to_string u));
367399
let size = ref 0 in
368400
while
369401
match Line_reader.peek ic with
@@ -407,7 +439,7 @@ let link ~output ~linkall ~mklib ~toplevel ~files ~resolve_sourcemap_url ~source
407439
read ()
408440
in
409441
read ();
410-
Line_writer.write oc "";
442+
write_line oc "";
411443
Line_reader.close ic;
412444
(match is_runtime with
413445
| None -> ()
@@ -429,10 +461,11 @@ let link ~output ~linkall ~mklib ~toplevel ~files ~resolve_sourcemap_url ~source
429461
(Parse_bytecode.Debug.create ~include_cmis:false false)
430462
code;
431463
let content = Buffer.contents b in
432-
Line_writer.write_lines oc content);
464+
write_lines oc content);
433465
(match !sm_for_file with
434466
| None -> ()
435-
| Some x -> sm := (x, !reloc) :: !sm);
467+
| Some x ->
468+
sm := (file, x, List.rev !edits, Line_writer.lnum oc - old_line_count) :: !sm);
436469
match !build_info, build_info_for_file with
437470
| None, None -> ()
438471
| Some _, None -> ()
@@ -445,32 +478,51 @@ let link ~output ~linkall ~mklib ~toplevel ~files ~resolve_sourcemap_url ~source
445478
match source_map with
446479
| None -> ()
447480
| Some (file, init_sm) ->
448-
let sm =
449-
List.rev_map !sm ~f:(fun (sm, reloc) ->
450-
let tbl = Hashtbl.create 17 in
451-
List.iter reloc ~f:(fun (a, b) -> Hashtbl.add tbl a b);
452-
Source_map.filter_map sm ~f:(Hashtbl.find_opt tbl))
481+
let sourcemaps_and_line_counts =
482+
List.rev_map !sm ~f:(fun (file, sm, edits, lcount) ->
483+
if debug ()
484+
then (
485+
Format.eprintf "@[<v>line actions for '%s' (lcount %d)@," file lcount;
486+
Format.eprintf "%a@," Source_map.Line_edits.pp edits;
487+
Format.eprintf "@]");
488+
let mappings = sm.Source_map.mappings in
489+
let mappings = Source_map.Mappings.edit ~strict:false mappings edits in
490+
{ sm with mappings }, lcount)
453491
in
454-
(match Source_map.merge (init_sm :: sm) with
455-
| None -> ()
456-
| Some sm -> (
457-
(* preserve some info from [init_sm] *)
458-
let sm =
459-
{ sm with
460-
version = init_sm.version
461-
; file = init_sm.file
462-
; sourceroot = init_sm.sourceroot
463-
}
464-
in
465-
match file with
466-
| None ->
467-
let data = Source_map.to_string sm in
468-
let s = sourceMappingURL_base64 ^ Base64.encode_exn data in
469-
Line_writer.write oc s
470-
| Some file ->
471-
Source_map.to_file sm file;
472-
let s = sourceMappingURL ^ Filename.basename file in
473-
Line_writer.write oc s));
492+
let merged_sourcemap =
493+
let open Source_map in
494+
assert (String.equal (Mappings.to_string init_sm.mappings) "");
495+
{ version = init_sm.version
496+
; file = init_sm.file
497+
; Index.sections =
498+
(let _, sections =
499+
List.fold_left
500+
sourcemaps_and_line_counts
501+
~f:(fun (cur_ofs, sections) (sm, generated_line_count) ->
502+
let offset = Index.{ gen_line = cur_ofs; gen_column = 0 } in
503+
cur_ofs + generated_line_count, (offset, `Map sm) :: sections)
504+
~init:(0, [])
505+
in
506+
List.rev sections)
507+
}
508+
in
509+
(* preserve some info from [init_sm] *)
510+
let merged_sourcemap =
511+
{ merged_sourcemap with
512+
sections =
513+
List.map merged_sourcemap.sections ~f:(fun (ofs, `Map sm) ->
514+
ofs, `Map { sm with sourceroot = init_sm.sourceroot })
515+
}
516+
in
517+
(match file with
518+
| None ->
519+
let data = Source_map.Index.to_string merged_sourcemap in
520+
let s = sourceMappingURL_base64 ^ Base64.encode_exn data in
521+
Line_writer.write oc s ~add:(fun _ -> ()) |> ignore
522+
| Some file ->
523+
Source_map.Index.to_file merged_sourcemap file;
524+
let s = sourceMappingURL ^ Filename.basename file in
525+
Line_writer.write oc s ~add:(fun _ -> ()) |> ignore);
474526
if times () then Format.eprintf " sourcemap: %a@." Timer.print t
475527

476528
let link ~output ~linkall ~mklib ~toplevel ~files ~resolve_sourcemap_url ~source_map =

0 commit comments

Comments
 (0)