authorgravatar for me@paperdave.netdave caruso <me@paperdave.net> 2024-07-27 02:27:50-07:00
committergravatar for me@paperdave.netdave caruso <me@paperdave.net> 2024-07-27 02:28:31-07:00
log04faedd592fa4b470cf574b0c14c87cdea2d95a4
treed28d440450cee0f0fb2aaa6a2cc8cab33fd73b3f
parentd28747e59ec8a41c28a8dde92b0fdf533df836cb
signature Commit is signed but in an unrecognized format.

feat: implement result types and inferred struct literals (#76)

result types (not to be confused with Rust's `Result`) are the semantics of being able to reflect the type of an expression at compile time before it is analyzed. this is how inferred struct literals can figure out what type they are before the actual coercion happens.

13 files changed, 1320 insertions(+), 995 deletions(-)

CHANGELOG.md+2
......@@ -1,6 +1,8 @@
11## 0.1.5
22
33- feat: clarify "defined runtime representation" and "can be represented at runtime"
4- feat: implement inferred structs + result type semantics
5- chore: remove group expressions, use single expression blocks instead
46- fix(sema): fix passing structs as function params
57- fix: diagnostic box drawing characters line up after line 9
68- fix: diagnostics on last line of file print correctly
build.zig+1
......@@ -103,6 +103,7 @@ pub fn build(b: *std.Build) !void {
103103 const manual_out = manual_run.addOutputFileArg("language-reference.html");
104104 const install_manual = b.addInstallFile(manual_out, "doc/README.html");
105105 b.step("manual", "build language reference").dependOn(&install_manual.step);
106 b.install_tls.step.dependOn(&install_manual.step);
106107
107108 // const wasm_api = b.addExecutable(.{
108109 // .name = "compiler",
doc/language-manual.md+3-4
......@@ -176,13 +176,12 @@ print(center.x, center.y);
176176
177177fn addPositions(a: Position, b: Position) -> Position {
178178 // To allow less verbose code, the struct type can be inferred with `.`
179 // TODO: that is not implemented ;-;
180 Position{ x: a.x + b.x, y: a.y + b.y }
179 .{ x: a.x + b.x, y: a.y + b.y }
181180}
182181
183182let added = addPositions(
184 Position{ x: 1, y: -4 },
185 Position{ x: 5, y: 8 },
183 .{ x: 1, y: -4 },
184 .{ x: 5, y: 8 },
186185);
187186print("Add result:", added.x, added.y);
188187```
src/core/Ast.zig+7-6
......@@ -1,7 +1,7 @@
11//! The Abstract Syntax Tree preserves most details of the source code,
22//! as it is used for formatting. Before semantic analysis, this
33//! structure is normalized into `Teg`, which removes constructs like
4//! grouping, unneeded let bindings, and other transforms.
4//! whitespace, unneeded let bindings, and other transforms.
55//!
66//! `ast.nodes` contains nodes stored as three parts:
77//! - tag: the type of the node
......@@ -130,10 +130,6 @@ pub const Node = struct {
130130 /// main_token is the `break`
131131 /// data is .@"break"
132132 @"break",
133 /// `(expr)`
134 /// main_token is the `(`
135 /// data is .un
136 group,
137133 /// `lhs + rhs`. main_token is the operator.
138134 add,
139135 /// `lhs - rhs`. main_token is the operator.
......@@ -222,6 +218,10 @@ pub const Node = struct {
222218 array_type,
223219 /// Array initialization
224220 array_init,
221 /// Tuple type
222 tuple_type,
223 /// Tuple Init
224 tuple_init,
225225
226226 pub fn isAssignBin(op: Tag) bool {
227227 return switch (op) {
......@@ -309,7 +309,6 @@ pub const Node = struct {
309309 .fn_decl = .function,
310310 .fn_expr = .function,
311311 .ge = .bin,
312 .group = .un,
313312 .gt = .bin,
314313 .id = .none,
315314 .index = .bin,
......@@ -344,6 +343,8 @@ pub const Node = struct {
344343 .sub = .bin,
345344 .sub_assign = .bin,
346345 .var_decl = .var_decl,
346 .tuple_type = .list,
347 .tuple_init = .list,
347348 // sort-lines:end
348349 });
349350
src/core/Parse.zig+22-32
......@@ -31,7 +31,7 @@ pub fn parse(gpa: std.mem.Allocator, source: [:0]const u8, ast_error: *AstError.
3131 .err = err,
3232 .byte = err_byte,
3333 } },
34 .trace = Report.DebugStoredTrace.from(@errorReturnTrace()),
34 .trace = DebugStoredTrace.from(@errorReturnTrace()),
3535 };
3636 return error.ParseFail;
3737 },
......@@ -143,7 +143,7 @@ pub const AstError = union(enum) {
143143
144144 array_type_flipped_hint: Token,
145145
146 pub fn toReport(err: AstError, comp: *Comp, file: *Comp.File, trace: Report.DebugStoredTrace) !Report {
146 pub fn toReport(err: AstError, comp: *Comp, file: *Comp.File, trace: DebugStoredTrace) !Report {
147147 return (Report.Err{ .ast = err }).toReport(comp, file, trace);
148148 }
149149
......@@ -193,7 +193,7 @@ pub const AstError = union(enum) {
193193
194194 pub const WithTrace = struct {
195195 err: AstError,
196 trace: Report.DebugStoredTrace,
196 trace: DebugStoredTrace,
197197
198198 pub fn toReport(err: WithTrace, comp: *Comp, file: *Comp.File) !Report {
199199 return err.err.toReport(comp, file, err.trace);
......@@ -856,7 +856,7 @@ fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex {
856856 .kw_return => try p.parseReturn(),
857857 .start_brace => try p.parseBlock(),
858858 .start_bracket => try p.parseArrayValueOrType(mode),
859 .start_paren => try p.parseGroup(),
859 .start_paren => try p.parseTupleType(),
860860
861861 // Unary operators
862862 inline .bang,
......@@ -1136,25 +1136,6 @@ fn parseElseValue(p: *Parse) Error!NodeIndex {
11361136 };
11371137}
11381138
1139fn parseGroup(p: *Parse) Error!NodeIndex {
1140 const trace = t.trace(@src());
1141 defer trace.end();
1142
1143 const main_token = p.assertToken(.start_paren);
1144
1145 const expr = try p.parseExpression(.expression);
1146
1147 _ = try p.expect(.end_paren);
1148
1149 return try p.addNode(.{
1150 .tag = .group,
1151 .main_token = main_token,
1152 .data = .{ .un = .{
1153 .expr = expr,
1154 } },
1155 });
1156}
1157
11581139fn parseAnonInstantiation(p: *Parse) Error!NodeIndex {
11591140 const trace = t.trace(@src());
11601141 defer trace.end();
......@@ -1266,6 +1247,16 @@ fn parseArrayValueOrType(p: *Parse, mode: ParseExprMode) Error!NodeIndex {
12661247 });
12671248}
12681249
1250fn parseTupleType(p: *Parse) Error!NodeIndex {
1251 const main_token = p.assertToken(.start_bracket);
1252 const list = try p.parseExpressionList(.end_paren, .expression);
1253 return p.addNode(.{
1254 .tag = .tuple_type,
1255 .main_token = main_token,
1256 .data = .{ .list = list },
1257 });
1258}
1259
12691260// helpers
12701261
12711262/// Append a node and return its index.
......@@ -1292,7 +1283,7 @@ fn addExtra(p: *Parse, comptime T: type, extra: T) !ExtraIndex {
12921283fn fail(p: *Parse, ast_error: AstError) Error {
12931284 p.ast_error = .{
12941285 .err = ast_error,
1295 .trace = Report.DebugStoredTrace.capture(@returnAddress()),
1286 .trace = DebugStoredTrace.capture(@returnAddress()),
12961287 };
12971288 return Error.ParseFail;
12981289}
......@@ -1366,10 +1357,6 @@ fn nextToken(p: *Parse) TokenIndex {
13661357 return result;
13671358}
13681359
1369fn tokensOnSameLine(p: *Parse, token1: TokenIndex, token2: TokenIndex) bool {
1370 return std.mem.indexOfScalar(u8, p.source[p.token_starts[token1]..p.token_starts[token2]], '\n') == null;
1371}
1372
13731360fn fullToken(p: *Parse, token: TokenIndex) Token {
13741361 return Token{
13751362 .tag = p.token_tags[token],
......@@ -1398,11 +1385,14 @@ const mem = std.mem;
13981385const Allocator = mem.Allocator;
13991386const ArrayListUnmanaged = std.ArrayListUnmanaged;
14001387
1401const Tokenizer = @import("Tokenizer.zig");
1388const core = @import("mod.zig");
1389const DebugStoredTrace = core.DebugStoredTrace;
1390
1391const Tokenizer = core.Tokenizer;
14021392const Token = Tokenizer.Token;
14031393const ByteOffset = Tokenizer.ByteOffset;
14041394
1405const Ast = @import("Ast.zig");
1395const Ast = core.Ast;
14061396const Node = Ast.Node;
14071397const Data = Node.Data;
14081398const SubList = Ast.SubList;
......@@ -1418,5 +1408,5 @@ const optionalExtraIndex = OptionalExtraIndex.init;
14181408
14191409const t = @import("tracer");
14201410
1421const Report = @import("Report.zig");
1422const Comp = @import("Comp.zig");
1411const Report = core.Report;
1412const Comp = core.Comp;
src/core/Report.zig+26-64
......@@ -135,6 +135,17 @@ pub const Err = union(enum) {
135135 );
136136 }
137137 },
138 missing_result_type: struct {
139 node: NodeIndex,
140
141 fn build(data: @This(), ctx: *MsgContext) !void {
142 try ctx.msgNode(
143 data.node,
144 "Cannot infer struct initialization type",
145 .{},
146 );
147 }
148 },
138149 unresolved_label: struct {
139150 node: NodeIndex,
140151
......@@ -523,62 +534,6 @@ pub const Warn = union(enum) {
523534 },
524535};
525536
526/// A variant of std.builtin.StackTrace that stores its data within itself
527/// instead of being a pointer. This allows capturing the stack trace and
528/// saving it for later printing.
529pub const StoredTrace = struct {
530 data: [32]usize,
531 index: usize,
532
533 pub const empty = .{
534 .data = .{0} ** 32,
535 .index = 0,
536 };
537
538 fn trace(stored: *StoredTrace) std.builtin.StackTrace {
539 return .{
540 .index = stored.index,
541 .instruction_addresses = &stored.data,
542 };
543 }
544
545 pub fn capture(begin: ?usize) StoredTrace {
546 var stored: StoredTrace = undefined;
547 var frame = stored.trace();
548 std.debug.captureStackTrace(begin orelse @returnAddress(), &frame);
549 stored.index = frame.index;
550 return stored;
551 }
552
553 pub fn from(stack_trace: ?*std.builtin.StackTrace) StoredTrace {
554 if (stack_trace) |stack| {
555 var data: [32]usize = undefined;
556 @memset(&data, 0);
557 const items = @min(stack.instruction_addresses.len, 32);
558 @memcpy(data[0..items], stack.instruction_addresses[0..items]);
559 return .{
560 .data = data,
561 .index = @min(items, stack.index),
562 };
563 } else {
564 return empty;
565 }
566 }
567};
568
569pub const DebugStoredTrace = if (builtin.mode == .Debug)
570 StoredTrace
571else
572 struct {
573 pub fn capture(_: ?usize) @This() {
574 return .{};
575 }
576
577 pub fn from(_: ?*std.builtin.StackTrace) @This() {
578 return .{};
579 }
580 };
581
582537pub const UnusedKind = enum {
583538 param,
584539 constant,
......@@ -817,7 +772,7 @@ pub const Builder = struct {
817772 try b.errors.append(b.gpa, .{
818773 .data = .{ .@"error" = @unionInit(Err, @tagName(kind), data) },
819774 .url = try b.currentFileString(),
820 .debug_stack_trace = if (builtin.mode == .Debug) StoredTrace.capture(@returnAddress()) else .{},
775 .debug_stack_trace = DebugStoredTrace.capture(@returnAddress()),
821776 });
822777 }
823778
......@@ -825,7 +780,7 @@ pub const Builder = struct {
825780 try b.warnings.append(b.gpa, .{
826781 .data = .{ .warn = @unionInit(Warn, @tagName(kind), data) },
827782 .url = try b.currentFileString(),
828 .debug_stack_trace = if (builtin.mode == .Debug) StoredTrace.capture(@returnAddress()) else .{},
783 .debug_stack_trace = DebugStoredTrace.capture(@returnAddress()),
829784 });
830785 }
831786
......@@ -1050,7 +1005,7 @@ pub const LazyNode = struct {
10501005 const node_tag = ctx.ast.nodeTag(base_node);
10511006
10521007 switch (teg.instTag(lazy.inst)) {
1053 .as => switch (node_tag) {
1008 .coercion => switch (node_tag) {
10541009 .var_decl => switch (lazy.operand) {
10551010 .lhs => return data.var_decl.type.unwrap().?,
10561011 .rhs => return data.var_decl.init,
......@@ -1121,6 +1076,10 @@ pub const LazyNode = struct {
11211076 const items = data.list.slice(ctx.ast);
11221077 return items[lazy.operand.getArgId()];
11231078 },
1079 .field => {
1080 assert(node_tag == .field);
1081 return base_node;
1082 },
11241083 else => {},
11251084 }
11261085
......@@ -1358,19 +1317,22 @@ const builtin = @import("builtin");
13581317
13591318const unicode_props = @import("unicode_props.zig");
13601319
1361const Tokenizer = @import("Tokenizer.zig");
1320const core = @import("mod.zig");
1321const DebugStoredTrace = core.DebugStoredTrace;
1322
1323const Tokenizer = core.Tokenizer;
13621324const ByteOffset = Tokenizer.ByteOffset;
13631325
1364const Ast = @import("Ast.zig");
1326const Ast = core.Ast;
13651327const TokenIndex = Ast.TokenIndex;
13661328const NodeIndex = Ast.NodeIndex;
13671329const Location = Ast.Location;
13681330
1369const Teg = @import("Teg.zig");
1331const Teg = core.Teg;
13701332
1371const Comp = @import("Comp.zig");
1333const Comp = core.Comp;
13721334const File = Comp.File;
13731335
1374const InternPool = @import("InternPool.zig");
1336const InternPool = core.InternPool;
13751337const Value = InternPool.Value;
13761338const Type = InternPool.Type;
src/core/Sema.zig+152-55
......@@ -66,24 +66,30 @@ pub const Error = error{
6666 OutOfMemory,
6767} || Comp.File.GetTegError;
6868
69/// Instead of using a Hash map for this, just pre-allocate a list long enough to hold everything.
70/// This is fine because all instructions are going to be adjacent in a single declaration.
69/// Instead of using a Hash map for this, all of the information is pre-allocated.
70/// This is only possible because all instructions are adjacent in Teg.
7171const InstMap = struct {
7272 items: []Air.Inst.Ref.Optional,
7373 start: Teg.Inst.Index,
74 debug_traces: if (std.debug.runtime_safety) []StoredTrace else void,
7475
7576 pub fn init(allocator: Allocator, code: *const Teg, inst_start: Teg.Inst.Index) !InstMap {
7677 const decl = code.declData(inst_start);
7778 const insts = try allocator.alloc(Air.Inst.Ref.Optional, decl.instruction_count);
79 const debug_traces = if (std.debug.runtime_safety)
80 try allocator.alloc(StoredTrace, decl.instruction_count);
7881 @memset(insts, .none);
7982 return .{
8083 .items = insts,
8184 .start = inst_start,
85 .debug_traces = debug_traces,
8286 };
8387 }
8488
8589 pub fn deinit(map: *InstMap, allocator: Allocator) void {
8690 allocator.free(map.items);
91 if (std.debug.runtime_safety)
92 allocator.free(map.debug_traces);
8793 map.* = undefined;
8894 }
8995
......@@ -95,16 +101,21 @@ const InstMap = struct {
95101 }
96102
97103 pub fn add(map: *InstMap, from: Teg.Inst.Index, to: Air.Inst.Ref) !void {
104 const source_i = @intFromEnum(from);
105 const start_i = @intFromEnum(map.start);
106 const index = source_i - start_i;
98107 if (assertFailed(map.get(from) == null)) {
99108 const s: *const Sema = @fieldParentPtr("inst_map", map);
109 std.debug.print("Original was added here:\n", .{});
110 std.debug.dumpStackTrace(map.debug_traces[index].trace());
100111 std.debug.panic("%{d} is already mapped (to {})", .{
101112 @intFromEnum(from),
102113 map.get(from).?.dump(s.ip),
103114 });
104115 }
105 const source_i = @intFromEnum(from);
106 const start_i = @intFromEnum(map.start);
107 map.items[source_i - start_i] = to.toOptional();
116 map.items[index] = to.toOptional();
117 if (std.debug.runtime_safety)
118 map.debug_traces[index] = StoredTrace.capture(@returnAddress());
108119 }
109120};
110121
......@@ -216,36 +227,44 @@ pub fn analyzeFunctionDeclaration(s: *Sema) Error!Value {
216227 s.params[i] = ty;
217228 }
218229
219 const return_type = try s.resolveType(
220 s.inst_map.start.op(.return_type),
221 fn_decl.return_type,
222 );
230 var return_type, const early_value = if (fn_decl.return_type.unwrap()) |ty| brk: {
231 const return_type = try s.resolveType(
232 s.inst_map.start.op(.return_type),
233 ty,
234 );
235 const value = try s.makeValue(.{
236 .func = .{
237 .ty = try s.makeType(.{
238 .function_type = .{
239 .param_types = s.params,
240 .return_type = return_type,
241 },
242 }),
243 .owner_decl = s.decl_index,
244 .teg_body_inst = s.decl.teg_decl_index,
245 },
246 });
223247
224 const func_prototype = (try s.ip.get(s.gpa, .{
225 .function_type = .{
226 .param_types = s.params,
227 .return_type = return_type,
228 },
229 })).toTypeUnsafe(s.ip);
230 const value = try s.ip.get(s.gpa, .{
231 .func = .{
232 .ty = func_prototype,
233 .owner_decl = s.decl_index,
234 .teg_body_inst = s.decl.teg_decl_index,
235 },
236 });
248 s.decl.raw_value = value.typeOf(s.ip).value();
249 s.decl.analysis = .in_progress_type_available;
237250
238 s.decl.raw_value = value.typeOf(s.ip).value();
239 s.decl.analysis = .in_progress_type_available;
251 break :brk .{ return_type, value };
252 } else .{ null, null };
240253
241254 const root_block = decl.body;
242255 switch (root_block.decode()) {
243256 .value => |val| {
244 const coerced = try s.coerceValue(
245 s.inst_map.start.op(.operand),
246 val,
247 return_type,
248 );
257 const coerced = if (return_type) |ty|
258 try s.coerceValue(
259 s.inst_map.start.op(.operand),
260 val,
261 ty,
262 )
263 else brk: {
264 return_type = val.typeOf(s.ip);
265 break :brk val;
266 };
267
249268 const block = try s.addBlock();
250269 _ = try s.addInstRef(.@"break", .{
251270 .block_ref = block.block_index,
......@@ -253,11 +272,30 @@ pub fn analyzeFunctionDeclaration(s: *Sema) Error!Value {
253272 });
254273 },
255274 .index => |index| {
256 try s.temp_result_map.putNoClobber(s.arena, index, return_type);
257 _ = try s.analyzeBlock(index);
275 if (return_type) |ty| {
276 try s.temp_result_map.putNoClobber(s.arena, index, ty);
277 }
278
279 const out = try s.analyzeBlock(index);
280
281 return_type = return_type orelse s.typeOfRef(out);
258282 },
259283 }
260284
285 const value = early_value orelse try s.makeValue(.{
286 .func = .{
287 .ty = try s.makeType(.{
288 .function_type = .{
289 .param_types = s.params,
290 .return_type = return_type orelse
291 unreachable, // type is reassigned above
292 },
293 }),
294 .owner_decl = s.decl_index,
295 .teg_body_inst = s.decl.teg_decl_index,
296 },
297 });
298
261299 s.decl.analysis = .complete;
262300 s.decl.raw_value = value;
263301
......@@ -342,7 +380,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
342380 return s.addInstRef(.js_global_ref, operand.asString(s.ip));
343381 },
344382 .field => {
345 const field: Teg.Inst.Bin = s.code.instPayload(.field, in);
383 const field: Teg.Bin = s.code.instPayload(.field, in);
346384 const base = try s.resolveRef(field.lhs);
347385
348386 const rhs = try s.resolveCoerceValue(in.op(.rhs), field.rhs, .string);
......@@ -402,7 +440,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
402440 });
403441 },
404442 .load_decl => {
405 const request: Teg.Inst.StrNode = s.code.instData(.load_decl, in);
443 const request: Teg.StrNode = s.code.instData(.load_decl, in);
406444 const decl_name = try s.ip.getOrPutString(
407445 s.gpa,
408446 request.string.slice(s.code.string_bytes),
......@@ -416,7 +454,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
416454 return resolved.toAir();
417455 },
418456 .param => {
419 const param: Teg.Inst.DataNode = s.code.instData(.param, in);
457 const param: Teg.DataNode = s.code.instData(.param, in);
420458 return s.addInstRef(.arg, .{
421459 .type = s.params[param.data],
422460 .arg_number = param.data,
......@@ -430,7 +468,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
430468 .gt,
431469 .eq,
432470 => |tag| {
433 const bin: Teg.Inst.Bin = s.code.instPayload(tag, in);
471 const bin: Teg.Bin = s.code.instPayload(tag, in);
434472 const op = @field(BinaryOp, @tagName(tag));
435473
436474 const lhs = try s.resolveRef(bin.lhs);
......@@ -458,7 +496,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
458496 });
459497 },
460498 .bool_not => {
461 const un: Teg.Inst.RefNode = s.code.instData(.bool_not, in);
499 const un: Teg.RefNode = s.code.instData(.bool_not, in);
462500
463501 const operand = try s.resolveCoerceRef(in.op(.operand), un.operand, .bool);
464502
......@@ -474,7 +512,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
474512 return s.addInstRef(.bool_not, .{ .operand = operand });
475513 },
476514 .negate => {
477 const un: Teg.Inst.RefNode = s.code.instData(.negate, in);
515 const un: Teg.RefNode = s.code.instData(.negate, in);
478516
479517 const operand = try s.resolveRef(un.operand);
480518
......@@ -496,7 +534,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
496534 return s.addInstRef(.negate, .{ .operand = operand });
497535 },
498536 .@"break" => {
499 const brk: Teg.Inst.Break = s.code.instPayload(.@"break", in);
537 const brk: Teg.Break = s.code.instPayload(.@"break", in);
500538 const operand = try s.resolveRef(brk.operand);
501539 const block_ref = s.inst_map.get(brk.block).?.decode().index; // block ref is resolved by teg
502540
......@@ -511,7 +549,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
511549 });
512550 },
513551 .call => {
514 const call: Teg.Inst.Call = s.code.instPayload(.call, in);
552 const call: Teg.Call = s.code.instPayload(.call, in);
515553 const base = try s.resolveRef(call.base);
516554 const base_type = s.typeOfRef(base);
517555
......@@ -568,7 +606,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
568606 });
569607 },
570608 .js_new => {
571 const call: Teg.Inst.JsNew = s.code.instPayload(.js_new, in);
609 const call: Teg.JsNew = s.code.instPayload(.js_new, in);
572610 const base = try s.resolveRef(call.base);
573611
574612 const scratch = try s.arena.alloc(Air.Inst.Ref, call.args.len);
......@@ -612,7 +650,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
612650 return InternPool.Value.void.toAir();
613651 },
614652 .@"if" => {
615 const data: Teg.Inst.If = s.code.instData(.@"if", in).extra_index.get(&s.code);
653 const data: Teg.If = s.code.instData(.@"if", in).extra_index.get(&s.code);
616654
617655 const cond_raw = try s.resolveRef(data.cond);
618656 const cond = try s.coerceRef(in.op(.operand), cond_raw, .bool);
......@@ -661,7 +699,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
661699 });
662700 },
663701 .store => {
664 const data: Teg.Inst.Store = s.code.instPayload(.store, in);
702 const data: Teg.Store = s.code.instPayload(.store, in);
665703 assert(s.code.instTag(data.memory) == .memory); // corrupt store instruction
666704
667705 const location = s.mapTeg(data.memory.toRef()).decode().index;
......@@ -677,24 +715,24 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
677715 .operand = operand_coerced,
678716 });
679717 },
680 .as => {
681 const as: Teg.Inst.As = s.code.instPayload(.as, in);
718 .coercion => {
719 const as: Teg.Coercion = s.code.instPayload(.coercion, in);
682720 const coerce_to = try s.resolveCoerceValue(in.op(.lhs), as.type, .type);
683721 const operand = try s.resolveRef(as.operand);
684722 return try s.coerceRef(in.op(.rhs), operand, coerce_to.toType(s.ip).?);
685723 },
686724 .@"comptime" => {
687 const node: Teg.Inst.RefNode = s.code.instData(.@"comptime", in);
725 const node: Teg.RefNode = s.code.instData(.@"comptime", in);
688726 const value = try s.resolveValue(node.operand);
689727 return value.toAir();
690728 },
691729 .type_of => {
692 const node: Teg.Inst.RefNode = s.code.instData(.type_of, in);
730 const node: Teg.RefNode = s.code.instData(.type_of, in);
693731 const value = try s.resolveValue(node.operand);
694732 return value.typeOf(s.ip).value().toAir();
695733 },
696734 .compile_error => {
697 const node: Teg.Inst.RefNode = s.code.instData(.compile_error, in);
735 const node: Teg.RefNode = s.code.instData(.compile_error, in);
698736 const value = try s.resolveCoerceValue(
699737 in.op(operandArgument(0)),
700738 node.operand,
......@@ -707,7 +745,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
707745 });
708746 },
709747 .struct_decl => {
710 const node: Teg.Inst.StructDecl = s.code.instPayload(.struct_decl, in);
748 const node: Teg.StructDecl = s.code.instPayload(.struct_decl, in);
711749
712750 const scratch = try s.scratch.reserveMulti(
713751 s.arena,
......@@ -732,7 +770,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
732770 } });
733771 },
734772 .struct_init => {
735 const node: Teg.Inst.StructInit = s.code.instPayload(.struct_init, in);
773 const node: Teg.StructInit = s.code.instPayload(.struct_init, in);
736774
737775 // Ensure we have a valid struct to operate on
738776 const ty = try s.resolveType(in.op(.operand), node.base_type);
......@@ -835,9 +873,8 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
835873 });
836874 }
837875 },
838
839876 .array_init => {
840 const node: Teg.Inst.ArrayInit = s.code.instPayload(.array_init, in);
877 const node: Teg.ArrayInit = s.code.instPayload(.array_init, in);
841878
842879 assert(node.list.len > 0);
843880
......@@ -894,13 +931,72 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
894931 });
895932 }
896933 },
897
898934 .array_type => {
899 const un: Teg.Inst.RefNode = s.code.instData(.array_type, in);
935 const un: Teg.RefNode = s.code.instData(.array_type, in);
900936 const operand = try s.resolveType(in.op(.operand), un.operand);
901937 return s.makeValueAir(.{ .array_type = .{ .item_type = operand } });
902938 },
903939
940 .result_type_argument => {
941 const result: Teg.ResultTypeArgument = s.code.instData(.result_type_argument, in);
942
943 const operand = try s.resolveRef(result.call_base);
944 const ty = s.typeOfRef(operand);
945
946 const func: InternPool.Key.FunctionType = ty.toFunctionPrototype(s.ip) orelse
947 return Value.opaque_type.toAir();
948
949 if (result.argument >= func.param_types.len)
950 return Value.opaque_type.toAir();
951
952 return func.param_types[result.argument].value().toAir();
953 },
954
955 .result_type_array_value => {
956 const result: Teg.ResultTypeArrayValue = s.code.instData(.result_type_array_value, in);
957
958 const operand = try s.resolveValue(result.type);
959 const ty = operand.toType(s.ip) orelse
960 return Value.opaque_type;
961
962 const array_type = ty.toArrayType(s.ip) orelse
963 return Value.opaque_type.toAir();
964
965 return array_type.item_type.value().toAir();
966 },
967
968 .result_type_tuple_field => {
969 const result: Teg.ResultTypeTupleField = s.code.instData(.result_type_array_value, in);
970
971 const operand = try s.resolveValue(result.type);
972 const ty = operand.toType(s.ip) orelse
973 return Value.opaque_type;
974
975 _ = ty;
976
977 @panic("TODO: tuple result type resolution"); // tuples are not implemented
978 },
979
980 .result_type_struct_field => {
981 const result: Teg.ResultTypeStructField = s.code.instData(.result_type_struct_field, in);
982
983 const operand = try s.resolveValue(result.type);
984 const ty = operand.toType(s.ip) orelse
985 return Value.opaque_type;
986
987 const struct_type = ty.toStructType(s.ip) orelse
988 return Value.opaque_type.toAir();
989
990 const to_match = result.field.slice(s.code.string_bytes);
991 for (struct_type.field_keys, struct_type.field_types) |k, v| {
992 if (k.eqlSlice(to_match, s.ip)) {
993 return v.value().toAir();
994 }
995 }
996
997 return Value.opaque_type.toAir();
998 },
999
9041000 else => |tag| std.debug.panic("TODO: analyze of .{s}", .{@tagName(tag)}),
9051001 }
9061002}
......@@ -1001,13 +1097,13 @@ fn resolveValue(s: *Sema, ref: Teg.Inst.Ref) !Value {
10011097 const break_inst = indexes[1];
10021098
10031099 if (s.code.instTag(break_inst) != .@"break") break :special_cased;
1004 const break_data: Teg.Inst.Break = s.code.instPayload(.@"break", break_inst);
1100 const break_data: Teg.Break = s.code.instPayload(.@"break", break_inst);
10051101 if (break_data.operand != load_inst.toRef()) break :special_cased;
10061102 if (break_data.block != index) break :special_cased;
10071103
10081104 if (s.code.instTag(load_inst) != .load_decl) break :special_cased;
10091105
1010 const request: Teg.Inst.StrNode = s.code.instData(.load_decl, load_inst);
1106 const request: Teg.StrNode = s.code.instData(.load_decl, load_inst);
10111107 const decl_name = try s.ip.getOrPutString(
10121108 s.gpa,
10131109 request.string.slice(s.code.string_bytes),
......@@ -1620,6 +1716,7 @@ const DynamicBitSetUnmanaged = std.DynamicBitSetUnmanaged;
16201716
16211717const core = @import("mod.zig");
16221718const assertFailed = core.assertFailed;
1719const StoredTrace = core.StoredTrace;
16231720
16241721const Comp = core.Comp;
16251722const File = Comp.File;
src/core/StoredTrace.zig created+65
......@@ -0,0 +1,65 @@
1//! A variant of std.builtin.StackTrace that stores its data within itself
2//! instead of being a pointer. This allows capturing the stack trace and
3//! saving it for later printing.
4
5data: [31]usize,
6index: usize,
7
8pub const empty = .{
9 .data = .{0} ** 31,
10 .index = 0,
11};
12
13pub fn trace(stored: *StoredTrace) std.builtin.StackTrace {
14 return .{
15 .index = stored.index,
16 .instruction_addresses = &stored.data,
17 };
18}
19
20pub fn capture(begin: ?usize) StoredTrace {
21 var stored: StoredTrace = undefined;
22 var frame = stored.trace();
23 std.debug.captureStackTrace(begin orelse @returnAddress(), &frame);
24 stored.index = frame.index;
25 return stored;
26}
27
28pub fn from(stack_trace: ?*std.builtin.StackTrace) StoredTrace {
29 if (stack_trace) |stack| {
30 var data: [31]usize = undefined;
31 @memset(&data, 0);
32 const items = @min(stack.instruction_addresses.len, 32);
33 @memcpy(data[0..items], stack.instruction_addresses[0..items]);
34 return .{
35 .data = data,
36 .index = @min(items, stack.index),
37 };
38 } else {
39 return empty;
40 }
41}
42
43pub const BlankImpl = struct {
44 pub fn trace(_: *BlankImpl) std.builtin.StackTrace {
45 @compileError("trace is not available with StoredTrace.BlankImpl");
46 }
47
48 pub fn capture(_: ?usize) @This() {
49 return .{};
50 }
51
52 pub fn from(_: ?*std.builtin.StackTrace) @This() {
53 return .{};
54 }
55};
56
57pub const DebugStoredTrace = if (builtin.mode == .Debug)
58 StoredTrace
59else
60 BlankImpl;
61
62pub const StoredTrace = @This();
63
64const std = @import("std");
65const builtin = @import("builtin");
src/core/Teg.Dump.zig+48-16
......@@ -68,9 +68,11 @@ const State = struct {
6868 try writer.writeAll("params: ");
6969 try s.dumpRefList(writer, fn_data.params.slice(teg), '[', ']');
7070 try s.newline(writer);
71 try writer.writeAll("return_type: ");
72 try s.dumpRef(writer, fn_data.return_type);
73 try s.newline(writer);
71 if (fn_data.return_type.unwrap()) |return_type| {
72 try writer.writeAll("return_type: ");
73 try s.dumpRef(writer, return_type);
74 try s.newline(writer);
75 }
7476 },
7577 .immutable => {},
7678 }
......@@ -129,8 +131,8 @@ const State = struct {
129131 try s.dumpRefMarker(writer, data.rhs);
130132 try writer.writeAll(")");
131133 },
132 .as => {
133 const data = teg.instData(.as, inst).extra_index.get(teg);
134 .coercion => {
135 const data = teg.instData(.coercion, inst).extra_index.get(teg);
134136 try writer.writeAll("(");
135137 try s.dumpRefMarker(writer, data.type);
136138 try writer.writeAll(", ");
......@@ -138,7 +140,7 @@ const State = struct {
138140 try writer.writeAll(")");
139141 },
140142 .@"break" => {
141 const data: Teg.Inst.Break = teg.instData(.@"break", inst).extra_index.get(teg);
143 const data: Teg.Break = teg.instData(.@"break", inst).extra_index.get(teg);
142144 try writer.writeAll("(");
143145 try s.dumpRefMarker(writer, data.block.toRef());
144146 try writer.writeAll(", ");
......@@ -146,7 +148,7 @@ const State = struct {
146148 try writer.writeAll(")");
147149 },
148150 .store => {
149 const data: Teg.Inst.Store = teg.instData(.store, inst).extra_index.get(teg);
151 const data: Teg.Store = teg.instData(.store, inst).extra_index.get(teg);
150152 try writer.writeAll("(");
151153 try s.dumpRefMarker(writer, data.memory.toRef());
152154 try writer.writeAll(", ");
......@@ -164,7 +166,6 @@ const State = struct {
164166 inline .memory,
165167 .discard,
166168 .ensure_void,
167 .ensure_bool,
168169 .negate,
169170 .bool_not,
170171 .bit_not,
......@@ -181,7 +182,7 @@ const State = struct {
181182 try writer.writeAll(")");
182183 },
183184 .call => {
184 const call: Teg.Inst.Call = teg.instData(.call, inst).extra_index.get(teg);
185 const call: Teg.Call = teg.instData(.call, inst).extra_index.get(teg);
185186 try writer.writeAll("(");
186187 try s.dumpRefMarker(writer, call.base);
187188 try writer.writeAll(",");
......@@ -189,7 +190,7 @@ const State = struct {
189190 try writer.writeAll(")");
190191 },
191192 .js_new => {
192 const call: Teg.Inst.JsNew = teg.instData(.js_new, inst).extra_index.get(teg);
193 const call: Teg.JsNew = teg.instData(.js_new, inst).extra_index.get(teg);
193194 try writer.writeAll("(");
194195 try s.dumpRefMarker(writer, call.base);
195196 try writer.writeAll(",");
......@@ -197,14 +198,14 @@ const State = struct {
197198 try writer.writeAll(")");
198199 },
199200 .load_decl => {
200 const call: Teg.Inst.StrNode = teg.instData(.load_decl, inst);
201 const call: Teg.StrNode = teg.instData(.load_decl, inst);
201202
202203 try tty.setColor(writer, .red);
203204 try writer.print(" {s}", .{call.string.slice(teg.string_bytes)});
204205 try tty.setColor(writer, .reset);
205206 },
206207 .@"if" => {
207 const data: Teg.Inst.If = teg.instPayload(.@"if", inst);
208 const data: Teg.If = teg.instPayload(.@"if", inst);
208209 try writer.writeAll("(");
209210 try s.dumpRefMarker(writer, data.cond);
210211 try writer.writeAll(") {");
......@@ -220,13 +221,13 @@ const State = struct {
220221 try writer.writeAll("}");
221222 },
222223 .@"comptime" => {
223 const data: Teg.Inst.RefNode = teg.instData(.@"comptime", inst);
224 const data: Teg.RefNode = teg.instData(.@"comptime", inst);
224225 try writer.writeAll("(");
225226 try s.dumpRef(writer, data.operand);
226227 try writer.writeAll(")");
227228 },
228229 .struct_decl => {
229 const data: Teg.Inst.StructDecl = teg.instPayload(.struct_decl, inst);
230 const data: Teg.StructDecl = teg.instPayload(.struct_decl, inst);
230231 try writer.writeAll(" {");
231232 s.indent += 1;
232233 try s.newline(writer);
......@@ -241,7 +242,7 @@ const State = struct {
241242 try writer.writeAll("}");
242243 },
243244 .struct_init => {
244 const data: Teg.Inst.StructInit = teg.instPayload(.struct_init, inst);
245 const data: Teg.StructInit = teg.instPayload(.struct_init, inst);
245246 try writer.writeAll("(");
246247 try s.dumpRefMarker(writer, data.base_type);
247248 try writer.writeAll(") {");
......@@ -258,7 +259,7 @@ const State = struct {
258259 try writer.writeAll("}");
259260 },
260261 .array_init => {
261 const data: Teg.Inst.ArrayInit = teg.instPayload(.array_init, inst);
262 const data: Teg.ArrayInit = teg.instPayload(.array_init, inst);
262263
263264 if (data.type_hint.unwrap()) |type_hint| {
264265 try writer.writeAll("(");
......@@ -284,6 +285,37 @@ const State = struct {
284285 if (data.type_hint != .none)
285286 try writer.writeAll(")");
286287 },
288 .result_type_argument => {
289 const data: Teg.ResultTypeArgument = teg.instData(.result_type_argument, inst);
290
291 try writer.writeAll("(");
292 try s.dumpRefMarker(writer, data.call_base);
293 try writer.print(", {d}", .{data.argument});
294 try writer.writeAll(")");
295 },
296 .result_type_struct_field => {
297 const data: Teg.ResultTypeStructField = teg.instData(.result_type_struct_field, inst);
298
299 try writer.writeAll("(");
300 try s.dumpRefMarker(writer, data.type);
301 try writer.print(", {s}", .{data.field.slice(s.teg.string_bytes)});
302 try writer.writeAll(")");
303 },
304 .result_type_tuple_field => {
305 const data: Teg.ResultTypeTupleField = teg.instData(.result_type_tuple_field, inst);
306
307 try writer.writeAll("(");
308 try s.dumpRefMarker(writer, data.type);
309 try writer.print(", {d}", .{data.item});
310 try writer.writeAll(")");
311 },
312 .result_type_array_value => {
313 const data: Teg.ResultTypeArrayValue = teg.instData(.result_type_array_value, inst);
314
315 try writer.writeAll("(");
316 try s.dumpRefMarker(writer, data.type);
317 try writer.writeAll(")");
318 },
287319 }
288320 }
289321
src/core/Teg.zig+331-281
......@@ -6,183 +6,199 @@ extra: ExtraStore = .{},
66report: Report,
77
88pub const ExtraStore = dod.ExtraStore(.teg, .@"const");
9pub const EncodedData = dod.EncodedData(Inst.Tag, u64);
9pub const EncodedData = dod.EncodedData(Tag, u64);
1010pub const ExtraIndex = ExtraStore.Index;
1111pub const Dump = @import("Teg.Dump.zig");
1212
13pub const Inst = struct {
14 pub const List = std.MultiArrayList(Inst);
13pub const Tag = enum {
14 /// Either the top level module or a `module` keyword. Contains a list of `.declarations`
15 /// Every analyzed module gets a `Comp.Namespace`
16 module,
17 /// Smallest unit of re-compilation. Every analyzed declaration gets a `Comp.Decl`
18 declaration,
19 /// Retrieves a declaration value via it's name. Since declarations are
20 /// analyzed in isolation of each other, all references to instructions
21 /// outside of the current declaration must be in the form of this.
22 load_decl,
23 /// Blocks are a list of instructions. The block will end with a `.break`,
24 /// which will assign the block a return value.
25 block,
26 /// Ends a block (current or any parent) with a value.
27 /// `return` expressions are lowered to this.
28 @"break",
29 /// Creates a memory cell in the current scope with a starting value.
30 memory,
31 /// Re-assign a memory cell with a new ref.
32 store,
33 /// Performs a type coercion, such as in variable declarations.
34 /// The resulting Air Ref will be guaranteed to be the coerced type.
35 coercion,
36 /// Load a parameter
37 param,
38 /// Asserts the operand is not void. Used for discard statements.
39 discard,
40 /// Asserts the operand is `void`. Used for undiscarded expressions.
41 ensure_void,
42 /// Numeric constant.
43 number,
44 /// String literal constant.
45 string,
46 /// Branch takes a boolean condition, and yes and no paths.
47 @"if",
48
49 // The following instructions are used to implement `Transform.ResultType`
50 result_type_argument,
51 result_type_struct_field,
52 result_type_tuple_field,
53 result_type_array_value,
54
55 struct_decl,
56 struct_init,
57
58 array_type,
59 array_init,
60
61 add,
62 sub,
63 mul,
64 div,
65 rem,
66 pow,
67 @"and",
68 @"or",
69 bit_and,
70 bit_or,
71 bit_xor,
72 shl,
73 shr,
74 eq,
75 ne,
76 lt,
77 le,
78 gt,
79 ge,
80
81 negate,
82 bool_not,
83 bit_not,
84 field,
85 index,
86 call,
87 @"comptime",
88
89 /// Implements the `@js` builtin
90 js,
91 /// Implements the `@jsNew` builtin
92 js_new,
93 /// Implements the `@import` builtin
94 import,
95 /// Implements the `@fetch` builtin
96 fetch,
97 /// Implements the `@compileError` builtin
98 compile_error,
99 /// Implements the `@typeOf` builtin
100 type_of,
101
102 pub fn Data(comptime tag: Tag) type {
103 return switch (tag) {
104 .module => PlNode(IndexList),
105 .declaration => PlNode(Decl),
106 .block => PlNode(IndexList),
107 .coercion => PlNode(Coercion),
108 .@"break" => PlNode(Break),
109 .number => PlNode(Float),
110 .string => PlNode(SlicedString),
111 .call => PlNode(Call),
112 .@"if" => PlNode(If),
113 .load_decl => StrNode,
114 .param => DataNode,
115 .store => PlNode(Store),
116 .js_new => PlNode(JsNew),
117 .struct_decl => PlNode(StructDecl),
118 .struct_init => PlNode(StructInit),
119 .array_init => PlNode(ArrayInit),
120
121 .result_type_argument => ResultTypeArgument,
122 .result_type_struct_field => ResultTypeStructField,
123 .result_type_tuple_field => ResultTypeTupleField,
124 .result_type_array_value => ResultTypeArrayValue,
125
126 // Single instruction operand
127 .memory,
128 .discard,
129 .ensure_void,
130 .negate,
131 .bool_not,
132 .bit_not,
133 .js,
134 .import,
135 .fetch,
136 .compile_error,
137 .@"comptime",
138 .type_of,
139 .array_type,
140 => RefNode,
141
142 // Two instruction operands
143 .field,
144 .index,
145 .add,
146 .sub,
147 .mul,
148 .div,
149 .rem,
150 .pow,
151 .@"and",
152 .@"or",
153 .bit_and,
154 .bit_or,
155 .bit_xor,
156 .shl,
157 .shr,
158 .eq,
159 .ne,
160 .lt,
161 .le,
162 .gt,
163 .ge,
164 => PlNode(Bin),
165 };
166 }
15167
16 tag: Tag,
17 data: EncodedData,
168 pub fn airTag(comptime tag: Tag) Air.Inst.Tag {
169 return @field(Air.Inst.Tag, @tagName(tag));
170 }
171};
18172
19 // TODO: fully document every thing
20 pub const Tag = enum {
21 module,
22 declaration,
23
24 block,
25 /// Creates a memory cell in the current scope.
26 memory,
27 /// Performs a type coercion, such as in variable declarations.
28 /// Emits a sema error if the type conversion fails.
29 /// `let x: i32 = 1`
30 as,
31 load_decl,
32 /// Load a parameter
33 param,
34 store,
35 discard,
36 ensure_void,
37 ensure_bool,
38 /// lowered from `break` or `return`
39 /// ends a block with a resulting value
40 @"break",
41 number,
42 string,
43
44 @"if",
45
46 struct_decl,
47 struct_init,
48
49 array_type,
50 array_init,
51
52 add,
53 sub,
54 mul,
55 div,
56 rem,
57 pow,
58 @"and",
59 @"or",
60 bit_and,
61 bit_or,
62 bit_xor,
63 shl,
64 shr,
65 eq,
66 ne,
67 lt,
68 le,
69 gt,
70 ge,
71
72 negate,
73 bool_not,
74 bit_not,
75 field,
76 index,
77 call,
78 @"comptime",
79
80 /// Implements the `@js` builtin
81 js,
82 /// Implements the `@jsNew` builtin
83 js_new,
84 /// Implements the `@import` builtin
85 import,
86 /// Implements the `@fetch` builtin
87 fetch,
88 /// Implements the `@compileError` builtin
89 compile_error,
90 /// Implements the `@typeOf` builtin
91 type_of,
92
93 pub fn Data(comptime tag: Tag) type {
94 return switch (tag) {
95 .module => PlNode(IndexList),
96 .declaration => PlNode(Decl),
97 .block => PlNode(IndexList),
98 .as => PlNode(As),
99 .@"break" => PlNode(Break),
100 .number => PlNode(Float),
101 .string => PlNode(SlicedString),
102 .call => PlNode(Call),
103 .@"if" => PlNode(If),
104 .load_decl => StrNode,
105 .param => DataNode,
106 .store => PlNode(Store),
107 .js_new => PlNode(JsNew),
108 .struct_decl => PlNode(StructDecl),
109 .struct_init => PlNode(StructInit),
110 .array_init => PlNode(ArrayInit),
111
112 // Single instruction operand
113 .memory,
114 .discard,
115 .ensure_void,
116 .ensure_bool,
117 .negate,
118 .bool_not,
119 .bit_not,
120 .js,
121 .import,
122 .fetch,
123 .compile_error,
124 .@"comptime",
125 .type_of,
126 .array_type,
127 => RefNode,
128
129 // Two instruction operands
130 .field,
131 .index,
132 .add,
133 .sub,
134 .mul,
135 .div,
136 .rem,
137 .pow,
138 .@"and",
139 .@"or",
140 .bit_and,
141 .bit_or,
142 .bit_xor,
143 .shl,
144 .shr,
145 .eq,
146 .ne,
147 .lt,
148 .le,
149 .gt,
150 .ge,
151 => PlNode(Bin),
152 };
153 }
173pub fn PlNode(comptime Payload: type) type {
174 return struct {
175 pub const T = Payload;
154176
155 pub fn airTag(comptime tag: Tag) Air.Inst.Tag {
156 return @field(Air.Inst.Tag, @tagName(tag));
157 }
177 src_node: NodeOffset,
178 extra_index: ExtraIndex(Payload),
158179 };
180}
159181
160 // TODO: make this an enum
161 const NodeOffset = i32;
162
163 pub fn PlNode(comptime Payload: type) type {
164 return struct {
165 pub const T = Payload;
182pub const RefNode = struct {
183 src_node: NodeOffset,
184 operand: Inst.Ref,
185};
166186
167 src_node: NodeOffset,
168 extra_index: ExtraIndex(Payload),
169 };
170 }
187pub const StrNode = struct {
188 src_node: NodeOffset,
189 string: NullTerminatedString,
190};
171191
172 pub const RefNode = struct {
173 src_node: NodeOffset,
174 operand: Inst.Ref,
175 };
192pub const DataNode = struct {
193 src_node: NodeOffset,
194 data: u32,
195};
176196
177 pub const StrNode = struct {
178 src_node: NodeOffset,
179 string: NullTerminatedString,
180 };
197pub const Inst = struct {
198 pub const List = std.MultiArrayList(Inst);
181199
182 pub const DataNode = struct {
183 src_node: NodeOffset,
184 data: u32,
185 };
200 tag: Tag,
201 data: EncodedData,
186202
187203 /// The position of a Teg instruction within the `Teg` instructions array.
188204 pub const Index = enum(u32) {
......@@ -197,36 +213,28 @@ pub const Inst = struct {
197213 return @enumFromInt(static_len + @intFromEnum(i));
198214 }
199215
200 pub fn toOptional(i: Index) OptionalIndex {
216 pub fn toOptional(i: Index) Optional {
201217 return @enumFromInt(@intFromEnum(i));
202218 }
203219
204220 pub fn op(i: Index, operand: Report.Operand) InstOp {
205221 return .{ .index = i, .op = operand };
206222 }
207 };
208223
209 pub const OptionalIndex = enum(u32) {
210 /// Teg is structured so that the outermost "main" module of any file
211 /// is always at index 0.
212 main_module_inst = 0,
213 ref_start_index = Index.static_len,
214 none = std.math.maxInt(u32),
215 _,
224 pub const Optional = enum(u32) {
225 none = std.math.maxInt(u32),
226 _,
216227
217 pub fn unwrap(oi: OptionalIndex) ?Index {
218 return if (oi == .none) null else @enumFromInt(@intFromEnum(oi));
219 }
228 pub fn unwrap(oi: Optional) ?Index {
229 return if (oi == .none) null else @enumFromInt(@intFromEnum(oi));
230 }
231 };
220232 };
221233
222 /// A reference to an instruction, or to an InternPool index, or to neither.
234 /// A reference to an instruction, or to a static InternPool value.
223235 ///
224236 /// If the integer tag value is < InternPool.static_len, then it
225 /// corresponds to an InternPool index. Otherwise, this refers to a ZIR
226 /// instruction.
227 ///
228 /// The tag type is specified so that it is safe to bitcast between `[]u32`
229 /// and `[]Ref`.
237 /// corresponds to a Value. Otherwise, it is an instruction index.
230238 pub const Ref = enum(u32) {
231239 opaque_type,
232240 bool_type,
......@@ -282,6 +290,10 @@ pub const Inst = struct {
282290 return @enumFromInt(ref_int);
283291 }
284292
293 pub fn toOptional(ref: Ref) Optional {
294 return @enumFromInt(@intFromEnum(ref));
295 }
296
285297 pub const Optional = enum(u32) {
286298 none = std.math.maxInt(u32),
287299 _,
......@@ -291,113 +303,143 @@ pub const Inst = struct {
291303 }
292304 };
293305 };
306};
294307
295 pub const Bin = struct {
296 lhs: Inst.Ref,
297 rhs: Inst.Ref,
298 };
308pub const Bin = struct {
309 lhs: Inst.Ref,
310 rhs: Inst.Ref,
311};
299312
300 pub const Store = struct {
301 memory: Inst.Index,
302 operand: Inst.Ref,
303 };
313pub const Store = struct {
314 memory: Inst.Index,
315 operand: Inst.Ref,
316};
304317
305 pub const As = struct {
306 type: Inst.Ref,
307 operand: Inst.Ref,
308 };
318pub const Coercion = struct {
319 type: Inst.Ref,
320 operand: Inst.Ref,
321};
309322
310 pub const Break = struct {
311 block: Inst.Index,
312 operand: Inst.Ref,
313 };
323pub const Break = struct {
324 block: Inst.Index,
325 operand: Inst.Ref,
326};
314327
315 pub const Call = struct {
316 base: Inst.Ref,
317 args: RefList,
318 };
328pub const Call = struct {
329 base: Inst.Ref,
330 args: RefList,
331};
319332
320 pub const If = struct {
321 cond: Inst.Ref,
322 yes: Inst.Ref,
323 no: Inst.Ref,
324 };
333pub const If = struct {
334 cond: Inst.Ref,
335 yes: Inst.Ref,
336 no: Inst.Ref,
337};
325338
326 pub const JsNew = struct {
327 base: Inst.Ref,
328 args: RefList,
329 };
339pub const JsNew = struct {
340 base: Inst.Ref,
341 args: RefList,
342};
330343
331 /// Index into extra which contains `field_len * 2` u32s. The first half
332 /// are the keys (NullTerminatedString) and the second half are field types
333 /// (Ref). This basically is like a specialized MultiArrayList.
334 pub const StructDecl = struct {
335 payload_index: u32,
336 field_len: u32,
337
338 pub fn keys(decl: StructDecl, teg: *const Teg) []const NullTerminatedString {
339 return @ptrCast(
340 teg.extra.list.items[decl.payload_index..][0..decl.field_len],
341 );
342 }
344/// Index into extra which contains `field_len * 2` u32s. The first half
345/// are the keys (NullTerminatedString) and the second half are field types
346/// (Ref). This basically is like a specialized MultiArrayList.
347pub const StructDecl = struct {
348 payload_index: u32,
349 field_len: u32,
350
351 pub fn keys(decl: StructDecl, teg: *const Teg) []const NullTerminatedString {
352 return @ptrCast(
353 teg.extra.list.items[decl.payload_index..][0..decl.field_len],
354 );
355 }
343356
344 pub fn types(decl: StructDecl, teg: *const Teg) []const Ref {
345 return @ptrCast(
346 teg.extra.list.items[decl.payload_index + decl.field_len ..][0..decl.field_len],
347 );
348 }
349 };
357 pub fn types(decl: StructDecl, teg: *const Teg) []const Inst.Ref {
358 return @ptrCast(
359 teg.extra.list.items[decl.payload_index + decl.field_len ..][0..decl.field_len],
360 );
361 }
362};
350363
351 /// This memory is laid out with the same strategy described in StructDecl
352 pub const StructInit = struct {
353 base_type: Ref,
354 payload_index: u32,
355 field_len: u32,
364/// This memory is laid out with the same strategy described in StructDecl
365pub const StructInit = struct {
366 base_type: Inst.Ref,
367 payload_index: u32,
368 field_len: u32,
356369
357 pub fn keys(decl: StructInit, teg: *const Teg) []const NullTerminatedString {
358 return @ptrCast(
359 teg.extra.list.items[decl.payload_index..][0..decl.field_len],
360 );
361 }
370 pub fn keys(decl: StructInit, teg: *const Teg) []const NullTerminatedString {
371 return @ptrCast(
372 teg.extra.list.items[decl.payload_index..][0..decl.field_len],
373 );
374 }
362375
363 pub fn inits(decl: StructInit, teg: *const Teg) []const Ref {
364 return @ptrCast(
365 teg.extra.list.items[decl.payload_index + decl.field_len ..][0..decl.field_len],
366 );
367 }
368 };
376 pub fn inits(decl: StructInit, teg: *const Teg) []const Inst.Ref {
377 return @ptrCast(
378 teg.extra.list.items[decl.payload_index + decl.field_len ..][0..decl.field_len],
379 );
380 }
381};
369382
370 pub const ArrayInit = struct {
371 /// Optionally provide a result type for more reliable type coercion.
372 /// If specified, this must be an array type. Otherwise, the first
373 /// element's type is used.
374 type_hint: Ref.Optional,
375 /// The items in the array
376 list: RefList,
377 };
383pub const ArrayInit = struct {
384 /// Optionally provide a result type for more reliable type coercion.
385 /// If specified, this must be an array type. Otherwise, the first
386 /// element's type is used.
387 type_hint: Inst.Ref.Optional,
388 /// The items in the array
389 list: RefList,
390};
378391
379 /// This will contain trailing data depending on the .kind
380 pub const Decl = struct {
381 name: NullTerminatedString,
382 hash: Cache.BinDigest,
383 kind: Kind,
384 instruction_count: u32,
385 body: Ref,
386
387 pub const Kind = enum {
388 // TODO: remove function, see #45
389 function,
390 immutable,
391 };
392 };
392/// This will contain trailing data depending on the .kind
393pub const Decl = struct {
394 name: NullTerminatedString,
395 hash: Cache.BinDigest,
396 kind: Kind,
397 instruction_count: u32,
398 body: Inst.Ref,
393399
394 /// Trailing data for function declarations
395 pub const DeclFn = struct {
396 params: RefList,
397 return_type: Ref,
400 pub const Kind = enum {
401 // TODO: remove function, see #45
402 function,
403 immutable,
398404 };
399405};
400406
407/// Trailing data for function declarations
408pub const DeclFn = struct {
409 params: RefList,
410 return_type: Inst.Ref.Optional,
411};
412
413pub const ResultTypeArgument = struct {
414 /// The VALUE that is going to be called.
415 call_base: Inst.Ref,
416 /// Which argument
417 argument: u32,
418};
419
420pub const ResultTypeStructField = struct {
421 /// The type of the struct itself,
422 type: Inst.Ref,
423 /// Which field
424 field: NullTerminatedString,
425};
426
427pub const ResultTypeTupleField = struct {
428 /// The type of the tuple itself,
429 type: Inst.Ref,
430 /// Which item
431 item: u32,
432};
433
434pub const ResultTypeArrayValue = struct {
435 /// The type of the array itself,
436 type: Inst.Ref,
437 unused: u32 = 0,
438};
439
440// TODO: make this an enum
441const NodeOffset = i32;
442
401443pub const InstOp = struct {
402444 index: Inst.Index,
403445 op: Report.Operand,
......@@ -470,7 +512,7 @@ pub fn deinit(teg: *Teg, gpa: std.mem.Allocator) void {
470512 teg.* = undefined;
471513}
472514
473pub fn instTag(teg: *const Teg, index: Inst.Index) Inst.Tag {
515pub fn instTag(teg: *const Teg, index: Inst.Index) Teg.Tag {
474516 return teg.insts.items(.tag)[@intFromEnum(index)];
475517}
476518
......@@ -478,17 +520,25 @@ pub fn instEncodedData(teg: *const Teg, index: Inst.Index) EncodedData {
478520 return teg.insts.items(.data)[@intFromEnum(index)];
479521}
480522
481pub fn instData(teg: *const Teg, comptime tag: Inst.Tag, index: Inst.Index) tag.Data() {
523pub fn instData(teg: *const Teg, comptime tag: Teg.Tag, index: Inst.Index) tag.Data() {
482524 return teg.instEncodedData(index).decode(tag, teg.instTag(index));
483525}
484526
485pub fn instPayload(teg: *const Teg, comptime tag: Inst.Tag, index: Inst.Index) tag.Data().T {
527pub fn instPayload(teg: *const Teg, comptime tag: Teg.Tag, index: Inst.Index) tag.Data().T {
486528 return teg.instData(tag, index).extra_index.get(teg);
487529}
488530
489531pub fn instNodeOffset(teg: *const Teg, index: Inst.Index) i32 {
490532 return switch (teg.instTag(index)) {
491 .declaration => 0,
533 // these nodes never get errors, but Teg.Dump will print these regardless
534 // TODO:
535 .declaration,
536 .result_type_argument,
537 .result_type_struct_field,
538 .result_type_tuple_field,
539 .result_type_array_value,
540 => 0,
541
492542 inline else => |tag| teg.instData(tag, index).src_node,
493543 };
494544}
......@@ -497,10 +547,10 @@ pub fn blockInstructions(teg: *const Teg, index: Inst.Index) []const Inst.Index
497547 return teg.instData(.block, index).extra_index.get(teg).slice(teg);
498548}
499549
500pub fn fullFuncDecl(teg: *const Teg, index: Inst.Index) struct { Inst.Decl, Inst.DeclFn } {
550pub fn fullFuncDecl(teg: *const Teg, index: Inst.Index) struct { Teg.Decl, Teg.DeclFn } {
501551 const decl, var trail = teg.instData(.declaration, index).extra_index.getTrail(teg);
502552 assert(decl.kind == .function);
503 const decl_fn = trail.read(&teg.extra, Inst.DeclFn);
553 const decl_fn = trail.read(&teg.extra, Teg.DeclFn);
504554 return .{ decl, decl_fn };
505555}
506556
......@@ -512,7 +562,7 @@ pub const FullDecl = struct {
512562 body: Inst.Ref,
513563
514564 const Data = union(enum) {
515 function: Inst.DeclFn,
565 function: Teg.DeclFn,
516566 immutable: struct {},
517567 };
518568};
......@@ -537,7 +587,7 @@ pub fn fullDecl(teg: *const Teg, index: Inst.Index) FullDecl {
537587 };
538588}
539589
540pub fn declData(teg: *const Teg, index: Inst.Index) Inst.Decl {
590pub fn declData(teg: *const Teg, index: Inst.Index) Teg.Decl {
541591 return teg.instData(.declaration, index).extra_index.get(teg);
542592}
543593
......@@ -565,7 +615,7 @@ pub fn rootFunction(teg: *const Teg) Inst.Index {
565615
566616pub fn declName(teg: *const Teg, decl_index: Inst.Index) []const u8 {
567617 const decl = teg.instData(.declaration, decl_index);
568 const decl_data: Inst.Decl = decl.extra_index.get(teg);
618 const decl_data: Teg.Decl = decl.extra_index.get(teg);
569619 return decl_data.name.slice(teg.string_bytes);
570620}
571621
src/core/Transform.zig+660-532
......@@ -1,26 +1,35 @@
11//! Transform pass converts an Ast into a high-level/untyped instruction format.
22//! See Teg.zig for more details about the IR. Transforming handles:
33//!
4//! - Flattening nested expressions into instruction sequences.
45//! - Resolving all local variables into reference or declaration names.
5//! - Resolving breaks and returns to their block id.
6//! - Resolving breaks and returns to their generated block id.
67//! - Annotating expression statements with `.discard` and `.ensure_void`
7//! - Flattening nested expressions into instruction sequences.
8//! - Resolving implicit expression with their corresponding result type
89//!
910//! Some trivial diagnostics such as unused variables, unknown result types,
1011//! invalid assignment targets, and other style issues are also reported here.
12
1113gpa: Allocator,
14arena: Allocator,
15
1216ast: Ast,
17
18// these values are allocated through `gpa`, so that resulting IR can be
19// trivially shrunk before returning.
20
1321insts: Inst.List = .{},
14string_bytes: std.ArrayListUnmanaged(u8) = .{},
1522extra: ExtraStore = .{},
23string_bytes: std.ArrayListUnmanaged(u8) = .{},
24report: Report.Builder,
1625
17scratch: std.ArrayListUnmanaged(u32) = .{},
18id_decode_buf: std.ArrayList(u8),
26// however, these are allocated through `arena`, since they are always freed at
27// the end of the pass.
1928
29scratch: core.U32Stack = .{},
30id_decode_buf: std.ArrayList(u8),
2031scopes: std.ArrayListUnmanaged(Scope) = .{},
2132
22report: Report.Builder,
23
2433pub const Error = error{
2534 /// A `Report` will contain at least one error.
2635 TransformFail,
......@@ -40,6 +49,8 @@ pub fn transform(
4049 var t: Transform = .{
4150 .ast = ast,
4251 .gpa = gpa,
52 // TODO: Currently this is illusion of free will
53 .arena = gpa,
4354 .report = .{ .gpa = gpa, .current_file = file },
4455 .id_decode_buf = std.ArrayList(u8).initCapacity(sfa.get(), 4096) catch unreachable,
4556 };
......@@ -57,9 +68,7 @@ pub fn transform(
5768 return error.TransformFail;
5869 }
5970
60 assert(@intFromEnum(module) == 0);
61
62 try t.insts.setCapacity(t.gpa, t.insts.len);
71 assert(module == .main_module_inst);
6372
6473 return .{
6574 .insts = t.insts.toOwnedSlice(),
......@@ -69,84 +78,82 @@ pub fn transform(
6978 };
7079}
7180
72pub fn deinit(t: *Transform) void {
73 t.scratch.deinit(t.gpa);
81fn deinit(t: *Transform) void {
7482 assert(t.scopes.items.len == 0);
75 t.scopes.deinit(t.gpa);
83 t.id_decode_buf.deinit();
84 t.scratch.list.deinit(t.arena);
85 t.scopes.deinit(t.arena);
86 t.id_decode_buf.deinit();
7687 t.string_bytes.deinit(t.gpa);
7788 t.insts.deinit(t.gpa);
7889 t.extra.deinit(t.gpa);
79 t.id_decode_buf.deinit();
8090 t.* = undefined;
8191}
8292
83const Scope = struct {
84 symbols: std.StringArrayHashMapUnmanaged(Symbol) = .{},
85
86 const Symbol = struct {
87 node: NodeIndex,
88 data: Symbol.Data,
89 // TODO: optimize this structure
90 used: bool = false,
91 mutated: bool = false,
92 discarded: ?NodeIndex = null,
93
94 const Data = union(enum) {
95 declaration,
96 param,
97 mutable: Inst.Index,
98 immutable: Inst.Ref,
93/// Represents how the parent node is going to implicitly cast this expression.
94///
95/// Consider an expression like `let x: S = .{ y: 1 }`. The type from the
96/// declaration must impact the visited struct initialization expression. This
97/// behavior is recursive, so something more complex can properly infer:
98///
99/// struct S { a: int }
100/// struct Y {}
101/// let x: [](S, []Y) = [ .{ .{ a: 1 }, [ .{} ] } ];
102///
103/// The innermost `.{}` must eventually analyze to `Y{}`, which can be unwrapped
104/// from the type expression via specialized instructions for this purpose.
105const ResultType = union(enum) {
106 /// There is no result type available
107 none,
108 /// The result type was consumed
109 used,
110 /// The expression will be cast to this ref.
111 ref: Inst.Ref,
112 /// The expression will be cast to the type of this ref
113 type_of: Inst.Ref,
114 /// The expression is an argument to a function.
115 argument: Teg.ResultTypeArgument,
116 /// The expression is a field in a struct initialization
117 struct_field: Teg.ResultTypeStructField,
118 /// The expression is a field in a tuple initialization
119 tuple_field: Teg.ResultTypeTupleField,
120 /// The expression is a field in an array initialization
121 array_value: Inst.Ref,
122
123 /// This will return an instruction pointing to the the type
124 pub fn consume(result: *ResultType, t: *Transform) !?Inst.Ref {
125 if (result.* == .none) return null;
126 defer result.* = .used;
127 return switch (result.*) {
128 .none => unreachable,
129 .used => unreachable, // Use after free
130 .ref => |r| r,
131 .type_of => |r| try t.addRef(.type_of, .{
132 // Result types don't have associated nodes, which is ok because
133 // type_of cannot fail in the context we are using it. Instead,
134 // we will use a predictable poison value, so that an attempt
135 // to print this will crash.
136 .src_node = std.math.maxInt(i32),
137 .operand = r,
138 }),
139 .argument => |r| try t.addRef(.result_type_argument, r),
140 .struct_field => |r| try t.addRef(.result_type_struct_field, r),
141 .tuple_field => |r| try t.addRef(.result_type_tuple_field, r),
142 .array_value => |r| try t.addRef(.result_type_array_value, .{ .type = r }),
99143 };
100
101 pub fn addWarnings(symbol: Symbol, t: *Transform) !void {
102 if (!symbol.mutated and symbol.data == .mutable) {
103 try t.report.warn(.not_mutated_var, .{
104 .node = symbol.node,
105 });
106 }
107 if (!symbol.used) {
108 try t.report.warn(.unused, .{
109 .node = symbol.node,
110 .kind = switch (symbol.data) {
111 .declaration => .function,
112 .param => .param,
113 .mutable => .variable,
114 .immutable => switch (t.ast.nodeTag(symbol.node)) {
115 .param_decl => .param,
116 else => .constant,
117 },
118 },
119 });
120 } else if (symbol.discarded) |discard| {
121 try t.report.warn(.unnecessary_discard, .{
122 .node = discard,
123 });
124 }
125 }
126 };
127
128 fn addWarnings(s: Scope, t: *Transform) !void {
129 for (s.symbols.values()) |value| {
130 try value.addWarnings(t);
131 }
132 }
133
134 fn deinit(s: *Scope, t: *Transform) void {
135 for (s.symbols.keys()) |k| {
136 if (!isSliceInBuffer(u8, t.ast.utf8_source, k)) {
137 t.gpa.free(k);
138 }
139 }
140 s.symbols.deinit(t.gpa);
141 s.* = undefined;
142144 }
143145};
144146
145147const BlockContext = struct {
146 node_base: i32,
147 return_target: ?*?Inst.Index = null,
148 decl_node_offset: i32,
149 return_target: ?*ReturnTarget = null,
148150 break_target: ?*BreakTarget = null,
149151
152 const ReturnTarget = struct {
153 generated_block: ?Inst.Index = null,
154 result_type: ResultType,
155 };
156
150157 /// This struct is always stored on the stack; the idea is to pass mutable
151158 /// pointers to it. When `break :brk` is encountered, it will generate the
152159 /// block instruction. This way, redundant blocks are never created.
......@@ -157,6 +164,7 @@ const BlockContext = struct {
157164 /// If `null`, this label was unused.
158165 block_index: ?Inst.Index = null,
159166 parent: ?*BreakTarget = null,
167 result_type: *ResultType,
160168
161169 fn addChild(parent: *BreakTarget, label: ?[]const u8, is_loop: bool) BreakTarget {
162170 return .{
......@@ -168,10 +176,7 @@ const BlockContext = struct {
168176
169177 fn getInst(target: *BreakTarget, t: *Transform) !Inst.Index {
170178 return target.block_index orelse {
171 const inst = try t.addRootInst(.{
172 .tag = .block,
173 .data = undefined,
174 });
179 const inst = try t.addUntracked(.block, undefined);
175180 target.block_index = inst;
176181 return inst;
177182 };
......@@ -180,41 +185,41 @@ const BlockContext = struct {
180185
181186 fn init(root_node: NodeIndex) BlockContext {
182187 return .{
183 .node_base = @intCast(root_node),
188 .decl_node_offset = @intCast(root_node),
184189 };
185190 }
186191
187192 fn nodeOffset(self: BlockContext, node: NodeIndex) i32 {
188 return @as(i32, @intCast(node)) - self.node_base;
193 return @as(i32, @intCast(node)) - self.decl_node_offset;
189194 }
190195
191196 fn declOffset(self: BlockContext, node: NodeIndex) BlockContext {
192197 return .{
193 .node_base = self.nodeOffset(node),
198 .decl_node_offset = self.nodeOffset(node),
194199 };
195200 }
196201
197 fn withReturn(self: BlockContext, return_target: ?*?Inst.Index) BlockContext {
202 fn withReturn(self: BlockContext, return_target: ?*ReturnTarget) BlockContext {
198203 return .{
199 .node_base = self.node_base,
204 .decl_node_offset = self.decl_node_offset,
200205 .return_target = return_target,
201206 };
202207 }
203208
204209 fn withBreak(self: BlockContext, break_target: *BreakTarget) BlockContext {
205210 return .{
206 .node_base = self.node_base,
211 .decl_node_offset = self.decl_node_offset,
207212 .return_target = self.return_target,
208213 .break_target = break_target,
209214 };
210215 }
211216
212 fn findAndMarkBreak(self: BlockContext, t: *Transform, label: []const u8) !?Inst.Index {
217 fn lookupNamedBreak(self: BlockContext, label: []const u8) ?*BreakTarget {
213218 var break_target = self.break_target;
214219 while (break_target) |target| : (break_target = target.parent) {
215220 if (target.label) |target_label| {
216221 if (mem.eql(u8, label, target_label)) {
217 return try target.getInst(t);
222 return target;
218223 }
219224 }
220225 }
......@@ -222,6 +227,84 @@ const BlockContext = struct {
222227 }
223228};
224229
230const Scope = struct {
231 symbols: std.StringArrayHashMapUnmanaged(Symbol) = .{},
232
233 // TODO: optimize this structure's size.
234 const Symbol = struct {
235 node: NodeIndex,
236 data: Symbol.Data,
237 used: bool = false,
238 mutated: bool = false,
239 discarded: ?NodeIndex = null,
240 type: SymbolResultType,
241
242 const Data = union(enum) {
243 declaration,
244 param,
245 mutable: Inst.Index,
246 immutable: Inst.Ref,
247 };
248
249 const SymbolResultType = union(enum) {
250 /// Points to the resolved result type (.ref), as is.
251 present: Inst.Ref,
252 /// This points to the value. Which means a `type_of` instruction
253 /// must be inserted before it may be used.
254 missing: Inst.Ref,
255
256 pub fn resolve(srt: SymbolResultType) ResultType {
257 return switch (srt) {
258 .present => |type_ref| .{ .ref = type_ref },
259 .missing => |value_ref| .{ .type_of = value_ref },
260 };
261 }
262 };
263
264 pub fn addWarnings(symbol: Symbol, t: *Transform) !void {
265 if (!symbol.mutated and symbol.data == .mutable) {
266 try t.report.warn(.not_mutated_var, .{
267 .node = symbol.node,
268 });
269 }
270 if (!symbol.used) {
271 try t.report.warn(.unused, .{
272 .node = symbol.node,
273 .kind = switch (symbol.data) {
274 .declaration => .function,
275 .param => .param,
276 .mutable => .variable,
277 .immutable => switch (t.ast.nodeTag(symbol.node)) {
278 .param_decl => .param,
279 else => .constant,
280 },
281 },
282 });
283 } else if (symbol.discarded) |discard| {
284 try t.report.warn(.unnecessary_discard, .{
285 .node = discard,
286 });
287 }
288 }
289 };
290
291 fn addWarnings(s: Scope, t: *Transform) !void {
292 for (s.symbols.values()) |value| {
293 try value.addWarnings(t);
294 }
295 }
296
297 fn deinit(s: *Scope, t: *Transform) void {
298 for (s.symbols.keys()) |k| {
299 if (!isSliceInBuffer(u8, t.ast.utf8_source, k)) {
300 t.gpa.free(k);
301 }
302 }
303 s.symbols.deinit(t.gpa);
304 s.* = undefined;
305 }
306};
307
225308const Liveness = enum { alive, dead };
226309
227310const BuiltInIds = std.StaticStringMap(Inst.Ref).initComptime(.{
......@@ -242,10 +325,7 @@ fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.I
242325 const trace = tracer.trace(@src());
243326 defer trace.end();
244327
245 const module = try t.addRootInst(.{
246 .tag = .module,
247 .data = undefined,
248 });
328 const module = try t.addUntracked(.module, undefined);
249329
250330 const decl = BlockContext.init(module_node);
251331
......@@ -271,10 +351,7 @@ fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.I
271351 }
272352 }
273353
274 const top_level_fn = try t.addRootInst(.{
275 .tag = .declaration,
276 .data = undefined,
277 });
354 const top_level_fn = try t.addUntracked(.declaration, undefined);
278355
279356 temp[temp.len - 1] = top_level_fn;
280357
......@@ -285,42 +362,39 @@ fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.I
285362 try t.extra.list.appendSlice(t.gpa, @ptrCast(temp));
286363
287364 const top_level_code_ref: Inst.Ref = ref: {
288 const scratch_top = t.scratch.items.len;
289 defer t.scratch.shrinkRetainingCapacity(scratch_top);
365 const scratch = t.scratch.mark();
366 defer scratch.deinit();
290367
291368 for (statements) |statement| {
292369 try t.transformModuleStatement(decl, statement);
293370 }
294371
295372 // if there are no new instructions, we dont have to create a block context
296 if (scratch_top == t.scratch.items.len) {
373 if (scratch.len() == 0) {
297374 break :ref .void;
298375 }
299376
300 const block = try t.addRootInst(.{
301 .tag = .block,
302 .data = undefined,
303 });
377 const block = try t.addUntracked(.block, undefined);
304378
305379 t.getInstData(block).* = EncodedData.encode(.block, .{
306380 .src_node = decl.nodeOffset(module_node),
307 .extra_index = try t.extra.append(t.gpa, IndexList, try t.scratchToIndexList(scratch_top)),
381 .extra_index = try t.extra.append(t.gpa, IndexList, try t.scratchToIndexList(scratch)),
308382 });
309383
310384 break :ref block.toRef();
311385 };
312386
313 const decl_extra, var trail = try t.extra.appendTrailing(t.gpa, Inst.Decl, .{
387 const decl_extra, var trail = try t.extra.appendTrailing(t.gpa, Teg.Decl, .{
314388 .kind = .function,
315389 .name = NullTerminatedString.empty,
316390 .hash = .{0} ** 16,
317391 .instruction_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(top_level_fn),
318392 .body = top_level_code_ref,
319 }, dod.extraSizeOf(Inst.DeclFn));
393 }, extra.extraSizeOf(Teg.DeclFn));
320394
321 trail.writeStruct(Inst.DeclFn, .{
395 trail.writeStruct(Teg.DeclFn, .{
322396 .params = .{ .start = 0, .len = 0 },
323 .return_type = Inst.Ref.void,
397 .return_type = Inst.Ref.void.toOptional(),
324398 });
325399
326400 t.getInstData(top_level_fn).* = EncodedData.encode(.declaration, .{
......@@ -361,10 +435,14 @@ fn previsitModuleStatement(t: *Transform, node: NodeIndex) Allocator.Error!void
361435 gop.value_ptr.* = .{
362436 .node = node,
363437 .data = .declaration,
438 .type = .{ .present = .void },
364439 };
365440 },
366441 .struct_decl => {
367442 const decl = t.ast.fullStructDecl(node);
443 var id, const id_allocated = try t.decodeIdentifierExtra(decl.id);
444 if (id_allocated)
445 id = try t.gpa.dupe(u8, id);
368446 const gop = try t.scope().symbols.getOrPut(t.gpa, decl.id);
369447 if (gop.found_existing) {
370448 try t.report.err(.duplicate_function, .{
......@@ -377,6 +455,7 @@ fn previsitModuleStatement(t: *Transform, node: NodeIndex) Allocator.Error!void
377455 gop.value_ptr.* = .{
378456 .node = node,
379457 .data = .declaration,
458 .type = .{ .present = .void },
380459 };
381460 },
382461 else => {},
......@@ -397,14 +476,14 @@ fn transformModuleStatement(t: *Transform, decl: BlockContext, node: NodeIndex)
397476 .struct_decl,
398477 => {}, // declarations are analyzed separately
399478
400 else => _ = try t.transformBlockStatement(decl, node, false),
479 else => _ = try t.transformBlockStatement(decl, node, null),
401480 }
402481}
403482
404483fn transformDecl(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index {
405484 return switch (t.ast.nodeTag(node)) {
406485 .fn_decl => t.transformFunction(outer_decl, node),
407 .struct_decl => t.transformStruct(outer_decl, node),
486 .struct_decl => t.transformStructDeclaration(outer_decl, node),
408487 else => unreachable, // not a declaration
409488 };
410489}
......@@ -416,33 +495,40 @@ fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) A
416495 const decl = outer_decl.declOffset(node);
417496 const fn_decl = t.ast.fullFnDecl(node);
418497
419 const decl_inst = try t.addRootInst(.{
420 .tag = .declaration,
421 .data = undefined,
422 });
498 const decl_inst = try t.addUntracked(.declaration, undefined);
423499
424500 try t.pushScope();
425501 defer t.popScope();
426502
427503 const param_list = brk: {
428 const scratch_top = t.scratch.items.len;
429 defer t.scratch.shrinkRetainingCapacity(scratch_top);
504 const scratch = t.scratch.mark();
505 defer scratch.deinit();
430506
431507 for (fn_decl.params) |param| {
432 try t.addScratch(Inst.Ref, try t.transformParamDecl(decl, param));
508 try t.scratch.add(t.arena, Inst.Ref, try t.transformParamDecl(decl, param));
433509 }
434510
435 break :brk try t.scratchToRefList(scratch_top);
511 break :brk try t.scratchToRefList(scratch);
436512 };
437513
438514 const return_type = if (fn_decl.return_type) |return_type_node|
439 try t.transformExpressionToBlock(decl, return_type_node, false)
515 try t.transformToBlock(
516 decl,
517 return_type_node,
518 .result_without_coercion,
519 .{ .ref = .type_type },
520 )
440521 else
441 Inst.Ref.void;
522 null;
442523
443 const body = try t.transformExpressionToBlock(decl, fn_decl.body, true);
524 const body = try t.transformToBlock(
525 decl,
526 fn_decl.body,
527 .function_body,
528 if (return_type) |ref| .{ .ref = ref } else .none,
529 );
444530
445 const decl_extra = try t.extra.append(t.gpa, Inst.Decl, .{
531 const decl_extra = try t.extra.append(t.gpa, Teg.Decl, .{
446532 .kind = .function,
447533 .name = try t.addNullTerminatedString(fn_decl.id),
448534 .hash = .{0} ** 16,
......@@ -450,9 +536,9 @@ fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) A
450536 .body = body,
451537 });
452538
453 _ = try t.extra.append(t.gpa, Inst.DeclFn, .{
539 _ = try t.extra.append(t.gpa, Teg.DeclFn, .{
454540 .params = param_list,
455 .return_type = return_type,
541 .return_type = if (return_type) |r| r.toOptional() else .none,
456542 });
457543
458544 t.getInstData(decl_inst).* = EncodedData.encode(.declaration, .{
......@@ -465,21 +551,18 @@ fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) A
465551 return decl_inst;
466552}
467553
468fn transformStruct(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index {
554fn transformStructDeclaration(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index {
469555 const trace = tracer.trace(@src());
470556 defer trace.end();
471557
472558 const decl = outer_decl.declOffset(node);
473559 const decl_data = t.ast.fullStructDecl(node);
474560
475 const decl_inst = try t.addRootInst(.{
476 .tag = .declaration,
477 .data = undefined,
478 });
561 const decl_inst = try t.addUntracked(.declaration, undefined);
479562
480 const body = try t.transformExpressionToBlock(decl, node, false);
563 const body = try t.transformToBlock(decl, node, .result_without_coercion, .none);
481564
482 const decl_extra = try t.extra.append(t.gpa, Inst.Decl, .{
565 const decl_extra = try t.extra.append(t.gpa, Teg.Decl, .{
483566 .kind = .immutable,
484567 .name = try t.addNullTerminatedString(decl_data.id),
485568 .hash = .{0} ** 16,
......@@ -495,76 +578,83 @@ fn transformStruct(t: *Transform, outer_decl: BlockContext, node: NodeIndex) All
495578 return decl_inst;
496579}
497580
498const TransformFunctionCtx = struct {
499 param_list: RefList,
581const TransformToBlockMode = enum {
582 tracked_result,
583 result_without_coercion,
584 coerced_result,
585 function_body,
586
587 fn ResultType(mode: TransformToBlockMode) type {
588 return switch (mode) {
589 .tracked_result => *Transform.ResultType,
590 else => Transform.ResultType,
591 };
592 }
500593};
501594
502/// Converts expressions into blocks with a break. This is important
503/// for places where a block is not allowed, such as a parameter list.
504/// This is also used for the function body, even though it is a block.
505/// Simple refs turn into just the ref
506fn transformExpressionToBlock(
595/// guh
596fn transformToBlock(
507597 t: *Transform,
508 decl: BlockContext,
598 ctx: BlockContext,
509599 node: NodeIndex,
510 comptime is_function: bool,
600 comptime mode: TransformToBlockMode,
601 result_type: mode.ResultType(),
511602) Allocator.Error!Inst.Ref {
512603 const trace = tracer.trace(@src());
513604 defer trace.end();
514605
515 const node_tag = t.ast.nodes.items(.tag)[node];
516 switch (node_tag) {
517 .id => {
518 // Lower builtin ids without using a block
519 const id = try t.decodeIdentifier(t.ast.basicNodeSlice(node));
520 if (BuiltInIds.get(id)) |ref| {
521 return ref;
522 }
523 },
524
525 else => {},
526 }
527
528 var block: ?Inst.Index = null;
606 const scratch = t.scratch.mark();
607 defer scratch.deinit();
529608
530 const scratch_top = t.scratch.items.len;
531 defer t.scratch.shrinkRetainingCapacity(scratch_top);
532
533 if (is_function) {
609 // For function blocks, add .param instructions for all of the parameters
610 if (mode == .function_body) {
534611 const s = t.scopes.getLast();
535612 for (s.symbols.values(), 0..) |*param, i| {
536613 assert(param.data == .param);
537 const ref = try t.addInstRef(.{
538 .tag = .param,
539 .data = EncodedData.encode(.param, .{
540 .src_node = decl.nodeOffset(node),
541 .data = @intCast(i),
542 }),
614 const ref = try t.addRef(.param, .{
615 .src_node = ctx.nodeOffset(node),
616 .data = @intCast(i),
543617 });
544618 param.data = .{ .immutable = ref };
545619 }
546620 }
547621
622 // The result type is only passed by reference if mode is tracked_result.
623 const copies_result_type = mode.ResultType() == ResultType;
624 var result_type_storage = if (copies_result_type) result_type;
625
626 var ret = if (mode == .function_body)
627 BlockContext.ReturnTarget{ .result_type = result_type_storage };
628
548629 const expr = try t.transformExpression(
549 if (is_function)
550 decl.withReturn(&block)
630 // Function blocks are allowed to return, in which
631 // it will return to this block.
632 if (mode == .function_body)
633 ctx.withReturn(&ret)
551634 else
552 decl,
635 ctx,
636 // Pass result type pointer
637 if (copies_result_type)
638 &result_type_storage
639 else
640 result_type,
553641 node,
554642 );
555643
556644 // if there are no new instructions, we dont have to create a block context
557 if (scratch_top == t.scratch.items.len) {
558 const result = expr orelse unreachable; // null means we *added* break or return
645 if (scratch.len() == 0) {
646 const result = expr orelse
647 unreachable; // null means we *added* break or return
559648
560 // TODO: evaluate if `if` blocks would be fine in sema with instruction refs
561 // with this extra if check here, we filter out non-blocks.
562649 if (result.value() != null) {
563 assert(block == null);
650 if (mode == .function_body)
651 assert(ret.generated_block == null); // return was called without an instruction
652
564653 return result;
565654 }
566655 }
567656
657 // If we analyzed a block, then just use that. No need for an extra wrapper block.
568658 if (expr) |result| {
569659 if (result.index()) |index| {
570660 if (t.insts.items(.tag)[@intFromEnum(index)] == .block) {
......@@ -573,32 +663,26 @@ fn transformExpressionToBlock(
573663 }
574664 }
575665
576 const ret_block = if (block) |b|
577 b
578 else
579 try t.addRootInst(.{
580 .tag = .block,
581 .data = undefined,
582 });
666 // Otherwise try using the function return block, or create a new one
667 const maybe_generated_block = if (mode == .function_body) ret.generated_block else null;
668 const ret_block = maybe_generated_block orelse try t.addUntracked(.block, undefined);
583669
670 // The block must end with a break
584671 if (expr) |result| {
585 _ = try t.addInstRef(.{
586 .tag = .@"break",
587 .data = EncodedData.encode(.@"break", .{
588 .src_node = decl.nodeOffset(node),
589 .extra_index = try t.extra.append(t.gpa, Inst.Break, .{
590 .block = ret_block,
591 .operand = result,
592 }),
672 _ = try t.addIndex(.@"break", .{
673 .src_node = ctx.nodeOffset(node),
674 .extra_index = try t.extra.append(t.gpa, Teg.Break, .{
675 .block = ret_block,
676 .operand = result,
593677 }),
594678 });
595679 }
596680
597 const content = try t.scratchToIndexList(scratch_top);
681 const content = try t.scratchToIndexList(scratch);
598682 assert(content.len > 0);
599683
600684 t.getInstData(ret_block).* = EncodedData.encode(.block, .{
601 .src_node = decl.nodeOffset(node),
685 .src_node = ctx.nodeOffset(node),
602686 .extra_index = try t.extra.append(t.gpa, IndexList, content),
603687 });
604688
......@@ -610,7 +694,13 @@ fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Alloc
610694 defer trace.end();
611695
612696 const param_decl = t.ast.fullParamDecl(param);
613 const value = try t.transformExpressionToBlock(decl, param_decl.type orelse @panic("TODO: inferred param types"), false);
697 const ty = try t.transformToBlock(
698 decl,
699 param_decl.type orelse
700 @panic("TODO: inferred param types"),
701 .result_without_coercion,
702 .{ .ref = .type_type },
703 );
614704
615705 var id, const id_allocated = try t.decodeIdentifierExtra(param_decl.id);
616706 if (id_allocated)
......@@ -631,9 +721,10 @@ fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Alloc
631721 sym.value_ptr.* = .{
632722 .node = param,
633723 .data = .param,
724 .type = .{ .present = ty },
634725 };
635726
636 return value;
727 return ty;
637728}
638729
639730pub const Builtin = enum {
......@@ -664,14 +755,12 @@ pub const Builtin = enum {
664755 return .void;
665756 }
666757
667 const source = try t.transformExpression(decl, args[0]) orelse return null;
758 var result: ResultType = .{ .ref = .string_type };
759 const source = try t.transformExpression(decl, &result, args[0]) orelse return null;
668760
669 return try t.addInstRef(.{
670 .tag = .js,
671 .data = EncodedData.encode(.js, .{
672 .src_node = decl.nodeOffset(args[0]),
673 .operand = source,
674 }),
761 return try t.addRef(.js, .{
762 .src_node = decl.nodeOffset(args[0]),
763 .operand = source,
675764 });
676765 }
677766
......@@ -684,17 +773,15 @@ pub const Builtin = enum {
684773 return .void;
685774 }
686775
687 const base = try t.transformExpression(decl, args[0]) orelse return null;
688 const args_list = try t.transformExpressionList(decl, args[1..]) orelse return null;
776 var opaque_type: ResultType = .{ .ref = .opaque_type };
777 const base = try t.transformExpression(decl, &opaque_type, args[0]) orelse return null;
778 const args_list = try t.transformExpressionList(decl, .{ .ref = .opaque_type }, args[1..]) orelse return null;
689779
690 return try t.addInstRef(.{
691 .tag = .js_new,
692 .data = EncodedData.encode(.js_new, .{
693 .src_node = decl.nodeOffset(args[0]),
694 .extra_index = try t.extra.append(t.gpa, Inst.JsNew, .{
695 .base = base,
696 .args = args_list,
697 }),
780 return try t.addRef(.js_new, .{
781 .src_node = decl.nodeOffset(args[0]),
782 .extra_index = try t.extra.append(t.gpa, Teg.JsNew, .{
783 .base = base,
784 .args = args_list,
698785 }),
699786 });
700787 }
......@@ -708,15 +795,13 @@ pub const Builtin = enum {
708795 return .void;
709796 }
710797
711 const source = try t.transformExpression(decl, args[0]) orelse
798 var string_type: ResultType = .{ .ref = .string_type };
799 const source = try t.transformExpression(decl, &string_type, args[0]) orelse
712800 return null;
713801
714 return try t.addInstRef(.{
715 .tag = .compile_error,
716 .data = EncodedData.encode(.compile_error, .{
717 .src_node = decl.nodeOffset(node),
718 .operand = source,
719 }),
802 return try t.addRef(.compile_error, .{
803 .src_node = decl.nodeOffset(node),
804 .operand = source,
720805 });
721806 }
722807
......@@ -729,22 +814,25 @@ pub const Builtin = enum {
729814 return .void;
730815 }
731816
732 const source = try t.transformExpression(decl, args[0]) orelse
817 var result: ResultType = .none;
818 const source = try t.transformExpression(decl, &result, args[0]) orelse
733819 return null;
734820
735 return try t.addInstRef(.{
736 .tag = .type_of,
737 .data = EncodedData.encode(.type_of, .{
738 .src_node = decl.nodeOffset(node),
739 .operand = source,
740 }),
821 return try t.addRef(.type_of, .{
822 .src_node = decl.nodeOffset(node),
823 .operand = source,
741824 });
742825 }
743826};
744827
745828/// This is the primary transformation function: converting an AST expression into instructions.
746829/// `null` represents a `return` or `break`, aka this branch of code is dead.
747fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Allocator.Error!?Inst.Ref {
830fn transformExpression(
831 t: *Transform,
832 ctx: BlockContext,
833 result: *ResultType,
834 node: NodeIndex,
835) Allocator.Error!?Inst.Ref {
748836 const node_tag = t.ast.nodes.items(.tag)[node];
749837
750838 const trace = tracer.traceFmt(@src(), "{s}", .{@tagName(node_tag)});
......@@ -775,12 +863,9 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
775863 lookup.used = true;
776864
777865 return switch (lookup.data) {
778 .declaration => try t.addInstRef(.{
779 .tag = .load_decl,
780 .data = EncodedData.encode(.load_decl, .{
781 .src_node = decl.nodeOffset(node),
782 .string = try t.addNullTerminatedString(id),
783 }),
866 .declaration => try t.addRef(.load_decl, .{
867 .src_node = ctx.nodeOffset(node),
868 .string = try t.addNullTerminatedString(id),
784869 }),
785870 .mutable => |idx| idx.toRef(),
786871 .immutable => |ref| ref,
......@@ -800,41 +885,32 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
800885 try t.report.warn(.zero_prefixed_literal, .{ .token = t.ast.nodeToken(node) });
801886 }
802887
803 return try t.addInstRef(.{
804 .tag = .number,
805 .data = EncodedData.encode(.number, .{
806 .src_node = decl.nodeOffset(node),
807 .extra_index = try t.extra.append(t.gpa, Float, Float.encode(value)),
808 }),
888 return try t.addRef(.number, .{
889 .src_node = ctx.nodeOffset(node),
890 .extra_index = try t.extra.append(t.gpa, Float, Float.encode(value)),
809891 });
810892 },
811893
812894 .string => {
813895 const str = t.ast.basicNodeSlice(node);
814896 const sliced = try t.addDecodedSlicedString(str[1 .. str.len - 1]);
815 return try t.addInstRef(.{
816 .tag = .string,
817 .data = EncodedData.encode(.string, .{
818 .src_node = decl.nodeOffset(node),
819 .extra_index = try t.extra.append(t.gpa, SlicedString, sliced),
820 }),
897 return try t.addRef(.string, .{
898 .src_node = ctx.nodeOffset(node),
899 .extra_index = try t.extra.append(t.gpa, SlicedString, sliced),
821900 });
822901 },
823902
824903 .assign => {
825904 const ast_bin = t.ast.nodeData(node).bin;
826 const lhs = try t.transformAssignmentTarget(decl, ast_bin.lhs) orelse
905 const lhs, var result_type = try t.transformAssignmentTarget(ctx, ast_bin.lhs) orelse
827906 return .void;
828 const rhs = try t.transformExpression(decl, ast_bin.rhs) orelse
907 const rhs = try t.transformExpression(ctx, &result_type, ast_bin.rhs) orelse
829908 return null;
830 const assign_inst = try t.addInstRef(.{
831 .tag = .store,
832 .data = EncodedData.encode(.store, .{
833 .src_node = decl.nodeOffset(node),
834 .extra_index = try t.extra.append(t.gpa, Inst.Store, .{
835 .memory = lhs,
836 .operand = rhs,
837 }),
909 const assign_inst = try t.addRef(.store, .{
910 .src_node = ctx.nodeOffset(node),
911 .extra_index = try t.extra.append(t.gpa, Teg.Store, .{
912 .memory = lhs,
913 .operand = rhs,
838914 }),
839915 });
840916 return assign_inst;
......@@ -877,41 +953,39 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
877953 // compound compute + assign operation.
878954 const is_assign = comptime tag.isAssignBin();
879955 const teg_tag = @field(
880 Teg.Inst.Tag,
956 Teg.Tag,
881957 if (is_assign)
882958 @tagName(tag)[0 .. @tagName(tag).len - "_assign".len]
883959 else
884960 @tagName(tag),
885961 );
886962 const ast_bin = t.ast.nodeData(node).bin;
887 const lhs = if (is_assign)
888 try t.transformAssignmentTarget(decl, ast_bin.lhs) orelse
963 var lhs_result_type: ResultType = .none;
964 const lhs, var assign_result_type: ResultType = if (is_assign)
965 try t.transformAssignmentTarget(ctx, ast_bin.lhs) orelse
889966 return .void
890967 else
891 try t.transformExpression(decl, ast_bin.lhs) orelse
892 return null;
893 const rhs = try t.transformExpression(decl, ast_bin.rhs) orelse return null;
894
895 const compute_inst = try t.addInstRef(.{
896 .tag = teg_tag,
897 .data = EncodedData.encode(teg_tag, .{
898 .src_node = decl.nodeOffset(node),
899 .extra_index = try t.extra.append(t.gpa, Inst.Bin, .{
900 .lhs = if (is_assign) lhs.toRef() else lhs,
901 .rhs = rhs,
902 }),
968 .{
969 try t.transformExpression(ctx, &lhs_result_type, ast_bin.lhs) orelse
970 return null,
971 .none,
972 };
973 const rhs = try t.transformExpression(ctx, &assign_result_type, ast_bin.rhs) orelse return null;
974
975 const compute_inst = try t.addRef(teg_tag, .{
976 .src_node = ctx.nodeOffset(node),
977 .extra_index = try t.extra.append(t.gpa, Teg.Bin, .{
978 .lhs = if (is_assign) lhs.toRef() else lhs,
979 .rhs = rhs,
903980 }),
904981 });
905982
906983 if (is_assign) {
907 const assign_inst = try t.addInstRef(.{
908 .tag = .store,
909 .data = EncodedData.encode(.store, .{
910 .src_node = decl.nodeOffset(node),
911 .extra_index = try t.extra.append(t.gpa, Inst.Store, .{
912 .memory = lhs,
913 .operand = compute_inst,
914 }),
984 const assign_inst = try t.addRef(.store, .{
985 .src_node = ctx.nodeOffset(node),
986 .extra_index = try t.extra.append(t.gpa, Teg.Store, .{
987 .memory = lhs,
988 .operand = compute_inst,
915989 }),
916990 });
917991 return assign_inst;
......@@ -926,29 +1000,31 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
9261000 .array_type,
9271001 => |tag| {
9281002 const ast_un = t.ast.nodeData(node).un;
929 const teg_tag = @field(Teg.Inst.Tag, @tagName(tag));
1003 const teg_tag = @field(Teg.Tag, @tagName(tag));
9301004
931 const operand = try t.transformExpression(decl, ast_un.expr) orelse
1005 var operand_type: ResultType = switch (tag) {
1006 .negate => .none, // any numeric type allowed
1007 .bool_not => .{ .ref = .bool_type },
1008 .bit_not => .{ .ref = .int32_type },
1009 .array_type => .{ .ref = .type_type },
1010 else => @compileError(unreachable),
1011 };
1012
1013 const operand = try t.transformExpression(ctx, &operand_type, ast_un.expr) orelse
9321014 return null;
9331015
934 return try t.addInstRef(.{
935 .tag = teg_tag,
936 .data = EncodedData.encode(teg_tag, .{
937 .src_node = decl.nodeOffset(node),
938 .operand = operand,
939 }),
1016 return try t.addRef(teg_tag, .{
1017 .src_node = ctx.nodeOffset(node),
1018 .operand = operand,
9401019 });
9411020 },
9421021
9431022 .@"comptime" => {
9441023 const ast_un = t.ast.nodeData(node).un;
945 const operand = try t.transformExpressionToBlock(decl, ast_un.expr, false);
946 return try t.addInstRef(.{
947 .tag = .@"comptime",
948 .data = EncodedData.encode(.@"comptime", .{
949 .src_node = decl.nodeOffset(node),
950 .operand = operand,
951 }),
1024 const operand = try t.transformToBlock(ctx, ast_un.expr, .tracked_result, result);
1025 return try t.addRef(.@"comptime", .{
1026 .src_node = ctx.nodeOffset(node),
1027 .operand = operand,
9521028 });
9531029 },
9541030
......@@ -957,43 +1033,42 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
9571033 const label = t.ast.tokenSlice(t.ast.nodeToken(node));
9581034
9591035 var target: BlockContext.BreakTarget = .{
960 .parent = decl.break_target,
1036 .parent = ctx.break_target,
9611037 .label = label,
9621038 .is_loop = false,
1039 .result_type = result,
9631040 };
964 const inner_ctx = decl.withBreak(&target);
1041 const inner_ctx = ctx.withBreak(&target);
9651042
966 const scratch_top = t.scratch.items.len;
1043 const scratch = t.scratch.mark();
9671044
968 const inner_expr = try t.transformExpression(inner_ctx, expr);
1045 const inner_expr = try t.transformExpression(inner_ctx, result, expr);
9691046
9701047 // if there are no new instructions, we dont have to create a block context
971 if (scratch_top == t.scratch.items.len)
972 return inner_expr orelse unreachable; // null means we added break or return
1048 if (scratch.len() == 0)
1049 return inner_expr orelse
1050 unreachable; // null means we added break or return
9731051
9741052 if (target.block_index) |block| {
975 if (inner_expr) |result| {
976 _ = try t.addInstRef(.{
977 .tag = .@"break",
978 .data = EncodedData.encode(.@"break", .{
979 .src_node = decl.nodeOffset(node),
980 .extra_index = try t.extra.append(t.gpa, Inst.Break, .{
981 .block = block,
982 .operand = result,
983 }),
1053 if (inner_expr) |out| {
1054 _ = try t.addRef(.@"break", .{
1055 .src_node = ctx.nodeOffset(node),
1056 .extra_index = try t.extra.append(t.gpa, Teg.Break, .{
1057 .block = block,
1058 .operand = out,
9841059 }),
9851060 });
9861061 }
9871062
988 const content = try t.scratchToIndexList(scratch_top);
989 t.scratch.shrinkRetainingCapacity(scratch_top);
1063 const content = try t.scratchToIndexList(scratch);
1064 scratch.deinit();
9901065
9911066 t.getInstData(block).* = EncodedData.encode(.block, .{
992 .src_node = decl.nodeOffset(node),
1067 .src_node = ctx.nodeOffset(node),
9931068 .extra_index = try t.extra.append(t.gpa, IndexList, content),
9941069 });
9951070
996 try t.addScratch(Inst.Index, block);
1071 try t.scratch.add(t.arena, Inst.Index, block);
9971072
9981073 return block.toRef();
9991074 } else {
......@@ -1012,9 +1087,12 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
10121087 for (statements, 1..) |statement, i| {
10131088 const is_last = i == statements.len;
10141089 last = try t.transformBlockStatement(
1015 decl,
1090 ctx,
10161091 statement,
1017 tag == .block_implicit_return and is_last,
1092 if (tag == .block_implicit_return and is_last)
1093 result
1094 else
1095 null,
10181096 ) orelse {
10191097 return null;
10201098 };
......@@ -1023,8 +1101,7 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
10231101 return if (tag == .block_implicit_return) last else .void;
10241102 },
10251103
1026 .builtin_call,
1027 => {
1104 .builtin_call => {
10281105 const builtin = Builtin.map.get(t.ast.basicNodeSlice(node)[1..]) orelse {
10291106 try t.report.err(.invalid_builtin, .{ .node = node });
10301107 return .void;
......@@ -1034,12 +1111,12 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
10341111 const args = base.slice(&t.ast);
10351112
10361113 return switch (builtin) {
1037 .js => Builtin.builtin_js(t, decl, node, args),
1038 .js_new => Builtin.builtin_jsNew(t, decl, node, args),
1114 .js => Builtin.builtin_js(t, ctx, node, args),
1115 .js_new => Builtin.builtin_jsNew(t, ctx, node, args),
10391116 // .import => Builtin.import(t, decl, node, args),
10401117 // .fetch => Builtin.fetch(t, decl, node, args),
1041 .compile_error => Builtin.builtin_compileError(t, decl, node, args),
1042 .type_of => Builtin.builtin_typeOf(t, decl, node, args),
1118 .compile_error => Builtin.builtin_compileError(t, ctx, node, args),
1119 .type_of => Builtin.builtin_typeOf(t, ctx, node, args),
10431120 else => @panic("TODO"),
10441121 };
10451122 },
......@@ -1048,17 +1125,35 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
10481125 const call = t.ast.nodeData(node).call;
10491126 const args = t.ast.extraData(call.extra, Ast.Call).args.slice(&t.ast);
10501127
1051 const base = try t.transformExpression(decl, call.base) orelse return null;
1052 const args_list = try t.transformExpressionList(decl, args) orelse return null;
1128 var base_result: ResultType = .none;
1129 const base = try t.transformExpression(ctx, &base_result, call.base) orelse return null;
1130
1131 const args_list: RefList = call_args: {
1132 const extra_top = try t.extra.alloc(t.gpa, args.len);
1133
1134 for (args, 0.., extra_top..) |arg, arg_id, index| {
1135 var arg_result: ResultType = .{ .argument = .{
1136 .call_base = base,
1137 .argument = @intCast(arg_id),
1138 } };
1139 t.extra.setU32(
1140 index,
1141 @intFromEnum(try t.transformExpression(ctx, &arg_result, arg) orelse
1142 return null),
1143 );
1144 }
10531145
1054 const call_inst = try t.addInstRef(.{
1055 .tag = .call,
1056 .data = EncodedData.encode(.call, .{
1057 .src_node = decl.nodeOffset(node),
1058 .extra_index = try t.extra.append(t.gpa, Inst.Call, .{
1059 .base = base,
1060 .args = args_list,
1061 }),
1146 break :call_args .{
1147 .start = @intCast(extra_top),
1148 .len = @intCast(args.len),
1149 };
1150 };
1151
1152 const call_inst = try t.addRef(.call, .{
1153 .src_node = ctx.nodeOffset(node),
1154 .extra_index = try t.extra.append(t.gpa, Teg.Call, .{
1155 .base = base,
1156 .args = args_list,
10621157 }),
10631158 });
10641159
......@@ -1068,28 +1163,23 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
10681163 .field => {
10691164 const field = t.ast.nodeData(node).field;
10701165
1071 const base = try t.transformExpression(decl, field.base) orelse return null;
1166 var base_result: ResultType = .none;
1167 const base = try t.transformExpression(ctx, &base_result, field.base) orelse return null;
10721168
10731169 // TODO: handle identifier escapes
10741170 const id = t.ast.tokenSlice(field.field_token);
10751171 const prop_name_str = try t.addSlicedString(id);
10761172
1077 const string_ref = try t.addInstRef(.{
1078 .tag = .string,
1079 .data = EncodedData.encode(.string, .{
1080 .src_node = decl.nodeOffset(node),
1081 .extra_index = try t.extra.append(t.gpa, SlicedString, prop_name_str),
1082 }),
1173 const string_ref = try t.addRef(.string, .{
1174 .src_node = ctx.nodeOffset(node),
1175 .extra_index = try t.extra.append(t.gpa, SlicedString, prop_name_str),
10831176 });
10841177
1085 return try t.addInstRef(.{
1086 .tag = .field,
1087 .data = EncodedData.encode(.field, .{
1088 .src_node = decl.nodeOffset(node),
1089 .extra_index = try t.extra.append(t.gpa, Inst.Bin, .{
1090 .lhs = base,
1091 .rhs = string_ref,
1092 }),
1178 return try t.addRef(.field, .{
1179 .src_node = ctx.nodeOffset(node),
1180 .extra_index = try t.extra.append(t.gpa, Teg.Bin, .{
1181 .lhs = base,
1182 .rhs = string_ref,
10931183 }),
10941184 });
10951185 },
......@@ -1098,45 +1188,36 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
10981188 const if_node = t.ast.nodeData(node).@"if";
10991189 const if_data = t.ast.extraData(if_node.extra, Ast.If);
11001190
1101 const cond = try t.transformExpression(decl, if_node.cond) orelse
1191 var cond_result: ResultType = .{ .ref = .bool_type };
1192 const cond = try t.transformExpression(ctx, &cond_result, if_node.cond) orelse
11021193 return null;
11031194
1104 const yes = try t.transformExpressionToBlock(
1105 decl,
1106 if_data.yes,
1107 false,
1108 );
1195 var yes_result = result.*;
1196 const yes = try t.transformToBlock(ctx, if_data.yes, .tracked_result, &yes_result);
1197
1198 // TODO: can this re-use the resolved ref from above
1199 var no_result: ResultType = if (if_data.no == .none) .used else result.*;
11091200 const no = if (if_data.no.unwrap()) |no|
1110 try t.transformExpressionToBlock(
1111 decl,
1201 try t.transformToBlock(
1202 ctx,
11121203 no,
1113 false,
1204 .tracked_result,
1205 &no_result,
11141206 )
11151207 else
11161208 Inst.Ref.void;
11171209
1118 if (yes == .void and no == .void) {
1119 // when both branches do nothing, we can omit the branch,
1120 // but we still need to type-check the condition operand
1121 _ = try t.addInstRef(.{
1122 .tag = .ensure_bool,
1123 .data = EncodedData.encode(.ensure_bool, .{
1124 .src_node = decl.nodeOffset(node),
1125 .operand = cond,
1126 }),
1127 });
1128 return .void;
1210 // Mark the incoming result type as used if both branches use the result type
1211 if (result.* != .none and yes_result == .none and no_result == .none) {
1212 result.* = .used;
11291213 }
11301214
1131 return try t.addInstRef(.{
1132 .tag = .@"if",
1133 .data = EncodedData.encode(.@"if", .{
1134 .src_node = decl.nodeOffset(node),
1135 .extra_index = try t.extra.append(t.gpa, Inst.If, .{
1136 .cond = cond,
1137 .yes = yes,
1138 .no = no,
1139 }),
1215 return try t.addRef(.@"if", .{
1216 .src_node = ctx.nodeOffset(node),
1217 .extra_index = try t.extra.append(t.gpa, Teg.If, .{
1218 .cond = cond,
1219 .yes = yes,
1220 .no = no,
11401221 }),
11411222 });
11421223 },
......@@ -1144,36 +1225,35 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
11441225 .@"return" => {
11451226 const ret_node = t.ast.nodeData(node).@"return";
11461227
1147 const ret_block_ptr = decl.return_target orelse {
1228 const target = ctx.return_target orelse {
11481229 try t.report.err(.invalid_return, .{ .node = node });
11491230 return .void;
11501231 };
11511232
1152 const ret_block = if (ret_block_ptr.*) |block|
1233 const ret_block = if (target.generated_block) |block|
11531234 block
11541235 else append_block: {
1155 const block = try t.addRootInst(.{
1156 .tag = .block,
1157 .data = undefined,
1158 });
1159 ret_block_ptr.* = block;
1236 const block = try t.addUntracked(.block, undefined);
1237 target.generated_block = block;
11601238 break :append_block block;
11611239 };
11621240
1241 var return_result = target.result_type;
1242
11631243 const operand: Inst.Ref = if (ret_node.expr.unwrap()) |expr|
1164 try t.transformExpression(decl.withReturn(null), expr) orelse
1165 return null
1244 try t.transformExpression(
1245 ctx.withReturn(null),
1246 &return_result,
1247 expr,
1248 ) orelse return null
11661249 else
11671250 .void;
11681251
1169 _ = try t.addInstRef(.{
1170 .tag = .@"break",
1171 .data = EncodedData.encode(.@"break", .{
1172 .src_node = decl.nodeOffset(node),
1173 .extra_index = try t.extra.append(t.gpa, Inst.Break, .{
1174 .block = ret_block,
1175 .operand = operand,
1176 }),
1252 _ = try t.addIndex(.@"break", .{
1253 .src_node = ctx.nodeOffset(node),
1254 .extra_index = try t.extra.append(t.gpa, Teg.Break, .{
1255 .block = ret_block,
1256 .operand = operand,
11771257 }),
11781258 });
11791259
......@@ -1187,36 +1267,29 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
11871267 @panic("TODO: unlabelled breaks"),
11881268 );
11891269
1190 const block = try decl.findAndMarkBreak(t, label) orelse {
1270 const block: *BlockContext.BreakTarget = ctx.lookupNamedBreak(label) orelse {
11911271 try t.report.err(.unresolved_label, .{ .node = node });
11921272 return null;
11931273 };
11941274
1275 var break_result = block.result_type.*;
11951276 const operand: Inst.Ref = if (brk_node.value.unwrap()) |expr|
1196 try t.transformExpression(decl.withReturn(null), expr) orelse
1277 try t.transformExpression(ctx.withReturn(null), &break_result, expr) orelse
11971278 return null
11981279 else
11991280 .void;
12001281
1201 _ = try t.addInstRef(.{
1202 .tag = .@"break",
1203 .data = EncodedData.encode(.@"break", .{
1204 .src_node = decl.nodeOffset(node),
1205 .extra_index = try t.extra.append(t.gpa, Inst.Break, .{
1206 .block = block,
1207 .operand = operand,
1208 }),
1282 _ = try t.addIndex(.@"break", .{
1283 .src_node = ctx.nodeOffset(node),
1284 .extra_index = try t.extra.append(t.gpa, Teg.Break, .{
1285 .block = try block.getInst(t),
1286 .operand = operand,
12091287 }),
12101288 });
12111289
12121290 return null; // control flow ends
12131291 },
12141292
1215 .group => {
1216 const group_node = t.ast.nodeData(node).un;
1217 return try t.transformExpression(decl, group_node.expr);
1218 },
1219
12201293 .struct_decl => {
12211294 const struct_node = t.ast.fullStructDecl(node);
12221295 const payload_index = try t.extra.alloc(t.gpa, struct_node.fields.len * 2);
......@@ -1229,32 +1302,39 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
12291302 const field = t.ast.fullStructField(field_node);
12301303
12311304 const id = try t.decodeIdentifier(field.id);
1305 const key = try t.addNullTerminatedString(id);
1306
1307 var type_result: ResultType = .{ .ref = .type_type };
1308 const value = try t.transformExpression(ctx, &type_result, field.type) orelse
1309 return null;
12321310
1233 t.extra.setU32(i, @intFromEnum(try t.addNullTerminatedString(id)));
1234 t.extra.setU32(j, @intFromEnum(try t.transformExpressionToBlock(decl, field.type, false)));
1311 t.extra.setU32(i, @intFromEnum(key));
1312 t.extra.setU32(j, @intFromEnum(value));
12351313 }
12361314
1237 return try t.addInstRef(.{
1238 .tag = .struct_decl,
1239 .data = EncodedData.encode(.struct_decl, .{
1240 .src_node = decl.nodeOffset(node),
1241 .extra_index = try t.extra.append(t.gpa, Inst.StructDecl, .{
1242 .payload_index = payload_index,
1243 .field_len = @intCast(struct_node.fields.len),
1244 }),
1315 return try t.addRef(.struct_decl, .{
1316 .src_node = ctx.nodeOffset(node),
1317 .extra_index = try t.extra.append(t.gpa, Teg.StructDecl, .{
1318 .payload_index = payload_index,
1319 .field_len = @intCast(struct_node.fields.len),
12451320 }),
12461321 });
12471322 },
12481323
12491324 .struct_init => {
12501325 const struct_node = t.ast.fullStructInit(node);
1326 // TODO: use t.scratch for this
12511327 const payload_index = try t.extra.alloc(t.gpa, struct_node.fields.len * 2);
12521328
1253 const base_type = try t.transformExpression(
1254 decl,
1255 struct_node.type orelse @panic("TODO: TEG implicit struct"),
1256 ) orelse
1257 @panic("TODO: print better error");
1329 var result_type: ResultType = .{ .ref = .type_type };
1330 const base_type = if (struct_node.type) |ty|
1331 try t.transformExpression(ctx, &result_type, ty) orelse
1332 return null
1333 else
1334 (try result.consume(t)) orelse {
1335 try t.report.err(.missing_result_type, .{ .node = node });
1336 return .void;
1337 };
12581338
12591339 for (
12601340 struct_node.fields,
......@@ -1264,21 +1344,30 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
12641344 const field = t.ast.fullStructField(field_node);
12651345
12661346 const id = try t.decodeIdentifier(field.id);
1267
1268 t.extra.setU32(i, @intFromEnum(try t.addNullTerminatedString(id)));
1269 t.extra.setU32(j, @intFromEnum((try t.transformExpression(decl, field.type)) orelse
1270 @panic("TODO: print better error")));
1347 const field_name = try t.addNullTerminatedString(id);
1348
1349 var field_result_type: ResultType = .{ .struct_field = .{
1350 .type = base_type,
1351 .field = field_name,
1352 } };
1353 const field_value = (try t.transformExpression(
1354 ctx,
1355 &field_result_type,
1356 field.type,
1357 )) orelse return null;
1358
1359 t.extra.setU32(i, @intFromEnum(field_name));
1360 t.extra.setU32(j, @intFromEnum(
1361 field_value,
1362 ));
12711363 }
12721364
1273 return try t.addInstRef(.{
1274 .tag = .struct_init,
1275 .data = EncodedData.encode(.struct_init, .{
1276 .src_node = decl.nodeOffset(node),
1277 .extra_index = try t.extra.append(t.gpa, Inst.StructInit, .{
1278 .base_type = base_type,
1279 .payload_index = payload_index,
1280 .field_len = @intCast(struct_node.fields.len),
1281 }),
1365 return try t.addRef(.struct_init, .{
1366 .src_node = ctx.nodeOffset(node),
1367 .extra_index = try t.extra.append(t.gpa, Teg.StructInit, .{
1368 .base_type = base_type,
1369 .payload_index = payload_index,
1370 .field_len = @intCast(struct_node.fields.len),
12821371 }),
12831372 });
12841373 },
......@@ -1286,17 +1375,19 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
12861375 .array_init => {
12871376 const array_init = t.ast.nodeData(node).list;
12881377
1289 const items = try t.transformExpressionList(decl, array_init.slice(&t.ast)) orelse
1290 return null;
1378 const array_type = try result.consume(t);
12911379
1292 return try t.addInstRef(.{
1293 .tag = .array_init,
1294 .data = EncodedData.encode(.array_init, .{
1295 .src_node = decl.nodeOffset(node),
1296 .extra_index = try t.extra.append(t.gpa, Inst.ArrayInit, .{
1297 .type_hint = .none,
1298 .list = items,
1299 }),
1380 const items = try t.transformExpressionList(
1381 ctx,
1382 if (array_type) |ty| .{ .array_value = ty } else .none,
1383 array_init.slice(&t.ast),
1384 ) orelse return null;
1385
1386 return try t.addRef(.array_init, .{
1387 .src_node = ctx.nodeOffset(node),
1388 .extra_index = try t.extra.append(t.gpa, Teg.ArrayInit, .{
1389 .type_hint = .none,
1390 .list = items,
13001391 }),
13011392 });
13021393 },
......@@ -1305,12 +1396,16 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
13051396 }
13061397}
13071398
1308fn transformExpressionList(t: *Transform, decl: BlockContext, args: []const NodeIndex) !?RefList {
1399fn transformExpressionList(t: *Transform, decl: BlockContext, result: ResultType, args: []const NodeIndex) !?RefList {
13091400 const extra_top = try t.extra.alloc(t.gpa, args.len);
13101401
13111402 for (args, extra_top..) |arg, i| {
1312 t.extra.setU32(i, @intFromEnum(try t.transformExpression(decl, arg) orelse
1313 return null));
1403 var result_mut = result;
1404 t.extra.setU32(
1405 i,
1406 @intFromEnum(try t.transformExpression(decl, &result_mut, arg) orelse
1407 return null),
1408 );
13141409 }
13151410
13161411 return .{
......@@ -1319,7 +1414,25 @@ fn transformExpressionList(t: *Transform, decl: BlockContext, args: []const Node
13191414 };
13201415}
13211416
1322fn transformAssignmentTarget(t: *Transform, decl: BlockContext, node: NodeIndex) Allocator.Error!?Inst.Index {
1417/// The left-hand side of an assignment is known as the "assignment target".
1418/// This returns the assignment target's Ref, as well as a result type to visit
1419/// the right-hand side with. Note that only a few expressions are valid here:
1420///
1421/// - A plain identifier, which is resolved directly to a `memory` instruction
1422/// - A `field` node: TODO
1423/// - An `index` node: TODO
1424///
1425/// Note that these three are the root nodes, it is legal to have:
1426///
1427/// get().x = 1; // `field` of a `call` node
1428///
1429/// In the end, the assignment target must comprise either a ref to a `memory`
1430/// or a value with a reference type; and optionally a sub property.
1431fn transformAssignmentTarget(
1432 t: *Transform,
1433 decl: BlockContext,
1434 node: NodeIndex,
1435) Allocator.Error!?struct { Inst.Index, ResultType } {
13231436 const node_tag = t.ast.nodes.items(.tag)[node];
13241437
13251438 const trace = tracer.traceFmt(@src(), "{s}", .{@tagName(node_tag)});
......@@ -1345,7 +1458,7 @@ fn transformAssignmentTarget(t: *Transform, decl: BlockContext, node: NodeIndex)
13451458 return null;
13461459 }
13471460
1348 const lookup = t.lookupId(id) orelse {
1461 const lookup: *Scope.Symbol = t.lookupId(id) orelse {
13491462 try t.report.err(.unknown_identifier, .{ .node = node });
13501463 return null;
13511464 };
......@@ -1353,7 +1466,7 @@ fn transformAssignmentTarget(t: *Transform, decl: BlockContext, node: NodeIndex)
13531466 return switch (lookup.data) {
13541467 .mutable => |inst| {
13551468 lookup.mutated = true;
1356 return inst;
1469 return .{ inst, lookup.type.resolve() };
13571470 },
13581471 else => {
13591472 lookup.used = true;
......@@ -1371,6 +1484,7 @@ fn transformAssignmentTarget(t: *Transform, decl: BlockContext, node: NodeIndex)
13711484 },
13721485 },
13731486 });
1487
13741488 return null;
13751489 },
13761490 };
......@@ -1379,10 +1493,13 @@ fn transformAssignmentTarget(t: *Transform, decl: BlockContext, node: NodeIndex)
13791493 // TODO: allow field expressions, but these need to emit a different store node
13801494
13811495 else => {
1382 _ = try t.transformExpression(decl, node);
1496 var result_type: ResultType = .none;
1497 _ = try t.transformExpression(decl, &result_type, node);
1498
13831499 try t.report.err(.invalid_assignment_target, .{
13841500 .node = node,
13851501 });
1502
13861503 return null;
13871504 },
13881505 }
......@@ -1390,10 +1507,13 @@ fn transformAssignmentTarget(t: *Transform, decl: BlockContext, node: NodeIndex)
13901507
13911508fn transformBlockStatement(
13921509 t: *Transform,
1393 decl: BlockContext,
1510 ctx: BlockContext,
13941511 node: NodeIndex,
1395 is_implicit_return_expression: bool,
1512 maybe_result: ?*ResultType,
13961513) !?Inst.Ref {
1514 var void_result_storage: ResultType = .{ .ref = .void };
1515 const result = maybe_result orelse &void_result_storage;
1516
13971517 const node_tag = t.ast.nodes.items(.tag)[node];
13981518
13991519 const trace = tracer.traceFmt(@src(), "{s}", .{@tagName(node_tag)});
......@@ -1437,16 +1557,16 @@ fn transformBlockStatement(
14371557
14381558 return .void;
14391559 },
1440 else => try t.transformExpression(decl, var_decl.init) orelse
1441 return null,
1560 else => ref: {
1561 var empty_type: ResultType = .none;
1562 break :ref try t.transformExpression(ctx, &empty_type, var_decl.init) orelse
1563 return null;
1564 },
14421565 };
14431566
1444 return try t.addInstRef(.{
1445 .tag = .discard,
1446 .data = EncodedData.encode(.discard, .{
1447 .src_node = decl.nodeOffset(node),
1448 .operand = ref,
1449 }),
1567 return try t.addRef(.discard, .{
1568 .src_node = ctx.nodeOffset(node),
1569 .operand = ref,
14501570 });
14511571 }
14521572
......@@ -1464,36 +1584,37 @@ fn transformBlockStatement(
14641584 return .void;
14651585 }
14661586
1587 var type_result: ResultType = .{ .ref = .type_type };
14671588 const type_ref = if (var_decl.type) |data_type|
1468 try t.transformExpression(decl, data_type)
1589 try t.transformExpression(ctx, &type_result, data_type)
14691590 else
14701591 null;
1471 const data_ref = try t.transformExpression(decl, var_decl.init) orelse
1472 return null;
1473 const data_coerced_ref = if (type_ref) |ref|
1474 try t.addInstRef(.{
1475 .tag = .as,
1476 .data = EncodedData.encode(.as, .{
1477 .src_node = decl.nodeOffset(node),
1478 .extra_index = try t.extra.append(t.gpa, Inst.As, .{
1479 .type = ref,
1480 .operand = data_ref,
1481 }),
1482 }),
1483 })
1484 else
1485 data_ref;
14861592
1487 const mem_ref = if (var_decl.is_const)
1488 data_coerced_ref
1593 var value_result: ResultType = if (type_ref) |ty|
1594 .{ .ref = ty }
14891595 else
1490 try t.addInstRef(.{
1491 .tag = .memory,
1492 .data = EncodedData.encode(.memory, .{
1493 .src_node = decl.nodeOffset(node),
1494 .operand = data_coerced_ref,
1495 }),
1496 });
1596 .none;
1597
1598 const data_ref = try t.transformExpression(ctx, &value_result, var_decl.init) orelse
1599 return null;
1600
1601 // Add a .coerce instruction if there was a type declared.
1602 const data_coerced_ref = coerced: {
1603 if (type_ref) |ref| {
1604 // We can avoid the coercion instruction if the result type was used.
1605 if (value_result != .used) {
1606 break :coerced try t.addRef(.coercion, .{
1607 .src_node = ctx.nodeOffset(node),
1608 .extra_index = try t.extra.append(t.gpa, Teg.Coercion, .{
1609 .type = ref,
1610 .operand = data_ref,
1611 }),
1612 });
1613 }
1614 }
1615
1616 break :coerced data_ref;
1617 };
14971618
14981619 const gop = try t.scope().symbols.getOrPut(t.gpa, id);
14991620
......@@ -1505,16 +1626,26 @@ fn transformBlockStatement(
15051626
15061627 gop.value_ptr.* = .{
15071628 .node = node,
1508 .data = switch (var_decl.is_const) {
1509 true => .{ .immutable = mem_ref },
1510 false => .{ .mutable = mem_ref.index().? },
1511 },
1629 .data = if (var_decl.is_const)
1630 .{ .immutable = data_coerced_ref }
1631 else
1632 .{
1633 .mutable = try t.addIndex(.memory, .{
1634 .src_node = ctx.nodeOffset(node),
1635 .operand = data_coerced_ref,
1636 }),
1637 },
1638 .type = if (type_ref) |ty|
1639 .{ .present = ty }
1640 else
1641 .{ .missing = data_ref },
15121642 };
15131643
15141644 return .void;
15151645 },
15161646
15171647 .fn_decl,
1648 .struct_decl,
15181649 => |tag| {
15191650 std.debug.panic("TODO: block statement {s}", .{@tagName(tag)});
15201651 },
......@@ -1530,18 +1661,17 @@ fn transformBlockStatement(
15301661 // discard, instead of appending an .ensure_void check,
15311662 // we will add a .discard instruction instead.
15321663 // this will emit a warning if given void.
1533 const result = try t.transformExpression(decl, bin.rhs) orelse
1664 var result_type: ResultType = .none;
1665 const value = try t.transformExpression(ctx, &result_type, bin.rhs) orelse
15341666 return null;
1535 return try t.addInstRef(.{
1536 .tag = .discard,
1537 .data = EncodedData.encode(.discard, .{
1538 .src_node = decl.nodeOffset(node),
1539 .operand = result,
1540 }),
1667
1668 return try t.addRef(.discard, .{
1669 .src_node = ctx.nodeOffset(node),
1670 .operand = value,
15411671 });
15421672 } else {
15431673 // process this assignment as usual, do not append .ensure_void
1544 return try t.transformExpression(decl, node);
1674 return try t.transformExpression(ctx, result, node);
15451675 }
15461676 },
15471677
......@@ -1560,50 +1690,51 @@ fn transformBlockStatement(
15601690 .shr_assign,
15611691 => {
15621692 // do not append a .ensure_void check
1563 return try t.transformExpression(decl, node);
1693 return try t.transformExpression(ctx, result, node);
15641694 },
15651695
15661696 else => {
1567 const result = try t.transformExpression(decl, node) orelse
1697 const value = try t.transformExpression(ctx, result, node) orelse
15681698 return null;
15691699
1570 if (!is_implicit_return_expression and result != .void) {
1700 if (maybe_result == null and value != .void) {
15711701 if (t.ast.nodeTag(node) == .id) {
15721702 try t.report.warn(.dangling_identifier, .{
15731703 .node = node,
15741704 });
1575 return result;
15761705 }
15771706
1578 return try t.addInstRef(.{
1579 .tag = .ensure_void,
1580 .data = EncodedData.encode(.ensure_void, .{
1581 .src_node = decl.nodeOffset(node),
1582 .operand = result,
1583 }),
1707 return try t.addRef(.ensure_void, .{
1708 .src_node = ctx.nodeOffset(node),
1709 .operand = value,
15841710 });
15851711 }
15861712
1587 return result;
1713 return value;
15881714 },
15891715 }
15901716}
15911717
1592fn addRootInst(t: *Transform, inst: Inst) !Inst.Index {
1593 try t.insts.append(t.gpa, inst);
1718fn addUntracked(t: *Transform, comptime tag: Teg.Tag, data: tag.Data()) !Inst.Index {
1719 try t.insts.append(t.gpa, .{
1720 .tag = tag,
1721 .data = EncodedData.encode(tag, data),
1722 });
15941723 return @enumFromInt(t.insts.len - 1);
15951724}
15961725
1597fn addInstRef(t: *Transform, inst: Inst) !Inst.Ref {
1598 try t.scratch.ensureUnusedCapacity(t.gpa, 1);
1599 try t.insts.append(t.gpa, inst);
1600 const index: Inst.Index = @enumFromInt(t.insts.len - 1);
1601 t.scratch.appendAssumeCapacity(@intFromEnum(index));
1602 return index.toRef();
1726fn addIndex(t: *Transform, comptime tag: Teg.Tag, data: tag.Data()) !Inst.Index {
1727 const index = try t.addUntracked(tag, data);
1728 try t.scratch.add(t.arena, Inst.Index, index);
1729 return index;
1730}
1731
1732fn addRef(t: *Transform, comptime tag: Teg.Tag, data: tag.Data()) !Inst.Ref {
1733 return (try t.addIndex(tag, data)).toRef();
16031734}
16041735
16051736fn pushScope(t: *Transform) !void {
1606 try t.scopes.append(t.gpa, .{});
1737 try t.scopes.append(t.arena, .{});
16071738}
16081739
16091740fn popScope(t: *Transform) void {
......@@ -1620,22 +1751,18 @@ fn getInstData(t: *Transform, index: Inst.Index) *EncodedData {
16201751 return &t.insts.items(.data)[@intFromEnum(index)];
16211752}
16221753
1623fn addScratch(t: *Transform, comptime T: type, value: T) !void {
1624 try t.scratch.append(t.gpa, @intFromEnum(value));
1625}
1626
1627fn scratchToRefList(t: *Transform, scratch_top: usize) !RefList {
1628 const items = t.scratch.items[scratch_top..];
1754fn scratchToRefList(t: *Transform, scratch_top: core.U32Stack.Mark) !RefList {
1755 const items = scratch_top.slice(u32);
16291756 const extra_top = t.extra.list.items.len;
1630 try t.extra.list.appendSlice(t.gpa, t.scratch.items[scratch_top..]);
1757 try t.extra.list.appendSlice(t.gpa, items);
16311758 return .{
16321759 .start = @intCast(extra_top),
16331760 .len = @intCast(items.len),
16341761 };
16351762}
16361763
1637fn scratchToIndexList(t: *Transform, scratch_top: usize) !IndexList {
1638 const items = t.scratch.items[scratch_top..];
1764fn scratchToIndexList(t: *Transform, scratch: core.U32Stack.Mark) !IndexList {
1765 const items = scratch.slice(u32);
16391766 const extra_top = t.extra.list.items.len;
16401767 try t.extra.list.appendSlice(t.gpa, items);
16411768 return .{
......@@ -1715,8 +1842,8 @@ fn addDecodedSlicedString(t: *Transform, escaped: []const u8) !SlicedString {
17151842 // string. this is done so that duplicate strings will get de-duplicated
17161843 // in string_bytes. It is more likely that there will be remaining bytes
17171844 // in `.scratch` vs `.id_decode_buf`
1718 try t.scratch.ensureUnusedCapacity(t.gpa, @divFloor(len, @sizeOf(u32)) + 1);
1719 const buf = mem.sliceAsBytes(t.scratch.unusedCapacitySlice());
1845 try t.scratch.list.ensureUnusedCapacity(t.gpa, @divFloor(len, @sizeOf(u32)) + 1);
1846 const buf = mem.sliceAsBytes(t.scratch.list.unusedCapacitySlice());
17201847
17211848 break :brk Tokenizer.decodeStringBuf(escaped, buf);
17221849 } else escaped;
......@@ -1767,7 +1894,8 @@ const mem = std.mem;
17671894const Allocator = mem.Allocator;
17681895const assert = std.debug.assert;
17691896
1770const Teg = @import("Teg.zig");
1897const core = @import("mod.zig");
1898const Teg = core.Teg;
17711899const Inst = Teg.Inst;
17721900const NullTerminatedString = Teg.NullTerminatedString;
17731901const SlicedString = Teg.SlicedString;
......@@ -1776,15 +1904,15 @@ const IndexList = Teg.IndexList;
17761904const EncodedData = Teg.EncodedData;
17771905const ExtraIndex = Teg.ExtraIndex;
17781906
1779const dod = @import("array.zig");
1780const ExtraStore = dod.ExtraStore(.teg, .@"var");
1781const Float = dod.Float;
1907const extra = core.array;
1908const ExtraStore = extra.ExtraStore(.teg, .@"var");
1909const Float = extra.Float;
17821910
1783const Tokenizer = @import("Tokenizer.zig");
1911const Tokenizer = core.Tokenizer;
17841912const Token = Tokenizer.Token;
17851913const ByteOffset = Tokenizer.ByteOffset;
17861914
1787const Ast = @import("Ast.zig");
1915const Ast = core.Ast;
17881916const Unused = Ast.Unused;
17891917const Node = Ast.Node;
17901918const Data = Node.Data;
......@@ -1798,9 +1926,9 @@ const optionalTokenIndex = OptionalTokenIndex.init;
17981926const optionalNodeIndex = OptionalNodeIndex.init;
17991927const optionalExtraIndex = OptionalExtraIndex.init;
18001928
1801const InternPool = @import("InternPool.zig");
1929const InternPool = core.InternPool;
18021930
1803const Report = @import("Report.zig");
1804const Comp = @import("Comp.zig");
1931const Report = core.Report;
1932const Comp = core.Comp;
18051933
18061934const tracer = @import("tracer");
src/core/main.zig-5
......@@ -22,11 +22,6 @@ pub fn main() !void {
2222
2323 try unicode_props.init();
2424
25 if (builtin.mode == .Debug and std.io.getStdErr().isTty()) {
26 // zig build system doesnt print a newline :/
27 std.debug.print("\r\x1b[K", .{});
28 }
29
3025 const use_gpa = builtin.mode == .Debug;
3126 var gpa = if (use_gpa)
3227 std.heap.GeneralPurposeAllocator(.{
src/core/mod.zig+3
......@@ -32,6 +32,9 @@ pub const Sema = @import("Sema.zig");
3232pub const Stack = @import("stack.zig").Stack;
3333pub const U32Stack = Stack(u32);
3434
35pub const StoredTrace = @import("StoredTrace.zig");
36pub const DebugStoredTrace = StoredTrace.DebugStoredTrace;
37
3538/// Untyped Intermediate Representation. Flat instruction list
3639pub const Teg = @import("Teg.zig");
3740