lib/choir/src/bytecode/bytecode.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const ir = @import("../core/root.zig");
3
4 const magic = "SLIRBC1\x00";
5 pub const format_version: u32 = 12;
6 pub const minimum_readable_version: u32 = format_version;
7 pub const maximum_readable_version: u32 = format_version;
8 pub const default_dialect_version: u32 = 0;
9 pub const default_dialect_flags: u32 = 0;
10 const current_endianness: u8 = 1;
11
12 pub const FormatVersionStatus = enum {
13 readable,
14 unsupported_older,
15 unsupported_newer,
16 };
17
18 pub const FormatCompatibility = struct {
19 version: u32,
20 minimum_readable: u32 = minimum_readable_version,
21 maximum_readable: u32 = maximum_readable_version,
22 status: FormatVersionStatus,
23
24 pub fn readable(self: FormatCompatibility) bool {
25 return self.status == .readable;
26 }
27 };
28
29 pub const ContainerHeader = struct {
30 version: u32,
31 endianness: u8,
32 ptr_size: u8,
33 flags: u16,
34 section_table_offset: u64,
35 section_count: u64,
36 file_hash: u64,
37
38 pub fn compatibility(self: ContainerHeader) FormatCompatibility {
39 return classifyFormatVersion(self.version);
40 }
41 };
42
43 pub fn classifyFormatVersion(version: u32) FormatCompatibility {
44 return .{
45 .version = version,
46 .status = if (version < minimum_readable_version)
47 .unsupported_older
48 else if (version > maximum_readable_version)
49 .unsupported_newer
50 else
51 .readable,
52 };
53 }
54
55 pub const SectionKind = enum(u32) {
56 strings = 1,
57 dialects = 2,
58 types = 3,
59 attributes = 4,
60 locations = 5,
61 module = 6,
62 resources = 7,
63 };
64
65 const SectionMeta = struct {
66 kind: u32,
67 flags: u32,
68 offset: u64,
69 size: u64,
70 };
71
72 const SectionData = struct {
73 kind: u32,
74 flags: u32,
75 data: []const u8,
76 };
77
78 pub const DialectEntry = struct {
79 name: []const u8,
80 version: u32,
81 flags: u32,
82
83 pub fn usesDefaultVersion(self: DialectEntry) bool {
84 return self.version == default_dialect_version and self.flags == default_dialect_flags;
85 }
86 };
87
88 pub const Resource = struct {
89 namespace: []const u8,
90 name: []const u8,
91 type_id: []const u8,
92 data: []const u8,
93 };
94
95 pub const DecodedTables = struct {
96 allocator: std.mem.Allocator,
97 format: FormatCompatibility,
98 strings: []const []const u8,
99 dialects: []const DialectEntry,
100 types: []const ir.Type,
101 attrs: []const ir.Attribute,
102 locs: []const ir.Location,
103 loc_slices: std.ArrayList([]const ir.Location),
104
105 pub fn deinit(self: *DecodedTables) void {
106 for (self.loc_slices.items) |slice| {
107 self.allocator.free(@constCast(slice));
108 }
109 self.loc_slices.deinit(self.allocator);
110
111 for (self.strings) |str| {
112 self.allocator.free(@constCast(str));
113 }
114 self.allocator.free(self.strings);
115 self.allocator.free(self.dialects);
116 self.allocator.free(self.types);
117 self.allocator.free(self.attrs);
118 self.allocator.free(self.locs);
119 }
120 };
121
122 pub const DecodedModule = struct {
123 module: *ir.Operation,
124 tables: DecodedTables,
125 resources: []const Resource = &.{},
126
127 pub fn format(self: *const DecodedModule) FormatCompatibility {
128 return self.tables.format;
129 }
130
131 pub fn deinit(self: *DecodedModule) void {
132 freeResources(self.tables.allocator, self.resources);
133 self.tables.deinit();
134 }
135 };
136
137 pub const FlatModuleDecoder = struct {
138 allocator: std.mem.Allocator,
139 tables: DecodedTables,
140 resources: []const Resource,
141 decoder: ModuleDecoder,
142 reader: SliceReader,
143 module_name: []const u8,
144 module_location: ir.Location,
145 operations_remaining: usize,
146 value_base: usize = 0,
147 current: ?*ir.Operation = null,
148
149 pub fn init(
150 allocator: std.mem.Allocator,
151 ctx: *ir.Context,
152 bytes: []const u8,
153 ) !FlatModuleDecoder {
154 const sections = try parseSections(bytes);
155 const module_bytes = sections.module orelse return error.MissingModule;
156
157 var tables = try decodeTablesFromSections(allocator, ctx, sections);
158 errdefer tables.deinit();
159
160 const resources = try decodeResourcesSection(allocator, sections.resources);
161 errdefer freeResources(allocator, resources);
162
163 var decoder = ModuleDecoder.init(allocator, ctx, &tables);
164 errdefer decoder.deinit();
165
166 var reader = SliceReader.init(module_bytes);
167 const envelope = try decodeFlatModuleEnvelope(&decoder, &reader);
168 return .{
169 .allocator = allocator,
170 .tables = tables,
171 .resources = resources,
172 .decoder = decoder,
173 .reader = reader,
174 .module_name = envelope.name,
175 .module_location = envelope.location,
176 .operations_remaining = envelope.operation_count,
177 };
178 }
179
180 pub fn deinit(self: *FlatModuleDecoder) void {
181 self.releaseCurrent();
182 self.decoder.deinit();
183 freeResources(self.allocator, self.resources);
184 self.tables.deinit();
185 self.* = undefined;
186 }
187
188 pub fn format(self: *const FlatModuleDecoder) FormatCompatibility {
189 return self.tables.format;
190 }
191
192 pub fn name(self: *const FlatModuleDecoder) []const u8 {
193 return self.module_name;
194 }
195
196 pub fn location(self: *const FlatModuleDecoder) ir.Location {
197 return self.module_location;
198 }
199
200 pub fn next(self: *FlatModuleDecoder) !?*ir.Operation {
201 self.releaseCurrent();
202 if (self.operations_remaining == 0) {
203 if (self.reader.offset != self.reader.bytes.len) {
204 return error.InvalidModule;
205 }
206 return null;
207 }
208 self.decoder.value_base = self.value_base;
209 const op = try decodeOperation(
210 &self.decoder,
211 &self.reader,
212 null,
213 null,
214 );
215 self.current = op;
216 self.operations_remaining -= 1;
217 return op;
218 }
219
220 fn releaseCurrent(self: *FlatModuleDecoder) void {
221 const op = self.current orelse return;
222 op.erase();
223 self.value_base = std.math.add(
224 usize,
225 self.value_base,
226 self.decoder.values.items.len,
227 ) catch unreachable;
228 self.decoder.values.clearRetainingCapacity();
229 self.current = null;
230 }
231 };
232
233 const StringTable = struct {
234 allocator: std.mem.Allocator,
235 items: std.ArrayList([]const u8),
236 map: std.StringHashMap(u32),
237
238 pub fn init(allocator: std.mem.Allocator) StringTable {
239 return .{
240 .allocator = allocator,
241 .items = .empty,
242 .map = std.StringHashMap(u32).init(allocator),
243 };
244 }
245
246 pub fn deinit(self: *StringTable) void {
247 for (self.items.items) |value| {
248 self.allocator.free(@constCast(value));
249 }
250 self.items.deinit(self.allocator);
251 self.map.deinit();
252 }
253
254 pub fn intern(self: *StringTable, value: []const u8) !u32 {
255 if (self.map.get(value)) |id| return id;
256 const id = std.math.cast(u32, self.items.items.len) orelse return error.TableLimit;
257 try self.items.ensureUnusedCapacity(self.allocator, 1);
258 try self.map.ensureUnusedCapacity(1);
259 const owned = try self.allocator.dupe(u8, value);
260 self.items.appendAssumeCapacity(owned);
261 self.map.putAssumeCapacity(owned, id);
262 return id;
263 }
264 };
265
266 fn stringAllocationScenario(allocator: std.mem.Allocator) !void {
267 var table = StringTable.init(allocator);
268 defer table.deinit();
269 errdefer std.debug.assert(table.items.items.len == table.map.count());
270 for ([_][]const u8{ "a", "b", "c", "d", "e", "f", "g", "h", "i" }, 0..) |value, ordinal| {
271 try std.testing.expectEqual(ordinal, try table.intern(value));
272 }
273 try std.testing.expectEqual(0, try table.intern("a"));
274 }
275
276 test "bytecode string interning owns a consistent prefix across allocation failures" {
277 try std.testing.checkAllAllocationFailures(std.testing.allocator, stringAllocationScenario, .{});
278 }
279
280 const DialectRecord = struct {
281 name_id: u32,
282 version: u32,
283 flags: u32,
284 };
285
286 const DialectTable = struct {
287 items: std.ArrayList(DialectRecord),
288 map: std.AutoHashMap(u32, u32),
289
290 pub fn init(allocator: std.mem.Allocator) DialectTable {
291 return .{
292 .items = .empty,
293 .map = std.AutoHashMap(u32, u32).init(allocator),
294 };
295 }
296
297 pub fn deinit(self: *DialectTable, allocator: std.mem.Allocator) void {
298 self.items.deinit(allocator);
299 self.map.deinit();
300 }
301
302 pub fn intern(self: *DialectTable, allocator: std.mem.Allocator, name_id: u32) !u32 {
303 if (self.map.get(name_id)) |id| return id;
304 try self.items.append(allocator, .{
305 .name_id = name_id,
306 .version = default_dialect_version,
307 .flags = default_dialect_flags,
308 });
309 const id: u32 = @intCast(self.items.items.len - 1);
310 try self.map.put(name_id, id);
311 return id;
312 }
313 };
314
315 pub const TableBuilder = struct {
316 allocator: std.mem.Allocator,
317 strings: StringTable,
318 dialects: DialectTable,
319 types: std.ArrayList(ir.Type),
320 attrs: std.ArrayList(ir.Attribute),
321 locs: std.ArrayList(ir.Location),
322
323 pub fn init(allocator: std.mem.Allocator) TableBuilder {
324 return .{
325 .allocator = allocator,
326 .strings = StringTable.init(allocator),
327 .dialects = DialectTable.init(allocator),
328 .types = .empty,
329 .attrs = .empty,
330 .locs = .empty,
331 };
332 }
333
334 pub fn deinit(self: *TableBuilder) void {
335 self.locs.deinit(self.allocator);
336 self.attrs.deinit(self.allocator);
337 self.types.deinit(self.allocator);
338 self.dialects.deinit(self.allocator);
339 self.strings.deinit();
340 }
341
342 pub fn internString(self: *TableBuilder, value: []const u8) !u32 {
343 return self.strings.intern(value);
344 }
345
346 pub fn internDialect(self: *TableBuilder, name: []const u8) !u32 {
347 const name_id = try self.internString(name);
348 return self.dialects.intern(self.allocator, name_id);
349 }
350
351 pub fn internType(self: *TableBuilder, typ: ir.Type) !u32 {
352 if (findTypeIndex(self.types.items, typ)) |id| return id;
353 const storage = typ.getDialectStorage() orelse return error.UnsupportedType;
354 const dialect = dialectNamespace(storage.name);
355 if (dialect.len > 0) {
356 _ = try self.internDialect(dialect);
357 const type_name = dialectTypeName(storage.name);
358 _ = try self.internString(type_name);
359 } else {
360 _ = try self.internDialect(storage.name);
361 }
362 if (storage.param_key.len > 0) {
363 _ = try self.internString(storage.param_key);
364 }
365
366 try self.types.append(self.allocator, typ);
367 return @intCast(self.types.items.len - 1);
368 }
369
370 pub fn internAttribute(self: *TableBuilder, attr: ir.Attribute) !u32 {
371 if (findAttrIndex(self.attrs.items, attr)) |id| return id;
372 if (attr.abstract.name.len == 0) return error.UnsupportedAttribute;
373
374 const dialect = dialectNamespace(attr.abstract.name);
375 if (dialect.len > 0) {
376 _ = try self.internDialect(dialect);
377 }
378 const attr_name = dialectTypeName(attr.abstract.name);
379 _ = try self.internString(attr_name);
380
381 const is_typed = std.mem.eql(u8, attr.abstract.name, ir.builtin_attr_names.integer) or
382 std.mem.eql(u8, attr.abstract.name, ir.builtin_attr_names.float_) or
383 std.mem.eql(u8, attr.abstract.name, ir.builtin_attr_names.bool_) or
384 std.mem.eql(u8, attr.abstract.name, ir.builtin_attr_names.string) or
385 std.mem.eql(u8, attr.abstract.name, ir.builtin_attr_names.symbol_ref) or
386 std.mem.eql(u8, attr.abstract.name, ir.builtin_attr_names.string_list) or
387 std.mem.eql(u8, attr.abstract.name, ir.builtin_attr_names.type_list) or
388 std.mem.eql(u8, attr.abstract.name, ir.builtin_attr_names.array);
389 if (!is_typed) {
390 _ = attr.cast(ir.Attribute.DialectAttr) orelse return error.UnsupportedAttribute;
391 }
392 if (std.mem.eql(u8, attr.abstract.name, ir.builtin_attr_names.type_list)) {
393 const type_list_attr = attr.cast(ir.Attribute.TypeListAttr) orelse return error.UnsupportedAttribute;
394 for (type_list_attr.values) |typ| {
395 _ = try self.internType(typ);
396 }
397 }
398 if (std.mem.eql(u8, attr.abstract.name, ir.builtin_attr_names.array)) {
399 const array_attr = attr.cast(ir.Attribute.ArrayAttr) orelse return error.UnsupportedAttribute;
400 for (array_attr.values) |child| {
401 _ = try self.internAttribute(child);
402 }
403 }
404
405 try self.attrs.append(self.allocator, attr);
406 return @intCast(self.attrs.items.len - 1);
407 }
408
409 pub fn internLocation(self: *TableBuilder, loc: ir.Location) !u32 {
410 if (findLocationIndex(self.locs.items, loc)) |id| return id;
411
412 switch (loc) {
413 .unknown => {},
414 .file => |file_loc| {
415 _ = try self.internString(file_loc.filename);
416 },
417 .file_range => |file_range| {
418 _ = try self.internString(file_range.filename);
419 },
420 .name => |name_loc| {
421 _ = try self.internString(name_loc.name);
422 if (name_loc.child) |child| {
423 _ = try self.internLocation(child.*);
424 }
425 },
426 .fused => |fused| {
427 for (fused.locations) |child_loc| {
428 _ = try self.internLocation(child_loc);
429 }
430 },
431 .call_site => |call_site| {
432 _ = try self.internLocation(call_site.callee.*);
433 _ = try self.internLocation(call_site.caller.*);
434 },
435 }
436
437 try self.locs.append(self.allocator, loc);
438 return @intCast(self.locs.items.len - 1);
439 }
440 };
441
442 pub fn encodeTables(allocator: std.mem.Allocator, builder: *TableBuilder) ![]u8 {
443 var sections: std.ArrayList(SectionData) = .empty;
444 defer {
445 for (sections.items) |section| {
446 allocator.free(@constCast(section.data));
447 }
448 sections.deinit(allocator);
449 }
450
451 const strings_data = try encodeStringsSection(allocator, builder);
452 try sections.append(allocator, .{ .kind = @backingInt(SectionKind.strings), .flags = 0, .data = strings_data });
453
454 const dialects_data = try encodeDialectsSection(allocator, builder);
455 try sections.append(allocator, .{ .kind = @backingInt(SectionKind.dialects), .flags = 0, .data = dialects_data });
456
457 const types_data = try encodeTypesSection(allocator, builder);
458 try sections.append(allocator, .{ .kind = @backingInt(SectionKind.types), .flags = 0, .data = types_data });
459
460 const attrs_data = try encodeAttributesSection(allocator, builder);
461 try sections.append(allocator, .{ .kind = @backingInt(SectionKind.attributes), .flags = 0, .data = attrs_data });
462
463 const locs_data = try encodeLocationsSection(allocator, builder);
464 try sections.append(allocator, .{ .kind = @backingInt(SectionKind.locations), .flags = 0, .data = locs_data });
465
466 return try writeContainer(allocator, sections.items);
467 }
468
469 pub fn encodeModule(allocator: std.mem.Allocator, module: *ir.Operation) ![]u8 {
470 return encodeModuleWithResources(allocator, module, &.{});
471 }
472
473 pub fn encodeModuleWithResources(
474 allocator: std.mem.Allocator,
475 module: *ir.Operation,
476 resources: []const Resource,
477 ) ![]u8 {
478 var builder = TableBuilder.init(allocator);
479 defer builder.deinit();
480
481 _ = try builder.internLocation(ir.Location.getUnknown());
482 try collectOperationTables(&builder, module);
483
484 var sections: std.ArrayList(SectionData) = .empty;
485 defer {
486 for (sections.items) |section| {
487 allocator.free(@constCast(section.data));
488 }
489 sections.deinit(allocator);
490 }
491
492 try appendOwnedSection(
493 allocator,
494 §ions,
495 @backingInt(SectionKind.strings),
496 try encodeStringsSection(allocator, &builder),
497 );
498
499 try appendOwnedSection(
500 allocator,
501 §ions,
502 @backingInt(SectionKind.dialects),
503 try encodeDialectsSection(allocator, &builder),
504 );
505
506 try appendOwnedSection(
507 allocator,
508 §ions,
509 @backingInt(SectionKind.types),
510 try encodeTypesSection(allocator, &builder),
511 );
512
513 try appendOwnedSection(
514 allocator,
515 §ions,
516 @backingInt(SectionKind.attributes),
517 try encodeAttributesSection(allocator, &builder),
518 );
519
520 try appendOwnedSection(
521 allocator,
522 §ions,
523 @backingInt(SectionKind.locations),
524 try encodeLocationsSection(allocator, &builder),
525 );
526
527 try appendOwnedSection(
528 allocator,
529 §ions,
530 @backingInt(SectionKind.module),
531 try encodeModuleSection(allocator, &builder, module),
532 );
533
534 if (resources.len != 0) {
535 try appendOwnedSection(
536 allocator,
537 §ions,
538 @backingInt(SectionKind.resources),
539 try encodeResourcesSection(allocator, resources),
540 );
541 }
542
543 return try writeContainer(allocator, sections.items);
544 }
545
546 fn appendOwnedSection(
547 allocator: std.mem.Allocator,
548 sections: *std.ArrayList(SectionData),
549 kind: u32,
550 data: []const u8,
551 ) !void {
552 errdefer allocator.free(@constCast(data));
553 try sections.append(allocator, .{ .kind = kind, .flags = 0, .data = data });
554 }
555
556 pub fn decodeTables(allocator: std.mem.Allocator, ctx: *ir.Context, bytes: []const u8) !DecodedTables {
557 const sections = try parseSections(bytes);
558 return decodeTablesFromSections(allocator, ctx, sections);
559 }
560
561 fn decodeTablesFromSections(
562 allocator: std.mem.Allocator,
563 ctx: *ir.Context,
564 sections: ParsedSections,
565 ) !DecodedTables {
566 const strings_bytes = sections.strings orelse return error.MissingStrings;
567 const strings = try decodeStrings(allocator, strings_bytes);
568 errdefer freeStringTable(allocator, strings);
569
570 const dialects = try decodeDialects(allocator, sections.dialects, strings);
571 errdefer allocator.free(dialects);
572
573 const types = try decodeTypes(allocator, ctx, sections.types, strings, dialects);
574 errdefer allocator.free(types);
575
576 const attrs = try decodeAttributes(allocator, ctx, sections.attrs, strings, dialects, types);
577 errdefer allocator.free(attrs);
578
579 var loc_slices: std.ArrayList([]const ir.Location) = .empty;
580 errdefer {
581 for (loc_slices.items) |slice| {
582 allocator.free(@constCast(slice));
583 }
584 loc_slices.deinit(allocator);
585 }
586
587 const locs = try decodeLocations(allocator, sections.locs, strings, &loc_slices);
588 errdefer allocator.free(locs);
589
590 return .{
591 .allocator = allocator,
592 .format = sections.header.compatibility(),
593 .strings = strings,
594 .dialects = dialects,
595 .types = types,
596 .attrs = attrs,
597 .locs = locs,
598 .loc_slices = loc_slices,
599 };
600 }
601
602 pub fn decodeModule(allocator: std.mem.Allocator, ctx: *ir.Context, bytes: []const u8) !DecodedModule {
603 const sections = try parseSections(bytes);
604 const module_bytes = sections.module orelse return error.MissingModule;
605
606 var tables = try decodeTablesFromSections(allocator, ctx, sections);
607 errdefer tables.deinit();
608
609 const resources = try decodeResourcesSection(allocator, sections.resources);
610 errdefer freeResources(allocator, resources);
611
612 const module_op = try decodeModuleSection(allocator, ctx, module_bytes, &tables);
613 return .{
614 .module = module_op,
615 .tables = tables,
616 .resources = resources,
617 };
618 }
619
620 fn headerSize() u64 {
621 return 8 + 4 + 1 + 1 + 2 + 8 + 8 + 8;
622 }
623
624 fn sectionEntrySize() u64 {
625 return 4 + 4 + 8 + 8;
626 }
627
628 fn writeContainer(allocator: std.mem.Allocator, sections: []const SectionData) ![]u8 {
629 const section_count: u64 = @intCast(sections.len);
630 const table_offset = headerSize();
631 const table_size = section_count * sectionEntrySize();
632
633 var metas = try allocator.alloc(SectionMeta, sections.len);
634 defer allocator.free(metas);
635
636 var cursor = table_offset + table_size;
637 for (sections, 0..) |section, idx| {
638 metas[idx] = .{
639 .kind = section.kind,
640 .flags = section.flags,
641 .offset = cursor,
642 .size = @intCast(section.data.len),
643 };
644 cursor += @intCast(section.data.len);
645 }
646
647 var writer = BufferWriter.init(allocator);
648 errdefer writer.deinit();
649
650 try writer.writeAll(magic);
651 try writer.writeU32(format_version);
652 try writer.writeByte(current_endianness);
653 try writer.writeByte(@intCast(@sizeOf(usize)));
654 try writer.writeU16(0);
655 try writer.writeU64(table_offset);
656 try writer.writeU64(section_count);
657 try writer.writeU64(0);
658
659 for (metas) |meta| {
660 try writer.writeU32(meta.kind);
661 try writer.writeU32(meta.flags);
662 try writer.writeU64(meta.offset);
663 try writer.writeU64(meta.size);
664 }
665
666 for (sections) |section| {
667 try writer.writeAll(section.data);
668 }
669
670 return writer.toOwnedSlice();
671 }
672
673 pub fn inspectHeader(bytes: []const u8) !ContainerHeader {
674 var reader = SliceReader.init(bytes);
675 return readHeader(&reader);
676 }
677
678 fn readHeader(reader: *SliceReader) !ContainerHeader {
679 const got_magic = try reader.readBytes(magic.len);
680 if (!std.mem.eql(u8, got_magic, magic)) return error.InvalidHeader;
681
682 return .{
683 .version = try reader.readU32(),
684 .endianness = try reader.readByte(),
685 .ptr_size = try reader.readByte(),
686 .flags = try reader.readU16(),
687 .section_table_offset = try reader.readU64(),
688 .section_count = try reader.readU64(),
689 .file_hash = try reader.readU64(),
690 };
691 }
692
693 pub const ParsedSections = struct {
694 header: ContainerHeader,
695 strings: ?[]const u8,
696 dialects: ?[]const u8,
697 types: ?[]const u8,
698 attrs: ?[]const u8,
699 locs: ?[]const u8,
700 module: ?[]const u8,
701 resources: ?[]const u8,
702 };
703
704 pub fn parseSections(bytes: []const u8) !ParsedSections {
705 var header_reader = SliceReader.init(bytes);
706 const header = try readHeader(&header_reader);
707
708 if (!header.compatibility().readable()) return error.UnsupportedVersion;
709 if (header.endianness != current_endianness) return error.UnsupportedEndianness;
710
711 const bytes_len_u64: u64 = @intCast(bytes.len);
712 const section_table_size = std.math.mul(
713 u64,
714 header.section_count,
715 sectionEntrySize(),
716 ) catch return error.InvalidSectionTable;
717 const section_table_end = std.math.add(
718 u64,
719 header.section_table_offset,
720 section_table_size,
721 ) catch return error.InvalidSectionTable;
722 if (section_table_end > bytes_len_u64) return error.InvalidSectionTable;
723
724 var sections = ParsedSections{
725 .header = header,
726 .strings = null,
727 .dialects = null,
728 .types = null,
729 .attrs = null,
730 .locs = null,
731 .module = null,
732 .resources = null,
733 };
734
735 const table_offset: usize = @intCast(header.section_table_offset);
736 var table_reader = SliceReader.init(bytes[table_offset..]);
737 var i: u64 = 0;
738 while (i < header.section_count) : (i += 1) {
739 const kind_raw = try table_reader.readU32();
740 const flags = try table_reader.readU32();
741 const offset = try table_reader.readU64();
742 const size = try table_reader.readU64();
743 _ = flags;
744
745 const end_u64 = std.math.add(
746 u64,
747 offset,
748 size,
749 ) catch return error.InvalidSection;
750 if (end_u64 > bytes_len_u64) return error.InvalidSection;
751 const start: usize = @intCast(offset);
752 const end: usize = @intCast(end_u64);
753 const slice = bytes[start..end];
754
755 switch (kind_raw) {
756 @backingInt(SectionKind.strings) => {
757 if (sections.strings != null) return error.DuplicateSection;
758 sections.strings = slice;
759 },
760 @backingInt(SectionKind.dialects) => {
761 if (sections.dialects != null) return error.DuplicateSection;
762 sections.dialects = slice;
763 },
764 @backingInt(SectionKind.types) => {
765 if (sections.types != null) return error.DuplicateSection;
766 sections.types = slice;
767 },
768 @backingInt(SectionKind.attributes) => {
769 if (sections.attrs != null) return error.DuplicateSection;
770 sections.attrs = slice;
771 },
772 @backingInt(SectionKind.locations) => {
773 if (sections.locs != null) return error.DuplicateSection;
774 sections.locs = slice;
775 },
776 @backingInt(SectionKind.module) => {
777 if (sections.module != null) return error.DuplicateSection;
778 sections.module = slice;
779 },
780 @backingInt(SectionKind.resources) => {
781 if (sections.resources != null) return error.DuplicateSection;
782 sections.resources = slice;
783 },
784 else => {},
785 }
786 }
787
788 return sections;
789 }
790
791 fn encodeStringsSection(allocator: std.mem.Allocator, builder: *TableBuilder) ![]u8 {
792 var writer = BufferWriter.init(allocator);
793 errdefer writer.deinit();
794
795 try writer.writeULEB(builder.strings.items.items.len);
796 for (builder.strings.items.items) |value| {
797 try writer.writeBytes(value);
798 }
799
800 return writer.toOwnedSlice();
801 }
802
803 fn encodeDialectsSection(allocator: std.mem.Allocator, builder: *TableBuilder) ![]u8 {
804 var writer = BufferWriter.init(allocator);
805 errdefer writer.deinit();
806
807 try writer.writeULEB(builder.dialects.items.items.len);
808 for (builder.dialects.items.items) |dialect| {
809 try writer.writeULEB(dialect.name_id);
810 try writer.writeULEB(dialect.version);
811 try writer.writeULEB(dialect.flags);
812 }
813
814 return writer.toOwnedSlice();
815 }
816
817 fn encodeTypesSection(allocator: std.mem.Allocator, builder: *TableBuilder) ![]u8 {
818 var writer = BufferWriter.init(allocator);
819 errdefer writer.deinit();
820
821 try writer.writeULEB(builder.types.items.len);
822 for (builder.types.items) |typ| {
823 try encodeType(builder, &writer, typ);
824 }
825
826 return writer.toOwnedSlice();
827 }
828
829 fn encodeAttributesSection(allocator: std.mem.Allocator, builder: *TableBuilder) ![]u8 {
830 var writer = BufferWriter.init(allocator);
831 errdefer writer.deinit();
832
833 try writer.writeULEB(builder.attrs.items.len);
834 for (builder.attrs.items) |attr| {
835 try encodeAttribute(builder, &writer, attr);
836 }
837
838 return writer.toOwnedSlice();
839 }
840
841 fn encodeLocationsSection(allocator: std.mem.Allocator, builder: *TableBuilder) ![]u8 {
842 var writer = BufferWriter.init(allocator);
843 errdefer writer.deinit();
844
845 try writer.writeULEB(builder.locs.items.len);
846 for (builder.locs.items) |loc| {
847 try encodeLocation(builder, &writer, loc);
848 }
849
850 return writer.toOwnedSlice();
851 }
852
853 fn encodeResourcesSection(
854 allocator: std.mem.Allocator,
855 resources: []const Resource,
856 ) ![]u8 {
857 var writer = BufferWriter.init(allocator);
858 errdefer writer.deinit();
859
860 try writer.writeULEB(resources.len);
861 for (resources) |resource| {
862 try writer.writeBytes(resource.namespace);
863 try writer.writeBytes(resource.name);
864 try writer.writeBytes(resource.type_id);
865 try writer.writeBytes(resource.data);
866 }
867
868 return writer.toOwnedSlice();
869 }
870
871 fn decodeResourcesSection(
872 allocator: std.mem.Allocator,
873 bytes_opt: ?[]const u8,
874 ) ![]const Resource {
875 const bytes = bytes_opt orelse return &.{};
876
877 var reader = SliceReader.init(bytes);
878 const count = try reader.readULEBToUsize();
879 if (count == 0) {
880 if (reader.offset != bytes.len) return error.InvalidSection;
881 return &.{};
882 }
883
884 const resources = try allocator.alloc(Resource, count);
885 var initialized: usize = 0;
886 errdefer {
887 for (resources[0..initialized]) |resource| {
888 allocator.free(@constCast(resource.namespace));
889 allocator.free(@constCast(resource.name));
890 allocator.free(@constCast(resource.type_id));
891 allocator.free(@constCast(resource.data));
892 }
893 if (resources.len != 0) allocator.free(resources);
894 }
895
896 while (initialized < count) : (initialized += 1) {
897 const namespace = try reader.readBytesWithLen();
898 const name = try reader.readBytesWithLen();
899 const type_id = try reader.readBytesWithLen();
900 const data = try reader.readBytesWithLen();
901 const namespace_owned = try allocator.dupe(u8, namespace);
902 errdefer allocator.free(namespace_owned);
903 const name_owned = try allocator.dupe(u8, name);
904 errdefer allocator.free(name_owned);
905 const type_id_owned = try allocator.dupe(u8, type_id);
906 errdefer allocator.free(type_id_owned);
907 const data_owned = try allocator.dupe(u8, data);
908 errdefer allocator.free(data_owned);
909 resources[initialized] = .{
910 .namespace = namespace_owned,
911 .name = name_owned,
912 .type_id = type_id_owned,
913 .data = data_owned,
914 };
915 }
916 if (reader.offset != bytes.len) return error.InvalidSection;
917
918 return resources;
919 }
920
921 fn freeResources(allocator: std.mem.Allocator, resources: []const Resource) void {
922 for (resources) |resource| {
923 allocator.free(@constCast(resource.namespace));
924 allocator.free(@constCast(resource.name));
925 allocator.free(@constCast(resource.type_id));
926 allocator.free(@constCast(resource.data));
927 }
928 if (resources.len != 0) allocator.free(@constCast(resources));
929 }
930
931 fn decodeStrings(allocator: std.mem.Allocator, bytes: []const u8) ![]const []const u8 {
932 var reader = SliceReader.init(bytes);
933 const count = try reader.readULEBToUsize();
934
935 const strings = try allocator.alloc([]const u8, count);
936 var initialized: usize = 0;
937 errdefer {
938 for (strings[0..initialized]) |value| allocator.free(value);
939 allocator.free(strings);
940 }
941 for (strings) |*slot| {
942 const slice = try reader.readBytesWithLen();
943 const owned = try allocator.dupe(u8, slice);
944 slot.* = owned;
945 initialized += 1;
946 }
947
948 return strings;
949 }
950
951 fn decodeDialects(
952 allocator: std.mem.Allocator,
953 bytes_opt: ?[]const u8,
954 strings: []const []const u8,
955 ) ![]const DialectEntry {
956 if (bytes_opt == null) return allocator.alloc(DialectEntry, 0);
957
958 var reader = SliceReader.init(bytes_opt.?);
959 const count = try reader.readULEBToUsize();
960 const dialects = try allocator.alloc(DialectEntry, count);
961 errdefer allocator.free(dialects);
962 for (dialects) |*slot| {
963 const name_id = try reader.readULEBToUsize();
964 const version = try reader.readULEBToU32();
965 const flags = try reader.readULEBToU32();
966 if (name_id >= strings.len) return error.InvalidTable;
967 slot.* = .{
968 .name = strings[name_id],
969 .version = version,
970 .flags = flags,
971 };
972 }
973
974 return dialects;
975 }
976
977 fn decodeTypes(
978 allocator: std.mem.Allocator,
979 ctx: *ir.Context,
980 bytes_opt: ?[]const u8,
981 strings: []const []const u8,
982 dialects: []const DialectEntry,
983 ) ![]const ir.Type {
984 if (bytes_opt == null) return allocator.alloc(ir.Type, 0);
985
986 var reader = SliceReader.init(bytes_opt.?);
987 const count = try reader.readULEBToUsize();
988 const types = try allocator.alloc(ir.Type, count);
989 errdefer allocator.free(types);
990 for (types) |*slot| {
991 slot.* = try decodeType(ctx, &reader, strings, dialects, types);
992 }
993
994 return types;
995 }
996
997 fn decodeAttributes(
998 allocator: std.mem.Allocator,
999 ctx: *ir.Context,
1000 bytes_opt: ?[]const u8,
1001 strings: []const []const u8,
1002 dialects: []const DialectEntry,
1003 types: []const ir.Type,
1004 ) ![]const ir.Attribute {
1005 if (bytes_opt == null) return allocator.alloc(ir.Attribute, 0);
1006
1007 var reader = SliceReader.init(bytes_opt.?);
1008 const count = try reader.readULEBToUsize();
1009 const attrs = try allocator.alloc(ir.Attribute, count);
1010 errdefer allocator.free(attrs);
1011 for (attrs, 0..) |*slot, index| {
1012 slot.* = try decodeAttribute(allocator, ctx, &reader, strings, dialects, types, attrs[0..index]);
1013 }
1014
1015 return attrs;
1016 }
1017
1018 fn decodeLocations(
1019 allocator: std.mem.Allocator,
1020 bytes_opt: ?[]const u8,
1021 strings: []const []const u8,
1022 loc_slices: *std.ArrayList([]const ir.Location),
1023 ) ![]const ir.Location {
1024 if (bytes_opt == null) return allocator.alloc(ir.Location, 0);
1025
1026 var reader = SliceReader.init(bytes_opt.?);
1027 const count = try reader.readULEBToUsize();
1028 const locs = try allocator.alloc(ir.Location, count);
1029 errdefer allocator.free(locs);
1030
1031 var idx: usize = 0;
1032 while (idx < count) : (idx += 1) {
1033 locs[idx] = try decodeLocation(allocator, &reader, strings, locs, loc_slices);
1034 }
1035
1036 return locs;
1037 }
1038
1039 fn freeStringTable(allocator: std.mem.Allocator, strings: []const []const u8) void {
1040 for (strings) |value| {
1041 allocator.free(@constCast(value));
1042 }
1043 allocator.free(strings);
1044 }
1045
1046 fn decodeType(
1047 ctx: *ir.Context,
1048 reader: *SliceReader,
1049 strings: []const []const u8,
1050 dialects: []const DialectEntry,
1051 decoded: []const ir.Type,
1052 ) !ir.Type {
1053 _ = decoded;
1054 const kind = try reader.readByte();
1055 switch (kind) {
1056 @backingInt(TypeKind.builtin_scalar) => {
1057 const scalar_kind = std.enums.fromInt(BuiltinScalarKind, try reader.readByte()) orelse
1058 return error.UnsupportedType;
1059 const width = try reader.readByte();
1060 const name = builtinScalarName(scalar_kind, width) orelse return error.UnsupportedType;
1061 return ctx.getDialectTypeFromName(name);
1062 },
1063 @backingInt(TypeKind.dialect) => {
1064 const dialect_id = try reader.readULEBToUsize();
1065 const type_name_id = try reader.readULEBToUsize();
1066 const has_key = (try reader.readByte()) != 0;
1067 const key_id = if (has_key) try reader.readULEBToUsize() else 0;
1068 if (dialect_id >= dialects.len) return error.InvalidTable;
1069 if (type_name_id >= strings.len) return error.InvalidTable;
1070 if (has_key and key_id >= strings.len) return error.InvalidTable;
1071
1072 const dialect = dialects[dialect_id].name;
1073 const type_name = strings[type_name_id];
1074 const param_key = if (has_key) strings[key_id] else "";
1075 const allocator = ir.context.transientAllocator(ctx);
1076 const full_name = try std.fmt.allocPrint(allocator, "{s}.{s}", .{ dialect, type_name });
1077 defer allocator.free(full_name);
1078 return ctx.getDialectTypeFromNameWithKey(full_name, param_key);
1079 },
1080 @backingInt(TypeKind.dialect_only) => {
1081 const dialect_id = try reader.readULEBToUsize();
1082 const has_key = (try reader.readByte()) != 0;
1083 const key_id = if (has_key) try reader.readULEBToUsize() else 0;
1084 if (dialect_id >= dialects.len) return error.InvalidTable;
1085 if (has_key and key_id >= strings.len) return error.InvalidTable;
1086
1087 const dialect = dialects[dialect_id].name;
1088 const param_key = if (has_key) strings[key_id] else "";
1089 return ctx.getDialectTypeFromNameWithKey(dialect, param_key);
1090 },
1091 else => return error.UnsupportedType,
1092 }
1093 }
1094
1095 fn decodeAttribute(
1096 allocator: std.mem.Allocator,
1097 ctx: *ir.Context,
1098 reader: *SliceReader,
1099 strings: []const []const u8,
1100 dialects: []const DialectEntry,
1101 types: []const ir.Type,
1102 decoded: []const ir.Attribute,
1103 ) !ir.Attribute {
1104 const kind = try reader.readByte();
1105 switch (kind) {
1106 @backingInt(AttrKind.integer) => {
1107 const value = try reader.readSLEB();
1108 const width = try reader.readByte();
1109 const is_signed = (try reader.readByte()) != 0;
1110 return ctx.getIntegerAttr(value, width, is_signed);
1111 },
1112 @backingInt(AttrKind.float_) => {
1113 const bits = try reader.readU64();
1114 const width = try reader.readByte();
1115 return ctx.getFloatAttr(@bitCast(bits), width);
1116 },
1117 @backingInt(AttrKind.bool_) => {
1118 const value = (try reader.readByte()) != 0;
1119 return ctx.getBoolAttr(value);
1120 },
1121 @backingInt(AttrKind.string) => {
1122 const value = try reader.readBytesWithLen();
1123 return ctx.getStringAttr(value);
1124 },
1125 @backingInt(AttrKind.symbol_ref) => {
1126 const root_reference = try reader.readBytesWithLen();
1127 const count = try reader.readULEBToUsize();
1128 const nested_references = try allocator.alloc([]const u8, count);
1129 defer allocator.free(nested_references);
1130 for (nested_references) |*slot| {
1131 slot.* = try reader.readBytesWithLen();
1132 }
1133 return ctx.getSymbolRefAttr(root_reference, nested_references);
1134 },
1135 @backingInt(AttrKind.string_list) => {
1136 const count = try reader.readULEBToUsize();
1137 const values = try allocator.alloc([]const u8, count);
1138 defer allocator.free(values);
1139 for (values) |*slot| {
1140 slot.* = try reader.readBytesWithLen();
1141 }
1142 return ctx.getStringListAttr(values);
1143 },
1144 @backingInt(AttrKind.type_list) => {
1145 const count = try reader.readULEBToUsize();
1146 const values = try allocator.alloc(ir.Type, count);
1147 defer allocator.free(values);
1148 for (values) |*slot| {
1149 const type_id = try reader.readULEBToUsize();
1150 if (type_id >= types.len) return error.InvalidTable;
1151 slot.* = types[type_id];
1152 }
1153 return ctx.getTypeListAttr(values);
1154 },
1155 @backingInt(AttrKind.array) => {
1156 const count = try reader.readULEBToUsize();
1157 const values = try allocator.alloc(ir.Attribute, count);
1158 defer allocator.free(values);
1159 for (values) |*slot| {
1160 const attr_id = try reader.readULEBToUsize();
1161 if (attr_id >= decoded.len) return error.InvalidTable;
1162 slot.* = decoded[attr_id];
1163 }
1164 return ctx.getArrayAttr(values);
1165 },
1166 @backingInt(AttrKind.dialect) => {
1167 const dialect_id = try reader.readULEBToUsize();
1168 const name_id = try reader.readULEBToUsize();
1169 const payload = try reader.readBytesWithLen();
1170 if (dialect_id >= dialects.len) return error.InvalidTable;
1171 if (name_id >= strings.len) return error.InvalidTable;
1172
1173 const dialect = dialects[dialect_id].name;
1174 const attr_name = strings[name_id];
1175 const transient_allocator = ir.context.transientAllocator(ctx);
1176 const full_name = try std.fmt.allocPrint(transient_allocator, "{s}.{s}", .{ dialect, attr_name });
1177 defer transient_allocator.free(full_name);
1178
1179 return ctx.getDialectAttr(full_name, payload);
1180 },
1181 else => return error.UnsupportedAttribute,
1182 }
1183 }
1184
1185 fn decodeLocation(
1186 allocator: std.mem.Allocator,
1187 reader: *SliceReader,
1188 strings: []const []const u8,
1189 locs: []const ir.Location,
1190 loc_slices: *std.ArrayList([]const ir.Location),
1191 ) !ir.Location {
1192 const kind = try reader.readByte();
1193 switch (kind) {
1194 @backingInt(LocationKind.unknown) => return ir.Location.getUnknown(),
1195 @backingInt(LocationKind.file) => {
1196 const name_id = try reader.readULEBToUsize();
1197 const line = try reader.readULEBToU32();
1198 const column = try reader.readULEBToU32();
1199 if (name_id >= strings.len) return error.InvalidTable;
1200 return ir.Location.getFile(strings[name_id], line, column);
1201 },
1202 @backingInt(LocationKind.file_range) => {
1203 const name_id = try reader.readULEBToUsize();
1204 const start_byte = try reader.readULEB();
1205 const start_line = try reader.readULEBToU32();
1206 const start_column = try reader.readULEBToU32();
1207 const end_byte = try reader.readULEB();
1208 const end_line = try reader.readULEBToU32();
1209 const end_column = try reader.readULEBToU32();
1210 if (name_id >= strings.len or start_byte > end_byte) return error.InvalidTable;
1211 return ir.Location.getFileRange(
1212 strings[name_id],
1213 .{ .byte = start_byte, .line = start_line, .column = start_column },
1214 .{ .byte = end_byte, .line = end_line, .column = end_column },
1215 );
1216 },
1217 @backingInt(LocationKind.name) => {
1218 const name_id = try reader.readULEBToUsize();
1219 const has_child = (try reader.readByte()) != 0;
1220 if (name_id >= strings.len) return error.InvalidTable;
1221 if (has_child) {
1222 const child_id = try reader.readULEBToUsize();
1223 if (child_id >= locs.len) return error.InvalidTable;
1224 return ir.Location.getName(strings[name_id], &locs[child_id]);
1225 }
1226 return ir.Location.getName(strings[name_id], null);
1227 },
1228 @backingInt(LocationKind.fused) => {
1229 const count = try reader.readULEBToUsize();
1230 const fused_locs = try allocator.alloc(ir.Location, count);
1231 errdefer allocator.free(fused_locs);
1232 var i: usize = 0;
1233 while (i < count) : (i += 1) {
1234 const loc_id = try reader.readULEBToUsize();
1235 if (loc_id >= locs.len) return error.InvalidTable;
1236 fused_locs[i] = locs[loc_id];
1237 }
1238 try loc_slices.append(allocator, fused_locs);
1239 return .{ .fused = .{ .locations = fused_locs, .metadata = null } };
1240 },
1241 @backingInt(LocationKind.call_site) => {
1242 const callee_id = try reader.readULEBToUsize();
1243 const caller_id = try reader.readULEBToUsize();
1244 if (callee_id >= locs.len or caller_id >= locs.len) return error.InvalidTable;
1245 return .{ .call_site = .{ .callee = &locs[callee_id], .caller = &locs[caller_id] } };
1246 },
1247 else => return error.UnsupportedLocation,
1248 }
1249 }
1250
1251 fn encodeType(builder: *TableBuilder, writer: *BufferWriter, typ: ir.Type) !void {
1252 const storage = typ.getDialectStorage() orelse return error.UnsupportedType;
1253 if (builtinScalarFromName(storage.name)) |builtin| {
1254 if (storage.param_key.len > 0) return error.UnsupportedType;
1255 try writer.writeByte(@backingInt(TypeKind.builtin_scalar));
1256 try writer.writeByte(@backingInt(builtin.kind));
1257 try writer.writeByte(builtin.width);
1258 return;
1259 }
1260 const dialect = dialectNamespace(storage.name);
1261 if (dialect.len == 0) {
1262 try writer.writeByte(@backingInt(TypeKind.dialect_only));
1263 const dialect_id = try builder.internDialect(storage.name);
1264 try writer.writeULEB(dialect_id);
1265 if (storage.param_key.len > 0) {
1266 try writer.writeByte(1);
1267 try writer.writeULEB(try builder.internString(storage.param_key));
1268 } else {
1269 try writer.writeByte(0);
1270 }
1271 return;
1272 }
1273 try writer.writeByte(@backingInt(TypeKind.dialect));
1274 const dialect_id = try builder.internDialect(dialect);
1275 try writer.writeULEB(dialect_id);
1276 const type_name = dialectTypeName(storage.name);
1277 try writer.writeULEB(try builder.internString(type_name));
1278 if (storage.param_key.len > 0) {
1279 try writer.writeByte(1);
1280 try writer.writeULEB(try builder.internString(storage.param_key));
1281 } else {
1282 try writer.writeByte(0);
1283 }
1284 }
1285
1286 fn encodeAttribute(builder: *TableBuilder, writer: *BufferWriter, attr: ir.Attribute) !void {
1287 if (attr.abstract.name.len == 0) return error.UnsupportedAttribute;
1288 const full_name = attr.abstract.name;
1289
1290 if (std.mem.eql(u8, full_name, ir.builtin_attr_names.integer)) {
1291 const int_attr = attr.cast(ir.Attribute.IntegerAttr) orelse return error.UnsupportedAttribute;
1292 try writer.writeByte(@backingInt(AttrKind.integer));
1293 try writer.writeSLEB(int_attr.value);
1294 try writer.writeByte(int_attr.width);
1295 try writer.writeByte(if (int_attr.is_signed) 1 else 0);
1296 return;
1297 } else if (std.mem.eql(u8, full_name, ir.builtin_attr_names.float_)) {
1298 const float_attr = attr.cast(ir.Attribute.FloatAttr) orelse return error.UnsupportedAttribute;
1299 try writer.writeByte(@backingInt(AttrKind.float_));
1300 try writer.writeU64(@bitCast(float_attr.value));
1301 try writer.writeByte(float_attr.width);
1302 return;
1303 } else if (std.mem.eql(u8, full_name, ir.builtin_attr_names.bool_)) {
1304 const bool_attr = attr.cast(ir.Attribute.BoolAttr) orelse return error.UnsupportedAttribute;
1305 try writer.writeByte(@backingInt(AttrKind.bool_));
1306 try writer.writeByte(if (bool_attr.value) 1 else 0);
1307 return;
1308 } else if (std.mem.eql(u8, full_name, ir.builtin_attr_names.string)) {
1309 const str_attr = attr.cast(ir.Attribute.StringAttr) orelse return error.UnsupportedAttribute;
1310 try writer.writeByte(@backingInt(AttrKind.string));
1311 try writer.writeBytes(str_attr.value);
1312 return;
1313 } else if (std.mem.eql(u8, full_name, ir.builtin_attr_names.symbol_ref)) {
1314 const symbol_ref_attr = attr.cast(ir.Attribute.SymbolRefAttr) orelse return error.UnsupportedAttribute;
1315 try writer.writeByte(@backingInt(AttrKind.symbol_ref));
1316 try writer.writeBytes(symbol_ref_attr.root_reference);
1317 try writer.writeULEB(symbol_ref_attr.nested_references.len);
1318 for (symbol_ref_attr.nested_references) |nested| {
1319 try writer.writeBytes(nested);
1320 }
1321 return;
1322 } else if (std.mem.eql(u8, full_name, ir.builtin_attr_names.string_list)) {
1323 const str_list_attr = attr.cast(ir.Attribute.StringListAttr) orelse return error.UnsupportedAttribute;
1324 try writer.writeByte(@backingInt(AttrKind.string_list));
1325 try writer.writeULEB(str_list_attr.values.len);
1326 for (str_list_attr.values) |value| {
1327 try writer.writeBytes(value);
1328 }
1329 return;
1330 } else if (std.mem.eql(u8, full_name, ir.builtin_attr_names.type_list)) {
1331 const type_list_attr = attr.cast(ir.Attribute.TypeListAttr) orelse return error.UnsupportedAttribute;
1332 try writer.writeByte(@backingInt(AttrKind.type_list));
1333 try writer.writeULEB(type_list_attr.values.len);
1334 for (type_list_attr.values) |typ| {
1335 try writer.writeULEB(try lookupTypeId(builder, typ));
1336 }
1337 return;
1338 } else if (std.mem.eql(u8, full_name, ir.builtin_attr_names.array)) {
1339 const array_attr = attr.cast(ir.Attribute.ArrayAttr) orelse return error.UnsupportedAttribute;
1340 try writer.writeByte(@backingInt(AttrKind.array));
1341 try writer.writeULEB(array_attr.values.len);
1342 for (array_attr.values) |child| {
1343 try writer.writeULEB(try lookupAttrId(builder, child));
1344 }
1345 return;
1346 }
1347
1348 const dialect = dialectNamespace(full_name);
1349 const dialect_id = if (dialect.len > 0)
1350 try builder.internDialect(dialect)
1351 else
1352 0;
1353 const attr_name = dialectTypeName(full_name);
1354 const attr_name_id = try builder.internString(attr_name);
1355
1356 const dialect_attr = attr.cast(ir.Attribute.DialectAttr) orelse return error.UnsupportedAttribute;
1357
1358 try writer.writeByte(@backingInt(AttrKind.dialect));
1359 try writer.writeULEB(dialect_id);
1360 try writer.writeULEB(attr_name_id);
1361 try writer.writeBytes(dialect_attr.payload);
1362 }
1363
1364 fn encodeLocation(builder: *TableBuilder, writer: *BufferWriter, loc: ir.Location) !void {
1365 switch (loc) {
1366 .unknown => {
1367 try writer.writeByte(@backingInt(LocationKind.unknown));
1368 },
1369 .file => |file_loc| {
1370 try writer.writeByte(@backingInt(LocationKind.file));
1371 try writer.writeULEB(try builder.internString(file_loc.filename));
1372 try writer.writeULEB(file_loc.line);
1373 try writer.writeULEB(file_loc.column);
1374 },
1375 .file_range => |range| {
1376 try writer.writeByte(@backingInt(LocationKind.file_range));
1377 try writer.writeULEB(try builder.internString(range.filename));
1378 try writer.writeULEB(range.start.byte);
1379 try writer.writeULEB(range.start.line);
1380 try writer.writeULEB(range.start.column);
1381 try writer.writeULEB(range.end.byte);
1382 try writer.writeULEB(range.end.line);
1383 try writer.writeULEB(range.end.column);
1384 },
1385 .name => |name_loc| {
1386 try writer.writeByte(@backingInt(LocationKind.name));
1387 try writer.writeULEB(try builder.internString(name_loc.name));
1388 if (name_loc.child) |child| {
1389 try writer.writeByte(1);
1390 try writer.writeULEB(try builder.internLocation(child.*));
1391 } else {
1392 try writer.writeByte(0);
1393 }
1394 },
1395 .fused => |fused| {
1396 try writer.writeByte(@backingInt(LocationKind.fused));
1397 try writer.writeULEB(fused.locations.len);
1398 for (fused.locations) |child_loc| {
1399 try writer.writeULEB(try builder.internLocation(child_loc));
1400 }
1401 },
1402 .call_site => |call_site| {
1403 try writer.writeByte(@backingInt(LocationKind.call_site));
1404 try writer.writeULEB(try builder.internLocation(call_site.callee.*));
1405 try writer.writeULEB(try builder.internLocation(call_site.caller.*));
1406 },
1407 }
1408 }
1409
1410 fn collectOperationTables(builder: *TableBuilder, op: *ir.Operation) anyerror!void {
1411 _ = try builder.internString(op.name.name);
1412 _ = try builder.internLocation(op.location);
1413
1414 for (op.results.items) |result| {
1415 _ = try builder.internType(result.type);
1416 }
1417 for (op.operands.items) |operand| {
1418 _ = try builder.internType(operand.value.type);
1419 }
1420 for (op.getRawDictionaryAttrs()) |attr| {
1421 _ = try builder.internString(attr.name);
1422 _ = try builder.internAttribute(attr.value);
1423 }
1424 if (try op.getPropertiesAsAttr()) |properties| {
1425 _ = try builder.internAttribute(properties);
1426 }
1427
1428 for (op.regions.items) |*region| {
1429 var blocks = region.getBlocks();
1430 while (blocks.next()) |block| {
1431 for (block.arguments.items, 0..) |arg, index| {
1432 _ = try builder.internType(arg.type);
1433 _ = try builder.internLocation(block.getArgumentLocation(index).?);
1434 }
1435
1436 var current: ?*ir.Operation = if (block.operations.head) |any_ptr|
1437 @ptrCast(@alignCast(any_ptr))
1438 else
1439 null;
1440 while (current) |child| {
1441 try collectOperationTables(builder, child);
1442 current = child.next_op;
1443 }
1444 }
1445 }
1446 }
1447
1448 fn lookupStringId(builder: *TableBuilder, value: []const u8) !u32 {
1449 return builder.strings.map.get(value) orelse error.MissingTableEntry;
1450 }
1451
1452 fn lookupTypeId(builder: *TableBuilder, typ: ir.Type) !u32 {
1453 return findTypeIndex(builder.types.items, typ) orelse error.MissingTableEntry;
1454 }
1455
1456 fn lookupAttrId(builder: *TableBuilder, attr: ir.Attribute) !u32 {
1457 return findAttrIndex(builder.attrs.items, attr) orelse error.MissingTableEntry;
1458 }
1459
1460 fn lookupLocationId(builder: *TableBuilder, loc: ir.Location) !u32 {
1461 return findLocationIndex(builder.locs.items, loc) orelse error.MissingTableEntry;
1462 }
1463
1464 const ModuleEncoder = struct {
1465 allocator: std.mem.Allocator,
1466 builder: *TableBuilder,
1467 value_ids: std.AutoHashMap(*ir.Value, u32),
1468 next_value_id: u32,
1469
1470 pub fn init(allocator: std.mem.Allocator, builder: *TableBuilder) !ModuleEncoder {
1471 return .{
1472 .allocator = allocator,
1473 .builder = builder,
1474 .value_ids = std.AutoHashMap(*ir.Value, u32).init(allocator),
1475 .next_value_id = 0,
1476 };
1477 }
1478
1479 pub fn deinit(self: *ModuleEncoder) void {
1480 self.value_ids.deinit();
1481 }
1482
1483 pub fn defineValue(self: *ModuleEncoder, value: *ir.Value) !u32 {
1484 if (self.value_ids.get(value)) |id| return id;
1485 const id = self.next_value_id;
1486 self.next_value_id += 1;
1487 try self.value_ids.put(value, id);
1488 return id;
1489 }
1490
1491 pub fn getValueId(self: *ModuleEncoder, value: *ir.Value) !u32 {
1492 return self.value_ids.get(value) orelse error.InvalidModule;
1493 }
1494 };
1495
1496 fn encodeModuleSection(
1497 allocator: std.mem.Allocator,
1498 builder: *TableBuilder,
1499 module: *ir.Operation,
1500 ) ![]u8 {
1501 var encoder = try ModuleEncoder.init(allocator, builder);
1502 defer encoder.deinit();
1503
1504 var writer = BufferWriter.init(allocator);
1505 errdefer writer.deinit();
1506
1507 try encodeOperation(&encoder, &writer, module, null);
1508
1509 return writer.toOwnedSlice();
1510 }
1511
1512 fn indexRegionBlocks(
1513 encoder: *ModuleEncoder,
1514 region: *ir.Region,
1515 blocks: *std.ArrayList(*ir.Block),
1516 block_ids: *std.AutoHashMap(*ir.Block, u32),
1517 ) anyerror!void {
1518 var iter = region.getBlocks();
1519 while (iter.next()) |block| {
1520 const id: u32 = @intCast(blocks.items.len);
1521 try blocks.append(encoder.allocator, block);
1522 try block_ids.put(block, id);
1523 }
1524 }
1525
1526 fn encodeBlockArguments(encoder: *ModuleEncoder, writer: *BufferWriter, block: *ir.Block) anyerror!void {
1527 try writer.writeULEB(block.arguments.items.len);
1528 for (block.arguments.items, 0..) |arg, index| {
1529 try writer.writeULEB(try lookupTypeId(encoder.builder, arg.type));
1530 try writer.writeULEB(try lookupLocationId(
1531 encoder.builder,
1532 block.getArgumentLocation(index).?,
1533 ));
1534 _ = try encoder.defineValue(arg);
1535 }
1536 }
1537
1538 fn countBlockOps(block: *ir.Block) usize {
1539 var count: usize = 0;
1540 var current: ?*ir.Operation = if (block.operations.head) |any_ptr|
1541 @ptrCast(@alignCast(any_ptr))
1542 else
1543 null;
1544 while (current) |op| {
1545 count += 1;
1546 current = op.next_op;
1547 }
1548 return count;
1549 }
1550
1551 fn encodeOperation(
1552 encoder: *ModuleEncoder,
1553 writer: *BufferWriter,
1554 op: *ir.Operation,
1555 block_ids: ?*const std.AutoHashMap(*ir.Block, u32),
1556 ) anyerror!void {
1557 const name_id = try lookupStringId(encoder.builder, op.name.name);
1558 const loc_id = try lookupLocationId(encoder.builder, op.location);
1559
1560 try writer.writeULEB(name_id);
1561 try writer.writeULEB(loc_id);
1562
1563 try writer.writeULEB(op.results.items.len);
1564 for (op.results.items) |result| {
1565 try writer.writeULEB(try lookupTypeId(encoder.builder, result.type));
1566 }
1567
1568 try writer.writeULEB(op.operands.items.len);
1569 for (op.operands.items) |operand| {
1570 const value_id = try encoder.getValueId(operand.value);
1571 try writer.writeULEB(value_id);
1572 }
1573
1574 const attrs = op.getRawDictionaryAttrs();
1575 try writer.writeULEB(attrs.len);
1576 for (attrs) |attr| {
1577 const attr_name_id = try lookupStringId(encoder.builder, attr.name);
1578 const attr_id = try lookupAttrId(encoder.builder, attr.value);
1579 try writer.writeULEB(attr_name_id);
1580 try writer.writeULEB(attr_id);
1581 }
1582
1583 if (try op.getPropertiesAsAttr()) |properties| {
1584 try writer.writeByte(1);
1585 try writer.writeULEB(try lookupAttrId(encoder.builder, properties));
1586 } else {
1587 try writer.writeByte(0);
1588 }
1589
1590 try writer.writeULEB(op.successors.items.len);
1591 if (op.successors.items.len > 0) {
1592 const map = block_ids orelse return error.InvalidModule;
1593 for (op.successors.items) |succ| {
1594 const block_id = map.get(succ) orelse return error.InvalidModule;
1595 try writer.writeULEB(block_id);
1596 }
1597 }
1598
1599 try writer.writeULEB(op.regions.items.len);
1600
1601 for (op.results.items) |*result| {
1602 _ = try encoder.defineValue(result);
1603 }
1604
1605 for (op.regions.items) |*region| {
1606 var region_blocks: std.ArrayList(*ir.Block) = .empty;
1607 defer region_blocks.deinit(encoder.allocator);
1608
1609 var region_block_ids = std.AutoHashMap(*ir.Block, u32).init(encoder.allocator);
1610 defer region_block_ids.deinit();
1611
1612 try indexRegionBlocks(encoder, region, ®ion_blocks, ®ion_block_ids);
1613 try writer.writeULEB(region_blocks.items.len);
1614
1615 for (region_blocks.items) |block| {
1616 try encodeBlockArguments(encoder, writer, block);
1617
1618 const op_count = countBlockOps(block);
1619 try writer.writeULEB(op_count);
1620
1621 var current: ?*ir.Operation = if (block.operations.head) |any_ptr|
1622 @ptrCast(@alignCast(any_ptr))
1623 else
1624 null;
1625 while (current) |child| {
1626 try encodeOperation(encoder, writer, child, ®ion_block_ids);
1627 current = child.next_op;
1628 }
1629 }
1630 }
1631 }
1632
1633 const ModuleDecoder = struct {
1634 allocator: std.mem.Allocator,
1635 ctx: *ir.Context,
1636 strings: []const []const u8,
1637 types: []const ir.Type,
1638 attrs: []const ir.Attribute,
1639 locs: []const ir.Location,
1640 values: std.ArrayList(*ir.Value),
1641 value_base: usize,
1642
1643 pub fn init(allocator: std.mem.Allocator, ctx: *ir.Context, tables: *DecodedTables) ModuleDecoder {
1644 return .{
1645 .allocator = allocator,
1646 .ctx = ctx,
1647 .strings = tables.strings,
1648 .types = tables.types,
1649 .attrs = tables.attrs,
1650 .locs = tables.locs,
1651 .values = .empty,
1652 .value_base = 0,
1653 };
1654 }
1655
1656 pub fn deinit(self: *ModuleDecoder) void {
1657 self.values.deinit(self.allocator);
1658 }
1659 };
1660
1661 const FlatModuleEnvelope = struct {
1662 name: []const u8,
1663 location: ir.Location,
1664 operation_count: usize,
1665 };
1666
1667 fn decodeFlatModuleEnvelope(
1668 decoder: *ModuleDecoder,
1669 reader: *SliceReader,
1670 ) !FlatModuleEnvelope {
1671 const name_id = try reader.readULEBToUsize();
1672 const loc_id = try reader.readULEBToUsize();
1673 if (name_id >= decoder.strings.len or loc_id >= decoder.locs.len) {
1674 return error.InvalidTable;
1675 }
1676 if (try reader.readULEBToUsize() != 0) return error.InvalidModule;
1677 if (try reader.readULEBToUsize() != 0) return error.InvalidModule;
1678
1679 const attr_count = try reader.readULEBToUsize();
1680 var attr_index: usize = 0;
1681 while (attr_index < attr_count) : (attr_index += 1) {
1682 const attr_name_id = try reader.readULEBToUsize();
1683 const attr_id = try reader.readULEBToUsize();
1684 if (attr_name_id >= decoder.strings.len or
1685 attr_id >= decoder.attrs.len)
1686 {
1687 return error.InvalidTable;
1688 }
1689 }
1690
1691 const has_properties = try reader.readByte();
1692 if (has_properties > 1) return error.InvalidModule;
1693 if (has_properties == 1) {
1694 const property_id = try reader.readULEBToUsize();
1695 if (property_id >= decoder.attrs.len) return error.InvalidTable;
1696 }
1697 if (try reader.readULEBToUsize() != 0) return error.InvalidModule;
1698 if (try reader.readULEBToUsize() != 1) return error.InvalidModule;
1699 if (try reader.readULEBToUsize() != 1) return error.InvalidModule;
1700 if (try reader.readULEBToUsize() != 0) return error.InvalidModule;
1701
1702 return .{
1703 .name = decoder.strings[name_id],
1704 .location = decoder.locs[loc_id],
1705 .operation_count = try reader.readULEBToUsize(),
1706 };
1707 }
1708
1709 fn decodeModuleSection(
1710 allocator: std.mem.Allocator,
1711 ctx: *ir.Context,
1712 bytes: []const u8,
1713 tables: *DecodedTables,
1714 ) !*ir.Operation {
1715 var decoder = ModuleDecoder.init(allocator, ctx, tables);
1716 defer decoder.deinit();
1717
1718 var reader = SliceReader.init(bytes);
1719 const module_op = try decodeOperation(&decoder, &reader, null, null);
1720 errdefer module_op.erase();
1721 if (reader.offset != bytes.len) return error.InvalidModule;
1722 return module_op;
1723 }
1724
1725 fn createRegionBlocks(decoder: *ModuleDecoder, reader: *SliceReader, region: *ir.Region) anyerror!std.ArrayList(*ir.Block) {
1726 const block_count = try reader.readULEBToUsize();
1727
1728 var blocks: std.ArrayList(*ir.Block) = .empty;
1729 errdefer blocks.deinit(decoder.allocator);
1730
1731 var i: usize = 0;
1732 while (i < block_count) : (i += 1) {
1733 const block = try region.addBlock();
1734 try blocks.append(decoder.allocator, block);
1735 }
1736
1737 return blocks;
1738 }
1739
1740 fn decodeBlockArguments(decoder: *ModuleDecoder, reader: *SliceReader, block: *ir.Block) anyerror!void {
1741 const arg_count = try reader.readULEBToUsize();
1742 if (arg_count > 0) {
1743 try block.arguments.ensureTotalCapacity(block.allocator, arg_count);
1744 }
1745 const arg_start = block.arguments.items.len;
1746 var arg_idx: usize = 0;
1747 while (arg_idx < arg_count) : (arg_idx += 1) {
1748 const type_id = try reader.readULEBToUsize();
1749 const loc_id = try reader.readULEBToUsize();
1750 if (type_id >= decoder.types.len or loc_id >= decoder.locs.len) return error.InvalidTable;
1751 _ = try block.addArgument(decoder.types[type_id], decoder.locs[loc_id]);
1752 }
1753 for (block.arguments.items[arg_start..]) |arg| {
1754 try decoder.values.append(decoder.allocator, arg);
1755 }
1756 }
1757
1758 fn decodeOperation(
1759 decoder: *ModuleDecoder,
1760 reader: *SliceReader,
1761 blocks: ?[]const *ir.Block,
1762 parent_block: ?*ir.Block,
1763 ) anyerror!*ir.Operation {
1764 const name_id = try reader.readULEBToUsize();
1765 const loc_id = try reader.readULEBToUsize();
1766 if (name_id >= decoder.strings.len or loc_id >= decoder.locs.len) return error.InvalidTable;
1767
1768 const result_count = try reader.readULEBToUsize();
1769 var result_types: []ir.Type = &.{};
1770 if (result_count > 0) {
1771 result_types = try reader.readTypeList(decoder.allocator, result_count, decoder.types);
1772 }
1773 defer if (result_types.len > 0) decoder.allocator.free(result_types);
1774
1775 const operand_count = try reader.readULEBToUsize();
1776 var operands: []*ir.Value = &.{};
1777 defer if (operands.len > 0) decoder.allocator.free(operands);
1778 if (operand_count > 0) {
1779 operands = try decoder.allocator.alloc(*ir.Value, operand_count);
1780 var op_idx: usize = 0;
1781 while (op_idx < operand_count) : (op_idx += 1) {
1782 const value_id = try reader.readULEBToUsize();
1783 if (value_id < decoder.value_base) return error.InvalidModule;
1784 const local_value_id = value_id - decoder.value_base;
1785 if (local_value_id >= decoder.values.items.len) {
1786 return error.InvalidModule;
1787 }
1788 operands[op_idx] = decoder.values.items[local_value_id];
1789 }
1790 }
1791 const attr_count = try reader.readULEBToUsize();
1792 var attrs: []ir.NamedAttribute = &.{};
1793 defer if (attrs.len > 0) decoder.allocator.free(attrs);
1794 if (attr_count > 0) {
1795 attrs = try decoder.allocator.alloc(ir.NamedAttribute, attr_count);
1796 var attr_idx: usize = 0;
1797 while (attr_idx < attr_count) : (attr_idx += 1) {
1798 const attr_name_id = try reader.readULEBToUsize();
1799 const attr_id = try reader.readULEBToUsize();
1800 if (attr_name_id >= decoder.strings.len or attr_id >= decoder.attrs.len) return error.InvalidTable;
1801 attrs[attr_idx] = .{
1802 .name = decoder.strings[attr_name_id],
1803 .value = decoder.attrs[attr_id],
1804 };
1805 }
1806 }
1807 const has_properties = try reader.readByte();
1808 if (has_properties > 1) return error.InvalidModule;
1809 const properties = if (has_properties == 1) blk: {
1810 const property_id = try reader.readULEBToUsize();
1811 if (property_id >= decoder.attrs.len) return error.InvalidTable;
1812 break :blk decoder.attrs[property_id];
1813 } else null;
1814
1815 const succ_count = try reader.readULEBToUsize();
1816 var successors: []*ir.Block = &.{};
1817 defer if (successors.len > 0) decoder.allocator.free(successors);
1818 if (succ_count > 0) {
1819 const block_list = blocks orelse return error.InvalidModule;
1820 successors = try decoder.allocator.alloc(*ir.Block, succ_count);
1821 var succ_idx: usize = 0;
1822 while (succ_idx < succ_count) : (succ_idx += 1) {
1823 const block_id = try reader.readULEBToUsize();
1824 if (block_id >= block_list.len) return error.InvalidModule;
1825 successors[succ_idx] = block_list[block_id];
1826 }
1827 }
1828 const region_count = try reader.readULEBToUsize();
1829
1830 var state = ir.Operation.State.init(decoder.strings[name_id], decoder.locs[loc_id]);
1831 if (operand_count > 0) state.addOperands(operands);
1832 if (result_count > 0) state.addTypes(result_types);
1833 if (attr_count > 0) state.addRawAttributes(attrs);
1834 if (properties) |payload| try state.setPropertiesAttr(payload);
1835 if (succ_count > 0) state.addSuccessors(successors);
1836 var region_idx: usize = 0;
1837 while (region_idx < region_count) : (region_idx += 1) {
1838 state.addRegion();
1839 }
1840
1841 const op = try decoder.ctx.createOperation(state);
1842 errdefer op.erase();
1843 if (parent_block) |block| {
1844 try block.addOperation(op);
1845 }
1846
1847 for (op.results.items) |*result| {
1848 try decoder.values.append(decoder.allocator, result);
1849 }
1850
1851 region_idx = 0;
1852 while (region_idx < region_count) : (region_idx += 1) {
1853 const region = op.getRegion(region_idx) orelse return error.InvalidModule;
1854 var region_blocks = try createRegionBlocks(decoder, reader, region);
1855 defer region_blocks.deinit(decoder.allocator);
1856
1857 for (region_blocks.items) |block| {
1858 try decodeBlockArguments(decoder, reader, block);
1859
1860 const op_count = try reader.readULEBToUsize();
1861 var op_idx: usize = 0;
1862 while (op_idx < op_count) : (op_idx += 1) {
1863 _ = try decodeOperation(decoder, reader, region_blocks.items, block);
1864 }
1865 }
1866 }
1867
1868 return op;
1869 }
1870
1871 pub const TypeKind = enum(u8) {
1872 dialect = 0,
1873 builtin_scalar = 1,
1874 dialect_only = 2,
1875 };
1876
1877 pub const BuiltinScalarKind = enum(u8) {
1878 index = 0,
1879 bool_ = 1,
1880 integer = 2,
1881 float_ = 3,
1882 };
1883
1884 fn builtinScalarFromName(name: []const u8) ?struct { kind: BuiltinScalarKind, width: u8 } {
1885 if (std.mem.eql(u8, name, "arith.index")) return .{ .kind = .index, .width = 0 };
1886 if (std.mem.eql(u8, name, "arith.bool")) return .{ .kind = .bool_, .width = 1 };
1887 if (std.mem.eql(u8, name, "arith.i8")) return .{ .kind = .integer, .width = 8 };
1888 if (std.mem.eql(u8, name, "arith.i16")) return .{ .kind = .integer, .width = 16 };
1889 if (std.mem.eql(u8, name, "arith.i32")) return .{ .kind = .integer, .width = 32 };
1890 if (std.mem.eql(u8, name, "arith.i64")) return .{ .kind = .integer, .width = 64 };
1891 if (std.mem.eql(u8, name, "arith.f16")) return .{ .kind = .float_, .width = 16 };
1892 if (std.mem.eql(u8, name, "arith.f32")) return .{ .kind = .float_, .width = 32 };
1893 if (std.mem.eql(u8, name, "arith.f64")) return .{ .kind = .float_, .width = 64 };
1894 return null;
1895 }
1896
1897 fn builtinScalarName(kind: BuiltinScalarKind, width: u8) ?[]const u8 {
1898 return switch (kind) {
1899 .index => "arith.index",
1900 .bool_ => "arith.bool",
1901 .integer => switch (width) {
1902 8 => "arith.i8",
1903 16 => "arith.i16",
1904 32 => "arith.i32",
1905 64 => "arith.i64",
1906 else => null,
1907 },
1908 .float_ => switch (width) {
1909 16 => "arith.f16",
1910 32 => "arith.f32",
1911 64 => "arith.f64",
1912 else => null,
1913 },
1914 };
1915 }
1916
1917 pub const AttrKind = enum(u8) {
1918 dialect = 0,
1919 integer = 1,
1920 float_ = 2,
1921 bool_ = 3,
1922 string = 4,
1923 type_list = 5,
1924 string_list = 6,
1925 symbol_ref = 7,
1926 array = 8,
1927 };
1928
1929 pub const LocationKind = enum(u8) {
1930 unknown = 0,
1931 file = 1,
1932 name = 2,
1933 fused = 3,
1934 call_site = 4,
1935 file_range = 5,
1936 };
1937
1938 fn findTypeIndex(list: []const ir.Type, typ: ir.Type) ?u32 {
1939 for (list, 0..) |item, idx| {
1940 if (item.eql(typ)) return @intCast(idx);
1941 }
1942 return null;
1943 }
1944
1945 fn findAttrIndex(list: []const ir.Attribute, attr: ir.Attribute) ?u32 {
1946 for (list, 0..) |item, idx| {
1947 if (attrEqual(item, attr)) return @intCast(idx);
1948 }
1949 return null;
1950 }
1951
1952 fn findLocationIndex(list: []const ir.Location, loc: ir.Location) ?u32 {
1953 for (list, 0..) |item, idx| {
1954 if (locationEqual(item, loc)) return @intCast(idx);
1955 }
1956 return null;
1957 }
1958
1959 fn attrEqual(a: ir.Attribute, b: ir.Attribute) bool {
1960 return a.eql(b);
1961 }
1962
1963 fn locationEqual(a: ir.Location, b: ir.Location) bool {
1964 if (std.meta.activeTag(a) != std.meta.activeTag(b)) return false;
1965 switch (a) {
1966 .unknown => return true,
1967 .file => |lhs| {
1968 const rhs = b.file;
1969 return lhs.line == rhs.line and lhs.column == rhs.column and std.mem.eql(u8, lhs.filename, rhs.filename);
1970 },
1971 .file_range => |lhs| {
1972 const rhs = b.file_range;
1973 return lhs.start.byte == rhs.start.byte and
1974 lhs.start.line == rhs.start.line and
1975 lhs.start.column == rhs.start.column and
1976 lhs.end.byte == rhs.end.byte and
1977 lhs.end.line == rhs.end.line and
1978 lhs.end.column == rhs.end.column and
1979 std.mem.eql(u8, lhs.filename, rhs.filename);
1980 },
1981 .name => |lhs| {
1982 const rhs = b.name;
1983 if (!std.mem.eql(u8, lhs.name, rhs.name)) return false;
1984 if (lhs.child == null and rhs.child == null) return true;
1985 if (lhs.child == null or rhs.child == null) return false;
1986 return locationEqual(lhs.child.?.*, rhs.child.?.*);
1987 },
1988 .fused => |lhs| {
1989 const rhs = b.fused;
1990 if (lhs.locations.len != rhs.locations.len) return false;
1991 for (lhs.locations, 0..) |loc, idx| {
1992 if (!locationEqual(loc, rhs.locations[idx])) return false;
1993 }
1994 return true;
1995 },
1996 .call_site => |lhs| {
1997 const rhs = b.call_site;
1998 return locationEqual(lhs.callee.*, rhs.callee.*) and locationEqual(lhs.caller.*, rhs.caller.*);
1999 },
2000 }
2001 }
2002
2003 fn dialectNamespace(full_name: []const u8) []const u8 {
2004 const op_name = ir.Operation.OperationName.init(full_name);
2005 return op_name.getDialectNamespace();
2006 }
2007
2008 fn dialectTypeName(full_name: []const u8) []const u8 {
2009 if (std.mem.indexOf(u8, full_name, ".")) |idx| {
2010 return full_name[idx + 1 ..];
2011 }
2012 return full_name;
2013 }
2014
2015 const BufferWriter = struct {
2016 allocator: std.mem.Allocator,
2017 buffer: std.ArrayList(u8),
2018
2019 pub fn init(allocator: std.mem.Allocator) BufferWriter {
2020 return .{ .allocator = allocator, .buffer = .empty };
2021 }
2022
2023 pub fn deinit(self: *BufferWriter) void {
2024 self.buffer.deinit(self.allocator);
2025 }
2026
2027 pub fn toOwnedSlice(self: *BufferWriter) ![]u8 {
2028 return self.buffer.toOwnedSlice(self.allocator);
2029 }
2030
2031 pub fn writeAll(self: *BufferWriter, bytes: []const u8) !void {
2032 try self.buffer.appendSlice(self.allocator, bytes);
2033 }
2034
2035 pub fn writeByte(self: *BufferWriter, value: u8) !void {
2036 try self.buffer.append(self.allocator, value);
2037 }
2038
2039 pub fn writeU16(self: *BufferWriter, value: u16) !void {
2040 var buf: [2]u8 = undefined;
2041 std.mem.writeInt(u16, &buf, value, .little);
2042 try self.writeAll(buf[0..]);
2043 }
2044
2045 pub fn writeU32(self: *BufferWriter, value: u32) !void {
2046 var buf: [4]u8 = undefined;
2047 std.mem.writeInt(u32, &buf, value, .little);
2048 try self.writeAll(buf[0..]);
2049 }
2050
2051 pub fn writeU64(self: *BufferWriter, value: u64) !void {
2052 var buf: [8]u8 = undefined;
2053 std.mem.writeInt(u64, &buf, value, .little);
2054 try self.writeAll(buf[0..]);
2055 }
2056
2057 pub fn writeULEB(self: *BufferWriter, value: anytype) !void {
2058 const v64: u64 = @intCast(value);
2059 var v = v64;
2060 while (true) {
2061 var byte: u8 = @intCast(v & 0x7f);
2062 v >>= 7;
2063 if (v != 0) byte |= 0x80;
2064 try self.writeByte(byte);
2065 if (v == 0) break;
2066 }
2067 }
2068
2069 pub fn writeSLEB(self: *BufferWriter, value: i64) !void {
2070 var more = true;
2071 var v = value;
2072 while (more) {
2073 var byte: u8 = @intCast(v & 0x7f);
2074 const sign_bit = (byte & 0x40) != 0;
2075 v >>= 7;
2076 if ((v == 0 and !sign_bit) or (v == -1 and sign_bit)) {
2077 more = false;
2078 } else {
2079 byte |= 0x80;
2080 }
2081 try self.writeByte(byte);
2082 }
2083 }
2084
2085 pub fn writeBytes(self: *BufferWriter, bytes: []const u8) !void {
2086 try self.writeULEB(bytes.len);
2087 try self.writeAll(bytes);
2088 }
2089 };
2090
2091 pub const SliceReader = struct {
2092 bytes: []const u8,
2093 offset: usize,
2094
2095 pub fn init(bytes: []const u8) SliceReader {
2096 return .{ .bytes = bytes, .offset = 0 };
2097 }
2098
2099 pub fn readByte(self: *SliceReader) !u8 {
2100 if (self.offset >= self.bytes.len) return error.EndOfStream;
2101 const value = self.bytes[self.offset];
2102 self.offset += 1;
2103 return value;
2104 }
2105
2106 pub fn readBytes(self: *SliceReader, len: usize) ![]const u8 {
2107 if (len > self.bytes.len - self.offset) return error.EndOfStream;
2108 const end = self.offset + len;
2109 const slice = self.bytes[self.offset..end];
2110 self.offset = end;
2111 return slice;
2112 }
2113
2114 pub fn readBytesWithLen(self: *SliceReader) ![]const u8 {
2115 const len = try self.readULEBToUsize();
2116 return self.readBytes(len);
2117 }
2118
2119 pub fn readU16(self: *SliceReader) !u16 {
2120 const slice = try self.readBytes(2);
2121 var buf: [2]u8 = undefined;
2122 std.mem.copyForwards(u8, buf[0..], slice);
2123 return std.mem.readInt(u16, &buf, .little);
2124 }
2125
2126 pub fn readU32(self: *SliceReader) !u32 {
2127 const slice = try self.readBytes(4);
2128 var buf: [4]u8 = undefined;
2129 std.mem.copyForwards(u8, buf[0..], slice);
2130 return std.mem.readInt(u32, &buf, .little);
2131 }
2132
2133 pub fn readU64(self: *SliceReader) !u64 {
2134 const slice = try self.readBytes(8);
2135 var buf: [8]u8 = undefined;
2136 std.mem.copyForwards(u8, buf[0..], slice);
2137 return std.mem.readInt(u64, &buf, .little);
2138 }
2139
2140 pub fn readULEB(self: *SliceReader) !u64 {
2141 var result: u64 = 0;
2142 var shift: u8 = 0;
2143 while (true) {
2144 const byte = try self.readByte();
2145 const payload = byte & 0x7f;
2146 if (shift == 63) {
2147 if (payload > 1) return error.InvalidLEB128;
2148 result |= @as(u64, payload) << 63;
2149 } else {
2150 const bit_shift: u6 = @intCast(shift);
2151 result |= @as(u64, payload) << bit_shift;
2152 }
2153 if ((byte & 0x80) == 0) return result;
2154 if (shift >= 63) return error.InvalidLEB128;
2155 shift += 7;
2156 }
2157 }
2158
2159 pub fn readSLEB(self: *SliceReader) !i64 {
2160 var result: u64 = 0;
2161 var shift: u8 = 0;
2162 while (true) {
2163 const byte = try self.readByte();
2164 const payload = byte & 0x7f;
2165 if (shift == 63) {
2166 if (payload == 0x7f) {
2167 result |= @as(u64, 1) << 63;
2168 } else if (payload != 0) {
2169 return error.InvalidLEB128;
2170 }
2171 } else {
2172 const bit_shift: u6 = @intCast(shift);
2173 result |= @as(u64, payload) << bit_shift;
2174 }
2175 if ((byte & 0x80) == 0) {
2176 const used = shift + 7;
2177 if (used < 64 and (byte & 0x40) != 0) {
2178 const sign_shift: u6 = @intCast(used);
2179 result |= @as(u64, std.math.maxInt(u64)) << sign_shift;
2180 }
2181 return @bitCast(result);
2182 }
2183 if (shift >= 63) return error.InvalidLEB128;
2184 shift += 7;
2185 }
2186 }
2187
2188 pub fn readULEBToU32(self: *SliceReader) !u32 {
2189 const value = try self.readULEB();
2190 if (value > std.math.maxInt(u32)) return error.InvalidTable;
2191 return @intCast(value);
2192 }
2193
2194 pub fn readULEBToUsize(self: *SliceReader) !usize {
2195 const value = try self.readULEB();
2196 if (value > std.math.maxInt(usize)) return error.InvalidTable;
2197 return @intCast(value);
2198 }
2199
2200 pub fn readTypeList(
2201 self: *SliceReader,
2202 allocator: std.mem.Allocator,
2203 count: usize,
2204 types: []const ir.Type,
2205 ) ![]ir.Type {
2206 const list = try allocator.alloc(ir.Type, count);
2207 errdefer allocator.free(list);
2208 var i: usize = 0;
2209 while (i < count) : (i += 1) {
2210 const type_id = try self.readULEBToUsize();
2211 if (type_id >= types.len) return error.InvalidTable;
2212 list[i] = types[type_id];
2213 }
2214 return list;
2215 }
2216 };
2217
2218 const BytecodeProperties = struct {
2219 payload: ?ir.Attribute = null,
2220
2221 fn from(storage: *anyopaque) *@This() {
2222 return @ptrCast(@alignCast(storage));
2223 }
2224
2225 fn fromConst(storage: *const anyopaque) *const @This() {
2226 return @ptrCast(@alignCast(storage));
2227 }
2228
2229 fn init(storage: *anyopaque, _: std.mem.Allocator) anyerror!void {
2230 from(storage).* = .{};
2231 }
2232
2233 fn deinit(_: *anyopaque, _: std.mem.Allocator) void {}
2234
2235 fn get(_: *const ir.Operation, storage: *const anyopaque, name: []const u8) ?ir.Attribute {
2236 const payload = fromConst(storage).payload orelse return null;
2237 const values = payload.cast(ir.Attribute.ArrayAttr).?.values;
2238 if (std.mem.eql(u8, name, "left")) return values[0];
2239 if (std.mem.eql(u8, name, "right")) return values[1];
2240 return null;
2241 }
2242
2243 fn getProperties(_: *const ir.Operation, storage: *const anyopaque) ?ir.Attribute {
2244 return fromConst(storage).payload;
2245 }
2246
2247 fn setProperties(_: *ir.Operation, storage: *anyopaque, attr: ir.Attribute) anyerror!void {
2248 const array = attr.cast(ir.Attribute.ArrayAttr) orelse return error.InvalidTestProperties;
2249 if (array.values.len != 2) return error.InvalidTestProperties;
2250 from(storage).payload = attr;
2251 }
2252
2253 fn copyProperties(dest: *anyopaque, source: *const anyopaque) anyerror!void {
2254 from(dest).* = fromConst(source).*;
2255 }
2256
2257 const model = ir.OperationPropertiesModel{
2258 .name = "test.bytecode.properties",
2259 .size = @sizeOf(@This()),
2260 .alignment = std.mem.Alignment.fromByteUnits(@alignOf(@This())),
2261 .init = init,
2262 .deinit = deinit,
2263 .getInherentAttr = get,
2264 .getPropertiesAsAttr = getProperties,
2265 .setPropertiesFromAttr = setProperties,
2266 .copyProperties = copyProperties,
2267 };
2268 };
2269
2270 fn registerBytecodeProperties(context: *ir.Context) !void {
2271 try context.allowUnregistered();
2272 _ = try context.registerOperation("test.bytecode_properties", .{});
2273 try context.registerOperationInherentAttributeNames(
2274 "test.bytecode_properties",
2275 &.{ "left", "right" },
2276 );
2277 try context.registerOperationPropertiesModel(
2278 "test.bytecode_properties",
2279 BytecodeProperties.model,
2280 );
2281 }
2282
2283 test "bytecode qualification refuses unqualified property models despite conversion hooks" {
2284 const qualification = @import("root.zig").qualification;
2285 const allocator = std.testing.allocator;
2286 var source_context = try ir.Context.init(allocator, ir.Context.Limits.testing);
2287 defer source_context.deinit(allocator);
2288 try registerBytecodeProperties(&source_context);
2289 var decode_context = try ir.Context.init(allocator, ir.Context.Limits.testing);
2290 defer decode_context.deinit(allocator);
2291 try registerBytecodeProperties(&decode_context);
2292 const left = try source_context.getI64Attr(11);
2293 const right = try source_context.getI64Attr(22);
2294 const payload = try source_context.getArrayAttr(&.{ left, right });
2295 var state = ir.Operation.State.init("test.bytecode_properties", .unknown);
2296 try state.setPropertiesAttr(payload);
2297 const source = try source_context.createOperation(state);
2298 try std.testing.expectError(error.UnencodableProduct, qualification.encode(
2299 allocator,
2300 source,
2301 &.{},
2302 &decode_context,
2303 .{ .operations = 10, .entities = 10, .fields = 100, .depth = 16 },
2304 ));
2305 }
2306
2307 test "bytecode tables round-trip" {
2308 const testing = std.testing;
2309 const allocator = testing.allocator;
2310 const test_dialect = @import("../dialects/fixture/root.zig");
2311
2312 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2313 defer ctx.deinit(allocator);
2314 try ctx.allowUnregistered();
2315
2316 var builder = TableBuilder.init(allocator);
2317 defer builder.deinit();
2318
2319 const i32_type = try test_dialect.TestDialect.getI32Type(&ctx);
2320 const f64_type = try test_dialect.TestDialect.getF64Type(&ctx);
2321 const dialect_type = try ctx.getDialectTypeFromNameWithKey("test.ty", "key");
2322
2323 _ = try builder.internType(i32_type);
2324 _ = try builder.internType(f64_type);
2325 _ = try builder.internType(dialect_type);
2326
2327 const unit_attr = try test_dialect.TestDialect.getUnitAttr(&ctx);
2328 const bool_attr = try test_dialect.TestDialect.getBoolAttr(&ctx, true);
2329 const int_attr = try test_dialect.TestDialect.getIntegerAttr(&ctx, 42);
2330 const string_attr = try test_dialect.TestDialect.getStringAttr(&ctx, "hello");
2331 const symbol_ref_attr = try ctx.getSymbolRefAttr("module", &.{ "nested", "leaf" });
2332 const string_list_attr = try ctx.getStringListAttr(&.{ "ctx", "value_out" });
2333 const type_attr = try test_dialect.TestDialect.getTypeAttr(&ctx, i32_type);
2334 const type_list_attr = try ctx.getTypeListAttr(&.{ i32_type, dialect_type });
2335 const array_attr = try ctx.getArrayAttr(&.{ string_attr, symbol_ref_attr, type_list_attr });
2336
2337 _ = try builder.internAttribute(unit_attr);
2338 _ = try builder.internAttribute(bool_attr);
2339 _ = try builder.internAttribute(int_attr);
2340 _ = try builder.internAttribute(string_attr);
2341 _ = try builder.internAttribute(symbol_ref_attr);
2342 _ = try builder.internAttribute(string_list_attr);
2343 _ = try builder.internAttribute(type_attr);
2344 _ = try builder.internAttribute(type_list_attr);
2345 _ = try builder.internAttribute(array_attr);
2346
2347 var child_loc = ir.Location.getFile("test.ir", 3, 9);
2348 const range_loc = ir.Location.getFileRange(
2349 "model.gen",
2350 .{ .byte = 4, .line = 2, .column = 3 },
2351 .{ .byte = 9, .line = 2, .column = 8 },
2352 );
2353 const name_loc = ir.Location.getName("generated", &child_loc);
2354 const unknown_loc = ir.Location.getUnknown();
2355
2356 _ = try builder.internLocation(name_loc);
2357 _ = try builder.internLocation(unknown_loc);
2358 _ = try builder.internLocation(range_loc);
2359
2360 const bytes = try encodeTables(allocator, &builder);
2361 defer allocator.free(bytes);
2362
2363 var decode_ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2364 defer decode_ctx.deinit(allocator);
2365 try decode_ctx.allowUnregistered();
2366
2367 var tables = try decodeTables(allocator, &decode_ctx, bytes);
2368 defer tables.deinit();
2369
2370 try testing.expect(tables.format.readable());
2371 try testing.expectEqual(format_version, tables.format.version);
2372 try testing.expectEqual(@as(usize, 2), tables.dialects.len);
2373 try testing.expect(hasDialect(tables.dialects, "builtin"));
2374 try testing.expect(hasDialect(tables.dialects, "test"));
2375 for (tables.dialects) |dialect| {
2376 try testing.expect(dialect.usesDefaultVersion());
2377 }
2378
2379 const decoded_i32 = try test_dialect.TestDialect.getI32Type(&decode_ctx);
2380 const decoded_f64 = try test_dialect.TestDialect.getF64Type(&decode_ctx);
2381 const decoded_dialect = try decode_ctx.getDialectTypeFromNameWithKey("test.ty", "key");
2382
2383 try testing.expect(hasType(tables.types, decoded_i32));
2384 try testing.expect(hasType(tables.types, decoded_f64));
2385 try testing.expect(hasType(tables.types, decoded_dialect));
2386
2387 const decoded_unit = try test_dialect.TestDialect.getUnitAttr(&decode_ctx);
2388 const decoded_bool = try test_dialect.TestDialect.getBoolAttr(&decode_ctx, true);
2389 const decoded_int = try test_dialect.TestDialect.getIntegerAttr(&decode_ctx, 42);
2390 const decoded_string = try test_dialect.TestDialect.getStringAttr(&decode_ctx, "hello");
2391 const decoded_symbol_ref = try decode_ctx.getSymbolRefAttr("module", &.{ "nested", "leaf" });
2392 const decoded_string_list = try decode_ctx.getStringListAttr(&.{ "ctx", "value_out" });
2393 const decoded_type = try test_dialect.TestDialect.getTypeAttr(&decode_ctx, decoded_i32);
2394 const decoded_type_list = try decode_ctx.getTypeListAttr(&.{ decoded_i32, decoded_dialect });
2395 const decoded_array = try decode_ctx.getArrayAttr(&.{ decoded_string, decoded_symbol_ref, decoded_type_list });
2396
2397 try testing.expect(hasAttr(tables.attrs, decoded_unit));
2398 try testing.expect(hasAttr(tables.attrs, decoded_bool));
2399 try testing.expect(hasAttr(tables.attrs, decoded_int));
2400 try testing.expect(hasAttr(tables.attrs, decoded_string));
2401 try testing.expect(hasAttr(tables.attrs, decoded_symbol_ref));
2402 try testing.expect(hasAttr(tables.attrs, decoded_string_list));
2403 try testing.expect(hasAttr(tables.attrs, decoded_type));
2404 try testing.expect(hasAttr(tables.attrs, decoded_type_list));
2405 try testing.expect(hasAttr(tables.attrs, decoded_array));
2406
2407 try testing.expect(hasLocation(tables.locs, name_loc));
2408 try testing.expect(hasLocation(tables.locs, unknown_loc));
2409 try testing.expect(hasLocation(tables.locs, range_loc));
2410 }
2411
2412 test "bytecode round-trips builtin scalar types" {
2413 const testing = std.testing;
2414 const allocator = testing.allocator;
2415 const arith = @import("../dialects/root.zig").ArithDialect;
2416
2417 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2418 defer ctx.deinit(allocator);
2419
2420 var builder = TableBuilder.init(allocator);
2421 defer builder.deinit();
2422
2423 const i32_type = try arith.getI32Type(&ctx);
2424 const f32_type = try arith.getScalarType(&ctx, .f32);
2425 const index_type = try arith.getIndexType(&ctx);
2426 const bool_type = try arith.getScalarType(&ctx, .bool);
2427
2428 _ = try builder.internType(i32_type);
2429 _ = try builder.internType(f32_type);
2430 _ = try builder.internType(index_type);
2431 _ = try builder.internType(bool_type);
2432
2433 const bytes = try encodeTables(allocator, &builder);
2434 defer allocator.free(bytes);
2435
2436 var decode_ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2437 defer decode_ctx.deinit(allocator);
2438 _ = try arith.getI32Type(&decode_ctx);
2439
2440 var tables = try decodeTables(allocator, &decode_ctx, bytes);
2441 defer tables.deinit();
2442
2443 const decoded_i32 = try arith.getI32Type(&decode_ctx);
2444 const decoded_f32 = try arith.getScalarType(&decode_ctx, .f32);
2445 const decoded_index = try arith.getIndexType(&decode_ctx);
2446 const decoded_bool = try arith.getScalarType(&decode_ctx, .bool);
2447
2448 try testing.expect(hasType(tables.types, decoded_i32));
2449 try testing.expect(hasType(tables.types, decoded_f32));
2450 try testing.expect(hasType(tables.types, decoded_index));
2451 try testing.expect(hasType(tables.types, decoded_bool));
2452 }
2453
2454 fn testTableContainer(allocator: std.mem.Allocator, replacement: ?SectionData) ![]u8 {
2455 var sections = [_]SectionData{
2456 .{ .kind = @backingInt(SectionKind.strings), .flags = 0, .data = "\x02\x01s\x01t" },
2457 .{ .kind = @backingInt(SectionKind.dialects), .flags = 0, .data = "\x01\x00\x00\x00" },
2458 .{ .kind = @backingInt(SectionKind.types), .flags = 0, .data = "\x01\x01\x02\x20" },
2459 .{ .kind = @backingInt(SectionKind.attributes), .flags = 0, .data = "\x01\x03\x01" },
2460 .{ .kind = @backingInt(SectionKind.locations), .flags = 0, .data = "\x02\x00\x03\x01\x00" },
2461 .{ .kind = @backingInt(SectionKind.module), .flags = 0, .data = "" },
2462 };
2463 if (replacement) |value| {
2464 for (§ions) |*section| {
2465 if (section.kind == value.kind) section.* = value;
2466 }
2467 }
2468 return writeContainer(allocator, §ions);
2469 }
2470
2471 test "bytecode table decoders release partial malformed containers" {
2472 const allocator = std.testing.allocator;
2473 const cases = [_]struct { kind: SectionKind, data: []const u8, err: anyerror }{
2474 .{ .kind = .strings, .data = "\x02\x01a", .err = error.EndOfStream },
2475 .{ .kind = .dialects, .data = "\x02\x00\x00\x00", .err = error.EndOfStream },
2476 .{ .kind = .types, .data = "\x02\x01\x02\x20", .err = error.EndOfStream },
2477 .{ .kind = .attributes, .data = "\x02\x03\x01", .err = error.EndOfStream },
2478 .{ .kind = .locations, .data = "\x02\x00", .err = error.EndOfStream },
2479 .{ .kind = .locations, .data = "\x02\x00\x03\x02\x00", .err = error.EndOfStream },
2480 .{ .kind = .locations, .data = "\x03\x00\x03\x01\x00", .err = error.EndOfStream },
2481 .{ .kind = .dialects, .data = "\x01\x02\x00\x00", .err = error.InvalidTable },
2482 .{ .kind = .locations, .data = "\x02\x00\x03\x01\x02", .err = error.InvalidTable },
2483 };
2484 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2485 defer ctx.deinit(allocator);
2486 try ctx.allowUnregistered();
2487 for (cases) |case| {
2488 const bytes = try testTableContainer(allocator, .{
2489 .kind = @backingInt(case.kind),
2490 .flags = 0,
2491 .data = case.data,
2492 });
2493 defer allocator.free(bytes);
2494 try std.testing.expectError(case.err, decodeTables(allocator, &ctx, bytes));
2495 try std.testing.expectError(case.err, decodeModule(allocator, &ctx, bytes));
2496 }
2497 }
2498
2499 test "bytecode table decoders reject every invalid scalar tag" {
2500 const allocator = std.testing.allocator;
2501 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2502 defer ctx.deinit(allocator);
2503 for (4..256) |tag| {
2504 const types = [_]u8{ 1, @backingInt(TypeKind.builtin_scalar), @intCast(tag), 32 };
2505 const bytes = try testTableContainer(allocator, .{
2506 .kind = @backingInt(SectionKind.types),
2507 .flags = 0,
2508 .data = &types,
2509 });
2510 defer allocator.free(bytes);
2511 try std.testing.expectError(error.UnsupportedType, decodeTables(allocator, &ctx, bytes));
2512 try std.testing.expectError(error.UnsupportedType, decodeModule(allocator, &ctx, bytes));
2513 }
2514 }
2515
2516 test "bytecode table decoders release every failed allocation" {
2517 const allocator = std.testing.allocator;
2518 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2519 defer ctx.deinit(allocator);
2520 try ctx.allowUnregistered();
2521 const bytes = try testTableContainer(allocator, null);
2522 defer allocator.free(bytes);
2523 try std.testing.checkAllAllocationFailures(allocator, testTableAllocationFailure, .{ &ctx, bytes });
2524 }
2525
2526 fn testTableAllocationFailure(
2527 allocator: std.mem.Allocator,
2528 ctx: *ir.Context,
2529 bytes: []const u8,
2530 ) !void {
2531 var tables = try decodeTables(allocator, ctx, bytes);
2532 defer tables.deinit();
2533 try std.testing.expectEqual(@as(usize, 2), tables.strings.len);
2534 try std.testing.expectEqual(@as(usize, 1), tables.dialects.len);
2535 try std.testing.expectEqual(@as(usize, 1), tables.types.len);
2536 try std.testing.expectEqual(@as(usize, 1), tables.attrs.len);
2537 try std.testing.expectEqual(@as(usize, 2), tables.locs.len);
2538 try std.testing.expectEqual(@as(usize, 1), tables.locs[1].fused.locations.len);
2539 try std.testing.expectEqual(@as(usize, 1), tables.loc_slices.items.len);
2540 }
2541
2542 test "bytecode encoding is deterministic" {
2543 const testing = std.testing;
2544 const allocator = testing.allocator;
2545 const test_dialect = @import("../dialects/fixture/root.zig");
2546
2547 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2548 defer ctx.deinit(allocator);
2549 try ctx.allowUnregistered();
2550
2551 var builder = TableBuilder.init(allocator);
2552 defer builder.deinit();
2553
2554 const i32_type = try test_dialect.TestDialect.getI32Type(&ctx);
2555 const attr = try test_dialect.TestDialect.getIntegerAttr(&ctx, 7);
2556 _ = try builder.internType(i32_type);
2557 _ = try builder.internAttribute(attr);
2558 _ = try builder.internLocation(ir.Location.getUnknown());
2559
2560 const bytes1 = try encodeTables(allocator, &builder);
2561 defer allocator.free(bytes1);
2562 const bytes2 = try encodeTables(allocator, &builder);
2563 defer allocator.free(bytes2);
2564
2565 try testing.expectEqualSlices(u8, bytes1, bytes2);
2566 }
2567
2568 test "bytecode rejects invalid magic" {
2569 const testing = std.testing;
2570 const allocator = testing.allocator;
2571
2572 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2573 defer ctx.deinit(allocator);
2574
2575 var builder = TableBuilder.init(allocator);
2576 defer builder.deinit();
2577
2578 const bytes = try encodeTables(allocator, &builder);
2579 defer allocator.free(bytes);
2580
2581 var corrupted = try allocator.dupe(u8, bytes);
2582 defer allocator.free(corrupted);
2583 corrupted[0] = 'X';
2584
2585 try testing.expectError(error.InvalidHeader, decodeTables(allocator, &ctx, corrupted));
2586 }
2587
2588 test "bytecode rejects overflowed section ranges" {
2589 const testing = std.testing;
2590 const allocator = testing.allocator;
2591
2592 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2593 defer ctx.deinit(allocator);
2594
2595 var builder = TableBuilder.init(allocator);
2596 defer builder.deinit();
2597
2598 const bytes = try encodeTables(allocator, &builder);
2599 defer allocator.free(bytes);
2600
2601 var section_count = try allocator.dupe(u8, bytes);
2602 defer allocator.free(section_count);
2603 const section_count_offset = magic.len + 4 + 1 + 1 + 2 + 8;
2604 std.mem.writeInt(
2605 u64,
2606 section_count[section_count_offset..][0..8],
2607 std.math.maxInt(u64),
2608 .little,
2609 );
2610 try testing.expectError(
2611 error.InvalidSectionTable,
2612 decodeTables(allocator, &ctx, section_count),
2613 );
2614
2615 var table_offset = try allocator.dupe(u8, bytes);
2616 defer allocator.free(table_offset);
2617 const table_offset_offset = magic.len + 4 + 1 + 1 + 2;
2618 std.mem.writeInt(
2619 u64,
2620 table_offset[table_offset_offset..][0..8],
2621 std.math.maxInt(u64),
2622 .little,
2623 );
2624 try testing.expectError(
2625 error.InvalidSectionTable,
2626 decodeTables(allocator, &ctx, table_offset),
2627 );
2628
2629 var section_range = try allocator.dupe(u8, bytes);
2630 defer allocator.free(section_range);
2631 const first_section_offset: usize = @intCast(headerSize() + 8);
2632 std.mem.writeInt(
2633 u64,
2634 section_range[first_section_offset..][0..8],
2635 std.math.maxInt(u64),
2636 .little,
2637 );
2638 std.mem.writeInt(
2639 u64,
2640 section_range[first_section_offset + 8 ..][0..8],
2641 1,
2642 .little,
2643 );
2644 try testing.expectError(
2645 error.InvalidSection,
2646 decodeTables(allocator, &ctx, section_range),
2647 );
2648 }
2649
2650 test "bytecode reader rejects overflowing lengths" {
2651 var empty = SliceReader.init("");
2652 try std.testing.expectError(
2653 error.EndOfStream,
2654 empty.readBytes(std.math.maxInt(usize)),
2655 );
2656
2657 const continued = [_]u8{
2658 0x80,
2659 0x80,
2660 0x80,
2661 0x80,
2662 0x80,
2663 0x80,
2664 0x80,
2665 0x80,
2666 0x80,
2667 0x80,
2668 };
2669 var unsigned_reader = SliceReader.init(&continued);
2670 try std.testing.expectError(
2671 error.InvalidLEB128,
2672 unsigned_reader.readULEB(),
2673 );
2674 var signed_reader = SliceReader.init(&continued);
2675 try std.testing.expectError(
2676 error.InvalidLEB128,
2677 signed_reader.readSLEB(),
2678 );
2679 }
2680
2681 test "bytecode exposes format version policy" {
2682 const current = classifyFormatVersion(format_version);
2683 try std.testing.expect(current.readable());
2684 try std.testing.expectEqual(FormatVersionStatus.readable, current.status);
2685 try std.testing.expectEqual(format_version, current.version);
2686 try std.testing.expectEqual(minimum_readable_version, current.minimum_readable);
2687 try std.testing.expectEqual(maximum_readable_version, current.maximum_readable);
2688
2689 const older = classifyFormatVersion(minimum_readable_version - 1);
2690 try std.testing.expect(!older.readable());
2691 try std.testing.expectEqual(FormatVersionStatus.unsupported_older, older.status);
2692
2693 const newer = classifyFormatVersion(maximum_readable_version + 1);
2694 try std.testing.expect(!newer.readable());
2695 try std.testing.expectEqual(FormatVersionStatus.unsupported_newer, newer.status);
2696 }
2697
2698 test "bytecode inspects and rejects unsupported versions" {
2699 const testing = std.testing;
2700 const allocator = testing.allocator;
2701
2702 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2703 defer ctx.deinit(allocator);
2704
2705 var builder = TableBuilder.init(allocator);
2706 defer builder.deinit();
2707
2708 const bytes = try encodeTables(allocator, &builder);
2709 defer allocator.free(bytes);
2710
2711 const header = try inspectHeader(bytes);
2712 try testing.expectEqual(format_version, header.version);
2713 try testing.expect(header.compatibility().readable());
2714
2715 const corrupted_future = try allocator.dupe(u8, bytes);
2716 defer allocator.free(corrupted_future);
2717 writeTestVersion(corrupted_future, maximum_readable_version + 1);
2718 const future_header = try inspectHeader(corrupted_future);
2719 try testing.expectEqual(FormatVersionStatus.unsupported_newer, future_header.compatibility().status);
2720 try testing.expectError(error.UnsupportedVersion, decodeTables(allocator, &ctx, corrupted_future));
2721
2722 const corrupted_previous = try allocator.dupe(u8, bytes);
2723 defer allocator.free(corrupted_previous);
2724 writeTestVersion(corrupted_previous, minimum_readable_version - 1);
2725 const previous_header = try inspectHeader(corrupted_previous);
2726 try testing.expectEqual(FormatVersionStatus.unsupported_older, previous_header.compatibility().status);
2727 try testing.expectError(error.UnsupportedVersion, decodeTables(allocator, &ctx, corrupted_previous));
2728
2729 const corrupted_old = try allocator.dupe(u8, bytes);
2730 defer allocator.free(corrupted_old);
2731 writeTestVersion(corrupted_old, 3);
2732 try testing.expectError(error.UnsupportedVersion, decodeTables(allocator, &ctx, corrupted_old));
2733 }
2734
2735 test "bytecode ignores unknown sections" {
2736 const testing = std.testing;
2737 const allocator = testing.allocator;
2738
2739 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2740 defer ctx.deinit(allocator);
2741
2742 var builder = TableBuilder.init(allocator);
2743 defer builder.deinit();
2744 _ = try builder.internLocation(ir.Location.getUnknown());
2745
2746 const strings_data = try encodeStringsSection(allocator, &builder);
2747 defer allocator.free(strings_data);
2748 const dialects_data = try encodeDialectsSection(allocator, &builder);
2749 defer allocator.free(dialects_data);
2750 const types_data = try encodeTypesSection(allocator, &builder);
2751 defer allocator.free(types_data);
2752 const attrs_data = try encodeAttributesSection(allocator, &builder);
2753 defer allocator.free(attrs_data);
2754 const locs_data = try encodeLocationsSection(allocator, &builder);
2755 defer allocator.free(locs_data);
2756
2757 const unknown_payload = try allocator.dupe(u8, "junk");
2758 defer allocator.free(unknown_payload);
2759
2760 const sections = [_]SectionData{
2761 .{ .kind = @backingInt(SectionKind.strings), .flags = 0, .data = strings_data },
2762 .{ .kind = @backingInt(SectionKind.dialects), .flags = 0, .data = dialects_data },
2763 .{ .kind = @backingInt(SectionKind.types), .flags = 0, .data = types_data },
2764 .{ .kind = @backingInt(SectionKind.attributes), .flags = 0, .data = attrs_data },
2765 .{ .kind = @backingInt(SectionKind.locations), .flags = 0, .data = locs_data },
2766 .{ .kind = 99, .flags = 0, .data = unknown_payload },
2767 };
2768
2769 const bytes = try writeContainer(allocator, §ions);
2770 defer allocator.free(bytes);
2771
2772 var tables = try decodeTables(allocator, &ctx, bytes);
2773 defer tables.deinit();
2774
2775 try testing.expectEqual(@as(usize, 1), tables.locs.len);
2776 }
2777
2778 test "bytecode module round-trip" {
2779 const testing = std.testing;
2780 const allocator = testing.allocator;
2781 const test_dialect = @import("../dialects/fixture/root.zig");
2782
2783 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2784 defer ctx.deinit(allocator);
2785 try ctx.allowUnregistered();
2786
2787 const loc = ir.Location.getUnknown();
2788 const argument_loc = ir.Location.getFileRange(
2789 "roundtrip.mlir",
2790 .{ .byte = 4, .line = 2, .column = 3 },
2791 .{ .byte = 9, .line = 2, .column = 8 },
2792 );
2793 const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
2794 const module_region = module_op.getBody();
2795 const entry_block = module_op.getBodyBlock();
2796
2797 const i32_type = try test_dialect.TestDialect.getI32Type(&ctx);
2798 _ = try entry_block.addArgument(i32_type, argument_loc);
2799 _ = try entry_block.addArgument(i32_type, loc);
2800 const arg0 = entry_block.getArgument(0).?;
2801 const arg1 = entry_block.getArgument(1).?;
2802
2803 const answer_attr = try test_dialect.TestDialect.getIntegerAttr(&ctx, 42);
2804 const add_attrs = [_]ir.NamedAttribute{.{ .name = "answer", .value = answer_attr }};
2805
2806 var add_state = ir.Operation.State.init("test.add", loc);
2807 add_state.addOperands(&.{ arg0, arg1 });
2808 add_state.addTypes(&.{i32_type});
2809 add_state.addAttributes(&add_attrs);
2810 const add_op = try ctx.createOperation(add_state);
2811 try entry_block.addOperation(add_op);
2812
2813 var use_state = ir.Operation.State.init("test.use", loc);
2814 use_state.addOperands(&.{add_op.getResult(0).?});
2815 const use_op = try ctx.createOperation(use_state);
2816 try entry_block.addOperation(use_op);
2817
2818 const second_block = try module_region.addBlock();
2819
2820 var br_state = ir.Operation.State.init("test.br", loc);
2821 br_state.addSuccessors(&.{second_block});
2822 const br_op = try ctx.createOperation(br_state);
2823 try entry_block.addOperation(br_op);
2824
2825 var nested_state = ir.Operation.State.init("test.with_region", loc);
2826 nested_state.addRegion();
2827 const nested_op = try ctx.createOperation(nested_state);
2828 try entry_block.addOperation(nested_op);
2829
2830 const nested_region = nested_op.getRegion(0).?;
2831 const nested_block = try nested_region.addBlock();
2832 const inner_state = ir.Operation.State.init("test.inner", loc);
2833 const inner_op = try ctx.createOperation(inner_state);
2834 try nested_block.addOperation(inner_op);
2835
2836 const bytes = try encodeModule(allocator, module_op.op);
2837 defer allocator.free(bytes);
2838
2839 var decode_ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2840 defer decode_ctx.deinit(allocator);
2841 try decode_ctx.allowUnregistered();
2842
2843 var decoded = try decodeModule(allocator, &decode_ctx, bytes);
2844 defer decoded.deinit();
2845
2846 try testing.expect(decoded.format().readable());
2847 try testing.expectEqual(format_version, decoded.format().version);
2848 const decoded_module = decoded.module;
2849 try testing.expectEqualStrings("test.module", decoded_module.name.name);
2850 try testing.expectEqual(@as(usize, 0), decoded.resources.len);
2851
2852 const decoded_region = decoded_module.getRegion(0).?;
2853 var block_iter = decoded_region.getBlocks();
2854 const decoded_entry = block_iter.next() orelse return error.TestExpectedBlock;
2855 const decoded_second = block_iter.next() orelse return error.TestExpectedBlock;
2856 try testing.expect(block_iter.next() == null);
2857
2858 try testing.expectEqual(@as(usize, 2), decoded_entry.getNumArguments());
2859 const decoded_i32 = try test_dialect.TestDialect.getI32Type(&decode_ctx);
2860 try testing.expect(decoded_entry.getArgument(0).?.type.eql(decoded_i32));
2861 try testing.expect(decoded_entry.getArgument(1).?.type.eql(decoded_i32));
2862 try testing.expect(decoded_entry.getArgumentLocation(0).?.eql(argument_loc));
2863 try testing.expect(decoded_entry.getArgumentLocation(1).?.eql(loc));
2864
2865 const head_any = decoded_entry.operations.head orelse return error.TestExpectedOp;
2866 const op_add: *ir.Operation = @ptrCast(@alignCast(head_any));
2867 try testing.expectEqualStrings("test.add", op_add.name.name);
2868
2869 const decoded_answer = op_add.getAttr("answer") orelse return error.TestExpectedAttr;
2870 const decoded_value = test_dialect.TestDialect.getIntegerValue(decoded_answer) orelse return error.TestExpectedAttr;
2871 try testing.expectEqual(@as(i64, 42), decoded_value);
2872
2873 const op_use = op_add.next_op orelse return error.TestExpectedOp;
2874 try testing.expectEqualStrings("test.use", op_use.name.name);
2875 try testing.expect(op_use.getOperand(0).? == op_add.getResult(0).?);
2876
2877 const op_br = op_use.next_op orelse return error.TestExpectedOp;
2878 try testing.expectEqualStrings("test.br", op_br.name.name);
2879 try testing.expectEqual(@as(usize, 1), op_br.getNumSuccessors());
2880 try testing.expect(op_br.getSuccessor(0).? == decoded_second);
2881
2882 const op_nested = op_br.next_op orelse return error.TestExpectedOp;
2883 try testing.expectEqualStrings("test.with_region", op_nested.name.name);
2884 try testing.expectEqual(@as(usize, 1), op_nested.getNumRegions());
2885
2886 const nested_region_dec = op_nested.getRegion(0).?;
2887 const nested_entry = nested_region_dec.getEntryBlock() orelse return error.TestExpectedBlock;
2888 const nested_head = nested_entry.operations.head orelse return error.TestExpectedOp;
2889 const decoded_inner: *ir.Operation = @ptrCast(@alignCast(nested_head));
2890 try testing.expectEqualStrings("test.inner", decoded_inner.name.name);
2891 }
2892
2893 test "bytecode flat module decoder retains one top-level operation" {
2894 const testing = std.testing;
2895 const allocator = testing.allocator;
2896 const dialects = @import("../dialects/root.zig");
2897
2898 var encode_ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2899 defer encode_ctx.deinit(allocator);
2900 try dialects.registerChoirDialect(&encode_ctx);
2901 try encode_ctx.allowUnregistered();
2902
2903 const loc = ir.Location.getUnknown();
2904 const module = try dialects.BuiltinDialect.ModuleOp.create(
2905 &encode_ctx,
2906 loc,
2907 );
2908 const int = try dialects.ArithDialect.getScalarType(&encode_ctx, .i32);
2909 var first_state = ir.Operation.State.init("test.first", loc);
2910 first_state.addTypes(&.{int});
2911 try module.getBodyBlock().addOperation(
2912 try encode_ctx.createOperation(first_state),
2913 );
2914 var second_state = ir.Operation.State.init("test.second", loc);
2915 second_state.addTypes(&.{int});
2916 try module.getBodyBlock().addOperation(
2917 try encode_ctx.createOperation(second_state),
2918 );
2919
2920 const bytes = try encodeModule(allocator, module.op);
2921 defer allocator.free(bytes);
2922
2923 var decode_ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2924 defer decode_ctx.deinit(allocator);
2925 try dialects.registerChoirDialect(&decode_ctx);
2926 try decode_ctx.allowUnregistered();
2927
2928 var decoded = try FlatModuleDecoder.init(
2929 allocator,
2930 &decode_ctx,
2931 bytes,
2932 );
2933 defer decoded.deinit();
2934 try testing.expectEqualStrings(
2935 dialects.BuiltinDialect.ModuleOp.operation_name,
2936 decoded.name(),
2937 );
2938 try testing.expectEqualStrings("test.first", (try decoded.next()).?.name.name);
2939 try testing.expectEqual(@as(usize, 1), decode_ctx.operationCount());
2940 try testing.expectEqualStrings("test.second", (try decoded.next()).?.name.name);
2941 try testing.expectEqual(@as(usize, 1), decode_ctx.operationCount());
2942 try testing.expect((try decoded.next()) == null);
2943 try testing.expectEqual(@as(usize, 0), decode_ctx.operationCount());
2944 }
2945
2946 test "bytecode flat module decoder rejects cross-operation values" {
2947 const testing = std.testing;
2948 const allocator = testing.allocator;
2949 const dialects = @import("../dialects/root.zig");
2950
2951 var encode_ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2952 defer encode_ctx.deinit(allocator);
2953 try dialects.registerChoirDialect(&encode_ctx);
2954 try encode_ctx.allowUnregistered();
2955
2956 const loc = ir.Location.getUnknown();
2957 const module = try dialects.BuiltinDialect.ModuleOp.create(
2958 &encode_ctx,
2959 loc,
2960 );
2961 const int = try dialects.ArithDialect.getScalarType(&encode_ctx, .i32);
2962 var producer_state = ir.Operation.State.init("test.producer", loc);
2963 producer_state.addTypes(&.{int});
2964 const producer = try encode_ctx.createOperation(producer_state);
2965 try module.getBodyBlock().addOperation(producer);
2966 var consumer_state = ir.Operation.State.init("test.consumer", loc);
2967 consumer_state.addOperands(&.{producer.getResult(0).?});
2968 try module.getBodyBlock().addOperation(
2969 try encode_ctx.createOperation(consumer_state),
2970 );
2971
2972 const bytes = try encodeModule(allocator, module.op);
2973 defer allocator.free(bytes);
2974
2975 var decode_ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
2976 defer decode_ctx.deinit(allocator);
2977 try dialects.registerChoirDialect(&decode_ctx);
2978 try decode_ctx.allowUnregistered();
2979
2980 var decoded = try FlatModuleDecoder.init(
2981 allocator,
2982 &decode_ctx,
2983 bytes,
2984 );
2985 defer decoded.deinit();
2986 try testing.expectEqualStrings(
2987 "test.producer",
2988 (try decoded.next()).?.name.name,
2989 );
2990 try testing.expectError(error.InvalidModule, decoded.next());
2991 }
2992
2993 test "bytecode round-trips read-only properties and raw shadows" {
2994 const testing = std.testing;
2995 const allocator = testing.allocator;
2996
2997 var encode_context = try ir.Context.init(allocator, ir.Context.Limits.testing);
2998 defer encode_context.deinit(allocator);
2999 try registerBytecodeProperties(&encode_context);
3000
3001 const left = try encode_context.getI64Attr(11);
3002 const right = try encode_context.getI64Attr(22);
3003 const payload = try encode_context.getArrayAttr(&.{ left, right });
3004 const raw_left = try encode_context.getI64Attr(99);
3005 const note = try encode_context.getStringAttr("kept");
3006 const raw_attributes = [_]ir.NamedAttribute{
3007 .{ .name = "left", .value = raw_left },
3008 .{ .name = "debug.note", .value = note },
3009 };
3010 var state = ir.Operation.State.init("test.bytecode_properties", ir.Location.getUnknown());
3011 state.addRawAttributes(&raw_attributes);
3012 try state.setPropertiesAttr(payload);
3013 const source = try encode_context.createOperation(state);
3014
3015 const encoded = try encodeModule(allocator, source);
3016 defer allocator.free(encoded);
3017
3018 var decode_context = try ir.Context.init(allocator, ir.Context.Limits.testing);
3019 defer decode_context.deinit(allocator);
3020 try registerBytecodeProperties(&decode_context);
3021 var decoded = try decodeModule(allocator, &decode_context, encoded);
3022 defer decoded.deinit();
3023
3024 const decoded_left = decoded.module.getAttr("left") orelse return error.TestExpectedAttr;
3025 const decoded_right = decoded.module.getAttr("right") orelse return error.TestExpectedAttr;
3026 try testing.expectEqual(@as(i64, 11), decoded_left.cast(ir.Attribute.IntegerAttr).?.value);
3027 try testing.expectEqual(@as(i64, 22), decoded_right.cast(ir.Attribute.IntegerAttr).?.value);
3028 try testing.expectEqual(@as(usize, 2), decoded.module.getRawDictionaryAttrs().len);
3029 try testing.expectEqual(
3030 @as(i64, 99),
3031 decoded.module.raw_dictionary_attrs.get("left").?.cast(ir.Attribute.IntegerAttr).?.value,
3032 );
3033 try testing.expectEqualStrings(
3034 "kept",
3035 decoded.module.getAttrAs(ir.Attribute.StringAttr, "debug.note").?.value,
3036 );
3037
3038 const reencoded = try encodeModule(allocator, decoded.module);
3039 defer allocator.free(reencoded);
3040 try testing.expectEqualSlices(u8, encoded, reencoded);
3041 }
3042
3043 test "bytecode module round-trips resources" {
3044 const testing = std.testing;
3045 const allocator = testing.allocator;
3046 const test_dialect = @import("../dialects/fixture/root.zig");
3047
3048 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
3049 defer ctx.deinit(allocator);
3050
3051 const loc = ir.Location.getUnknown();
3052 const module_op = try test_dialect.TestDialect.ModuleOp.create(&ctx, loc);
3053
3054 const resources = [_]Resource{
3055 .{
3056 .namespace = "choir.artifact",
3057 .name = "manifest",
3058 .type_id = "choir.backends.artifact.Manifest/v1",
3059 .data = "{\"target\":\"x86_64\"}",
3060 },
3061 .{
3062 .namespace = "accy.artifact",
3063 .name = "kernel-metadata",
3064 .type_id = "accy.cuda.KernelMetadata/v1",
3065 .data = "entry=add_f32",
3066 },
3067 };
3068
3069 const bytes = try encodeModuleWithResources(allocator, module_op.op, &resources);
3070 defer allocator.free(bytes);
3071
3072 var decode_ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
3073 defer decode_ctx.deinit(allocator);
3074 try test_dialect.registerTestDialect(&decode_ctx);
3075
3076 var decoded = try decodeModule(allocator, &decode_ctx, bytes);
3077 defer decoded.deinit();
3078
3079 try testing.expectEqualStrings("test.module", decoded.module.name.name);
3080 try testing.expectEqual(resources.len, decoded.resources.len);
3081 for (resources, decoded.resources) |expected, actual| {
3082 try testing.expectEqualStrings(expected.namespace, actual.namespace);
3083 try testing.expectEqualStrings(expected.name, actual.name);
3084 try testing.expectEqualStrings(expected.type_id, actual.type_id);
3085 try testing.expectEqualStrings(expected.data, actual.data);
3086 }
3087 }
3088
3089 test "bytecode round-trips undotted dialect type names" {
3090 const testing = std.testing;
3091 const allocator = testing.allocator;
3092 const arith = @import("../dialects/root.zig").ArithDialect;
3093 const root_dialects = @import("../dialects/root.zig");
3094 const MemrefDialect = root_dialects.MemrefDialect;
3095 const interfaces = ir.interfaces;
3096
3097 var enc_ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
3098 defer enc_ctx.deinit(allocator);
3099 try root_dialects.registerChoirDialect(&enc_ctx);
3100
3101 const f32_type = try arith.getScalarType(&enc_ctx, .f32);
3102 const memref_type = try MemrefDialect.getMemrefType1D(&enc_ctx, 1024, f32_type, .host);
3103
3104 var builder = TableBuilder.init(allocator);
3105 defer builder.deinit();
3106 _ = try builder.internType(memref_type);
3107
3108 const bytes = try encodeTables(allocator, &builder);
3109 defer allocator.free(bytes);
3110
3111 var dec_ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
3112 defer dec_ctx.deinit(allocator);
3113 try dec_ctx.requireRegistered();
3114 try root_dialects.registerChoirDialect(&dec_ctx);
3115
3116 var tables = try decodeTables(allocator, &dec_ctx, bytes);
3117 defer tables.deinit();
3118
3119 try testing.expectEqual(@as(usize, 1), tables.types.len);
3120 const decoded = tables.types[0];
3121
3122 const storage = decoded.getDialectStorage() orelse return error.TestExpectedDialectStorage;
3123 try testing.expectEqualStrings("memref", storage.name);
3124 try testing.expectEqualStrings("1024,arith.f32,host", storage.param_key);
3125
3126 try testing.expect((try dec_ctx.getTypeParamPayload(decoded, MemrefDialect.MemrefTypePayload)) != null);
3127 try testing.expect(dec_ctx.typeInterface(decoded, interfaces.ShapedTypeInterface) != null);
3128
3129 try testing.expect(hasDialect(tables.dialects, "memref"));
3130 }
3131
3132 fn hasType(types: []const ir.Type, needle: ir.Type) bool {
3133 for (types) |item| {
3134 if (item.eql(needle)) return true;
3135 }
3136 return false;
3137 }
3138
3139 fn writeTestVersion(bytes: []u8, version: u32) void {
3140 std.mem.writeInt(u32, bytes[magic.len..][0..4], version, .little);
3141 }
3142
3143 fn hasDialect(dialects: []const DialectEntry, needle: []const u8) bool {
3144 for (dialects) |item| {
3145 if (std.mem.eql(u8, item.name, needle)) return true;
3146 }
3147 return false;
3148 }
3149
3150 fn hasAttr(attrs: []const ir.Attribute, needle: ir.Attribute) bool {
3151 for (attrs) |item| {
3152 if (attrEqual(item, needle)) return true;
3153 }
3154 return false;
3155 }
3156
3157 fn hasLocation(locs: []const ir.Location, needle: ir.Location) bool {
3158 for (locs) |item| {
3159 if (locationEqual(item, needle)) return true;
3160 }
3161 return false;
3162 }