GameLib is a collection of libraries for creating applications in Cakelisp.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

117 lines
5.5 KiB

;; Profiling auto instrumentation
;;
;; By importing this file, every Cakelisp-defined function will automatically have function-level
;; profiling instrumentation added. This is a great way to quickly reason about your program's
;; performance without having to accept the drawbacks of sampling profilers.
;; Note that this does increase code generation time because it requires re-evaluation of every
;; function, which isn't ideal, but gives me incentive to optimize generation more.
;; This feature was inspired by Jonathan Blow's "Demo: Code Modification" presentation:
;; https://www.youtube.com/watch?v=59lKAlb6cRg
(defun-comptime profiling-auto-instrument (environment (& EvaluatorEnvironment)
&return bool)
(get-or-create-comptime-var instrumented-functions bool false)
(when (deref instrumented-functions) ;; Instrument only once
(return true))
(set (deref instrumented-functions) true)
(Log "Profiling: Auto-instrumenting code.\n")
(defstruct replace-definition
name (in std string)
expanded-definition (<> std::vector Token)
source-file (* (const char)))
(var definitions-to-replace (<> std::vector replace-definition))
(for-in definition-pair (& ObjectDefinitionPair) (field environment definitions)
(unless (= (field definition-pair second type) ObjectType_Function)
(continue))
(var new-replace-definition replace-definition (array))
(var definition (& ObjectDefinition) (field definition-pair second))
(unless (CreateDefinitionCopyMacroExpanded definition
(field new-replace-definition expanded-definition))
(return false))
(set (field new-replace-definition name) (field definition name))
(set (field new-replace-definition source-file)
(path definition . definitionInvocation > source))
(call-on push_back definitions-to-replace
;; Make sure we don't copy the tokens and strings *again*
(call (in std move) new-replace-definition)))
;; Note: The pointer is used directly because all tokens in the same source file share the same pointer
(var modules-with-import (<> (in std unordered_map) (* (const char)) int))
(for-in definition-to-replace (& replace-definition) definitions-to-replace
(var expanded-definition (& (<> std::vector Token))
(field definition-to-replace expanded-definition))
;; TODO: Cleaner way to destructure arguments
(var start-token-index int 0)
(var end-invocation-index int (- (call-on size expanded-definition) 1))
(var invocation-index int
(getExpectedArgument "expected invocation"
expanded-definition
start-token-index 0
end-invocation-index))
(when (= invocation-index -1)
(return false))
(var def-name-index int
(getExpectedArgument "expected function name"
expanded-definition
start-token-index 1
end-invocation-index))
(when (= def-name-index -1)
(return false))
(var signature-index int
(getExpectedArgument "expected function signature"
expanded-definition
start-token-index 2
end-invocation-index))
(when (= signature-index -1)
(return false))
(var body-start-index int
(getExpectedArgument "expected function body"
expanded-definition
start-token-index 3
end-invocation-index))
(when (= body-start-index -1)
(return false))
(var invocation-token (& Token) (at invocation-index expanded-definition))
(var def-name-token (& Token) (at def-name-index expanded-definition))
(var signature-token (& Token) (at signature-index expanded-definition))
(var body-start-token (& Token) (at body-start-index expanded-definition))
(var scope-name-token Token def-name-token)
(scope
(var scope-name-buffer ([] 128 char) (array 0))
(PrintfBuffer scope-name-buffer "%s-timing" (call-on c_str (field scope-name-token contents)))
(set (field scope-name-token contents) scope-name-buffer))
(var new-definition (* (<> std::vector Token)) (new (<> std::vector Token)))
(call-on push_back (field environment comptimeTokens) new-definition)
;; TODO: This isn't ideal; force the module to include the profiler
;; TODO: Added the true because definitions aren't in order in file
(when (or true (= (call-on end modules-with-import)
(call-on find modules-with-import (field definition-to-replace source-file))))
(tokenize-push (deref new-definition)
;; TODO: Allow the client to define which profiler to use
(import "Tracy.cake"))
(set (at (field definition-to-replace source-file) modules-with-import) 1))
(tokenize-push (deref new-definition)
((token-splice-addr invocation-token def-name-token signature-token)
(time-this-scope (token-splice-addr scope-name-token))
(token-splice-rest (addr body-start-token) expanded-definition)))
;; (prettyPrintTokens (deref new-definition))
(unless (ReplaceAndEvaluateDefinition environment
(call-on c_str (field definition-to-replace name))
(deref new-definition))
(return false)))
(Log "Profiling: Auto-instrumenting code completed.\n")
(return true))
(add-compile-time-hook post-references-resolved profiling-auto-instrument)