1 (* Copyright (C) 2002-2007 Henry Cejtin, Matthew Fluet, Suresh
2 * Jagannathan, and Stephen Weeks.
4 * MLton is released under a BSD-style license.
5 * See the file MLton-LICENSE for details.
8 functor ImplementProfiling (S: IMPLEMENT_PROFILING_STRUCTS): IMPLEMENT_PROFILING =
21 val gcState = cpointer
25 fun make {args, name, prototype} =
27 convention = Convention.Cdecl,
28 kind = Kind.Runtime {bytesNeeded = NONE,
29 ensuresBytesFree = false,
31 maySwitchThreads = false,
32 modifiesFrontier = false,
34 writesStackTop = false},
35 prototype = (prototype, NONE),
37 symbolScope = SymbolScope.Private,
38 target = Target.Direct name}
40 val profileEnter = fn () =>
41 make {args = Vector.new1 (Type.gcState ()),
42 name = "GC_profileEnter",
43 prototype = Vector.new1 CType.gcState}
44 val profileInc = fn () =>
45 make {args = Vector.new2 (Type.gcState (), Type.csize ()),
46 name = "GC_profileInc",
47 prototype = Vector.new2 (CType.gcState, CType.csize ())}
48 val profileLeave = fn () =>
49 make {args = Vector.new1 (Type.gcState ()),
50 name = "GC_profileLeave",
51 prototype = Vector.new1 CType.gcState}
55 type sourceSeq = int list
59 datatype t = T of {info: SourceInfo.t,
62 successors: t list ref}
65 fun make f (T r) = f r
68 val sourcesIndex = make #sourcesIndex
71 fun layout (T {info, ...}) =
72 Layout.record [("info", SourceInfo.layout info)]
74 fun equals (n: t, n': t): bool = SourceInfo.equals (info n, info n')
76 fun call {from = T {successors, ...},
77 to as T {info = i', ...}} =
82 orelse equals (i', main)
83 orelse equals (i', unknown)
84 end orelse List.exists (!successors, fn n => equals (n, to))
86 else List.push (successors, to)
89 Trace.trace ("Profile.InfoNode.call",
91 Layout.record [("from", layout from),
99 datatype t = T of {callers: InfoNode.t list ref,
100 enters: InfoNode.t list ref,
102 tailCalls: t list ref}
104 fun new () = T {callers = ref [],
114 | Skip of SourceInfo.t
121 Enter n => seq [str "Enter ", InfoNode.layout n]
122 | Skip i => seq [str "Skip ", SourceInfo.layout i]
125 fun toSources (ps: t list): int list =
126 List.fold (rev ps, [], fn (p, ac) =>
128 Enter (InfoNode.T {sourcesIndex, ...}) =>
134 Trace.trace2 ("Profile.enter",
135 List.layout Push.layout,
137 Layout.tuple2 (List.layout Push.layout, Bool.layout))
140 if !Control.profile = Control.ProfileNone
141 then (program, fn _ => NONE)
144 val Program.T {functions, handlesSignals, main, objectTypes} = program
146 datatype z = datatype Control.profile
147 val profile = !Control.profile
148 val profileStack: bool = !Control.profileStack
149 val needProfileLabels: bool =
150 profile = ProfileTimeLabel orelse profile = ProfileLabel
151 val needCodeCoverage: bool =
152 needProfileLabels orelse (profile = ProfileTimeField)
153 val frameProfileIndices: (Label.t * int) list ref = ref []
154 val infoNodes: InfoNode.t list ref = ref []
155 val nameCounter = Counter.new 0
156 val names: string list ref = ref []
158 val sourceCounter = Counter.new 0
160 if profile = ProfileCallStack
163 val {get = nameIndex, ...} =
164 Property.get (SourceInfo.plist,
167 (List.push (names, SourceInfo.toString' (si, sep))
168 ; Counter.next nameCounter)))
170 fun sourceInfoNode (si: SourceInfo.t) =
173 InfoNode.T {info = si,
174 nameIndex = nameIndex si,
175 sourcesIndex = Counter.next sourceCounter,
177 val _ = List.push (infoNodes, infoNode)
182 fun firstEnter (ps: Push.t list): InfoNode.t option =
183 List.peekMap (ps, fn p =>
185 Push.Enter n => SOME n
187 (* unknown must be 0, which == SOURCES_INDEX_UNKNOWN from gc.h *)
188 val unknownInfoNode = sourceInfoNode SourceInfo.unknown
189 (* gc must be 1 which == SOURCES_INDEX_GC from gc.h *)
190 val gcInfoNode = sourceInfoNode SourceInfo.gc
191 val mainInfoNode = sourceInfoNode SourceInfo.main
192 fun wantedSource (si: SourceInfo.t): bool =
194 then List.length (!Control.profileC) > 0
195 else (case SourceInfo.file si of
199 (!Control.profileInclExcl, true,
200 fn ((re, keep), b) =>
201 if Regexp.Compiled.matchesAll (re, file)
205 Trace.trace ("Profile.wantedSource", SourceInfo.layout, Bool.layout)
207 fun wantedCSource (si: SourceInfo.t): bool =
212 else (case SourceInfo.file si of
216 (!Control.profileC, false,
218 if Regexp.Compiled.matchesAll (re, file)
222 Trace.trace ("Profile.wantedCSource", SourceInfo.layout, Bool.layout)
224 fun keepSource (si: SourceInfo.t): bool =
225 profile <> ProfileCount
226 orelse wantedSource si
228 Trace.trace ("Profile.keepSource", SourceInfo.layout, Bool.layout)
230 (* With -profile count, we want to get zero counts for all functions,
231 * whether or not they made it into the final executable.
236 List.foreach (SourceInfo.all (), fn si =>
238 then ignore (sourceInfoNode si)
246 if equals (si, unknown)
248 else if equals (si, gc)
250 else if equals (si, main)
252 else sourceInfoNode si
255 Trace.trace ("Profile.sourceInfoNode", SourceInfo.layout, InfoNode.layout)
258 val table: {hash: word,
260 sourceSeq: int vector} HashSet.t =
261 HashSet.new {hash = #hash}
262 val c = Counter.new 0
263 val sourceSeqs: int vector list ref = ref []
265 fun sourceSeqIndex (s: sourceSeq): int =
267 val s = Vector.fromListRev s
269 Vector.fold (s, 0w0, fn (i, w) =>
270 w * 0w31 + Word.fromInt i)
273 (HashSet.lookupOrInsert
275 fn {sourceSeq = s', ...} => s = s',
277 val _ = List.push (sourceSeqs, s)
280 index = Counter.next c,
284 fun makeSourceSeqs () = Vector.fromListRev (!sourceSeqs)
286 (* Ensure that [SourceInfo.unknown] is index 0. *)
287 val _ = sourceSeqIndex [InfoNode.sourcesIndex unknownInfoNode]
288 (* Ensure that [SourceInfo.gc] is index 1. *)
289 val _ = sourceSeqIndex [InfoNode.sourcesIndex gcInfoNode]
290 fun addFrameProfileIndex (label: Label.t,
292 List.push (frameProfileIndices, (label, index))
293 fun addFrameProfilePushes (label: Label.t,
294 pushes: Push.t list): unit =
295 addFrameProfileIndex (label,
296 sourceSeqIndex (Push.toSources pushes))
297 val {get = labelInfo: Label.t -> {block: Block.t,
300 set = setLabelInfo, ...} =
302 (Label.plist, Property.initRaise ("info", Label.layout))
304 fun profileLabelFromIndex (sourceSeqsIndex: int): Statement.t =
306 val l = ProfileLabel.new ()
307 val _ = List.push (labels, {label = l,
308 sourceSeqsIndex = sourceSeqsIndex})
310 Statement.ProfileLabel l
312 fun setCurSourceSeqsIndexFromIndex (sourceSeqsIndex: int): Statement.t =
314 val curSourceSeqsIndex =
315 Operand.Runtime Runtime.GCField.CurSourceSeqsIndex
318 {dst = curSourceSeqsIndex,
319 src = Operand.word (WordX.fromIntInf
320 (IntInf.fromInt sourceSeqsIndex,
323 fun codeCoverageStatementFromIndex (sourceSeqsIndex: int): Statement.t =
325 then profileLabelFromIndex sourceSeqsIndex
326 else if profile = ProfileTimeField
327 then setCurSourceSeqsIndexFromIndex sourceSeqsIndex
328 else Error.bug "Profile.codeCoverageStatement"
329 fun codeCoverageStatement (sourceSeq: int list): Statement.t =
330 codeCoverageStatementFromIndex (sourceSeqIndex sourceSeq)
332 val {get: Func.t -> FuncInfo.t, ...} =
333 Property.get (Func.plist, Property.initFun (fn _ => FuncInfo.new ()))
336 fun addFuncEdges () =
337 (* Don't need to add edges for main because no one calls it. *)
341 val allSeen: bool ref list ref = ref []
342 val func = Function.name f
343 val fi as FuncInfo.T {callers, ...} = get func
344 (* Add edges from all the callers to the enters in f and all
345 * functions that f tail calls.
347 fun call (FuncInfo.T {enters, seen, tailCalls, ...}): unit =
353 val _ = List.push (allSeen, seen)
356 (!callers, fn from =>
359 InfoNode.call {from = from, to = to}))
361 List.foreach (!tailCalls, call)
364 val _ = List.foreach (!allSeen, fn r => r := false)
369 fun doFunction (f: Function.t): Function.t =
371 val {args, blocks, name, raises, returns, start} = Function.dest f
375 else print (concat ["doFunction ", Func.toString name, "\n"])
376 val FuncInfo.T {enters, tailCalls, ...} = funcInfo name
377 fun enter (ps: Push.t list, si: SourceInfo.t): Push.t list * bool =
379 val node = Promise.lazy (fn () => sourceInfoNode si)
380 fun yes () = (Push.Enter (node ()) :: ps, true)
381 fun no () = (Push.Skip si :: ps, false)
383 if SourceInfo.equals (si, SourceInfo.unknown)
386 case firstEnter ps of
389 then (List.push (enters, node ())
392 | SOME (node' as InfoNode.T {info = si', ...}) =>
402 equals (si', unknown)
406 not (equals (si, gcArrayAllocate))
411 andalso not (equals (si', main)))))
413 then (InfoNode.call {from = node', to = node ()}
417 val enter = traceEnter enter
420 (blocks, fn block as Block.T {label, ...} =>
421 setLabelInfo (label, {block = block,
422 visited1 = ref false,
423 visited2 = ref false}))
424 (* Find the first Enter statement and (conceptually) move it to the
425 * front of the function.
428 exception Yes of Label.t * Statement.t
431 val {block, visited1, ...} = labelInfo l
437 val () = visited1 := true
438 val Block.T {statements, transfer, ...} = block
443 Statement.Profile (ProfileExp.Enter _) =>
446 val () = Transfer.foreachLabel (transfer, goto)
452 val first = (goto start; NONE) handle Yes z => SOME z
455 datatype z = datatype Statement.t
456 datatype z = datatype ProfileExp.t
462 statements: Statement.t list,
463 transfer: Transfer.t}: unit =
465 val (_, ncc, sourceSeq, statements) =
468 (leaves, true, sourceSeq, []),
469 fn (s, (leaves, ncc, sourceSeq, ss)) =>
471 Object _ => (leaves, true, sourceSeq, s :: ss)
478 andalso not (List.isEmpty sourceSeq)
480 codeCoverageStatement sourceSeq :: ss)
483 val (leaves, sourceSeq) =
488 "Profile.backward: unmatched Enter"
489 | _ :: sis => (leaves, sis))
493 "Profile.backward: missing Leave"
494 | infoNode :: leaves =>
496 InfoNode.sourcesIndex infoNode
499 (leaves, ncc, sourceSeq, ss)
501 | _ => (leaves, true, sourceSeq, s :: ss))
505 then codeCoverageStatement sourceSeq :: statements
507 val {args, kind, label} =
508 if profileStack andalso (case kind of
510 | Kind.Handler => true
514 val func = CFunction.profileLeave ()
515 val newLabel = Label.newNoname ()
518 (newLabel, sourceSeqIndex sourceSeq)
522 (codeCoverageStatement sourceSeq))
531 statements = statements,
534 {args = Vector.new1 Operand.GCState,
536 return = SOME newLabel}})
538 {args = Vector.new0 (),
539 kind = Kind.CReturn {func = func},
548 Block.T {args = args,
551 statements = Vector.fromList statements,
552 transfer = transfer})
557 fn {leaves, statements, sourceSeq, ...} =>
561 record [("leaves", List.layout InfoNode.layout leaves),
562 ("sourceSeq", List.layout Int.layout sourceSeq),
564 List.layout Statement.layout statements)]
568 fun profileEnter (pushes: Push.t list,
569 transfer: Transfer.t): Transfer.t =
571 val func = CFunction.profileEnter ()
572 val newLabel = Label.newNoname ()
573 val index = sourceSeqIndex (Push.toSources pushes)
574 val _ = addFrameProfileIndex (newLabel, index)
577 then Vector.new1 (codeCoverageStatementFromIndex index)
582 Block.T {args = Vector.new0 (),
583 kind = Kind.CReturn {func = func},
585 statements = statements,
586 transfer = transfer})
588 Transfer.CCall {args = Vector.new1 Operand.GCState,
590 return = SOME newLabel}
592 fun goto (l: Label.t, pushes: Push.t list): unit =
601 outputl (seq [str "goto (",
604 List.layout Push.layout pushes,
608 val {block, visited2, ...} = labelInfo l
614 val _ = visited2 := true
615 val Block.T {args, kind, label, statements, transfer,
620 | SOME (firstLabel, firstEnter) =>
621 if Label.equals (label, firstLabel)
626 Profile (Enter _) => true
628 else if Label.equals (label, start)
631 [Vector.new1 firstEnter,
637 addFrameProfilePushes (label, pushes)
638 datatype z = datatype Kind.t
642 | CReturn {func, ...} =>
644 datatype z = datatype CFunction.Target.t
645 val target = CFunction.target func
647 add (#1 (enter (pushes, si)))
650 Direct "GC_collect" => doit SourceInfo.gc
651 | Direct "GC_arrayAllocate" =>
652 doit SourceInfo.gcArrayAllocate
653 | Direct "MLton_bug" => add pushes
654 | Direct name => doit (SourceInfo.fromC name)
655 | Indirect => doit (SourceInfo.fromC "<indirect>")
657 | Handler => add pushes
660 fun maybeSplit {args,
661 bytesAllocated: Bytes.t,
670 bytesAllocated = Bytes.zero,
674 statements = statements}
677 val newLabel = Label.newNoname ()
679 addFrameProfilePushes (newLabel, pushes)
680 val func = CFunction.profileInc ()
683 ProfileAlloc => Bytes.toInt bytesAllocated
685 | _ => Error.bug "Profile.maybeSplit: amount"
692 (IntInf.fromInt amount,
693 WordSize.csize ())))),
695 return = SOME newLabel}
696 val sourceSeq = Push.toSources pushes
698 backward {args = args,
702 sourceSeq = sourceSeq,
703 statements = statements,
706 {args = Vector.new0 (),
707 bytesAllocated = Bytes.zero,
708 kind = Kind.CReturn {func = func},
713 val {args, bytesAllocated, kind, label, leaves, pushes,
718 bytesAllocated = Bytes.zero,
724 fn (s, {args, bytesAllocated, kind, label,
735 (seq [List.layout Push.layout pushes,
742 Object {size, ...} =>
744 bytesAllocated = Bytes.+ (bytesAllocated, size),
749 statements = s :: statements}
753 profile = ProfileAlloc
754 andalso Bytes.> (bytesAllocated,
756 val {args, bytesAllocated, kind, label,
757 leaves, statements} =
760 bytesAllocated = bytesAllocated,
765 shouldSplit = shouldSplit,
766 statements = statements}
767 datatype z = datatype ProfileExp.t
768 val (pushes, keep, leaves) =
775 (pushes, keep, leaves)
780 "Profile.goto: unmatched Leave"
783 val (keep, si', leaves) =
795 if SourceInfo.equals (si, si')
800 "Profile.goto: mismatched Leave"
803 profile = ProfileCount
807 val {args, bytesAllocated, kind, label,
808 leaves, statements} =
811 bytesAllocated = bytesAllocated,
816 shouldSplit = shouldSplit,
817 statements = statements}
824 bytesAllocated = bytesAllocated,
829 statements = statements}
833 bytesAllocated = bytesAllocated,
838 statements = s :: statements})
841 profile = ProfileAlloc
842 andalso Bytes.> (bytesAllocated, Bytes.zero)
843 val {args, kind, label, leaves, statements, ...} =
844 maybeSplit {args = args,
845 bytesAllocated = bytesAllocated,
850 shouldSplit = shouldSplit,
851 statements = statements}
853 Transfer.foreachLabel
854 (transfer, fn l => goto (l, pushes))
857 Transfer.Call {func, return, ...} =>
859 val fi as FuncInfo.T {callers, ...} =
866 case firstEnter pushes of
868 List.push (tailCalls, fi)
870 List.push (callers, n)
873 then profileEnter (pushes,
878 (List.push (tailCalls, fi)
883 backward {args = args,
887 sourceSeq = Push.toSources pushes,
888 statements = statements,
892 val _ = goto (start, [])
893 val blocks = Vector.fromList (!blocks)
895 Function.new {args = args,
902 val program = Program.T {functions = List.revMap (functions, doFunction),
903 handlesSignals = handlesSignals,
904 main = doFunction main,
905 objectTypes = objectTypes}
906 val _ = addFuncEdges ()
907 val names = Vector.fromListRev (!names)
910 (Vector.fromListRev (!infoNodes),
911 fn InfoNode.T {nameIndex, successors, ...} =>
912 {nameIndex = nameIndex,
913 successorsIndex = (sourceSeqIndex
914 (List.revMap (!successors,
915 InfoNode.sourcesIndex)))})
916 (* makeSourceSeqs () must happen after making sources, since that creates
919 val sourceSeqs = makeSourceSeqs ()
920 fun makeProfileInfo {frames} =
922 val {get, set, ...} =
925 Property.initRaise ("frameProfileIndex", Label.layout))
927 List.foreach (!frameProfileIndices, fn (l, i) =>
929 val frameSources = Vector.map (frames, get)
931 SOME (Machine.ProfileInfo.T
932 {frameSources = frameSources,
933 labels = Vector.fromList (!labels),
935 sourceSeqs = sourceSeqs,
939 (program, makeProfileInfo)