| author | |
| committer | |
| log | 3c3abaf3907e344305620fb4565e7c1acb0a9c88 |
| tree | d418d08645bf500bb30e3b3b388dbfa4a21e4aec |
| parent | 5d6f7b44c19b064a543b0c1eecb6ef5c671b612e |
It's pretty compact, with each AIR instruction only taking up 4 bits,
plus a sparse table for special instructions such as conditional branch,
switch branch, and function calls with more than 2 arguments.5 files changed, 541 insertions(+), 378 deletions(-)
BRANCH_TODO-73| ... | ... | @@ -57,79 +57,6 @@ |
| 57 | 57 | unreachable; |
| 58 | 58 | } |
| 59 | 59 | |
| 60 | pub fn Type(tag: Tag) type { | |
| 61 | return switch (tag) { | |
| 62 | .alloc, | |
| 63 | .retvoid, | |
| 64 | .unreach, | |
| 65 | .breakpoint, | |
| 66 | => NoOp, | |
| 67 | ||
| 68 | .ref, | |
| 69 | .ret, | |
| 70 | .bitcast, | |
| 71 | .not, | |
| 72 | .is_non_null, | |
| 73 | .is_non_null_ptr, | |
| 74 | .is_null, | |
| 75 | .is_null_ptr, | |
| 76 | .is_err, | |
| 77 | .is_non_err, | |
| 78 | .is_err_ptr, | |
| 79 | .is_non_err_ptr, | |
| 80 | .ptrtoint, | |
| 81 | .floatcast, | |
| 82 | .intcast, | |
| 83 | .load, | |
| 84 | .optional_payload, | |
| 85 | .optional_payload_ptr, | |
| 86 | .wrap_optional, | |
| 87 | .unwrap_errunion_payload, | |
| 88 | .unwrap_errunion_err, | |
| 89 | .unwrap_errunion_payload_ptr, | |
| 90 | .unwrap_errunion_err_ptr, | |
| 91 | .wrap_errunion_payload, | |
| 92 | .wrap_errunion_err, | |
| 93 | => UnOp, | |
| 94 | ||
| 95 | .add, | |
| 96 | .addwrap, | |
| 97 | .sub, | |
| 98 | .subwrap, | |
| 99 | .mul, | |
| 100 | .mulwrap, | |
| 101 | .div, | |
| 102 | .cmp_lt, | |
| 103 | .cmp_lte, | |
| 104 | .cmp_eq, | |
| 105 | .cmp_gte, | |
| 106 | .cmp_gt, | |
| 107 | .cmp_neq, | |
| 108 | .store, | |
| 109 | .bool_and, | |
| 110 | .bool_or, | |
| 111 | .bit_and, | |
| 112 | .bit_or, | |
| 113 | .xor, | |
| 114 | => BinOp, | |
| 115 | ||
| 116 | .arg => Arg, | |
| 117 | .assembly => Assembly, | |
| 118 | .block => Block, | |
| 119 | .br => Br, | |
| 120 | .br_block_flat => BrBlockFlat, | |
| 121 | .br_void => BrVoid, | |
| 122 | .call => Call, | |
| 123 | .condbr => CondBr, | |
| 124 | .constant => Constant, | |
| 125 | .loop => Loop, | |
| 126 | .varptr => VarPtr, | |
| 127 | .struct_field_ptr => StructFieldPtr, | |
| 128 | .switchbr => SwitchBr, | |
| 129 | .dbg_stmt => DbgStmt, | |
| 130 | }; | |
| 131 | } | |
| 132 | ||
| 133 | 60 | pub fn Args(comptime T: type) type { |
| 134 | 61 | return std.meta.fieldInfo(T, .args).field_type; |
| 135 | 62 | } |
src/Air.zig+79-43| ... | ... | @@ -10,10 +10,18 @@ const Air = @This(); |
| 10 | 10 | |
| 11 | 11 | instructions: std.MultiArrayList(Inst).Slice, |
| 12 | 12 | /// The meaning of this data is determined by `Inst.Tag` value. |
| 13 | /// The first few indexes are reserved. See `ExtraIndex` for the values. | |
| 13 | 14 | extra: []u32, |
| 14 | 15 | values: []Value, |
| 15 | 16 | variables: []*Module.Var, |
| 16 | 17 | |
| 18 | pub const ExtraIndex = enum(u32) { | |
| 19 | /// Payload index of the main `Block` in the `extra` array. | |
| 20 | main_block, | |
| 21 | ||
| 22 | _, | |
| 23 | }; | |
| 24 | ||
| 17 | 25 | pub const Inst = struct { |
| 18 | 26 | tag: Tag, |
| 19 | 27 | data: Data, |
| ... | ... | @@ -231,11 +239,25 @@ pub const Inst = struct { |
| 231 | 239 | .neq => .cmp_neq, |
| 232 | 240 | }; |
| 233 | 241 | } |
| 242 | ||
| 243 | pub fn toCmpOp(tag: Tag) ?std.math.CompareOperator { | |
| 244 | return switch (tag) { | |
| 245 | .cmp_lt => .lt, | |
| 246 | .cmp_lte => .lte, | |
| 247 | .cmp_eq => .eq, | |
| 248 | .cmp_gte => .gte, | |
| 249 | .cmp_gt => .gt, | |
| 250 | .cmp_neq => .neq, | |
| 251 | else => null, | |
| 252 | }; | |
| 253 | } | |
| 234 | 254 | }; |
| 235 | 255 | |
| 236 | 256 | /// The position of an AIR instruction within the `Air` instructions array. |
| 237 | 257 | pub const Index = u32; |
| 238 | 258 | |
| 259 | pub const Ref = @import("Zir.zig").Inst.Ref; | |
| 260 | ||
| 239 | 261 | /// All instructions have an 8-byte payload, which is contained within |
| 240 | 262 | /// this union. `Tag` determines which union field is active, as well as |
| 241 | 263 | /// how to interpret the data within. |
| ... | ... | @@ -281,55 +303,69 @@ pub const Inst = struct { |
| 281 | 303 | } |
| 282 | 304 | } |
| 283 | 305 | }; |
| 306 | }; | |
| 284 | 307 | |
| 285 | pub fn cmpOperator(base: *Inst) ?std.math.CompareOperator { | |
| 286 | return switch (base.tag) { | |
| 287 | .cmp_lt => .lt, | |
| 288 | .cmp_lte => .lte, | |
| 289 | .cmp_eq => .eq, | |
| 290 | .cmp_gte => .gte, | |
| 291 | .cmp_gt => .gt, | |
| 292 | .cmp_neq => .neq, | |
| 293 | else => null, | |
| 294 | }; | |
| 295 | } | |
| 308 | /// Trailing is a list of instruction indexes for every `body_len`. | |
| 309 | pub const Block = struct { | |
| 310 | body_len: u32, | |
| 311 | }; | |
| 296 | 312 | |
| 297 | /// Trailing is a list of instruction indexes for every `body_len`. | |
| 298 | pub const Block = struct { | |
| 299 | body_len: u32, | |
| 300 | }; | |
| 313 | /// Trailing is a list of `Ref` for every `args_len`. | |
| 314 | pub const Call = struct { | |
| 315 | args_len: u32, | |
| 316 | }; | |
| 301 | 317 | |
| 302 | /// Trailing is a list of `Ref` for every `args_len`. | |
| 303 | pub const Call = struct { | |
| 304 | args_len: u32, | |
| 305 | }; | |
| 318 | /// This data is stored inside extra, with two sets of trailing `Ref`: | |
| 319 | /// * 0. the then body, according to `then_body_len`. | |
| 320 | /// * 1. the else body, according to `else_body_len`. | |
| 321 | pub const CondBr = struct { | |
| 322 | then_body_len: u32, | |
| 323 | else_body_len: u32, | |
| 324 | }; | |
| 306 | 325 | |
| 307 | /// This data is stored inside extra, with two sets of trailing `Ref`: | |
| 308 | /// * 0. the then body, according to `then_body_len`. | |
| 309 | /// * 1. the else body, according to `else_body_len`. | |
| 310 | pub const CondBr = struct { | |
| 311 | condition: Ref, | |
| 312 | then_body_len: u32, | |
| 313 | else_body_len: u32, | |
| 314 | }; | |
| 326 | /// Trailing: | |
| 327 | /// * 0. `Case` for each `cases_len` | |
| 328 | /// * 1. the else body, according to `else_body_len`. | |
| 329 | pub const SwitchBr = struct { | |
| 330 | cases_len: u32, | |
| 331 | else_body_len: u32, | |
| 315 | 332 | |
| 316 | 333 | /// Trailing: |
| 317 | /// * 0. `Case` for each `cases_len` | |
| 318 | /// * 1. the else body, according to `else_body_len`. | |
| 319 | pub const SwitchBr = struct { | |
| 320 | cases_len: u32, | |
| 321 | else_body_len: u32, | |
| 322 | ||
| 323 | /// Trailing: | |
| 324 | /// * instruction index for each `body_len`. | |
| 325 | pub const Case = struct { | |
| 326 | item: Ref, | |
| 327 | body_len: u32, | |
| 328 | }; | |
| 334 | /// * instruction index for each `body_len`. | |
| 335 | pub const Case = struct { | |
| 336 | item: Ref, | |
| 337 | body_len: u32, | |
| 329 | 338 | }; |
| 339 | }; | |
| 330 | 340 | |
| 331 | pub const StructField = struct { | |
| 332 | struct_ptr: Ref, | |
| 333 | field_index: u32, | |
| 334 | }; | |
| 341 | pub const StructField = struct { | |
| 342 | struct_ptr: Ref, | |
| 343 | field_index: u32, | |
| 335 | 344 | }; |
| 345 | ||
| 346 | pub fn getMainBody(air: Air) []const Air.Inst.Index { | |
| 347 | const body_index = air.extra[@enumToInt(ExtraIndex.main_block)]; | |
| 348 | const body_len = air.extra[body_index]; | |
| 349 | return air.extra[body_index..][0..body_len]; | |
| 350 | } | |
| 351 | ||
| 352 | /// Returns the requested data, as well as the new index which is at the start of the | |
| 353 | /// trailers for the object. | |
| 354 | pub fn extraData(air: Air, comptime T: type, index: usize) struct { data: T, end: usize } { | |
| 355 | const fields = std.meta.fields(T); | |
| 356 | var i: usize = index; | |
| 357 | var result: T = undefined; | |
| 358 | inline for (fields) |field| { | |
| 359 | @field(result, field.name) = switch (field.field_type) { | |
| 360 | u32 => air.extra[i], | |
| 361 | Inst.Ref => @intToEnum(Inst.Ref, air.extra[i]), | |
| 362 | i32 => @bitCast(i32, air.extra[i]), | |
| 363 | else => @compileError("bad field type"), | |
| 364 | }; | |
| 365 | i += 1; | |
| 366 | } | |
| 367 | return .{ | |
| 368 | .data = result, | |
| 369 | .end = i, | |
| 370 | }; | |
| 371 | } |
src/Liveness.zig created+457| ... | ... | @@ -0,0 +1,457 @@ |
| 1 | //! For each AIR instruction, we want to know: | |
| 2 | //! * Is the instruction unreferenced (e.g. dies immediately)? | |
| 3 | //! * For each of its operands, does the operand die with this instruction (e.g. is | |
| 4 | //! this the last reference to it)? | |
| 5 | //! Some instructions are special, such as: | |
| 6 | //! * Conditional Branches | |
| 7 | //! * Switch Branches | |
| 8 | const Liveness = @This(); | |
| 9 | const std = @import("std"); | |
| 10 | const Air = @import("Air.zig"); | |
| 11 | const trace = @import("tracy.zig").trace; | |
| 12 | const log = std.log.scoped(.liveness); | |
| 13 | const assert = std.debug.assert; | |
| 14 | const Allocator = std.mem.Allocator; | |
| 15 | ||
| 16 | /// This array is split into sets of 4 bits per AIR instruction. | |
| 17 | /// The MSB (0bX000) is whether the instruction is unreferenced. | |
| 18 | /// The LSB (0b000X) is the first operand, and so on, up to 3 operands. A set bit means the | |
| 19 | /// operand dies after this instruction. | |
| 20 | /// Instructions which need more data to track liveness have special handling via the | |
| 21 | /// `special` table. | |
| 22 | tomb_bits: []const usize, | |
| 23 | /// Sparse table of specially handled instructions. The value is an index into the `extra` | |
| 24 | /// array. The meaning of the data depends on the AIR tag. | |
| 25 | special: std.AutoHashMapUnmanaged(Air.Inst.Index, u32), | |
| 26 | /// Auxilliary data. The way this data is interpreted is determined contextually. | |
| 27 | extra: []const u32, | |
| 28 | ||
| 29 | /// Trailing is the set of instructions whose lifetimes end at the start of the then branch, | |
| 30 | /// followed by the set of instructions whose lifetimes end at the start of the else branch. | |
| 31 | pub const CondBr = struct { | |
| 32 | then_death_count: u32, | |
| 33 | else_death_count: u32, | |
| 34 | }; | |
| 35 | ||
| 36 | /// Trailing is: | |
| 37 | /// * For each case in the same order as in the AIR: | |
| 38 | /// - case_death_count: u32 | |
| 39 | /// - Air.Inst.Index for each `case_death_count`: set of instructions whose lifetimes | |
| 40 | /// end at the start of this case. | |
| 41 | /// * Air.Inst.Index for each `else_death_count`: set of instructions whose lifetimes | |
| 42 | /// end at the start of the else case. | |
| 43 | pub const SwitchBr = struct { | |
| 44 | else_death_count: u32, | |
| 45 | }; | |
| 46 | ||
| 47 | pub fn analyze(gpa: *Allocator, air: Air) Allocator.Error!Liveness { | |
| 48 | const tracy = trace(@src()); | |
| 49 | defer tracy.end(); | |
| 50 | ||
| 51 | var a: Analysis = .{ | |
| 52 | .gpa = gpa, | |
| 53 | .air = &air, | |
| 54 | .table = .{}, | |
| 55 | .tomb_bits = try gpa.alloc( | |
| 56 | usize, | |
| 57 | (air.instructions.len * bpi + @bitSizeOf(usize) - 1) / @bitSizeOf(usize), | |
| 58 | ), | |
| 59 | .extra = .{}, | |
| 60 | .special = .{}, | |
| 61 | }; | |
| 62 | errdefer gpa.free(a.tomb_bits); | |
| 63 | errdefer a.special.deinit(gpa); | |
| 64 | defer a.extra.deinit(gpa); | |
| 65 | defer a.table.deinit(gpa); | |
| 66 | ||
| 67 | const main_body = air.getMainBody(); | |
| 68 | try a.table.ensureTotalCapacity(main_body.len); | |
| 69 | try analyzeWithContext(&a, null, main_body); | |
| 70 | return Liveness{ | |
| 71 | .tomb_bits = a.tomb_bits, | |
| 72 | .special = a.special, | |
| 73 | .extra = a.extra.toOwnedSlice(gpa), | |
| 74 | }; | |
| 75 | } | |
| 76 | ||
| 77 | pub fn deinit(l: *Liveness, gpa: *Allocator) void { | |
| 78 | gpa.free(l.tomb_bits); | |
| 79 | gpa.free(l.extra); | |
| 80 | l.special.deinit(gpa); | |
| 81 | } | |
| 82 | ||
| 83 | /// How many tomb bits per AIR instruction. | |
| 84 | const bpi = 4; | |
| 85 | const Bpi = std.meta.Int(.unsigned, bpi); | |
| 86 | ||
| 87 | /// In-progress data; on successful analysis converted into `Liveness`. | |
| 88 | const Analysis = struct { | |
| 89 | gpa: *Allocator, | |
| 90 | air: *const Air, | |
| 91 | table: std.AutoHashMapUnmanaged(Air.Inst.Index, void), | |
| 92 | tomb_bits: []usize, | |
| 93 | extra: std.ArrayListUnmanaged(u32), | |
| 94 | ||
| 95 | fn storeTombBits(a: *Analysis, inst: Air.Inst.Index, tomb_bits: Bpi) void { | |
| 96 | const usize_index = (inst * bpi) / @bitSizeOf(usize); | |
| 97 | a.tomb_bits[usize_index] |= tomb_bits << (inst % (@bitSizeOf(usize) / bpi)) * bpi; | |
| 98 | } | |
| 99 | ||
| 100 | fn addExtra(a: *Analysis, extra: anytype) Allocator.Error!u32 { | |
| 101 | const fields = std.meta.fields(@TypeOf(extra)); | |
| 102 | try a.extra.ensureUnusedCapacity(a.gpa, fields.len); | |
| 103 | return addExtraAssumeCapacity(a, extra); | |
| 104 | } | |
| 105 | ||
| 106 | fn addExtraAssumeCapacity(a: *Analysis, extra: anytype) u32 { | |
| 107 | const fields = std.meta.fields(@TypeOf(extra)); | |
| 108 | const result = @intCast(u32, a.extra.items.len); | |
| 109 | inline for (fields) |field| { | |
| 110 | a.extra.appendAssumeCapacity(switch (field.field_type) { | |
| 111 | u32 => @field(extra, field.name), | |
| 112 | else => @compileError("bad field type"), | |
| 113 | }); | |
| 114 | } | |
| 115 | return result; | |
| 116 | } | |
| 117 | }; | |
| 118 | ||
| 119 | fn analyzeWithContext( | |
| 120 | a: *Analysis, | |
| 121 | new_set: ?*std.AutoHashMapUnmanaged(Air.Inst.Index, void), | |
| 122 | body: []const Air.Inst.Index, | |
| 123 | ) Allocator.Error!void { | |
| 124 | var i: usize = body.len; | |
| 125 | ||
| 126 | if (new_set) |ns| { | |
| 127 | // We are only interested in doing this for instructions which are born | |
| 128 | // before a conditional branch, so after obtaining the new set for | |
| 129 | // each branch we prune the instructions which were born within. | |
| 130 | while (i != 0) { | |
| 131 | i -= 1; | |
| 132 | const inst = body[i]; | |
| 133 | _ = ns.remove(inst); | |
| 134 | try analyzeInst(a, new_set, inst); | |
| 135 | } | |
| 136 | } else { | |
| 137 | while (i != 0) { | |
| 138 | i -= 1; | |
| 139 | const inst = body[i]; | |
| 140 | try analyzeInst(a, new_set, inst); | |
| 141 | } | |
| 142 | } | |
| 143 | } | |
| 144 | ||
| 145 | fn analyzeInst( | |
| 146 | a: *Analysis, | |
| 147 | new_set: ?*std.AutoHashMap(Air.Inst.Index, void), | |
| 148 | inst: Air.Inst.Index, | |
| 149 | ) Allocator.Error!void { | |
| 150 | const gpa = a.gpa; | |
| 151 | const table = &a.table; | |
| 152 | const inst_tags = a.air.instructions.items(.tag); | |
| 153 | ||
| 154 | // No tombstone for this instruction means it is never referenced, | |
| 155 | // and its birth marks its own death. Very metal 🤘 | |
| 156 | const main_tomb = !table.contains(inst); | |
| 157 | ||
| 158 | switch (inst_tags[inst]) { | |
| 159 | .add, | |
| 160 | .addwrap, | |
| 161 | .sub, | |
| 162 | .subwrap, | |
| 163 | .mul, | |
| 164 | .mulwrap, | |
| 165 | .div, | |
| 166 | .bit_and, | |
| 167 | .bit_or, | |
| 168 | .xor, | |
| 169 | .cmp_lt, | |
| 170 | .cmp_lte, | |
| 171 | .cmp_eq, | |
| 172 | .cmp_gte, | |
| 173 | .cmp_gt, | |
| 174 | .cmp_neq, | |
| 175 | .bool_and, | |
| 176 | .bool_or, | |
| 177 | .store, | |
| 178 | => { | |
| 179 | const o = inst_datas[inst].bin_op; | |
| 180 | return trackOperands(a, new_set, inst, main_tomb, .{ o.lhs, o.rhs, .none }); | |
| 181 | }, | |
| 182 | ||
| 183 | .alloc, | |
| 184 | .br, | |
| 185 | .constant, | |
| 186 | .breakpoint, | |
| 187 | .dbg_stmt, | |
| 188 | .varptr, | |
| 189 | .unreach, | |
| 190 | => return trackOperands(a, new_set, inst, main_tomb, .{ .none, .none, .none }), | |
| 191 | ||
| 192 | .not, | |
| 193 | .bitcast, | |
| 194 | .load, | |
| 195 | .ref, | |
| 196 | .floatcast, | |
| 197 | .intcast, | |
| 198 | .optional_payload, | |
| 199 | .optional_payload_ptr, | |
| 200 | .wrap_optional, | |
| 201 | .unwrap_errunion_payload, | |
| 202 | .unwrap_errunion_err, | |
| 203 | .unwrap_errunion_payload_ptr, | |
| 204 | .unwrap_errunion_err_ptr, | |
| 205 | .wrap_errunion_payload, | |
| 206 | .wrap_errunion_err, | |
| 207 | => { | |
| 208 | const o = inst_datas[inst].ty_op; | |
| 209 | return trackOperands(a, new_set, inst, main_tomb, .{ o.operand, .none, .none }); | |
| 210 | }, | |
| 211 | ||
| 212 | .is_null, | |
| 213 | .is_non_null, | |
| 214 | .is_null_ptr, | |
| 215 | .is_non_null_ptr, | |
| 216 | .is_err, | |
| 217 | .is_non_err, | |
| 218 | .is_err_ptr, | |
| 219 | .is_non_err_ptr, | |
| 220 | .ptrtoint, | |
| 221 | .ret, | |
| 222 | => { | |
| 223 | const operand = inst_datas[inst].un_op; | |
| 224 | return trackOperands(a, new_set, inst, main_tomb, .{ operand, .none, .none }); | |
| 225 | }, | |
| 226 | ||
| 227 | .call => { | |
| 228 | const inst_data = inst_datas[inst].pl_op; | |
| 229 | const callee = inst_data.operand; | |
| 230 | const extra = a.air.extraData(Air.Call, inst_data.payload); | |
| 231 | const args = a.air.extra[extra.end..][0..extra.data.args_len]; | |
| 232 | if (args.len <= bpi - 2) { | |
| 233 | var buf: [bpi - 1]Air.Inst.Ref = undefined; | |
| 234 | buf[0] = callee; | |
| 235 | std.mem.copy(&buf, buf[1..], args); | |
| 236 | return trackOperands(a, new_set, inst, main_tomb, buf); | |
| 237 | } | |
| 238 | @panic("TODO: liveness analysis for function with many args"); | |
| 239 | }, | |
| 240 | .struct_field_ptr => { | |
| 241 | const extra = a.air.extraData(Air.StructField, inst_datas[inst].ty_pl.payload).data; | |
| 242 | return trackOperands(a, new_set, inst, main_tomb, .{ extra.struct_ptr, .none, .none }); | |
| 243 | }, | |
| 244 | .block => { | |
| 245 | const extra = a.air.extraData(Air.Block, inst_datas[inst].ty_pl.payload); | |
| 246 | const body = a.air.extra[extra.end..][0..extra.data.body_len]; | |
| 247 | try analyzeWithContext(a, new_set, body); | |
| 248 | // We let this continue so that it can possibly mark the block as | |
| 249 | // unreferenced below. | |
| 250 | return trackOperands(a, new_set, inst, main_tomb, .{ .none, .none, .none }); | |
| 251 | }, | |
| 252 | .loop => { | |
| 253 | const extra = a.air.extraData(Air.Block, inst_datas[inst].ty_pl.payload); | |
| 254 | const body = a.air.extra[extra.end..][0..extra.data.body_len]; | |
| 255 | try analyzeWithContext(a, new_set, body); | |
| 256 | return; // Loop has no operands and it is always unreferenced. | |
| 257 | }, | |
| 258 | .cond_br => { | |
| 259 | // Each death that occurs inside one branch, but not the other, needs | |
| 260 | // to be added as a death immediately upon entering the other branch. | |
| 261 | const inst_data = inst_datas[inst].pl_op; | |
| 262 | const condition = inst_data.operand; | |
| 263 | const extra = a.air.extraData(Air.CondBr, inst_data.payload); | |
| 264 | const then_body = a.air.extra[extra.end..][0..extra.data.then_body_len]; | |
| 265 | const else_body = a.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]; | |
| 266 | ||
| 267 | var then_table = std.AutoHashMap(Air.Inst.Index, void).init(gpa); | |
| 268 | defer then_table.deinit(); | |
| 269 | try analyzeWithContext(a, &then_table, then_body); | |
| 270 | ||
| 271 | // Reset the table back to its state from before the branch. | |
| 272 | { | |
| 273 | var it = then_table.keyIterator(); | |
| 274 | while (it.next()) |key| { | |
| 275 | assert(table.remove(key.*)); | |
| 276 | } | |
| 277 | } | |
| 278 | ||
| 279 | var else_table = std.AutoHashMap(Air.Inst.Index, void).init(gpa); | |
| 280 | defer else_table.deinit(); | |
| 281 | try analyzeWithContext(a, &else_table, else_body); | |
| 282 | ||
| 283 | var then_entry_deaths = std.ArrayList(Air.Inst.Index).init(gpa); | |
| 284 | defer then_entry_deaths.deinit(); | |
| 285 | var else_entry_deaths = std.ArrayList(Air.Inst.Index).init(gpa); | |
| 286 | defer else_entry_deaths.deinit(); | |
| 287 | ||
| 288 | { | |
| 289 | var it = else_table.keyIterator(); | |
| 290 | while (it.next()) |key| { | |
| 291 | const else_death = key.*; | |
| 292 | if (!then_table.contains(else_death)) { | |
| 293 | try then_entry_deaths.append(else_death); | |
| 294 | } | |
| 295 | } | |
| 296 | } | |
| 297 | // This loop is the same, except it's for the then branch, and it additionally | |
| 298 | // has to put its items back into the table to undo the reset. | |
| 299 | { | |
| 300 | var it = then_table.keyIterator(); | |
| 301 | while (it.next()) |key| { | |
| 302 | const then_death = key.*; | |
| 303 | if (!else_table.contains(then_death)) { | |
| 304 | try else_entry_deaths.append(then_death); | |
| 305 | } | |
| 306 | try table.put(gpa, then_death, {}); | |
| 307 | } | |
| 308 | } | |
| 309 | // Now we have to correctly populate new_set. | |
| 310 | if (new_set) |ns| { | |
| 311 | try ns.ensureCapacity(@intCast(u32, ns.count() + then_table.count() + else_table.count())); | |
| 312 | var it = then_table.keyIterator(); | |
| 313 | while (it.next()) |key| { | |
| 314 | _ = ns.putAssumeCapacity(key.*, {}); | |
| 315 | } | |
| 316 | it = else_table.keyIterator(); | |
| 317 | while (it.next()) |key| { | |
| 318 | _ = ns.putAssumeCapacity(key.*, {}); | |
| 319 | } | |
| 320 | } | |
| 321 | const then_death_count = @intCast(u32, then_entry_deaths.items.len); | |
| 322 | const else_death_count = @intCast(u32, else_entry_deaths.items.len); | |
| 323 | ||
| 324 | try a.extra.ensureUnusedCapacity(std.meta.fields(@TypeOf(CondBr)).len + | |
| 325 | then_death_count + else_death_count); | |
| 326 | const extra_index = a.addExtraAssumeCapacity(CondBr{ | |
| 327 | .then_death_count = then_death_count, | |
| 328 | .else_death_count = else_death_count, | |
| 329 | }); | |
| 330 | a.extra.appendSliceAssumeCapacity(then_entry_deaths.items); | |
| 331 | a.extra.appendSliceAssumeCapacity(else_entry_deaths.items); | |
| 332 | try a.special.put(inst, extra_index); | |
| 333 | ||
| 334 | // Continue on with the instruction analysis. The following code will find the condition | |
| 335 | // instruction, and the deaths flag for the CondBr instruction will indicate whether the | |
| 336 | // condition's lifetime ends immediately before entering any branch. | |
| 337 | return trackOperands(a, new_set, inst, main_tomb, .{ condition, .none, .none }); | |
| 338 | }, | |
| 339 | .switch_br => { | |
| 340 | const inst_data = inst_datas[inst].pl_op; | |
| 341 | const condition = inst_data.operand; | |
| 342 | const switch_br = a.air.extraData(Air.SwitchBr, inst_data.payload); | |
| 343 | ||
| 344 | const Table = std.AutoHashMapUnmanaged(Air.Inst.Index, void); | |
| 345 | const case_tables = try gpa.alloc(Table, switch_br.data.cases_len + 1); // +1 for else | |
| 346 | defer gpa.free(case_tables); | |
| 347 | ||
| 348 | std.mem.set(Table, case_tables, .{}); | |
| 349 | defer for (case_tables) |*ct| ct.deinit(gpa); | |
| 350 | ||
| 351 | var air_extra_index: usize = switch_br.end; | |
| 352 | for (case_tables[0..switch_br.data.cases_len]) |*case_table| { | |
| 353 | const case = a.air.extraData(Air.SwitchBr.Case, air_extra_index); | |
| 354 | const case_body = a.air.extra[case.end..][0..case.data.body_len]; | |
| 355 | air_extra_index = case.end + case_body.len; | |
| 356 | try analyzeWithContext(a, case_table, case_body); | |
| 357 | ||
| 358 | // Reset the table back to its state from before the case. | |
| 359 | var it = case_table.keyIterator(); | |
| 360 | while (it.next()) |key| { | |
| 361 | assert(table.remove(key.*)); | |
| 362 | } | |
| 363 | } | |
| 364 | { // else | |
| 365 | const else_table = &case_tables[case_tables.len - 1]; | |
| 366 | const else_body = a.air.extra[air_extra_index..][0..switch_br.data.else_body_len]; | |
| 367 | try analyzeWithContext(a, else_table, else_body); | |
| 368 | ||
| 369 | // Reset the table back to its state from before the case. | |
| 370 | var it = else_table.keyIterator(); | |
| 371 | while (it.next()) |key| { | |
| 372 | assert(table.remove(key.*)); | |
| 373 | } | |
| 374 | } | |
| 375 | ||
| 376 | const List = std.ArrayListUnmanaged(Air.Inst.Index); | |
| 377 | const case_deaths = try gpa.alloc(List, case_tables.len); // includes else | |
| 378 | defer gpa.free(case_deaths); | |
| 379 | ||
| 380 | std.mem.set(List, case_deaths, .{}); | |
| 381 | defer for (case_deaths) |*cd| cd.deinit(gpa); | |
| 382 | ||
| 383 | var total_deaths: u32 = 0; | |
| 384 | for (case_tables) |*ct, i| { | |
| 385 | total_deaths += ct.count(); | |
| 386 | var it = ct.keyIterator(); | |
| 387 | while (it.next()) |key| { | |
| 388 | const case_death = key.*; | |
| 389 | for (case_tables) |*ct_inner, j| { | |
| 390 | if (i == j) continue; | |
| 391 | if (!ct_inner.contains(case_death)) { | |
| 392 | // instruction is not referenced in this case | |
| 393 | try case_deaths[j].append(gpa, case_death); | |
| 394 | } | |
| 395 | } | |
| 396 | // undo resetting the table | |
| 397 | try table.put(gpa, case_death, {}); | |
| 398 | } | |
| 399 | } | |
| 400 | ||
| 401 | // Now we have to correctly populate new_set. | |
| 402 | if (new_set) |ns| { | |
| 403 | try ns.ensureUnusedCapacity(gpa, total_deaths); | |
| 404 | for (case_tables) |*ct| { | |
| 405 | var it = ct.keyIterator(); | |
| 406 | while (it.next()) |key| { | |
| 407 | _ = ns.putAssumeCapacity(key.*, {}); | |
| 408 | } | |
| 409 | } | |
| 410 | } | |
| 411 | ||
| 412 | const else_death_count = @intCast(u32, case_deaths[case_deaths.len - 1].items.len); | |
| 413 | const extra_index = try a.addExtra(SwitchBr{ | |
| 414 | .else_death_count = else_death_count, | |
| 415 | }); | |
| 416 | for (case_deaths[0 .. case_deaths.len - 1]) |*cd| { | |
| 417 | const case_death_count = @intCast(u32, cd.items.len); | |
| 418 | try a.extra.ensureUnusedCapacity(1 + case_death_count + else_death_count); | |
| 419 | a.extra.appendAssumeCapacity(case_death_count); | |
| 420 | a.extra.appendSliceAssumeCapacity(cd.items); | |
| 421 | } | |
| 422 | a.extra.appendSliceAssumeCapacity(case_deaths[case_deaths.len - 1].items); | |
| 423 | try a.special.put(inst, extra_index); | |
| 424 | ||
| 425 | return trackOperands(a, new_set, inst, main_tomb, .{ condition, .none, .none }); | |
| 426 | }, | |
| 427 | } | |
| 428 | } | |
| 429 | ||
| 430 | fn trackOperands( | |
| 431 | a: *Analysis, | |
| 432 | new_set: ?*std.AutoHashMap(Air.Inst.Index, void), | |
| 433 | inst: Air.Inst.Index, | |
| 434 | main_tomb: bool, | |
| 435 | operands: [bpi - 1]Air.Inst.Ref, | |
| 436 | ) Allocator.Error!void { | |
| 437 | const table = &a.table; | |
| 438 | const gpa = a.gpa; | |
| 439 | ||
| 440 | var tomb_bits: Bpi = @boolToInt(main_tomb); | |
| 441 | var i = operands.len; | |
| 442 | ||
| 443 | while (i > 0) { | |
| 444 | i -= 1; | |
| 445 | tomb_bits <<= 1; | |
| 446 | const op_int = @enumToInt(operands[i]); | |
| 447 | if (op_int < Air.Inst.Ref.typed_value_map.len) continue; | |
| 448 | const operand: Air.Inst.Index = op_int - Air.Inst.Ref.typed_value_map.len; | |
| 449 | const prev = try table.fetchPut(gpa, operand, {}); | |
| 450 | if (prev == null) { | |
| 451 | // Death. | |
| 452 | tomb_bits |= 1; | |
| 453 | if (new_set) |ns| try ns.putNoClobber(operand, {}); | |
| 454 | } | |
| 455 | } | |
| 456 | a.storeTombBits(inst, tomb_bits); | |
| 457 | } |
src/codegen.zig+5-8| ... | ... | @@ -297,7 +297,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 297 | 297 | /// across each runtime branch upon joining. |
| 298 | 298 | branch_stack: *std.ArrayList(Branch), |
| 299 | 299 | |
| 300 | blocks: std.AutoHashMapUnmanaged(*ir.Inst.Block, BlockData) = .{}, | |
| 300 | // Key is the block instruction | |
| 301 | blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, BlockData) = .{}, | |
| 301 | 302 | |
| 302 | 303 | register_manager: RegisterManager(Self, Register, &callee_preserved_regs) = .{}, |
| 303 | 304 | /// Maps offset to what is stored there. |
| ... | ... | @@ -383,7 +384,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 383 | 384 | }; |
| 384 | 385 | |
| 385 | 386 | const Branch = struct { |
| 386 | inst_table: std.AutoArrayHashMapUnmanaged(*ir.Inst, MCValue) = .{}, | |
| 387 | inst_table: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, MCValue) = .{}, | |
| 387 | 388 | |
| 388 | 389 | fn deinit(self: *Branch, gpa: *Allocator) void { |
| 389 | 390 | self.inst_table.deinit(gpa); |
| ... | ... | @@ -392,7 +393,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 392 | 393 | }; |
| 393 | 394 | |
| 394 | 395 | const StackAllocation = struct { |
| 395 | inst: *ir.Inst, | |
| 396 | inst: Air.Inst.Index, | |
| 396 | 397 | /// TODO do we need size? should be determined by inst.ty.abiSize() |
| 397 | 398 | size: u32, |
| 398 | 399 | }; |
| ... | ... | @@ -720,7 +721,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 720 | 721 | try self.dbgAdvancePCAndLine(self.end_di_line, self.end_di_column); |
| 721 | 722 | } |
| 722 | 723 | |
| 723 | fn genBody(self: *Self, body: ir.Body) InnerError!void { | |
| 724 | fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { | |
| 724 | 725 | for (body.instructions) |inst| { |
| 725 | 726 | try self.ensureProcessDeathCapacity(@popCount(@TypeOf(inst.deaths), inst.deaths)); |
| 726 | 727 | |
| ... | ... | @@ -2824,10 +2825,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 2824 | 2825 | } |
| 2825 | 2826 | |
| 2826 | 2827 | fn genDbgStmt(self: *Self, inst: *ir.Inst.DbgStmt) !MCValue { |
| 2827 | // TODO when reworking AIR memory layout, rework source locations here as | |
| 2828 | // well to be more efficient, as well as support inlined function calls correctly. | |
| 2829 | // For now we convert LazySrcLoc to absolute byte offset, to match what the | |
| 2830 | // existing codegen code expects. | |
| 2831 | 2828 | try self.dbgAdvancePCAndLine(inst.line, inst.column); |
| 2832 | 2829 | assert(inst.base.isUnused()); |
| 2833 | 2830 | return MCValue.dead; |
src/liveness.zig deleted-254| ... | ... | @@ -1,254 +0,0 @@ |
| 1 | const std = @import("std"); | |
| 2 | const Air = @import("Air.zig"); | |
| 3 | const trace = @import("tracy.zig").trace; | |
| 4 | const log = std.log.scoped(.liveness); | |
| 5 | const assert = std.debug.assert; | |
| 6 | ||
| 7 | /// Perform Liveness Analysis over the `Body`. Each `Inst` will have its `deaths` field populated. | |
| 8 | pub fn analyze( | |
| 9 | /// Used for temporary storage during the analysis. | |
| 10 | gpa: *std.mem.Allocator, | |
| 11 | /// Used to tack on extra allocations in the same lifetime as the existing instructions. | |
| 12 | arena: *std.mem.Allocator, | |
| 13 | body: ir.Body, | |
| 14 | ) error{OutOfMemory}!void { | |
| 15 | const tracy = trace(@src()); | |
| 16 | defer tracy.end(); | |
| 17 | ||
| 18 | var table = std.AutoHashMap(*ir.Inst, void).init(gpa); | |
| 19 | defer table.deinit(); | |
| 20 | try table.ensureCapacity(@intCast(u32, body.instructions.len)); | |
| 21 | try analyzeWithTable(arena, &table, null, body); | |
| 22 | } | |
| 23 | ||
| 24 | fn analyzeWithTable( | |
| 25 | arena: *std.mem.Allocator, | |
| 26 | table: *std.AutoHashMap(*ir.Inst, void), | |
| 27 | new_set: ?*std.AutoHashMap(*ir.Inst, void), | |
| 28 | body: ir.Body, | |
| 29 | ) error{OutOfMemory}!void { | |
| 30 | var i: usize = body.instructions.len; | |
| 31 | ||
| 32 | if (new_set) |ns| { | |
| 33 | // We are only interested in doing this for instructions which are born | |
| 34 | // before a conditional branch, so after obtaining the new set for | |
| 35 | // each branch we prune the instructions which were born within. | |
| 36 | while (i != 0) { | |
| 37 | i -= 1; | |
| 38 | const base = body.instructions[i]; | |
| 39 | _ = ns.remove(base); | |
| 40 | try analyzeInst(arena, table, new_set, base); | |
| 41 | } | |
| 42 | } else { | |
| 43 | while (i != 0) { | |
| 44 | i -= 1; | |
| 45 | const base = body.instructions[i]; | |
| 46 | try analyzeInst(arena, table, new_set, base); | |
| 47 | } | |
| 48 | } | |
| 49 | } | |
| 50 | ||
| 51 | fn analyzeInst( | |
| 52 | arena: *std.mem.Allocator, | |
| 53 | table: *std.AutoHashMap(*ir.Inst, void), | |
| 54 | new_set: ?*std.AutoHashMap(*ir.Inst, void), | |
| 55 | base: *ir.Inst, | |
| 56 | ) error{OutOfMemory}!void { | |
| 57 | if (table.contains(base)) { | |
| 58 | base.deaths = 0; | |
| 59 | } else { | |
| 60 | // No tombstone for this instruction means it is never referenced, | |
| 61 | // and its birth marks its own death. Very metal 🤘 | |
| 62 | base.deaths = 1 << ir.Inst.unreferenced_bit_index; | |
| 63 | } | |
| 64 | ||
| 65 | switch (base.tag) { | |
| 66 | .constant => return, | |
| 67 | .block => { | |
| 68 | const inst = base.castTag(.block).?; | |
| 69 | try analyzeWithTable(arena, table, new_set, inst.body); | |
| 70 | // We let this continue so that it can possibly mark the block as | |
| 71 | // unreferenced below. | |
| 72 | }, | |
| 73 | .loop => { | |
| 74 | const inst = base.castTag(.loop).?; | |
| 75 | try analyzeWithTable(arena, table, new_set, inst.body); | |
| 76 | return; // Loop has no operands and it is always unreferenced. | |
| 77 | }, | |
| 78 | .condbr => { | |
| 79 | const inst = base.castTag(.condbr).?; | |
| 80 | ||
| 81 | // Each death that occurs inside one branch, but not the other, needs | |
| 82 | // to be added as a death immediately upon entering the other branch. | |
| 83 | ||
| 84 | var then_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator); | |
| 85 | defer then_table.deinit(); | |
| 86 | try analyzeWithTable(arena, table, &then_table, inst.then_body); | |
| 87 | ||
| 88 | // Reset the table back to its state from before the branch. | |
| 89 | { | |
| 90 | var it = then_table.keyIterator(); | |
| 91 | while (it.next()) |key| { | |
| 92 | assert(table.remove(key.*)); | |
| 93 | } | |
| 94 | } | |
| 95 | ||
| 96 | var else_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator); | |
| 97 | defer else_table.deinit(); | |
| 98 | try analyzeWithTable(arena, table, &else_table, inst.else_body); | |
| 99 | ||
| 100 | var then_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator); | |
| 101 | defer then_entry_deaths.deinit(); | |
| 102 | var else_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator); | |
| 103 | defer else_entry_deaths.deinit(); | |
| 104 | ||
| 105 | { | |
| 106 | var it = else_table.keyIterator(); | |
| 107 | while (it.next()) |key| { | |
| 108 | const else_death = key.*; | |
| 109 | if (!then_table.contains(else_death)) { | |
| 110 | try then_entry_deaths.append(else_death); | |
| 111 | } | |
| 112 | } | |
| 113 | } | |
| 114 | // This loop is the same, except it's for the then branch, and it additionally | |
| 115 | // has to put its items back into the table to undo the reset. | |
| 116 | { | |
| 117 | var it = then_table.keyIterator(); | |
| 118 | while (it.next()) |key| { | |
| 119 | const then_death = key.*; | |
| 120 | if (!else_table.contains(then_death)) { | |
| 121 | try else_entry_deaths.append(then_death); | |
| 122 | } | |
| 123 | try table.put(then_death, {}); | |
| 124 | } | |
| 125 | } | |
| 126 | // Now we have to correctly populate new_set. | |
| 127 | if (new_set) |ns| { | |
| 128 | try ns.ensureCapacity(@intCast(u32, ns.count() + then_table.count() + else_table.count())); | |
| 129 | var it = then_table.keyIterator(); | |
| 130 | while (it.next()) |key| { | |
| 131 | _ = ns.putAssumeCapacity(key.*, {}); | |
| 132 | } | |
| 133 | it = else_table.keyIterator(); | |
| 134 | while (it.next()) |key| { | |
| 135 | _ = ns.putAssumeCapacity(key.*, {}); | |
| 136 | } | |
| 137 | } | |
| 138 | inst.then_death_count = std.math.cast(@TypeOf(inst.then_death_count), then_entry_deaths.items.len) catch return error.OutOfMemory; | |
| 139 | inst.else_death_count = std.math.cast(@TypeOf(inst.else_death_count), else_entry_deaths.items.len) catch return error.OutOfMemory; | |
| 140 | const allocated_slice = try arena.alloc(*ir.Inst, then_entry_deaths.items.len + else_entry_deaths.items.len); | |
| 141 | inst.deaths = allocated_slice.ptr; | |
| 142 | std.mem.copy(*ir.Inst, inst.thenDeaths(), then_entry_deaths.items); | |
| 143 | std.mem.copy(*ir.Inst, inst.elseDeaths(), else_entry_deaths.items); | |
| 144 | ||
| 145 | // Continue on with the instruction analysis. The following code will find the condition | |
| 146 | // instruction, and the deaths flag for the CondBr instruction will indicate whether the | |
| 147 | // condition's lifetime ends immediately before entering any branch. | |
| 148 | }, | |
| 149 | .switchbr => { | |
| 150 | const inst = base.castTag(.switchbr).?; | |
| 151 | ||
| 152 | const Table = std.AutoHashMap(*ir.Inst, void); | |
| 153 | const case_tables = try table.allocator.alloc(Table, inst.cases.len + 1); // +1 for else | |
| 154 | defer table.allocator.free(case_tables); | |
| 155 | ||
| 156 | std.mem.set(Table, case_tables, Table.init(table.allocator)); | |
| 157 | defer for (case_tables) |*ct| ct.deinit(); | |
| 158 | ||
| 159 | for (inst.cases) |case, i| { | |
| 160 | try analyzeWithTable(arena, table, &case_tables[i], case.body); | |
| 161 | ||
| 162 | // Reset the table back to its state from before the case. | |
| 163 | var it = case_tables[i].keyIterator(); | |
| 164 | while (it.next()) |key| { | |
| 165 | assert(table.remove(key.*)); | |
| 166 | } | |
| 167 | } | |
| 168 | { // else | |
| 169 | try analyzeWithTable(arena, table, &case_tables[case_tables.len - 1], inst.else_body); | |
| 170 | ||
| 171 | // Reset the table back to its state from before the case. | |
| 172 | var it = case_tables[case_tables.len - 1].keyIterator(); | |
| 173 | while (it.next()) |key| { | |
| 174 | assert(table.remove(key.*)); | |
| 175 | } | |
| 176 | } | |
| 177 | ||
| 178 | const List = std.ArrayList(*ir.Inst); | |
| 179 | const case_deaths = try table.allocator.alloc(List, case_tables.len); // +1 for else | |
| 180 | defer table.allocator.free(case_deaths); | |
| 181 | ||
| 182 | std.mem.set(List, case_deaths, List.init(table.allocator)); | |
| 183 | defer for (case_deaths) |*cd| cd.deinit(); | |
| 184 | ||
| 185 | var total_deaths: u32 = 0; | |
| 186 | for (case_tables) |*ct, i| { | |
| 187 | total_deaths += ct.count(); | |
| 188 | var it = ct.keyIterator(); | |
| 189 | while (it.next()) |key| { | |
| 190 | const case_death = key.*; | |
| 191 | for (case_tables) |*ct_inner, j| { | |
| 192 | if (i == j) continue; | |
| 193 | if (!ct_inner.contains(case_death)) { | |
| 194 | // instruction is not referenced in this case | |
| 195 | try case_deaths[j].append(case_death); | |
| 196 | } | |
| 197 | } | |
| 198 | // undo resetting the table | |
| 199 | try table.put(case_death, {}); | |
| 200 | } | |
| 201 | } | |
| 202 | ||
| 203 | // Now we have to correctly populate new_set. | |
| 204 | if (new_set) |ns| { | |
| 205 | try ns.ensureCapacity(@intCast(u32, ns.count() + total_deaths)); | |
| 206 | for (case_tables) |*ct| { | |
| 207 | var it = ct.keyIterator(); | |
| 208 | while (it.next()) |key| { | |
| 209 | _ = ns.putAssumeCapacity(key.*, {}); | |
| 210 | } | |
| 211 | } | |
| 212 | } | |
| 213 | ||
| 214 | total_deaths = 0; | |
| 215 | for (case_deaths[0 .. case_deaths.len - 1]) |*ct, i| { | |
| 216 | inst.cases[i].index = total_deaths; | |
| 217 | const len = std.math.cast(@TypeOf(inst.else_deaths), ct.items.len) catch return error.OutOfMemory; | |
| 218 | inst.cases[i].deaths = len; | |
| 219 | total_deaths += len; | |
| 220 | } | |
| 221 | { // else | |
| 222 | const else_deaths = std.math.cast(@TypeOf(inst.else_deaths), case_deaths[case_deaths.len - 1].items.len) catch return error.OutOfMemory; | |
| 223 | inst.else_index = total_deaths; | |
| 224 | inst.else_deaths = else_deaths; | |
| 225 | total_deaths += else_deaths; | |
| 226 | } | |
| 227 | ||
| 228 | const allocated_slice = try arena.alloc(*ir.Inst, total_deaths); | |
| 229 | inst.deaths = allocated_slice.ptr; | |
| 230 | for (case_deaths[0 .. case_deaths.len - 1]) |*cd, i| { | |
| 231 | std.mem.copy(*ir.Inst, inst.caseDeaths(i), cd.items); | |
| 232 | } | |
| 233 | std.mem.copy(*ir.Inst, inst.elseDeaths(), case_deaths[case_deaths.len - 1].items); | |
| 234 | }, | |
| 235 | else => {}, | |
| 236 | } | |
| 237 | ||
| 238 | const needed_bits = base.operandCount(); | |
| 239 | if (needed_bits <= ir.Inst.deaths_bits) { | |
| 240 | var bit_i: ir.Inst.DeathsBitIndex = 0; | |
| 241 | while (base.getOperand(bit_i)) |operand| : (bit_i += 1) { | |
| 242 | const prev = try table.fetchPut(operand, {}); | |
| 243 | if (prev == null) { | |
| 244 | // Death. | |
| 245 | base.deaths |= @as(ir.Inst.DeathsInt, 1) << bit_i; | |
| 246 | if (new_set) |ns| try ns.putNoClobber(operand, {}); | |
| 247 | } | |
| 248 | } | |
| 249 | } else { | |
| 250 | @panic("Handle liveness analysis for instructions with many parameters"); | |
| 251 | } | |
| 252 | ||
| 253 | log.debug("analyze {}: 0b{b}\n", .{ base.tag, base.deaths }); | |
| 254 | } |