diff --git a/CHANGELOG.md b/CHANGELOG.md index ff2f16993cefa6a4e096634687b07948833207b5..203b85d4b1f119d04100705c5470f32f9d610dec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ ## 0.1.5 - feat: clarify "defined runtime representation" and "can be represented at runtime" +- feat: implement inferred structs + result type semantics +- chore: remove group expressions, use single expression blocks instead - fix(sema): fix passing structs as function params - fix: diagnostic box drawing characters line up after line 9 - fix: diagnostics on last line of file print correctly diff --git a/build.zig b/build.zig index 1d89a00731183659d272831ae7928c5871cf622a..54c19b08bf70601da6aa4e9b8020b6b13dc4da79 100644 --- a/build.zig +++ b/build.zig @@ -103,6 +103,7 @@ pub fn build(b: *std.Build) !void { const manual_out = manual_run.addOutputFileArg("language-reference.html"); const install_manual = b.addInstallFile(manual_out, "doc/README.html"); b.step("manual", "build language reference").dependOn(&install_manual.step); + b.install_tls.step.dependOn(&install_manual.step); // const wasm_api = b.addExecutable(.{ // .name = "compiler", diff --git a/doc/language-manual.md b/doc/language-manual.md index 7ba4ed14cb4f7d4c96bb6823c8507c5867504919..3a03a2dca825a00ef1861210225be936d15449d6 100644 --- a/doc/language-manual.md +++ b/doc/language-manual.md @@ -176,13 +176,12 @@ print(center.x, center.y); fn addPositions(a: Position, b: Position) -> Position { // To allow less verbose code, the struct type can be inferred with `.` - // TODO: that is not implemented ;-; - Position{ x: a.x + b.x, y: a.y + b.y } + .{ x: a.x + b.x, y: a.y + b.y } } let added = addPositions( - Position{ x: 1, y: -4 }, - Position{ x: 5, y: 8 }, + .{ x: 1, y: -4 }, + .{ x: 5, y: 8 }, ); print("Add result:", added.x, added.y); ``` diff --git a/src/core/Ast.zig b/src/core/Ast.zig index a80f54645797091cf27f3f3f8f3456835a367b40..788346e7ec2f57c55194232e3a3d420e0127313a 100644 --- a/src/core/Ast.zig +++ b/src/core/Ast.zig @@ -1,7 +1,7 @@ //! The Abstract Syntax Tree preserves most details of the source code, //! as it is used for formatting. Before semantic analysis, this //! structure is normalized into `Teg`, which removes constructs like -//! grouping, unneeded let bindings, and other transforms. +//! whitespace, unneeded let bindings, and other transforms. //! //! `ast.nodes` contains nodes stored as three parts: //! - tag: the type of the node @@ -130,10 +130,6 @@ pub const Node = struct { /// main_token is the `break` /// data is .@"break" @"break", - /// `(expr)` - /// main_token is the `(` - /// data is .un - group, /// `lhs + rhs`. main_token is the operator. add, /// `lhs - rhs`. main_token is the operator. @@ -222,6 +218,10 @@ pub const Node = struct { array_type, /// Array initialization array_init, + /// Tuple type + tuple_type, + /// Tuple Init + tuple_init, pub fn isAssignBin(op: Tag) bool { return switch (op) { @@ -309,7 +309,6 @@ pub const Node = struct { .fn_decl = .function, .fn_expr = .function, .ge = .bin, - .group = .un, .gt = .bin, .id = .none, .index = .bin, @@ -344,6 +343,8 @@ pub const Node = struct { .sub = .bin, .sub_assign = .bin, .var_decl = .var_decl, + .tuple_type = .list, + .tuple_init = .list, // sort-lines:end }); diff --git a/src/core/Parse.zig b/src/core/Parse.zig index cea204e7dbcd9ae887bd7a7b9faee639d418d3f1..039fb77d812ea42d4285774c7406179fe225c896 100644 --- a/src/core/Parse.zig +++ b/src/core/Parse.zig @@ -31,7 +31,7 @@ pub fn parse(gpa: std.mem.Allocator, source: [:0]const u8, ast_error: *AstError. .err = err, .byte = err_byte, } }, - .trace = Report.DebugStoredTrace.from(@errorReturnTrace()), + .trace = DebugStoredTrace.from(@errorReturnTrace()), }; return error.ParseFail; }, @@ -143,7 +143,7 @@ pub const AstError = union(enum) { array_type_flipped_hint: Token, - pub fn toReport(err: AstError, comp: *Comp, file: *Comp.File, trace: Report.DebugStoredTrace) !Report { + pub fn toReport(err: AstError, comp: *Comp, file: *Comp.File, trace: DebugStoredTrace) !Report { return (Report.Err{ .ast = err }).toReport(comp, file, trace); } @@ -193,7 +193,7 @@ pub const AstError = union(enum) { pub const WithTrace = struct { err: AstError, - trace: Report.DebugStoredTrace, + trace: DebugStoredTrace, pub fn toReport(err: WithTrace, comp: *Comp, file: *Comp.File) !Report { return err.err.toReport(comp, file, err.trace); @@ -856,7 +856,7 @@ fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex { .kw_return => try p.parseReturn(), .start_brace => try p.parseBlock(), .start_bracket => try p.parseArrayValueOrType(mode), - .start_paren => try p.parseGroup(), + .start_paren => try p.parseTupleType(), // Unary operators inline .bang, @@ -1136,25 +1136,6 @@ fn parseElseValue(p: *Parse) Error!NodeIndex { }; } -fn parseGroup(p: *Parse) Error!NodeIndex { - const trace = t.trace(@src()); - defer trace.end(); - - const main_token = p.assertToken(.start_paren); - - const expr = try p.parseExpression(.expression); - - _ = try p.expect(.end_paren); - - return try p.addNode(.{ - .tag = .group, - .main_token = main_token, - .data = .{ .un = .{ - .expr = expr, - } }, - }); -} - fn parseAnonInstantiation(p: *Parse) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); @@ -1266,6 +1247,16 @@ fn parseArrayValueOrType(p: *Parse, mode: ParseExprMode) Error!NodeIndex { }); } +fn parseTupleType(p: *Parse) Error!NodeIndex { + const main_token = p.assertToken(.start_bracket); + const list = try p.parseExpressionList(.end_paren, .expression); + return p.addNode(.{ + .tag = .tuple_type, + .main_token = main_token, + .data = .{ .list = list }, + }); +} + // helpers /// Append a node and return its index. @@ -1292,7 +1283,7 @@ fn addExtra(p: *Parse, comptime T: type, extra: T) !ExtraIndex { fn fail(p: *Parse, ast_error: AstError) Error { p.ast_error = .{ .err = ast_error, - .trace = Report.DebugStoredTrace.capture(@returnAddress()), + .trace = DebugStoredTrace.capture(@returnAddress()), }; return Error.ParseFail; } @@ -1366,10 +1357,6 @@ fn nextToken(p: *Parse) TokenIndex { return result; } -fn tokensOnSameLine(p: *Parse, token1: TokenIndex, token2: TokenIndex) bool { - return std.mem.indexOfScalar(u8, p.source[p.token_starts[token1]..p.token_starts[token2]], '\n') == null; -} - fn fullToken(p: *Parse, token: TokenIndex) Token { return Token{ .tag = p.token_tags[token], @@ -1398,11 +1385,14 @@ const mem = std.mem; const Allocator = mem.Allocator; const ArrayListUnmanaged = std.ArrayListUnmanaged; -const Tokenizer = @import("Tokenizer.zig"); +const core = @import("mod.zig"); +const DebugStoredTrace = core.DebugStoredTrace; + +const Tokenizer = core.Tokenizer; const Token = Tokenizer.Token; const ByteOffset = Tokenizer.ByteOffset; -const Ast = @import("Ast.zig"); +const Ast = core.Ast; const Node = Ast.Node; const Data = Node.Data; const SubList = Ast.SubList; @@ -1418,5 +1408,5 @@ const optionalExtraIndex = OptionalExtraIndex.init; const t = @import("tracer"); -const Report = @import("Report.zig"); -const Comp = @import("Comp.zig"); +const Report = core.Report; +const Comp = core.Comp; diff --git a/src/core/Report.zig b/src/core/Report.zig index 86c8df1f8bf7d39d1f66ff2819448a91ebb0a289..18fc277abcce5dd1e39f0ea8cf9aab2f7a264ff5 100644 --- a/src/core/Report.zig +++ b/src/core/Report.zig @@ -135,6 +135,17 @@ pub const Err = union(enum) { ); } }, + missing_result_type: struct { + node: NodeIndex, + + fn build(data: @This(), ctx: *MsgContext) !void { + try ctx.msgNode( + data.node, + "Cannot infer struct initialization type", + .{}, + ); + } + }, unresolved_label: struct { node: NodeIndex, @@ -523,62 +534,6 @@ pub const Warn = union(enum) { }, }; -/// A variant of std.builtin.StackTrace that stores its data within itself -/// instead of being a pointer. This allows capturing the stack trace and -/// saving it for later printing. -pub const StoredTrace = struct { - data: [32]usize, - index: usize, - - pub const empty = .{ - .data = .{0} ** 32, - .index = 0, - }; - - fn trace(stored: *StoredTrace) std.builtin.StackTrace { - return .{ - .index = stored.index, - .instruction_addresses = &stored.data, - }; - } - - pub fn capture(begin: ?usize) StoredTrace { - var stored: StoredTrace = undefined; - var frame = stored.trace(); - std.debug.captureStackTrace(begin orelse @returnAddress(), &frame); - stored.index = frame.index; - return stored; - } - - pub fn from(stack_trace: ?*std.builtin.StackTrace) StoredTrace { - if (stack_trace) |stack| { - var data: [32]usize = undefined; - @memset(&data, 0); - const items = @min(stack.instruction_addresses.len, 32); - @memcpy(data[0..items], stack.instruction_addresses[0..items]); - return .{ - .data = data, - .index = @min(items, stack.index), - }; - } else { - return empty; - } - } -}; - -pub const DebugStoredTrace = if (builtin.mode == .Debug) - StoredTrace -else - struct { - pub fn capture(_: ?usize) @This() { - return .{}; - } - - pub fn from(_: ?*std.builtin.StackTrace) @This() { - return .{}; - } - }; - pub const UnusedKind = enum { param, constant, @@ -817,7 +772,7 @@ pub const Builder = struct { try b.errors.append(b.gpa, .{ .data = .{ .@"error" = @unionInit(Err, @tagName(kind), data) }, .url = try b.currentFileString(), - .debug_stack_trace = if (builtin.mode == .Debug) StoredTrace.capture(@returnAddress()) else .{}, + .debug_stack_trace = DebugStoredTrace.capture(@returnAddress()), }); } @@ -825,7 +780,7 @@ pub const Builder = struct { try b.warnings.append(b.gpa, .{ .data = .{ .warn = @unionInit(Warn, @tagName(kind), data) }, .url = try b.currentFileString(), - .debug_stack_trace = if (builtin.mode == .Debug) StoredTrace.capture(@returnAddress()) else .{}, + .debug_stack_trace = DebugStoredTrace.capture(@returnAddress()), }); } @@ -1050,7 +1005,7 @@ pub const LazyNode = struct { const node_tag = ctx.ast.nodeTag(base_node); switch (teg.instTag(lazy.inst)) { - .as => switch (node_tag) { + .coercion => switch (node_tag) { .var_decl => switch (lazy.operand) { .lhs => return data.var_decl.type.unwrap().?, .rhs => return data.var_decl.init, @@ -1121,6 +1076,10 @@ pub const LazyNode = struct { const items = data.list.slice(ctx.ast); return items[lazy.operand.getArgId()]; }, + .field => { + assert(node_tag == .field); + return base_node; + }, else => {}, } @@ -1358,19 +1317,22 @@ const builtin = @import("builtin"); const unicode_props = @import("unicode_props.zig"); -const Tokenizer = @import("Tokenizer.zig"); +const core = @import("mod.zig"); +const DebugStoredTrace = core.DebugStoredTrace; + +const Tokenizer = core.Tokenizer; const ByteOffset = Tokenizer.ByteOffset; -const Ast = @import("Ast.zig"); +const Ast = core.Ast; const TokenIndex = Ast.TokenIndex; const NodeIndex = Ast.NodeIndex; const Location = Ast.Location; -const Teg = @import("Teg.zig"); +const Teg = core.Teg; -const Comp = @import("Comp.zig"); +const Comp = core.Comp; const File = Comp.File; -const InternPool = @import("InternPool.zig"); +const InternPool = core.InternPool; const Value = InternPool.Value; const Type = InternPool.Type; diff --git a/src/core/Sema.zig b/src/core/Sema.zig index d54ba755c8d44f1c23211e7d2cb63e1d2263f644..4a3922ab18ee7b83a2b3510e5da7552429f15199 100644 --- a/src/core/Sema.zig +++ b/src/core/Sema.zig @@ -66,24 +66,30 @@ pub const Error = error{ OutOfMemory, } || Comp.File.GetTegError; -/// Instead of using a Hash map for this, just pre-allocate a list long enough to hold everything. -/// This is fine because all instructions are going to be adjacent in a single declaration. +/// Instead of using a Hash map for this, all of the information is pre-allocated. +/// This is only possible because all instructions are adjacent in Teg. const InstMap = struct { items: []Air.Inst.Ref.Optional, start: Teg.Inst.Index, + debug_traces: if (std.debug.runtime_safety) []StoredTrace else void, pub fn init(allocator: Allocator, code: *const Teg, inst_start: Teg.Inst.Index) !InstMap { const decl = code.declData(inst_start); const insts = try allocator.alloc(Air.Inst.Ref.Optional, decl.instruction_count); + const debug_traces = if (std.debug.runtime_safety) + try allocator.alloc(StoredTrace, decl.instruction_count); @memset(insts, .none); return .{ .items = insts, .start = inst_start, + .debug_traces = debug_traces, }; } pub fn deinit(map: *InstMap, allocator: Allocator) void { allocator.free(map.items); + if (std.debug.runtime_safety) + allocator.free(map.debug_traces); map.* = undefined; } @@ -95,16 +101,21 @@ const InstMap = struct { } pub fn add(map: *InstMap, from: Teg.Inst.Index, to: Air.Inst.Ref) !void { + const source_i = @intFromEnum(from); + const start_i = @intFromEnum(map.start); + const index = source_i - start_i; if (assertFailed(map.get(from) == null)) { const s: *const Sema = @fieldParentPtr("inst_map", map); + std.debug.print("Original was added here:\n", .{}); + std.debug.dumpStackTrace(map.debug_traces[index].trace()); std.debug.panic("%{d} is already mapped (to {})", .{ @intFromEnum(from), map.get(from).?.dump(s.ip), }); } - const source_i = @intFromEnum(from); - const start_i = @intFromEnum(map.start); - map.items[source_i - start_i] = to.toOptional(); + map.items[index] = to.toOptional(); + if (std.debug.runtime_safety) + map.debug_traces[index] = StoredTrace.capture(@returnAddress()); } }; @@ -216,36 +227,44 @@ pub fn analyzeFunctionDeclaration(s: *Sema) Error!Value { s.params[i] = ty; } - const return_type = try s.resolveType( - s.inst_map.start.op(.return_type), - fn_decl.return_type, - ); + var return_type, const early_value = if (fn_decl.return_type.unwrap()) |ty| brk: { + const return_type = try s.resolveType( + s.inst_map.start.op(.return_type), + ty, + ); + const value = try s.makeValue(.{ + .func = .{ + .ty = try s.makeType(.{ + .function_type = .{ + .param_types = s.params, + .return_type = return_type, + }, + }), + .owner_decl = s.decl_index, + .teg_body_inst = s.decl.teg_decl_index, + }, + }); - const func_prototype = (try s.ip.get(s.gpa, .{ - .function_type = .{ - .param_types = s.params, - .return_type = return_type, - }, - })).toTypeUnsafe(s.ip); - const value = try s.ip.get(s.gpa, .{ - .func = .{ - .ty = func_prototype, - .owner_decl = s.decl_index, - .teg_body_inst = s.decl.teg_decl_index, - }, - }); + s.decl.raw_value = value.typeOf(s.ip).value(); + s.decl.analysis = .in_progress_type_available; - s.decl.raw_value = value.typeOf(s.ip).value(); - s.decl.analysis = .in_progress_type_available; + break :brk .{ return_type, value }; + } else .{ null, null }; const root_block = decl.body; switch (root_block.decode()) { .value => |val| { - const coerced = try s.coerceValue( - s.inst_map.start.op(.operand), - val, - return_type, - ); + const coerced = if (return_type) |ty| + try s.coerceValue( + s.inst_map.start.op(.operand), + val, + ty, + ) + else brk: { + return_type = val.typeOf(s.ip); + break :brk val; + }; + const block = try s.addBlock(); _ = try s.addInstRef(.@"break", .{ .block_ref = block.block_index, @@ -253,11 +272,30 @@ pub fn analyzeFunctionDeclaration(s: *Sema) Error!Value { }); }, .index => |index| { - try s.temp_result_map.putNoClobber(s.arena, index, return_type); - _ = try s.analyzeBlock(index); + if (return_type) |ty| { + try s.temp_result_map.putNoClobber(s.arena, index, ty); + } + + const out = try s.analyzeBlock(index); + + return_type = return_type orelse s.typeOfRef(out); }, } + const value = early_value orelse try s.makeValue(.{ + .func = .{ + .ty = try s.makeType(.{ + .function_type = .{ + .param_types = s.params, + .return_type = return_type orelse + unreachable, // type is reassigned above + }, + }), + .owner_decl = s.decl_index, + .teg_body_inst = s.decl.teg_decl_index, + }, + }); + s.decl.analysis = .complete; s.decl.raw_value = value; @@ -342,7 +380,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { return s.addInstRef(.js_global_ref, operand.asString(s.ip)); }, .field => { - const field: Teg.Inst.Bin = s.code.instPayload(.field, in); + const field: Teg.Bin = s.code.instPayload(.field, in); const base = try s.resolveRef(field.lhs); 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 { }); }, .load_decl => { - const request: Teg.Inst.StrNode = s.code.instData(.load_decl, in); + const request: Teg.StrNode = s.code.instData(.load_decl, in); const decl_name = try s.ip.getOrPutString( s.gpa, request.string.slice(s.code.string_bytes), @@ -416,7 +454,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { return resolved.toAir(); }, .param => { - const param: Teg.Inst.DataNode = s.code.instData(.param, in); + const param: Teg.DataNode = s.code.instData(.param, in); return s.addInstRef(.arg, .{ .type = s.params[param.data], .arg_number = param.data, @@ -430,7 +468,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { .gt, .eq, => |tag| { - const bin: Teg.Inst.Bin = s.code.instPayload(tag, in); + const bin: Teg.Bin = s.code.instPayload(tag, in); const op = @field(BinaryOp, @tagName(tag)); const lhs = try s.resolveRef(bin.lhs); @@ -458,7 +496,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { }); }, .bool_not => { - const un: Teg.Inst.RefNode = s.code.instData(.bool_not, in); + const un: Teg.RefNode = s.code.instData(.bool_not, in); const operand = try s.resolveCoerceRef(in.op(.operand), un.operand, .bool); @@ -474,7 +512,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { return s.addInstRef(.bool_not, .{ .operand = operand }); }, .negate => { - const un: Teg.Inst.RefNode = s.code.instData(.negate, in); + const un: Teg.RefNode = s.code.instData(.negate, in); const operand = try s.resolveRef(un.operand); @@ -496,7 +534,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { return s.addInstRef(.negate, .{ .operand = operand }); }, .@"break" => { - const brk: Teg.Inst.Break = s.code.instPayload(.@"break", in); + const brk: Teg.Break = s.code.instPayload(.@"break", in); const operand = try s.resolveRef(brk.operand); const block_ref = s.inst_map.get(brk.block).?.decode().index; // block ref is resolved by teg @@ -511,7 +549,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { }); }, .call => { - const call: Teg.Inst.Call = s.code.instPayload(.call, in); + const call: Teg.Call = s.code.instPayload(.call, in); const base = try s.resolveRef(call.base); const base_type = s.typeOfRef(base); @@ -568,7 +606,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { }); }, .js_new => { - const call: Teg.Inst.JsNew = s.code.instPayload(.js_new, in); + const call: Teg.JsNew = s.code.instPayload(.js_new, in); const base = try s.resolveRef(call.base); 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 { return InternPool.Value.void.toAir(); }, .@"if" => { - const data: Teg.Inst.If = s.code.instData(.@"if", in).extra_index.get(&s.code); + const data: Teg.If = s.code.instData(.@"if", in).extra_index.get(&s.code); const cond_raw = try s.resolveRef(data.cond); 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 { }); }, .store => { - const data: Teg.Inst.Store = s.code.instPayload(.store, in); + const data: Teg.Store = s.code.instPayload(.store, in); assert(s.code.instTag(data.memory) == .memory); // corrupt store instruction 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 { .operand = operand_coerced, }); }, - .as => { - const as: Teg.Inst.As = s.code.instPayload(.as, in); + .coercion => { + const as: Teg.Coercion = s.code.instPayload(.coercion, in); const coerce_to = try s.resolveCoerceValue(in.op(.lhs), as.type, .type); const operand = try s.resolveRef(as.operand); return try s.coerceRef(in.op(.rhs), operand, coerce_to.toType(s.ip).?); }, .@"comptime" => { - const node: Teg.Inst.RefNode = s.code.instData(.@"comptime", in); + const node: Teg.RefNode = s.code.instData(.@"comptime", in); const value = try s.resolveValue(node.operand); return value.toAir(); }, .type_of => { - const node: Teg.Inst.RefNode = s.code.instData(.type_of, in); + const node: Teg.RefNode = s.code.instData(.type_of, in); const value = try s.resolveValue(node.operand); return value.typeOf(s.ip).value().toAir(); }, .compile_error => { - const node: Teg.Inst.RefNode = s.code.instData(.compile_error, in); + const node: Teg.RefNode = s.code.instData(.compile_error, in); const value = try s.resolveCoerceValue( in.op(operandArgument(0)), node.operand, @@ -707,7 +745,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { }); }, .struct_decl => { - const node: Teg.Inst.StructDecl = s.code.instPayload(.struct_decl, in); + const node: Teg.StructDecl = s.code.instPayload(.struct_decl, in); const scratch = try s.scratch.reserveMulti( s.arena, @@ -732,7 +770,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { } }); }, .struct_init => { - const node: Teg.Inst.StructInit = s.code.instPayload(.struct_init, in); + const node: Teg.StructInit = s.code.instPayload(.struct_init, in); // Ensure we have a valid struct to operate on 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 { }); } }, - .array_init => { - const node: Teg.Inst.ArrayInit = s.code.instPayload(.array_init, in); + const node: Teg.ArrayInit = s.code.instPayload(.array_init, in); assert(node.list.len > 0); @@ -894,13 +931,72 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { }); } }, - .array_type => { - const un: Teg.Inst.RefNode = s.code.instData(.array_type, in); + const un: Teg.RefNode = s.code.instData(.array_type, in); const operand = try s.resolveType(in.op(.operand), un.operand); return s.makeValueAir(.{ .array_type = .{ .item_type = operand } }); }, + .result_type_argument => { + const result: Teg.ResultTypeArgument = s.code.instData(.result_type_argument, in); + + const operand = try s.resolveRef(result.call_base); + const ty = s.typeOfRef(operand); + + const func: InternPool.Key.FunctionType = ty.toFunctionPrototype(s.ip) orelse + return Value.opaque_type.toAir(); + + if (result.argument >= func.param_types.len) + return Value.opaque_type.toAir(); + + return func.param_types[result.argument].value().toAir(); + }, + + .result_type_array_value => { + const result: Teg.ResultTypeArrayValue = s.code.instData(.result_type_array_value, in); + + const operand = try s.resolveValue(result.type); + const ty = operand.toType(s.ip) orelse + return Value.opaque_type; + + const array_type = ty.toArrayType(s.ip) orelse + return Value.opaque_type.toAir(); + + return array_type.item_type.value().toAir(); + }, + + .result_type_tuple_field => { + const result: Teg.ResultTypeTupleField = s.code.instData(.result_type_array_value, in); + + const operand = try s.resolveValue(result.type); + const ty = operand.toType(s.ip) orelse + return Value.opaque_type; + + _ = ty; + + @panic("TODO: tuple result type resolution"); // tuples are not implemented + }, + + .result_type_struct_field => { + const result: Teg.ResultTypeStructField = s.code.instData(.result_type_struct_field, in); + + const operand = try s.resolveValue(result.type); + const ty = operand.toType(s.ip) orelse + return Value.opaque_type; + + const struct_type = ty.toStructType(s.ip) orelse + return Value.opaque_type.toAir(); + + const to_match = result.field.slice(s.code.string_bytes); + for (struct_type.field_keys, struct_type.field_types) |k, v| { + if (k.eqlSlice(to_match, s.ip)) { + return v.value().toAir(); + } + } + + return Value.opaque_type.toAir(); + }, + else => |tag| std.debug.panic("TODO: analyze of .{s}", .{@tagName(tag)}), } } @@ -1001,13 +1097,13 @@ fn resolveValue(s: *Sema, ref: Teg.Inst.Ref) !Value { const break_inst = indexes[1]; if (s.code.instTag(break_inst) != .@"break") break :special_cased; - const break_data: Teg.Inst.Break = s.code.instPayload(.@"break", break_inst); + const break_data: Teg.Break = s.code.instPayload(.@"break", break_inst); if (break_data.operand != load_inst.toRef()) break :special_cased; if (break_data.block != index) break :special_cased; if (s.code.instTag(load_inst) != .load_decl) break :special_cased; - const request: Teg.Inst.StrNode = s.code.instData(.load_decl, load_inst); + const request: Teg.StrNode = s.code.instData(.load_decl, load_inst); const decl_name = try s.ip.getOrPutString( s.gpa, request.string.slice(s.code.string_bytes), @@ -1620,6 +1716,7 @@ const DynamicBitSetUnmanaged = std.DynamicBitSetUnmanaged; const core = @import("mod.zig"); const assertFailed = core.assertFailed; +const StoredTrace = core.StoredTrace; const Comp = core.Comp; const File = Comp.File; diff --git a/src/core/StoredTrace.zig b/src/core/StoredTrace.zig new file mode 100644 index 0000000000000000000000000000000000000000..c8ca441a35b2a087ba9ebae134cfec15c49c96bc --- /dev/null +++ b/src/core/StoredTrace.zig @@ -0,0 +1,65 @@ +//! A variant of std.builtin.StackTrace that stores its data within itself +//! instead of being a pointer. This allows capturing the stack trace and +//! saving it for later printing. + +data: [31]usize, +index: usize, + +pub const empty = .{ + .data = .{0} ** 31, + .index = 0, +}; + +pub fn trace(stored: *StoredTrace) std.builtin.StackTrace { + return .{ + .index = stored.index, + .instruction_addresses = &stored.data, + }; +} + +pub fn capture(begin: ?usize) StoredTrace { + var stored: StoredTrace = undefined; + var frame = stored.trace(); + std.debug.captureStackTrace(begin orelse @returnAddress(), &frame); + stored.index = frame.index; + return stored; +} + +pub fn from(stack_trace: ?*std.builtin.StackTrace) StoredTrace { + if (stack_trace) |stack| { + var data: [31]usize = undefined; + @memset(&data, 0); + const items = @min(stack.instruction_addresses.len, 32); + @memcpy(data[0..items], stack.instruction_addresses[0..items]); + return .{ + .data = data, + .index = @min(items, stack.index), + }; + } else { + return empty; + } +} + +pub const BlankImpl = struct { + pub fn trace(_: *BlankImpl) std.builtin.StackTrace { + @compileError("trace is not available with StoredTrace.BlankImpl"); + } + + pub fn capture(_: ?usize) @This() { + return .{}; + } + + pub fn from(_: ?*std.builtin.StackTrace) @This() { + return .{}; + } +}; + +pub const DebugStoredTrace = if (builtin.mode == .Debug) + StoredTrace +else + BlankImpl; + +pub const StoredTrace = @This(); + +const std = @import("std"); +const builtin = @import("builtin"); diff --git a/src/core/Teg.Dump.zig b/src/core/Teg.Dump.zig index ebb02f0270df35a178c69589d84eba5f02d5a6f2..91c3f7556fedf350c5ef076b22e53674bf8f1c95 100644 --- a/src/core/Teg.Dump.zig +++ b/src/core/Teg.Dump.zig @@ -68,9 +68,11 @@ const State = struct { try writer.writeAll("params: "); try s.dumpRefList(writer, fn_data.params.slice(teg), '[', ']'); try s.newline(writer); - try writer.writeAll("return_type: "); - try s.dumpRef(writer, fn_data.return_type); - try s.newline(writer); + if (fn_data.return_type.unwrap()) |return_type| { + try writer.writeAll("return_type: "); + try s.dumpRef(writer, return_type); + try s.newline(writer); + } }, .immutable => {}, } @@ -129,8 +131,8 @@ const State = struct { try s.dumpRefMarker(writer, data.rhs); try writer.writeAll(")"); }, - .as => { - const data = teg.instData(.as, inst).extra_index.get(teg); + .coercion => { + const data = teg.instData(.coercion, inst).extra_index.get(teg); try writer.writeAll("("); try s.dumpRefMarker(writer, data.type); try writer.writeAll(", "); @@ -138,7 +140,7 @@ const State = struct { try writer.writeAll(")"); }, .@"break" => { - const data: Teg.Inst.Break = teg.instData(.@"break", inst).extra_index.get(teg); + const data: Teg.Break = teg.instData(.@"break", inst).extra_index.get(teg); try writer.writeAll("("); try s.dumpRefMarker(writer, data.block.toRef()); try writer.writeAll(", "); @@ -146,7 +148,7 @@ const State = struct { try writer.writeAll(")"); }, .store => { - const data: Teg.Inst.Store = teg.instData(.store, inst).extra_index.get(teg); + const data: Teg.Store = teg.instData(.store, inst).extra_index.get(teg); try writer.writeAll("("); try s.dumpRefMarker(writer, data.memory.toRef()); try writer.writeAll(", "); @@ -164,7 +166,6 @@ const State = struct { inline .memory, .discard, .ensure_void, - .ensure_bool, .negate, .bool_not, .bit_not, @@ -181,7 +182,7 @@ const State = struct { try writer.writeAll(")"); }, .call => { - const call: Teg.Inst.Call = teg.instData(.call, inst).extra_index.get(teg); + const call: Teg.Call = teg.instData(.call, inst).extra_index.get(teg); try writer.writeAll("("); try s.dumpRefMarker(writer, call.base); try writer.writeAll(","); @@ -189,7 +190,7 @@ const State = struct { try writer.writeAll(")"); }, .js_new => { - const call: Teg.Inst.JsNew = teg.instData(.js_new, inst).extra_index.get(teg); + const call: Teg.JsNew = teg.instData(.js_new, inst).extra_index.get(teg); try writer.writeAll("("); try s.dumpRefMarker(writer, call.base); try writer.writeAll(","); @@ -197,14 +198,14 @@ const State = struct { try writer.writeAll(")"); }, .load_decl => { - const call: Teg.Inst.StrNode = teg.instData(.load_decl, inst); + const call: Teg.StrNode = teg.instData(.load_decl, inst); try tty.setColor(writer, .red); try writer.print(" {s}", .{call.string.slice(teg.string_bytes)}); try tty.setColor(writer, .reset); }, .@"if" => { - const data: Teg.Inst.If = teg.instPayload(.@"if", inst); + const data: Teg.If = teg.instPayload(.@"if", inst); try writer.writeAll("("); try s.dumpRefMarker(writer, data.cond); try writer.writeAll(") {"); @@ -220,13 +221,13 @@ const State = struct { try writer.writeAll("}"); }, .@"comptime" => { - const data: Teg.Inst.RefNode = teg.instData(.@"comptime", inst); + const data: Teg.RefNode = teg.instData(.@"comptime", inst); try writer.writeAll("("); try s.dumpRef(writer, data.operand); try writer.writeAll(")"); }, .struct_decl => { - const data: Teg.Inst.StructDecl = teg.instPayload(.struct_decl, inst); + const data: Teg.StructDecl = teg.instPayload(.struct_decl, inst); try writer.writeAll(" {"); s.indent += 1; try s.newline(writer); @@ -241,7 +242,7 @@ const State = struct { try writer.writeAll("}"); }, .struct_init => { - const data: Teg.Inst.StructInit = teg.instPayload(.struct_init, inst); + const data: Teg.StructInit = teg.instPayload(.struct_init, inst); try writer.writeAll("("); try s.dumpRefMarker(writer, data.base_type); try writer.writeAll(") {"); @@ -258,7 +259,7 @@ const State = struct { try writer.writeAll("}"); }, .array_init => { - const data: Teg.Inst.ArrayInit = teg.instPayload(.array_init, inst); + const data: Teg.ArrayInit = teg.instPayload(.array_init, inst); if (data.type_hint.unwrap()) |type_hint| { try writer.writeAll("("); @@ -284,6 +285,37 @@ const State = struct { if (data.type_hint != .none) try writer.writeAll(")"); }, + .result_type_argument => { + const data: Teg.ResultTypeArgument = teg.instData(.result_type_argument, inst); + + try writer.writeAll("("); + try s.dumpRefMarker(writer, data.call_base); + try writer.print(", {d}", .{data.argument}); + try writer.writeAll(")"); + }, + .result_type_struct_field => { + const data: Teg.ResultTypeStructField = teg.instData(.result_type_struct_field, inst); + + try writer.writeAll("("); + try s.dumpRefMarker(writer, data.type); + try writer.print(", {s}", .{data.field.slice(s.teg.string_bytes)}); + try writer.writeAll(")"); + }, + .result_type_tuple_field => { + const data: Teg.ResultTypeTupleField = teg.instData(.result_type_tuple_field, inst); + + try writer.writeAll("("); + try s.dumpRefMarker(writer, data.type); + try writer.print(", {d}", .{data.item}); + try writer.writeAll(")"); + }, + .result_type_array_value => { + const data: Teg.ResultTypeArrayValue = teg.instData(.result_type_array_value, inst); + + try writer.writeAll("("); + try s.dumpRefMarker(writer, data.type); + try writer.writeAll(")"); + }, } } diff --git a/src/core/Teg.zig b/src/core/Teg.zig index d194ba986f8ba8a409e4c41bbd0640e40f4d8ca8..5916b860690a9213b6fac0fbdb8157e01d458ef4 100644 --- a/src/core/Teg.zig +++ b/src/core/Teg.zig @@ -6,184 +6,200 @@ extra: ExtraStore = .{}, report: Report, pub const ExtraStore = dod.ExtraStore(.teg, .@"const"); -pub const EncodedData = dod.EncodedData(Inst.Tag, u64); +pub const EncodedData = dod.EncodedData(Tag, u64); pub const ExtraIndex = ExtraStore.Index; pub const Dump = @import("Teg.Dump.zig"); +pub const Tag = enum { + /// Either the top level module or a `module` keyword. Contains a list of `.declarations` + /// Every analyzed module gets a `Comp.Namespace` + module, + /// Smallest unit of re-compilation. Every analyzed declaration gets a `Comp.Decl` + declaration, + /// Retrieves a declaration value via it's name. Since declarations are + /// analyzed in isolation of each other, all references to instructions + /// outside of the current declaration must be in the form of this. + load_decl, + /// Blocks are a list of instructions. The block will end with a `.break`, + /// which will assign the block a return value. + block, + /// Ends a block (current or any parent) with a value. + /// `return` expressions are lowered to this. + @"break", + /// Creates a memory cell in the current scope with a starting value. + memory, + /// Re-assign a memory cell with a new ref. + store, + /// Performs a type coercion, such as in variable declarations. + /// The resulting Air Ref will be guaranteed to be the coerced type. + coercion, + /// Load a parameter + param, + /// Asserts the operand is not void. Used for discard statements. + discard, + /// Asserts the operand is `void`. Used for undiscarded expressions. + ensure_void, + /// Numeric constant. + number, + /// String literal constant. + string, + /// Branch takes a boolean condition, and yes and no paths. + @"if", + + // The following instructions are used to implement `Transform.ResultType` + result_type_argument, + result_type_struct_field, + result_type_tuple_field, + result_type_array_value, + + struct_decl, + struct_init, + + array_type, + array_init, + + add, + sub, + mul, + div, + rem, + pow, + @"and", + @"or", + bit_and, + bit_or, + bit_xor, + shl, + shr, + eq, + ne, + lt, + le, + gt, + ge, + + negate, + bool_not, + bit_not, + field, + index, + call, + @"comptime", + + /// Implements the `@js` builtin + js, + /// Implements the `@jsNew` builtin + js_new, + /// Implements the `@import` builtin + import, + /// Implements the `@fetch` builtin + fetch, + /// Implements the `@compileError` builtin + compile_error, + /// Implements the `@typeOf` builtin + type_of, + + pub fn Data(comptime tag: Tag) type { + return switch (tag) { + .module => PlNode(IndexList), + .declaration => PlNode(Decl), + .block => PlNode(IndexList), + .coercion => PlNode(Coercion), + .@"break" => PlNode(Break), + .number => PlNode(Float), + .string => PlNode(SlicedString), + .call => PlNode(Call), + .@"if" => PlNode(If), + .load_decl => StrNode, + .param => DataNode, + .store => PlNode(Store), + .js_new => PlNode(JsNew), + .struct_decl => PlNode(StructDecl), + .struct_init => PlNode(StructInit), + .array_init => PlNode(ArrayInit), + + .result_type_argument => ResultTypeArgument, + .result_type_struct_field => ResultTypeStructField, + .result_type_tuple_field => ResultTypeTupleField, + .result_type_array_value => ResultTypeArrayValue, + + // Single instruction operand + .memory, + .discard, + .ensure_void, + .negate, + .bool_not, + .bit_not, + .js, + .import, + .fetch, + .compile_error, + .@"comptime", + .type_of, + .array_type, + => RefNode, + + // Two instruction operands + .field, + .index, + .add, + .sub, + .mul, + .div, + .rem, + .pow, + .@"and", + .@"or", + .bit_and, + .bit_or, + .bit_xor, + .shl, + .shr, + .eq, + .ne, + .lt, + .le, + .gt, + .ge, + => PlNode(Bin), + }; + } + + pub fn airTag(comptime tag: Tag) Air.Inst.Tag { + return @field(Air.Inst.Tag, @tagName(tag)); + } +}; + +pub fn PlNode(comptime Payload: type) type { + return struct { + pub const T = Payload; + + src_node: NodeOffset, + extra_index: ExtraIndex(Payload), + }; +} + +pub const RefNode = struct { + src_node: NodeOffset, + operand: Inst.Ref, +}; + +pub const StrNode = struct { + src_node: NodeOffset, + string: NullTerminatedString, +}; + +pub const DataNode = struct { + src_node: NodeOffset, + data: u32, +}; + pub const Inst = struct { pub const List = std.MultiArrayList(Inst); tag: Tag, data: EncodedData, - // TODO: fully document every thing - pub const Tag = enum { - module, - declaration, - - block, - /// Creates a memory cell in the current scope. - memory, - /// Performs a type coercion, such as in variable declarations. - /// Emits a sema error if the type conversion fails. - /// `let x: i32 = 1` - as, - load_decl, - /// Load a parameter - param, - store, - discard, - ensure_void, - ensure_bool, - /// lowered from `break` or `return` - /// ends a block with a resulting value - @"break", - number, - string, - - @"if", - - struct_decl, - struct_init, - - array_type, - array_init, - - add, - sub, - mul, - div, - rem, - pow, - @"and", - @"or", - bit_and, - bit_or, - bit_xor, - shl, - shr, - eq, - ne, - lt, - le, - gt, - ge, - - negate, - bool_not, - bit_not, - field, - index, - call, - @"comptime", - - /// Implements the `@js` builtin - js, - /// Implements the `@jsNew` builtin - js_new, - /// Implements the `@import` builtin - import, - /// Implements the `@fetch` builtin - fetch, - /// Implements the `@compileError` builtin - compile_error, - /// Implements the `@typeOf` builtin - type_of, - - pub fn Data(comptime tag: Tag) type { - return switch (tag) { - .module => PlNode(IndexList), - .declaration => PlNode(Decl), - .block => PlNode(IndexList), - .as => PlNode(As), - .@"break" => PlNode(Break), - .number => PlNode(Float), - .string => PlNode(SlicedString), - .call => PlNode(Call), - .@"if" => PlNode(If), - .load_decl => StrNode, - .param => DataNode, - .store => PlNode(Store), - .js_new => PlNode(JsNew), - .struct_decl => PlNode(StructDecl), - .struct_init => PlNode(StructInit), - .array_init => PlNode(ArrayInit), - - // Single instruction operand - .memory, - .discard, - .ensure_void, - .ensure_bool, - .negate, - .bool_not, - .bit_not, - .js, - .import, - .fetch, - .compile_error, - .@"comptime", - .type_of, - .array_type, - => RefNode, - - // Two instruction operands - .field, - .index, - .add, - .sub, - .mul, - .div, - .rem, - .pow, - .@"and", - .@"or", - .bit_and, - .bit_or, - .bit_xor, - .shl, - .shr, - .eq, - .ne, - .lt, - .le, - .gt, - .ge, - => PlNode(Bin), - }; - } - - pub fn airTag(comptime tag: Tag) Air.Inst.Tag { - return @field(Air.Inst.Tag, @tagName(tag)); - } - }; - - // TODO: make this an enum - const NodeOffset = i32; - - pub fn PlNode(comptime Payload: type) type { - return struct { - pub const T = Payload; - - src_node: NodeOffset, - extra_index: ExtraIndex(Payload), - }; - } - - pub const RefNode = struct { - src_node: NodeOffset, - operand: Inst.Ref, - }; - - pub const StrNode = struct { - src_node: NodeOffset, - string: NullTerminatedString, - }; - - pub const DataNode = struct { - src_node: NodeOffset, - data: u32, - }; - /// The position of a Teg instruction within the `Teg` instructions array. pub const Index = enum(u32) { /// Teg is structured so that the outermost "main" struct of any file @@ -197,36 +213,28 @@ pub const Inst = struct { return @enumFromInt(static_len + @intFromEnum(i)); } - pub fn toOptional(i: Index) OptionalIndex { + pub fn toOptional(i: Index) Optional { return @enumFromInt(@intFromEnum(i)); } pub fn op(i: Index, operand: Report.Operand) InstOp { return .{ .index = i, .op = operand }; } - }; - pub const OptionalIndex = enum(u32) { - /// Teg is structured so that the outermost "main" module of any file - /// is always at index 0. - main_module_inst = 0, - ref_start_index = Index.static_len, - none = std.math.maxInt(u32), - _, + pub const Optional = enum(u32) { + none = std.math.maxInt(u32), + _, - pub fn unwrap(oi: OptionalIndex) ?Index { - return if (oi == .none) null else @enumFromInt(@intFromEnum(oi)); - } + pub fn unwrap(oi: Optional) ?Index { + return if (oi == .none) null else @enumFromInt(@intFromEnum(oi)); + } + }; }; - /// A reference to an instruction, or to an InternPool index, or to neither. + /// A reference to an instruction, or to a static InternPool value. /// /// If the integer tag value is < InternPool.static_len, then it - /// corresponds to an InternPool index. Otherwise, this refers to a ZIR - /// instruction. - /// - /// The tag type is specified so that it is safe to bitcast between `[]u32` - /// and `[]Ref`. + /// corresponds to a Value. Otherwise, it is an instruction index. pub const Ref = enum(u32) { opaque_type, bool_type, @@ -282,6 +290,10 @@ pub const Inst = struct { return @enumFromInt(ref_int); } + pub fn toOptional(ref: Ref) Optional { + return @enumFromInt(@intFromEnum(ref)); + } + pub const Optional = enum(u32) { none = std.math.maxInt(u32), _, @@ -291,113 +303,143 @@ pub const Inst = struct { } }; }; +}; - pub const Bin = struct { - lhs: Inst.Ref, - rhs: Inst.Ref, - }; +pub const Bin = struct { + lhs: Inst.Ref, + rhs: Inst.Ref, +}; - pub const Store = struct { - memory: Inst.Index, - operand: Inst.Ref, - }; +pub const Store = struct { + memory: Inst.Index, + operand: Inst.Ref, +}; - pub const As = struct { - type: Inst.Ref, - operand: Inst.Ref, - }; +pub const Coercion = struct { + type: Inst.Ref, + operand: Inst.Ref, +}; - pub const Break = struct { - block: Inst.Index, - operand: Inst.Ref, - }; +pub const Break = struct { + block: Inst.Index, + operand: Inst.Ref, +}; - pub const Call = struct { - base: Inst.Ref, - args: RefList, - }; +pub const Call = struct { + base: Inst.Ref, + args: RefList, +}; - pub const If = struct { - cond: Inst.Ref, - yes: Inst.Ref, - no: Inst.Ref, - }; +pub const If = struct { + cond: Inst.Ref, + yes: Inst.Ref, + no: Inst.Ref, +}; - pub const JsNew = struct { - base: Inst.Ref, - args: RefList, - }; +pub const JsNew = struct { + base: Inst.Ref, + args: RefList, +}; - /// Index into extra which contains `field_len * 2` u32s. The first half - /// are the keys (NullTerminatedString) and the second half are field types - /// (Ref). This basically is like a specialized MultiArrayList. - pub const StructDecl = struct { - payload_index: u32, - field_len: u32, - - pub fn keys(decl: StructDecl, teg: *const Teg) []const NullTerminatedString { - return @ptrCast( - teg.extra.list.items[decl.payload_index..][0..decl.field_len], - ); - } - - pub fn types(decl: StructDecl, teg: *const Teg) []const Ref { - return @ptrCast( - teg.extra.list.items[decl.payload_index + decl.field_len ..][0..decl.field_len], - ); - } - }; +/// Index into extra which contains `field_len * 2` u32s. The first half +/// are the keys (NullTerminatedString) and the second half are field types +/// (Ref). This basically is like a specialized MultiArrayList. +pub const StructDecl = struct { + payload_index: u32, + field_len: u32, + + pub fn keys(decl: StructDecl, teg: *const Teg) []const NullTerminatedString { + return @ptrCast( + teg.extra.list.items[decl.payload_index..][0..decl.field_len], + ); + } + + pub fn types(decl: StructDecl, teg: *const Teg) []const Inst.Ref { + return @ptrCast( + teg.extra.list.items[decl.payload_index + decl.field_len ..][0..decl.field_len], + ); + } +}; - /// This memory is laid out with the same strategy described in StructDecl - pub const StructInit = struct { - base_type: Ref, - payload_index: u32, - field_len: u32, - - pub fn keys(decl: StructInit, teg: *const Teg) []const NullTerminatedString { - return @ptrCast( - teg.extra.list.items[decl.payload_index..][0..decl.field_len], - ); - } - - pub fn inits(decl: StructInit, teg: *const Teg) []const Ref { - return @ptrCast( - teg.extra.list.items[decl.payload_index + decl.field_len ..][0..decl.field_len], - ); - } - }; +/// This memory is laid out with the same strategy described in StructDecl +pub const StructInit = struct { + base_type: Inst.Ref, + payload_index: u32, + field_len: u32, + + pub fn keys(decl: StructInit, teg: *const Teg) []const NullTerminatedString { + return @ptrCast( + teg.extra.list.items[decl.payload_index..][0..decl.field_len], + ); + } + + pub fn inits(decl: StructInit, teg: *const Teg) []const Inst.Ref { + return @ptrCast( + teg.extra.list.items[decl.payload_index + decl.field_len ..][0..decl.field_len], + ); + } +}; - pub const ArrayInit = struct { - /// Optionally provide a result type for more reliable type coercion. - /// If specified, this must be an array type. Otherwise, the first - /// element's type is used. - type_hint: Ref.Optional, - /// The items in the array - list: RefList, - }; +pub const ArrayInit = struct { + /// Optionally provide a result type for more reliable type coercion. + /// If specified, this must be an array type. Otherwise, the first + /// element's type is used. + type_hint: Inst.Ref.Optional, + /// The items in the array + list: RefList, +}; - /// This will contain trailing data depending on the .kind - pub const Decl = struct { - name: NullTerminatedString, - hash: Cache.BinDigest, - kind: Kind, - instruction_count: u32, - body: Ref, - - pub const Kind = enum { - // TODO: remove function, see #45 - function, - immutable, - }; +/// This will contain trailing data depending on the .kind +pub const Decl = struct { + name: NullTerminatedString, + hash: Cache.BinDigest, + kind: Kind, + instruction_count: u32, + body: Inst.Ref, + + pub const Kind = enum { + // TODO: remove function, see #45 + function, + immutable, }; +}; - /// Trailing data for function declarations - pub const DeclFn = struct { - params: RefList, - return_type: Ref, - }; +/// Trailing data for function declarations +pub const DeclFn = struct { + params: RefList, + return_type: Inst.Ref.Optional, +}; + +pub const ResultTypeArgument = struct { + /// The VALUE that is going to be called. + call_base: Inst.Ref, + /// Which argument + argument: u32, +}; + +pub const ResultTypeStructField = struct { + /// The type of the struct itself, + type: Inst.Ref, + /// Which field + field: NullTerminatedString, +}; + +pub const ResultTypeTupleField = struct { + /// The type of the tuple itself, + type: Inst.Ref, + /// Which item + item: u32, +}; + +pub const ResultTypeArrayValue = struct { + /// The type of the array itself, + type: Inst.Ref, + unused: u32 = 0, }; +// TODO: make this an enum +const NodeOffset = i32; + pub const InstOp = struct { index: Inst.Index, op: Report.Operand, @@ -470,7 +512,7 @@ pub fn deinit(teg: *Teg, gpa: std.mem.Allocator) void { teg.* = undefined; } -pub fn instTag(teg: *const Teg, index: Inst.Index) Inst.Tag { +pub fn instTag(teg: *const Teg, index: Inst.Index) Teg.Tag { return teg.insts.items(.tag)[@intFromEnum(index)]; } @@ -478,17 +520,25 @@ pub fn instEncodedData(teg: *const Teg, index: Inst.Index) EncodedData { return teg.insts.items(.data)[@intFromEnum(index)]; } -pub fn instData(teg: *const Teg, comptime tag: Inst.Tag, index: Inst.Index) tag.Data() { +pub fn instData(teg: *const Teg, comptime tag: Teg.Tag, index: Inst.Index) tag.Data() { return teg.instEncodedData(index).decode(tag, teg.instTag(index)); } -pub fn instPayload(teg: *const Teg, comptime tag: Inst.Tag, index: Inst.Index) tag.Data().T { +pub fn instPayload(teg: *const Teg, comptime tag: Teg.Tag, index: Inst.Index) tag.Data().T { return teg.instData(tag, index).extra_index.get(teg); } pub fn instNodeOffset(teg: *const Teg, index: Inst.Index) i32 { return switch (teg.instTag(index)) { - .declaration => 0, + // these nodes never get errors, but Teg.Dump will print these regardless + // TODO: + .declaration, + .result_type_argument, + .result_type_struct_field, + .result_type_tuple_field, + .result_type_array_value, + => 0, + inline else => |tag| teg.instData(tag, index).src_node, }; } @@ -497,10 +547,10 @@ pub fn blockInstructions(teg: *const Teg, index: Inst.Index) []const Inst.Index return teg.instData(.block, index).extra_index.get(teg).slice(teg); } -pub fn fullFuncDecl(teg: *const Teg, index: Inst.Index) struct { Inst.Decl, Inst.DeclFn } { +pub fn fullFuncDecl(teg: *const Teg, index: Inst.Index) struct { Teg.Decl, Teg.DeclFn } { const decl, var trail = teg.instData(.declaration, index).extra_index.getTrail(teg); assert(decl.kind == .function); - const decl_fn = trail.read(&teg.extra, Inst.DeclFn); + const decl_fn = trail.read(&teg.extra, Teg.DeclFn); return .{ decl, decl_fn }; } @@ -512,7 +562,7 @@ pub const FullDecl = struct { body: Inst.Ref, const Data = union(enum) { - function: Inst.DeclFn, + function: Teg.DeclFn, immutable: struct {}, }; }; @@ -537,7 +587,7 @@ pub fn fullDecl(teg: *const Teg, index: Inst.Index) FullDecl { }; } -pub fn declData(teg: *const Teg, index: Inst.Index) Inst.Decl { +pub fn declData(teg: *const Teg, index: Inst.Index) Teg.Decl { return teg.instData(.declaration, index).extra_index.get(teg); } @@ -565,7 +615,7 @@ pub fn rootFunction(teg: *const Teg) Inst.Index { pub fn declName(teg: *const Teg, decl_index: Inst.Index) []const u8 { const decl = teg.instData(.declaration, decl_index); - const decl_data: Inst.Decl = decl.extra_index.get(teg); + const decl_data: Teg.Decl = decl.extra_index.get(teg); return decl_data.name.slice(teg.string_bytes); } diff --git a/src/core/Transform.zig b/src/core/Transform.zig index a1dcf9f122e3610bdef97ba6603103f2a586eea0..1a24a2bd5da49c04ce1fa1798a7fe75d13be8588 100644 --- a/src/core/Transform.zig +++ b/src/core/Transform.zig @@ -1,26 +1,35 @@ //! Transform pass converts an Ast into a high-level/untyped instruction format. //! See Teg.zig for more details about the IR. Transforming handles: //! +//! - Flattening nested expressions into instruction sequences. //! - Resolving all local variables into reference or declaration names. -//! - Resolving breaks and returns to their block id. +//! - Resolving breaks and returns to their generated block id. //! - Annotating expression statements with `.discard` and `.ensure_void` -//! - Flattening nested expressions into instruction sequences. +//! - Resolving implicit expression with their corresponding result type //! //! Some trivial diagnostics such as unused variables, unknown result types, //! invalid assignment targets, and other style issues are also reported here. + gpa: Allocator, +arena: Allocator, + ast: Ast, + +// these values are allocated through `gpa`, so that resulting IR can be +// trivially shrunk before returning. + insts: Inst.List = .{}, -string_bytes: std.ArrayListUnmanaged(u8) = .{}, extra: ExtraStore = .{}, +string_bytes: std.ArrayListUnmanaged(u8) = .{}, +report: Report.Builder, -scratch: std.ArrayListUnmanaged(u32) = .{}, +// however, these are allocated through `arena`, since they are always freed at +// the end of the pass. + +scratch: core.U32Stack = .{}, id_decode_buf: std.ArrayList(u8), - scopes: std.ArrayListUnmanaged(Scope) = .{}, -report: Report.Builder, - pub const Error = error{ /// A `Report` will contain at least one error. TransformFail, @@ -40,6 +49,8 @@ pub fn transform( var t: Transform = .{ .ast = ast, .gpa = gpa, + // TODO: Currently this is illusion of free will + .arena = gpa, .report = .{ .gpa = gpa, .current_file = file }, .id_decode_buf = std.ArrayList(u8).initCapacity(sfa.get(), 4096) catch unreachable, }; @@ -57,9 +68,7 @@ pub fn transform( return error.TransformFail; } - assert(@intFromEnum(module) == 0); - - try t.insts.setCapacity(t.gpa, t.insts.len); + assert(module == .main_module_inst); return .{ .insts = t.insts.toOwnedSlice(), @@ -69,27 +78,166 @@ pub fn transform( }; } -pub fn deinit(t: *Transform) void { - t.scratch.deinit(t.gpa); +fn deinit(t: *Transform) void { assert(t.scopes.items.len == 0); - t.scopes.deinit(t.gpa); + t.id_decode_buf.deinit(); + t.scratch.list.deinit(t.arena); + t.scopes.deinit(t.arena); + t.id_decode_buf.deinit(); t.string_bytes.deinit(t.gpa); t.insts.deinit(t.gpa); t.extra.deinit(t.gpa); - t.id_decode_buf.deinit(); t.* = undefined; } +/// Represents how the parent node is going to implicitly cast this expression. +/// +/// Consider an expression like `let x: S = .{ y: 1 }`. The type from the +/// declaration must impact the visited struct initialization expression. This +/// behavior is recursive, so something more complex can properly infer: +/// +/// struct S { a: int } +/// struct Y {} +/// let x: [](S, []Y) = [ .{ .{ a: 1 }, [ .{} ] } ]; +/// +/// The innermost `.{}` must eventually analyze to `Y{}`, which can be unwrapped +/// from the type expression via specialized instructions for this purpose. +const ResultType = union(enum) { + /// There is no result type available + none, + /// The result type was consumed + used, + /// The expression will be cast to this ref. + ref: Inst.Ref, + /// The expression will be cast to the type of this ref + type_of: Inst.Ref, + /// The expression is an argument to a function. + argument: Teg.ResultTypeArgument, + /// The expression is a field in a struct initialization + struct_field: Teg.ResultTypeStructField, + /// The expression is a field in a tuple initialization + tuple_field: Teg.ResultTypeTupleField, + /// The expression is a field in an array initialization + array_value: Inst.Ref, + + /// This will return an instruction pointing to the the type + pub fn consume(result: *ResultType, t: *Transform) !?Inst.Ref { + if (result.* == .none) return null; + defer result.* = .used; + return switch (result.*) { + .none => unreachable, + .used => unreachable, // Use after free + .ref => |r| r, + .type_of => |r| try t.addRef(.type_of, .{ + // Result types don't have associated nodes, which is ok because + // type_of cannot fail in the context we are using it. Instead, + // we will use a predictable poison value, so that an attempt + // to print this will crash. + .src_node = std.math.maxInt(i32), + .operand = r, + }), + .argument => |r| try t.addRef(.result_type_argument, r), + .struct_field => |r| try t.addRef(.result_type_struct_field, r), + .tuple_field => |r| try t.addRef(.result_type_tuple_field, r), + .array_value => |r| try t.addRef(.result_type_array_value, .{ .type = r }), + }; + } +}; + +const BlockContext = struct { + decl_node_offset: i32, + return_target: ?*ReturnTarget = null, + break_target: ?*BreakTarget = null, + + const ReturnTarget = struct { + generated_block: ?Inst.Index = null, + result_type: ResultType, + }; + + /// This struct is always stored on the stack; the idea is to pass mutable + /// pointers to it. When `break :brk` is encountered, it will generate the + /// block instruction. This way, redundant blocks are never created. + const BreakTarget = struct { + label: ?[]const u8, + /// This allows `continue` as well as implicit break targeting. + is_loop: bool, + /// If `null`, this label was unused. + block_index: ?Inst.Index = null, + parent: ?*BreakTarget = null, + result_type: *ResultType, + + fn addChild(parent: *BreakTarget, label: ?[]const u8, is_loop: bool) BreakTarget { + return .{ + .parent = parent, + .label = label, + .is_loop = is_loop, + }; + } + + fn getInst(target: *BreakTarget, t: *Transform) !Inst.Index { + return target.block_index orelse { + const inst = try t.addUntracked(.block, undefined); + target.block_index = inst; + return inst; + }; + } + }; + + fn init(root_node: NodeIndex) BlockContext { + return .{ + .decl_node_offset = @intCast(root_node), + }; + } + + fn nodeOffset(self: BlockContext, node: NodeIndex) i32 { + return @as(i32, @intCast(node)) - self.decl_node_offset; + } + + fn declOffset(self: BlockContext, node: NodeIndex) BlockContext { + return .{ + .decl_node_offset = self.nodeOffset(node), + }; + } + + fn withReturn(self: BlockContext, return_target: ?*ReturnTarget) BlockContext { + return .{ + .decl_node_offset = self.decl_node_offset, + .return_target = return_target, + }; + } + + fn withBreak(self: BlockContext, break_target: *BreakTarget) BlockContext { + return .{ + .decl_node_offset = self.decl_node_offset, + .return_target = self.return_target, + .break_target = break_target, + }; + } + + fn lookupNamedBreak(self: BlockContext, label: []const u8) ?*BreakTarget { + var break_target = self.break_target; + while (break_target) |target| : (break_target = target.parent) { + if (target.label) |target_label| { + if (mem.eql(u8, label, target_label)) { + return target; + } + } + } + return null; + } +}; + const Scope = struct { symbols: std.StringArrayHashMapUnmanaged(Symbol) = .{}, + // TODO: optimize this structure's size. const Symbol = struct { node: NodeIndex, data: Symbol.Data, - // TODO: optimize this structure used: bool = false, mutated: bool = false, discarded: ?NodeIndex = null, + type: SymbolResultType, const Data = union(enum) { declaration, @@ -98,6 +246,21 @@ const Scope = struct { immutable: Inst.Ref, }; + const SymbolResultType = union(enum) { + /// Points to the resolved result type (.ref), as is. + present: Inst.Ref, + /// This points to the value. Which means a `type_of` instruction + /// must be inserted before it may be used. + missing: Inst.Ref, + + pub fn resolve(srt: SymbolResultType) ResultType { + return switch (srt) { + .present => |type_ref| .{ .ref = type_ref }, + .missing => |value_ref| .{ .type_of = value_ref }, + }; + } + }; + pub fn addWarnings(symbol: Symbol, t: *Transform) !void { if (!symbol.mutated and symbol.data == .mutable) { try t.report.warn(.not_mutated_var, .{ @@ -142,86 +305,6 @@ const Scope = struct { } }; -const BlockContext = struct { - node_base: i32, - return_target: ?*?Inst.Index = null, - break_target: ?*BreakTarget = null, - - /// This struct is always stored on the stack; the idea is to pass mutable - /// pointers to it. When `break :brk` is encountered, it will generate the - /// block instruction. This way, redundant blocks are never created. - const BreakTarget = struct { - label: ?[]const u8, - /// This allows `continue` as well as implicit break targeting. - is_loop: bool, - /// If `null`, this label was unused. - block_index: ?Inst.Index = null, - parent: ?*BreakTarget = null, - - fn addChild(parent: *BreakTarget, label: ?[]const u8, is_loop: bool) BreakTarget { - return .{ - .parent = parent, - .label = label, - .is_loop = is_loop, - }; - } - - fn getInst(target: *BreakTarget, t: *Transform) !Inst.Index { - return target.block_index orelse { - const inst = try t.addRootInst(.{ - .tag = .block, - .data = undefined, - }); - target.block_index = inst; - return inst; - }; - } - }; - - fn init(root_node: NodeIndex) BlockContext { - return .{ - .node_base = @intCast(root_node), - }; - } - - fn nodeOffset(self: BlockContext, node: NodeIndex) i32 { - return @as(i32, @intCast(node)) - self.node_base; - } - - fn declOffset(self: BlockContext, node: NodeIndex) BlockContext { - return .{ - .node_base = self.nodeOffset(node), - }; - } - - fn withReturn(self: BlockContext, return_target: ?*?Inst.Index) BlockContext { - return .{ - .node_base = self.node_base, - .return_target = return_target, - }; - } - - fn withBreak(self: BlockContext, break_target: *BreakTarget) BlockContext { - return .{ - .node_base = self.node_base, - .return_target = self.return_target, - .break_target = break_target, - }; - } - - fn findAndMarkBreak(self: BlockContext, t: *Transform, label: []const u8) !?Inst.Index { - var break_target = self.break_target; - while (break_target) |target| : (break_target = target.parent) { - if (target.label) |target_label| { - if (mem.eql(u8, label, target_label)) { - return try target.getInst(t); - } - } - } - return null; - } -}; - const Liveness = enum { alive, dead }; const BuiltInIds = std.StaticStringMap(Inst.Ref).initComptime(.{ @@ -242,10 +325,7 @@ fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.I const trace = tracer.trace(@src()); defer trace.end(); - const module = try t.addRootInst(.{ - .tag = .module, - .data = undefined, - }); + const module = try t.addUntracked(.module, undefined); const decl = BlockContext.init(module_node); @@ -271,10 +351,7 @@ fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.I } } - const top_level_fn = try t.addRootInst(.{ - .tag = .declaration, - .data = undefined, - }); + const top_level_fn = try t.addUntracked(.declaration, undefined); temp[temp.len - 1] = top_level_fn; @@ -285,42 +362,39 @@ fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.I try t.extra.list.appendSlice(t.gpa, @ptrCast(temp)); const top_level_code_ref: Inst.Ref = ref: { - const scratch_top = t.scratch.items.len; - defer t.scratch.shrinkRetainingCapacity(scratch_top); + const scratch = t.scratch.mark(); + defer scratch.deinit(); for (statements) |statement| { try t.transformModuleStatement(decl, statement); } // if there are no new instructions, we dont have to create a block context - if (scratch_top == t.scratch.items.len) { + if (scratch.len() == 0) { break :ref .void; } - const block = try t.addRootInst(.{ - .tag = .block, - .data = undefined, - }); + const block = try t.addUntracked(.block, undefined); t.getInstData(block).* = EncodedData.encode(.block, .{ .src_node = decl.nodeOffset(module_node), - .extra_index = try t.extra.append(t.gpa, IndexList, try t.scratchToIndexList(scratch_top)), + .extra_index = try t.extra.append(t.gpa, IndexList, try t.scratchToIndexList(scratch)), }); break :ref block.toRef(); }; - const decl_extra, var trail = try t.extra.appendTrailing(t.gpa, Inst.Decl, .{ + const decl_extra, var trail = try t.extra.appendTrailing(t.gpa, Teg.Decl, .{ .kind = .function, .name = NullTerminatedString.empty, .hash = .{0} ** 16, .instruction_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(top_level_fn), .body = top_level_code_ref, - }, dod.extraSizeOf(Inst.DeclFn)); + }, extra.extraSizeOf(Teg.DeclFn)); - trail.writeStruct(Inst.DeclFn, .{ + trail.writeStruct(Teg.DeclFn, .{ .params = .{ .start = 0, .len = 0 }, - .return_type = Inst.Ref.void, + .return_type = Inst.Ref.void.toOptional(), }); t.getInstData(top_level_fn).* = EncodedData.encode(.declaration, .{ @@ -361,10 +435,14 @@ fn previsitModuleStatement(t: *Transform, node: NodeIndex) Allocator.Error!void gop.value_ptr.* = .{ .node = node, .data = .declaration, + .type = .{ .present = .void }, }; }, .struct_decl => { const decl = t.ast.fullStructDecl(node); + var id, const id_allocated = try t.decodeIdentifierExtra(decl.id); + if (id_allocated) + id = try t.gpa.dupe(u8, id); const gop = try t.scope().symbols.getOrPut(t.gpa, decl.id); if (gop.found_existing) { try t.report.err(.duplicate_function, .{ @@ -377,6 +455,7 @@ fn previsitModuleStatement(t: *Transform, node: NodeIndex) Allocator.Error!void gop.value_ptr.* = .{ .node = node, .data = .declaration, + .type = .{ .present = .void }, }; }, else => {}, @@ -397,14 +476,14 @@ fn transformModuleStatement(t: *Transform, decl: BlockContext, node: NodeIndex) .struct_decl, => {}, // declarations are analyzed separately - else => _ = try t.transformBlockStatement(decl, node, false), + else => _ = try t.transformBlockStatement(decl, node, null), } } fn transformDecl(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index { return switch (t.ast.nodeTag(node)) { .fn_decl => t.transformFunction(outer_decl, node), - .struct_decl => t.transformStruct(outer_decl, node), + .struct_decl => t.transformStructDeclaration(outer_decl, node), else => unreachable, // not a declaration }; } @@ -416,33 +495,40 @@ fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) A const decl = outer_decl.declOffset(node); const fn_decl = t.ast.fullFnDecl(node); - const decl_inst = try t.addRootInst(.{ - .tag = .declaration, - .data = undefined, - }); + const decl_inst = try t.addUntracked(.declaration, undefined); try t.pushScope(); defer t.popScope(); const param_list = brk: { - const scratch_top = t.scratch.items.len; - defer t.scratch.shrinkRetainingCapacity(scratch_top); + const scratch = t.scratch.mark(); + defer scratch.deinit(); for (fn_decl.params) |param| { - try t.addScratch(Inst.Ref, try t.transformParamDecl(decl, param)); + try t.scratch.add(t.arena, Inst.Ref, try t.transformParamDecl(decl, param)); } - break :brk try t.scratchToRefList(scratch_top); + break :brk try t.scratchToRefList(scratch); }; const return_type = if (fn_decl.return_type) |return_type_node| - try t.transformExpressionToBlock(decl, return_type_node, false) + try t.transformToBlock( + decl, + return_type_node, + .result_without_coercion, + .{ .ref = .type_type }, + ) else - Inst.Ref.void; + null; - const body = try t.transformExpressionToBlock(decl, fn_decl.body, true); + const body = try t.transformToBlock( + decl, + fn_decl.body, + .function_body, + if (return_type) |ref| .{ .ref = ref } else .none, + ); - const decl_extra = try t.extra.append(t.gpa, Inst.Decl, .{ + const decl_extra = try t.extra.append(t.gpa, Teg.Decl, .{ .kind = .function, .name = try t.addNullTerminatedString(fn_decl.id), .hash = .{0} ** 16, @@ -450,9 +536,9 @@ fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) A .body = body, }); - _ = try t.extra.append(t.gpa, Inst.DeclFn, .{ + _ = try t.extra.append(t.gpa, Teg.DeclFn, .{ .params = param_list, - .return_type = return_type, + .return_type = if (return_type) |r| r.toOptional() else .none, }); t.getInstData(decl_inst).* = EncodedData.encode(.declaration, .{ @@ -465,21 +551,18 @@ fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) A return decl_inst; } -fn transformStruct(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index { +fn transformStructDeclaration(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index { const trace = tracer.trace(@src()); defer trace.end(); const decl = outer_decl.declOffset(node); const decl_data = t.ast.fullStructDecl(node); - const decl_inst = try t.addRootInst(.{ - .tag = .declaration, - .data = undefined, - }); + const decl_inst = try t.addUntracked(.declaration, undefined); - const body = try t.transformExpressionToBlock(decl, node, false); + const body = try t.transformToBlock(decl, node, .result_without_coercion, .none); - const decl_extra = try t.extra.append(t.gpa, Inst.Decl, .{ + const decl_extra = try t.extra.append(t.gpa, Teg.Decl, .{ .kind = .immutable, .name = try t.addNullTerminatedString(decl_data.id), .hash = .{0} ** 16, @@ -495,76 +578,83 @@ fn transformStruct(t: *Transform, outer_decl: BlockContext, node: NodeIndex) All return decl_inst; } -const TransformFunctionCtx = struct { - param_list: RefList, +const TransformToBlockMode = enum { + tracked_result, + result_without_coercion, + coerced_result, + function_body, + + fn ResultType(mode: TransformToBlockMode) type { + return switch (mode) { + .tracked_result => *Transform.ResultType, + else => Transform.ResultType, + }; + } }; -/// Converts expressions into blocks with a break. This is important -/// for places where a block is not allowed, such as a parameter list. -/// This is also used for the function body, even though it is a block. -/// Simple refs turn into just the ref -fn transformExpressionToBlock( +/// guh +fn transformToBlock( t: *Transform, - decl: BlockContext, + ctx: BlockContext, node: NodeIndex, - comptime is_function: bool, + comptime mode: TransformToBlockMode, + result_type: mode.ResultType(), ) Allocator.Error!Inst.Ref { const trace = tracer.trace(@src()); defer trace.end(); - const node_tag = t.ast.nodes.items(.tag)[node]; - switch (node_tag) { - .id => { - // Lower builtin ids without using a block - const id = try t.decodeIdentifier(t.ast.basicNodeSlice(node)); - if (BuiltInIds.get(id)) |ref| { - return ref; - } - }, + const scratch = t.scratch.mark(); + defer scratch.deinit(); - else => {}, - } - - var block: ?Inst.Index = null; - - const scratch_top = t.scratch.items.len; - defer t.scratch.shrinkRetainingCapacity(scratch_top); - - if (is_function) { + // For function blocks, add .param instructions for all of the parameters + if (mode == .function_body) { const s = t.scopes.getLast(); for (s.symbols.values(), 0..) |*param, i| { assert(param.data == .param); - const ref = try t.addInstRef(.{ - .tag = .param, - .data = EncodedData.encode(.param, .{ - .src_node = decl.nodeOffset(node), - .data = @intCast(i), - }), + const ref = try t.addRef(.param, .{ + .src_node = ctx.nodeOffset(node), + .data = @intCast(i), }); param.data = .{ .immutable = ref }; } } + // The result type is only passed by reference if mode is tracked_result. + const copies_result_type = mode.ResultType() == ResultType; + var result_type_storage = if (copies_result_type) result_type; + + var ret = if (mode == .function_body) + BlockContext.ReturnTarget{ .result_type = result_type_storage }; + const expr = try t.transformExpression( - if (is_function) - decl.withReturn(&block) + // Function blocks are allowed to return, in which + // it will return to this block. + if (mode == .function_body) + ctx.withReturn(&ret) else - decl, + ctx, + // Pass result type pointer + if (copies_result_type) + &result_type_storage + else + result_type, node, ); // if there are no new instructions, we dont have to create a block context - if (scratch_top == t.scratch.items.len) { - const result = expr orelse unreachable; // null means we *added* break or return + if (scratch.len() == 0) { + const result = expr orelse + unreachable; // null means we *added* break or return - // TODO: evaluate if `if` blocks would be fine in sema with instruction refs - // with this extra if check here, we filter out non-blocks. if (result.value() != null) { - assert(block == null); + if (mode == .function_body) + assert(ret.generated_block == null); // return was called without an instruction + return result; } } + // If we analyzed a block, then just use that. No need for an extra wrapper block. if (expr) |result| { if (result.index()) |index| { if (t.insts.items(.tag)[@intFromEnum(index)] == .block) { @@ -573,32 +663,26 @@ fn transformExpressionToBlock( } } - const ret_block = if (block) |b| - b - else - try t.addRootInst(.{ - .tag = .block, - .data = undefined, - }); + // Otherwise try using the function return block, or create a new one + const maybe_generated_block = if (mode == .function_body) ret.generated_block else null; + const ret_block = maybe_generated_block orelse try t.addUntracked(.block, undefined); + // The block must end with a break if (expr) |result| { - _ = try t.addInstRef(.{ - .tag = .@"break", - .data = EncodedData.encode(.@"break", .{ - .src_node = decl.nodeOffset(node), - .extra_index = try t.extra.append(t.gpa, Inst.Break, .{ - .block = ret_block, - .operand = result, - }), + _ = try t.addIndex(.@"break", .{ + .src_node = ctx.nodeOffset(node), + .extra_index = try t.extra.append(t.gpa, Teg.Break, .{ + .block = ret_block, + .operand = result, }), }); } - const content = try t.scratchToIndexList(scratch_top); + const content = try t.scratchToIndexList(scratch); assert(content.len > 0); t.getInstData(ret_block).* = EncodedData.encode(.block, .{ - .src_node = decl.nodeOffset(node), + .src_node = ctx.nodeOffset(node), .extra_index = try t.extra.append(t.gpa, IndexList, content), }); @@ -610,7 +694,13 @@ fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Alloc defer trace.end(); const param_decl = t.ast.fullParamDecl(param); - const value = try t.transformExpressionToBlock(decl, param_decl.type orelse @panic("TODO: inferred param types"), false); + const ty = try t.transformToBlock( + decl, + param_decl.type orelse + @panic("TODO: inferred param types"), + .result_without_coercion, + .{ .ref = .type_type }, + ); var id, const id_allocated = try t.decodeIdentifierExtra(param_decl.id); if (id_allocated) @@ -631,9 +721,10 @@ fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Alloc sym.value_ptr.* = .{ .node = param, .data = .param, + .type = .{ .present = ty }, }; - return value; + return ty; } pub const Builtin = enum { @@ -664,14 +755,12 @@ pub const Builtin = enum { return .void; } - const source = try t.transformExpression(decl, args[0]) orelse return null; + var result: ResultType = .{ .ref = .string_type }; + const source = try t.transformExpression(decl, &result, args[0]) orelse return null; - return try t.addInstRef(.{ - .tag = .js, - .data = EncodedData.encode(.js, .{ - .src_node = decl.nodeOffset(args[0]), - .operand = source, - }), + return try t.addRef(.js, .{ + .src_node = decl.nodeOffset(args[0]), + .operand = source, }); } @@ -684,17 +773,15 @@ pub const Builtin = enum { return .void; } - const base = try t.transformExpression(decl, args[0]) orelse return null; - const args_list = try t.transformExpressionList(decl, args[1..]) orelse return null; + var opaque_type: ResultType = .{ .ref = .opaque_type }; + const base = try t.transformExpression(decl, &opaque_type, args[0]) orelse return null; + const args_list = try t.transformExpressionList(decl, .{ .ref = .opaque_type }, args[1..]) orelse return null; - return try t.addInstRef(.{ - .tag = .js_new, - .data = EncodedData.encode(.js_new, .{ - .src_node = decl.nodeOffset(args[0]), - .extra_index = try t.extra.append(t.gpa, Inst.JsNew, .{ - .base = base, - .args = args_list, - }), + return try t.addRef(.js_new, .{ + .src_node = decl.nodeOffset(args[0]), + .extra_index = try t.extra.append(t.gpa, Teg.JsNew, .{ + .base = base, + .args = args_list, }), }); } @@ -708,15 +795,13 @@ pub const Builtin = enum { return .void; } - const source = try t.transformExpression(decl, args[0]) orelse + var string_type: ResultType = .{ .ref = .string_type }; + const source = try t.transformExpression(decl, &string_type, args[0]) orelse return null; - return try t.addInstRef(.{ - .tag = .compile_error, - .data = EncodedData.encode(.compile_error, .{ - .src_node = decl.nodeOffset(node), - .operand = source, - }), + return try t.addRef(.compile_error, .{ + .src_node = decl.nodeOffset(node), + .operand = source, }); } @@ -729,22 +814,25 @@ pub const Builtin = enum { return .void; } - const source = try t.transformExpression(decl, args[0]) orelse + var result: ResultType = .none; + const source = try t.transformExpression(decl, &result, args[0]) orelse return null; - return try t.addInstRef(.{ - .tag = .type_of, - .data = EncodedData.encode(.type_of, .{ - .src_node = decl.nodeOffset(node), - .operand = source, - }), + return try t.addRef(.type_of, .{ + .src_node = decl.nodeOffset(node), + .operand = source, }); } }; /// This is the primary transformation function: converting an AST expression into instructions. /// `null` represents a `return` or `break`, aka this branch of code is dead. -fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Allocator.Error!?Inst.Ref { +fn transformExpression( + t: *Transform, + ctx: BlockContext, + result: *ResultType, + node: NodeIndex, +) Allocator.Error!?Inst.Ref { const node_tag = t.ast.nodes.items(.tag)[node]; const trace = tracer.traceFmt(@src(), "{s}", .{@tagName(node_tag)}); @@ -775,12 +863,9 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc lookup.used = true; return switch (lookup.data) { - .declaration => try t.addInstRef(.{ - .tag = .load_decl, - .data = EncodedData.encode(.load_decl, .{ - .src_node = decl.nodeOffset(node), - .string = try t.addNullTerminatedString(id), - }), + .declaration => try t.addRef(.load_decl, .{ + .src_node = ctx.nodeOffset(node), + .string = try t.addNullTerminatedString(id), }), .mutable => |idx| idx.toRef(), .immutable => |ref| ref, @@ -800,41 +885,32 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc try t.report.warn(.zero_prefixed_literal, .{ .token = t.ast.nodeToken(node) }); } - return try t.addInstRef(.{ - .tag = .number, - .data = EncodedData.encode(.number, .{ - .src_node = decl.nodeOffset(node), - .extra_index = try t.extra.append(t.gpa, Float, Float.encode(value)), - }), + return try t.addRef(.number, .{ + .src_node = ctx.nodeOffset(node), + .extra_index = try t.extra.append(t.gpa, Float, Float.encode(value)), }); }, .string => { const str = t.ast.basicNodeSlice(node); const sliced = try t.addDecodedSlicedString(str[1 .. str.len - 1]); - return try t.addInstRef(.{ - .tag = .string, - .data = EncodedData.encode(.string, .{ - .src_node = decl.nodeOffset(node), - .extra_index = try t.extra.append(t.gpa, SlicedString, sliced), - }), + return try t.addRef(.string, .{ + .src_node = ctx.nodeOffset(node), + .extra_index = try t.extra.append(t.gpa, SlicedString, sliced), }); }, .assign => { const ast_bin = t.ast.nodeData(node).bin; - const lhs = try t.transformAssignmentTarget(decl, ast_bin.lhs) orelse + const lhs, var result_type = try t.transformAssignmentTarget(ctx, ast_bin.lhs) orelse return .void; - const rhs = try t.transformExpression(decl, ast_bin.rhs) orelse + const rhs = try t.transformExpression(ctx, &result_type, ast_bin.rhs) orelse return null; - const assign_inst = try t.addInstRef(.{ - .tag = .store, - .data = EncodedData.encode(.store, .{ - .src_node = decl.nodeOffset(node), - .extra_index = try t.extra.append(t.gpa, Inst.Store, .{ - .memory = lhs, - .operand = rhs, - }), + const assign_inst = try t.addRef(.store, .{ + .src_node = ctx.nodeOffset(node), + .extra_index = try t.extra.append(t.gpa, Teg.Store, .{ + .memory = lhs, + .operand = rhs, }), }); return assign_inst; @@ -877,41 +953,39 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc // compound compute + assign operation. const is_assign = comptime tag.isAssignBin(); const teg_tag = @field( - Teg.Inst.Tag, + Teg.Tag, if (is_assign) @tagName(tag)[0 .. @tagName(tag).len - "_assign".len] else @tagName(tag), ); const ast_bin = t.ast.nodeData(node).bin; - const lhs = if (is_assign) - try t.transformAssignmentTarget(decl, ast_bin.lhs) orelse + var lhs_result_type: ResultType = .none; + const lhs, var assign_result_type: ResultType = if (is_assign) + try t.transformAssignmentTarget(ctx, ast_bin.lhs) orelse return .void else - try t.transformExpression(decl, ast_bin.lhs) orelse - return null; - const rhs = try t.transformExpression(decl, ast_bin.rhs) orelse return null; + .{ + try t.transformExpression(ctx, &lhs_result_type, ast_bin.lhs) orelse + return null, + .none, + }; + const rhs = try t.transformExpression(ctx, &assign_result_type, ast_bin.rhs) orelse return null; - const compute_inst = try t.addInstRef(.{ - .tag = teg_tag, - .data = EncodedData.encode(teg_tag, .{ - .src_node = decl.nodeOffset(node), - .extra_index = try t.extra.append(t.gpa, Inst.Bin, .{ - .lhs = if (is_assign) lhs.toRef() else lhs, - .rhs = rhs, - }), + const compute_inst = try t.addRef(teg_tag, .{ + .src_node = ctx.nodeOffset(node), + .extra_index = try t.extra.append(t.gpa, Teg.Bin, .{ + .lhs = if (is_assign) lhs.toRef() else lhs, + .rhs = rhs, }), }); if (is_assign) { - const assign_inst = try t.addInstRef(.{ - .tag = .store, - .data = EncodedData.encode(.store, .{ - .src_node = decl.nodeOffset(node), - .extra_index = try t.extra.append(t.gpa, Inst.Store, .{ - .memory = lhs, - .operand = compute_inst, - }), + const assign_inst = try t.addRef(.store, .{ + .src_node = ctx.nodeOffset(node), + .extra_index = try t.extra.append(t.gpa, Teg.Store, .{ + .memory = lhs, + .operand = compute_inst, }), }); return assign_inst; @@ -926,29 +1000,31 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc .array_type, => |tag| { const ast_un = t.ast.nodeData(node).un; - const teg_tag = @field(Teg.Inst.Tag, @tagName(tag)); + const teg_tag = @field(Teg.Tag, @tagName(tag)); - const operand = try t.transformExpression(decl, ast_un.expr) orelse + var operand_type: ResultType = switch (tag) { + .negate => .none, // any numeric type allowed + .bool_not => .{ .ref = .bool_type }, + .bit_not => .{ .ref = .int32_type }, + .array_type => .{ .ref = .type_type }, + else => @compileError(unreachable), + }; + + const operand = try t.transformExpression(ctx, &operand_type, ast_un.expr) orelse return null; - return try t.addInstRef(.{ - .tag = teg_tag, - .data = EncodedData.encode(teg_tag, .{ - .src_node = decl.nodeOffset(node), - .operand = operand, - }), + return try t.addRef(teg_tag, .{ + .src_node = ctx.nodeOffset(node), + .operand = operand, }); }, .@"comptime" => { const ast_un = t.ast.nodeData(node).un; - const operand = try t.transformExpressionToBlock(decl, ast_un.expr, false); - return try t.addInstRef(.{ - .tag = .@"comptime", - .data = EncodedData.encode(.@"comptime", .{ - .src_node = decl.nodeOffset(node), - .operand = operand, - }), + const operand = try t.transformToBlock(ctx, ast_un.expr, .tracked_result, result); + return try t.addRef(.@"comptime", .{ + .src_node = ctx.nodeOffset(node), + .operand = operand, }); }, @@ -957,43 +1033,42 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc const label = t.ast.tokenSlice(t.ast.nodeToken(node)); var target: BlockContext.BreakTarget = .{ - .parent = decl.break_target, + .parent = ctx.break_target, .label = label, .is_loop = false, + .result_type = result, }; - const inner_ctx = decl.withBreak(&target); + const inner_ctx = ctx.withBreak(&target); - const scratch_top = t.scratch.items.len; + const scratch = t.scratch.mark(); - const inner_expr = try t.transformExpression(inner_ctx, expr); + const inner_expr = try t.transformExpression(inner_ctx, result, expr); // if there are no new instructions, we dont have to create a block context - if (scratch_top == t.scratch.items.len) - return inner_expr orelse unreachable; // null means we added break or return + if (scratch.len() == 0) + return inner_expr orelse + unreachable; // null means we added break or return if (target.block_index) |block| { - if (inner_expr) |result| { - _ = try t.addInstRef(.{ - .tag = .@"break", - .data = EncodedData.encode(.@"break", .{ - .src_node = decl.nodeOffset(node), - .extra_index = try t.extra.append(t.gpa, Inst.Break, .{ - .block = block, - .operand = result, - }), + if (inner_expr) |out| { + _ = try t.addRef(.@"break", .{ + .src_node = ctx.nodeOffset(node), + .extra_index = try t.extra.append(t.gpa, Teg.Break, .{ + .block = block, + .operand = out, }), }); } - const content = try t.scratchToIndexList(scratch_top); - t.scratch.shrinkRetainingCapacity(scratch_top); + const content = try t.scratchToIndexList(scratch); + scratch.deinit(); t.getInstData(block).* = EncodedData.encode(.block, .{ - .src_node = decl.nodeOffset(node), + .src_node = ctx.nodeOffset(node), .extra_index = try t.extra.append(t.gpa, IndexList, content), }); - try t.addScratch(Inst.Index, block); + try t.scratch.add(t.arena, Inst.Index, block); return block.toRef(); } else { @@ -1012,9 +1087,12 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc for (statements, 1..) |statement, i| { const is_last = i == statements.len; last = try t.transformBlockStatement( - decl, + ctx, statement, - tag == .block_implicit_return and is_last, + if (tag == .block_implicit_return and is_last) + result + else + null, ) orelse { return null; }; @@ -1023,8 +1101,7 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc return if (tag == .block_implicit_return) last else .void; }, - .builtin_call, - => { + .builtin_call => { const builtin = Builtin.map.get(t.ast.basicNodeSlice(node)[1..]) orelse { try t.report.err(.invalid_builtin, .{ .node = node }); return .void; @@ -1034,12 +1111,12 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc const args = base.slice(&t.ast); return switch (builtin) { - .js => Builtin.builtin_js(t, decl, node, args), - .js_new => Builtin.builtin_jsNew(t, decl, node, args), + .js => Builtin.builtin_js(t, ctx, node, args), + .js_new => Builtin.builtin_jsNew(t, ctx, node, args), // .import => Builtin.import(t, decl, node, args), // .fetch => Builtin.fetch(t, decl, node, args), - .compile_error => Builtin.builtin_compileError(t, decl, node, args), - .type_of => Builtin.builtin_typeOf(t, decl, node, args), + .compile_error => Builtin.builtin_compileError(t, ctx, node, args), + .type_of => Builtin.builtin_typeOf(t, ctx, node, args), else => @panic("TODO"), }; }, @@ -1048,17 +1125,35 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc const call = t.ast.nodeData(node).call; const args = t.ast.extraData(call.extra, Ast.Call).args.slice(&t.ast); - const base = try t.transformExpression(decl, call.base) orelse return null; - const args_list = try t.transformExpressionList(decl, args) orelse return null; - - const call_inst = try t.addInstRef(.{ - .tag = .call, - .data = EncodedData.encode(.call, .{ - .src_node = decl.nodeOffset(node), - .extra_index = try t.extra.append(t.gpa, Inst.Call, .{ - .base = base, - .args = args_list, - }), + var base_result: ResultType = .none; + const base = try t.transformExpression(ctx, &base_result, call.base) orelse return null; + + const args_list: RefList = call_args: { + const extra_top = try t.extra.alloc(t.gpa, args.len); + + for (args, 0.., extra_top..) |arg, arg_id, index| { + var arg_result: ResultType = .{ .argument = .{ + .call_base = base, + .argument = @intCast(arg_id), + } }; + t.extra.setU32( + index, + @intFromEnum(try t.transformExpression(ctx, &arg_result, arg) orelse + return null), + ); + } + + break :call_args .{ + .start = @intCast(extra_top), + .len = @intCast(args.len), + }; + }; + + const call_inst = try t.addRef(.call, .{ + .src_node = ctx.nodeOffset(node), + .extra_index = try t.extra.append(t.gpa, Teg.Call, .{ + .base = base, + .args = args_list, }), }); @@ -1068,28 +1163,23 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc .field => { const field = t.ast.nodeData(node).field; - const base = try t.transformExpression(decl, field.base) orelse return null; + var base_result: ResultType = .none; + const base = try t.transformExpression(ctx, &base_result, field.base) orelse return null; // TODO: handle identifier escapes const id = t.ast.tokenSlice(field.field_token); const prop_name_str = try t.addSlicedString(id); - const string_ref = try t.addInstRef(.{ - .tag = .string, - .data = EncodedData.encode(.string, .{ - .src_node = decl.nodeOffset(node), - .extra_index = try t.extra.append(t.gpa, SlicedString, prop_name_str), - }), + const string_ref = try t.addRef(.string, .{ + .src_node = ctx.nodeOffset(node), + .extra_index = try t.extra.append(t.gpa, SlicedString, prop_name_str), }); - return try t.addInstRef(.{ - .tag = .field, - .data = EncodedData.encode(.field, .{ - .src_node = decl.nodeOffset(node), - .extra_index = try t.extra.append(t.gpa, Inst.Bin, .{ - .lhs = base, - .rhs = string_ref, - }), + return try t.addRef(.field, .{ + .src_node = ctx.nodeOffset(node), + .extra_index = try t.extra.append(t.gpa, Teg.Bin, .{ + .lhs = base, + .rhs = string_ref, }), }); }, @@ -1098,45 +1188,36 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc const if_node = t.ast.nodeData(node).@"if"; const if_data = t.ast.extraData(if_node.extra, Ast.If); - const cond = try t.transformExpression(decl, if_node.cond) orelse + var cond_result: ResultType = .{ .ref = .bool_type }; + const cond = try t.transformExpression(ctx, &cond_result, if_node.cond) orelse return null; - const yes = try t.transformExpressionToBlock( - decl, - if_data.yes, - false, - ); + var yes_result = result.*; + const yes = try t.transformToBlock(ctx, if_data.yes, .tracked_result, &yes_result); + + // TODO: can this re-use the resolved ref from above + var no_result: ResultType = if (if_data.no == .none) .used else result.*; const no = if (if_data.no.unwrap()) |no| - try t.transformExpressionToBlock( - decl, + try t.transformToBlock( + ctx, no, - false, + .tracked_result, + &no_result, ) else Inst.Ref.void; - if (yes == .void and no == .void) { - // when both branches do nothing, we can omit the branch, - // but we still need to type-check the condition operand - _ = try t.addInstRef(.{ - .tag = .ensure_bool, - .data = EncodedData.encode(.ensure_bool, .{ - .src_node = decl.nodeOffset(node), - .operand = cond, - }), - }); - return .void; + // Mark the incoming result type as used if both branches use the result type + if (result.* != .none and yes_result == .none and no_result == .none) { + result.* = .used; } - return try t.addInstRef(.{ - .tag = .@"if", - .data = EncodedData.encode(.@"if", .{ - .src_node = decl.nodeOffset(node), - .extra_index = try t.extra.append(t.gpa, Inst.If, .{ - .cond = cond, - .yes = yes, - .no = no, - }), + return try t.addRef(.@"if", .{ + .src_node = ctx.nodeOffset(node), + .extra_index = try t.extra.append(t.gpa, Teg.If, .{ + .cond = cond, + .yes = yes, + .no = no, }), }); }, @@ -1144,36 +1225,35 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc .@"return" => { const ret_node = t.ast.nodeData(node).@"return"; - const ret_block_ptr = decl.return_target orelse { + const target = ctx.return_target orelse { try t.report.err(.invalid_return, .{ .node = node }); return .void; }; - const ret_block = if (ret_block_ptr.*) |block| + const ret_block = if (target.generated_block) |block| block else append_block: { - const block = try t.addRootInst(.{ - .tag = .block, - .data = undefined, - }); - ret_block_ptr.* = block; + const block = try t.addUntracked(.block, undefined); + target.generated_block = block; break :append_block block; }; + var return_result = target.result_type; + const operand: Inst.Ref = if (ret_node.expr.unwrap()) |expr| - try t.transformExpression(decl.withReturn(null), expr) orelse - return null + try t.transformExpression( + ctx.withReturn(null), + &return_result, + expr, + ) orelse return null else .void; - _ = try t.addInstRef(.{ - .tag = .@"break", - .data = EncodedData.encode(.@"break", .{ - .src_node = decl.nodeOffset(node), - .extra_index = try t.extra.append(t.gpa, Inst.Break, .{ - .block = ret_block, - .operand = operand, - }), + _ = try t.addIndex(.@"break", .{ + .src_node = ctx.nodeOffset(node), + .extra_index = try t.extra.append(t.gpa, Teg.Break, .{ + .block = ret_block, + .operand = operand, }), }); @@ -1187,36 +1267,29 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc @panic("TODO: unlabelled breaks"), ); - const block = try decl.findAndMarkBreak(t, label) orelse { + const block: *BlockContext.BreakTarget = ctx.lookupNamedBreak(label) orelse { try t.report.err(.unresolved_label, .{ .node = node }); return null; }; + var break_result = block.result_type.*; const operand: Inst.Ref = if (brk_node.value.unwrap()) |expr| - try t.transformExpression(decl.withReturn(null), expr) orelse + try t.transformExpression(ctx.withReturn(null), &break_result, expr) orelse return null else .void; - _ = try t.addInstRef(.{ - .tag = .@"break", - .data = EncodedData.encode(.@"break", .{ - .src_node = decl.nodeOffset(node), - .extra_index = try t.extra.append(t.gpa, Inst.Break, .{ - .block = block, - .operand = operand, - }), + _ = try t.addIndex(.@"break", .{ + .src_node = ctx.nodeOffset(node), + .extra_index = try t.extra.append(t.gpa, Teg.Break, .{ + .block = try block.getInst(t), + .operand = operand, }), }); return null; // control flow ends }, - .group => { - const group_node = t.ast.nodeData(node).un; - return try t.transformExpression(decl, group_node.expr); - }, - .struct_decl => { const struct_node = t.ast.fullStructDecl(node); 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 const field = t.ast.fullStructField(field_node); const id = try t.decodeIdentifier(field.id); + const key = try t.addNullTerminatedString(id); - t.extra.setU32(i, @intFromEnum(try t.addNullTerminatedString(id))); - t.extra.setU32(j, @intFromEnum(try t.transformExpressionToBlock(decl, field.type, false))); + var type_result: ResultType = .{ .ref = .type_type }; + const value = try t.transformExpression(ctx, &type_result, field.type) orelse + return null; + + t.extra.setU32(i, @intFromEnum(key)); + t.extra.setU32(j, @intFromEnum(value)); } - return try t.addInstRef(.{ - .tag = .struct_decl, - .data = EncodedData.encode(.struct_decl, .{ - .src_node = decl.nodeOffset(node), - .extra_index = try t.extra.append(t.gpa, Inst.StructDecl, .{ - .payload_index = payload_index, - .field_len = @intCast(struct_node.fields.len), - }), + return try t.addRef(.struct_decl, .{ + .src_node = ctx.nodeOffset(node), + .extra_index = try t.extra.append(t.gpa, Teg.StructDecl, .{ + .payload_index = payload_index, + .field_len = @intCast(struct_node.fields.len), }), }); }, .struct_init => { const struct_node = t.ast.fullStructInit(node); + // TODO: use t.scratch for this const payload_index = try t.extra.alloc(t.gpa, struct_node.fields.len * 2); - const base_type = try t.transformExpression( - decl, - struct_node.type orelse @panic("TODO: TEG implicit struct"), - ) orelse - @panic("TODO: print better error"); + var result_type: ResultType = .{ .ref = .type_type }; + const base_type = if (struct_node.type) |ty| + try t.transformExpression(ctx, &result_type, ty) orelse + return null + else + (try result.consume(t)) orelse { + try t.report.err(.missing_result_type, .{ .node = node }); + return .void; + }; for ( struct_node.fields, @@ -1264,21 +1344,30 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc const field = t.ast.fullStructField(field_node); const id = try t.decodeIdentifier(field.id); + const field_name = try t.addNullTerminatedString(id); - t.extra.setU32(i, @intFromEnum(try t.addNullTerminatedString(id))); - t.extra.setU32(j, @intFromEnum((try t.transformExpression(decl, field.type)) orelse - @panic("TODO: print better error"))); + var field_result_type: ResultType = .{ .struct_field = .{ + .type = base_type, + .field = field_name, + } }; + const field_value = (try t.transformExpression( + ctx, + &field_result_type, + field.type, + )) orelse return null; + + t.extra.setU32(i, @intFromEnum(field_name)); + t.extra.setU32(j, @intFromEnum( + field_value, + )); } - return try t.addInstRef(.{ - .tag = .struct_init, - .data = EncodedData.encode(.struct_init, .{ - .src_node = decl.nodeOffset(node), - .extra_index = try t.extra.append(t.gpa, Inst.StructInit, .{ - .base_type = base_type, - .payload_index = payload_index, - .field_len = @intCast(struct_node.fields.len), - }), + return try t.addRef(.struct_init, .{ + .src_node = ctx.nodeOffset(node), + .extra_index = try t.extra.append(t.gpa, Teg.StructInit, .{ + .base_type = base_type, + .payload_index = payload_index, + .field_len = @intCast(struct_node.fields.len), }), }); }, @@ -1286,17 +1375,19 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc .array_init => { const array_init = t.ast.nodeData(node).list; - const items = try t.transformExpressionList(decl, array_init.slice(&t.ast)) orelse - return null; + const array_type = try result.consume(t); - return try t.addInstRef(.{ - .tag = .array_init, - .data = EncodedData.encode(.array_init, .{ - .src_node = decl.nodeOffset(node), - .extra_index = try t.extra.append(t.gpa, Inst.ArrayInit, .{ - .type_hint = .none, - .list = items, - }), + const items = try t.transformExpressionList( + ctx, + if (array_type) |ty| .{ .array_value = ty } else .none, + array_init.slice(&t.ast), + ) orelse return null; + + return try t.addRef(.array_init, .{ + .src_node = ctx.nodeOffset(node), + .extra_index = try t.extra.append(t.gpa, Teg.ArrayInit, .{ + .type_hint = .none, + .list = items, }), }); }, @@ -1305,12 +1396,16 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc } } -fn transformExpressionList(t: *Transform, decl: BlockContext, args: []const NodeIndex) !?RefList { +fn transformExpressionList(t: *Transform, decl: BlockContext, result: ResultType, args: []const NodeIndex) !?RefList { const extra_top = try t.extra.alloc(t.gpa, args.len); for (args, extra_top..) |arg, i| { - t.extra.setU32(i, @intFromEnum(try t.transformExpression(decl, arg) orelse - return null)); + var result_mut = result; + t.extra.setU32( + i, + @intFromEnum(try t.transformExpression(decl, &result_mut, arg) orelse + return null), + ); } return .{ @@ -1319,7 +1414,25 @@ fn transformExpressionList(t: *Transform, decl: BlockContext, args: []const Node }; } -fn transformAssignmentTarget(t: *Transform, decl: BlockContext, node: NodeIndex) Allocator.Error!?Inst.Index { +/// The left-hand side of an assignment is known as the "assignment target". +/// This returns the assignment target's Ref, as well as a result type to visit +/// the right-hand side with. Note that only a few expressions are valid here: +/// +/// - A plain identifier, which is resolved directly to a `memory` instruction +/// - A `field` node: TODO +/// - An `index` node: TODO +/// +/// Note that these three are the root nodes, it is legal to have: +/// +/// get().x = 1; // `field` of a `call` node +/// +/// In the end, the assignment target must comprise either a ref to a `memory` +/// or a value with a reference type; and optionally a sub property. +fn transformAssignmentTarget( + t: *Transform, + decl: BlockContext, + node: NodeIndex, +) Allocator.Error!?struct { Inst.Index, ResultType } { const node_tag = t.ast.nodes.items(.tag)[node]; const trace = tracer.traceFmt(@src(), "{s}", .{@tagName(node_tag)}); @@ -1345,7 +1458,7 @@ fn transformAssignmentTarget(t: *Transform, decl: BlockContext, node: NodeIndex) return null; } - const lookup = t.lookupId(id) orelse { + const lookup: *Scope.Symbol = t.lookupId(id) orelse { try t.report.err(.unknown_identifier, .{ .node = node }); return null; }; @@ -1353,7 +1466,7 @@ fn transformAssignmentTarget(t: *Transform, decl: BlockContext, node: NodeIndex) return switch (lookup.data) { .mutable => |inst| { lookup.mutated = true; - return inst; + return .{ inst, lookup.type.resolve() }; }, else => { lookup.used = true; @@ -1371,6 +1484,7 @@ fn transformAssignmentTarget(t: *Transform, decl: BlockContext, node: NodeIndex) }, }, }); + return null; }, }; @@ -1379,10 +1493,13 @@ fn transformAssignmentTarget(t: *Transform, decl: BlockContext, node: NodeIndex) // TODO: allow field expressions, but these need to emit a different store node else => { - _ = try t.transformExpression(decl, node); + var result_type: ResultType = .none; + _ = try t.transformExpression(decl, &result_type, node); + try t.report.err(.invalid_assignment_target, .{ .node = node, }); + return null; }, } @@ -1390,10 +1507,13 @@ fn transformAssignmentTarget(t: *Transform, decl: BlockContext, node: NodeIndex) fn transformBlockStatement( t: *Transform, - decl: BlockContext, + ctx: BlockContext, node: NodeIndex, - is_implicit_return_expression: bool, + maybe_result: ?*ResultType, ) !?Inst.Ref { + var void_result_storage: ResultType = .{ .ref = .void }; + const result = maybe_result orelse &void_result_storage; + const node_tag = t.ast.nodes.items(.tag)[node]; const trace = tracer.traceFmt(@src(), "{s}", .{@tagName(node_tag)}); @@ -1437,16 +1557,16 @@ fn transformBlockStatement( return .void; }, - else => try t.transformExpression(decl, var_decl.init) orelse - return null, + else => ref: { + var empty_type: ResultType = .none; + break :ref try t.transformExpression(ctx, &empty_type, var_decl.init) orelse + return null; + }, }; - return try t.addInstRef(.{ - .tag = .discard, - .data = EncodedData.encode(.discard, .{ - .src_node = decl.nodeOffset(node), - .operand = ref, - }), + return try t.addRef(.discard, .{ + .src_node = ctx.nodeOffset(node), + .operand = ref, }); } @@ -1464,36 +1584,37 @@ fn transformBlockStatement( return .void; } + var type_result: ResultType = .{ .ref = .type_type }; const type_ref = if (var_decl.type) |data_type| - try t.transformExpression(decl, data_type) + try t.transformExpression(ctx, &type_result, data_type) else null; - const data_ref = try t.transformExpression(decl, var_decl.init) orelse + + var value_result: ResultType = if (type_ref) |ty| + .{ .ref = ty } + else + .none; + + const data_ref = try t.transformExpression(ctx, &value_result, var_decl.init) orelse return null; - const data_coerced_ref = if (type_ref) |ref| - try t.addInstRef(.{ - .tag = .as, - .data = EncodedData.encode(.as, .{ - .src_node = decl.nodeOffset(node), - .extra_index = try t.extra.append(t.gpa, Inst.As, .{ - .type = ref, - .operand = data_ref, - }), - }), - }) - else - data_ref; - const mem_ref = if (var_decl.is_const) - data_coerced_ref - else - try t.addInstRef(.{ - .tag = .memory, - .data = EncodedData.encode(.memory, .{ - .src_node = decl.nodeOffset(node), - .operand = data_coerced_ref, - }), - }); + // Add a .coerce instruction if there was a type declared. + const data_coerced_ref = coerced: { + if (type_ref) |ref| { + // We can avoid the coercion instruction if the result type was used. + if (value_result != .used) { + break :coerced try t.addRef(.coercion, .{ + .src_node = ctx.nodeOffset(node), + .extra_index = try t.extra.append(t.gpa, Teg.Coercion, .{ + .type = ref, + .operand = data_ref, + }), + }); + } + } + + break :coerced data_ref; + }; const gop = try t.scope().symbols.getOrPut(t.gpa, id); @@ -1505,16 +1626,26 @@ fn transformBlockStatement( gop.value_ptr.* = .{ .node = node, - .data = switch (var_decl.is_const) { - true => .{ .immutable = mem_ref }, - false => .{ .mutable = mem_ref.index().? }, - }, + .data = if (var_decl.is_const) + .{ .immutable = data_coerced_ref } + else + .{ + .mutable = try t.addIndex(.memory, .{ + .src_node = ctx.nodeOffset(node), + .operand = data_coerced_ref, + }), + }, + .type = if (type_ref) |ty| + .{ .present = ty } + else + .{ .missing = data_ref }, }; return .void; }, .fn_decl, + .struct_decl, => |tag| { std.debug.panic("TODO: block statement {s}", .{@tagName(tag)}); }, @@ -1530,18 +1661,17 @@ fn transformBlockStatement( // discard, instead of appending an .ensure_void check, // we will add a .discard instruction instead. // this will emit a warning if given void. - const result = try t.transformExpression(decl, bin.rhs) orelse + var result_type: ResultType = .none; + const value = try t.transformExpression(ctx, &result_type, bin.rhs) orelse return null; - return try t.addInstRef(.{ - .tag = .discard, - .data = EncodedData.encode(.discard, .{ - .src_node = decl.nodeOffset(node), - .operand = result, - }), + + return try t.addRef(.discard, .{ + .src_node = ctx.nodeOffset(node), + .operand = value, }); } else { // process this assignment as usual, do not append .ensure_void - return try t.transformExpression(decl, node); + return try t.transformExpression(ctx, result, node); } }, @@ -1560,50 +1690,51 @@ fn transformBlockStatement( .shr_assign, => { // do not append a .ensure_void check - return try t.transformExpression(decl, node); + return try t.transformExpression(ctx, result, node); }, else => { - const result = try t.transformExpression(decl, node) orelse + const value = try t.transformExpression(ctx, result, node) orelse return null; - if (!is_implicit_return_expression and result != .void) { + if (maybe_result == null and value != .void) { if (t.ast.nodeTag(node) == .id) { try t.report.warn(.dangling_identifier, .{ .node = node, }); - return result; } - return try t.addInstRef(.{ - .tag = .ensure_void, - .data = EncodedData.encode(.ensure_void, .{ - .src_node = decl.nodeOffset(node), - .operand = result, - }), + return try t.addRef(.ensure_void, .{ + .src_node = ctx.nodeOffset(node), + .operand = value, }); } - return result; + return value; }, } } -fn addRootInst(t: *Transform, inst: Inst) !Inst.Index { - try t.insts.append(t.gpa, inst); +fn addUntracked(t: *Transform, comptime tag: Teg.Tag, data: tag.Data()) !Inst.Index { + try t.insts.append(t.gpa, .{ + .tag = tag, + .data = EncodedData.encode(tag, data), + }); return @enumFromInt(t.insts.len - 1); } -fn addInstRef(t: *Transform, inst: Inst) !Inst.Ref { - try t.scratch.ensureUnusedCapacity(t.gpa, 1); - try t.insts.append(t.gpa, inst); - const index: Inst.Index = @enumFromInt(t.insts.len - 1); - t.scratch.appendAssumeCapacity(@intFromEnum(index)); - return index.toRef(); +fn addIndex(t: *Transform, comptime tag: Teg.Tag, data: tag.Data()) !Inst.Index { + const index = try t.addUntracked(tag, data); + try t.scratch.add(t.arena, Inst.Index, index); + return index; +} + +fn addRef(t: *Transform, comptime tag: Teg.Tag, data: tag.Data()) !Inst.Ref { + return (try t.addIndex(tag, data)).toRef(); } fn pushScope(t: *Transform) !void { - try t.scopes.append(t.gpa, .{}); + try t.scopes.append(t.arena, .{}); } fn popScope(t: *Transform) void { @@ -1620,22 +1751,18 @@ fn getInstData(t: *Transform, index: Inst.Index) *EncodedData { return &t.insts.items(.data)[@intFromEnum(index)]; } -fn addScratch(t: *Transform, comptime T: type, value: T) !void { - try t.scratch.append(t.gpa, @intFromEnum(value)); -} - -fn scratchToRefList(t: *Transform, scratch_top: usize) !RefList { - const items = t.scratch.items[scratch_top..]; +fn scratchToRefList(t: *Transform, scratch_top: core.U32Stack.Mark) !RefList { + const items = scratch_top.slice(u32); const extra_top = t.extra.list.items.len; - try t.extra.list.appendSlice(t.gpa, t.scratch.items[scratch_top..]); + try t.extra.list.appendSlice(t.gpa, items); return .{ .start = @intCast(extra_top), .len = @intCast(items.len), }; } -fn scratchToIndexList(t: *Transform, scratch_top: usize) !IndexList { - const items = t.scratch.items[scratch_top..]; +fn scratchToIndexList(t: *Transform, scratch: core.U32Stack.Mark) !IndexList { + const items = scratch.slice(u32); const extra_top = t.extra.list.items.len; try t.extra.list.appendSlice(t.gpa, items); return .{ @@ -1715,8 +1842,8 @@ fn addDecodedSlicedString(t: *Transform, escaped: []const u8) !SlicedString { // string. this is done so that duplicate strings will get de-duplicated // in string_bytes. It is more likely that there will be remaining bytes // in `.scratch` vs `.id_decode_buf` - try t.scratch.ensureUnusedCapacity(t.gpa, @divFloor(len, @sizeOf(u32)) + 1); - const buf = mem.sliceAsBytes(t.scratch.unusedCapacitySlice()); + try t.scratch.list.ensureUnusedCapacity(t.gpa, @divFloor(len, @sizeOf(u32)) + 1); + const buf = mem.sliceAsBytes(t.scratch.list.unusedCapacitySlice()); break :brk Tokenizer.decodeStringBuf(escaped, buf); } else escaped; @@ -1767,7 +1894,8 @@ const mem = std.mem; const Allocator = mem.Allocator; const assert = std.debug.assert; -const Teg = @import("Teg.zig"); +const core = @import("mod.zig"); +const Teg = core.Teg; const Inst = Teg.Inst; const NullTerminatedString = Teg.NullTerminatedString; const SlicedString = Teg.SlicedString; @@ -1776,15 +1904,15 @@ const IndexList = Teg.IndexList; const EncodedData = Teg.EncodedData; const ExtraIndex = Teg.ExtraIndex; -const dod = @import("array.zig"); -const ExtraStore = dod.ExtraStore(.teg, .@"var"); -const Float = dod.Float; +const extra = core.array; +const ExtraStore = extra.ExtraStore(.teg, .@"var"); +const Float = extra.Float; -const Tokenizer = @import("Tokenizer.zig"); +const Tokenizer = core.Tokenizer; const Token = Tokenizer.Token; const ByteOffset = Tokenizer.ByteOffset; -const Ast = @import("Ast.zig"); +const Ast = core.Ast; const Unused = Ast.Unused; const Node = Ast.Node; const Data = Node.Data; @@ -1798,9 +1926,9 @@ const optionalTokenIndex = OptionalTokenIndex.init; const optionalNodeIndex = OptionalNodeIndex.init; const optionalExtraIndex = OptionalExtraIndex.init; -const InternPool = @import("InternPool.zig"); +const InternPool = core.InternPool; -const Report = @import("Report.zig"); -const Comp = @import("Comp.zig"); +const Report = core.Report; +const Comp = core.Comp; const tracer = @import("tracer"); diff --git a/src/core/main.zig b/src/core/main.zig index dbe1cb55392467ffb1024f22a35fcc03aed2a3f7..c7bb7ce0fc99035cc82189db440f4a8e6890f409 100644 --- a/src/core/main.zig +++ b/src/core/main.zig @@ -22,11 +22,6 @@ pub fn main() !void { try unicode_props.init(); - if (builtin.mode == .Debug and std.io.getStdErr().isTty()) { - // zig build system doesnt print a newline :/ - std.debug.print("\r\x1b[K", .{}); - } - const use_gpa = builtin.mode == .Debug; var gpa = if (use_gpa) std.heap.GeneralPurposeAllocator(.{ diff --git a/src/core/mod.zig b/src/core/mod.zig index cc3a1e17802775e1f7583fd91c2e5237571d1db1..a7c78c625208ae3f521622d5c35a1598945bc9fa 100644 --- a/src/core/mod.zig +++ b/src/core/mod.zig @@ -32,6 +32,9 @@ pub const Sema = @import("Sema.zig"); pub const Stack = @import("stack.zig").Stack; pub const U32Stack = Stack(u32); +pub const StoredTrace = @import("StoredTrace.zig"); +pub const DebugStoredTrace = StoredTrace.DebugStoredTrace; + /// Untyped Intermediate Representation. Flat instruction list pub const Teg = @import("Teg.zig");