| 1 | insts: Inst.List.Slice, |
| 2 | extra: ExtraStore, |
| 3 | |
| 4 | pub const ExtraStore = dod.ExtraStore(.air, .@"const"); |
| 5 | pub const EncodedData = dod.EncodedData(Inst.Tag, u64); |
| 6 | pub const ExtraIndex = ExtraStore.Index; |
| 7 | |
| 8 | pub const Inst = struct { |
| 9 | tag: Tag, |
| 10 | data: EncodedData, |
| 11 | flags: Flags, |
| 12 | |
| 13 | pub const List = std.MultiArrayList(Inst); |
| 14 | |
| 15 | pub const Tag = enum { |
| 16 | block, |
| 17 | call, |
| 18 | js_new, |
| 19 | @"break", |
| 20 | @"if", |
| 21 | arg, |
| 22 | |
| 23 | memory, |
| 24 | store, |
| 25 | |
| 26 | struct_init, |
| 27 | array_init, |
| 28 | tuple_init, |
| 29 | |
| 30 | field_opaque, |
| 31 | field_struct, |
| 32 | field_tuple, |
| 33 | field_array, |
| 34 | |
| 35 | array_len, |
| 36 | |
| 37 | add, |
| 38 | sub, |
| 39 | mul, |
| 40 | div, |
| 41 | eq, |
| 42 | lt, |
| 43 | le, |
| 44 | gt, |
| 45 | ge, |
| 46 | |
| 47 | negate, |
| 48 | bool_not, |
| 49 | panic, |
| 50 | |
| 51 | pub fn Data(comptime tag: Tag) type { |
| 52 | return switch (tag) { |
| 53 | .block => IndexList, |
| 54 | .@"break" => Break, |
| 55 | .@"if" => If, |
| 56 | .call, .js_new => Call, |
| 57 | .arg => Arg, |
| 58 | .memory => Memory, |
| 59 | .store => Store, |
| 60 | .struct_init => StructInit, |
| 61 | .array_init => ArrayInit, |
| 62 | .tuple_init => TupleInit, |
| 63 | .field_struct, .field_tuple => FieldStruct, |
| 64 | .field_opaque, |
| 65 | .field_array, |
| 66 | .add, |
| 67 | .sub, |
| 68 | .mul, |
| 69 | .div, |
| 70 | .lt, |
| 71 | .gt, |
| 72 | .eq, |
| 73 | .le, |
| 74 | .ge, |
| 75 | => Bin, |
| 76 | .negate, |
| 77 | .bool_not, |
| 78 | .array_len, |
| 79 | .panic, |
| 80 | => Un, |
| 81 | }; |
| 82 | } |
| 83 | }; |
| 84 | |
| 85 | /// reference to either a value or another instruction in this air |
| 86 | pub const Ref = packed struct(u32) { |
| 87 | kind: Kind, |
| 88 | data: u31, |
| 89 | |
| 90 | pub const @"void" = Value.void.toAir(); |
| 91 | |
| 92 | pub const Kind = enum(u1) { value, index }; |
| 93 | |
| 94 | pub const Decoded = union(enum) { |
| 95 | value: Value, |
| 96 | index: Index, |
| 97 | }; |
| 98 | |
| 99 | pub fn decode(ref: Ref) Decoded { |
| 100 | return switch (ref.kind) { |
| 101 | .value => .{ .value = @enumFromInt(ref.data) }, |
| 102 | .index => .{ .index = @enumFromInt(ref.data) }, |
| 103 | }; |
| 104 | } |
| 105 | |
| 106 | pub fn value(ref: Ref) ?Value { |
| 107 | return if (ref.kind == .value) return @enumFromInt(ref.data) else null; |
| 108 | } |
| 109 | |
| 110 | pub fn index(ref: Ref) ?Index { |
| 111 | return if (ref.kind == .index) return @enumFromInt(ref.data) else null; |
| 112 | } |
| 113 | |
| 114 | pub fn toOptional(ref: Ref) Optional { |
| 115 | return @enumFromInt(@as(u32, @bitCast(ref))); |
| 116 | } |
| 117 | |
| 118 | pub const Optional = enum(u32) { |
| 119 | none = std.math.maxInt(u32), |
| 120 | _, |
| 121 | |
| 122 | pub fn init(ref: ?Ref) Optional { |
| 123 | return if (ref) |r| @enumFromInt(@as(u32, @bitCast(r))) else .none; |
| 124 | } |
| 125 | |
| 126 | pub fn unwrap(opt: Optional) ?Ref { |
| 127 | return if (opt != .none) @bitCast(@intFromEnum(opt)) else null; |
| 128 | } |
| 129 | }; |
| 130 | |
| 131 | pub fn isVoid(ref: Ref) bool { |
| 132 | return ref.kind == .value and |
| 133 | ref.decode().value == .void; |
| 134 | } |
| 135 | |
| 136 | pub fn eql(ref: Ref, other: Ref) bool { |
| 137 | return @as(u32, @bitCast(ref)) == @as(u32, @bitCast(other)); |
| 138 | } |
| 139 | |
| 140 | pub fn dump(ref: Ref, ip: *const InternPool) std.fmt.Formatter(dumpImpl) { |
| 141 | return .{ .data = .{ ref, ip } }; |
| 142 | } |
| 143 | |
| 144 | fn dumpImpl( |
| 145 | data: struct { Ref, *const InternPool }, |
| 146 | comptime fmt: []const u8, |
| 147 | _: std.fmt.FormatOptions, |
| 148 | w: anytype, |
| 149 | ) !void { |
| 150 | comptime assert(fmt.len == 0); |
| 151 | const ref, const ip = data; |
| 152 | switch (ref.decode()) { |
| 153 | .value => |v| try w.print("{}", .{v.dump(ip)}), |
| 154 | .index => |i| try w.print("#{d}", .{@intFromEnum(i)}), |
| 155 | } |
| 156 | } |
| 157 | }; |
| 158 | |
| 159 | /// reference to another instruction in this air |
| 160 | /// note that these cannot point to code in other functions, such makes no sense |
| 161 | pub const Index = enum(u32) { |
| 162 | main_block, |
| 163 | _, |
| 164 | |
| 165 | pub fn toRef(index: Index) Ref { |
| 166 | return .{ |
| 167 | .kind = .index, |
| 168 | .data = @intCast(@intFromEnum(index)), |
| 169 | }; |
| 170 | } |
| 171 | |
| 172 | pub fn toOptional(index: Index) Optional { |
| 173 | return @enumFromInt(@intFromEnum(index)); |
| 174 | } |
| 175 | |
| 176 | pub const Optional = enum(u32) { |
| 177 | none = std.math.maxInt(u32), |
| 178 | _, |
| 179 | |
| 180 | pub fn unwrap(oi: Optional) ?Index { |
| 181 | return if (oi != .none) @enumFromInt(@intFromEnum(oi)) else null; |
| 182 | } |
| 183 | }; |
| 184 | }; |
| 185 | }; |
| 186 | |
| 187 | pub const Flags = packed struct { |
| 188 | uses: Uses = .none, |
| 189 | |
| 190 | pub const Uses = enum(u2) { |
| 191 | none, |
| 192 | single, |
| 193 | multi, |
| 194 | |
| 195 | pub fn increment(uses: Uses) Uses { |
| 196 | return switch (uses) { |
| 197 | .none => .single, |
| 198 | .single, .multi => .multi, |
| 199 | }; |
| 200 | } |
| 201 | }; |
| 202 | }; |
| 203 | |
| 204 | pub const IndexList = ExtraStore.Slice(Air.Inst.Index); |
| 205 | pub const RefList = ExtraStore.Slice(Air.Inst.Ref); |
| 206 | |
| 207 | pub const Break = struct { |
| 208 | block: Inst.Index, |
| 209 | // TODO: rename to operand |
| 210 | value: Inst.Ref, |
| 211 | }; |
| 212 | |
| 213 | pub const Bin = struct { |
| 214 | lhs: Inst.Ref, |
| 215 | rhs: Inst.Ref, |
| 216 | }; |
| 217 | |
| 218 | pub const Store = struct { |
| 219 | memory: Inst.Index, |
| 220 | operand: Inst.Ref, |
| 221 | }; |
| 222 | |
| 223 | pub const Un = struct { |
| 224 | operand: Inst.Ref, |
| 225 | unused: Unused = .zero, |
| 226 | }; |
| 227 | |
| 228 | pub const FieldOpaque = struct { |
| 229 | base: Inst.Ref, |
| 230 | field: InternPool.NullTerminatedString, |
| 231 | }; |
| 232 | |
| 233 | pub const Call = struct { |
| 234 | base: Inst.Ref, |
| 235 | call_data: ExtraIndex(Data), |
| 236 | |
| 237 | pub const Data = struct { |
| 238 | args: RefList, |
| 239 | }; |
| 240 | }; |
| 241 | |
| 242 | pub const Arg = struct { |
| 243 | type: Type, |
| 244 | arg_number: u32, |
| 245 | }; |
| 246 | |
| 247 | pub const If = struct { |
| 248 | cond: Inst.Ref, |
| 249 | data: ExtraIndex(Data), |
| 250 | |
| 251 | pub const Data = struct { |
| 252 | yes: Inst.Ref, |
| 253 | no: Inst.Ref, |
| 254 | // TODO: delete this. both refs should be coerced to the common type |
| 255 | union_ty: Type, |
| 256 | }; |
| 257 | }; |
| 258 | |
| 259 | pub const Memory = struct { |
| 260 | init: Inst.Ref, |
| 261 | ty: Type, |
| 262 | }; |
| 263 | |
| 264 | pub const StructInit = struct { |
| 265 | ty: Type, |
| 266 | field_values: u32, |
| 267 | |
| 268 | pub fn valuesPtr( |
| 269 | init: *const StructInit, |
| 270 | air: *const Air, |
| 271 | ) [*]const Air.Inst.Ref { |
| 272 | return @ptrCast(air.extra.list.items[init.field_values..].ptr); |
| 273 | } |
| 274 | }; |
| 275 | |
| 276 | pub const ArrayInit = struct { |
| 277 | ty: Type, |
| 278 | list: ExtraIndex(RefList), |
| 279 | }; |
| 280 | |
| 281 | pub const TupleInit = struct { |
| 282 | ty: Type, |
| 283 | field_values: u32, |
| 284 | |
| 285 | pub fn valuesPtr( |
| 286 | init: *const TupleInit, |
| 287 | air: *const Air, |
| 288 | ) [*]const Air.Inst.Ref { |
| 289 | return @ptrCast(air.extra.list.items[init.field_values..].ptr); |
| 290 | } |
| 291 | }; |
| 292 | |
| 293 | pub const FieldStruct = struct { |
| 294 | base: Inst.Index, |
| 295 | field_index: u32, |
| 296 | }; |
| 297 | |
| 298 | pub fn deinit(air: *Air, gpa: std.mem.Allocator) void { |
| 299 | defer air.* = undefined; |
| 300 | air.insts.deinit(gpa); |
| 301 | air.extra.deinit(gpa); |
| 302 | } |
| 303 | |
| 304 | pub fn instTag(air: *const Air, index: Inst.Index) Inst.Tag { |
| 305 | return air.insts.items(.tag)[@intFromEnum(index)]; |
| 306 | } |
| 307 | |
| 308 | pub fn instFlags(air: *const Air, index: Inst.Index) Flags { |
| 309 | return air.insts.items(.flags)[@intFromEnum(index)]; |
| 310 | } |
| 311 | |
| 312 | pub fn instEncodedData(air: *const Air, index: Inst.Index) EncodedData { |
| 313 | return air.insts.items(.data)[@intFromEnum(index)]; |
| 314 | } |
| 315 | |
| 316 | pub fn instData(air: *const Air, comptime tag: Inst.Tag, index: Inst.Index) tag.Data() { |
| 317 | return air.instEncodedData(index).decode(tag, air.instTag(index)); |
| 318 | } |
| 319 | |
| 320 | pub fn instPayload(air: *const Air, comptime tag: Inst.Tag, index: Inst.Index) tag.Data().T { |
| 321 | return air.instData(tag, index).extra_index.get(air); |
| 322 | } |
| 323 | |
| 324 | pub fn typeOf(air: *const Air, ref: Inst.Ref, ip: *const InternPool) InternPool.Type { |
| 325 | return switch (ref.decode()) { |
| 326 | .value => |v| v.typeOf(ip), |
| 327 | .index => |i| air.typeOfIndex(i, ip), |
| 328 | }; |
| 329 | } |
| 330 | |
| 331 | pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) InternPool.Type { |
| 332 | return switch (air.instTag(index)) { |
| 333 | // These values are by definition, opaque |
| 334 | .js_new => .@"opaque", |
| 335 | |
| 336 | // These operations always produce known types |
| 337 | .lt, .gt, .le, .ge, .eq, .bool_not => .bool, |
| 338 | .@"break", .panic => .never, |
| 339 | .field_opaque => .@"opaque", |
| 340 | .array_len => .int32, |
| 341 | |
| 342 | // For many instructions, the type is stored on it's data directly. This |
| 343 | // is avoided when possible to reduce the size of Air. |
| 344 | .memory => air.instData(.memory, index).ty, |
| 345 | .store => air.instData(.memory, air.instData(.store, index).memory).ty, |
| 346 | .@"if" => air.instData(.@"if", index).data.get(air).union_ty, |
| 347 | .struct_init => air.instData(.struct_init, index).ty, |
| 348 | .array_init => air.instData(.array_init, index).ty, |
| 349 | .tuple_init => air.instData(.tuple_init, index).ty, |
| 350 | .arg => air.instData(.arg, index).type, |
| 351 | |
| 352 | // A handful of binary operators are lowered to a form where both sides |
| 353 | // are the same type, so the type of the left side can be used. |
| 354 | inline .add, |
| 355 | .sub, |
| 356 | .mul, |
| 357 | .div, |
| 358 | => |t| { |
| 359 | const left = air.typeOf(air.instData(t, index).lhs, ip); |
| 360 | if (core.runtime_safety) { |
| 361 | assert(left == air.typeOf(air.instData(t, index).rhs, ip)); |
| 362 | } |
| 363 | return left; |
| 364 | }, |
| 365 | // Negate follows a similar pattern to the above |
| 366 | .negate => air.typeOf(air.instData(.negate, index).operand, ip), |
| 367 | |
| 368 | // TODO: taking the last instruction is likely wrong. |
| 369 | // The last instruction is going to be a .break and be of type .never |
| 370 | .block => { |
| 371 | const items = air.instData(.block, index).get(air); |
| 372 | assert(items.len > 0); // empty blocks should always be `Value.void` |
| 373 | return air.typeOfIndex(items[items.len - 1], ip); |
| 374 | }, |
| 375 | |
| 376 | // Calls return the return type of the operand. |
| 377 | .call => { |
| 378 | const base_type = air.typeOf(air.instData(.call, index).base, ip); |
| 379 | if (base_type == .@"opaque") return .@"opaque"; |
| 380 | return base_type.toUnresolvedFunctionPrototype(ip).?.return_type orelse |
| 381 | unreachable; // return type was not resolved |
| 382 | }, |
| 383 | |
| 384 | // Field accesses on structs are special-cased by storing the field |
| 385 | // index instead of the name string. This avoids creating hashmaps. |
| 386 | .field_struct => { |
| 387 | const inst = air.instData(.field_struct, index); |
| 388 | const base_type = air.typeOfIndex(inst.base, ip) |
| 389 | .toStructType(ip) orelse unreachable; // field_struct demands struct type |
| 390 | return base_type.field_types.at(ip, inst.field_index); |
| 391 | }, |
| 392 | |
| 393 | // Array accesses use the array they are accessing into. |
| 394 | .field_array => { |
| 395 | const inst = air.instData(.field_array, index); |
| 396 | const base_type = air.typeOf(inst.lhs, ip) |
| 397 | .toArrayType(ip) orelse unreachable; // field_array demands array type |
| 398 | return base_type.item_type; |
| 399 | }, |
| 400 | |
| 401 | // Tuple accesses use the tuple they are accessing into. |
| 402 | .field_tuple => { |
| 403 | const inst = air.instData(.field_tuple, index); |
| 404 | const base_type = air.typeOfIndex(inst.base, ip) |
| 405 | .toTupleType(ip) orelse unreachable; // field_tuple demands tuple type |
| 406 | return base_type.items.at(ip, inst.field_index); |
| 407 | }, |
| 408 | }; |
| 409 | } |
| 410 | |
| 411 | pub fn paramCount(air: Air) u32 { |
| 412 | for (air.insts.items(.tag)[1..], 0..) |tag, i| { |
| 413 | if (tag != .arg) return @intCast(i); |
| 414 | } |
| 415 | return 0; |
| 416 | } |
| 417 | |
| 418 | pub fn dumpToWriter(air: Air, ip: *InternPool, out: core.OutStream) !void { |
| 419 | try out.print("{}", .{Dump{ .air = air, .ip = ip, .tty = out.tty }}); |
| 420 | } |
| 421 | |
| 422 | pub const Dump = @import("Air.Dump.zig"); |
| 423 | |
| 424 | const Air = @This(); |
| 425 | const std = @import("std"); |
| 426 | const mem = std.mem; |
| 427 | const assert = std.debug.assert; |
| 428 | |
| 429 | const core = @import("mod.zig"); |
| 430 | const InternPool = core.InternPool; |
| 431 | const Value = InternPool.Value; |
| 432 | const Type = InternPool.Type; |
| 433 | |
| 434 | const dod = @import("array.zig"); |
| 435 | |
| 436 | const Unused = enum(u32) { zero = 0 }; |