forked from roc-lang/roc
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCIR.zig
More file actions
527 lines (435 loc) · 18.9 KB
/
CIR.zig
File metadata and controls
527 lines (435 loc) · 18.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
//! Common IR types and utilities
//! This module contains type definitions and utilities used across the canonicalization IR.
const std = @import("std");
const types_mod = @import("types");
const collections = @import("collections");
const base = @import("base");
const reporting = @import("reporting");
const builtins = @import("builtins");
const CompactWriter = collections.CompactWriter;
const Ident = base.Ident;
const StringLiteral = base.StringLiteral;
const RegionInfo = base.RegionInfo;
const Region = base.Region;
const SExprTree = base.SExprTree;
const SExpr = base.SExpr;
const TypeVar = types_mod.Var;
// Re-export these from other modules for convenience
pub const NodeStore = @import("NodeStore.zig");
pub const Node = @import("Node.zig");
pub const Expr = @import("Expression.zig").Expr;
pub const Pattern = @import("Pattern.zig").Pattern;
pub const Statement = @import("Statement.zig").Statement;
pub const TypeAnno = @import("TypeAnnotation.zig").TypeAnno;
pub const Diagnostic = @import("Diagnostic.zig").Diagnostic;
// Type definitions for module compilation
/// Represents a definition (binding of a pattern to an expression) in the CIR
pub const Def = struct {
pub const Idx = enum(u32) { _ };
pub const Span = struct { span: base.DataSpan };
pattern: Pattern.Idx,
expr: Expr.Idx,
annotation: ?Annotation.Idx,
kind: Kind,
pub const Kind = union(enum) {
/// A def that introduces identifiers
let: void,
/// A standalone statement with an fx variable
stmt: TypeVar,
/// Ignored result, must be effectful
ignored: TypeVar,
pub fn decode(encoded: [2]u32) Kind {
if (encoded[0] == 0) {
return .let;
} else if (encoded[0] == 1) {
return .{ .stmt = @as(TypeVar, @enumFromInt(encoded[1])) };
} else {
return .{ .ignored = @as(TypeVar, @enumFromInt(encoded[1])) };
}
}
pub fn encode(self: Kind) [2]u32 {
switch (self) {
.let => return .{ 0, 0 },
.stmt => |ty_var| return .{ 1, @intFromEnum(ty_var) },
.ignored => |ty_var| return .{ 2, @intFromEnum(ty_var) },
}
}
};
pub fn pushToSExprTree(self: *const Def, cir: anytype, tree: anytype) !void {
const begin = tree.beginNode();
const name: []const u8 = switch (self.kind) {
.let => "d-let",
.stmt => "d-stmt",
.ignored => "d-ignored",
};
try tree.pushStaticAtom(name);
const attrs = tree.beginNode();
try cir.store.getPattern(self.pattern).pushToSExprTree(cir, tree, self.pattern);
try cir.store.getExpr(self.expr).pushToSExprTree(cir, tree, self.expr);
if (self.annotation) |annotation_idx| {
try cir.store.getAnnotation(annotation_idx).pushToSExprTree(cir, tree, annotation_idx);
}
try tree.endNode(begin, attrs);
}
};
/// Represents a type header (e.g., 'Maybe a' or 'Result err ok') in type annotations
pub const TypeHeader = struct {
pub const Idx = enum(u32) { _ };
pub const Span = struct { start: u32, len: u32 };
name: base.Ident.Idx,
args: TypeAnno.Span,
pub fn pushToSExprTree(self: *const TypeHeader, cir: anytype, tree: anytype, idx: TypeHeader.Idx) !void {
const begin = tree.beginNode();
try tree.pushStaticAtom("ty-header");
// Get the region for this TypeHeader
const node_idx: Node.Idx = @enumFromInt(@intFromEnum(idx));
const region = cir.store.getRegionAt(node_idx);
try cir.appendRegionInfoToSExprTreeFromRegion(tree, region);
const name_str = cir.getIdent(self.name);
try tree.pushStringPair("name", name_str);
const attrs = tree.beginNode();
if (self.args.span.len > 0) {
const args_begin = tree.beginNode();
try tree.pushStaticAtom("ty-args");
const args_attrs = tree.beginNode();
for (cir.store.sliceTypeAnnos(self.args)) |anno_idx| {
try cir.store.getTypeAnno(anno_idx).pushToSExprTree(cir, tree, anno_idx);
}
try tree.endNode(args_begin, args_attrs);
}
try tree.endNode(begin, attrs);
}
};
/// Represents a where clause constraint in type definitions
pub const WhereClause = union(enum) {
pub const Idx = enum(u32) { _ };
pub const Span = struct { span: base.DataSpan };
mod_method: struct {
var_name: base.Ident.Idx,
method_name: base.Ident.Idx,
args: TypeAnno.Span,
ret_anno: TypeAnno.Idx,
external_decl: ExternalDecl.Idx,
},
mod_alias: struct {
var_name: base.Ident.Idx,
alias_name: base.Ident.Idx,
external_decl: ExternalDecl.Idx,
},
malformed: struct {
diagnostic: Diagnostic.Idx,
},
pub fn pushToSExprTree(self: *const WhereClause, cir: anytype, tree: anytype, idx: WhereClause.Idx) !void {
switch (self.*) {
.mod_method => |method| {
const begin = tree.beginNode();
try tree.pushStaticAtom("method");
// Get the region for this WhereClause
const node_idx: Node.Idx = @enumFromInt(@intFromEnum(idx));
const region = cir.store.getRegionAt(node_idx);
try cir.appendRegionInfoToSExprTreeFromRegion(tree, region);
// Add module-of and ident information
const var_name_str = cir.getIdent(method.var_name);
try tree.pushStringPair("module-of", var_name_str);
const method_name_str = cir.getIdent(method.method_name);
try tree.pushStringPair("ident", method_name_str);
const attrs = tree.beginNode();
// Add actual argument types
const args_begin = tree.beginNode();
try tree.pushStaticAtom("args");
const args_attrs = tree.beginNode();
for (cir.store.sliceTypeAnnos(method.args)) |arg_idx| {
try cir.store.getTypeAnno(arg_idx).pushToSExprTree(cir, tree, arg_idx);
}
try tree.endNode(args_begin, args_attrs);
// Add actual return type
try cir.store.getTypeAnno(method.ret_anno).pushToSExprTree(cir, tree, method.ret_anno);
try tree.endNode(begin, attrs);
},
.mod_alias => |alias| {
const begin = tree.beginNode();
try tree.pushStaticAtom("alias");
// Get the region for this WhereClause
const node_idx: Node.Idx = @enumFromInt(@intFromEnum(idx));
const region = cir.store.getRegionAt(node_idx);
try cir.appendRegionInfoToSExprTreeFromRegion(tree, region);
const var_name_str = cir.getIdent(alias.var_name);
try tree.pushStringPair("module-of", var_name_str);
const alias_name_str = cir.getIdent(alias.alias_name);
try tree.pushStringPair("ident", alias_name_str);
const attrs = tree.beginNode();
try tree.endNode(begin, attrs);
},
.malformed => |malformed| {
const begin = tree.beginNode();
try tree.pushStaticAtom("malformed");
// Get the region for this WhereClause
const node_idx: Node.Idx = @enumFromInt(@intFromEnum(idx));
const region = cir.store.getRegionAt(node_idx);
try cir.appendRegionInfoToSExprTreeFromRegion(tree, region);
_ = malformed;
const attrs = tree.beginNode();
try tree.endNode(begin, attrs);
},
}
}
};
/// Represents a type annotation associated with definitions
pub const Annotation = struct {
pub const Idx = enum(u32) { _ };
type_anno: TypeAnno.Idx,
signature: TypeVar,
pub fn pushToSExprTree(self: *const Annotation, cir: anytype, tree: anytype, idx: Annotation.Idx) !void {
const begin = tree.beginNode();
try tree.pushStaticAtom("annotation");
// Get the region for this Annotation
const node_idx: Node.Idx = @enumFromInt(@intFromEnum(idx));
const region = cir.store.getRegionAt(node_idx);
try cir.appendRegionInfoToSExprTreeFromRegion(tree, region);
const attrs = tree.beginNode();
const type_anno_begin = tree.beginNode();
try tree.pushStaticAtom("declared-type");
const type_anno_attrs = tree.beginNode();
try cir.store.getTypeAnno(self.type_anno).pushToSExprTree(cir, tree, self.type_anno);
try tree.endNode(type_anno_begin, type_anno_attrs);
try tree.endNode(begin, attrs);
}
};
/// Represents an item exposed by a module's interface
pub const ExposedItem = struct {
pub const Idx = enum(u32) { _ };
pub const Span = struct { span: base.DataSpan };
name: base.Ident.Idx,
alias: ?base.Ident.Idx,
is_wildcard: bool,
pub fn pushToSExprTree(self: *const ExposedItem, _: anytype, cir: anytype, tree: anytype) !void {
const begin = tree.beginNode();
try tree.pushStaticAtom("exposed");
const name_str = cir.getIdent(self.name);
try tree.pushStringPair("name", name_str);
if (self.alias) |alias_idx| {
const alias_str = cir.getIdent(alias_idx);
try tree.pushStringPair("alias", alias_str);
}
try tree.pushBoolPair("wildcard", self.is_wildcard);
const attrs = tree.beginNode();
try tree.endNode(begin, attrs);
}
};
/// Represents a field in a record pattern for pattern matching
pub const PatternRecordField = struct {
pub const Idx = enum(u32) { _ };
pub const Span = struct { start: u32, len: u32 };
};
/// Represents an arbitrary precision integer value
pub const IntValue = struct {
bytes: [16]u8,
kind: enum {
i64,
u64,
i128,
u128,
},
pub fn toI128(self: IntValue) i128 {
return @bitCast(self.bytes);
}
};
// RocDec type definition (for missing export)
// Must match the structure of builtins.RocDec
pub const RocDec = builtins.dec.RocDec;
/// Converts a RocDec to an i128 integer
pub fn toI128(self: RocDec) i128 {
return self.num;
}
/// Creates a RocDec from an f64 value, returns null if conversion fails
pub fn fromF64(f: f64) ?RocDec {
// Simple conversion - the real implementation is in builtins/dec.zig
const scaled = @as(i128, @intFromFloat(f * 1_000_000_000_000_000_000.0));
return RocDec{ .num = scaled };
}
/// Represents an import statement in a module
pub const Import = struct {
pub const Idx = enum(u32) { _ };
pub const Store = struct {
/// Map from interned string idx to Import.Idx for deduplication
map: std.AutoHashMapUnmanaged(base.StringLiteral.Idx, Import.Idx) = .{},
/// List of interned string IDs indexed by Import.Idx
imports: collections.SafeList(base.StringLiteral.Idx) = .{},
pub fn init() Store {
return .{};
}
pub fn deinit(self: *Store, allocator: std.mem.Allocator) void {
self.map.deinit(allocator);
self.imports.deinit(allocator);
}
/// Get or create an Import.Idx for the given module name.
/// The module name is first checked against existing imports by comparing strings.
pub fn getOrPut(self: *Store, allocator: std.mem.Allocator, strings: *base.StringLiteral.Store, module_name: []const u8) !Import.Idx {
// First check if we already have this module name by comparing strings
for (self.imports.items.items, 0..) |existing_string_idx, i| {
const existing_name = strings.get(existing_string_idx);
if (std.mem.eql(u8, existing_name, module_name)) {
// Found existing import with same name
return @as(Import.Idx, @enumFromInt(i));
}
}
// Not found - create new import
const string_idx = try strings.insert(allocator, module_name);
const idx = @as(Import.Idx, @enumFromInt(self.imports.len()));
// Add to both the list and the map
_ = try self.imports.append(allocator, string_idx);
try self.map.put(allocator, string_idx, idx);
return idx;
}
/// Serialize this Store to the given CompactWriter. The resulting Store
/// in the writer's buffer will have offsets instead of pointers. Calling any
/// methods on it or dereferencing its internal "pointers" (which are now
/// offsets) is illegal behavior!
pub fn serialize(
self: *const Store,
allocator: std.mem.Allocator,
writer: *CompactWriter,
) std.mem.Allocator.Error!*const Store {
// First, write the Store struct itself
const offset_self = try writer.appendAlloc(allocator, Store);
// Then serialize the sub-structures and update the struct
offset_self.* = .{
.map = .{}, // Map will be empty after deserialization (only used for deduplication during insertion)
.imports = (try self.imports.serialize(allocator, writer)).*,
};
return @constCast(offset_self);
}
/// Add the given offset to the memory addresses of all pointers in `self`.
pub fn relocate(self: *Store, offset: isize) void {
self.imports.relocate(offset);
}
pub const Serialized = struct {
// Placeholder to match Store size - not serialized
map: std.AutoHashMapUnmanaged(base.StringLiteral.Idx, Import.Idx) = .{},
imports: collections.SafeList(base.StringLiteral.Idx).Serialized,
/// Serialize a Store into this Serialized struct, appending data to the writer
pub fn serialize(
self: *Serialized,
store: *const Store,
allocator: std.mem.Allocator,
writer: *CompactWriter,
) std.mem.Allocator.Error!void {
// Serialize the imports SafeList
try self.imports.serialize(&store.imports, allocator, writer);
// Note: The map is not serialized as it's only used for deduplication during insertion
}
/// Deserialize this Serialized struct into a Store
pub fn deserialize(self: *Serialized, offset: i64, allocator: std.mem.Allocator) *Store {
// Overwrite ourself with the deserialized version, and return our pointer after casting it to Store.
const store = @as(*Store, @ptrFromInt(@intFromPtr(self)));
store.* = .{
.map = .{}, // Will be repopulated below
.imports = self.imports.deserialize(offset).*,
};
// Pre-allocate the exact capacity needed for the map
const import_count = store.imports.items.items.len;
store.map.ensureTotalCapacity(allocator, @intCast(import_count)) catch unreachable;
// Repopulate the map - we know there's enough capacity since we
// are deserializing from a Serialized struct
for (store.imports.items.items, 0..) |string_idx, i| {
const import_idx = @as(Import.Idx, @enumFromInt(i));
store.map.putAssumeCapacityNoClobber(string_idx, import_idx);
}
return store;
}
};
};
};
/// Represents a field in a record expression
pub const RecordField = struct {
pub const Idx = enum(u32) { _ };
pub const Span = struct { span: base.DataSpan };
name: base.Ident.Idx,
value: Expr.Idx,
pub fn pushToSExprTree(self: *const RecordField, cir: anytype, tree: anytype) !void {
const begin = tree.beginNode();
try tree.pushStaticAtom("field");
try tree.pushStringPair("name", cir.getIdent(self.name));
const attrs = tree.beginNode();
try cir.store.getExpr(self.value).pushToSExprTree(cir, tree, self.value);
try tree.endNode(begin, attrs);
}
};
/// Represents an external declaration from another module
pub const ExternalDecl = struct {
/// Fully qualified name (e.g., "json.Json.utf8")
qualified_name: base.Ident.Idx,
/// Module this decl comes from (e.g., "json.Json")
module_name: base.Ident.Idx,
/// Local name within that module (e.g., "utf8")
local_name: base.Ident.Idx,
/// Type variable for this declaration
type_var: TypeVar,
/// Kind of external declaration
kind: enum { value, type },
/// Region where this was referenced
region: Region,
pub const Idx = enum(u32) { _ };
pub const Span = struct { span: base.DataSpan };
/// A safe list of external declarations
pub const SafeList = collections.SafeList(ExternalDecl);
pub fn pushToSExprTree(self: *const ExternalDecl, cir: anytype, tree: anytype) !void {
const node = tree.beginNode();
try tree.pushStaticAtom("ext-decl");
try cir.appendRegionInfoToSExprTreeFromRegion(tree, self.region);
// Add fully qualified name
try tree.pushStringPair("ident", cir.getIdent(self.qualified_name));
// Add kind
switch (self.kind) {
.value => try tree.pushStringPair("kind", "value"),
.type => try tree.pushStringPair("kind", "type"),
}
const attrs = tree.beginNode();
try tree.endNode(node, attrs);
}
pub fn pushToSExprTreeWithRegion(self: *const ExternalDecl, cir: anytype, tree: anytype, region: Region) !void {
const node = tree.beginNode();
try tree.pushStaticAtom("ext-decl");
try cir.appendRegionInfoToSExprTreeFromRegion(tree, region);
// Add fully qualified name
try tree.pushStringPair("ident", cir.getIdent(self.qualified_name));
// Add kind
switch (self.kind) {
.value => try tree.pushStringPair("kind", "value"),
.type => try tree.pushStringPair("kind", "type"),
}
const attrs = tree.beginNode();
try tree.endNode(node, attrs);
}
};
// Real Report type from the reporting module
pub const Report = reporting.Report;
/// Checks if a type is castable for index type conversions
pub fn isCastable(comptime T: type) bool {
return switch (T) {
Expr.Idx,
Pattern.Idx,
Statement.Idx,
TypeAnno.Idx,
Def.Idx,
TypeHeader.Idx,
RecordField.Idx,
Pattern.RecordDestruct.Idx,
Expr.IfBranch.Idx,
Expr.Match.Branch.Idx,
WhereClause.Idx,
Annotation.Idx,
TypeAnno.RecordField.Idx,
ExposedItem.Idx,
Expr.Match.BranchPattern.Idx,
PatternRecordField.Idx,
Node.Idx,
TypeVar,
=> true,
else => false,
};
}
/// Safely casts between compatible index types
pub fn castIdx(comptime From: type, comptime To: type, idx: From) To {
return @as(To, @enumFromInt(@intFromEnum(idx)));
}