1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
|
# Compiler roadmap
English | [日本語](roadmap.ja.md)
Open compiler work and language proposals, reviewed against the current
snapshots on 2026-10-25. These are future changes, not promises about the
language implemented by the tutorial. Language changes still need their own
design decisions and chapter boundaries.
## Correctness
### Narrow field results after method updates
Chapters 25-27 can return an untruncated increment when a method updates a
narrow field and then reads it. With evaluator stores corrected, this example
returns 366 instead of 110. Chapter 25's generated RISC-V code also returns
366: the method's optimized Return uses the raw Add with range `[1-256]`,
although the Store writes a byte. Chapters 23, 24a/24b, and 28a/28b return 110
with the corrected evaluator.
```simple
struct C { u8 x=255; val next_noInline={ -> x++; return x; }; };
var c=new C;
val a=c.next_noInline();
val b=c.next_noInline();
return a+10*b+100*c.x;
```
Find where the field read loses its narrowing during parsing or optimization,
and preserve the converted result independently of the Store. Check both
signed and unsigned fields and compare ideal evaluation with generated code.
## Compilation and optimization
### Reliable incremental builds
Chapter 28b now checks the reachable source-backed inputs before reusing saved
IR, including transitive dependencies, bundled sources, and missing inputs.
The remaining work is a build driver with recorded input identities, beyond
those conservative timestamp checks. An unchanged public signature does not
establish compatibility: optimization imports bodies and constants too.
Record the actual resolved input paths and versions, compiler/serialization
versions, target, ABI, and relevant options. Content hashes can cover preserved
timestamps and changed library search paths. Distinguish rebuildable source
from incompatible source-free libraries; their installation timestamps alone
cannot validate a set of objects compiled against one another.
An unchanged build should do no compilation or linking. Publish outputs only
after successful compilation, and relink when an object or other link input
changes. Start with a small build driver and conservative rebuilding of every
dependent; finer interface/body fingerprints can follow. The
[incremental-build design](../skills/incremental-builds.md) records the input
ownership rules and validation cases.
### Selective loading of library IR
Importing `sys.io.p` currently loads all ten units bundled in `sys.o`.
Independent, unrelated objects can remain unloaded, but selecting an object
reconstructs all its saved IR and follows its recorded dependencies.
Design member-level IR loading and dependency discovery together. Splitting
objects alone is insufficient while reexports and unused function bodies
still pull in their dependencies. Preserve type identity, initialization rules,
and imported escape summaries. Loading, initializing, and emitting a class
are separate decisions. This optimization is independent of making
incremental rebuilding correct.
### Deterministic inlining
The current inliner has a bounded exception for small initializer boilerplate,
but still uses parse-size estimates and worklist order. After shrinking
rewrites converge, choose one growing inline using current body sizes and a
deterministic ranking. Cache sizes per convergence round and use stable
call-site provenance to break ties, rather than newly allocated node IDs.
Avoid repeated whole-body scans and per-node dependencies while a large
function gradually shrinks.
### CFG frequency estimates
After lowering and loop-tree construction, estimate block frequencies for GCM
and code layout. Treat initialization and known-failure paths as rare; use
bounded loop-trip estimates and branch probabilities when no profile is
available. Keep estimates consistent with control flow and shared between
scheduling and layout. These are backend estimates, not ideal-IR types.
### Loop rotation
Consider lowering `for` loops with a zero-trip entry test and a bottom-tested
loop, or adding partial peeling. Preserve condition side effects, loop-local
scope, and the destinations of `break` and `continue`. The current parser
constructs a loop with its test at the head.
### Constant instance-field storage
When a final instance field has the same constant value in every instance,
fold accesses or store the value on the class object and omit the instance
slot. Preserve field-access semantics and public function reachability:
externally accessible function fields remain callable regardless of storage.
Existing static images for class constants do not implement this general
instance-layout optimization.
## Language boundaries
### Source privacy
Underscores already select the public interface, but source access to another
class's private fields is not rejected. The proposal is to enforce privacy
starting in Chapter 28a, then preserve it across imports in 28b. This language
change still needs review; it is not a correction to impose on earlier chapters.
- A field or method beginning with `_` is accessible only from its declaring
class, including constructors and methods. Enclosing, nested, and sibling
classes gain no special access. Same-class code may access another instance.
- A private class name is available in its declaring scope and inside the
class itself. Naming `App._Cache` must not grant access to `_Cache._data`.
Dotted lookup cannot bypass access rules, and private ancestors keep their
descendants out of the public interface. Distinguish naming a private class
from using a public value returned by a factory.
- Reads, writes, updates, method references, type names, and class-value
references must follow the same rule. Preserve source owner and location
through unresolved receiver types, inlining, and imports. Folding away a
Load or an unused result must not erase an illegal source access.
- Native declarations follow the same rules. Public wrappers may deliberately
expose private bindings; `sys` has no special exemption.
The bundled `sys/io.smp` calls `libc._exit` directly. Keep that native binding
private, add a public `exit` wrapper in `sys/libc.smp`, and change IO to call
the wrapper. Bitset and array helpers access their own private fields and
remain legal. String spill fixtures with helpers outside `_String` accessing
`_hashCode` need explicit adaptation; do not grant file-wide access merely to
keep those fixtures unchanged. Validate native execution and imported source
ownership alongside the access diagnostics.
### Constructor helpers and completion
Local SSA field updates, including final fields, repeated assignments, and
loops, are implemented. The remaining question is broader: should constructors
be allowed to pass unfinished `self` to helpers that become safe after
inlining, and should completion checks wait until SCCP?
Decide those rules before changing the current parser-time completion checks
and early-publication restrictions. Separate reading an initialized field of
an unfinished object from exposing that object to arbitrary code. The
construction/escape teaching boundary can then be reviewed against the chosen
semantics.
### ELF visibility and unknown callers
Escape analysis tracks functions reachable by unknown callers, while the ELF
writer exports retained named functions owned by the compilation unit. Decide
which symbols should be public, local, or hidden across separate compilations.
Keep source privacy, ownership for emission, and unknown-caller edges distinct;
keeping a body for linking must not imply arbitrary callers can reach it.
## Initialization and runtime
### Entry arguments
Replace the integer class-initializer argument with an argument array. The
selected entry class receives a non-null array, empty when there are no
arguments; other class initializers receive null. Coordinate the parser,
entry signature, runtime driver, and separately compiled interfaces.
The native runtime already adapts process arguments for an explicit
`simple_main` function. That does not implement argument arrays for class
initializers.
### Pre-initialize classes from ELF data
Prove when a class's entire initialization can be represented by static data
and relocations, without remaining runtime effects. Emit those contents and
start its `$init` flag as true. Carry that fact through separate compilation
so importing compilers can remove checks too.
Constant final fields already have static images; that alone does not prove
the whole class can be pre-initialized. I/O and argument-dependent work still
require execution. Preserve dynamic parent requirements, and do not let cyclic
dependencies justify their own pre-initialization. Static reference cycles
can use relocations without requiring a dynamic initialization cycle.
Mutable fields may have static initial contents without becoming constants.
For a wholly immutable, pre-initialized class, eliminate the mutable `$init`
state as well as the checks so the class image can reside in ROM. Native
function thunks already preserve static initialization of function fields.
### Compact code and data arenas
Use four-byte Simple references into bounded code and data arenas. The arenas
need not overlap or live near zero; choose absolute addresses or offsets from
an arena base when implementing placement and indirect calls. Native function
thunks can occupy the code arena while native libraries live elsewhere.
Currently function pointers occupy four bytes and use low-address thunks;
ordinary data pointers still occupy eight bytes.
Give full-width native pointers separate types and foreign lifetime contracts.
Opaque device addresses may need to pass through without CPU dereference.
Automatic Simple object lifetimes without a GC still need a design for
ownership, borrowing, cycles, and native retention. Compact storage alone
does not establish those rules. Preserve static class images and explicit
native boundary conversions when choosing the representation.
## Data and library proposals
These are language-design alternatives. The sketches below are not executable
examples of the current Simple grammar.
### Structs with trailing arrays
Allow named fields followed by an array length and inline element storage:
```simple
struct vecInt {
u32 size; // Used elements
u32 #; // Capacity
int []; // Trailing elements in the same allocation
};
```
The final field would be named `[]`, with an unsigned length field `#`
immediately before it. Element types could include references to the same
struct. Current arrays have a fixed `u32` length and a special two-field
layout; supporting arbitrary leading fields requires parser and layout work.
Consider `u8`, `u16`, `u32`, or `u64` length fields to reduce small-array
overhead. Arrays with different length representations would be distinct
types, with explicit conversions and capacity checks. The spelling and
alignment rules remain to be decided.
Growing such an object requires a replacement allocation. An extra level of
indirection, as in a growable-buffer wrapper, instead preserves the wrapper's
identity. Specify ownership before treating a growing call as consuming or
freeing its receiver.
### Array initialization
Allow an element-producing function at allocation, for example
`new u8[buffer#, { u32 idx -> buffer[idx] }]`. This would support final arrays
and fully initialized non-null reference arrays. Define when each element is
evaluated and when the array can be published. Existing zero-filled array
allocation does not provide this constructor operation.
### String libraries
Design a family of byte-based strings with UTF-8 decoding supplied by library
functions. Consider these distinct representations and interfaces:
- A growable string with inline bytes and cached metadata, returning a
potentially new receiver when capacity increases.
- An immutable string constructed all at once, with defensive copying where
necessary. Read-only access alone does not establish exclusive ownership.
- A StringBuilder-style wrapper holding a replaceable pointer to its buffer.
- Unicode decoding and iteration over the byte representation, including
behavior at invalid character boundaries.
- A C-compatible string with a trailing zero and directly available length.
- A short string packed with its length into a machine word, passed by value.
Keep mutable storage private by default and specify sharing, concatenation,
capacity growth, and native I/O contracts. Existing `sys` byte-array and
buffer helpers are a starting point, not this complete string design.
### Struct values
Allow a struct to be stored inline in another object, array, or argument list
rather than always through a reference. A proposed `*Complex` or `inline`
spelling would place both coordinates of `struct Complex { f64 x,y; };`
directly in the containing value.
Define whole-value copying, reference-to-value conversion, and assignment
through an existing reference. A value must not silently become a reference
or expose an interior address with an invalid lifetime. Decide how consuming
an enclosing object's lifetime could permit such an address. Keep method
syntax uniform and define the calling convention for by-value arguments.
### Default integer width
Consider making `int` mean `i32` instead of the current `i64`. This changes
overflow behavior, layouts, APIs, and native interfaces, so it needs a language
decision and a deliberate compatibility boundary.
### Parser Cleanup
Get the Parsers' structure closer to an ideal R-D parser, and matching the
grammer doc. Move TYPES to CodeGen, and generally restructure for readability.
Ponder using try-w-resources to do the keep/unkeep pairing (do NOT do any
allocation in the `try`, just bring up an object w/auto-closable which tracks
the unkeeps) - see if that looks any cleaner.
|