Skip to content

Commit 4138bb6

Browse files
authored
add an IL with a dedicated pointer type (#172)
## Summary Add the `LPtr` IL providing a dedicated pointer type and an `Offset` operation for pointer arithmetic. Higher-level ILs as well as `skully` and `source2il` are changed to use the new `Ptr` type. ## Details ### Idea The idea behind a dedicated pointer type is to: 1. allow for better static error detection, once type checking for ILs is implemented 2. allow higher-level passes to defer decision making (i.e., regarding how a pointer is implemented) 3. keep the type for code generators that need it ### Implementation * add the `LPtr` providing the `Ptr` type and `Nil` and `Offset` expression. It extends the `L0` IL and is the new basis for the `L1` IL * integrate the new IL and the new `ptrToInt` pass (`LPtr` -> `L0`) into the main executable and the REPL * update the higher-level ILs to consider `Ptr` and `Nil` * change the stack allocation pass to emit `Offset` instead of integer arithmetic * change the aggregate-related passes, as well as the global-to-pointer pass, to use `Ptr` instead of lowering to `UInt` * update the expected output of the modified existing tests * add tests for the new `ptrToInt` pass The IL uses a symbolic name (rather than a numeric one) so that the existing ILs don't have to be renamed. Usage of `Offset` is disallowed past the `L2` IL for now, so that raw pointer arithmetic is not exposed to higher-level ILs for now. ### Source2IL * add an internal-only `tkPointer` type * replace the `pointerType` constant with using `tkPointer` directly * translate `tkPointer` and procedure pointers to the IL `Ptr` type * update the hand-written `L30` code to not use pointer arithmetic ### Skully * translate MIR pointer-like types to `Ptr` * use `Nil` for representing nil pointers * adjust the cast translation, which needs to handle pointers no longer being arithmetic types
1 parent 625c962 commit 4138bb6

37 files changed

+551
-209
lines changed

languages/lang1.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
## L1 Language
22

33
```grammar
4-
.extends lang0
4+
.extends langPtr
55
```
66

7-
Numeric types can be identified types and identified types can be used in all
8-
places where types are expected.
7+
Numeric and pointer types can be identified types and identified types can be
8+
used in all places where types are expected.
99

1010
```grammar
1111
typedesc += <numtype>
12+
| (Ptr)
1213
type += <type_id>
1314
```
1415

languages/lang3.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,9 @@ expr += (Addr <path>)
5656
| (Copy <path>)
5757
stmt += (Asgn <path> <expr>)
5858
```
59+
60+
Pointer arithmetic is not allowed anymore.
61+
62+
```grammar
63+
expr -= (Offset <expr> <expr> <intVal>)
64+
```

languages/langPtr.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
## LPtr
2+
3+
```grammar
4+
.extends lang0
5+
```
6+
7+
Pointer types are introduced.
8+
9+
```grammar
10+
type += (Ptr)
11+
```
12+
13+
The pointer value representing a pointer that doesn't point anywhere is
14+
represented by the `Nil` expression.
15+
16+
```grammar
17+
expr += (Nil)
18+
```
19+
20+
Pointer arithmetic is done using the `Offset` operation, which applies an
21+
unsigned integer with the same byte-width as a pointer scaled by a positive,
22+
non-zero constant value.
23+
24+
```grammar
25+
expr += (Offset base:<expr> idx:<expr> scale:<intVal>)
26+
```

passes/pass_aggregateParams.nim

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ type
2525
TypeId = uint32
2626

2727
Context = object
28-
addrType: Node
2928
cache: seq[NodeIndex]
3029
## indexed by procedure IDs. Caches the signature type for every
3130
## procedure. This greatly speeds up type computation of calls, as it
@@ -49,6 +48,8 @@ type
4948
params: PackedSet[LocalId]
5049
## the parameters that are turned into pointers
5150

51+
const PtrType = Node(kind: Ptr)
52+
5253
using
5354
c: var Context
5455
tree: PackedTree[NodeKind]
@@ -101,7 +102,7 @@ proc typeof(c; tree; n): Node =
101102
unreachable()
102103

103104
proc newLocal(c; typ: Node): Node =
104-
assert typ.kind in {Type, UInt, Int, Float}
105+
assert typ.kind in {Type, UInt, Int, Float, Ptr}
105106
result = Node(kind: Local, val: uint32(c.firstTemp + c.temps.len))
106107
c.temps.add typ
107108

@@ -168,7 +169,7 @@ proc lowerOperand(c; tree; n; reference: int, bu) =
168169
# code and/or more work for later passes (more locals usually equals more
169170
# work and bookkeeping).
170171
case tree[n].kind
171-
of IntVal, FloatVal, ProcVal:
172+
of IntVal, FloatVal, ProcVal, Nil:
172173
discard "nothing to do"
173174
of Addr:
174175
c.lowerAddr(tree, n, reference, bu)
@@ -220,7 +221,7 @@ proc lowerCallArgs(c; tree; n; start: int, last: BackwardsIndex; bu) =
220221

221222
proc lowerExpr(c; tree; n, bu) =
222223
case tree[n].kind
223-
of IntVal, FloatVal, ProcVal:
224+
of IntVal, FloatVal, ProcVal, Nil:
224225
discard "nothing to do"
225226
of Copy:
226227
let x = tree.child(n, 0)
@@ -362,7 +363,7 @@ proc lowerProc(c; tree; n; sig: NodeIndex, bu) =
362363

363364
if isAggregate(tree, tree[sig, 0]):
364365
c.hasOutParam = true
365-
c.outParam = c.newLocal(c.addrType)
366+
c.outParam = c.newLocal(PtrType)
366367
else:
367368
c.hasOutParam = false
368369

@@ -400,29 +401,29 @@ proc lowerProc(c; tree; n; sig: NodeIndex, bu) =
400401
bu.modifyTree tree, c.locals, list:
401402
# update the parameter locals:
402403
for it in c.params.items:
403-
bu.replace tree.child(c.locals, it.ord), c.addrType
404+
bu.replace tree.child(c.locals, it.ord), PtrType
404405

405406
# append the new locals to the list of locals:
406407
let last = tree.fin(c.locals)
407408
for it in c.temps.items:
408409
bu.insert list, last, it
409410

410-
proc lower*(tree; ptrSize: int): ChangeSet[NodeKind] =
411+
proc lower*(tree): ChangeSet[NodeKind] =
411412
## Computes the changeset representing the lowering for a whole module
412-
## (`tree`). `ptrSize` is the size-in-bytes of a pointer value.
413-
var c = Context(addrType: Node(kind: UInt, val: ptrSize.uint32))
413+
## (`tree`).
414+
var c = Context()
414415

415416
for it in tree.items(tree.child(0)):
416417
if tree[it].kind == ProcTy:
417418
result.modifyTree tree, it, m:
418419
# turn an aggregate return value into an out parameter:
419420
if isAggregate(tree, tree[it, 0]):
420421
result.replace tree.child(it, 0), result.buildTree(tree(Void))
421-
result.insert m, tree.fin(it), c.addrType
422+
result.insert m, tree.fin(it), PtrType
422423

423424
for x in tree.items(it, 1):
424425
if isAggregate(tree, tree[x]):
425-
result.replace x, c.addrType
426+
result.replace x, PtrType
426427

427428
let procs = tree.child(2)
428429

passes/pass_aggregatesToBlob.nim

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ import
55
passes/[changesets, syntax, trees]
66

77
type
8-
Node = TreeNode[NodeKind]
98
Context = object
10-
addrType: Node
9+
ptrSize: int
1110
# per-procedure state:
1211
locals: NodeIndex
1312

@@ -29,11 +28,12 @@ proc resolve(tree; n): NodeIndex =
2928
else:
3029
n
3130

32-
proc size(tree; n: NodeIndex): uint =
31+
proc size(c; tree; n: NodeIndex): uint =
3332
## `n` must be the node index of a type description.
3433
case tree[n].kind
3534
of Record, Union, Array: tree[n, 0].val.uint
3635
of Int, UInt, Float: tree[n].val.uint
36+
of Ptr: c.ptrSize.uint
3737
else: unreachable()
3838

3939
proc alignment(tree; n: NodeIndex): uint =
@@ -42,12 +42,12 @@ proc alignment(tree; n: NodeIndex): uint =
4242
of Record, Union, Array: tree[n, 1].val.uint
4343
else: unreachable()
4444

45-
proc elementOffset(tree; n: NodeIndex, elem: uint32): uint =
45+
proc elementOffset(c; tree; n: NodeIndex, elem: uint32): uint =
4646
## `n` must be the node index of a type description.
4747
case tree[n].kind
4848
of Union: 0'u
4949
of Record: tree[tree.child(n, elem + 2), 0].val
50-
of Array: size(tree, tree.resolve(tree.child(n, 3))) * elem
50+
of Array: size(c, tree, tree.resolve(tree.child(n, 3))) * elem
5151
else: unreachable()
5252

5353
proc typeOfElem(tree; n: NodeIndex, elem: uint32): NodeIndex =
@@ -76,11 +76,11 @@ proc lowerPath(c; tree; n; bu): VirtualTree =
7676
for it in tree.items(n, 2):
7777
if tree[it].kind == Immediate:
7878
# a static field or array access
79-
offset += elementOffset(tree, typ, tree[it].val)
79+
offset += elementOffset(c, tree, typ, tree[it].val)
8080
typ = tree.resolve(typeOfElem(tree, typ, tree[it].val))
8181
elif tree[it].kind == IntVal:
8282
# a static array access
83-
offset += elementOffset(tree, typ, tree.getInt(it).uint32)
83+
offset += elementOffset(c, tree, typ, tree.getInt(it).uint32)
8484
typ = tree.resolve(typeOfElem(tree, typ, 0))
8585
else:
8686
# an array access with a dynamic index
@@ -89,26 +89,27 @@ proc lowerPath(c; tree; n; bu): VirtualTree =
8989
# adding new packed numbers
9090
# add the static offset computed so far:
9191
result = bu.buildTree:
92-
tree(Add, node(c.addrType),
92+
tree(Offset,
9393
embed(result),
94-
node(IntVal, offset.uint32))
94+
node(IntVal, offset.uint32),
95+
node(IntVal, 1))
9596
offset = 0
9697

9798
typ = tree.resolve(typeOfElem(tree, typ, 0))
9899

99100
# apply the dynamic array element offset:
100101
result = bu.buildTree:
101-
tree(Add, node(c.addrType),
102+
tree(Offset,
102103
embed(result),
103-
tree(Mul, node(c.addrType),
104-
embed(c.loweredExpr(tree, it, bu)),
105-
node(IntVal, size(tree, typ).uint32)))
104+
embed(c.loweredExpr(tree, it, bu)),
105+
node(IntVal, size(c, tree, typ).uint32))
106106

107107
if offset > 0:
108108
result = bu.buildTree:
109-
tree(Add, node(c.addrType),
109+
tree(Offset,
110110
embed(result),
111-
node(IntVal, offset.uint32))
111+
node(IntVal, offset.uint32),
112+
node(IntVal, 1))
112113

113114
proc lowerExpr(c; tree; n; bu) =
114115
case tree[n].kind
@@ -153,26 +154,26 @@ proc lowerStmt(c; tree; n; bu) =
153154
# no statement-specific transformation, just lower the expressions within
154155
c.lowerExpr(tree, n, bu)
155156

156-
proc lower*(tree; ptrSize: int): ChangeSet[NodeKind] =
157+
proc lower*(tree; ptrSize: Positive): ChangeSet[NodeKind] =
157158
## Computes the changeset representing the lowering for a whole module
158159
## (`tree`). `ptrSize` is the size-in-bytes of a pointer value.
159160

160161
# turn all aggregate types into blob types:
161162
for it in tree.items(tree.child(0)):
162163
if tree[it].kind in {Record, Union, Array}:
164+
let c = Context(ptrSize: ptrSize)
163165
result.replace it:
164166
result.buildTree:
165167
tree(Blob,
166-
node(Immediate, uint32 size(tree, it)),
168+
node(Immediate, uint32 size(c, tree, it)),
167169
node(Immediate, uint32 alignment(tree, it)))
168170

169171
# lower the procedures:
170172
for it in tree.items(tree.child(2)):
171173
if tree[it].kind == ProcDef:
172174
let
173175
(_, locals, body) = tree.triplet(it)
174-
c = Context(locals: locals,
175-
addrType: Node(kind: UInt, val: uint32 ptrSize))
176+
c = Context(ptrSize: ptrSize, locals: locals)
176177

177178
for blk in tree.items(body):
178179
for s in tree.items(blk, 1):

passes/pass_globalsToPointer.nim

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import
77
passes/[changesets, syntax, trees]
88

99
type
10+
Node = TreeNode[NodeKind]
1011
Context = object
11-
ptrSize: int
12-
globals: Table[uint32, TreeNode[NodeKind]]
12+
ptrSize: uint32
13+
globals: Table[uint32, Node]
1314
## global ID -> global's type. Only keeps a mapping for globals that
1415
## refer to addressable locations
1516

@@ -19,14 +20,16 @@ using
1920
n: NodeIndex
2021
bu: var ChangeSet[NodeKind]
2122

22-
proc sizeAndAlignment(tree; n): tuple[size, align: uint32] =
23+
proc sizeAndAlignment(c: Context; tree; n): tuple[size, align: uint32] =
2324
case tree[n].kind
2425
of Type:
25-
sizeAndAlignment(tree, tree.child(tree.child(0), tree[n].val))
26+
sizeAndAlignment(c, tree, tree.child(tree.child(0), tree[n].val))
2627
of Union, Record, Array:
2728
(tree[n, 0].val, tree[n, 1].val)
2829
of Int, UInt, Float:
2930
(tree[n].val, tree[n].val)
31+
of Ptr:
32+
(c.ptrSize, c.ptrSize)
3033
else:
3134
unreachable()
3235

@@ -81,12 +84,12 @@ proc lowerGlobal(c; tree; n; bu) =
8184
let data = tree.child(n, 1)
8285

8386
assert tree[data].kind == Data
84-
let (s, a) = sizeAndAlignment(tree, tree.child(data, 0))
87+
let (s, a) = sizeAndAlignment(c, tree, tree.child(data, 0))
8588
if tree.len(data) == 1:
8689
# (Data <typ>) -> (Data <align> <size>)
8790
bu.replace(n, bu.buildTree(
8891
tree(GlobalDef,
89-
node(UInt, c.ptrSize.uint32),
92+
node(Node(kind: Ptr)),
9093
tree(Data,
9194
# TODO: use packed values once supported, so that the full integer
9295
# range works
@@ -96,15 +99,15 @@ proc lowerGlobal(c; tree; n; bu) =
9699
# (Data <typ> <content>) -> (Data <align> <content>)
97100
bu.replace(n, bu.buildTree(
98101
tree(GlobalDef,
99-
node(UInt, c.ptrSize.uint32),
102+
node(Node(kind: Ptr)),
100103
tree(Data,
101104
node(Immediate, a),
102105
node(tree[data, 1]))))) # the string value stays
103106

104-
proc lower*(tree; ptrSize: int): ChangeSet[NodeKind] =
107+
proc lower*(tree; ptrSize: Positive): ChangeSet[NodeKind] =
105108
## Computes the changeset representing the lowering for a whole module
106109
## (`tree`). `ptrSize` is the size-in-bytes of a pointer.
107-
var c = Context(ptrSize: ptrSize)
110+
var c = Context(ptrSize: ptrSize.uint32)
108111
let (_, globals, procs) = tree.triplet(NodeIndex(0))
109112

110113
for i, it in tree.pairs(globals):

passes/pass_legalizeBlobOps.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ proc isBlob(tree; n): bool =
1919
case tree[n].kind
2020
of Blob: true
2121
of Type: tree[tree.child(0), tree[n].val].kind == Blob
22-
of Int, UInt, Float: false
22+
of Int, UInt, Float, Ptr: false
2323
else: unreachable()
2424

2525
proc prepareOperand(tree; n; bu): VirtualTree =

passes/pass_localsToBlob.nim

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ type
99
LocalId = distinct uint32
1010

1111
Context = object
12+
ptrSize: int
13+
# per-procedure context:
1214
locals: NodeIndex
1315
marker: PackedSet[LocalId]
1416

@@ -32,9 +34,18 @@ proc resolve(tree; n: NodeIndex): NodeIndex =
3234
proc typeof(c; tree; id: LocalId): NodeIndex =
3335
tree.child(c.locals, ord id)
3436

37+
proc sizeAndAlignment(c; tree; n): (uint32, uint32) =
38+
case tree[n].kind
39+
of Int, UInt, Float:
40+
(tree[n].val, tree[n].val)
41+
of Ptr:
42+
(c.ptrSize.uint32, c.ptrSize.uint32)
43+
else:
44+
unreachable()
45+
3546
proc isSimple(tree; n): bool =
3647
case tree[n].kind
37-
of Int, UInt, Float: true
48+
of Int, UInt, Float, Ptr: true
3849
of Blob: false
3950
of Type: isSimple(tree, tree.child(tree.child(0), tree[n].val))
4051
else: unreachable()
@@ -111,18 +122,18 @@ proc lowerProc(c; tree; n; bu) =
111122
for it in c.marker.items:
112123
let
113124
slot = tree.child(c.locals, ord it)
114-
size = tree[tree.resolve(slot)].val
125+
(size, align) = sizeAndAlignment(c, tree, tree.resolve(slot))
115126
bu.replace slot:
116-
bu.buildTree tree(Blob, node(Immediate, size), node(Immediate, size))
127+
bu.buildTree tree(Blob, node(Immediate, size), node(Immediate, align))
117128

118129
for it in newLocals.items:
119130
bu.insert m, tree.fin(c.locals), it
120131

121-
proc lower*(tree): ChangeSet[NodeKind] =
132+
proc lower*(tree; ptrSize: Positive): ChangeSet[NodeKind] =
122133
## Computes the changeset representing the lowering for a whole module
123-
## (`tree`). `ptrSize` is the size-in-bytes of a pointer value.
134+
## (`tree`).
124135

125-
var c = Context()
136+
var c = Context(ptrSize: ptrSize)
126137
# lower the procedures:
127138
for it in tree.items(tree.child(2)):
128139
if tree[it].kind == ProcDef:

0 commit comments

Comments
 (0)