1//! Language Compilation Unit
2//!
3//! This is all of the state for a compilation, which starts at one or more URLs
4//! (entry points), and will eventually emit one or more object files.
5
6/// General purpose and thread-safe allocator.
7gpa: Allocator,
8/// Stores all Type and Value objects.
9intern_pool: InternPool,
10/// Frontend options.
11options: Options,
12/// All text output is printed here. A typical compiler binary will set this to
13/// standard error, but a testing or web interface may want stronger control
14/// over the output.
15out: core.OutStream,
16/// Backend and it's configuration.
17backend: Backend,
18
19/// All files in the compilation
20// TODO: turn this into a HashMap(*File, void) as File owns it's key
21files: std.StringHashMapUnmanaged(*File),
22
23ongoing_analysis: std.AutoHashMapUnmanaged(InternPool.AnalysisUnit, *Sema),
24
25jobs: LinearFifo(Job, .Dynamic),
26
27// TODO: remove when it is possible to return `Report`s nicely
28temp_flag_had_errors: bool,
29
30const WorkItem = union(enum) {
31 sema: *Sema,
32 code_gen: *Air,
33};
34
35const InitOptions = struct {
36 gpa: Allocator,
37 options: Options = .{},
38 backend: Backend,
39 out: ?core.OutStream = null,
40};
41
42pub fn init(opts: InitOptions) !Comp {
43 var comp: Comp = .{
44 .gpa = opts.gpa,
45 .options = opts.options,
46 .backend = opts.backend,
47 .out = opts.out orelse .getStderr(),
48 .files = .empty,
49 .temp_flag_had_errors = false,
50 .intern_pool = .{},
51 .jobs = .init(opts.gpa),
52 .ongoing_analysis = .empty,
53 };
54
55 try comp.intern_pool.init(comp.gpa);
56
57 return comp;
58}
59
60pub fn deinit(comp: *Comp) void {
61 {
62 var it = comp.ongoing_analysis.valueIterator();
63 while (it.next()) |sema| {
64 _ = sema.*.deinit(.free_all);
65 comp.gpa.destroy(sema.*);
66 }
67 comp.ongoing_analysis.deinit(comp.gpa);
68 }
69 {
70 var it = comp.files.valueIterator();
71 while (it.next()) |file| {
72 file.*.deinit(comp.gpa);
73 }
74 comp.files.deinit(comp.gpa);
75 }
76 comp.intern_pool.deinit(comp.gpa);
77 comp.backend.deinit(comp);
78 comp.jobs.deinit();
79}
80
81const Job = union(enum) {
82 analysis: struct {
83 au: InternPool.AnalysisUnit,
84 first_blocked_sema: ?*Sema = null,
85 },
86 codegen_func: struct {
87 value: InternPool.Value,
88 air: Air,
89 },
90
91 pub fn dump(job: *const Job, ip: *const InternPool) std.fmt.Formatter(formatImpl) {
92 return .{ .data = .{ .job = job, .ip = ip } };
93 }
94
95 fn formatImpl(
96 data: struct { job: *const Job, ip: *const InternPool },
97 comptime unused: []const u8,
98 _: std.fmt.FormatOptions,
99 w: std.io.AnyWriter,
100 ) !void {
101 comptime assert(unused.len == 0);
102 try w.print(".{s}(", .{@tagName(data.job.*)});
103 switch (data.job.*) {
104 .analysis => |detail| try w.print("{}", .{detail.au.dump(data.ip)}),
105 .codegen_func => |detail| try w.print("{}", .{detail.value.dump(data.ip)}),
106 }
107 try w.writeAll(")");
108 }
109};
110
111pub fn enqueueEntryModule(comp: *Comp, file: *File) !Decl.Index {
112 var report: Report = undefined;
113 const hir = try file.getHir(comp, &report);
114
115 // TODO, move this elsewhere. we should print all errors and warnings at once
116 if (hir.report.warnings.len > 0) {
117 try hir.report.dump(.initComp(comp, file), comp.out);
118 }
119
120 const ns = try comp.indexHirModule(file, .main_module_inst);
121 const main_str = try comp.intern_pool.getOrPutString(comp.gpa, "main", .no_embedded_nulls);
122 const decl_index = comp.intern_pool.namespacePtr(ns).lookupDirect(comp, main_str) orelse {
123 // TODO: add a helper function into Report to render a single error
124 var rb = Report.Builder.init(comp.gpa);
125 defer rb.deinit();
126 try rb.err(.no_main_func, .{});
127 const r: Report = try rb.finalize();
128 defer r.deinit(comp.gpa);
129 try r.dump(.{ .comp = .{ .comp = comp, .file = file } }, comp.out);
130 return error.TransformFail;
131 };
132
133 try comp.jobs.writeItem(.{ .analysis = .{ .au = .wrap(.{ .decl = decl_index }) } });
134
135 return decl_index; // root decl is needed for linking
136}
137
138pub fn runAllJobs(comp: *Comp) !void {
139 while (comp.jobs.readItem()) |job| {
140 // log.info("processing {}", .{job.dump(&comp.intern_pool)});
141 switch (job) {
142 .analysis => |detail| try comp.runAnalysisJob(detail.au, detail.first_blocked_sema),
143 .codegen_func => |detail| try comp.runCodegenJob(detail.value, detail.air),
144 }
145 }
146
147 if (core.assertFailed(comp.ongoing_analysis.size == 0)) {
148 std.debug.print("Unresumed AnalysisUnits:\n", .{});
149 var it = comp.ongoing_analysis.keyIterator();
150 while (it.next()) |au| {
151 std.debug.print("- {}\n", .{au.dump(&comp.intern_pool)});
152 }
153 std.debug.panic("runAllJobs returned with {d} remaining ongoing_analysis", .{comp.ongoing_analysis.size});
154 }
155}
156
157const AnalysisJobError = Allocator.Error || error{ AnalysisFail, TooManyValues };
158
159fn runAnalysisJob(comp: *Comp, unit: InternPool.AnalysisUnit, first_blocked_sema: ?*Sema) AnalysisJobError!void {
160 const gop = try comp.ongoing_analysis.getOrPut(comp.gpa, unit);
161 if (!gop.found_existing) {
162 errdefer comp.ongoing_analysis.removeByPtr(gop.key_ptr);
163
164 const sema = try comp.gpa.create(Sema);
165 const decl_index = switch (unit.unwrap()) {
166 .decl => |index| index,
167 .func => |value| comp.intern_pool.valueToKey(value).func.owner_decl,
168 };
169 const decl = comp.intern_pool.declPtr(decl_index);
170 const file = comp.intern_pool.namespacePtr(decl.src_namespace).file_scope;
171 const hir = file.hir orelse @panic("TODO: make sure hir is loaded to avoid annoying error set?");
172
173 try sema.init(
174 comp,
175 file,
176 hir,
177 unit,
178 decl.src_namespace,
179 std.heap.ArenaAllocator.init(comp.gpa),
180 );
181 errdefer _ = sema.deinit(.free_all);
182 gop.value_ptr.* = sema;
183
184 switch (unit.unwrap()) {
185 .decl => decl.analysis = .in_progress,
186 .func => |v| {
187 v.toKey(&comp.intern_pool).func.analysis.set(&comp.intern_pool, .in_progress);
188 },
189 }
190
191 if (first_blocked_sema) |blocked_sema|
192 try sema.blocked_semas.append(comp.gpa, blocked_sema);
193 } else if (first_blocked_sema) |blocked_sema| {
194 try gop.value_ptr.*.blocked_semas.append(comp.gpa, blocked_sema);
195 }
196
197 const sema: *Sema = gop.value_ptr.*;
198 sema.run() catch |err| switch (err) {
199 error.Interrupt => return,
200
201 error.OutOfMemory,
202 error.TooManyValues,
203 => |e| return e,
204
205 error.AnalysisFail => |e| {
206 const report = sema.report.finalize() catch {
207 comp.ongoing_analysis.removeByPtr(gop.key_ptr);
208 _ = sema.deinit(.free_all); // TODO: arena reuse
209 comp.gpa.destroy(sema);
210 return e;
211 };
212 defer report.deinit(comp.gpa);
213
214 report.dump(.{ .comp = .{ .comp = comp, .file = sema.file } }, comp.out) catch {};
215
216 comp.ongoing_analysis.removeByPtr(gop.key_ptr);
217 _ = sema.deinit(.free_all); // TODO: arena reuse
218 comp.gpa.destroy(sema);
219
220 return e;
221 },
222 };
223
224 errdefer {
225 comp.ongoing_analysis.removeByPtr(gop.key_ptr);
226 _ = sema.deinit(.free_all); // TODO: arena reuse
227 comp.gpa.destroy(sema);
228 }
229
230 const report = try sema.report.finalize();
231 defer report.deinit(comp.gpa); // TODO: store warnings
232 if (report.warnings.len > 0) {
233 report.dump(.{ .comp = .{ .comp = comp, .file = sema.file } }, comp.out) catch {};
234 }
235
236 switch (unit.unwrap()) {
237 .decl => |index| {
238 comp.intern_pool.declPtr(index).analysis = .complete;
239 },
240 .func => |v| {
241 v.toKey(&comp.intern_pool).func.analysis.set(&comp.intern_pool, .complete);
242 },
243 }
244
245 comp.ongoing_analysis.removeByPtr(gop.key_ptr);
246 try comp.jobs.ensureUnusedCapacity(sema.blocked_semas.items.len);
247 for (sema.blocked_semas.items) |other_sema| {
248 comp.jobs.writeItemAssumeCapacity(.{ .analysis = .{ .au = other_sema.owner } });
249 }
250 _ = sema.deinit(.free_all); // TODO: arena reuse
251 comp.gpa.destroy(sema);
252}
253
254pub fn runCodegenJob(c: *Comp, value: InternPool.Value, air_const: Air) !void {
255 const t = tracer.trace(@src());
256 defer t.end();
257
258 var arena_state = std.heap.ArenaAllocator.init(c.gpa);
259 defer arena_state.deinit();
260 const arena = arena_state.allocator();
261
262 if (c.options.dump_ir) {
263 air_const.dumpToWriter(&c.intern_pool, c.out) catch {};
264 }
265
266 var air = air_const; // this line is a workaround
267 defer air.deinit(c.gpa);
268
269 _ = try c.backend.handleFunctionAir(c, value, air, arena);
270}
271
272/// Add or retrieve a file to the compilation via it's file path.
273pub fn getFile(comp: *Comp, absolute_file_path: []const u8) !*File {
274 assert(std.fs.path.isAbsolute(absolute_file_path));
275 const gop = try comp.files.getOrPut(comp.gpa, absolute_file_path);
276 if (gop.found_existing) {
277 return gop.value_ptr.*;
278 }
279 const file = try comp.gpa.create(File);
280 file.* = .{
281 .file_path = absolute_file_path,
282 .path_digest = null,
283 .root_namespace = .none,
284 .source = null,
285 .ast = null,
286 .hir = null,
287 };
288 gop.value_ptr.* = file;
289 return file;
290}
291
292pub const Source = struct {
293 code: [:0]const u8,
294 stat: Stat,
295};
296
297/// A file in the compilation and it's associated data structures. Ast and Hir
298/// are not guaranteed to be loaded, even if it's `status` says `success`.
299pub const File = struct {
300 /// TODO: change this to a URL
301 file_path: []const u8,
302
303 root_namespace: Namespace.Index.Optional,
304
305 source: ?Source,
306 ast: ?Ast,
307 hir: ?Hir,
308
309 path_digest: ?Cache.BinDigest,
310
311 const GetSourceError =
312 std.fs.File.OpenError ||
313 std.fs.File.ReadError ||
314 std.fs.File.StatError ||
315 Allocator.Error || error{
316 FileTooBig,
317 UnexpectedEndOfFile,
318 };
319
320 pub fn getSource(file: *File, gpa: Allocator) GetSourceError!Source {
321 if (file.source) |source| {
322 return source;
323 }
324
325 // Keep track of inode, file size, mtime, hash so the incremental
326 // compiler can detect which files have been modified when an
327 // incremental update is requested.
328 var f = try std.fs.openFileAbsolute(file.file_path, .{});
329 defer f.close();
330
331 const stat = try f.stat();
332
333 if (stat.size > std.math.maxInt(u32))
334 return error.FileTooBig;
335
336 const source = try gpa.allocSentinel(u8, @as(usize, @intCast(stat.size)), 0);
337 errdefer gpa.free(source);
338 const amt = try f.readAll(source);
339 if (amt != stat.size)
340 return error.UnexpectedEndOfFile;
341
342 const loaded_source: Source = .{
343 .code = source,
344 .stat = .{
345 .size = stat.size,
346 .inode = stat.inode,
347 .mtime = stat.mtime,
348 },
349 };
350 file.source = loaded_source;
351 return loaded_source;
352 }
353
354 pub const GetAstError = GetSourceError || Parse.Error;
355
356 pub fn getAst(file: *File, comp: *Comp) GetAstError!*const Ast {
357 if (file.ast) |*ast| {
358 return ast;
359 }
360
361 var err_data: Parse.AstError.WithTrace = undefined;
362 const ast = Parse.parse(
363 comp.gpa,
364 (try file.getSource(comp.gpa)).code,
365 &err_data,
366 ) catch |err| switch (err) {
367 error.OutOfMemory => return err,
368 error.ParseFail => {
369 // TODO: let this merge with other report
370 var report = try err_data.toReport(comp.gpa);
371 defer report.deinit(comp.gpa);
372 report.dump(.{ .comp = .{ .comp = comp, .file = file } }, comp.out) catch {};
373 return err;
374 },
375 };
376
377 if (comp.options.dump_ir) {
378 ast.dumpToWriter(std.io.getStdErr().writer()) catch {
379 // Simplify the error set.
380 };
381 }
382
383 file.ast = ast;
384 return &(file.ast.?);
385 }
386
387 pub const GetHirError = GetAstError || Transform.Error;
388
389 pub fn getHir(file: *File, comp: *Comp, fail_report: *Report) GetHirError!Hir {
390 if (file.hir) |hir| {
391 return hir;
392 }
393
394 const bin_digest = hash: {
395 var path_hash: Cache.HashHelper = .{};
396 path_hash.addBytes(@import("config").version_string);
397 path_hash.addBytes(file.file_path);
398 var bin: Cache.BinDigest = undefined;
399 path_hash.hasher.final(&bin);
400 break :hash bin;
401 };
402 file.path_digest = bin_digest;
403
404 const hir = try Transform.transform((try file.getAst(comp)).*, comp.gpa, fail_report);
405
406 file.hir = hir;
407 return hir;
408 }
409
410 pub fn unloadSource(file: *File, gpa: Allocator) void {
411 if (file.source) |source| {
412 gpa.free(source.code);
413 file.source = null;
414 }
415 }
416
417 pub fn unloadAst(file: *File, gpa: Allocator) void {
418 if (file.ast) |*ast| {
419 ast.deinit(gpa);
420 file.ast = null;
421 }
422 }
423
424 pub fn unloadHir(file: *File, gpa: Allocator) void {
425 if (file.hir) |*hir| {
426 hir.deinit(gpa);
427 file.hir = null;
428 }
429 }
430
431 pub fn deinit(file: *File, gpa: Allocator) void {
432 file.unloadHir(gpa);
433 file.unloadAst(gpa);
434 file.unloadSource(gpa);
435 gpa.destroy(file);
436 }
437
438 pub fn fmtJson(file: *const File, comp: *const Comp) JsonFormatter {
439 return .{ .file = file, .comp = comp };
440 }
441
442 const JsonFormatter = struct {
443 file: *const File,
444 comp: *const Comp,
445
446 pub fn jsonStringify(ctx: JsonFormatter, json_writer: anytype) !void {
447 // file path is already written
448 const jw = core.jsonWriterCast(json_writer);
449 try jw.beginObject();
450 try jw.objectField("path_digest");
451 try if (ctx.file.path_digest) |digest|
452 jw.write(std.fmt.bytesToHex(digest, .lower))
453 else
454 jw.write(null);
455 try jw.objectField("root_namespace");
456 if (ctx.file.root_namespace.unwrap()) |ns_index| {
457 const ns = ctx.comp.intern_pool.namespacePtrConst(ns_index);
458 try jw.write(ns.fmtJson(ctx.comp));
459 } else {
460 try jw.write(null);
461 }
462 try jw.endObject();
463 }
464 };
465};
466
467/// A declaration is a named value which also represents the smallest unit of
468/// compilation/re-compilation. A declaration contains zero or more `Func`
469/// objects, which represent the different callable values the declaration has.
470///
471/// A special type of declaration is the top-level declaration, which has the
472/// name "" (empty string). This is used to facilitate top-level side effects.
473pub const Decl = struct {
474 name: InternPool.NullTerminatedString,
475
476 /// The Value that this declaration evaluates to.
477 raw_value: ?InternPool.Value,
478
479 /// The direct parent namespace of the Decl. In the case of the Decl
480 /// corresponding to a file, this is the namespace of the struct, since
481 /// there is no parent.
482 src_namespace: Namespace.Index,
483
484 // /// The AST node index of this declaration.
485 // /// Must be recomputed when the corresponding source file is modified.
486 // src_node: Ast.NodeIndex,
487
488 // /// Line number corresponding to `src_node`. Stored separately so that source files
489 // /// do not need to be loaded into memory in order to compute debug line numbers.
490 // /// This value is absolute.
491 // src_line: u32,
492
493 /// Index of the Hir `declaration` instruction from which this `Decl` was created.
494 teg_decl_index: InternPool.TrackedInst.Index,
495
496 /// Represents the "shallow" analysis status. For example, for decls that are functions,
497 /// the function type is analyzed with this set to `in_progress`; however, the semantic
498 /// analysis of the function body is performed with this value set to `success`. Functions
499 /// have their own analysis status field.
500 analysis: InternPool.AnalysisState,
501
502 pub fn value(decl: *const Decl) InternPool.Value {
503 assert(decl.analysis == .complete);
504 return decl.raw_value orelse unreachable;
505 }
506
507 pub fn fmtJson(decl: *const Decl, comp: *const Comp) JsonFormatter {
508 return .{ .decl = decl, .comp = comp };
509 }
510
511 const JsonFormatter = struct {
512 decl: *const Decl,
513 comp: *const Comp,
514
515 pub fn jsonStringify(ctx: JsonFormatter, json_writer: anytype) !void {
516 const decl = ctx.decl;
517 const comp = ctx.comp;
518
519 const jw = core.jsonWriterCast(json_writer);
520 try jw.beginObject();
521
522 try jw.objectField("name");
523 try jw.write(decl.name.toSlice(&comp.intern_pool));
524 try jw.objectField("analysis");
525 try jw.write(decl.analysis);
526 try jw.objectField("teg_decl_index");
527 try jw.write(decl.teg_decl_index.get(&comp.intern_pool));
528 try jw.objectField("value");
529 if (decl.raw_value) |val| {
530 var buf: [8192]u8 = undefined;
531 try jw.print("", .{});
532 try jw.stream.print("[{d}, ", .{@intFromEnum(val)});
533 try std.json.encodeJsonString(
534 std.fmt.bufPrint(&buf, "{}", .{val.dump(&comp.intern_pool)}) catch &buf,
535 .{},
536 jw.stream,
537 );
538 try jw.stream.print("]", .{});
539 }
540 try jw.endObject();
541 }
542 };
543
544 pub const Index = InternPool.DeclIndex;
545};
546
547/// A namespace is a container for declarations. Currently the only form of
548/// namespace that exists is file-scope. In the future, the `module` keyword
549/// will create nested namespaces, and it is probable that `struct` and other
550/// constructs will also create namespaces.
551pub const Namespace = struct {
552 parent: Index.Optional,
553 file_scope: *File,
554 /// Direct children of the namespace.
555 /// Declaration order is preserved via entry order.
556 decls: std.ArrayHashMapUnmanaged(Decl.Index, void, DeclContext, true) = .{},
557
558 pub const DeclContext = struct {
559 comp: *Comp,
560
561 pub fn hash(ctx: @This(), decl_index: Decl.Index) u32 {
562 const decl = ctx.comp.intern_pool.declPtr(decl_index);
563 return std.hash.uint32(@intFromEnum(decl.name));
564 }
565
566 pub fn eql(
567 ctx: @This(),
568 a_decl_index: Decl.Index,
569 b_decl_index: Decl.Index,
570 b_index: usize,
571 ) bool {
572 _ = b_index;
573 const a_decl = ctx.comp.intern_pool.declPtr(a_decl_index);
574 const b_decl = ctx.comp.intern_pool.declPtr(b_decl_index);
575 return a_decl.name == b_decl.name;
576 }
577 };
578
579 pub const DeclNameAdapter = struct {
580 comp: *Comp,
581
582 pub fn hash(self: @This(), s: InternPool.NullTerminatedString) u32 {
583 _ = self;
584 return std.hash.uint32(@intFromEnum(s));
585 }
586
587 pub fn eql(
588 self: @This(),
589 a: InternPool.NullTerminatedString,
590 b_decl_index: Decl.Index,
591 b_index: usize,
592 ) bool {
593 _ = b_index;
594 return a == self.comp.intern_pool.declPtr(b_decl_index).name;
595 }
596 };
597
598 pub fn lookupDirect(
599 ns: *Namespace,
600 comp: *Comp,
601 name: InternPool.NullTerminatedString,
602 ) ?Decl.Index {
603 return ns.decls.getKeyAdapted(name, Namespace.DeclNameAdapter{ .comp = comp });
604 }
605
606 pub fn fmtJson(ns: *const Namespace, comp: *const Comp) JsonFormatter {
607 return .{ .ns = ns, .comp = comp };
608 }
609
610 const JsonFormatter = struct {
611 ns: *const Namespace,
612 comp: *const Comp,
613
614 pub fn jsonStringify(ctx: JsonFormatter, json_writer: anytype) !void {
615 const ns = ctx.ns;
616 const comp = ctx.comp;
617
618 const jw = core.jsonWriterCast(json_writer);
619 try jw.beginObject();
620 // try jw.objectField("parent");
621 // try if (ns.parent.unwrap()) |p| jw.write(@intFromEnum(p)) else jw.write(null);
622 try jw.objectField("decls");
623 try jw.beginObject();
624 for (ns.decls.keys()) |decl_index| {
625 const decl = comp.intern_pool.declPtrConst(decl_index);
626 try jw.objectField(decl.name.toSlice(&comp.intern_pool));
627 try jw.write(decl.fmtJson(comp));
628 }
629 try jw.endObject();
630 try jw.endObject();
631 }
632 };
633
634 pub const Index = InternPool.NamespaceIndex;
635};
636
637/// Index a Hir's module scope as a namespace. This prepares `Decl` objects for
638/// every declaration, but does not begin analysis of them.
639pub fn indexHirModule(comp: *Comp, file: *File, module_index: Hir.Inst.Index) !Namespace.Index {
640 const hir = file.hir.?;
641
642 assert(file.root_namespace == .none);
643
644 const ns = try comp.intern_pool.createNamespace(comp.gpa, .{
645 .parent = .none,
646 .file_scope = file,
647 });
648 file.root_namespace = ns.toOptional();
649 const ns_ptr = comp.intern_pool.namespacePtr(ns);
650 errdefer comp.intern_pool.destroyNamespace(comp.gpa, ns);
651
652 for (hir.moduleDeclarations(module_index)) |decl_index| {
653 switch (hir.instTag(decl_index)) {
654 .declaration => {
655 const name = try comp.intern_pool.getOrPutString(
656 comp.gpa,
657 hir.declName(decl_index),
658 .no_embedded_nulls,
659 );
660 const tracked_inst = try comp.intern_pool.trackInst(comp.gpa, file, decl_index);
661 const decl = try comp.intern_pool.createDecl(comp.gpa, .{
662 .name = name,
663 .raw_value = null,
664 .src_namespace = ns,
665 .teg_decl_index = tracked_inst,
666 .analysis = .unreferenced,
667 });
668 try ns_ptr.decls.putNoClobberContext(comp.gpa, decl, void{}, .{ .comp = comp });
669 },
670 .variable_declaration => @panic("TODO: variable declarations"),
671 .test_declaration => {
672 // TODO: test mode
673 },
674 else => unreachable,
675 }
676 }
677
678 return ns;
679}
680
681const Options = struct {
682 dump_ir: bool = false,
683 verbose_sema: bool = false,
684};
685
686/// The compiler backend is stored separately from the compilation, and
687/// interaction is done via this static dispatch contract.
688pub const Backend = union(enum) {
689 js: *@import("js/Js.zig"),
690
691 pub fn deinit(backend: Backend, comp: *Comp) void {
692 backend.dispatch("deinit", .{comp});
693 }
694
695 pub const HandleFunctionAirError = error{OutOfMemory};
696
697 /// Once semantic analysis finishes analyzing a Function value, it hands
698 /// `Air` to the backend for code generation. Returned is an opaque integer
699 /// which is stored on the Function value.
700 pub fn handleFunctionAir(
701 backend: Backend,
702 comp: *Comp,
703 value: InternPool.Value,
704 air: Air,
705 arena: Allocator,
706 ) HandleFunctionAirError!u32 {
707 return backend.dispatch("handleFunctionAir", .{ comp, value, air, arena });
708 }
709
710 inline fn dispatch(
711 backend: Backend,
712 comptime name: []const u8,
713 args: anytype,
714 ) @typeInfo(@TypeOf(@field(Backend, name))).@"fn".return_type.? {
715 return switch (backend) {
716 inline else => |ptr| @call(.auto, @field(@TypeOf(ptr.*), name), .{ptr} ++ args),
717 };
718 }
719
720 pub const Tag = std.meta.FieldEnum(Backend);
721};
722
723pub fn jsonStringify(comp: *const Comp, json_writer: anytype) !void {
724 const jw = core.jsonWriterCast(json_writer);
725
726 try jw.beginObject();
727 try jw.objectField("files");
728 try jw.beginObject();
729 var it = comp.files.valueIterator();
730 while (it.next()) |entry| {
731 try jw.objectField(entry.*.file_path);
732 try jw.write(entry.*.fmtJson(comp));
733 }
734 try jw.endObject();
735 try jw.objectField("intern_pool");
736 try jw.write(comp.intern_pool);
737 try jw.endObject();
738}
739
740const Comp = @This();
741
742const std = @import("std");
743const Allocator = std.mem.Allocator;
744const assert = std.debug.assert;
745const Stat = std.Build.Cache.File.Stat;
746const LinearFifo = std.fifo.LinearFifo;
747
748const core = @import("mod.zig");
749const InternPool = core.InternPool;
750
751const Ast = core.Ast;
752const Air = core.Air;
753const Hir = core.Hir;
754const Cache = core.Cache;
755const Sema = core.Sema;
756const Transform = core.Transform;
757const Report = core.Report;
758const Parse = core.Parse;
759
760const tracer = @import("tracer");