8000 rewrite arch-time · rysh/docs.scala-lang@23c0f3c · GitHub
[go: up one dir, main page]

Skip to content

Commit 23c0f3c

Browse files
committed
rewrite arch-time
1 parent 08e7e33 commit 23c0f3c

File tree

2 files changed

+50
-95
lines changed

2 files changed

+50
-95
lines changed

_overviews/scala3-contribution/arch-lifecycle.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,17 @@ representation that can run on one of Scala's target platforms.
1313

1414
## Introducing the Compiler's Lifecycle
1515

16+
At a high level, `dotc` is an interactive compiler, and can be invoked frequently,
17+
for example to answer questions for an IDE, provide REPL completions,
18+
or to manage incremental builds and more. Each of these use cases requires a customised
19+
workflow, but sharing a common core.
20+
1621
#### Core
17-
At a high level, `dotc` centers its work around a [Compiler], which maintains an ordered
18-
list of [phases][Phases], and is responsible for creating new [runs][Run].
19-
A run is a complete iteration of the compiler's phases over a list of input sources.
20-
A compiler is designed to be reusable and can create many runs over its lifetime.
22+
Customisation is provided by extending the [Compiler] class, which maintains an ordered
23+
list of [phases][Phases], and how to [run][Run] them. Each interaction with a compiler
24+
creates a new run, which is a complete iteration of the compiler's phases over a list
25+
of input sources. Runs enable `dotc` to be [aware of time][time], as each run can
26+
potentially generate new compiler entities and invalidate older ones.
2127

