1insts: Inst.List.Slice,
2extra: ExtraStore,
3
4pub const ExtraStore = dod.ExtraStore(.air, .@"const");
5pub const EncodedData = dod.EncodedData(Inst.Tag, u64);
6pub const ExtraIndex = ExtraStore.Index;
7
8pub 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
187pub 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
204pub const IndexList = ExtraStore.Slice(Air.Inst.Index);
205pub const RefList = ExtraStore.Slice(Air.Inst.Ref);
206
207pub const Break = struct {
208 block: Inst.Index,
209 // TODO: rename to operand
210 value: Inst.Ref,
211};
212
213pub const Bin = struct {
214 lhs: Inst.Ref,
215 rhs: Inst.Ref,
216};
217
218pub const Store = struct {
219 memory: Inst.Index,
220 operand: Inst.Ref,
221};
222
223pub const Un = struct {
224 operand: Inst.Ref,
225 unused: Unused = .zero,
226};
227
228pub const FieldOpaque = struct {
229 base: Inst.Ref,
230 field: InternPool.NullTerminatedString,
231};
232
233pub const Call = struct {
234 base: Inst.Ref,
235 call_data: ExtraIndex(Data),
236
237 pub const Data = struct {
238 args: RefList,
239 };
240};
241
242pub const Arg = struct {
243 type: Type,
244 arg_number: u32,
245};
246
247pub 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
259pub const Memory = struct {
260 init: Inst.Ref,
261 ty: Type,
262};
263
264pub 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
276pub const ArrayInit = struct {
277 ty: Type,
278 list: ExtraIndex(RefList),
279};
280
281pub 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
293pub const FieldStruct = struct {
294 base: Inst.Index,
295 field_index: u32,
296};
297
298pub 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
304pub fn instTag(air: *const Air, index: Inst.Index) Inst.Tag {
305 return air.insts.items(.tag)[@intFromEnum(index)];
306}
307
308pub fn instFlags(air: *const Air, index: Inst.Index) Flags {
309 return air.insts.items(.flags)[@intFromEnum(index)];
310}
311
312pub fn instEncodedData(air: *const Air, index: Inst.Index) EncodedData {
313 return air.insts.items(.data)[@intFromEnum(index)];
314}
315
316pub 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
320pub 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
324pub 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
331pub 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
411pub 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
418pub fn dumpToWriter(air: Air, ip: *InternPool, out: core.OutStream) !void {
419 try out.print("{}", .{Dump{ .air = air, .ip = ip, .tty = out.tty }});
420}
421
422pub const Dump = @import("Air.Dump.zig");
423
424const Air = @This();
425const std = @import("std");
426const mem = std.mem;
427const assert = std.debug.assert;
428
429const core = @import("mod.zig");
430const InternPool = core.InternPool;
431const Value = InternPool.Value;
432const Type = InternPool.Type;
433
434const dod = @import("array.zig");
435
436const Unused = enum(u32) { zero = 0 };