konpeito 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +75 -0
- data/CONTRIBUTING.md +123 -0
- data/LICENSE +21 -0
- data/README.md +257 -0
- data/Rakefile +11 -0
- data/bin/konpeito +6 -0
- data/konpeito.gemspec +43 -0
- data/lib/konpeito/ast/typed_ast.rb +620 -0
- data/lib/konpeito/ast/visitor.rb +78 -0
- data/lib/konpeito/cache/cache_manager.rb +230 -0
- data/lib/konpeito/cache/dependency_graph.rb +192 -0
- data/lib/konpeito/cache.rb +8 -0
- data/lib/konpeito/cli/base_command.rb +187 -0
- data/lib/konpeito/cli/build_command.rb +220 -0
- data/lib/konpeito/cli/check_command.rb +104 -0
- data/lib/konpeito/cli/config.rb +231 -0
- data/lib/konpeito/cli/deps_command.rb +128 -0
- data/lib/konpeito/cli/doctor_command.rb +340 -0
- data/lib/konpeito/cli/fmt_command.rb +199 -0
- data/lib/konpeito/cli/init_command.rb +312 -0
- data/lib/konpeito/cli/lsp_command.rb +40 -0
- data/lib/konpeito/cli/run_command.rb +150 -0
- data/lib/konpeito/cli/test_command.rb +248 -0
- data/lib/konpeito/cli/watch_command.rb +212 -0
- data/lib/konpeito/cli.rb +301 -0
- data/lib/konpeito/codegen/builtin_methods.rb +229 -0
- data/lib/konpeito/codegen/cruby_backend.rb +1090 -0
- data/lib/konpeito/codegen/debug_info.rb +352 -0
- data/lib/konpeito/codegen/inliner.rb +486 -0
- data/lib/konpeito/codegen/jvm_backend.rb +197 -0
- data/lib/konpeito/codegen/jvm_generator.rb +13412 -0
- data/lib/konpeito/codegen/llvm_generator.rb +13191 -0
- data/lib/konpeito/codegen/loop_optimizer.rb +363 -0
- data/lib/konpeito/codegen/monomorphizer.rb +359 -0
- data/lib/konpeito/codegen/profile_runtime.c +341 -0
- data/lib/konpeito/codegen/profiler.rb +99 -0
- data/lib/konpeito/compiler.rb +592 -0
- data/lib/konpeito/dependency_resolver.rb +296 -0
- data/lib/konpeito/diagnostics/collector.rb +127 -0
- data/lib/konpeito/diagnostics/diagnostic.rb +237 -0
- data/lib/konpeito/diagnostics/renderer.rb +144 -0
- data/lib/konpeito/formatter/formatter.rb +1214 -0
- data/lib/konpeito/hir/builder.rb +7167 -0
- data/lib/konpeito/hir/nodes.rb +2465 -0
- data/lib/konpeito/lsp/document_manager.rb +820 -0
- data/lib/konpeito/lsp/server.rb +183 -0
- data/lib/konpeito/lsp/transport.rb +38 -0
- data/lib/konpeito/parser/prism_adapter.rb +65 -0
- data/lib/konpeito/platform.rb +103 -0
- data/lib/konpeito/profile/report.rb +136 -0
- data/lib/konpeito/rbs_inline/preprocessor.rb +199 -0
- data/lib/konpeito/stdlib/compression/compression.rb +72 -0
- data/lib/konpeito/stdlib/compression/compression.rbs +60 -0
- data/lib/konpeito/stdlib/compression/compression_native.c +415 -0
- data/lib/konpeito/stdlib/compression/extconf.rb +19 -0
- data/lib/konpeito/stdlib/crypto/crypto.rb +85 -0
- data/lib/konpeito/stdlib/crypto/crypto.rbs +74 -0
- data/lib/konpeito/stdlib/crypto/crypto_native.c +312 -0
- data/lib/konpeito/stdlib/crypto/extconf.rb +40 -0
- data/lib/konpeito/stdlib/http/extconf.rb +19 -0
- data/lib/konpeito/stdlib/http/http.rb +125 -0
- data/lib/konpeito/stdlib/http/http.rbs +57 -0
- data/lib/konpeito/stdlib/http/http_native.c +440 -0
- data/lib/konpeito/stdlib/json/extconf.rb +17 -0
- data/lib/konpeito/stdlib/json/json.rb +44 -0
- data/lib/konpeito/stdlib/json/json.rbs +33 -0
- data/lib/konpeito/stdlib/json/json_native.c +286 -0
- data/lib/konpeito/stdlib/ui/extconf.rb +216 -0
- data/lib/konpeito/stdlib/ui/konpeito_ui_native.cpp +1625 -0
- data/lib/konpeito/stdlib/ui/konpeito_ui_native.h +162 -0
- data/lib/konpeito/stdlib/ui/ui.rb +318 -0
- data/lib/konpeito/stdlib/ui/ui.rbs +247 -0
- data/lib/konpeito/type_checker/annotation_parser.rb +67 -0
- data/lib/konpeito/type_checker/hm_inferrer.rb +2565 -0
- data/lib/konpeito/type_checker/inferrer.rb +565 -0
- data/lib/konpeito/type_checker/rbs_loader.rb +1621 -0
- data/lib/konpeito/type_checker/type_resolver.rb +276 -0
- data/lib/konpeito/type_checker/types.rb +1434 -0
- data/lib/konpeito/type_checker/unification.rb +323 -0
- data/lib/konpeito/ui/animation/animated_state.rb +80 -0
- data/lib/konpeito/ui/animation/easing.rb +59 -0
- data/lib/konpeito/ui/animation/value_tween.rb +66 -0
- data/lib/konpeito/ui/app.rb +379 -0
- data/lib/konpeito/ui/box.rb +38 -0
- data/lib/konpeito/ui/castella.rb +70 -0
- data/lib/konpeito/ui/castella_native.rb +76 -0
- data/lib/konpeito/ui/chart/area_chart.rb +305 -0
- data/lib/konpeito/ui/chart/bar_chart.rb +288 -0
- data/lib/konpeito/ui/chart/base_chart.rb +210 -0
- data/lib/konpeito/ui/chart/chart_helpers.rb +79 -0
- data/lib/konpeito/ui/chart/gauge_chart.rb +171 -0
- data/lib/konpeito/ui/chart/heatmap_chart.rb +222 -0
- data/lib/konpeito/ui/chart/line_chart.rb +289 -0
- data/lib/konpeito/ui/chart/pie_chart.rb +219 -0
- data/lib/konpeito/ui/chart/scales.rb +77 -0
- data/lib/konpeito/ui/chart/scatter_chart.rb +303 -0
- data/lib/konpeito/ui/chart/stacked_bar_chart.rb +276 -0
- data/lib/konpeito/ui/column.rb +271 -0
- data/lib/konpeito/ui/core.rb +2199 -0
- data/lib/konpeito/ui/dsl.rb +443 -0
- data/lib/konpeito/ui/frame.rb +171 -0
- data/lib/konpeito/ui/frame_native.rb +494 -0
- data/lib/konpeito/ui/markdown/ast.rb +124 -0
- data/lib/konpeito/ui/markdown/mermaid/layout.rb +387 -0
- data/lib/konpeito/ui/markdown/mermaid/models.rb +232 -0
- data/lib/konpeito/ui/markdown/mermaid/parser.rb +519 -0
- data/lib/konpeito/ui/markdown/mermaid/renderer.rb +336 -0
- data/lib/konpeito/ui/markdown/parser.rb +805 -0
- data/lib/konpeito/ui/markdown/renderer.rb +639 -0
- data/lib/konpeito/ui/markdown/theme.rb +165 -0
- data/lib/konpeito/ui/render_node.rb +260 -0
- data/lib/konpeito/ui/row.rb +207 -0
- data/lib/konpeito/ui/spacer.rb +18 -0
- data/lib/konpeito/ui/style.rb +799 -0
- data/lib/konpeito/ui/theme.rb +563 -0
- data/lib/konpeito/ui/themes/material.rb +35 -0
- data/lib/konpeito/ui/themes/tokyo_night.rb +6 -0
- data/lib/konpeito/ui/widgets/button.rb +103 -0
- data/lib/konpeito/ui/widgets/calendar.rb +1034 -0
- data/lib/konpeito/ui/widgets/checkbox.rb +119 -0
- data/lib/konpeito/ui/widgets/container.rb +91 -0
- data/lib/konpeito/ui/widgets/data_table.rb +667 -0
- data/lib/konpeito/ui/widgets/divider.rb +29 -0
- data/lib/konpeito/ui/widgets/image.rb +105 -0
- data/lib/konpeito/ui/widgets/input.rb +485 -0
- data/lib/konpeito/ui/widgets/markdown.rb +57 -0
- data/lib/konpeito/ui/widgets/modal.rb +163 -0
- data/lib/konpeito/ui/widgets/multiline_input.rb +968 -0
- data/lib/konpeito/ui/widgets/multiline_text.rb +180 -0
- data/lib/konpeito/ui/widgets/net_image.rb +100 -0
- data/lib/konpeito/ui/widgets/progress_bar.rb +70 -0
- data/lib/konpeito/ui/widgets/radio_buttons.rb +93 -0
- data/lib/konpeito/ui/widgets/slider.rb +133 -0
- data/lib/konpeito/ui/widgets/switch.rb +84 -0
- data/lib/konpeito/ui/widgets/tabs.rb +157 -0
- data/lib/konpeito/ui/widgets/text.rb +110 -0
- data/lib/konpeito/ui/widgets/tree.rb +426 -0
- data/lib/konpeito/version.rb +5 -0
- data/lib/konpeito.rb +109 -0
- data/test_native_array.rb +172 -0
- data/test_native_array_class.rb +197 -0
- data/test_native_class.rb +151 -0
- data/tools/konpeito-asm/build.sh +65 -0
- data/tools/konpeito-asm/lib/asm-9.7.1.jar +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KArray.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCompression.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KConditionVariable.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KCrypto.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KFile.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHTTP.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KHash.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON$Parser.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KJSON.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KMath.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactor.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KRactorPort.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KSizedQueue.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KThread.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/KTime.class +0 -0
- data/tools/konpeito-asm/runtime-classes/konpeito/runtime/RubyDispatch.class +0 -0
- data/tools/konpeito-asm/src/ClassIntrospector.java +312 -0
- data/tools/konpeito-asm/src/KonpeitoAssembler.java +659 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KArray.java +390 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KCompression.java +168 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KConditionVariable.java +48 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KCrypto.java +151 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KFile.java +100 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KHTTP.java +113 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KHash.java +228 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KJSON.java +405 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KMath.java +54 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KRactor.java +244 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KRactorPort.java +53 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KSizedQueue.java +49 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KThread.java +49 -0
- data/tools/konpeito-asm/src/konpeito/runtime/KTime.java +53 -0
- data/tools/konpeito-asm/src/konpeito/runtime/RubyDispatch.java +416 -0
- metadata +267 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Konpeito
|
|
4
|
+
module Codegen
|
|
5
|
+
# Monomorphizer generates specialized versions of polymorphic functions
|
|
6
|
+
# for specific type instantiations.
|
|
7
|
+
#
|
|
8
|
+
# Example:
|
|
9
|
+
# def identity(x)
|
|
10
|
+
# x
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# identity(42) # generates identity_Integer
|
|
14
|
+
# identity("hello") # generates identity_String
|
|
15
|
+
#
|
|
16
|
+
class Monomorphizer
|
|
17
|
+
attr_reader :specializations, :call_sites, :union_dispatches
|
|
18
|
+
|
|
19
|
+
def initialize(hir_program, type_info)
|
|
20
|
+
@hir_program = hir_program
|
|
21
|
+
@type_info = type_info # HMInferrer
|
|
22
|
+
@specializations = {} # { [func_name, type_args] => specialized_name }
|
|
23
|
+
@call_sites = [] # Array of { call: HIR::Call, types: [...] }
|
|
24
|
+
@union_call_sites = [] # Calls requiring runtime dispatch
|
|
25
|
+
@union_dispatches = {} # { [func_name, original_types] => dispatch_info }
|
|
26
|
+
@generated_functions = {}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Analyze the HIR program to find monomorphization opportunities
|
|
30
|
+
def analyze
|
|
31
|
+
@hir_program.functions.each do |func|
|
|
32
|
+
analyze_function(func)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Determine which specializations to generate
|
|
36
|
+
determine_specializations
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Apply monomorphization transformations
|
|
40
|
+
def transform
|
|
41
|
+
# Generate specialized function copies
|
|
42
|
+
generate_specialized_functions
|
|
43
|
+
|
|
44
|
+
# Rewrite call sites to use specialized functions
|
|
45
|
+
rewrite_call_sites
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Get the specialized function name for a call
|
|
49
|
+
def specialized_name(func_name, arg_types)
|
|
50
|
+
key = [func_name.to_s, arg_types.map(&:to_s)]
|
|
51
|
+
@specializations[key]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def analyze_function(func)
|
|
57
|
+
func.body.each do |block|
|
|
58
|
+
block.instructions.each do |inst|
|
|
59
|
+
analyze_instruction(inst, func)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def analyze_instruction(inst, context_func)
|
|
65
|
+
case inst
|
|
66
|
+
when HIR::Call
|
|
67
|
+
analyze_call(inst, context_func)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def analyze_call(call, context_func)
|
|
72
|
+
# Only specialize self-calls (calls on the same instance)
|
|
73
|
+
# Cross-class calls like h.add(1, 2) should not be specialized as direct calls
|
|
74
|
+
return unless self_receiver?(call.receiver)
|
|
75
|
+
|
|
76
|
+
# Check if this is a call to a local function that could be specialized
|
|
77
|
+
target_func = find_function(call.method_name)
|
|
78
|
+
return unless target_func
|
|
79
|
+
return if target_func.params.empty?
|
|
80
|
+
|
|
81
|
+
# Skip functions with rest params (*args) or keyword_rest (**kwargs)
|
|
82
|
+
# Rest params collect arguments into an array; specializing by element type is incorrect
|
|
83
|
+
return if target_func.params.any? { |p| p.rest || p.keyword_rest }
|
|
84
|
+
|
|
85
|
+
# Get parameter types from target function (may include Union types from RBS)
|
|
86
|
+
param_types = target_func.params.map(&:type)
|
|
87
|
+
|
|
88
|
+
# Get concrete argument types at this call site
|
|
89
|
+
arg_types = call.args.map { |arg| get_concrete_type(arg) }
|
|
90
|
+
|
|
91
|
+
# Check if any PARAMETER has a Union type (from RBS definition)
|
|
92
|
+
has_union_param = param_types.any? { |t| union_type?(t) }
|
|
93
|
+
|
|
94
|
+
# Also check if any argument has a Union type (from type inference)
|
|
95
|
+
has_union_arg = arg_types.any? { |t| union_type?(t) }
|
|
96
|
+
|
|
97
|
+
if has_union_param || has_union_arg
|
|
98
|
+
# Use parameter types for expansion (they contain the full Union info)
|
|
99
|
+
types_to_expand = param_types.each_with_index.map do |param_type, i|
|
|
100
|
+
if union_type?(param_type)
|
|
101
|
+
param_type
|
|
102
|
+
elsif i < arg_types.size && union_type?(arg_types[i])
|
|
103
|
+
arg_types[i]
|
|
104
|
+
else
|
|
105
|
+
arg_types[i] || param_type
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Expand union types into all possible combinations
|
|
110
|
+
expand_union_types(types_to_expand).each do |concrete_types|
|
|
111
|
+
@call_sites << {
|
|
112
|
+
call: call,
|
|
113
|
+
context: context_func,
|
|
114
|
+
target: target_func.name,
|
|
115
|
+
types: concrete_types,
|
|
116
|
+
union_dispatch: true,
|
|
117
|
+
original_types: types_to_expand
|
|
118
|
+
}
|
|
119
|
+
end
|
|
120
|
+
else
|
|
121
|
+
# Skip if any type is still polymorphic
|
|
122
|
+
return if arg_types.any? { |t| polymorphic_type?(t) }
|
|
123
|
+
|
|
124
|
+
@call_sites << {
|
|
125
|
+
call: call,
|
|
126
|
+
context: context_func,
|
|
127
|
+
target: target_func.name,
|
|
128
|
+
types: arg_types,
|
|
129
|
+
union_dispatch: false
|
|
130
|
+
}
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Check if a receiver is self (same instance)
|
|
135
|
+
# For monomorphization, we must be strict: only SelfRef is considered self
|
|
136
|
+
def self_receiver?(receiver)
|
|
137
|
+
receiver.is_a?(HIR::SelfRef)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def find_function(name)
|
|
141
|
+
@hir_program.functions.find { |f| f.name.to_s == name.to_s }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def get_concrete_type(hir_value)
|
|
145
|
+
case hir_value
|
|
146
|
+
when HIR::Instruction
|
|
147
|
+
hir_value.type
|
|
148
|
+
when HIR::Node
|
|
149
|
+
hir_value.type
|
|
150
|
+
else
|
|
151
|
+
TypeChecker::Types::UNTYPED
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def polymorphic_type?(type)
|
|
156
|
+
return true if type.nil?
|
|
157
|
+
return true if type.is_a?(TypeChecker::Types::Untyped)
|
|
158
|
+
return true if type.respond_to?(:id) && type.class.name.include?("TypeVar")
|
|
159
|
+
false
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Check if a type is a Union type
|
|
163
|
+
def union_type?(type)
|
|
164
|
+
type.is_a?(TypeChecker::Types::Union)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Expand union types into all possible combinations
|
|
168
|
+
# Example: [Integer, Union[String, Float]] => [[Integer, String], [Integer, Float]]
|
|
169
|
+
def expand_union_types(types)
|
|
170
|
+
combinations = [[]]
|
|
171
|
+
types.each do |type|
|
|
172
|
+
if union_type?(type)
|
|
173
|
+
new_combinations = []
|
|
174
|
+
combinations.each do |combo|
|
|
175
|
+
type.types.each { |member| new_combinations << (combo + [member]) }
|
|
176
|
+
end
|
|
177
|
+
combinations = new_combinations
|
|
178
|
+
else
|
|
179
|
+
combinations = combinations.map { |c| c + [type] }
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
combinations
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def determine_specializations
|
|
186
|
+
# Group call sites by (function, types)
|
|
187
|
+
grouped = @call_sites.group_by { |cs| [cs[:target], cs[:types]] }
|
|
188
|
+
|
|
189
|
+
grouped.each do |(func_name, types), sites|
|
|
190
|
+
next if types.all? { |t| t == TypeChecker::Types::UNTYPED }
|
|
191
|
+
|
|
192
|
+
type_suffix = types.map { |t| type_to_suffix(t) }.join("_")
|
|
193
|
+
specialized = "#{func_name}_#{type_suffix}"
|
|
194
|
+
|
|
195
|
+
key = [func_name.to_s, types.map(&:to_s)]
|
|
196
|
+
@specializations[key] = specialized
|
|
197
|
+
|
|
198
|
+
# Track if this came from union expansion
|
|
199
|
+
if sites.any? { |s| s[:union_dispatch] }
|
|
200
|
+
@union_call_sites << {
|
|
201
|
+
call: sites.first[:call],
|
|
202
|
+
target: func_name,
|
|
203
|
+
original_types: sites.first[:original_types],
|
|
204
|
+
concrete_types: types,
|
|
205
|
+
specialized_name: specialized
|
|
206
|
+
}
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Group union call sites by original call
|
|
211
|
+
consolidate_union_dispatches
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Group union call sites by their original (pre-expansion) call
|
|
215
|
+
def consolidate_union_dispatches
|
|
216
|
+
@union_dispatches = {}
|
|
217
|
+
|
|
218
|
+
# Group by (target, original_types)
|
|
219
|
+
grouped = @union_call_sites.group_by { |s| [s[:target], s[:original_types].map(&:to_s)] }
|
|
220
|
+
|
|
221
|
+
grouped.each do |(target, original_type_strs), sites|
|
|
222
|
+
# Build a mapping from concrete types to specialized function names
|
|
223
|
+
specializations = {}
|
|
224
|
+
sites.each do |site|
|
|
225
|
+
concrete_key = site[:concrete_types].map(&:to_s)
|
|
226
|
+
specializations[concrete_key] = site[:specialized_name]
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Find union positions (which argument indices have Union types)
|
|
230
|
+
original_types = sites.first[:original_types]
|
|
231
|
+
union_positions = original_types.each_with_index
|
|
232
|
+
.select { |t, _i| union_type?(t) }
|
|
233
|
+
.map { |_, i| i }
|
|
234
|
+
|
|
235
|
+
key = [target.to_s, original_type_strs]
|
|
236
|
+
@union_dispatches[key] = {
|
|
237
|
+
call: sites.first[:call],
|
|
238
|
+
target: target,
|
|
239
|
+
original_types: original_types,
|
|
240
|
+
union_positions: union_positions,
|
|
241
|
+
specializations: specializations
|
|
242
|
+
}
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def type_to_suffix(type)
|
|
247
|
+
case type
|
|
248
|
+
when TypeChecker::Types::ClassInstance
|
|
249
|
+
type.name.to_s
|
|
250
|
+
when TypeChecker::Types::NilType
|
|
251
|
+
"Nil"
|
|
252
|
+
when TypeChecker::Types::BoolType
|
|
253
|
+
"Bool"
|
|
254
|
+
else
|
|
255
|
+
type.to_s.gsub(/[^a-zA-Z0-9]/, "_")
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def generate_specialized_functions
|
|
260
|
+
@specializations.each do |(func_name, type_strs), specialized_name|
|
|
261
|
+
original = find_function(func_name)
|
|
262
|
+
next unless original
|
|
263
|
+
|
|
264
|
+
# Parse type strings back to types (for comparison in call sites)
|
|
265
|
+
types = @call_sites
|
|
266
|
+
.find { |cs| cs[:target].to_s == func_name && cs[:types].map(&:to_s) == type_strs }
|
|
267
|
+
&.dig(:types)
|
|
268
|
+
next unless types
|
|
269
|
+
|
|
270
|
+
specialized = clone_function(original, specialized_name, types)
|
|
271
|
+
@generated_functions[specialized_name] = specialized
|
|
272
|
+
@hir_program.functions << specialized
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def clone_function(original, new_name, param_types)
|
|
277
|
+
# Create specialized parameter list with concrete types
|
|
278
|
+
specialized_params = original.params.each_with_index.map do |param, i|
|
|
279
|
+
HIR::Param.new(
|
|
280
|
+
name: param.name,
|
|
281
|
+
type: param_types[i] || param.type,
|
|
282
|
+
default_value: param.default_value,
|
|
283
|
+
rest: param.rest,
|
|
284
|
+
keyword: param.keyword,
|
|
285
|
+
block: param.block
|
|
286
|
+
)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Deep clone the body blocks
|
|
290
|
+
specialized_body = deep_clone_blocks(original.body)
|
|
291
|
+
|
|
292
|
+
HIR::Function.new(
|
|
293
|
+
name: new_name,
|
|
294
|
+
params: specialized_params,
|
|
295
|
+
body: specialized_body,
|
|
296
|
+
return_type: original.return_type,
|
|
297
|
+
is_instance_method: original.is_instance_method,
|
|
298
|
+
owner_class: original.owner_class
|
|
299
|
+
)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def deep_clone_blocks(blocks)
|
|
303
|
+
# Create a simple deep copy of blocks
|
|
304
|
+
# In a full implementation, we'd update type information throughout
|
|
305
|
+
blocks.map do |block|
|
|
306
|
+
new_block = HIR::BasicBlock.new(label: block.label)
|
|
307
|
+
block.instructions.each do |inst|
|
|
308
|
+
new_block.add_instruction(clone_instruction(inst))
|
|
309
|
+
end
|
|
310
|
+
new_block.set_terminator(clone_terminator(block.terminator)) if block.terminator
|
|
311
|
+
new_block
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def clone_instruction(inst)
|
|
316
|
+
# Simple shallow clone for now
|
|
317
|
+
# A full implementation would update types based on specialization
|
|
318
|
+
inst.dup
|
|
319
|
+
rescue TypeError
|
|
320
|
+
# If dup fails, return the original
|
|
321
|
+
inst
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def clone_terminator(term)
|
|
325
|
+
term.dup
|
|
326
|
+
rescue TypeError
|
|
327
|
+
term
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def rewrite_call_sites
|
|
331
|
+
# Track which calls have already been processed (for Union calls that appear multiple times)
|
|
332
|
+
processed_calls = Set.new
|
|
333
|
+
|
|
334
|
+
@call_sites.each do |site|
|
|
335
|
+
call = site[:call]
|
|
336
|
+
next if processed_calls.include?(call.object_id)
|
|
337
|
+
|
|
338
|
+
if site[:union_dispatch]
|
|
339
|
+
# For union dispatch calls, set the dispatch info instead of a single specialized target
|
|
340
|
+
dispatch_key = [site[:target].to_s, site[:original_types].map(&:to_s)]
|
|
341
|
+
dispatch_info = @union_dispatches[dispatch_key]
|
|
342
|
+
if dispatch_info
|
|
343
|
+
call.instance_variable_set(:@union_dispatch_info, dispatch_info)
|
|
344
|
+
processed_calls.add(call.object_id)
|
|
345
|
+
end
|
|
346
|
+
else
|
|
347
|
+
# Regular monomorphized call
|
|
348
|
+
key = [site[:target].to_s, site[:types].map(&:to_s)]
|
|
349
|
+
specialized_name = @specializations[key]
|
|
350
|
+
next unless specialized_name
|
|
351
|
+
|
|
352
|
+
call.instance_variable_set(:@specialized_target, specialized_name)
|
|
353
|
+
processed_calls.add(call.object_id)
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
end
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/* profile_runtime.c - Konpeito Profiling Runtime
|
|
2
|
+
*
|
|
3
|
+
* Thread-safe profiling with minimal overhead.
|
|
4
|
+
* Uses atomic counters and clock_gettime for timing.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
#include <stdio.h>
|
|
8
|
+
#include <stdlib.h>
|
|
9
|
+
#include <string.h>
|
|
10
|
+
#include <stdint.h>
|
|
11
|
+
#include <time.h>
|
|
12
|
+
|
|
13
|
+
#ifdef __APPLE__
|
|
14
|
+
#include <mach/mach_time.h>
|
|
15
|
+
#endif
|
|
16
|
+
|
|
17
|
+
/* Maximum number of functions we can profile */
|
|
18
|
+
#define MAX_FUNCTIONS 4096
|
|
19
|
+
|
|
20
|
+
/* Maximum call stack depth for timing */
|
|
21
|
+
#define MAX_CALL_DEPTH 256
|
|
22
|
+
|
|
23
|
+
/* Maximum number of unique stack traces for flame graph */
|
|
24
|
+
#define MAX_STACK_SAMPLES 65536
|
|
25
|
+
|
|
26
|
+
/* Per-function profiling data */
|
|
27
|
+
typedef struct {
|
|
28
|
+
const char* name;
|
|
29
|
+
uint64_t call_count;
|
|
30
|
+
uint64_t total_time_ns;
|
|
31
|
+
} FunctionProfile;
|
|
32
|
+
|
|
33
|
+
/* Stack sample for flame graph */
|
|
34
|
+
typedef struct {
|
|
35
|
+
int func_ids[MAX_CALL_DEPTH];
|
|
36
|
+
int depth;
|
|
37
|
+
uint64_t time_ns; /* Time spent at this exact stack */
|
|
38
|
+
} StackSample;
|
|
39
|
+
|
|
40
|
+
/* Thread-local call stack for timing */
|
|
41
|
+
typedef struct {
|
|
42
|
+
int func_id;
|
|
43
|
+
uint64_t entry_time;
|
|
44
|
+
} CallStackEntry;
|
|
45
|
+
|
|
46
|
+
/* Global profiling state */
|
|
47
|
+
static FunctionProfile g_profiles[MAX_FUNCTIONS];
|
|
48
|
+
static int g_num_functions = 0;
|
|
49
|
+
static char g_output_path[1024] = "konpeito_profile.json";
|
|
50
|
+
static int g_initialized = 0;
|
|
51
|
+
|
|
52
|
+
/* Flame graph stack samples - stores aggregated time per unique call stack */
|
|
53
|
+
static StackSample g_stack_samples[MAX_STACK_SAMPLES];
|
|
54
|
+
static int g_num_stack_samples = 0;
|
|
55
|
+
|
|
56
|
+
/* Thread-local storage for call stack */
|
|
57
|
+
static __thread CallStackEntry tls_call_stack[MAX_CALL_DEPTH];
|
|
58
|
+
static __thread int tls_stack_depth = 0;
|
|
59
|
+
static __thread int tls_current_stack[MAX_CALL_DEPTH]; /* Current stack for flame graph */
|
|
60
|
+
|
|
61
|
+
#ifdef __APPLE__
|
|
62
|
+
static mach_timebase_info_data_t g_timebase_info;
|
|
63
|
+
#endif
|
|
64
|
+
|
|
65
|
+
/* Get current time in nanoseconds */
|
|
66
|
+
static inline uint64_t get_time_ns(void) {
|
|
67
|
+
#ifdef __APPLE__
|
|
68
|
+
uint64_t mach_time = mach_absolute_time();
|
|
69
|
+
return mach_time * g_timebase_info.numer / g_timebase_info.denom;
|
|
70
|
+
#else
|
|
71
|
+
struct timespec ts;
|
|
72
|
+
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
73
|
+
return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec;
|
|
74
|
+
#endif
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Forward declaration */
|
|
78
|
+
void konpeito_profile_finalize(void);
|
|
79
|
+
|
|
80
|
+
/* Find or create a stack sample for the current call stack */
|
|
81
|
+
static int find_or_create_stack_sample(int* stack, int depth) {
|
|
82
|
+
/* Search for existing sample with same stack */
|
|
83
|
+
for (int i = 0; i < g_num_stack_samples; i++) {
|
|
84
|
+
if (g_stack_samples[i].depth == depth) {
|
|
85
|
+
int match = 1;
|
|
86
|
+
for (int j = 0; j < depth; j++) {
|
|
87
|
+
if (g_stack_samples[i].func_ids[j] != stack[j]) {
|
|
88
|
+
match = 0;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (match) return i;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Create new sample if space available */
|
|
97
|
+
if (g_num_stack_samples >= MAX_STACK_SAMPLES) return -1;
|
|
98
|
+
|
|
99
|
+
int idx = g_num_stack_samples++;
|
|
100
|
+
g_stack_samples[idx].depth = depth;
|
|
101
|
+
for (int j = 0; j < depth; j++) {
|
|
102
|
+
g_stack_samples[idx].func_ids[j] = stack[j];
|
|
103
|
+
}
|
|
104
|
+
g_stack_samples[idx].time_ns = 0;
|
|
105
|
+
return idx;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Record time for current stack */
|
|
109
|
+
static void record_stack_time(uint64_t time_ns) {
|
|
110
|
+
if (tls_stack_depth <= 0) return;
|
|
111
|
+
|
|
112
|
+
int idx = find_or_create_stack_sample(tls_current_stack, tls_stack_depth);
|
|
113
|
+
if (idx >= 0) {
|
|
114
|
+
g_stack_samples[idx].time_ns += time_ns;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* Initialize profiling system */
|
|
119
|
+
void konpeito_profile_init(int num_functions, const char* output_path) {
|
|
120
|
+
if (g_initialized) return;
|
|
121
|
+
|
|
122
|
+
g_num_functions = num_functions < MAX_FUNCTIONS ? num_functions : MAX_FUNCTIONS;
|
|
123
|
+
|
|
124
|
+
if (output_path && strlen(output_path) < sizeof(g_output_path)) {
|
|
125
|
+
strncpy(g_output_path, output_path, sizeof(g_output_path) - 1);
|
|
126
|
+
g_output_path[sizeof(g_output_path) - 1] = '\0';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
for (int i = 0; i < MAX_FUNCTIONS; i++) {
|
|
130
|
+
g_profiles[i].name = NULL;
|
|
131
|
+
g_profiles[i].call_count = 0;
|
|
132
|
+
g_profiles[i].total_time_ns = 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
#ifdef __APPLE__
|
|
136
|
+
mach_timebase_info(&g_timebase_info);
|
|
137
|
+
#endif
|
|
138
|
+
|
|
139
|
+
g_initialized = 1;
|
|
140
|
+
|
|
141
|
+
/* Register atexit handler */
|
|
142
|
+
atexit(konpeito_profile_finalize);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* Called at function entry */
|
|
146
|
+
void konpeito_profile_enter(int func_id, const char* func_name) {
|
|
147
|
+
if (!g_initialized) return;
|
|
148
|
+
if (func_id < 0 || func_id >= MAX_FUNCTIONS) return;
|
|
149
|
+
if (tls_stack_depth >= MAX_CALL_DEPTH) return;
|
|
150
|
+
|
|
151
|
+
/* Register function name (only first call matters) */
|
|
152
|
+
if (g_profiles[func_id].name == NULL) {
|
|
153
|
+
g_profiles[func_id].name = func_name;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* Increment call count */
|
|
157
|
+
g_profiles[func_id].call_count++;
|
|
158
|
+
|
|
159
|
+
/* Push entry onto call stack with timestamp */
|
|
160
|
+
tls_call_stack[tls_stack_depth].func_id = func_id;
|
|
161
|
+
tls_call_stack[tls_stack_depth].entry_time = get_time_ns();
|
|
162
|
+
|
|
163
|
+
/* Track current stack for flame graph */
|
|
164
|
+
tls_current_stack[tls_stack_depth] = func_id;
|
|
165
|
+
|
|
166
|
+
tls_stack_depth++;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* Called at function exit */
|
|
170
|
+
void konpeito_profile_exit(int func_id) {
|
|
171
|
+
if (!g_initialized) return;
|
|
172
|
+
if (func_id < 0 || func_id >= MAX_FUNCTIONS) return;
|
|
173
|
+
if (tls_stack_depth <= 0) return;
|
|
174
|
+
|
|
175
|
+
/* Calculate elapsed time */
|
|
176
|
+
uint64_t exit_time = get_time_ns();
|
|
177
|
+
|
|
178
|
+
/* Verify we're exiting the right function */
|
|
179
|
+
if (tls_call_stack[tls_stack_depth - 1].func_id == func_id) {
|
|
180
|
+
uint64_t elapsed = exit_time - tls_call_stack[tls_stack_depth - 1].entry_time;
|
|
181
|
+
g_profiles[func_id].total_time_ns += elapsed;
|
|
182
|
+
|
|
183
|
+
/* Record time for flame graph at current stack depth */
|
|
184
|
+
record_stack_time(elapsed);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* Pop from call stack */
|
|
188
|
+
tls_stack_depth--;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* Escape string for JSON output */
|
|
192
|
+
static void write_json_string(FILE* fp, const char* str) {
|
|
193
|
+
fputc('"', fp);
|
|
194
|
+
while (*str) {
|
|
195
|
+
switch (*str) {
|
|
196
|
+
case '"': fputs("\\\"", fp); break;
|
|
197
|
+
case '\\': fputs("\\\\", fp); break;
|
|
198
|
+
case '\n': fputs("\\n", fp); break;
|
|
199
|
+
case '\r': fputs("\\r", fp); break;
|
|
200
|
+
case '\t': fputs("\\t", fp); break;
|
|
201
|
+
default: fputc(*str, fp); break;
|
|
202
|
+
}
|
|
203
|
+
str++;
|
|
204
|
+
}
|
|
205
|
+
fputc('"', fp);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* Write flame graph folded format */
|
|
209
|
+
static void write_flame_graph_folded(void) {
|
|
210
|
+
/* Generate folded file path */
|
|
211
|
+
char folded_path[1024];
|
|
212
|
+
strncpy(folded_path, g_output_path, sizeof(folded_path) - 1);
|
|
213
|
+
folded_path[sizeof(folded_path) - 1] = '\0';
|
|
214
|
+
|
|
215
|
+
/* Replace .json with .folded */
|
|
216
|
+
char* ext = strstr(folded_path, ".json");
|
|
217
|
+
if (ext) {
|
|
218
|
+
strcpy(ext, ".folded");
|
|
219
|
+
} else {
|
|
220
|
+
strncat(folded_path, ".folded", sizeof(folded_path) - strlen(folded_path) - 1);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
FILE* fp = fopen(folded_path, "w");
|
|
224
|
+
if (!fp) {
|
|
225
|
+
fprintf(stderr, "Warning: Could not write flame graph to %s\n", folded_path);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/* Write folded format: func1;func2;func3 samples
|
|
230
|
+
* Use microseconds as sample count for better granularity */
|
|
231
|
+
for (int i = 0; i < g_num_stack_samples; i++) {
|
|
232
|
+
if (g_stack_samples[i].time_ns == 0) continue;
|
|
233
|
+
|
|
234
|
+
/* Write stack (semicolon-separated function names) */
|
|
235
|
+
for (int j = 0; j < g_stack_samples[i].depth; j++) {
|
|
236
|
+
int func_id = g_stack_samples[i].func_ids[j];
|
|
237
|
+
const char* name = g_profiles[func_id].name;
|
|
238
|
+
if (name) {
|
|
239
|
+
if (j > 0) fputc(';', fp);
|
|
240
|
+
fputs(name, fp);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* Write sample count (microseconds) */
|
|
245
|
+
uint64_t samples = g_stack_samples[i].time_ns / 1000; /* ns to us */
|
|
246
|
+
if (samples == 0) samples = 1; /* At least 1 sample */
|
|
247
|
+
fprintf(fp, " %llu\n", (unsigned long long)samples);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
fclose(fp);
|
|
251
|
+
fprintf(stderr, "Flame graph data written to: %s\n", folded_path);
|
|
252
|
+
fprintf(stderr, " Generate SVG with: flamegraph.pl %s > profile.svg\n", folded_path);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* Finalize and write profile data */
|
|
256
|
+
void konpeito_profile_finalize(void) {
|
|
257
|
+
if (!g_initialized) return;
|
|
258
|
+
g_initialized = 0; /* Prevent double finalization */
|
|
259
|
+
|
|
260
|
+
/* Write flame graph folded format */
|
|
261
|
+
write_flame_graph_folded();
|
|
262
|
+
|
|
263
|
+
FILE* fp = fopen(g_output_path, "w");
|
|
264
|
+
if (!fp) {
|
|
265
|
+
fprintf(stderr, "Warning: Could not write profile to %s\n", g_output_path);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/* Calculate total time */
|
|
270
|
+
uint64_t total_time = 0;
|
|
271
|
+
for (int i = 0; i < g_num_functions; i++) {
|
|
272
|
+
if (g_profiles[i].name) {
|
|
273
|
+
total_time += g_profiles[i].total_time_ns;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/* Write JSON output */
|
|
278
|
+
fprintf(fp, "{\n \"functions\": [\n");
|
|
279
|
+
|
|
280
|
+
int first = 1;
|
|
281
|
+
for (int i = 0; i < g_num_functions; i++) {
|
|
282
|
+
if (g_profiles[i].name == NULL) continue;
|
|
283
|
+
|
|
284
|
+
uint64_t calls = g_profiles[i].call_count;
|
|
285
|
+
uint64_t time_ns = g_profiles[i].total_time_ns;
|
|
286
|
+
double time_ms = time_ns / 1000000.0;
|
|
287
|
+
double percent = total_time > 0 ? (time_ns * 100.0 / total_time) : 0.0;
|
|
288
|
+
|
|
289
|
+
if (!first) fprintf(fp, ",\n");
|
|
290
|
+
first = 0;
|
|
291
|
+
|
|
292
|
+
fprintf(fp, " {\n");
|
|
293
|
+
fprintf(fp, " \"name\": ");
|
|
294
|
+
write_json_string(fp, g_profiles[i].name);
|
|
295
|
+
fprintf(fp, ",\n");
|
|
296
|
+
fprintf(fp, " \"calls\": %llu,\n", (unsigned long long)calls);
|
|
297
|
+
fprintf(fp, " \"time_ms\": %.3f,\n", time_ms);
|
|
298
|
+
fprintf(fp, " \"percent\": %.2f\n", percent);
|
|
299
|
+
fprintf(fp, " }");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
fprintf(fp, "\n ],\n");
|
|
303
|
+
fprintf(fp, " \"total_time_ms\": %.3f\n", total_time / 1000000.0);
|
|
304
|
+
fprintf(fp, "}\n");
|
|
305
|
+
|
|
306
|
+
fclose(fp);
|
|
307
|
+
|
|
308
|
+
/* Print summary to stderr */
|
|
309
|
+
fprintf(stderr, "\n=== Konpeito Profile Summary ===\n");
|
|
310
|
+
fprintf(stderr, "%-40s %12s %12s %8s\n", "Function", "Calls", "Time (ms)", "%");
|
|
311
|
+
fprintf(stderr, "%-40s %12s %12s %8s\n",
|
|
312
|
+
"----------------------------------------",
|
|
313
|
+
"------------", "------------", "--------");
|
|
314
|
+
|
|
315
|
+
for (int i = 0; i < g_num_functions; i++) {
|
|
316
|
+
if (g_profiles[i].name == NULL) continue;
|
|
317
|
+
|
|
318
|
+
uint64_t calls = g_profiles[i].call_count;
|
|
319
|
+
uint64_t time_ns = g_profiles[i].total_time_ns;
|
|
320
|
+
double time_ms = time_ns / 1000000.0;
|
|
321
|
+
double percent = total_time > 0 ? (time_ns * 100.0 / total_time) : 0.0;
|
|
322
|
+
|
|
323
|
+
/* Truncate function name if too long */
|
|
324
|
+
char truncated_name[41];
|
|
325
|
+
if (strlen(g_profiles[i].name) > 40) {
|
|
326
|
+
strncpy(truncated_name, g_profiles[i].name, 37);
|
|
327
|
+
truncated_name[37] = '.';
|
|
328
|
+
truncated_name[38] = '.';
|
|
329
|
+
truncated_name[39] = '.';
|
|
330
|
+
truncated_name[40] = '\0';
|
|
331
|
+
} else {
|
|
332
|
+
strncpy(truncated_name, g_profiles[i].name, 40);
|
|
333
|
+
truncated_name[40] = '\0';
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
fprintf(stderr, "%-40s %12llu %12.3f %7.2f%%\n",
|
|
337
|
+
truncated_name, (unsigned long long)calls, time_ms, percent);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
fprintf(stderr, "\nProfile data written to: %s\n", g_output_path);
|
|
341
|
+
}
|