@@ -222,13 +222,44 @@ julia> consume(t)
222
222
223
223
# Implementation Notes
224
224
225
- Under the hood, a `TapedTask` is implemented in terms of a `MistyClosure.` Each SSA value in
226
- the original IR is replaced with a `Base.RefValue` of the same type. Each statement writes
227
- to / reads from these SSAs. These refs are stored in the data that the `MistyClosure` closes
228
- over, meaning that they persist between calls to `consume`. These refs are copied when a
229
- copy of a `TapedTask` is made -- since they define the entire state of a task, making a
230
- completely independent copy of them ensures that a completely independent copy of the task
231
- is made.
225
+ Under the hood, we implement a `TapedTask` by obtaining the `IRCode` associated to the
226
+ original function, transforming it so that it implements the semantics required by the
227
+ `produce` / `consume` interface, and placing it inside a `MistyClosure` to make it possible
228
+ to execute.
229
+
230
+ There are two main considerations when transforming the `IRCode`. The first is to ensure
231
+ that the "state" of a `TapedTask` can be copied, so that a `TapedTask` can be copied, and
232
+ resumed later. The complete state of a `TapedTask` is given by its arguments, and the value
233
+ associated to each ssa (these are initially undefined).
234
+ To make it possible to copy the state of the ssa values, we place a `Base.RefValue{T}`s into
235
+ the captures of the `MistyClosure` which implements the `TapedTask`, one for each ssa in the
236
+ IR (`T` is the type inferred for that ssa). A call is replaced by reading in values of ssas
237
+ from these refs, applying the original operation, and writing the result to the ref
238
+ associated to the instruction. For example, if the original snippet of `IRCode` is something
239
+ like
240
+ ```julia
241
+ %5 = f(%3, _1)
242
+ ```
243
+ the transformed IR would be something like
244
+ ```julia
245
+ %5 = ref_for_%3[]
246
+ %6 = f(%5, _1)
247
+ ref_for_%5[] = %6
248
+ ```
249
+ Setting things up in this manner ensures that an independent copy is made by simply copying
250
+ all of the refs. A `deepcopy` is required for correctness as, while the refs to not alias
251
+ one another (by construction), their contents might. For example, two refs may contain the
252
+ same `Array`, and in general the behaviour of a function depends on this relationship.
253
+
254
+ The second component of the transformation is implementing the `produce` mechanism, and the
255
+ ability to resume computation from where we produced. Roughly speaking, the `IRCode` must be
256
+ modified to ensure that whenever a `produce` call in encountered, the `MistyClosure` returns
257
+ the argument to `produce`, and that subsequent calls resume computation immediately after
258
+ the `produce` statement. Observe that this is also facilitated by the ref mechanism
259
+ discussed above, as it ensures that the state persists between calls to a `MistyClosure`.
260
+
261
+ The above gives the broad outline of how `TapedTask`s are implemented. We refer interested
262
+ readers to the code, which is extensively commented to explain implementation details.
232
263
"""
233
264
function TapedTask (taped_globals:: Any , fargs... ; kwargs... )
234
265
all_args = isempty (kwargs) ? fargs : (Core. kwcall, getfield (kwargs, :data ), fargs... )
0 commit comments