@@ -39,6 +39,21 @@ wip_zcu: std.ArrayListUnmanaged(ZcuTask),
39
39
/// index into `wip_zcu` which we have reached.
40
40
wip_zcu_idx : usize ,
41
41
42
+ /// The sum of all `air_bytes` for all currently-queued `ZcuTask.link_func` tasks. Because
43
+ /// MIR bytes are approximately proportional to AIR bytes, this acts to limit the amount of
44
+ /// AIR and MIR which is queued for codegen and link respectively, to prevent excessive
45
+ /// memory usage if analysis produces AIR faster than it can be processed by codegen/link.
46
+ /// The cap is `max_air_bytes_in_flight`.
47
+ /// Guarded by `mutex`.
48
+ air_bytes_in_flight : u32 ,
49
+ /// If nonzero, then a call to `enqueueZcu` is blocked waiting to add a `link_func` task, but
50
+ /// cannot until `air_bytes_in_flight` is no greater than this value.
51
+ /// Guarded by `mutex`.
52
+ air_bytes_waiting : u32 ,
53
+ /// After setting `air_bytes_waiting`, `enqueueZcu` will wait on this condition (with `mutex`).
54
+ /// When `air_bytes_waiting` many bytes can be queued, this condition should be signaled.
55
+ air_bytes_cond : std.Thread.Condition ,
56
+
42
57
/// Guarded by `mutex`.
43
58
state : union (enum ) {
44
59
/// The link thread is currently running or queued to run.
@@ -52,6 +67,11 @@ state: union(enum) {
52
67
wait_for_mir : * ZcuTask.LinkFunc.SharedMir ,
53
68
},
54
69
70
+ /// In the worst observed case, MIR is around 50 times as large as AIR. More typically, the ratio is
71
+ /// around 20. Going by that 50x multiplier, and assuming we want to consume no more than 500 MiB of
72
+ /// memory on AIR/MIR, we see a limit of around 10 MiB of AIR in-flight.
73
+ const max_air_bytes_in_flight = 10 * 1024 * 1024 ;
74
+
55
75
/// The initial `Queue` state, containing no tasks, expecting no prelink tasks, and with no running worker thread.
56
76
/// The `pending_prelink_tasks` and `queued_prelink` fields may be modified as needed before calling `start`.
57
77
pub const empty : Queue = .{
@@ -64,6 +84,9 @@ pub const empty: Queue = .{
64
84
.wip_zcu = .empty ,
65
85
.wip_zcu_idx = 0 ,
66
86
.state = .finished ,
87
+ .air_bytes_in_flight = 0 ,
88
+ .air_bytes_waiting = 0 ,
89
+ .air_bytes_cond = .{},
67
90
};
68
91
/// `lf` is needed to correctly deinit any pending `ZcuTask`s.
69
92
pub fn deinit (q : * Queue , comp : * Compilation ) void {
@@ -131,6 +154,16 @@ pub fn enqueueZcu(q: *Queue, comp: *Compilation, task: ZcuTask) Allocator.Error!
131
154
{
132
155
q .mutex .lock ();
133
156
defer q .mutex .unlock ();
157
+ // If this is a `link_func` task, we might need to wait for `air_bytes_in_flight` to fall.
158
+ if (task == .link_func ) {
159
+ const max_in_flight = max_air_bytes_in_flight - | task .link_func .air_bytes ;
160
+ while (q .air_bytes_in_flight > max_in_flight ) {
161
+ q .air_bytes_waiting = task .link_func .air_bytes ;
162
+ q .air_bytes_cond .wait (& q .mutex );
163
+ q .air_bytes_waiting = 0 ;
164
+ }
165
+ q .air_bytes_in_flight += task .link_func .air_bytes ;
166
+ }
134
167
try q .queued_zcu .append (comp .gpa , task );
135
168
switch (q .state ) {
136
169
.running , .wait_for_mir = > return ,
@@ -220,6 +253,17 @@ fn flushTaskQueue(tid: usize, q: *Queue, comp: *Compilation) void {
220
253
return ;
221
254
}
222
255
link .doZcuTask (comp , tid , task );
256
+ if (task == .link_func ) {
257
+ // Decrease `air_bytes_in_flight`, since we've finished processing this MIR.
258
+ q .mutex .lock ();
259
+ defer q .mutex .unlock ();
260
+ q .air_bytes_in_flight -= task .link_func .air_bytes ;
261
+ if (q .air_bytes_waiting != 0 and
262
+ q .air_bytes_in_flight <= max_air_bytes_in_flight - | q .air_bytes_waiting )
263
+ {
264
+ q .air_bytes_cond .signal ();
265
+ }
266
+ }
223
267
task .deinit (comp .zcu .? );
224
268
q .wip_zcu_idx += 1 ;
225
269
}
0 commit comments