Skip to content
← Back

src/compiler/compiler.ghul

1
namespace Compiler is
2
use System.Exception
3
4
use IO.Std
5
6
use Collections.Iterable
7
use Collections.MutableList
8
use Collections.LIST
9
10
use IoC
11
use Logging
12
13
class COMPILER is
14
container: CONTAINER
15
logger: Logger
16
17
source_files: LIST[SOURCE_FILE]
18
19
post_parse_passes: MutableList[Pass]
20
21
build_passes: MutableList[Pass]
22
23
_compile_expressions_pass: Pass
24
_verify_compile_state_had_errors: bool
25
26
is_full_compile_needed: bool public
27
28
// True when the declaration-level state - symbols, ancestries,
29
// override links, store-free bits - reflects every registered
30
// file's current source: set by a completed build, cleared the
31
// moment clear_symbols abandons the tables. Distinct from
32
// is_full_compile_needed, which tracks whether expression-level
33
// DIAGNOSTICS are stale for some files; right after an
34
// interface-affecting EDIT's rebuild the tables are current while
35
// a full compile is still needed.
36
are_tables_current: bool public
37
38
// Called at each per-file boundary of the compile-expressions
39
// walk, if anything is interested. Analysis mode uses it to
40
// answer queries that arrived while a compile was running; a
41
// batch build leaves it unset.
42
//
43
// The boundary is where a file has just been walked and both the
44
// scope stack and the namespace stack are back at their base -
45
// the only point during a compile at which the retained state is
46
// as consistent as it is between builds.
47
on_file_boundary: (() -> void)? public
48
49
generated_source_files: MutableList[string]
50
51
_effects_stopwatch: System.Diagnostics.Stopwatch?
52
53
// See the build() overload below.
54
_want_entry_point_selection: bool
55
56
// Files with a body walked since the effect relations were last
57
// solved: what the next solve has to re-derive before it can
58
// answer for them.
59
_effects_stale_files: Collections.SET[string]
60
61
init() is
62
container = IoC.CONTAINER.instance
63
logger = container.logger
64
_effects_stale_files = Collections.SET[string]()
65
66
// Nothing has been compiled yet, so expression-level state is
67
// certainly absent: a compile request against this fresh state
68
// has to run the full build, not the incremental effects pass,
69
// which assumes the symbol tables exist (in particular the
70
// namespaces only `build()` creates).
71
is_full_compile_needed = true
72
73
source_files = LIST[SOURCE_FILE]()
74
generated_source_files = LIST[string]()
75
76
post_parse_passes = LIST[Pass]()
77
add_pass(post_parse_passes, "conditional-compilation", (source_file) -> bool => conditional_compilation_pass(source_file))
78
add_pass(post_parse_passes, "collect-modifier-keyword-locations", (source_file) -> bool => collect_modifier_keyword_locations_pass(source_file))
79
add_pass(post_parse_passes, "rewrite-syntax-trees", (source_file) -> bool => rewrite_syntax_tree_pass(source_file))
80
add_pass(post_parse_passes, "collect-suppress-pragmas", (source_file) -> bool => collect_suppress_pragmas_pass(source_file))
81
82
build_passes = LIST[Pass]()
83
// Declares the type-level skeleton only: namespaces, types,
84
// variants and type parameters - everything a type expression
85
// can name. Members are declared by declare-members below.
86
add_pass(build_passes, "declare-symbols", (source_file) -> bool => declare_symbols_pass(source_file))
87
// First round: binds imports of namespaces and types. Imports
88
// of members bind in resolve-member-uses, once they exist.
89
add_pass(build_passes, "resolve-uses", (source_file) -> bool => resolve_uses_pass(source_file))
90
// Runs after resolve-uses so a member - in particular an
91
// impl/partial block's target name - can be reached through a
92
// use import and declared anywhere relative to its uses.
93
add_pass(build_passes, "declare-members", (source_file) -> bool => declare_members_pass(source_file))
94
// Second round: binds imports of members - static methods,
95
// global functions, enum members - and reports anything still
96
// unresolved.
97
add_pass(build_passes, "resolve-member-uses", (source_file) -> bool => resolve_member_uses_pass(source_file))
98
// Runs after declare-members so it can read each class's
99
// computed `is_abstract` (explicit + implicit) rather than
100
// just the syntactic modifier.
101
// Walks every file to report warnings but leaves nothing a
102
// later pass reads, so it reports its walk without advancing
103
// the compiled_through marker.
104
add_pass(build_passes, "check-name-conventions", (source_file) -> bool => check_name_conventions_pass(source_file))
105
.advances_milestone = false
106
add_pass(build_passes, "resolve-type-expressions", (source_file: SOURCE_FILE) -> bool => resolve_type_expressions_pass(source_file))
107
add_pass(build_passes, "resolve-ancestors", (source_file) -> bool => resolve_ancestors_pass(source_file))
108
add_pass(build_passes, "synthesize-class-equality", (source_file) -> bool => synthesize_class_equality_pass(source_file))
109
add_pass(build_passes, "synthesize-iterator-reset", (source_file) -> bool => synthesize_iterator_reset_pass(source_file))
110
add_pass(build_passes, "resolve-explicit-types", (source_file) -> bool => resolve_explicit_types_pass(source_file))
111
add_pass(build_passes, "check-type-argument-bounds", (source_file) -> bool => check_type_argument_bounds_pass(source_file))
112
add_pass(
113
build_passes,
114
"resolve-overrides",
115
() -> void is si,
116
(source_file) -> bool => resolve_overrides_pass(source_file),
117
() -> void is
118
container.resolve_overrides.check_duplicate_global_functions()
119
si
120
)
121
// Editor-only: announces each declaration's dispatch state
122
// once every file's overrides have been resolved. Reports
123
// nothing a later pass reads, so it does not advance the
124
// milestone.
125
add_pass(build_passes, "definition-virtuality", (source_file) -> bool => definition_virtuality_pass(source_file))
126
.advances_milestone = false
127
add_pass(build_passes, "register-source-intrinsics", (source_file) -> bool => register_source_intrinsics_pass(source_file))
128
add_pass(build_passes, "record-type-argument-uses", (source_file) -> bool => record_type_argument_uses_pass(source_file))
129
130
add_pass(build_passes, "mark-boxed-locals", (source_file) -> bool => mark_boxed_locals_pass(source_file))
131
132
_compile_expressions_pass = add_pass(
133
build_passes,
134
"compile-expressions",
135
() -> void is si,
136
(source_file) -> bool => compile_expressions_pass(source_file),
137
() -> void is si
138
)
139
140
// Re-walks every body with the types compile-expressions
141
// resolved, then solves the member-granular effect
142
// relations the crossing discharge reads. Reports nothing a
143
// later pass consumes through the milestone, so it does not
144
// advance it.
145
add_pass(
146
build_passes,
147
"infer-effects",
148
() -> void is
149
_effects_stopwatch = System.Diagnostics.Stopwatch.start_new()
150
151
Syntax.Process.EFFECT_FACTS.collecting = true
152
Syntax.Process.EFFECT_FACTS.begin_generation("infer-effects")
153
154
container.infer_store_free.set_resolved_mode(true)
155
container.infer_store_free.start_run()
156
si,
157
(source_file) -> bool => infer_effects_pass(source_file),
158
() -> void is
159
let stopwatch = _effects_stopwatch
160
let walk_ms = if stopwatch? then stopwatch.elapsed_milliseconds else 0L fi
161
162
let complete = _all_compiled_through_expressions(source_files)
163
164
container.infer_store_free.solve_effects(complete)
165
container.infer_store_free.set_resolved_mode(false)
166
167
Syntax.Process.EFFECT_FACTS.collecting = false
168
169
let solve_ms = if stopwatch? then stopwatch.elapsed_milliseconds else 0L fi
170
171
// Re-walk the files whose walk disagrees with the
172
// relations until a solve finds none, then judge
173
// and check against the final relations.
174
run_combined_solve_rounds(source_files)
175
176
let rounds_ms = if stopwatch? then stopwatch.elapsed_milliseconds else 0L fi
177
178
run_post_effects_purity_checks(source_files)
179
180
Syntax.Process.RETRY_STATS.report()
181
Syntax.Process.RETRY_SITE_STATS.report()
182
183
_dump_kills()
184
185
if System.Environment.get_environment_variable("GHUL_EFFECTS_TIMING")? /\ stopwatch? then
186
IO.Std.error.write_line("effects round: {stopwatch.elapsed_milliseconds} ms (walk {walk_ms} solve {solve_ms - walk_ms} rounds {rounds_ms - solve_ms} judge {stopwatch.elapsed_milliseconds - rounds_ms})")
187
fi
188
si
189
).advances_milestone = false
190
191
// After the effect rounds have re-walked whatever they were
192
// going to, so the state checked is the state generate-il
193
// will read. Whether the build had errors is read once, before
194
// any file is checked: a violation is itself an error, and
195
// reading the live count would stop the check at the first
196
// file that reports one.
197
add_pass(
198
build_passes,
199
"verify-compile-state",
200
() -> void is _verify_compile_state_had_errors = logger.any_errors si,
201
(source_file) -> bool => verify_compile_state_pass(source_file),
202
() -> void is si
203
).advances_milestone = false
204
205
// Before generate-il, so the choice - and the diagnostics it
206
// produces - are made by a pass that also runs in analysis
207
// mode, and after compile-expressions, so a candidate's
208
// inferred return type is settled when its signature is
209
// checked.
210
add_pass(
211
build_passes,
212
"select-entry-point",
213
() -> void is
214
if _want_entry_point_selection then
215
container.select_entry_point.start()
216
fi
217
si,
218
(source_file) -> bool => select_entry_point_pass(source_file),
219
() -> void is
220
if _want_entry_point_selection then
221
container.select_entry_point.finish()
222
fi
223
si
224
).advances_milestone = false
225
226
add_pass(
227
build_passes,
228
"generate-il",
229
() -> void is
230
if !is_emitting_assembly then
231
return
232
fi
233
234
let srm_emitter = container.ir_context.srm_assembly_emitter
235
236
srm_emitter.referenced_assemblies = container.referenced_assemblies
237
238
// Not at the point the emitter is constructed: the
239
// constructor signature these reference is built from
240
// the string type, which the symbol table does not
241
// hold until the reflected assemblies have been read.
242
container.assembly_info.gen(srm_emitter)
243
244
Semantic.DotNet.SESSION_CELL_ATTRIBUTES.gen(
245
srm_emitter,
246
container.build_flags.submission_name,
247
container.assemblies)
248
249
// Assign every definition its metadata row before any
250
// body is encoded, so a body can name a target whose
251
// row is not written yet.
252
IR.Emitter.SRM_STRUCTURE_WALK(srm_emitter)
253
.number(container.symbol_table.global_scope)
254
si,
255
(source_file) -> bool => generate_il_pass(source_file),
256
() -> void is
257
if !is_emitting_assembly then
258
return
259
fi
260
261
let srm_emitter = container.ir_context.srm_assembly_emitter
262
263
// Write the rows the numbering pass promised, now
264
// that every body has been encoded and deposited.
265
// Driven by the sequence that pass recorded, not by
266
// the symbol table, which has moved on since.
267
if !container.logger.any_errors then
268
IR.Emitter.SRM_STRUCTURE_WALK(srm_emitter)
269
.write_rows()
270
fi
271
272
// An executable build that named no entry point would
273
// otherwise be written out and fail only when something
274
// tried to run it. Surface it as a located compiler
275
// error instead; `finish_build` aborts on `any_errors`
276
// before the image is written. Library builds
277
// (`--library`) legitimately have no entry point. Gated
278
// on no prior errors so it doesn't pile onto a build
279
// already failing for another reason (an error-aborted
280
// build never reaches `.entrypoint` emission, so
281
// `seen_entrypoint` is false regardless of whether the
282
// source declares an `entry`).
283
let flags = container.build_flags
284
285
if flags.want_executable /\
286
!flags.want_library /\
287
!container.ir_context.seen_entrypoint /\
288
!container.logger.any_errors
289
then
290
let message =
291
if container.select_entry_point.named_entry_point_not_found then
292
"no function matches the --entry name {container.ir_context.entry_point_name}"
293
elif container.select_entry_point.left_top_level_statements_unrun then
294
"no entry point selected; name one with --entry, or leave one file carrying top-level statements"
295
else
296
"no entry point declared; add an 'entry()' function or build with --library"
297
fi
298
299
container.logger.error(Source.LOCATION.internal, message)
300
fi
301
si
302
)
303
304
// Pass.order is set in registration order so a SOURCE_FILE's
305
// compiled_through.order can be compared numerically against the
306
// order of a target pass (e.g. _compile_expressions_pass). Post-
307
// parse passes get their own counter starting at zero and don't
308
// participate in compiled_through queries.
309
assign_pass_orders(post_parse_passes)
310
assign_pass_orders(build_passes)
311
si
312
313
assign_pass_orders(passes: Collections.Iterable[Pass]) static is
314
let order mut = 0
315
for p in passes do
316
p.order = order
317
order = order + 1
318
od
319
si
320
321
dump_counts() is
322
container.namespaces.dump_counts()
323
container.symbol_use_locations.dump_counts()
324
container.symbol_definition_locations.dump_counts()
325
si
326
327
clear_symbols() is
328
clear_symbols(Collections.LIST[string]())
329
si
330
331
// Abandon the symbol tables ahead of a rebuild. Every source
332
// symbol is re-created; those of a file that was not edited are
333
// re-declared from its retained tree in the same order as
334
// before, and adopt the ids they had, so everything keyed by
335
// symbol id - the solved effect relations above all - carries
336
// over. Symbols of an edited file get fresh ids.
337
clear_symbols(edited_files: Collections.Iterable[string]) is
338
are_tables_current = false
339
340
// The retained effect facts describe the symbols being
341
// abandoned, and what the solve reads off a symbol - its
342
// owner's openness, its overriders, its purity - can change
343
// with the rebuild; the next solve starts from every body.
344
container.infer_store_free.start_run()
345
_effects_stale_files.clear()
346
347
container.symbol_table.clear()
348
container.namespaces.clear()
349
container.symbol_use_locations.clear()
350
container.symbol_definition_locations.clear_and_replay_ids(edited_files)
351
Semantic.Symbols.STORE_FREE_BITS.clear()
352
353
// The emitter's rows and reference caches name symbols that
354
// have just been abandoned, and analysis rebuilds on every
355
// edit: kept, they accumulate for the session's lifetime.
356
container.ir_context.reset_srm_assembly_emitter()
357
// container.logger.clear();
358
359
// Every previously-queued file's expression-level state now
360
// references symbols that have just been abandoned. Drop the
361
// marker so query handlers can tell.
362
for f in source_files do
363
f.compiled_through = null
364
od
365
si
366
367
is_compiled_through_expressions(source_file: SOURCE_FILE) -> bool =>
368
let compiled_through = source_file?.compiled_through in
369
compiled_through? /\
370
compiled_through.order >= _compile_expressions_pass.order
371
372
clear_queue() is
373
source_files.clear()
374
si
375
376
add_pass(passes: Collections.MutableList[Pass], description: string, apply: SOURCE_FILE -> bool) -> Pass is
377
let pass = PASS(container.timers, description, null, apply, null)
378
passes.add(pass)
379
return pass
380
si
381
382
add_pass(
383
passes: Collections.MutableList[Pass],
384
description: string,
385
start: () -> void,
386
apply: SOURCE_FILE -> bool,
387
finish: () -> void
388
) -> Pass
389
is
390
let pass = PASS(container.timers, description, start, apply, finish)
391
passes.add(pass)
392
return pass
393
si
394
395
add_pass(passes: Collections.MutableList[Pass], pass: Pass) is
396
passes.add(pass)
397
si
398
399
parse_and_queue(path: string, reader: IO.TextReader, want_compile_up_to_expressions: bool, want_compile_expressions: bool, is_internal_file: bool) is
400
queue(
401
parse(path, reader, want_compile_up_to_expressions, want_compile_expressions, is_internal_file)
402
)
403
si
404
405
queue(source_file: SOURCE_FILE) is
406
source_files.add(source_file)
407
si
408
409
queue(source_files: Collections.Iterable[SOURCE_FILE]) is
410
self.source_files.add_range(source_files)
411
si
412
413
parse(path: string, reader: IO.TextReader, want_compile_up_to_expressions: bool, want_compile_expressions: bool, is_internal_file: bool) -> SOURCE_FILE =>
414
parse(path, reader, want_compile_up_to_expressions, want_compile_expressions, is_internal_file, logger)
415
416
// Parsing into an explicitly-supplied logger lets a caller (the
417
// analysis-mode format handlers) parse without touching the shared
418
// diagnostics store: a throwaway logger keeps parse diagnostics and
419
// the speculation state stack isolated from the live analyser state.
420
parse(
421
path: string,
422
reader: IO.TextReader,
423
want_compile_up_to_expressions: bool,
424
want_compile_expressions: bool,
425
is_internal_file: bool,
426
logger: Logger
427
) -> SOURCE_FILE is
428
let tokenizer = Lexical.TOKENIZER(
429
logger,
430
path,
431
reader,
432
is_internal_file
433
)
434
435
// Speculative-lookahead ring buffer; a power of 2 (the
436
// queue masks indices with `size - 1`), sized generously so
437
// deep-but-finite speculation does not overflow it. A true
438
// speculation loop is caught separately by the tokenizer's
439
// backtrack-loop detector.
440
let token_queue = Lexical.TOKEN_QUEUE(2048)
441
442
let token_lookahead = Lexical.TOKEN_LOOKAHEAD(
443
token_queue,
444
tokenizer
445
)
446
447
let context = Syntax.Parsers.CONTEXT(
448
token_lookahead,
449
logger
450
)
451
452
// The file root parses as a definition list at namespace depth 0,
453
// where a file with no namespace may carry bare top-level
454
// statements (collected into a synthesised entry point).
455
let definitions = container.definition_global_list_parser.parse(context)!
456
457
let result =
458
SOURCE_FILE(
459
want_compile_up_to_expressions,
460
want_compile_expressions,
461
path,
462
definitions
463
)
464
465
result.trivia = tokenizer.trivia
466
467
return result
468
si
469
470
// Parses one file and nothing more, into a logger of its own, and says
471
// whether it is complete, stops before its end, or is wrong. A front
472
// end reading a program a line at a time asks this to decide whether
473
// to run what it has or to wait for another line.
474
check_complete(path: string, reader: IO.TextReader) -> Syntax.Parsers.COMPLETENESS =>
475
check(path, reader).completeness
476
477
// As check_complete, and what the last thing at the file's root is,
478
// as Syntax.Parsers.LAST_STATEMENT_KIND names it.
479
check(path: string, reader: IO.TextReader) -> (completeness: Syntax.Parsers.COMPLETENESS, last_statement: string?) is
480
let completeness_logger = Logging.DIAGNOSTICS_STORE()
481
482
let tokenizer = Lexical.TOKENIZER(completeness_logger, path, reader, false)
483
484
let context =
485
Syntax.Parsers.CONTEXT(
486
Lexical.TOKEN_LOOKAHEAD(Lexical.TOKEN_QUEUE(2048), tokenizer),
487
completeness_logger
488
)
489
490
let root = container.definition_global_list_parser.parse(context)
491
492
let errors = LIST[Source.LOCATION]()
493
494
for d in completeness_logger.diagnostics_for(path) do
495
if d.severity == DiagnosticSeverity.ERROR then
496
errors.add(d.location)
497
fi
498
od
499
500
let end_of_input_errors = LIST[Source.LOCATION]()
501
502
end_of_input_errors.add_range(context.end_of_input_error_locations)
503
end_of_input_errors.add_range(tokenizer.end_of_input_error_locations)
504
505
let completeness = Syntax.Parsers.COMPLETENESS_CLASSIFIER().classify(errors, end_of_input_errors)
506
507
let last_statement =
508
if completeness == Syntax.Parsers.COMPLETENESS.COMPLETE /\ root? then
509
Syntax.Parsers.LAST_STATEMENT_KIND.of(root)
510
else
511
null
512
fi
513
514
return (completeness = completeness, last_statement = last_statement)
515
si
516
517
post_parse(source_files: Iterable[SOURCE_FILE]) is
518
build(post_parse_passes, source_files)
519
si
520
521
post_parse() is
522
build(post_parse_passes, source_files)
523
si
524
525
build() is
526
build(true)
527
si
528
529
// `want_entry_point_selection` false leaves out the pass that
530
// settles which function the assembly enters at. Its answer can
531
// only change when the project's declarations do, and nothing an
532
// editor shows between one debounced compile and the next depends
533
// on it, so an edit-driven rebuild skips a whole-project walk it
534
// would repeat on every keystroke.
535
build(want_entry_point_selection: bool) is
536
_want_entry_point_selection = want_entry_point_selection
537
538
container.ghul_namespace_creator.create_namespaces()
539
540
Syntax.Process.PURE_SLOTS.clear()
541
Syntax.Process.KILL_LEDGER.clear_all()
542
543
_move_top_level_entry_file_first()
544
545
build(build_passes, source_files)
546
547
are_tables_current = true
548
si
549
550
// The file carrying the synthesised top-level entry compiles
551
// first: the entry's walk settles its top-level `let` variables'
552
// inferred types, and under --global-namespace a function in any
553
// other file can read them.
554
_move_top_level_entry_file_first() is
555
for i in 0..source_files.count do
556
if Syntax.Process.SYNTHESISE_TOP_LEVEL_ENTRY.has_top_level_entry(source_files[i].definition) then
557
if i > 0 then
558
let f = source_files[i]
559
560
source_files.remove_at(i)
561
source_files.insert(0, f)
562
fi
563
564
return
565
fi
566
od
567
si
568
569
build(passes: Collections.Iterable[Pass], source_files: Collections.Iterable[SOURCE_FILE]) is
570
let is_compiling_expressions mut = false
571
572
for pass in passes do
573
if pass == _compile_expressions_pass then
574
is_compiling_expressions = true
575
fi
576
577
logger.set_is_compiling_expressions(is_compiling_expressions)
578
579
pass.start()
580
581
for i in source_files do
582
let symbol_table = IoC.CONTAINER.instance.symbol_table
583
let symbol_table_mark = symbol_table.mark_scope_stack()
584
let diagnostics_mark = logger.mark()
585
let symbol_uses_mark = container.symbol_use_locations.mark()
586
587
let did_work mut = false
588
589
try
590
did_work = pass.apply(i)
591
catch e: Exception
592
logger.exception(diagnostics_mark, i.definition.location, e, "caught exception running pass {pass} on work item {i}")
593
finally
594
logger.release(diagnostics_mark)
595
container.symbol_use_locations.release(symbol_uses_mark)
596
symbol_table.release_scope_stack(symbol_table_mark)
597
yrt
598
599
if did_work then
600
WORK_COUNTERS.file_pass(container.timers, pass.description)
601
602
if pass.advances_milestone then
603
i.compiled_through = pass
604
fi
605
fi
606
607
IoC.CONTAINER.instance.namespaces.pop_all_namespaces()
608
609
if pass == _compile_expressions_pass then
610
_reached_file_boundary()
611
fi
612
od
613
614
// Every method has now been walked, which is what the
615
// constructor field check needs: a constructor is routinely
616
// written before the helper it delegates to, so the helper's
617
// summary does not exist until the pass is over.
618
if pass == _compile_expressions_pass then
619
container.compile_expressions.report_field_assignments()
620
fi
621
622
pass.finish()
623
od
624
si
625
626
// On-demand expression compile for one file against the retained
627
// symbol table, replaying only the pipeline's compile-expressions
628
// step with the same per-file bookkeeping as the build loop.
629
// Valid when the file has been built up to expressions in the
630
// current symbol generation - declare/resolve state current, only
631
// the expression-level walk missing. The analysis-mode query-miss
632
// recompile takes this instead of a whole-project rebuild when no
633
// interface change is pending.
634
compile_expressions_only(source_file: SOURCE_FILE) is
635
source_file.want_compile_expressions = true
636
_effects_stale_files.add(source_file.file_name)
637
638
// This walk is the only pass a file the client opened after
639
// the last full build ever gets: the build ran the
640
// declaration-level virtuality pass gated on the open-files
641
// set, so a file that was closed then recorded none of its
642
// carriers. Re-run it here, as the body re-walk does, so its
643
// inlays arrive with the expression-level ones.
644
container.definition_virtuality.apply(source_file)
645
646
// Same reasoning again: a closed file recorded none of the
647
// whole-project build's name-convention warnings either, and
648
// nothing else re-runs that walk for it.
649
container.check_name_conventions.apply(source_file)
650
651
// Same reasoning: this walk is the file's first and only
652
// expression-level walk, so the marking narrowing consults
653
// has to happen here rather than having been done by the
654
// pass the build skipped for a closed file.
655
container.mark_boxed_locals.apply(source_file.definition, _want_boxing)
656
657
logger.set_is_compiling_expressions(true)
658
659
let symbol_table = container.symbol_table
660
let symbol_table_mark = symbol_table.mark_scope_stack()
661
let diagnostics_mark = logger.mark()
662
let symbol_uses_mark = container.symbol_use_locations.mark()
663
664
let did_work mut = false
665
666
try
667
did_work = _compile_expressions_pass.apply(source_file)
668
catch e: Exception
669
logger.exception(diagnostics_mark, source_file.definition.location, e, "caught exception compiling expressions on demand for {source_file}")
670
finally
671
logger.release(diagnostics_mark)
672
container.symbol_use_locations.release(symbol_uses_mark)
673
symbol_table.release_scope_stack(symbol_table_mark)
674
675
container.namespaces.pop_all_namespaces()
676
677
logger.set_is_compiling_expressions(false)
678
yrt
679
680
if did_work then
681
WORK_COUNTERS.file_pass(container.timers, _compile_expressions_pass.description)
682
683
source_file.compiled_through = _compile_expressions_pass
684
fi
685
686
// This file is the whole of the walk, so its constructors are
687
// reported here rather than waiting for a build loop that may
688
// never run - without this an on-demand recompile reports the
689
// file differently from a whole-project build of the same
690
// source.
691
container.compile_expressions.report_field_assignments()
692
693
_reached_file_boundary()
694
si
695
696
_reached_file_boundary() is
697
let boundary = on_file_boundary
698
699
if boundary? then
700
boundary()
701
fi
702
si
703
704
// Drop what compile-expressions left behind for one file and put
705
// it back in the set that pass has yet to walk. A CE-output-only
706
// clear (preserve_resolved_types) drops that output - including
707
// the node-keyed stores - while leaving TypeExpression.type
708
// intact, so the walk can run again without re-resolving (and
709
// thus double-setting) the retained interface types.
710
//
711
// The caller either re-walks the file now, through
712
// recompile_file_expressions, or leaves it for the next compile's
713
// expressions-only pass to pick up.
714
invalidate_file_expressions(source_file: SOURCE_FILE) is
715
let ce_clear = Syntax.Process.CLEAR_STATE_VISITOR(true)
716
ce_clear.apply(source_file.definition)
717
718
container.state_store_registry.drop_file(source_file.file_name)
719
720
source_file.compiled_through = null
721
si
722
723
// Recompile one file's expressions against the current symbol
724
// table, keeping its resolved interface, so every body rebinds.
725
recompile_file_expressions(source_file: SOURCE_FILE) is
726
Syntax.Process.KILL_LEDGER.clear_for(source_file.file_name)
727
728
invalidate_file_expressions(source_file)
729
730
logger.clear_expression_diagnostics(source_file.file_name)
731
Syntax.Process.PURE_SLOTS.clear_for(source_file.file_name)
732
733
compile_expressions_only(source_file)
734
si
735
736
// Re-walk one definition's body against the current symbol table
737
// and the current effect relations: what the previous walk of that
738
// body left behind - its node state, its expression-level
739
// diagnostics and inlays, its ledger records and candidates - is
740
// dropped, and compile-expressions is driven over that body
741
// alone. The interface, and every other body in the file, is
742
// untouched.
743
recompile_body_expressions(source_file: SOURCE_FILE, function: Syntax.Trees.Definitions.FUNCTION) is
744
let file_name = source_file.file_name
745
let span = function.location
746
747
_effects_stale_files.add(file_name)
748
749
Syntax.Process.KILL_LEDGER.clear_for(function)
750
751
if let body = function.body then
752
Syntax.Process.CLEAR_STATE_VISITOR(true).apply(body)
753
fi
754
755
logger.clear_expression_diagnostics_within(file_name, span)
756
container.symbol_use_locations.drop_within(file_name, span)
757
Syntax.Process.PURE_SLOTS.clear_within(file_name, span)
758
759
let only = Collections.SET[Syntax.Trees.Definitions.FUNCTION]()
760
only.add(function)
761
762
logger.set_is_compiling_expressions(true)
763
764
container.compile_expressions.reset_between_walks()
765
766
let symbol_table = container.symbol_table
767
let mark = symbol_table.mark_scope_stack()
768
769
let rewalker = Syntax.Process.BODY_REWALKER(
770
logger,
771
symbol_table,
772
container.namespaces,
773
container.compile_expressions,
774
only
775
)
776
777
try
778
source_file.definition.walk(rewalker)
779
catch e: Exception
780
logger.exception(function.location, e, "caught exception re-walking body in {source_file}")
781
finally
782
symbol_table.release_scope_stack(mark)
783
container.namespaces.pop_all_namespaces()
784
785
logger.set_is_compiling_expressions(false)
786
yrt
787
788
container.compile_expressions.report_field_assignments()
789
si
790
791
// Declare, resolve and compile just the definitions an append-only
792
// interface edit added to the end of a file, against the retained
793
// symbol table. The appended subtree is new - nothing referenced
794
// it before this edit - so no retained symbol changes and no other
795
// file's state is touched: each pass walks only the new nodes.
796
// The new functions carry no proven store-free bit, which is
797
// the sound not-proven default until the next effects solve.
798
// Namespaces named by the new declarations
799
// merge into the retained namespace map exactly as they would in
800
// a full build.
801
build_appended(source_file: SOURCE_FILE, new_definitions: Syntax.Trees.Definitions.LIST) is
802
source_file.want_compile_up_to_expressions = true
803
source_file.want_compile_expressions = true
804
805
_run_appended_pass(source_file, "declare-symbols", () is container.declare_symbols.apply(new_definitions); si)
806
_run_appended_pass(source_file, "resolve-uses", () is container.resolve_uses.apply(new_definitions); si)
807
_run_appended_pass(source_file, "declare-members", () is container.declare_members.apply(new_definitions); si)
808
_run_appended_pass(source_file, "resolve-member-uses", () is container.resolve_member_uses.apply(new_definitions); si)
809
_run_appended_pass(source_file, "resolve-type-expressions", () is container.resolve_type_expressions.apply(new_definitions); si)
810
_run_appended_pass(source_file, "resolve-ancestors", () is container.resolve_ancestors.apply(new_definitions); si)
811
_run_appended_pass(source_file, "synthesize-class-equality", () is container.synthesize_class_equality.apply(new_definitions); si)
812
_run_appended_pass(source_file, "synthesize-iterator-reset", () is container.synthesize_iterator_reset.apply(new_definitions); si)
813
_run_appended_pass(source_file, "resolve-explicit-types", () is container.resolve_explicit_types.apply(new_definitions); si)
814
_run_appended_pass(source_file, "check-type-argument-bounds", () is container.check_type_argument_bounds.apply(new_definitions); si)
815
_run_appended_pass(source_file, "resolve-overrides", () is container.resolve_overrides.apply(new_definitions); si)
816
817
// These two are whole-file, declaration-level walks that
818
// only otherwise run as part of build_passes - the same gap
819
// rewalk_bodies and compile_expressions_only close for their
820
// own paths. Appended declarations get no other pass over
821
// them for either, so a badly-named or virtuality-relevant
822
// new member would go unflagged until the next full rebuild.
823
container.check_name_conventions.apply(source_file)
824
container.definition_virtuality.apply(source_file)
825
826
_run_appended_pass(source_file, "mark-boxed-locals", () is container.mark_boxed_locals.apply(new_definitions, _want_boxing); si)
827
828
logger.set_is_compiling_expressions(true)
829
830
_run_appended_pass(source_file, "compile-expressions", () is container.compile_expressions.apply(new_definitions); si)
831
832
logger.set_is_compiling_expressions(false)
833
si
834
835
// Declare and resolve a container's changed function members
836
// against the retained symbol table, with the scope cursor
837
// positioned inside the enclosing namespace and class scopes the
838
// way the build loop's tree walk would have it. Split from the
839
// expression walk so a class's pull-down can re-run between the
840
// two: overrides and pulled-down members must reflect the new
841
// member set before any body compiles against them.
842
build_members_interface(
843
source_file: SOURCE_FILE,
844
enclosing: Collections.List[Syntax.Trees.Definitions.Definition],
845
members: Syntax.Trees.Definitions.LIST
846
) is
847
source_file.want_compile_up_to_expressions = true
848
source_file.want_compile_expressions = true
849
850
_run_positioned_pass(source_file, enclosing, "declare-members", () is container.declare_members.apply(members); si)
851
_run_positioned_pass(source_file, enclosing, "resolve-type-expressions", () is container.resolve_type_expressions.apply(members); si)
852
_run_positioned_pass(source_file, enclosing, "resolve-explicit-types", () is container.resolve_explicit_types.apply(members); si)
853
_run_positioned_pass(source_file, enclosing, "check-type-argument-bounds", () is container.check_type_argument_bounds.apply(members); si)
854
si
855
856
build_members_expressions(
857
source_file: SOURCE_FILE,
858
enclosing: Collections.List[Syntax.Trees.Definitions.Definition],
859
members: Syntax.Trees.Definitions.LIST
860
) is
861
_run_positioned_pass(source_file, enclosing, "mark-boxed-locals", () is container.mark_boxed_locals.apply(members, _want_boxing); si)
862
863
logger.set_is_compiling_expressions(true)
864
865
_run_positioned_pass(source_file, enclosing, "compile-expressions", () is container.compile_expressions.apply(members); si)
866
867
logger.set_is_compiling_expressions(false)
868
869
// Same reasoning as build_appended: the replaced window's
870
// members get no other pass over these two whole-file
871
// declaration-level walks. Run here, after
872
// build_members_interface's caller has re-pulled overrides
873
// for the changed closure, so definition-virtuality's glyphs
874
// reflect the reconciled dispatch state rather than the
875
// pre-replace one.
876
container.check_name_conventions.apply(source_file)
877
container.definition_virtuality.apply(source_file)
878
si
879
880
// One pass application over a changed member subtree, with the
881
// symbol-table and namespace cursors positioned inside the
882
// enclosing scopes first - a namespace enters through its own
883
// node's scope plus the namespace prefix stack, a class-like
884
// definition through its own node's scope, mirroring
885
// ScopedVisitor. The finally's release and pop restore both
886
// cursors however deep the positioning got.
887
_run_positioned_pass(
888
source_file: SOURCE_FILE,
889
enclosing: Collections.List[Syntax.Trees.Definitions.Definition],
890
pass_name: string,
891
apply_pass: () -> void
892
) is
893
WORK_COUNTERS.subtree_pass(container.timers, pass_name)
894
895
let symbol_table = container.symbol_table
896
let symbol_table_mark = symbol_table.mark_scope_stack()
897
let diagnostics_mark = logger.mark()
898
let symbol_uses_mark = container.symbol_use_locations.mark()
899
900
try
901
for node in enclosing do
902
if let namespace_definition: Syntax.Trees.Definitions.NAMESPACE = node then
903
container.namespaces.enter_namespace(namespace_definition.name.location, namespace_definition.name.name)
904
symbol_table.enter_scope(namespace_definition)
905
elif let carrier: Syntax.Trees.ScopeCarrier = node then
906
symbol_table.enter_scope(carrier)
907
else
908
logger.poison(node.location, "no scope found for {node.get_type()}")
909
fi
910
od
911
912
apply_pass()
913
catch e: Exception
914
logger.exception(diagnostics_mark, source_file.definition.location, e, "caught exception building changed members in {source_file}")
915
finally
916
logger.release(diagnostics_mark)
917
container.symbol_use_locations.release(symbol_uses_mark)
918
symbol_table.release_scope_stack(symbol_table_mark)
919
920
container.namespaces.pop_all_namespaces()
921
yrt
922
si
923
924
// One pass application over an appended subtree, under the build
925
// loop's per-file bookkeeping.
926
_run_appended_pass(source_file: SOURCE_FILE, pass_name: string, apply_pass: () -> void) is
927
WORK_COUNTERS.subtree_pass(container.timers, pass_name)
928
929
let symbol_table = container.symbol_table
930
let symbol_table_mark = symbol_table.mark_scope_stack()
931
let diagnostics_mark = logger.mark()
932
let symbol_uses_mark = container.symbol_use_locations.mark()
933
934
try
935
apply_pass()
936
catch e: Exception
937
logger.exception(diagnostics_mark, source_file.definition.location, e, "caught exception building appended declarations in {source_file}")
938
finally
939
logger.release(diagnostics_mark)
940
container.symbol_use_locations.release(symbol_uses_mark)
941
symbol_table.release_scope_stack(symbol_table_mark)
942
943
container.namespaces.pop_all_namespaces()
944
yrt
945
si
946
947
// Incremental body re-walk: re-process only the edited file's new
948
// function bodies, after its donor bodies have been spliced in by the
949
// analysis-mode EDIT handler. Every pass runs body-scoped — through
950
// BODY_DECLARER for declare-symbols, BODY_REWALKER for the rest — so
951
// the retained interface (arguments, signatures, indexer / property
952
// parameters) is never re-processed. Re-resolving a retained,
953
// already-typed argument would poison it ("set type twice"); the
954
// bodies are leaves, so processing them alone is also sufficient.
955
// resolve-uses / resolve-ancestors / resolve-overrides are skipped —
956
// they do not visit body interiors and the interface is unchanged.
957
//
958
// Before the re-walk re-records the bodies, the edited file's stale
959
// symbol-use / definition entries are reconciled: `correspondence`
960
// gives every retained interface node's post-edit location, and
961
// `body_spans` the pre-edit spans of the re-walked bodies. Body
962
// entries are dropped (re-recorded here), interface entries and
963
// symbols moved to their post-edit locations.
964
rewalk_bodies(
965
source_file: SOURCE_FILE,
966
correspondence: Source.LOCATION_CORRESPONDENCE,
967
body_spans: Source.BODY_SPANS
968
) is
969
source_file.want_compile_up_to_expressions = true
970
source_file.want_compile_expressions = true
971
_effects_stale_files.add(source_file.file_name)
972
973
Syntax.Process.KILL_LEDGER.clear_for(source_file.file_name)
974
975
// Definition reconciliation first — it classifies symbols by
976
// their pre-edit location, before the use reconciliation moves
977
// the interface symbols.
978
container.symbol_definition_locations.refresh_edited_file(source_file.file_name, body_spans)
979
container.symbol_use_locations.refresh_edited_file(source_file.file_name, correspondence, body_spans)
980
981
let flags = container.build_flags
982
let symbol_table = container.symbol_table
983
984
let body_declarer = Syntax.Process.BODY_DECLARER(
985
logger,
986
symbol_table,
987
container.namespaces,
988
container.declare_members
989
)
990
991
// body-scoped declare-symbols
992
WORK_COUNTERS.file_rewalk(container.timers, "declare-symbols")
993
994
let mark = symbol_table.mark_scope_stack()
995
let diagnostics_mark = logger.mark()
996
997
try
998
source_file.definition.walk(body_declarer)
999
catch e: Exception
1000
logger.exception(diagnostics_mark, source_file.definition.location, e, "caught exception declaring bodies in incremental re-walk of {source_file}")
1001
finally
1002
logger.release(diagnostics_mark)
1003
symbol_table.release_scope_stack(mark)
1004
container.namespaces.pop_all_namespaces()
1005
yrt
1006
1007
rewalk_pass(source_file, "resolve-type-expressions", container.resolve_type_expressions)
1008
rewalk_pass(source_file, "synthesize-class-equality", container.synthesize_class_equality)
1009
rewalk_pass(source_file, "synthesize-iterator-reset", container.synthesize_iterator_reset)
1010
rewalk_pass(source_file, "resolve-explicit-types", container.resolve_explicit_types)
1011
rewalk_pass(source_file, "check-type-argument-bounds", container.check_type_argument_bounds)
1012
1013
if flags.want_assembler \/ flags.want_executable then
1014
rewalk_pass(source_file, "record-type-argument-uses", container.record_type_argument_uses)
1015
fi
1016
1017
// The edited file's declaration-level diagnostics were
1018
// dropped along with everything else before the re-walk, and
1019
// no whole-project pass runs to replace them. This one is a
1020
// per-file declaration walk over symbols an
1021
// interface-preserving edit did not touch, so re-running it
1022
// here restores its inlays against the refreshed locations
1023
// rather than leaving them absent until the next full build.
1024
container.definition_virtuality.apply(source_file)
1025
1026
// Same reasoning: check-name-conventions is another
1027
// whole-file, declaration-level walk that only otherwise runs
1028
// as part of build_passes. Without this, a name-convention
1029
// warning on a declaration an interface-preserving edit did
1030
// not touch is dropped by the clear above and never comes
1031
// back - `are_tables_current` staying true means the
1032
// whole-project rebuild that would re-run it never happens
1033
// again for this file.
1034
container.check_name_conventions.apply(source_file)
1035
1036
logger.set_is_compiling_expressions(true)
1037
1038
// The re-walk drives the visitor directly rather than through
1039
// its apply(), so the state apply() drops at a walk boundary
1040
// has to be dropped here instead. Left in place, the phantom
1041
// type-arg placeholders minted during the previous walk stay
1042
// in the registry, and the first function whose retry loop
1043
// fails to converge sweeps them and reports them against
1044
// their pre-edit locations.
1045
container.compile_expressions.reset_between_walks()
1046
1047
// Driven over the re-walked bodies rather than through
1048
// `apply`, so the marking is bracketed by hand.
1049
container.mark_boxed_locals.begin_marking()
1050
rewalk_pass(source_file, "mark-boxed-locals", container.mark_boxed_locals)
1051
container.mark_boxed_locals.end_marking(_want_boxing)
1052
1053
rewalk_pass(source_file, "compile-expressions", container.compile_expressions)
1054
1055
logger.set_is_compiling_expressions(false)
1056
1057
// Every constructor this re-walk touched has a fresh
1058
// summary; without this, the field check's report() -
1059
// called only from a whole-project build and from
1060
// compile_expressions_only - never runs on this path, and
1061
// a field a prior full build already flagged (or cleared)
1062
// stops tracking the source entirely once analysis mode
1063
// starts driving edits through incremental re-walk.
1064
container.compile_expressions.report_field_assignments()
1065
si
1066
1067
// Run one body-visiting pass over the edited file's bodies only,
1068
// driven by BODY_REWALKER, under a scope-stack mark/release. The
1069
// pass name is passed separately because a body-visiting pass is
1070
// a bare visitor, not a registered Pass with a description.
1071
rewalk_pass(source_file: SOURCE_FILE, pass_name: string, pass: Syntax.Visitor) is
1072
WORK_COUNTERS.file_rewalk(container.timers, pass_name)
1073
1074
let symbol_table = container.symbol_table
1075
let mark = symbol_table.mark_scope_stack()
1076
let diagnostics_mark = logger.mark()
1077
1078
let rewalker = Syntax.Process.BODY_REWALKER(
1079
logger,
1080
symbol_table,
1081
container.namespaces,
1082
pass
1083
)
1084
1085
try
1086
source_file.definition.walk(rewalker)
1087
catch e: Exception
1088
logger.exception(diagnostics_mark, source_file.definition.location, e, "caught exception in incremental re-walk of {source_file}")
1089
finally
1090
logger.release(diagnostics_mark)
1091
symbol_table.release_scope_stack(mark)
1092
container.namespaces.pop_all_namespaces()
1093
yrt
1094
si
1095
1096
conditional_compilation_pass(source_file: SOURCE_FILE) -> bool is
1097
let definition = source_file.definition
1098
1099
container
1100
.conditional_compilation
1101
.apply(
1102
definition
1103
)
1104
1105
return true
1106
si
1107
1108
rewrite_syntax_tree_pass(source_file: SOURCE_FILE) -> bool is
1109
let definition = source_file.definition
1110
1111
container
1112
.rewrite_primary_constructors
1113
.apply(definition)
1114
1115
container
1116
.synthesise_top_level_entry
1117
.apply(definition)
1118
1119
container
1120
.synthesise_entry_environment_wrapper
1121
.apply(definition)
1122
1123
container
1124
.expand_namespaces
1125
.apply(definition)
1126
1127
container
1128
.add_accessors_for_properties
1129
.apply(definition)
1130
1131
container
1132
.spill_awaits
1133
.apply(definition)
1134
1135
return true
1136
si
1137
1138
collect_suppress_pragmas_pass(source_file: SOURCE_FILE) -> bool is
1139
container
1140
.collect_suppress_pragmas
1141
.apply(source_file)
1142
1143
return true
1144
si
1145
1146
collect_modifier_keyword_locations_pass(source_file: SOURCE_FILE) -> bool is
1147
container
1148
.collect_modifier_keyword_locations
1149
.apply(source_file)
1150
1151
return true
1152
si
1153
1154
check_name_conventions_pass(source_file: SOURCE_FILE) -> bool is
1155
container
1156
.check_name_conventions
1157
.apply(source_file)
1158
1159
return true
1160
si
1161
1162
// Settle the entry point outside a build. The debounced COMPILE
1163
// an editor sends once edits stop is the one place the answer is
1164
// wanted in analysis mode, and by then its own path may have
1165
// rebuilt nothing - the edit that preceded it already did.
1166
select_entry_point(files: Collections.Iterable[SOURCE_FILE]) is
1167
container.select_entry_point.start()
1168
1169
for file in files do
1170
container.select_entry_point.apply(file)
1171
od
1172
1173
container.select_entry_point.finish()
1174
si
1175
1176
select_entry_point_pass(source_file: SOURCE_FILE) -> bool is
1177
if !_want_entry_point_selection then
1178
return false
1179
fi
1180
1181
container
1182
.select_entry_point
1183
.apply(source_file)
1184
1185
return true
1186
si
1187
1188
definition_virtuality_pass(source_file: SOURCE_FILE) -> bool is
1189
container
1190
.definition_virtuality
1191
.apply(source_file)
1192
1193
return true
1194
si
1195
1196
declare_symbols_pass(source_file: SOURCE_FILE) -> bool is
1197
let flags = container.build_flags
1198
let definition = source_file.definition
1199
1200
if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then
1201
container
1202
.declare_symbols
1203
.apply(definition)
1204
1205
return true
1206
fi
1207
1208
return false
1209
si
1210
1211
resolve_uses_pass(source_file: SOURCE_FILE) -> bool is
1212
let flags = container.build_flags
1213
let definition = source_file.definition
1214
1215
if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then
1216
container
1217
.resolve_uses
1218
.apply(definition)
1219
1220
return true
1221
fi
1222
1223
return false
1224
si
1225
1226
declare_members_pass(source_file: SOURCE_FILE) -> bool is
1227
let flags = container.build_flags
1228
let definition = source_file.definition
1229
1230
if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then
1231
container
1232
.declare_members
1233
.apply(definition)
1234
1235
return true
1236
fi
1237
1238
return false
1239
si
1240
1241
resolve_member_uses_pass(source_file: SOURCE_FILE) -> bool is
1242
let flags = container.build_flags
1243
let definition = source_file.definition
1244
1245
if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then
1246
container
1247
.resolve_member_uses
1248
.apply(definition)
1249
1250
return true
1251
fi
1252
1253
return false
1254
si
1255
1256
resolve_ancestors_pass(source_file: SOURCE_FILE) -> bool is
1257
let flags = container.build_flags
1258
let definition = source_file.definition
1259
1260
if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then
1261
container
1262
.resolve_ancestors
1263
.apply(definition)
1264
1265
return true
1266
fi
1267
1268
return false
1269
si
1270
1271
synthesize_class_equality_pass(source_file: SOURCE_FILE) -> bool is
1272
let flags = container.build_flags
1273
let definition = source_file.definition
1274
1275
if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then
1276
container
1277
.synthesize_class_equality
1278
.apply(definition)
1279
1280
return true
1281
fi
1282
1283
return false
1284
si
1285
1286
synthesize_iterator_reset_pass(source_file: SOURCE_FILE) -> bool is
1287
let flags = container.build_flags
1288
let definition = source_file.definition
1289
1290
if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then
1291
container
1292
.synthesize_iterator_reset
1293
.apply(definition)
1294
1295
return true
1296
fi
1297
1298
return false
1299
si
1300
1301
resolve_type_expressions_pass(source_file: SOURCE_FILE) -> bool is
1302
let flags = container.build_flags
1303
let definition = source_file.definition
1304
1305
if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then
1306
container
1307
.resolve_type_expressions
1308
.apply(definition)
1309
1310
return true
1311
fi
1312
1313
return false
1314
si
1315
1316
resolve_explicit_types_pass(source_file: SOURCE_FILE) -> bool is
1317
let flags = container.build_flags
1318
let definition = source_file.definition
1319
1320
if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then
1321
container
1322
.resolve_explicit_types
1323
.apply(definition)
1324
1325
return true
1326
fi
1327
1328
return false
1329
si
1330
1331
check_type_argument_bounds_pass(source_file: SOURCE_FILE) -> bool is
1332
let flags = container.build_flags
1333
let definition = source_file.definition
1334
1335
if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then
1336
container
1337
.check_type_argument_bounds
1338
.apply(definition)
1339
1340
return true
1341
fi
1342
1343
return false
1344
si
1345
1346
verify_compile_state_pass(source_file: SOURCE_FILE) -> bool is
1347
if source_file.want_compile_expressions /\ !_verify_compile_state_had_errors then
1348
container
1349
.verify_compile_state
1350
.apply(source_file.definition)
1351
1352
return true
1353
fi
1354
1355
return false
1356
si
1357
1358
compile_expressions_pass(source_file: SOURCE_FILE) -> bool is
1359
let definition = source_file.definition
1360
1361
if source_file.want_compile_expressions then
1362
container
1363
.compile_expressions
1364
.apply(definition)
1365
1366
return true
1367
fi
1368
1369
return false
1370
si
1371
1372
// Whether the boxing flags are worth deriving from the
1373
// marking. Boxing is a code generation strategy; the marking
1374
// itself is needed on every build, because flow narrowing
1375
// consults it.
1376
_want_boxing: bool =>
1377
container.build_flags.want_assembler \/
1378
container.build_flags.want_executable
1379
1380
mark_boxed_locals_pass(source_file: SOURCE_FILE) -> bool is
1381
let definition = source_file.definition
1382
1383
// The capture and reassignment marking is needed on
1384
// every build, because flow narrowing consults it to
1385
// decline facts on a local a closure body can rewrite.
1386
// Boxing only matters once the IR is lowered to IL, so
1387
// deriving `is_boxed` from the same flags stays gated on
1388
// an IL-bound build. Either way, pair the gate with
1389
// compile-expressions - if the file isn't being walked
1390
// for expression-level semantics this run, no downstream
1391
// consumer would see the flags we'd set.
1392
if source_file.want_compile_expressions then
1393
container
1394
.mark_boxed_locals
1395
.apply(definition, _want_boxing)
1396
1397
return true
1398
fi
1399
1400
return false
1401
si
1402
1403
resolve_overrides_pass(source_file: SOURCE_FILE) -> bool is
1404
let definition = source_file.definition
1405
1406
if source_file.want_compile_up_to_expressions then
1407
container
1408
.resolve_overrides
1409
.apply(definition)
1410
1411
return true
1412
fi
1413
1414
return false
1415
si
1416
1417
// The re-walk loop of the combined type + effect solve
1418
// prototype. A body walked before any relations were solved,
1419
// or against relations an edit has since changed, may disagree
1420
// with the relations just solved: the judge reports facts it
1421
// kept that are unbacked, and the ledger reports facts it
1422
// dropped that are backed. Files with either are re-walked
1423
// against the new relations, the relations are solved again,
1424
// and the loop stops when a solve finds every file exact.
1425
// Widening can only add callees and writes, and a re-walk
1426
// only reads the relations, so the loop is bounded; the cap is
1427
// a guard. See KILL_LEDGER.
1428
run_combined_solve_rounds(files: Collections.Iterable[SOURCE_FILE]) is
1429
if !Syntax.Process.EFFECTS.is_complete then
1430
return
1431
fi
1432
1433
let by_name = Collections.MAP[string, SOURCE_FILE]()
1434
1435
for source_file in files do
1436
by_name[source_file.file_name] = source_file
1437
od
1438
1439
let round mut = 1
1440
let max_rounds = 16
1441
1442
while round <= max_rounds do
1443
let inexact = Syntax.Process.KILL_LEDGER.take_inexact()
1444
1445
if inexact.is_empty then
1446
break
1447
fi
1448
1449
// Whole-file re-walks, for comparing against the per-body form.
1450
if System.Environment.get_environment_variable("GHUL_SOLVE_FILEWISE")? then
1451
for function in inexact.bodies do
1452
inexact.files.add(function.location.file_name)
1453
od
1454
1455
inexact.bodies.clear()
1456
fi
1457
1458
let timing = System.Environment.get_environment_variable("GHUL_EFFECTS_TIMING")?
1459
let sw = System.Diagnostics.Stopwatch.start_new()
1460
1461
Syntax.Process.KILL_LEDGER.in_descent = true
1462
1463
let rewalked = Collections.SET[SOURCE_FILE]()
1464
1465
// A file re-walked whole covers its inexact bodies too.
1466
for file_name in inexact.files do
1467
let source_file: SOURCE_FILE mut
1468
1469
if by_name.try_get_value(file_name, source_file ref) then
1470
recompile_file_expressions(source_file)
1471
rewalked.add(source_file)
1472
fi
1473
od
1474
1475
for function in inexact.bodies do
1476
let source_file: SOURCE_FILE mut
1477
1478
if by_name.try_get_value(function.location.file_name, source_file ref) then
1479
if !inexact.files.contains(source_file.file_name) then
1480
recompile_body_expressions(source_file, function)
1481
fi
1482
1483
rewalked.add(source_file)
1484
fi
1485
od
1486
1487
Syntax.Process.KILL_LEDGER.in_descent = false
1488
1489
let recompile_ms = sw.elapsed_milliseconds
1490
1491
let names = Collections.LIST[string]()
1492
1493
for source_file in rewalked do
1494
names.add(source_file.file_name)
1495
od
1496
1497
_run_partial_effects_round(files, names)
1498
1499
if timing then
1500
IO.Std.error.write_line("combined solve round {round}: recompile {recompile_ms} ms, effects {sw.elapsed_milliseconds - recompile_ms} ms")
1501
fi
1502
1503
round = round + 1
1504
od
1505
si
1506
1507
_dump_kills() is
1508
if let dump = System.Environment.get_environment_variable("GHUL_KILL_DUMP") then
1509
let writer = IO.File.append_text(dump)
1510
writer.write_line("=== solve")
1511
Syntax.Process.KILL_LEDGER.dump(writer)
1512
writer.dispose()
1513
fi
1514
si
1515
1516
// The infer-effects pass as a directly callable round, for the
1517
// analysis-mode COMPILE paths that recompile expressions
1518
// without running the pass list: re-walk every file's bodies
1519
// with resolved types, solve the effect relations, and judge
1520
// whatever reliances those recompiles recorded. Without this,
1521
// a reliance recorded on an incremental path would never be
1522
// reported.
1523
run_effects_pass(files: Collections.Iterable[SOURCE_FILE]) is
1524
let timing = System.Environment.get_environment_variable("GHUL_EFFECTS_TIMING")?
1525
let sw = System.Diagnostics.Stopwatch.start_new()
1526
1527
_restart_descent(files)
1528
1529
let t_restart = sw.elapsed_milliseconds
1530
1531
// Only the files walked since the last solve can have
1532
// changed what it reads; everything else's facts are
1533
// retained. That needs a complete solve to build on, and
1534
// for every such file to have been compiled through
1535
// expressions, or the round is the full one.
1536
let stale = Collections.LIST[string]()
1537
1538
for file_name in _effects_stale_files do
1539
stale.add(file_name)
1540
od
1541
1542
if _can_solve_partially(files, stale) then
1543
_run_partial_effects_round(files, stale)
1544
else
1545
_run_effects_round(files)
1546
fi
1547
1548
let t_round = sw.elapsed_milliseconds
1549
1550
run_combined_solve_rounds(files)
1551
1552
let t_rounds = sw.elapsed_milliseconds
1553
1554
_dump_kills()
1555
1556
run_post_effects_purity_checks(files)
1557
1558
if timing then
1559
IO.Std.error.write_line("effects pass: restart {t_restart} round {t_round - t_restart} descent {t_rounds - t_round} purity {sw.elapsed_milliseconds - t_rounds} ms")
1560
fi
1561
si
1562
1563
// The answer is defined as the descent from every fact kept, which
1564
// is what a batch build does. An edit path walks a body against
1565
// the last complete solve instead, which is fast but starts the
1566
// descent lower than the top, and from there it can settle short
1567
// of the batch answer. So a COMPILE after any such walk first
1568
// re-walks the bodies that killed outside a descent with kills
1569
// suspended; every other body walked as it would have from the
1570
// top already.
1571
_restart_descent(files: Collections.Iterable[SOURCE_FILE]) is
1572
Syntax.Process.KILL_LEDGER.kills_suspended = false
1573
1574
if !Syntax.Process.KILL_LEDGER.needs_restart then
1575
return
1576
fi
1577
1578
let by_name = Collections.MAP[string, SOURCE_FILE]()
1579
1580
for source_file in files do
1581
by_name[source_file.file_name] = source_file
1582
od
1583
1584
let bodies = Syntax.Process.KILL_LEDGER.take_restart_bodies()
1585
let sw = System.Diagnostics.Stopwatch.start_new()
1586
1587
Syntax.Process.KILL_LEDGER.kills_suspended = true
1588
1589
for function in bodies do
1590
let source_file: SOURCE_FILE mut
1591
1592
if by_name.try_get_value(function.location.file_name, source_file ref) then
1593
recompile_body_expressions(source_file, function)
1594
fi
1595
od
1596
1597
Syntax.Process.KILL_LEDGER.kills_suspended = false
1598
Syntax.Process.KILL_LEDGER.needs_restart = false
1599
1600
if System.Environment.get_environment_variable("GHUL_EFFECTS_TIMING")? then
1601
IO.Std.error.write_line("descent restarted from the top: {bodies.count} body(ies) re-walked in {sw.elapsed_milliseconds} ms")
1602
fi
1603
si
1604
1605
// One walk-and-solve round of the effects pass. The judge and
1606
// the purity checks run once, against the relations the last
1607
// round solved.
1608
_run_effects_round(files: Collections.Iterable[SOURCE_FILE]) is
1609
_effects_stale_files.clear()
1610
1611
Syntax.Process.EFFECT_FACTS.collecting = true
1612
Syntax.Process.EFFECT_FACTS.begin_generation("infer-effects")
1613
1614
container.infer_store_free.set_resolved_mode(true)
1615
container.infer_store_free.start_run()
1616
1617
for source_file in files do
1618
infer_effects_pass(source_file)
1619
od
1620
1621
container.infer_store_free.solve_effects(_all_compiled_through_expressions(files))
1622
container.infer_store_free.set_resolved_mode(false)
1623
1624
Syntax.Process.EFFECT_FACTS.collecting = false
1625
si
1626
1627
// Whether every one of these files has had its expressions
1628
// compiled, which is what makes a solve over them complete.
1629
_all_compiled_through_expressions(files: Collections.Iterable[SOURCE_FILE]) -> bool is
1630
let timing = System.Environment.get_environment_variable("GHUL_EFFECTS_TIMING")?
1631
let complete mut = true
1632
1633
for source_file in files do
1634
if !is_compiled_through_expressions(source_file) then
1635
if timing then
1636
IO.Std.error.write_line("effects: solve incomplete, not compiled through expressions: {source_file.file_name} (compiled_through {source_file.compiled_through?.description ?? "none"})")
1637
fi
1638
1639
complete = false
1640
fi
1641
od
1642
1643
return complete
1644
si
1645
1646
// Whether the stale files can be solved as a partial round over
1647
// the retained facts: the relations in use were solved from
1648
// them, and every stale file still present is compiled through
1649
// expressions.
1650
_can_solve_partially(files: Collections.Iterable[SOURCE_FILE], stale: Collections.List[string]) -> bool is
1651
if !container.infer_store_free.solved_over_facts then
1652
return false
1653
fi
1654
1655
let names = Collections.SET[string]()
1656
1657
for file_name in stale do
1658
names.add(file_name)
1659
od
1660
1661
for source_file in files do
1662
if names.contains(source_file.file_name) /\ !is_compiled_through_expressions(source_file) then
1663
return false
1664
fi
1665
od
1666
1667
return true
1668
si
1669
1670
// A round over only the files whose bodies were re-walked: their
1671
// facts are dropped and re-derived, every other file's are kept
1672
// from the previous round, and the relations are solved over
1673
// the union. The previous round left the facts in place for
1674
// exactly this.
1675
_run_partial_effects_round(files: Collections.Iterable[SOURCE_FILE], names: Collections.List[string]) is
1676
_effects_stale_files.clear()
1677
1678
Syntax.Process.EFFECT_FACTS.collecting = true
1679
1680
container.infer_store_free.set_resolved_mode(true)
1681
1682
let by_name = Collections.MAP[string, SOURCE_FILE]()
1683
1684
for source_file in files do
1685
by_name[source_file.file_name] = source_file
1686
od
1687
1688
// The re-walk usually re-derives exactly what it replaces: a
1689
// kill widens a receiver, and the widened dispatch reaches the
1690
// same callees and writes the same members; an edit that
1691
// touches no call or store leaves them alone too. Then the
1692
// installed relations already answer for the facts and the
1693
// solve is skipped.
1694
let timing = System.Environment.get_environment_variable("GHUL_EFFECTS_TIMING")?
1695
let sw = System.Diagnostics.Stopwatch.start_new()
1696
1697
let snapshot = container.infer_store_free.snapshot_inputs(names)
1698
1699
for file_name in names do
1700
container.infer_store_free.drop_file(file_name)
1701
od
1702
1703
for file_name in names do
1704
let source_file: SOURCE_FILE mut
1705
1706
if by_name.try_get_value(file_name, source_file ref) then
1707
infer_effects_pass(source_file)
1708
fi
1709
od
1710
1711
if timing then
1712
IO.Std.error.write_line("effects: partial round snapshot, drop and re-walk {sw.elapsed_milliseconds} ms")
1713
fi
1714
1715
let changed = container.infer_store_free.changed_inputs(names, snapshot)
1716
1717
if changed.count == 0 then
1718
if timing then
1719
IO.Std.error.write_line("effects: partial round over {names.count} file(s) changed no solve input, relations kept")
1720
fi
1721
else
1722
if timing then
1723
IO.Std.error.write_line("effects: partial round over {names.count} file(s) changed the inputs of {changed.count} function(s), re-solving")
1724
fi
1725
1726
container.infer_store_free.resolve_effects_for(changed)
1727
fi
1728
1729
container.infer_store_free.set_resolved_mode(false)
1730
1731
Syntax.Process.EFFECT_FACTS.collecting = false
1732
si
1733
1734
// The two purity checks that need a body's own store-freedom:
1735
// what reached a pure function-typed slot, and whether an
1736
// operator or override that has to be pure actually is. Both
1737
// ask the solved effect relations, so both run here rather
1738
// than against the declared-types-only approximation that is
1739
// all a pass before compile-expressions could have.
1740
run_post_effects_purity_checks(files: Collections.Iterable[SOURCE_FILE]) is
1741
logger.set_is_compiling_expressions(true)
1742
1743
Syntax.Process.PURE_SLOT_JUDGE(
1744
container.logger,
1745
container.infer_store_free.function_values).judge()
1746
1747
for source_file in files do
1748
if source_file.want_compile_up_to_expressions then
1749
check_pure_overrides_pass(source_file)
1750
fi
1751
od
1752
1753
logger.set_is_compiling_expressions(false)
1754
si
1755
1756
infer_effects_pass(source_file: SOURCE_FILE) -> bool is
1757
let definition = source_file.definition
1758
1759
if source_file.want_compile_up_to_expressions then
1760
// Always the full body re-walk: the per-file facts
1761
// retained for the pre-expressions run were derived
1762
// from declared types only, and reusing them here would
1763
// reintroduce exactly the imprecision this pass exists
1764
// to remove.
1765
container.infer_store_free.apply(definition)
1766
1767
return true
1768
fi
1769
1770
return false
1771
si
1772
1773
register_source_intrinsics_pass(source_file: SOURCE_FILE) -> bool is
1774
let flags = container.build_flags
1775
1776
if flags.want_declare_symbols \/ source_file.want_compile_up_to_expressions then
1777
container
1778
.register_source_intrinsics
1779
.apply(source_file.definition)
1780
1781
return true
1782
fi
1783
1784
return false
1785
si
1786
1787
check_pure_overrides_pass(source_file: SOURCE_FILE) -> bool is
1788
let definition = source_file.definition
1789
1790
if source_file.want_compile_up_to_expressions then
1791
container
1792
.check_pure_overrides
1793
.apply(definition)
1794
1795
return true
1796
fi
1797
1798
return false
1799
si
1800
1801
record_type_argument_uses_pass(source_file: SOURCE_FILE) -> bool is
1802
let flags = container.build_flags
1803
1804
if flags.want_assembler \/ flags.want_executable then
1805
let definition = source_file.definition
1806
1807
container
1808
.record_type_argument_uses
1809
.apply(definition)
1810
1811
return true
1812
fi
1813
1814
return false
1815
si
1816
1817
// Whether this build produces an assembly. Analysis mode does not,
1818
// and the generate-il pass then has nothing to do - including in
1819
// its whole-build hooks, which lay out and write the metadata for
1820
// an image nobody asks for.
1821
is_emitting_assembly: bool =>
1822
let flags = container.build_flags in
1823
flags.want_assembler \/ flags.want_executable
1824
1825
generate_il_pass(source_file: SOURCE_FILE) -> bool is
1826
if is_emitting_assembly then
1827
let definition = source_file.definition
1828
1829
container
1830
.generate_il
1831
.apply(definition)
1832
1833
return true
1834
fi
1835
1836
return false
1837
si
1838
si
1839
si