summaryrefslogtreecommitdiff
path: root/chapter16/README.md
blob: 1c5985683a01a6ec9b1831a8627caa8d1dfe1265 (plain)
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
# Chapter 16: Constructors and Final fields

# Table of Contents

1. [Constructors](#constructors)
2. [Initialization code](#initialization-code)
3. [Final fields](#final-fields)
4. [Multiple Decls](#multiple-declarations-of-the-same-type)
5. [Memory through constructors](#memory-through-constructors)

In this chapter, we add constructors and final fields.

You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter16) in a linear Git revision history on the [linear](https://github.com/SeaOfNodes/Simple/tree/linear) branch and [compare](https://github.com/SeaOfNodes/Simple/compare/linear-chapter15...linear-chapter16) it to the previous chapter.

## Constructors

A big problem with the [Chapter 13](../chapter13/README.md) refs is that
not-null fields always start out `null`.  This is fixed in this chapter, where
a *constructor* syntax is required to initialize `final` and not-null fields.

Fields are initialized in three ways:

1. A default zero/null initialization for fields that allow it.  No special
   syntax is required.
```
int x;                    // x is initialized to 0
struct Ref { Ref ptr?; }; // Instances of Ref will have ptr initialized to null
return new Ref.ptr;       // Returns a null
```

2. The type declaration can specify an initial value.  Later allocations will
   start with this value. 
```
int x = 5;                // x is initialized to 5
struct Point { int x=1; int y=1; }; // Point x and y will start as 1, not 0
return new Point.x;       // Returns a 1
```

3. The allocation can specify an initial value.
   
```
struct Point { int x=1; int y=1; }; // Point x and y will start as 1, not 0
return new Point { x=3; }.x;        // Returns a 3
```

Not-null and final fields *must* be initialized before first use and before the
end of the allocation.  They can be initialized in either the declaration or
allocation.  They do not start with the default value, although the
initialization can be to the default.

```
struct Person { u8[] name; };
return new Person; // ERROR: 'Person' is not fully initialized, field 'name' needs to be set in a constructor
```


## Initialization code

The parser allows a full Block statement to be parsed in either the type
declaration or the allocation.  Any amount of code is legal, including
`while` loops and `return`s.

```
struct Square {
    flt side = arg;
    flt diag = arg*arg/2;    
    // Newtons approximation to the square root, computed in a constructor.
    // The actual allocation will copy in this result as the initial
    // value for 'diag'.
    while( 1 ) {
        // The next-guess variable "next" is not a field in Square, 
        // because it does not appear at the top level
        flt next = (side/diag + diag)/2;
        if( next == diag ) break;
        diag = next;
    }
};
return new Square;
```

```
struct Buffer {
    if( arg < 0 || arg > 1000000 )
      return null; // Size out of bounds
    u8[] buffer = new u8[arg];
};
return new Buffer;
```


## Final fields

Final fields are declared with a `!` before the name.
This decision is revisited in the next chapter.

```
int !x = 17; // Final field x
x = 3;       // ERROR: cannot re-assign a final field
```

```
struct Person { u8[]? !name; };
return new Person { name = null; }; // OK: final name is assigned
```

```
struct Point { int !x; int !y; int !z; };
return new Point { x=arg; y=z; z=x; }; // ERROR:  'Point' is not fully initialized, field 'y' needs to be set in a constructor
```

```
struct Point { int x; int y; int z; }; // Fields not-final, default to 0
return new Point { x=arg; y=z; z=x; }; // Ok: x=arg, y=0, z=arg
```


### Multiple declarations of the same type

Also new this chapter is allowing multiple declarations with the same type, as
is commonly seen in other languages:

```
int x,y; // Two int variables declared
struct Point { int x,y,z; }; // Three fields declared
```

```
int !x=3,!y=5; // Two int variables declared, both are final and initialized
```
## Memory through constructors

The lazy memory partitioning from [Chapter 10b](../chapter10b/README.md) continues
through constructors. The parser tracks one `$mem` variable alongside its scalar
variables. Branches and loops merge that binding with BulkMemPhi; field access
splits out precise MemPhis when needed. The variable records for types and final
fields live in ScopeNode. There is no separate parser table of memory aliases.

A constructor computes its field values before the allocation. Reads, writes,
and loops in its body update the same `$mem` binding as ordinary code. New then
takes `{ctrl, $mem, size, field values...}` and produces `{ptr, $mem}`. The input
memory is a partial MemMerge containing the struct's aliases; the output memory
covers those same aliases. A whole-memory MemMerge preserves unrelated slices.
Arrays use the same layout, supplying their length and default element value as
initializer inputs.

A Load from its own New can use the matching initializer input. Alias contents
combine the initializer's type with incoming contents from older objects, so a
new object does not erase facts or effects for existing objects. Loads bypass
only allocations proven to be distinct. Scheduling follows partial aggregates
when establishing read-before-write order.

Sharing one allocation memory result makes Store-to-New folding more
conservative: the existing sole-use check now sees users of every covered
alias. This can leave a field Load where separate memory projections previously
allowed a constant fold. Phi factoring also retains the simple one-step Load
safety check.