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
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
//! The frame a call's capabilities travel in, beside the arguments rather than inside them.
//!
//! Design: `spec/safe-memory/05-representation.md` section 5.3.
//!
//! Everything [`mod@crate::slot`] does is about one function. A capability is made where the
//! pointer is made and read where a check needs it, and both ends are instructions in the same
//! body, so a frame slot and its address are the whole of the representation. This is what happens
//! at the one place that stops being true, which is a call.
//!
//! The rule the whole design hangs on is that an instrumented function's calling convention is
//! unchanged. A pointer argument goes in the register it always went in, at the size it always
//! was, and `sizeof(void *)` is still eight, which is what lets an object this compiler built link
//! against one nobody instrumented. So the capability cannot travel in the argument, and section
//! 5.3 puts it in a small frame in thread local storage instead, written by the caller and read by
//! the callee, indexed by argument position.
//!
//! `rucc_safe_rt::frame` is the other half and it was already finished. The magic word, the take
//! that consumes the frame so that nothing further down the chain believes it twice, the outer
//! link that makes frames nest the way calls do, and the clear that a call to an unknown callee
//! needs are all there, with `__rucc_frame_publish`, `__rucc_frame_take`, `__rucc_frame_clear` and
//! `__rucc_frame_restore` as the names generated code is compiled against. What was missing was a
//! way for the IR to say which capability belongs to which argument of which call, and that is
//! [`Opcode::CapPublish`] and [`Opcode::CapClear`] on the writing side and [`Opcode::CapArg`] on the
//! reading one.
//!
//! # The reading end
//!
//! A callee takes the frame once, at the top of the function, and then asks it one question per
//! pointer parameter, and the whole of what makes that pair work is that neither half has to know
//! what kind of caller the function turned out to have.
//!
//! Taking consumes the frame, which is why it happens once and why it happens first. Once, because a
//! second take finds the magic word already cleared and answers null, so a function that took twice
//! would recover half its own arguments for nothing. First, because every call this function makes
//! either publishes a frame of its own or clears, and a take after one of those finds what that call
//! left rather than what this function was given.
//!
//! The question is a call rather than a branch on whether the frame is null, and the deciding is the
//! runtime's. `rucc_safe_rt::recover::argument` is the whole of it: a capability the caller carried
//! is the answer, and the bottom one is the signal to walk the planes. That is the expensive answer,
//! it is counted as the weakening it is, and it is the one that is always available, which is what
//! lets a function compiled this way be called from code that knows nothing about any of it.
//!
//! # Why the empty frame is an instruction rather than an absence
//!
//! A publish with nothing in it and no publish at all are not the same thing, which is the one
//! part of this that reads backwards until the reason is said out loud.
//!
//! Publishing leaves the frame in place until somebody takes it. A callee that was never compiled
//! by this build does not take it, so it stays there for whatever that callee calls back into, and
//! a callback entered from uninstrumented code holding capabilities that belong to a different
//! call is worse than one entered holding none: the first reports on memory it was never about,
//! and the second recovers its arguments and says so in the count. Document 10 section 10.8 is
//! where that case is written down.
//!
//! Not publishing does not fix it either, because what is live at that point is whatever the frame
//! held before, which after an instrumented function has taken its own is its caller's caller's.
//! So a call whose callee cannot be vouched for says there is nothing, out loud, and that is
//! `cap_clear`. An empty publish would say the opposite: it sets the magic, so the callee takes a
//! frame that describes no arguments and then hands the outer one back to the next reader.
//!
//! # What decides which one a call gets
//!
//! Nothing here. [`crate::Frames`] is the classification and it already exists: a callee defined
//! in this unit with no checks left needs no frame, one that still checks something needs the
//! capabilities, and a callee outside the unit or reached through a pointer is one nothing here can
//! ask. That is a whole-unit question and this module is a lowering, so the counting came first and
//! the pass that reads it is a later box on tamnd/rucc#1085.
//!
//! # The one thing this does not handle
//!
//! A callee that leaves by `longjmp` rather than by returning skips the restore, so the frame stays
//! published over a stack that has been unwound past. Nothing in section 5.3 says what to do about
//! that and nothing in the runtime does either, so it is written down here rather than papered
//! over. The same instruction is a gap on the back end's own list for the same kind of reason, and
//! the frame is one more thing that will have to be unwound when it stops being one.
use Interner;
use ;
use crateslot;
/// How many pointer arguments a frame carries capabilities for.
///
/// Eight, which is section 5.3's number and `rucc_safe_rt::frame::ARGS` at the other end. It is
/// more pointer arguments than nearly any function takes, and a call that hands over more than this
/// describes the ones that fit and lets the callee recover the rest, which is a weakening the
/// summary counts rather than a refusal.
pub const ARGS: usize = 8;
/// How many bytes the frame takes.
///
/// The layout is `rucc_safe_rt::frame::Frame`'s and the two have to agree, so the number is written
/// down in both places and tested in both, the same way [`slot::BYTES`] is. It is the 64-bit shape:
/// the trailing link is a pointer and the count below assumes eight bytes of it, which is every
/// target this compiler has.
pub const BYTES: u64 = OUTER + WORD;
/// What the frame is aligned to, which is what its widest field needs.
pub const ALIGN: u32 = 8;
/// The magic word and the two half words beside it, which is what the capabilities start after.
const HEAD: u64 = 8;
/// How wide the trailing link is.
const WORD: u64 = 8;
/// Where the count of described arguments sits.
const ARGC: u64 = 4;
/// Where the spare half word beside the count sits.
const FLAGS: u64 = 6;
/// How wide each of those two is.
const HALF: u64 = 2;
/// Where the frame this one was published over is kept.
///
/// After the capabilities and after the one the callee writes its returned pointer's into, which is
/// why the count below is one more than [`ARGS`]. Nothing reads that last one yet. The arguments are
/// both halves now, and the returned pointer is neither of them, so it is the next box.
const OUTER: u64 = HEAD + BYTES * ;
/// The call a `cap_publish` or a `cap_clear` is about, which is the instruction after it.
///
/// Adjacency is the whole of the tie between the two, the way `meta_release` is tied to the atomic
/// it goes in front of. Anything else would mean a name for a call site in the IR, and a call site
/// already has one, which is where it is.
pub
/// Whether this is a `cap_publish` this module knows how to write.
///
/// Two things beyond the verifier's shape. It has to be in front of a call, since there is nothing
/// else for it to be about, and it has to describe no more arguments than the frame has room for.
/// The second is a refusal rather than a truncation on purpose: dropping the capabilities past the
/// eighth would be a silent weakening, where leaving the function alone is a capability the back end
/// says it cannot lower.
pub
/// `cap_publish` becomes the frame written out and `__rucc_frame_publish(frame)` in front of the
/// call, with the outer frame put back after it.
///
/// The frame is one `alloca` for the whole function rather than one per call site, because no two
/// calls in a body are live at once: the publish, the call and the restore are three instructions
/// in a row, and nothing between them is another call of this function's. Recursion is not an
/// exception, since the inner call is running in its own frame.
///
/// The argument count is written here and the magic word is not. `publish` writes the magic and the
/// outer link itself, which is the thing that makes the frame believable, and that belongs on the
/// side that knows what believable means.
pub
/// `cap_clear` becomes `__rucc_frame_clear()`, in place of itself.
///
/// In place rather than beside, unlike everything [`mod@crate::slot`] rewrites, because this
/// instruction gives nothing back and neither does the call. Nothing points at it and nothing has
/// to be pointed anywhere else.
pub
/// Puts the frame back to the one this call was published over, after the call has returned.
///
/// A load of the link `publish` wrote and a call with what it found, rather than a routine that
/// takes the frame and does both, because the runtime already has the entry point that takes an
/// outer frame and the load is one instruction. The link is right whether or not the callee took
/// the frame: a callee that took it restored to the same value on the way in, so this is a store of
/// what is already there, and a callee that did not is exactly the case this exists for.
/// One of the two half words at the front of the frame, written in front of `inst`.
/// Reserves the frame at the top of the entry block and gives back its address.
///
/// One per function, for the reason [`hand_over`] gives, so this is called once however many calls
/// the body hands capabilities to. At the top for the reason [`slot`]'s own reservation is: that is
/// where the verifier wants an `alloca` that is not a variable length array.
pub
/// Calls `__rucc_frame_take()` at the top of the entry block and gives back what it found.
///
/// Once per function, at the very front, and both halves of that are load bearing rather than tidy.
/// Once, because taking consumes the frame: a second take in the same body finds the magic word
/// already cleared and answers null, so a function that took twice would recover half its arguments
/// for no reason. At the front, because any call this function makes either publishes a frame of its
/// own or clears, and both of those are gone by the time a take after them runs.
///
/// The answer is a pointer that may be null, and null is not an error. It is a call from code this
/// build never compiled, a call whose caller could not vouch for this one, or a call this compiler
/// decided needed no frame, and the runtime treats all three the same way, which is to work the
/// capability out of the planes instead.
pub
/// `cap_arg` becomes `__rucc_frame_arg(slot, frame, position, pointer)`.
///
/// The slot in front, which is where the capability goes, and then the three things the runtime
/// needs to decide what it is: the frame the caller published or null, which of the call's arguments
/// this one was, and the pointer itself. The last of those is the answer when the first is null, and
/// putting the fallback in the runtime rather than in a branch here is what keeps this one call
/// whatever kind of caller the function turns out to have.
///
/// Beside the instruction and not in place of it, the way every other producer here is rewritten,
/// because the call gives nothing back and the instruction did.
pub