2228
#### Runs
2329
During a run, the input sources are converted to [compilation units][CompilationUnit] (i.e. the abstraction of
@@ -43,9 +49,6 @@ and the [Context][contexts]. For convenience, the [Driver] class contains high l
4349
configuring the compiler and invoking it programatically. The object [Main] inherits from `Driver`
4450
and is invoked by the `scalac` script.
4551

46-
<!-- #### Further Reading -->
47-
> [Read more about a compiler's lifecyle][time].
48-
4952
## Code Structure
5053

5154
The code of the compiler is found in the package [dotty.tools],

_overviews/scala3-contribution/arch-time.md

Lines changed: 40 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -7,94 +7,46 @@ previous-page: arch-types
77
next-page: arch-symbols
88
---
99

10-
> (The following is work in progress), adapted from dotty.epfl.ch
11-
12-
Conceptually, the `dotc` compiler's job is to maintain views of various
13-
artifacts associated with source code at all points in time. But what is
14-
*time* for `dotc`? In fact, it is a combination of compiler runs and compiler
15-
phases.
16-
17-
The *hours* of the compiler's clocks are measured in compiler [runs]. Every run
18-
creates a new hour, which follows all the compiler runs (hours) that happened
19-
before. `dotc` is designed to be used as an incremental compiler that can
20-
support incremental builds, as well as interactions in an IDE and a REPL. This
21-
means that new runs can occur quite frequently. At the extreme, every
22-
keystroke in an editor or REPL can potentially launch a new compiler run, so
23-
potentially an "hour" of compiler time might take only a fraction of a second
24-
in real time.
25-
26-
The *minutes* of the compiler's clocks are measured in phases. At every
27-
compiler run, the compiler cycles through a number of [phases]. The list of
28-
phases is defined in the [Compiler] object There are currently about 60 phases
29-
per run, so the minutes/hours analogy works out roughly. After every phase the
30-
view the compiler has of the world changes: trees are transformed, types are
31-
gradually simplified from Scala types to JVM types, definitions are rearranged,
32-
and so on.
33-
34-
Many pieces in the information compiler are time-dependent. For instance, a
35-
Scala symbol representing a definition has a type, but that type will usually
36-
change as one goes from the higher-level Scala view of things to the
37-
lower-level JVM view. There are different ways to deal with this. Many
38-
compilers change the type of a symbol destructively according to the "current
39-
phase". Another, more functional approach might be to have different symbols
40-
representing the same definition at different phases, which each symbol
41-
carrying a different immutable type. `dotc` employs yet another scheme, which
42-
is inspired by functional reactive programming (FRP): Symbols carry not a
43-
single type, but a function from compiler phase to type. So the type of a
44-
symbol is a time-indexed function, where time ranges over compiler phases.
45-
46-
Typically, the definition of a symbol or other quantity remains stable for a
47-
number of phases. This leads us to the concept of a [period]. Conceptually,
48-
period is an interval of some given phases in a given compiler run. Periods
49-
are conceptually represented by three pieces of information
50-
51-
* the ID of the current run,
52-
* the ID of the phase starting the period
53-
* the number of phases in the period
54-
55-
All three pieces of information are encoded in a value class over a 32 bit
56-
integer. Here's the API for class `Period`:
57-
58-
```scala
59-
class Period(val code: Int) extends AnyVal {
60-
def runId: RunId // The run identifier of this period.
61-
def firstPhaseId: PhaseId // The first phase of this period
62-
def lastPhaseId: PhaseId // The last phase of this period
63-
def phaseId: PhaseId // The phase identifier of this single-phase period
64-
65-
def containsPhaseId(id: PhaseId): Boolean
66-
def contains(that: Period): Boolean
67-
def overlaps(that: Period): Boolean
68-
69-
def & (that: Period): Period
70-
def | (that: Period): Period
71-
}
72-
```
73-
74-
We can access the parts of a period using `runId`, `firstPhaseId`,
75-
`lastPhaseId`, or using `phaseId` for periods consisting only of a single
76-
phase. They return `RunId` or `PhaseId` values, which are aliases of `Int`.
77-
`containsPhaseId`, `contains` and `overlaps` test whether a period contains a
78-
phase or a period as a sub-interval, or whether the interval overlaps with
79-
another period. Finally, `&` and `|` produce the intersection and the union of
80-
two period intervals (the union operation `|` takes as `runId` the `runId` of
81-
its left operand, as periods spanning different `runId`s cannot be constructed.
82-
83-
Periods are constructed using two `apply` methods:
84-
85-
```scala
86-
object Period {
87-
/** The single-phase period consisting of given run id and phase id */
88-
def apply(rid: RunId, pid: PhaseId): Period
89-
90-
/** The period consisting of given run id, and lo/hi phase ids */
91-
def apply(rid: RunId, loPid: PhaseId, hiPid: PhaseId): Period
92-
}
93-
```
94-
95-
As a sentinel value there's `Nowhere`, a period that is empty.
10+
As discussed in the [lifecycle] of `dotc`, the compiler is designed to be interactive,
11+
and so can answer questions about entities as they come into existance and change throughout time,
12+
for example:
13+
- which new definitions were added in a REPL session?
14+
- which definitions were replaced in an incremental build?
15+
- how are definitions simplified as they are adapted to the runtime system?
16+
17+
## Hours, Minutes, and Periods
18+
19+
For the compiler to be able to resolve the above temporal questions, and more, it maintains
20+
a concept of time. Additionally, because interactions are frequent, it is important to
21+
persist knowledge of entities between interactions, allowing the compiler to remain performant;
22+
knowing about time allows the compiler to efficiently mark entities as being outdated.
23+
24+
Conceptually, `dotc` works like a clock, where its minutes are represented by [phases],
25+
and its hours by [runs]. Like a clock, each run passes once each of its phases have completed
26+
sequentially, and then a new run can begin. Phases are further grouped into [periods], where
27+
during a period certain entities of the compiler remain stable.
28+
29+
The [Compiler] class is responsible for creating new runs, and also maintains the list of
30+
phases that make each run.
31+
32+
## Time Dependency
33+
34+
During a run, each phase can transform the world as the compiler sees it, for example:
35+
- to transform trees,
36+
- to gradually simplify type from Scala types to JVM types,
37+
- to move definitions out of inner scopes to outer ones, fitting the JVM's model,
38+
- and so on.
39+
40+
A significant consequence of this is that a definition's associated [Symbol] has a type that
41+
changes over time. Indeed, a cross module reference is always typed as either a `TermRef`
42+
or `TypeRef`, these reference types contain a prefix type and a name, which is resolved dynamically
43+
to a [Denotation]: a time-indexed function from phase to type. The type of a denotation can then
44+
be recovered at any moment by passing the relevant phase in question.
9645

9746
[runs]: https://github.com/lampepfl/dotty/blob/a527f3b1e49c0d48148ccfb2eb52e3302fc4a349/compiler/src/dotty/tools/dotc/Run.scala
98-
[phases]: https://github.com/lampepfl/dotty/blob/a527f3b1e49c0d48148ccfb2eb52e3302fc4a349/compiler/src/dotty/tools/dotc/core/Phases.scala
99-
[period]: https://github.com/lampepfl/dotty/blob/a527f3b1e49c0d48148ccfb2eb52e3302fc4a349/compiler/src/dotty/tools/dotc/core/Periods.scala
47+
[periods]: https://github.com/lampepfl/dotty/blob/a527f3b1e49c0d48148ccfb2eb52e3302fc4a349/compiler/src/dotty/tools/dotc/core/Periods.scala
10048
[Compiler]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/Compiler.scala
49+
[lifecycle]: {% link _overviews/scala3-contribution/arch-lifecycle.md %}#introducing-the-compilers-lifecycle
50+
[phases]: {% link _overviews/scala3-contribution/arch-phases.md %}
51+
[Symbol]: {% link _overviews/scala3-contribution/arch-symbols.md %}
52+
[Denotation]: https://github.com/lampepfl/dotty/blob/master/compiler/src/dotty/tools/dotc/core/Denotations.scala

0 commit comments

Comments
 (0)
0