diff --git a/lib/std/target.zig b/lib/std/target.zig index e1aab72786..04a65b45ad 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -436,6 +436,8 @@ pub const Target = struct { macho, wasm, c, + hex, + raw, }; pub const SubSystem = enum { diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index 2b42342f1e..02d1fd6ba2 100644 --- a/lib/std/zig/ast.zig +++ b/lib/std/zig/ast.zig @@ -495,8 +495,10 @@ pub const Node = struct { While, For, If, - ControlFlowExpression, Suspend, + Continue, + Break, + Return, // Type expressions AnyType, @@ -601,6 +603,24 @@ pub const Node = struct { .Try, => SimplePrefixOp, + .Identifier, + .BoolLiteral, + .NullLiteral, + .UndefinedLiteral, + .Unreachable, + .AnyType, + .ErrorType, + .IntegerLiteral, + .FloatLiteral, + .StringLiteral, + .CharLiteral, + => OneToken, + + .Continue, + .Break, + .Return, + => ControlFlowExpression, + .ArrayType => ArrayType, .ArrayTypeSentinel => ArrayTypeSentinel, @@ -621,23 +641,11 @@ pub const Node = struct { .While => While, .For => For, .If => If, - .ControlFlowExpression => ControlFlowExpression, .Suspend => Suspend, - .AnyType => AnyType, - .ErrorType => ErrorType, .FnProto => FnProto, .AnyFrameType => AnyFrameType, - .IntegerLiteral => IntegerLiteral, - .FloatLiteral => FloatLiteral, .EnumLiteral => EnumLiteral, - .StringLiteral => StringLiteral, .MultilineStringLiteral => MultilineStringLiteral, - .CharLiteral => CharLiteral, - .BoolLiteral => BoolLiteral, - .NullLiteral => NullLiteral, - .UndefinedLiteral => UndefinedLiteral, - .Unreachable => Unreachable, - .Identifier => Identifier, .GroupedExpression => GroupedExpression, .BuiltinCall => BuiltinCall, .ErrorSetDecl => ErrorSetDecl, @@ -1182,19 +1190,19 @@ pub const Node = struct { } }; - pub const Identifier = struct { - base: Node = Node{ .tag = .Identifier }, + pub const OneToken = struct { + base: Node, token: TokenIndex, - pub fn iterate(self: *const Identifier, index: usize) ?*Node { + pub fn iterate(self: *const OneToken, index: usize) ?*Node { return null; } - pub fn firstToken(self: *const Identifier) TokenIndex { + pub fn firstToken(self: *const OneToken) TokenIndex { return self.token; } - pub fn lastToken(self: *const Identifier) TokenIndex { + pub fn lastToken(self: *const OneToken) TokenIndex { return self.token; } }; @@ -2569,34 +2577,65 @@ pub const Node = struct { } }; - /// TODO break this into separate Break, Continue, Return AST Nodes to save memory. - /// Could be further broken into LabeledBreak, LabeledContinue, and ReturnVoid to save even more. + /// Trailed in memory by possibly many things, with each optional thing + /// determined by a bit in `trailer_flags`. + /// Can be: return, break, continue pub const ControlFlowExpression = struct { - base: Node = Node{ .tag = .ControlFlowExpression }, + base: Node, + trailer_flags: TrailerFlags, ltoken: TokenIndex, - kind: Kind, - rhs: ?*Node, - pub const Kind = union(enum) { - Break: ?*Node, - Continue: ?*Node, - Return, + pub const TrailerFlags = std.meta.TrailerFlags(struct { + rhs: *Node, + label: TokenIndex, + }); + + pub const RequiredFields = struct { + tag: Tag, + ltoken: TokenIndex, }; + pub fn getRHS(self: *const ControlFlowExpression) ?*Node { + return self.getTrailer("rhs"); + } + + pub fn getLabel(self: *const ControlFlowExpression) ?TokenIndex { + return self.getTrailer("label"); + } + + pub fn getTrailer(self: *const ControlFlowExpression, comptime name: []const u8) ?TrailerFlags.Field(name) { + const trailers_start = @ptrCast([*]const u8, self) + @sizeOf(ControlFlowExpression); + return self.trailer_flags.get(trailers_start, name); + } + + pub fn setTrailer(self: *ControlFlowExpression, comptime name: []const u8, value: TrailerFlags.Field(name)) void { + const trailers_start = @ptrCast([*]u8, self) + @sizeOf(ControlFlowExpression); + self.trailer_flags.set(trailers_start, name, value); + } + + pub fn create(allocator: *mem.Allocator, required: RequiredFields, trailers: anytype) !*ControlFlowExpression { + const trailer_flags = TrailerFlags.init(trailers); + const bytes = try allocator.alignedAlloc(u8, @alignOf(ControlFlowExpression), sizeInBytes(trailer_flags)); + const ctrl_flow_expr = @ptrCast(*ControlFlowExpression, bytes.ptr); + ctrl_flow_expr.* = .{ + .base = .{ .tag = required.tag }, + .trailer_flags = trailer_flags, + .ltoken = required.ltoken, + }; + const trailers_start = bytes.ptr + @sizeOf(ControlFlowExpression); + trailer_flags.setMany(trailers_start, trailers); + return ctrl_flow_expr; + } + + pub fn destroy(self: *ControlFlowExpression, allocator: *mem.Allocator) void { + const bytes = @ptrCast([*]u8, self)[0..sizeInBytes(self.trailer_flags)]; + allocator.free(bytes); + } + pub fn iterate(self: *const ControlFlowExpression, index: usize) ?*Node { var i = index; - switch (self.kind) { - .Break, .Continue => |maybe_label| { - if (maybe_label) |label| { - if (i < 1) return label; - i -= 1; - } - }, - .Return => {}, - } - - if (self.rhs) |rhs| { + if (self.getRHS()) |rhs| { if (i < 1) return rhs; i -= 1; } @@ -2609,21 +2648,20 @@ pub const Node = struct { } pub fn lastToken(self: *const ControlFlowExpression) TokenIndex { - if (self.rhs) |rhs| { + if (self.getRHS()) |rhs| { return rhs.lastToken(); } - switch (self.kind) { - .Break, .Continue => |maybe_label| { - if (maybe_label) |label| { - return label.lastToken(); - } - }, - .Return => return self.ltoken, + if (self.getLabel()) |label| { + return label; } return self.ltoken; } + + fn sizeInBytes(trailer_flags: TrailerFlags) usize { + return @sizeOf(ControlFlowExpression) + trailer_flags.sizeInBytes(); + } }; pub const Suspend = struct { @@ -2655,23 +2693,6 @@ pub const Node = struct { } }; - pub const IntegerLiteral = struct { - base: Node = Node{ .tag = .IntegerLiteral }, - token: TokenIndex, - - pub fn iterate(self: *const IntegerLiteral, index: usize) ?*Node { - return null; - } - - pub fn firstToken(self: *const IntegerLiteral) TokenIndex { - return self.token; - } - - pub fn lastToken(self: *const IntegerLiteral) TokenIndex { - return self.token; - } - }; - pub const EnumLiteral = struct { base: Node = Node{ .tag = .EnumLiteral }, dot: TokenIndex, @@ -2690,23 +2711,6 @@ pub const Node = struct { } }; - pub const FloatLiteral = struct { - base: Node = Node{ .tag = .FloatLiteral }, - token: TokenIndex, - - pub fn iterate(self: *const FloatLiteral, index: usize) ?*Node { - return null; - } - - pub fn firstToken(self: *const FloatLiteral) TokenIndex { - return self.token; - } - - pub fn lastToken(self: *const FloatLiteral) TokenIndex { - return self.token; - } - }; - /// Parameters are in memory following BuiltinCall. pub const BuiltinCall = struct { base: Node = Node{ .tag = .BuiltinCall }, @@ -2757,23 +2761,6 @@ pub const Node = struct { } }; - pub const StringLiteral = struct { - base: Node = Node{ .tag = .StringLiteral }, - token: TokenIndex, - - pub fn iterate(self: *const StringLiteral, index: usize) ?*Node { - return null; - } - - pub fn firstToken(self: *const StringLiteral) TokenIndex { - return self.token; - } - - pub fn lastToken(self: *const StringLiteral) TokenIndex { - return self.token; - } - }; - /// The string literal tokens appear directly in memory after MultilineStringLiteral. pub const MultilineStringLiteral = struct { base: Node = Node{ .tag = .MultilineStringLiteral }, @@ -2817,74 +2804,6 @@ pub const Node = struct { } }; - pub const CharLiteral = struct { - base: Node = Node{ .tag = .CharLiteral }, - token: TokenIndex, - - pub fn iterate(self: *const CharLiteral, index: usize) ?*Node { - return null; - } - - pub fn firstToken(self: *const CharLiteral) TokenIndex { - return self.token; - } - - pub fn lastToken(self: *const CharLiteral) TokenIndex { - return self.token; - } - }; - - pub const BoolLiteral = struct { - base: Node = Node{ .tag = .BoolLiteral }, - token: TokenIndex, - - pub fn iterate(self: *const BoolLiteral, index: usize) ?*Node { - return null; - } - - pub fn firstToken(self: *const BoolLiteral) TokenIndex { - return self.token; - } - - pub fn lastToken(self: *const BoolLiteral) TokenIndex { - return self.token; - } - }; - - pub const NullLiteral = struct { - base: Node = Node{ .tag = .NullLiteral }, - token: TokenIndex, - - pub fn iterate(self: *const NullLiteral, index: usize) ?*Node { - return null; - } - - pub fn firstToken(self: *const NullLiteral) TokenIndex { - return self.token; - } - - pub fn lastToken(self: *const NullLiteral) TokenIndex { - return self.token; - } - }; - - pub const UndefinedLiteral = struct { - base: Node = Node{ .tag = .UndefinedLiteral }, - token: TokenIndex, - - pub fn iterate(self: *const UndefinedLiteral, index: usize) ?*Node { - return null; - } - - pub fn firstToken(self: *const UndefinedLiteral) TokenIndex { - return self.token; - } - - pub fn lastToken(self: *const UndefinedLiteral) TokenIndex { - return self.token; - } - }; - pub const Asm = struct { base: Node = Node{ .tag = .Asm }, asm_token: TokenIndex, @@ -2904,7 +2823,7 @@ pub const Node = struct { rparen: TokenIndex, pub const Kind = union(enum) { - Variable: *Identifier, + Variable: *OneToken, Return: *Node, }; @@ -3005,57 +2924,6 @@ pub const Node = struct { } }; - pub const Unreachable = struct { - base: Node = Node{ .tag = .Unreachable }, - token: TokenIndex, - - pub fn iterate(self: *const Unreachable, index: usize) ?*Node { - return null; - } - - pub fn firstToken(self: *const Unreachable) TokenIndex { - return self.token; - } - - pub fn lastToken(self: *const Unreachable) TokenIndex { - return self.token; - } - }; - - pub const ErrorType = struct { - base: Node = Node{ .tag = .ErrorType }, - token: TokenIndex, - - pub fn iterate(self: *const ErrorType, index: usize) ?*Node { - return null; - } - - pub fn firstToken(self: *const ErrorType) TokenIndex { - return self.token; - } - - pub fn lastToken(self: *const ErrorType) TokenIndex { - return self.token; - } - }; - - pub const AnyType = struct { - base: Node = Node{ .tag = .AnyType }, - token: TokenIndex, - - pub fn iterate(self: *const AnyType, index: usize) ?*Node { - return null; - } - - pub fn firstToken(self: *const AnyType) TokenIndex { - return self.token; - } - - pub fn lastToken(self: *const AnyType) TokenIndex { - return self.token; - } - }; - /// TODO remove from the Node base struct /// TODO actually maybe remove entirely in favor of iterating backward from Node.firstToken() /// and forwards to find same-line doc comments. diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 0e1af4e8a1..86098f91ad 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -628,8 +628,11 @@ const Parser = struct { var type_expr: ?*Node = null; if (p.eatToken(.Colon)) |_| { if (p.eatToken(.Keyword_anytype) orelse p.eatToken(.Keyword_var)) |anytype_tok| { - const node = try p.arena.allocator.create(Node.AnyType); - node.* = .{ .token = anytype_tok }; + const node = try p.arena.allocator.create(Node.OneToken); + node.* = .{ + .base = .{ .tag = .AnyType }, + .token = anytype_tok, + }; type_expr = &node.base; } else { type_expr = try p.expectNode(parseTypeExpr, .{ @@ -1079,12 +1082,13 @@ const Parser = struct { if (p.eatToken(.Keyword_break)) |token| { const label = try p.parseBreakLabel(); const expr_node = try p.parseExpr(); - const node = try p.arena.allocator.create(Node.ControlFlowExpression); - node.* = .{ + const node = try Node.ControlFlowExpression.create(&p.arena.allocator, .{ + .tag = .Break, .ltoken = token, - .kind = .{ .Break = label }, + }, .{ + .label = label, .rhs = expr_node, - }; + }); return &node.base; } @@ -1115,12 +1119,13 @@ const Parser = struct { if (p.eatToken(.Keyword_continue)) |token| { const label = try p.parseBreakLabel(); - const node = try p.arena.allocator.create(Node.ControlFlowExpression); - node.* = .{ + const node = try Node.ControlFlowExpression.create(&p.arena.allocator, .{ + .tag = .Continue, .ltoken = token, - .kind = .{ .Continue = label }, + }, .{ + .label = label, .rhs = null, - }; + }); return &node.base; } @@ -1139,12 +1144,12 @@ const Parser = struct { if (p.eatToken(.Keyword_return)) |token| { const expr_node = try p.parseExpr(); - const node = try p.arena.allocator.create(Node.ControlFlowExpression); - node.* = .{ + const node = try Node.ControlFlowExpression.create(&p.arena.allocator, .{ + .tag = .Return, .ltoken = token, - .kind = .Return, + }, .{ .rhs = expr_node, - }; + }); return &node.base; } @@ -1516,8 +1521,9 @@ const Parser = struct { fn parsePrimaryTypeExpr(p: *Parser) !?*Node { if (try p.parseBuiltinCall()) |node| return node; if (p.eatToken(.CharLiteral)) |token| { - const node = try p.arena.allocator.create(Node.CharLiteral); + const node = try p.arena.allocator.create(Node.OneToken); node.* = .{ + .base = .{ .tag = .CharLiteral }, .token = token, }; return &node.base; @@ -1547,7 +1553,7 @@ const Parser = struct { const identifier = try p.expectNodeRecoverable(parseIdentifier, .{ .ExpectedIdentifier = .{ .token = p.tok_i }, }); - const global_error_set = try p.createLiteral(Node.ErrorType, token); + const global_error_set = try p.createLiteral(.ErrorType, token); if (period == null or identifier == null) return global_error_set; const node = try p.arena.allocator.create(Node.SimpleInfixOp); @@ -1559,8 +1565,8 @@ const Parser = struct { }; return &node.base; } - if (p.eatToken(.Keyword_false)) |token| return p.createLiteral(Node.BoolLiteral, token); - if (p.eatToken(.Keyword_null)) |token| return p.createLiteral(Node.NullLiteral, token); + if (p.eatToken(.Keyword_false)) |token| return p.createLiteral(.BoolLiteral, token); + if (p.eatToken(.Keyword_null)) |token| return p.createLiteral(.NullLiteral, token); if (p.eatToken(.Keyword_anyframe)) |token| { const node = try p.arena.allocator.create(Node.AnyFrameType); node.* = .{ @@ -1569,9 +1575,9 @@ const Parser = struct { }; return &node.base; } - if (p.eatToken(.Keyword_true)) |token| return p.createLiteral(Node.BoolLiteral, token); - if (p.eatToken(.Keyword_undefined)) |token| return p.createLiteral(Node.UndefinedLiteral, token); - if (p.eatToken(.Keyword_unreachable)) |token| return p.createLiteral(Node.Unreachable, token); + if (p.eatToken(.Keyword_true)) |token| return p.createLiteral(.BoolLiteral, token); + if (p.eatToken(.Keyword_undefined)) |token| return p.createLiteral(.UndefinedLiteral, token); + if (p.eatToken(.Keyword_unreachable)) |token| return p.createLiteral(.Unreachable, token); if (try p.parseStringLiteral()) |node| return node; if (try p.parseSwitchExpr()) |node| return node; @@ -1865,7 +1871,7 @@ const Parser = struct { const variable = try p.expectNode(parseIdentifier, .{ .ExpectedIdentifier = .{ .token = p.tok_i }, }); - break :blk .{ .Variable = variable.cast(Node.Identifier).? }; + break :blk .{ .Variable = variable.castTag(.Identifier).? }; }; const rparen = try p.expectToken(.RParen); @@ -1906,11 +1912,10 @@ const Parser = struct { } /// BreakLabel <- COLON IDENTIFIER - fn parseBreakLabel(p: *Parser) !?*Node { + fn parseBreakLabel(p: *Parser) !?TokenIndex { _ = p.eatToken(.Colon) orelse return null; - return try p.expectNode(parseIdentifier, .{ - .ExpectedIdentifier = .{ .token = p.tok_i }, - }); + const ident = try p.expectToken(.Identifier); + return ident; } /// BlockLabel <- IDENTIFIER COLON @@ -3022,8 +3027,9 @@ const Parser = struct { }); // lets pretend this was an identifier so we can continue parsing - const node = try p.arena.allocator.create(Node.Identifier); + const node = try p.arena.allocator.create(Node.OneToken); node.* = .{ + .base = .{ .tag = .Identifier }, .token = token, }; return &node.base; @@ -3054,8 +3060,9 @@ const Parser = struct { fn parseIdentifier(p: *Parser) !?*Node { const token = p.eatToken(.Identifier) orelse return null; - const node = try p.arena.allocator.create(Node.Identifier); + const node = try p.arena.allocator.create(Node.OneToken); node.* = .{ + .base = .{ .tag = .Identifier }, .token = token, }; return &node.base; @@ -3064,16 +3071,18 @@ const Parser = struct { fn parseAnyType(p: *Parser) !?*Node { const token = p.eatToken(.Keyword_anytype) orelse p.eatToken(.Keyword_var) orelse return null; // TODO remove in next release cycle - const node = try p.arena.allocator.create(Node.AnyType); + const node = try p.arena.allocator.create(Node.OneToken); node.* = .{ + .base = .{ .tag = .AnyType }, .token = token, }; return &node.base; } - fn createLiteral(p: *Parser, comptime T: type, token: TokenIndex) !*Node { - const result = try p.arena.allocator.create(T); - result.* = T{ + fn createLiteral(p: *Parser, tag: ast.Node.Tag, token: TokenIndex) !*Node { + const result = try p.arena.allocator.create(Node.OneToken); + result.* = .{ + .base = .{ .tag = tag }, .token = token, }; return &result.base; @@ -3081,8 +3090,9 @@ const Parser = struct { fn parseStringLiteralSingle(p: *Parser) !?*Node { if (p.eatToken(.StringLiteral)) |token| { - const node = try p.arena.allocator.create(Node.StringLiteral); + const node = try p.arena.allocator.create(Node.OneToken); node.* = .{ + .base = .{ .tag = .StringLiteral }, .token = token, }; return &node.base; @@ -3131,8 +3141,9 @@ const Parser = struct { fn parseIntegerLiteral(p: *Parser) !?*Node { const token = p.eatToken(.IntegerLiteral) orelse return null; - const node = try p.arena.allocator.create(Node.IntegerLiteral); + const node = try p.arena.allocator.create(Node.OneToken); node.* = .{ + .base = .{ .tag = .IntegerLiteral }, .token = token, }; return &node.base; @@ -3140,8 +3151,9 @@ const Parser = struct { fn parseFloatLiteral(p: *Parser) !?*Node { const token = p.eatToken(.FloatLiteral) orelse return null; - const node = try p.arena.allocator.create(Node.FloatLiteral); + const node = try p.arena.allocator.create(Node.OneToken); node.* = .{ + .base = .{ .tag = .FloatLiteral }, .token = token, }; return &node.base; diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 3b3fbd10b7..454ddde160 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -366,10 +366,32 @@ fn renderExpression( space: Space, ) (@TypeOf(stream).Error || Error)!void { switch (base.tag) { - .Identifier => { - const identifier = @fieldParentPtr(ast.Node.Identifier, "base", base); - return renderToken(tree, stream, identifier.token, indent, start_col, space); + .Identifier, + .IntegerLiteral, + .FloatLiteral, + .StringLiteral, + .CharLiteral, + .BoolLiteral, + .NullLiteral, + .Unreachable, + .ErrorType, + .UndefinedLiteral, + => { + const casted_node = base.cast(ast.Node.OneToken).?; + return renderToken(tree, stream, casted_node.token, indent, start_col, space); }, + + .AnyType => { + const any_type = base.castTag(.AnyType).?; + if (mem.eql(u8, tree.tokenSlice(any_type.token), "var")) { + // TODO remove in next release cycle + try stream.writeAll("anytype"); + if (space == .Comma) try stream.writeAll(",\n"); + return; + } + return renderToken(tree, stream, any_type.token, indent, start_col, space); + }, + .Block => { const block = @fieldParentPtr(ast.Node.Block, "base", base); @@ -399,6 +421,7 @@ fn renderExpression( return renderToken(tree, stream, block.rbrace, indent, start_col, space); } }, + .Defer => { const defer_node = @fieldParentPtr(ast.Node.Defer, "base", base); @@ -1107,50 +1130,48 @@ fn renderExpression( return renderToken(tree, stream, suffix_op.rtoken, indent, start_col, space); // ? }, - .ControlFlowExpression => { - const flow_expr = @fieldParentPtr(ast.Node.ControlFlowExpression, "base", base); + .Break => { + const flow_expr = base.castTag(.Break).?; + const maybe_rhs = flow_expr.getRHS(); + const maybe_label = flow_expr.getLabel(); - switch (flow_expr.kind) { - .Break => |maybe_label| { - if (maybe_label == null and flow_expr.rhs == null) { - return renderToken(tree, stream, flow_expr.ltoken, indent, start_col, space); // break - } - - try renderToken(tree, stream, flow_expr.ltoken, indent, start_col, Space.Space); // break - if (maybe_label) |label| { - const colon = tree.nextToken(flow_expr.ltoken); - try renderToken(tree, stream, colon, indent, start_col, Space.None); // : - - if (flow_expr.rhs == null) { - return renderExpression(allocator, stream, tree, indent, start_col, label, space); // label - } - try renderExpression(allocator, stream, tree, indent, start_col, label, Space.Space); // label - } - }, - .Continue => |maybe_label| { - assert(flow_expr.rhs == null); - - if (maybe_label == null and flow_expr.rhs == null) { - return renderToken(tree, stream, flow_expr.ltoken, indent, start_col, space); // continue - } - - try renderToken(tree, stream, flow_expr.ltoken, indent, start_col, Space.Space); // continue - if (maybe_label) |label| { - const colon = tree.nextToken(flow_expr.ltoken); - try renderToken(tree, stream, colon, indent, start_col, Space.None); // : - - return renderExpression(allocator, stream, tree, indent, start_col, label, space); - } - }, - .Return => { - if (flow_expr.rhs == null) { - return renderToken(tree, stream, flow_expr.ltoken, indent, start_col, space); - } - try renderToken(tree, stream, flow_expr.ltoken, indent, start_col, Space.Space); - }, + if (maybe_label == null and maybe_rhs == null) { + return renderToken(tree, stream, flow_expr.ltoken, indent, start_col, space); // break } - return renderExpression(allocator, stream, tree, indent, start_col, flow_expr.rhs.?, space); + try renderToken(tree, stream, flow_expr.ltoken, indent, start_col, Space.Space); // break + if (maybe_label) |label| { + const colon = tree.nextToken(flow_expr.ltoken); + try renderToken(tree, stream, colon, indent, start_col, Space.None); // : + + if (maybe_rhs == null) { + return renderToken(tree, stream, label, indent, start_col, space); // label + } + try renderToken(tree, stream, label, indent, start_col, Space.Space); // label + } + return renderExpression(allocator, stream, tree, indent, start_col, maybe_rhs.?, space); + }, + + .Continue => { + const flow_expr = base.castTag(.Continue).?; + if (flow_expr.getLabel()) |label| { + try renderToken(tree, stream, flow_expr.ltoken, indent, start_col, Space.Space); // continue + const colon = tree.nextToken(flow_expr.ltoken); + try renderToken(tree, stream, colon, indent, start_col, Space.None); // : + return renderToken(tree, stream, label, indent, start_col, space); // label + } else { + return renderToken(tree, stream, flow_expr.ltoken, indent, start_col, space); // continue + } + }, + + .Return => { + const flow_expr = base.castTag(.Return).?; + if (flow_expr.getRHS()) |rhs| { + try renderToken(tree, stream, flow_expr.ltoken, indent, start_col, Space.Space); + return renderExpression(allocator, stream, tree, indent, start_col, rhs, space); + } else { + return renderToken(tree, stream, flow_expr.ltoken, indent, start_col, space); + } }, .Payload => { @@ -1208,48 +1229,6 @@ fn renderExpression( return renderExpression(allocator, stream, tree, indent, start_col, field_init.expr, space); }, - .IntegerLiteral => { - const integer_literal = @fieldParentPtr(ast.Node.IntegerLiteral, "base", base); - return renderToken(tree, stream, integer_literal.token, indent, start_col, space); - }, - .FloatLiteral => { - const float_literal = @fieldParentPtr(ast.Node.FloatLiteral, "base", base); - return renderToken(tree, stream, float_literal.token, indent, start_col, space); - }, - .StringLiteral => { - const string_literal = @fieldParentPtr(ast.Node.StringLiteral, "base", base); - return renderToken(tree, stream, string_literal.token, indent, start_col, space); - }, - .CharLiteral => { - const char_literal = @fieldParentPtr(ast.Node.CharLiteral, "base", base); - return renderToken(tree, stream, char_literal.token, indent, start_col, space); - }, - .BoolLiteral => { - const bool_literal = @fieldParentPtr(ast.Node.CharLiteral, "base", base); - return renderToken(tree, stream, bool_literal.token, indent, start_col, space); - }, - .NullLiteral => { - const null_literal = @fieldParentPtr(ast.Node.NullLiteral, "base", base); - return renderToken(tree, stream, null_literal.token, indent, start_col, space); - }, - .Unreachable => { - const unreachable_node = @fieldParentPtr(ast.Node.Unreachable, "base", base); - return renderToken(tree, stream, unreachable_node.token, indent, start_col, space); - }, - .ErrorType => { - const error_type = @fieldParentPtr(ast.Node.ErrorType, "base", base); - return renderToken(tree, stream, error_type.token, indent, start_col, space); - }, - .AnyType => { - const any_type = @fieldParentPtr(ast.Node.AnyType, "base", base); - if (mem.eql(u8, tree.tokenSlice(any_type.token), "var")) { - // TODO remove in next release cycle - try stream.writeAll("anytype"); - if (space == .Comma) try stream.writeAll(",\n"); - return; - } - return renderToken(tree, stream, any_type.token, indent, start_col, space); - }, .ContainerDecl => { const container_decl = @fieldParentPtr(ast.Node.ContainerDecl, "base", base); @@ -1468,10 +1447,6 @@ fn renderExpression( } try stream.writeByteNTimes(' ', indent); }, - .UndefinedLiteral => { - const undefined_literal = @fieldParentPtr(ast.Node.UndefinedLiteral, "base", base); - return renderToken(tree, stream, undefined_literal.token, indent, start_col, space); - }, .BuiltinCall => { const builtin_call = @fieldParentPtr(ast.Node.BuiltinCall, "base", base); diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 13c5a065e2..62fb0df648 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -212,7 +212,8 @@ pub const Decl = struct { }, .block => unreachable, .gen_zir => unreachable, - .local_var => unreachable, + .local_val => unreachable, + .local_ptr => unreachable, .decl => unreachable, } } @@ -308,7 +309,8 @@ pub const Scope = struct { .block => return self.cast(Block).?.arena, .decl => return &self.cast(DeclAnalysis).?.arena.allocator, .gen_zir => return self.cast(GenZIR).?.arena, - .local_var => return self.cast(LocalVar).?.gen_zir.arena, + .local_val => return self.cast(LocalVal).?.gen_zir.arena, + .local_ptr => return self.cast(LocalPtr).?.gen_zir.arena, .zir_module => return &self.cast(ZIRModule).?.contents.module.arena.allocator, .file => unreachable, } @@ -320,7 +322,8 @@ pub const Scope = struct { return switch (self.tag) { .block => self.cast(Block).?.decl, .gen_zir => self.cast(GenZIR).?.decl, - .local_var => return self.cast(LocalVar).?.gen_zir.decl, + .local_val => return self.cast(LocalVal).?.gen_zir.decl, + .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl, .decl => self.cast(DeclAnalysis).?.decl, .zir_module => null, .file => null, @@ -333,7 +336,8 @@ pub const Scope = struct { switch (self.tag) { .block => return self.cast(Block).?.decl.scope, .gen_zir => return self.cast(GenZIR).?.decl.scope, - .local_var => return self.cast(LocalVar).?.gen_zir.decl.scope, + .local_val => return self.cast(LocalVal).?.gen_zir.decl.scope, + .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.scope, .decl => return self.cast(DeclAnalysis).?.decl.scope, .zir_module, .file => return self, } @@ -346,7 +350,8 @@ pub const Scope = struct { switch (self.tag) { .block => unreachable, .gen_zir => unreachable, - .local_var => unreachable, + .local_val => unreachable, + .local_ptr => unreachable, .decl => unreachable, .zir_module => return self.cast(ZIRModule).?.fullyQualifiedNameHash(name), .file => return self.cast(File).?.fullyQualifiedNameHash(name), @@ -361,7 +366,8 @@ pub const Scope = struct { .decl => return self.cast(DeclAnalysis).?.decl.scope.cast(File).?.contents.tree, .block => return self.cast(Block).?.decl.scope.cast(File).?.contents.tree, .gen_zir => return self.cast(GenZIR).?.decl.scope.cast(File).?.contents.tree, - .local_var => return self.cast(LocalVar).?.gen_zir.decl.scope.cast(File).?.contents.tree, + .local_val => return self.cast(LocalVal).?.gen_zir.decl.scope.cast(File).?.contents.tree, + .local_ptr => return self.cast(LocalPtr).?.gen_zir.decl.scope.cast(File).?.contents.tree, } } @@ -370,7 +376,8 @@ pub const Scope = struct { return switch (self.tag) { .block => unreachable, .gen_zir => self.cast(GenZIR).?, - .local_var => return self.cast(LocalVar).?.gen_zir, + .local_val => return self.cast(LocalVal).?.gen_zir, + .local_ptr => return self.cast(LocalPtr).?.gen_zir, .decl => unreachable, .zir_module => unreachable, .file => unreachable, @@ -397,7 +404,8 @@ pub const Scope = struct { .zir_module => return @fieldParentPtr(ZIRModule, "base", base).sub_file_path, .block => unreachable, .gen_zir => unreachable, - .local_var => unreachable, + .local_val => unreachable, + .local_ptr => unreachable, .decl => unreachable, } } @@ -408,7 +416,8 @@ pub const Scope = struct { .zir_module => return @fieldParentPtr(ZIRModule, "base", base).unload(gpa), .block => unreachable, .gen_zir => unreachable, - .local_var => unreachable, + .local_val => unreachable, + .local_ptr => unreachable, .decl => unreachable, } } @@ -418,7 +427,8 @@ pub const Scope = struct { .file => return @fieldParentPtr(File, "base", base).getSource(module), .zir_module => return @fieldParentPtr(ZIRModule, "base", base).getSource(module), .gen_zir => unreachable, - .local_var => unreachable, + .local_val => unreachable, + .local_ptr => unreachable, .block => unreachable, .decl => unreachable, } @@ -431,7 +441,8 @@ pub const Scope = struct { .zir_module => return @fieldParentPtr(ZIRModule, "base", base).removeDecl(child), .block => unreachable, .gen_zir => unreachable, - .local_var => unreachable, + .local_val => unreachable, + .local_ptr => unreachable, .decl => unreachable, } } @@ -451,7 +462,8 @@ pub const Scope = struct { }, .block => unreachable, .gen_zir => unreachable, - .local_var => unreachable, + .local_val => unreachable, + .local_ptr => unreachable, .decl => unreachable, } } @@ -472,7 +484,8 @@ pub const Scope = struct { block, decl, gen_zir, - local_var, + local_val, + local_ptr, }; pub const File = struct { @@ -708,17 +721,31 @@ pub const Scope = struct { instructions: std.ArrayListUnmanaged(*zir.Inst) = .{}, }; + /// This is always a `const` local and importantly the `inst` is a value type, not a pointer. /// This structure lives as long as the AST generation of the Block /// node that contains the variable. - pub const LocalVar = struct { - pub const base_tag: Tag = .local_var; + pub const LocalVal = struct { + pub const base_tag: Tag = .local_val; base: Scope = Scope{ .tag = base_tag }, - /// Parents can be: `LocalVar`, `GenZIR`. + /// Parents can be: `LocalVal`, `LocalPtr`, `GenZIR`. parent: *Scope, gen_zir: *GenZIR, name: []const u8, inst: *zir.Inst, }; + + /// This could be a `const` or `var` local. It has a pointer instead of a value. + /// This structure lives as long as the AST generation of the Block + /// node that contains the variable. + pub const LocalPtr = struct { + pub const base_tag: Tag = .local_ptr; + base: Scope = Scope{ .tag = base_tag }, + /// Parents can be: `LocalVal`, `LocalPtr`, `GenZIR`. + parent: *Scope, + gen_zir: *GenZIR, + name: []const u8, + ptr: *zir.Inst, + }; }; pub const AllErrors = struct { @@ -1176,12 +1203,19 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { const param_decls = fn_proto.params(); const param_types = try fn_type_scope.arena.alloc(*zir.Inst, param_decls.len); + + const fn_src = tree.token_locs[fn_proto.fn_token].start; + const type_type = try self.addZIRInstConst(&fn_type_scope.base, fn_src, .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.type_type), + }); + const type_type_rl: astgen.ResultLoc = .{ .ty = type_type }; for (param_decls) |param_decl, i| { const param_type_node = switch (param_decl.param_type) { .any_type => |node| return self.failNode(&fn_type_scope.base, node, "TODO implement anytype parameter", .{}), .type_expr => |node| node, }; - param_types[i] = try astgen.expr(self, &fn_type_scope.base, param_type_node); + param_types[i] = try astgen.expr(self, &fn_type_scope.base, type_type_rl, param_type_node); } if (fn_proto.getTrailer("var_args_token")) |var_args_token| { return self.failTok(&fn_type_scope.base, var_args_token, "TODO implement var args", .{}); @@ -1209,8 +1243,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .Invalid => |tok| return self.failTok(&fn_type_scope.base, tok, "unable to parse return type", .{}), }; - const return_type_inst = try astgen.expr(self, &fn_type_scope.base, return_type_expr); - const fn_src = tree.token_locs[fn_proto.fn_token].start; + const return_type_inst = try astgen.expr(self, &fn_type_scope.base, type_type_rl, return_type_expr); const fn_type_inst = try self.addZIRInst(&fn_type_scope.base, fn_src, zir.Inst.FnType, .{ .return_type = return_type_inst, .param_types = param_types, @@ -1266,7 +1299,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .kw_args = .{}, }; gen_scope.instructions.items[i] = &arg.base; - const sub_scope = try gen_scope_arena.allocator.create(Scope.LocalVar); + const sub_scope = try gen_scope_arena.allocator.create(Scope.LocalVal); sub_scope.* = .{ .parent = params_scope, .gen_zir = &gen_scope, @@ -1829,6 +1862,7 @@ fn resolveInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*In return self.analyzeDeref(scope, old_inst.src, decl_ref, old_inst.src); } +/// TODO split this into `requireRuntimeBlock` and `requireFunctionBlock` and audit callsites. fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block { return scope.cast(Scope.Block) orelse return self.fail(scope, src, "instruction illegal outside function body", .{}); @@ -2098,12 +2132,7 @@ pub fn addZIRInstSpecial( return inst; } -pub fn addZIRNoOp( - self: *Module, - scope: *Scope, - src: usize, - tag: zir.Inst.Tag, -) !*zir.Inst { +pub fn addZIRNoOpT(self: *Module, scope: *Scope, src: usize, tag: zir.Inst.Tag) !*zir.Inst.NoOp { const gen_zir = scope.getGenZIR(); try gen_zir.instructions.ensureCapacity(self.gpa, gen_zir.instructions.items.len + 1); const inst = try gen_zir.arena.create(zir.Inst.NoOp); @@ -2116,6 +2145,11 @@ pub fn addZIRNoOp( .kw_args = .{}, }; gen_zir.instructions.appendAssumeCapacity(&inst.base); + return inst; +} + +pub fn addZIRNoOp(self: *Module, scope: *Scope, src: usize, tag: zir.Inst.Tag) !*zir.Inst { + const inst = try self.addZIRNoOpT(scope, src, tag); return &inst.base; } @@ -2320,24 +2354,36 @@ fn analyzeInstConst(self: *Module, scope: *Scope, const_inst: *zir.Inst.Const) I fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst { switch (old_inst.tag) { + .alloc => return self.analyzeInstAlloc(scope, old_inst.castTag(.alloc).?), + .alloc_inferred => return self.analyzeInstAllocInferred(scope, old_inst.castTag(.alloc_inferred).?), .arg => return self.analyzeInstArg(scope, old_inst.castTag(.arg).?), + .bitcast_result_ptr => return self.analyzeInstBitCastResultPtr(scope, old_inst.castTag(.bitcast_result_ptr).?), .block => return self.analyzeInstBlock(scope, old_inst.castTag(.block).?), .@"break" => return self.analyzeInstBreak(scope, old_inst.castTag(.@"break").?), .breakpoint => return self.analyzeInstBreakpoint(scope, old_inst.castTag(.breakpoint).?), .breakvoid => return self.analyzeInstBreakVoid(scope, old_inst.castTag(.breakvoid).?), .call => return self.analyzeInstCall(scope, old_inst.castTag(.call).?), + .coerce_result_block_ptr => return self.analyzeInstCoerceResultBlockPtr(scope, old_inst.castTag(.coerce_result_block_ptr).?), + .coerce_result_ptr => return self.analyzeInstCoerceResultPtr(scope, old_inst.castTag(.coerce_result_ptr).?), + .coerce_to_ptr_elem => return self.analyzeInstCoerceToPtrElem(scope, old_inst.castTag(.coerce_to_ptr_elem).?), .compileerror => return self.analyzeInstCompileError(scope, old_inst.castTag(.compileerror).?), .@"const" => return self.analyzeInstConst(scope, old_inst.castTag(.@"const").?), .declref => return self.analyzeInstDeclRef(scope, old_inst.castTag(.declref).?), .declref_str => return self.analyzeInstDeclRefStr(scope, old_inst.castTag(.declref_str).?), .declval => return self.analyzeInstDeclVal(scope, old_inst.castTag(.declval).?), .declval_in_module => return self.analyzeInstDeclValInModule(scope, old_inst.castTag(.declval_in_module).?), + .ensure_result_used => return self.analyzeInstEnsureResultUsed(scope, old_inst.castTag(.ensure_result_used).?), + .ensure_result_non_error => return self.analyzeInstEnsureResultNonError(scope, old_inst.castTag(.ensure_result_non_error).?), + .ret_ptr => return self.analyzeInstRetPtr(scope, old_inst.castTag(.ret_ptr).?), + .ret_type => return self.analyzeInstRetType(scope, old_inst.castTag(.ret_type).?), + .store => return self.analyzeInstStore(scope, old_inst.castTag(.store).?), .str => return self.analyzeInstStr(scope, old_inst.castTag(.str).?), .int => { const big_int = old_inst.castTag(.int).?.positionals.int; return self.constIntBig(scope, old_inst.src, Type.initTag(.comptime_int), big_int); }, .inttype => return self.analyzeInstIntType(scope, old_inst.castTag(.inttype).?), + .param_type => return self.analyzeInstParamType(scope, old_inst.castTag(.param_type).?), .ptrtoint => return self.analyzeInstPtrToInt(scope, old_inst.castTag(.ptrtoint).?), .fieldptr => return self.analyzeInstFieldPtr(scope, old_inst.castTag(.fieldptr).?), .deref => return self.analyzeInstDeref(scope, old_inst.castTag(.deref).?), @@ -2369,6 +2415,94 @@ fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*In } } +fn analyzeInstCoerceResultBlockPtr( + self: *Module, + scope: *Scope, + inst: *zir.Inst.CoerceResultBlockPtr, +) InnerError!*Inst { + return self.fail(scope, inst.base.src, "TODO implement analyzeInstCoerceResultBlockPtr", .{}); +} + +fn analyzeInstBitCastResultPtr(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { + return self.fail(scope, inst.base.src, "TODO implement analyzeInstBitCastResultPtr", .{}); +} + +fn analyzeInstCoerceResultPtr(self: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { + return self.fail(scope, inst.base.src, "TODO implement analyzeInstCoerceResultPtr", .{}); +} + +fn analyzeInstCoerceToPtrElem(self: *Module, scope: *Scope, inst: *zir.Inst.CoerceToPtrElem) InnerError!*Inst { + return self.fail(scope, inst.base.src, "TODO implement analyzeInstCoerceToPtrElem", .{}); +} + +fn analyzeInstRetPtr(self: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { + return self.fail(scope, inst.base.src, "TODO implement analyzeInstRetPtr", .{}); +} + +fn analyzeInstRetType(self: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { + const b = try self.requireRuntimeBlock(scope, inst.base.src); + const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty; + const ret_type = fn_ty.fnReturnType(); + return self.constType(scope, inst.base.src, ret_type); +} + +fn analyzeInstEnsureResultUsed(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { + const operand = try self.resolveInst(scope, inst.positionals.operand); + switch (operand.ty.zigTypeTag()) { + .Void, .NoReturn => return self.constVoid(scope, operand.src), + else => return self.fail(scope, operand.src, "expression value is ignored", .{}), + } +} + +fn analyzeInstEnsureResultNonError(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { + const operand = try self.resolveInst(scope, inst.positionals.operand); + switch (operand.ty.zigTypeTag()) { + .ErrorSet, .ErrorUnion => return self.fail(scope, operand.src, "error is discarded", .{}), + else => return self.constVoid(scope, operand.src), + } +} + +fn analyzeInstAlloc(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { + return self.fail(scope, inst.base.src, "TODO implement analyzeInstAlloc", .{}); +} + +fn analyzeInstAllocInferred(self: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { + return self.fail(scope, inst.base.src, "TODO implement analyzeInstAllocInferred", .{}); +} + +fn analyzeInstStore(self: *Module, scope: *Scope, inst: *zir.Inst.Store) InnerError!*Inst { + return self.fail(scope, inst.base.src, "TODO implement analyzeInstStore", .{}); +} + +fn analyzeInstParamType(self: *Module, scope: *Scope, inst: *zir.Inst.ParamType) InnerError!*Inst { + const fn_inst = try self.resolveInst(scope, inst.positionals.func); + const arg_index = inst.positionals.arg_index; + + const fn_ty: Type = switch (fn_inst.ty.zigTypeTag()) { + .Fn => fn_inst.ty, + .BoundFn => { + return self.fail(scope, fn_inst.src, "TODO implement analyzeInstParamType for method call syntax", .{}); + }, + else => { + return self.fail(scope, fn_inst.src, "expected function, found '{}'", .{fn_inst.ty}); + }, + }; + + // TODO support C-style var args + const param_count = fn_ty.fnParamLen(); + if (arg_index >= param_count) { + return self.fail(scope, inst.base.src, "arg index {} out of bounds; '{}' has {} arguments", .{ + arg_index, + fn_ty, + param_count, + }); + } + + // TODO support generic functions + const param_type = fn_ty.fnParamType(arg_index); + return self.constType(scope, inst.base.src, param_type); +} + fn analyzeInstStr(self: *Module, scope: *Scope, str_inst: *zir.Inst.Str) InnerError!*Inst { // The bytes references memory inside the ZIR module, which can get deallocated // after semantic analysis is complete. We need the memory to be in the new anonymous Decl's arena. @@ -2746,13 +2880,13 @@ fn analyzeInstPrimitive(self: *Module, scope: *Scope, primitive: *zir.Inst.Primi return self.constInst(scope, primitive.base.src, primitive.positionals.tag.toTypedValue()); } -fn analyzeInstAs(self: *Module, scope: *Scope, as: *zir.Inst.As) InnerError!*Inst { - const dest_type = try self.resolveType(scope, as.positionals.dest_type); - const new_inst = try self.resolveInst(scope, as.positionals.value); +fn analyzeInstAs(self: *Module, scope: *Scope, as: *zir.Inst.BinOp) InnerError!*Inst { + const dest_type = try self.resolveType(scope, as.positionals.lhs); + const new_inst = try self.resolveInst(scope, as.positionals.rhs); return self.coerce(scope, dest_type, new_inst); } -fn analyzeInstPtrToInt(self: *Module, scope: *Scope, ptrtoint: *zir.Inst.PtrToInt) InnerError!*Inst { +fn analyzeInstPtrToInt(self: *Module, scope: *Scope, ptrtoint: *zir.Inst.UnOp) InnerError!*Inst { const ptr = try self.resolveInst(scope, ptrtoint.positionals.operand); if (ptr.ty.zigTypeTag() != .Pointer) { return self.fail(scope, ptrtoint.positionals.operand.src, "expected pointer, found '{}'", .{ptr.ty}); @@ -2797,16 +2931,16 @@ fn analyzeInstFieldPtr(self: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPt } } -fn analyzeInstIntCast(self: *Module, scope: *Scope, inst: *zir.Inst.IntCast) InnerError!*Inst { - const dest_type = try self.resolveType(scope, inst.positionals.dest_type); - const operand = try self.resolveInst(scope, inst.positionals.operand); +fn analyzeInstIntCast(self: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { + const dest_type = try self.resolveType(scope, inst.positionals.lhs); + const operand = try self.resolveInst(scope, inst.positionals.rhs); const dest_is_comptime_int = switch (dest_type.zigTypeTag()) { .ComptimeInt => true, .Int => false, else => return self.fail( scope, - inst.positionals.dest_type.src, + inst.positionals.lhs.src, "expected integer type, found '{}'", .{ dest_type, @@ -2818,7 +2952,7 @@ fn analyzeInstIntCast(self: *Module, scope: *Scope, inst: *zir.Inst.IntCast) Inn .ComptimeInt, .Int => {}, else => return self.fail( scope, - inst.positionals.operand.src, + inst.positionals.rhs.src, "expected integer type, found '{}'", .{operand.ty}, ), @@ -2833,22 +2967,22 @@ fn analyzeInstIntCast(self: *Module, scope: *Scope, inst: *zir.Inst.IntCast) Inn return self.fail(scope, inst.base.src, "TODO implement analyze widen or shorten int", .{}); } -fn analyzeInstBitCast(self: *Module, scope: *Scope, inst: *zir.Inst.BitCast) InnerError!*Inst { - const dest_type = try self.resolveType(scope, inst.positionals.dest_type); - const operand = try self.resolveInst(scope, inst.positionals.operand); +fn analyzeInstBitCast(self: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { + const dest_type = try self.resolveType(scope, inst.positionals.lhs); + const operand = try self.resolveInst(scope, inst.positionals.rhs); return self.bitcast(scope, dest_type, operand); } -fn analyzeInstFloatCast(self: *Module, scope: *Scope, inst: *zir.Inst.FloatCast) InnerError!*Inst { - const dest_type = try self.resolveType(scope, inst.positionals.dest_type); - const operand = try self.resolveInst(scope, inst.positionals.operand); +fn analyzeInstFloatCast(self: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { + const dest_type = try self.resolveType(scope, inst.positionals.lhs); + const operand = try self.resolveInst(scope, inst.positionals.rhs); const dest_is_comptime_float = switch (dest_type.zigTypeTag()) { .ComptimeFloat => true, .Float => false, else => return self.fail( scope, - inst.positionals.dest_type.src, + inst.positionals.lhs.src, "expected float type, found '{}'", .{ dest_type, @@ -2860,7 +2994,7 @@ fn analyzeInstFloatCast(self: *Module, scope: *Scope, inst: *zir.Inst.FloatCast) .ComptimeFloat, .Float, .ComptimeInt => {}, else => return self.fail( scope, - inst.positionals.operand.src, + inst.positionals.rhs.src, "expected float type, found '{}'", .{operand.ty}, ), @@ -3560,8 +3694,14 @@ fn failWithOwnedErrorMsg(self: *Module, scope: *Scope, src: usize, err_msg: *Err gen_zir.decl.generation = self.generation; self.failed_decls.putAssumeCapacityNoClobber(gen_zir.decl, err_msg); }, - .local_var => { - const gen_zir = scope.cast(Scope.LocalVar).?.gen_zir; + .local_val => { + const gen_zir = scope.cast(Scope.LocalVal).?.gen_zir; + gen_zir.decl.analysis = .sema_failure; + gen_zir.decl.generation = self.generation; + self.failed_decls.putAssumeCapacityNoClobber(gen_zir.decl, err_msg); + }, + .local_ptr => { + const gen_zir = scope.cast(Scope.LocalPtr).?.gen_zir; gen_zir.decl.analysis = .sema_failure; gen_zir.decl.generation = self.generation; self.failed_decls.putAssumeCapacityNoClobber(gen_zir.decl, err_msg); diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig index 2a5153448c..8893ac0304 100644 --- a/src-self-hosted/astgen.zig +++ b/src-self-hosted/astgen.zig @@ -1,5 +1,6 @@ const std = @import("std"); const mem = std.mem; +const Allocator = std.mem.Allocator; const Value = @import("value.zig").Value; const Type = @import("type.zig").Type; const TypedValue = @import("TypedValue.zig"); @@ -11,35 +12,67 @@ const trace = @import("tracy.zig").trace; const Scope = Module.Scope; const InnerError = Module.InnerError; +pub const ResultLoc = union(enum) { + /// The expression is the right-hand side of assignment to `_`. + discard, + /// The expression has an inferred type, and it will be evaluated as an rvalue. + none, + /// The expression will be type coerced into this type, but it will be evaluated as an rvalue. + ty: *zir.Inst, + /// The expression must store its result into this typed pointer. + ptr: *zir.Inst, + /// The expression must store its result into this allocation, which has an inferred type. + inferred_ptr: *zir.Inst.Tag.alloc_inferred.Type(), + /// The expression must store its result into this pointer, which is a typed pointer that + /// has been bitcasted to whatever the expression's type is. + bitcasted_ptr: *zir.Inst.UnOp, + /// There is a pointer for the expression to store its result into, however, its type + /// is inferred based on peer type resolution for a `zir.Inst.Block`. + block_ptr: *zir.Inst.Block, +}; + +pub fn typeExpr(mod: *Module, scope: *Scope, type_node: *ast.Node) InnerError!*zir.Inst { + const type_src = scope.tree().token_locs[type_node.firstToken()].start; + const type_type = try mod.addZIRInstConst(scope, type_src, .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.type_type), + }); + const type_rl: ResultLoc = .{ .ty = type_type }; + return expr(mod, scope, type_rl, type_node); +} + /// Turn Zig AST into untyped ZIR istructions. -pub fn expr(mod: *Module, scope: *Scope, node: *ast.Node) InnerError!*zir.Inst { +pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerError!*zir.Inst { switch (node.tag) { .VarDecl => unreachable, // Handled in `blockExpr`. + .Assign => unreachable, // Handled in `blockExpr`. - .Add => return simpleInfixOp(mod, scope, node.castTag(.Add).?, .add), - .Sub => return simpleInfixOp(mod, scope, node.castTag(.Sub).?, .sub), - .BangEqual => return simpleInfixOp(mod, scope, node.castTag(.BangEqual).?, .cmp_neq), - .EqualEqual => return simpleInfixOp(mod, scope, node.castTag(.EqualEqual).?, .cmp_eq), - .GreaterThan => return simpleInfixOp(mod, scope, node.castTag(.GreaterThan).?, .cmp_gt), - .GreaterOrEqual => return simpleInfixOp(mod, scope, node.castTag(.GreaterOrEqual).?, .cmp_gte), - .LessThan => return simpleInfixOp(mod, scope, node.castTag(.LessThan).?, .cmp_lt), - .LessOrEqual => return simpleInfixOp(mod, scope, node.castTag(.LessOrEqual).?, .cmp_lte), + .Add => return arithmetic(mod, scope, rl, node.castTag(.Add).?, .add), + .Sub => return arithmetic(mod, scope, rl, node.castTag(.Sub).?, .sub), - .Identifier => return identifier(mod, scope, node.castTag(.Identifier).?), - .Asm => return assembly(mod, scope, node.castTag(.Asm).?), - .StringLiteral => return stringLiteral(mod, scope, node.castTag(.StringLiteral).?), - .IntegerLiteral => return integerLiteral(mod, scope, node.castTag(.IntegerLiteral).?), - .BuiltinCall => return builtinCall(mod, scope, node.castTag(.BuiltinCall).?), - .Call => return callExpr(mod, scope, node.castTag(.Call).?), + .BangEqual => return cmp(mod, scope, rl, node.castTag(.BangEqual).?, .cmp_neq), + .EqualEqual => return cmp(mod, scope, rl, node.castTag(.EqualEqual).?, .cmp_eq), + .GreaterThan => return cmp(mod, scope, rl, node.castTag(.GreaterThan).?, .cmp_gt), + .GreaterOrEqual => return cmp(mod, scope, rl, node.castTag(.GreaterOrEqual).?, .cmp_gte), + .LessThan => return cmp(mod, scope, rl, node.castTag(.LessThan).?, .cmp_lt), + .LessOrEqual => return cmp(mod, scope, rl, node.castTag(.LessOrEqual).?, .cmp_lte), + + .Identifier => return rlWrap(mod, scope, rl, try identifier(mod, scope, node.castTag(.Identifier).?)), + .Asm => return rlWrap(mod, scope, rl, try assembly(mod, scope, node.castTag(.Asm).?)), + .StringLiteral => return rlWrap(mod, scope, rl, try stringLiteral(mod, scope, node.castTag(.StringLiteral).?)), + .IntegerLiteral => return rlWrap(mod, scope, rl, try integerLiteral(mod, scope, node.castTag(.IntegerLiteral).?)), + .BuiltinCall => return builtinCall(mod, scope, rl, node.castTag(.BuiltinCall).?), + .Call => return callExpr(mod, scope, rl, node.castTag(.Call).?), .Unreachable => return unreach(mod, scope, node.castTag(.Unreachable).?), - .ControlFlowExpression => return controlFlowExpr(mod, scope, node.castTag(.ControlFlowExpression).?), - .If => return ifExpr(mod, scope, node.castTag(.If).?), - .Assign => return assign(mod, scope, node.castTag(.Assign).?), - .Period => return field(mod, scope, node.castTag(.Period).?), - .Deref => return deref(mod, scope, node.castTag(.Deref).?), - .BoolNot => return boolNot(mod, scope, node.castTag(.BoolNot).?), - .FloatLiteral => return floatLiteral(mod, scope, node.castTag(.FloatLiteral).?), - .UndefinedLiteral, .BoolLiteral, .NullLiteral => return primitiveLiteral(mod, scope, node), + .Return => return ret(mod, scope, node.castTag(.Return).?), + .If => return ifExpr(mod, scope, rl, node.castTag(.If).?), + .Period => return rlWrap(mod, scope, rl, try field(mod, scope, node.castTag(.Period).?)), + .Deref => return rlWrap(mod, scope, rl, try deref(mod, scope, node.castTag(.Deref).?)), + .BoolNot => return rlWrap(mod, scope, rl, try boolNot(mod, scope, node.castTag(.BoolNot).?)), + .FloatLiteral => return rlWrap(mod, scope, rl, try floatLiteral(mod, scope, node.castTag(.FloatLiteral).?)), + .UndefinedLiteral => return rlWrap(mod, scope, rl, try undefLiteral(mod, scope, node.castTag(.UndefinedLiteral).?)), + .BoolLiteral => return rlWrap(mod, scope, rl, try boolLiteral(mod, scope, node.castTag(.BoolLiteral).?)), + .NullLiteral => return rlWrap(mod, scope, rl, try nullLiteral(mod, scope, node.castTag(.NullLiteral).?)), else => return mod.failNode(scope, node, "TODO implement astgen.Expr for {}", .{@tagName(node.tag)}), } } @@ -59,17 +92,28 @@ pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block for (block_node.statements()) |statement| { switch (statement.tag) { .VarDecl => { - const sub_scope = try block_arena.allocator.create(Scope.LocalVar); - const var_decl_node = @fieldParentPtr(ast.Node.VarDecl, "base", statement); - sub_scope.* = try varDecl(mod, scope, var_decl_node); - scope = &sub_scope.base; + const var_decl_node = statement.castTag(.VarDecl).?; + scope = try varDecl(mod, scope, var_decl_node, &block_arena.allocator); + }, + .Assign => { + const ass = statement.castTag(.Assign).?; + try assign(mod, scope, ass); + }, + else => { + const possibly_unused_result = try expr(mod, scope, .none, statement); + const src = scope.tree().token_locs[statement.firstToken()].start; + _ = try mod.addZIRUnOp(scope, src, .ensure_result_used, possibly_unused_result); }, - else => _ = try expr(mod, scope, statement), } } } -fn varDecl(mod: *Module, scope: *Scope, node: *ast.Node.VarDecl) InnerError!Scope.LocalVar { +fn varDecl( + mod: *Module, + scope: *Scope, + node: *ast.Node.VarDecl, + block_arena: *Allocator, +) InnerError!*Scope { // TODO implement detection of shadowing if (node.getTrailer("comptime_token")) |comptime_token| { return mod.failTok(scope, comptime_token, "TODO implement comptime locals", .{}); @@ -78,48 +122,96 @@ fn varDecl(mod: *Module, scope: *Scope, node: *ast.Node.VarDecl) InnerError!Scop return mod.failNode(scope, align_node, "TODO implement alignment on locals", .{}); } const tree = scope.tree(); + const name_src = tree.token_locs[node.name_token].start; + const ident_name = try identifierTokenString(mod, scope, node.name_token); + const init_node = node.getTrailer("init_node").?; switch (tree.token_ids[node.mut_token]) { .Keyword_const => { - if (node.getTrailer("type_node")) |type_node| { - return mod.failNode(scope, type_node, "TODO implement typed const locals", .{}); - } // Depending on the type of AST the initialization expression is, we may need an lvalue // or an rvalue as a result location. If it is an rvalue, we can use the instruction as // the variable, no memory location needed. - const init_node = node.getTrailer("init_node").?; if (nodeMayNeedMemoryLocation(init_node)) { - return mod.failNode(scope, init_node, "TODO implement result locations", .{}); + if (node.getTrailer("type_node")) |type_node| { + const type_inst = try typeExpr(mod, scope, type_node); + const alloc = try mod.addZIRUnOp(scope, name_src, .alloc, type_inst); + const result_loc: ResultLoc = .{ .ptr = alloc }; + const init_inst = try expr(mod, scope, result_loc, init_node); + const sub_scope = try block_arena.create(Scope.LocalVal); + sub_scope.* = .{ + .parent = scope, + .gen_zir = scope.getGenZIR(), + .name = ident_name, + .inst = init_inst, + }; + return &sub_scope.base; + } else { + const alloc = try mod.addZIRNoOpT(scope, name_src, .alloc_inferred); + const result_loc: ResultLoc = .{ .inferred_ptr = alloc }; + const init_inst = try expr(mod, scope, result_loc, init_node); + const sub_scope = try block_arena.create(Scope.LocalVal); + sub_scope.* = .{ + .parent = scope, + .gen_zir = scope.getGenZIR(), + .name = ident_name, + .inst = init_inst, + }; + return &sub_scope.base; + } + } else { + const result_loc: ResultLoc = if (node.getTrailer("type_node")) |type_node| + .{ .ty = try typeExpr(mod, scope, type_node) } + else + .none; + const init_inst = try expr(mod, scope, result_loc, init_node); + const sub_scope = try block_arena.create(Scope.LocalVal); + sub_scope.* = .{ + .parent = scope, + .gen_zir = scope.getGenZIR(), + .name = ident_name, + .inst = init_inst, + }; + return &sub_scope.base; } - const init_inst = try expr(mod, scope, init_node); - const ident_name = try identifierTokenString(mod, scope, node.name_token); - return Scope.LocalVar{ - .parent = scope, - .gen_zir = scope.getGenZIR(), - .name = ident_name, - .inst = init_inst, - }; }, .Keyword_var => { - return mod.failNode(scope, &node.base, "TODO implement local vars", .{}); + if (node.getTrailer("type_node")) |type_node| { + const type_inst = try typeExpr(mod, scope, type_node); + const alloc = try mod.addZIRUnOp(scope, name_src, .alloc, type_inst); + const result_loc: ResultLoc = .{ .ptr = alloc }; + const init_inst = try expr(mod, scope, result_loc, init_node); + const sub_scope = try block_arena.create(Scope.LocalPtr); + sub_scope.* = .{ + .parent = scope, + .gen_zir = scope.getGenZIR(), + .name = ident_name, + .ptr = alloc, + }; + return &sub_scope.base; + } else { + const alloc = try mod.addZIRNoOp(scope, name_src, .alloc_inferred); + const result_loc = .{ .inferred_ptr = alloc.castTag(.alloc_inferred).? }; + const init_inst = try expr(mod, scope, result_loc, init_node); + const sub_scope = try block_arena.create(Scope.LocalPtr); + sub_scope.* = .{ + .parent = scope, + .gen_zir = scope.getGenZIR(), + .name = ident_name, + .ptr = alloc, + }; + return &sub_scope.base; + } }, else => unreachable, } } -fn boolNot(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) InnerError!*zir.Inst { - const operand = try expr(mod, scope, node.rhs); - const tree = scope.tree(); - const src = tree.token_locs[node.op_token].start; - return mod.addZIRUnOp(scope, src, .boolnot, operand); -} - -fn assign(mod: *Module, scope: *Scope, infix_node: *ast.Node.SimpleInfixOp) InnerError!*zir.Inst { - if (infix_node.lhs.tag == .Identifier) { - const ident = @fieldParentPtr(ast.Node.Identifier, "base", infix_node.lhs); +fn assign(mod: *Module, scope: *Scope, infix_node: *ast.Node.SimpleInfixOp) InnerError!void { + if (infix_node.lhs.castTag(.Identifier)) |ident| { const tree = scope.tree(); const ident_name = try identifierTokenString(mod, scope, ident.token); if (std.mem.eql(u8, ident_name, "_")) { - return expr(mod, scope, infix_node.rhs); + _ = try expr(mod, scope, .discard, infix_node.rhs); + return; } else { return mod.failNode(scope, &infix_node.base, "TODO implement infix operator assign", .{}); } @@ -128,6 +220,17 @@ fn assign(mod: *Module, scope: *Scope, infix_node: *ast.Node.SimpleInfixOp) Inne } } +fn boolNot(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) InnerError!*zir.Inst { + const tree = scope.tree(); + const src = tree.token_locs[node.op_token].start; + const bool_type = try mod.addZIRInstConst(scope, src, .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.bool_type), + }); + const operand = try expr(mod, scope, .{ .ty = bool_type }, node.rhs); + return mod.addZIRUnOp(scope, src, .boolnot, operand); +} + /// Identifier token -> String (allocated in scope.arena()) pub fn identifierTokenString(mod: *Module, scope: *Scope, token: ast.TokenIndex) InnerError![]const u8 { const tree = scope.tree(); @@ -148,7 +251,7 @@ pub fn identifierTokenString(mod: *Module, scope: *Scope, token: ast.TokenIndex) return ident_name; } -pub fn identifierStringInst(mod: *Module, scope: *Scope, node: *ast.Node.Identifier) InnerError!*zir.Inst { +pub fn identifierStringInst(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError!*zir.Inst { const tree = scope.tree(); const src = tree.token_locs[node.token].start; @@ -158,10 +261,11 @@ pub fn identifierStringInst(mod: *Module, scope: *Scope, node: *ast.Node.Identif } fn field(mod: *Module, scope: *Scope, node: *ast.Node.SimpleInfixOp) InnerError!*zir.Inst { + // TODO introduce lvalues const tree = scope.tree(); const src = tree.token_locs[node.op_token].start; - const lhs = try expr(mod, scope, node.lhs); + const lhs = try expr(mod, scope, .none, node.lhs); const field_name = try identifierStringInst(mod, scope, node.rhs.castTag(.Identifier).?); const pointer = try mod.addZIRInst(scope, src, zir.Inst.FieldPtr, .{ .object_ptr = lhs, .field_name = field_name }, .{}); @@ -171,26 +275,44 @@ fn field(mod: *Module, scope: *Scope, node: *ast.Node.SimpleInfixOp) InnerError! fn deref(mod: *Module, scope: *Scope, node: *ast.Node.SimpleSuffixOp) InnerError!*zir.Inst { const tree = scope.tree(); const src = tree.token_locs[node.rtoken].start; - const lhs = try expr(mod, scope, node.lhs); + const lhs = try expr(mod, scope, .none, node.lhs); return mod.addZIRUnOp(scope, src, .deref, lhs); } -fn simpleInfixOp( +fn cmp( mod: *Module, scope: *Scope, + rl: ResultLoc, + infix_node: *ast.Node.SimpleInfixOp, + cmp_inst_tag: zir.Inst.Tag, +) InnerError!*zir.Inst { + const tree = scope.tree(); + const src = tree.token_locs[infix_node.op_token].start; + + const lhs = try expr(mod, scope, .none, infix_node.lhs); + const rhs = try expr(mod, scope, .none, infix_node.rhs); + const result = try mod.addZIRBinOp(scope, src, cmp_inst_tag, lhs, rhs); + return rlWrap(mod, scope, rl, result); +} + +fn arithmetic( + mod: *Module, + scope: *Scope, + rl: ResultLoc, infix_node: *ast.Node.SimpleInfixOp, op_inst_tag: zir.Inst.Tag, ) InnerError!*zir.Inst { - const lhs = try expr(mod, scope, infix_node.lhs); - const rhs = try expr(mod, scope, infix_node.rhs); + const lhs = try expr(mod, scope, .none, infix_node.lhs); + const rhs = try expr(mod, scope, .none, infix_node.rhs); const tree = scope.tree(); const src = tree.token_locs[infix_node.op_token].start; - return mod.addZIRBinOp(scope, src, op_inst_tag, lhs, rhs); + const result = try mod.addZIRBinOp(scope, src, op_inst_tag, lhs, rhs); + return rlWrap(mod, scope, rl, result); } -fn ifExpr(mod: *Module, scope: *Scope, if_node: *ast.Node.If) InnerError!*zir.Inst { +fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) InnerError!*zir.Inst { if (if_node.payload) |payload| { return mod.failNode(scope, payload, "TODO implement astgen.IfExpr for optionals", .{}); } @@ -207,10 +329,14 @@ fn ifExpr(mod: *Module, scope: *Scope, if_node: *ast.Node.If) InnerError!*zir.In }; defer block_scope.instructions.deinit(mod.gpa); - const cond = try expr(mod, &block_scope.base, if_node.condition); - const tree = scope.tree(); const if_src = tree.token_locs[if_node.if_token].start; + const bool_type = try mod.addZIRInstConst(scope, if_src, .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.bool_type), + }); + const cond = try expr(mod, &block_scope.base, .{ .ty = bool_type }, if_node.condition); + const condbr = try mod.addZIRInstSpecial(&block_scope.base, if_src, zir.Inst.CondBr, .{ .condition = cond, .then_body = undefined, // populated below @@ -228,7 +354,16 @@ fn ifExpr(mod: *Module, scope: *Scope, if_node: *ast.Node.If) InnerError!*zir.In }; defer then_scope.instructions.deinit(mod.gpa); - const then_result = try expr(mod, &then_scope.base, if_node.body); + // Most result location types can be forwarded directly; however + // if we need to write to a pointer which has an inferred type, + // proper type inference requires peer type resolution on the if's + // branches. + const branch_rl: ResultLoc = switch (rl) { + .discard, .none, .ty, .ptr => rl, + .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block }, + }; + + const then_result = try expr(mod, &then_scope.base, branch_rl, if_node.body); if (!then_result.tag.isNoReturn()) { const then_src = tree.token_locs[if_node.body.lastToken()].start; _ = try mod.addZIRInst(&then_scope.base, then_src, zir.Inst.Break, .{ @@ -249,7 +384,7 @@ fn ifExpr(mod: *Module, scope: *Scope, if_node: *ast.Node.If) InnerError!*zir.In defer else_scope.instructions.deinit(mod.gpa); if (if_node.@"else") |else_node| { - const else_result = try expr(mod, &else_scope.base, else_node.body); + const else_result = try expr(mod, &else_scope.base, branch_rl, else_node.body); if (!else_result.tag.isNoReturn()) { const else_src = tree.token_locs[else_node.body.lastToken()].start; _ = try mod.addZIRInst(&else_scope.base, else_src, zir.Inst.Break, .{ @@ -272,27 +407,25 @@ fn ifExpr(mod: *Module, scope: *Scope, if_node: *ast.Node.If) InnerError!*zir.In return &block.base; } -fn controlFlowExpr( - mod: *Module, - scope: *Scope, - cfe: *ast.Node.ControlFlowExpression, -) InnerError!*zir.Inst { - switch (cfe.kind) { - .Break => return mod.failNode(scope, &cfe.base, "TODO implement astgen.Expr for Break", .{}), - .Continue => return mod.failNode(scope, &cfe.base, "TODO implement astgen.Expr for Continue", .{}), - .Return => {}, - } +fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst { const tree = scope.tree(); const src = tree.token_locs[cfe.ltoken].start; - if (cfe.rhs) |rhs_node| { - const operand = try expr(mod, scope, rhs_node); - return mod.addZIRUnOp(scope, src, .@"return", operand); + if (cfe.getRHS()) |rhs_node| { + if (nodeMayNeedMemoryLocation(rhs_node)) { + const ret_ptr = try mod.addZIRNoOp(scope, src, .ret_ptr); + const operand = try expr(mod, scope, .{ .ptr = ret_ptr }, rhs_node); + return mod.addZIRUnOp(scope, src, .@"return", operand); + } else { + const fn_ret_ty = try mod.addZIRNoOp(scope, src, .ret_type); + const operand = try expr(mod, scope, .{ .ty = fn_ret_ty }, rhs_node); + return mod.addZIRUnOp(scope, src, .@"return", operand); + } } else { return mod.addZIRNoOp(scope, src, .returnvoid); } } -fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.Identifier) InnerError!*zir.Inst { +fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError!*zir.Inst { const tracy = trace(@src()); defer tracy.end(); @@ -345,12 +478,19 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.Identifier) InnerErr { var s = scope; while (true) switch (s.tag) { - .local_var => { - const local_var = s.cast(Scope.LocalVar).?; - if (mem.eql(u8, local_var.name, ident_name)) { - return local_var.inst; + .local_val => { + const local_val = s.cast(Scope.LocalVal).?; + if (mem.eql(u8, local_val.name, ident_name)) { + return local_val.inst; } - s = local_var.parent; + s = local_val.parent; + }, + .local_ptr => { + const local_ptr = s.cast(Scope.LocalPtr).?; + if (mem.eql(u8, local_ptr.name, ident_name)) { + return try mod.addZIRUnOp(scope, src, .deref, local_ptr.ptr); + } + s = local_ptr.parent; }, .gen_zir => s = s.cast(Scope.GenZIR).?.parent, else => break, @@ -364,7 +504,7 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.Identifier) InnerErr return mod.failNode(scope, &ident.base, "use of undeclared identifier '{}'", .{ident_name}); } -fn stringLiteral(mod: *Module, scope: *Scope, str_lit: *ast.Node.StringLiteral) InnerError!*zir.Inst { +fn stringLiteral(mod: *Module, scope: *Scope, str_lit: *ast.Node.OneToken) InnerError!*zir.Inst { const tree = scope.tree(); const unparsed_bytes = tree.tokenSlice(str_lit.token); const arena = scope.arena(); @@ -383,7 +523,7 @@ fn stringLiteral(mod: *Module, scope: *Scope, str_lit: *ast.Node.StringLiteral) return mod.addZIRInst(scope, src, zir.Inst.Str, .{ .bytes = bytes }, .{}); } -fn integerLiteral(mod: *Module, scope: *Scope, int_lit: *ast.Node.IntegerLiteral) InnerError!*zir.Inst { +fn integerLiteral(mod: *Module, scope: *Scope, int_lit: *ast.Node.OneToken) InnerError!*zir.Inst { const arena = scope.arena(); const tree = scope.tree(); const prefixed_bytes = tree.tokenSlice(int_lit.token); @@ -414,7 +554,7 @@ fn integerLiteral(mod: *Module, scope: *Scope, int_lit: *ast.Node.IntegerLiteral } } -fn floatLiteral(mod: *Module, scope: *Scope, float_lit: *ast.Node.FloatLiteral) InnerError!*zir.Inst { +fn floatLiteral(mod: *Module, scope: *Scope, float_lit: *ast.Node.OneToken) InnerError!*zir.Inst { const arena = scope.arena(); const tree = scope.tree(); const bytes = tree.tokenSlice(float_lit.token); @@ -434,30 +574,38 @@ fn floatLiteral(mod: *Module, scope: *Scope, float_lit: *ast.Node.FloatLiteral) }); } -fn primitiveLiteral(mod: *Module, scope: *Scope, node: *ast.Node) InnerError!*zir.Inst { +fn undefLiteral(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError!*zir.Inst { const arena = scope.arena(); const tree = scope.tree(); - const src = tree.token_locs[node.firstToken()].start; + const src = tree.token_locs[node.token].start; + return mod.addZIRInstConst(scope, src, .{ + .ty = Type.initTag(.@"undefined"), + .val = Value.initTag(.undef), + }); +} - if (node.cast(ast.Node.BoolLiteral)) |bool_node| { - return mod.addZIRInstConst(scope, src, .{ - .ty = Type.initTag(.bool), - .val = if (tree.token_ids[bool_node.token] == .Keyword_true) - Value.initTag(.bool_true) - else - Value.initTag(.bool_false), - }); - } else if (node.tag == .UndefinedLiteral) { - return mod.addZIRInstConst(scope, src, .{ - .ty = Type.initTag(.@"undefined"), - .val = Value.initTag(.undef), - }); - } else if (node.tag == .NullLiteral) { - return mod.addZIRInstConst(scope, src, .{ - .ty = Type.initTag(.@"null"), - .val = Value.initTag(.null_value), - }); - } else unreachable; +fn boolLiteral(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError!*zir.Inst { + const arena = scope.arena(); + const tree = scope.tree(); + const src = tree.token_locs[node.token].start; + return mod.addZIRInstConst(scope, src, .{ + .ty = Type.initTag(.bool), + .val = switch (tree.token_ids[node.token]) { + .Keyword_true => Value.initTag(.bool_true), + .Keyword_false => Value.initTag(.bool_false), + else => unreachable, + }, + }); +} + +fn nullLiteral(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError!*zir.Inst { + const arena = scope.arena(); + const tree = scope.tree(); + const src = tree.token_locs[node.token].start; + return mod.addZIRInstConst(scope, src, .{ + .ty = Type.initTag(.@"null"), + .val = Value.initTag(.null_value), + }); } fn assembly(mod: *Module, scope: *Scope, asm_node: *ast.Node.Asm) InnerError!*zir.Inst { @@ -470,19 +618,26 @@ fn assembly(mod: *Module, scope: *Scope, asm_node: *ast.Node.Asm) InnerError!*zi const inputs = try arena.alloc(*zir.Inst, asm_node.inputs.len); const args = try arena.alloc(*zir.Inst, asm_node.inputs.len); + const src = tree.token_locs[asm_node.asm_token].start; + + const str_type = try mod.addZIRInstConst(scope, src, .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.const_slice_u8_type), + }); + const str_type_rl: ResultLoc = .{ .ty = str_type }; + for (asm_node.inputs) |input, i| { // TODO semantically analyze constraints - inputs[i] = try expr(mod, scope, input.constraint); - args[i] = try expr(mod, scope, input.expr); + inputs[i] = try expr(mod, scope, str_type_rl, input.constraint); + args[i] = try expr(mod, scope, .none, input.expr); } - const src = tree.token_locs[asm_node.asm_token].start; const return_type = try mod.addZIRInstConst(scope, src, .{ .ty = Type.initTag(.type), .val = Value.initTag(.void_type), }); const asm_inst = try mod.addZIRInst(scope, src, zir.Inst.Asm, .{ - .asm_source = try expr(mod, scope, asm_node.template), + .asm_source = try expr(mod, scope, str_type_rl, asm_node.template), .return_type = return_type, }, .{ .@"volatile" = asm_node.volatile_token != null, @@ -493,63 +648,174 @@ fn assembly(mod: *Module, scope: *Scope, asm_node: *ast.Node.Asm) InnerError!*zi return asm_inst; } -fn builtinCall(mod: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { - const tree = scope.tree(); - const builtin_name = tree.tokenSlice(call.builtin_token); - const src = tree.token_locs[call.builtin_token].start; +fn ensureBuiltinParamCount(mod: *Module, scope: *Scope, call: *ast.Node.BuiltinCall, count: u32) !void { + if (call.params_len == count) + return; - inline for (std.meta.declarations(zir.Inst)) |inst| { - if (inst.data != .Type) continue; - const T = inst.data.Type; - if (!@hasDecl(T, "builtin_name")) continue; - if (std.mem.eql(u8, builtin_name, T.builtin_name)) { - var value: T = undefined; - const positionals = @typeInfo(std.meta.fieldInfo(T, "positionals").field_type).Struct; - if (positionals.fields.len == 0) { - return mod.addZIRInst(scope, src, T, value.positionals, value.kw_args); - } - const arg_count: ?usize = if (positionals.fields[0].field_type == []*zir.Inst) null else positionals.fields.len; - if (arg_count) |some| { - if (call.params_len != some) { - return mod.failTok( - scope, - call.builtin_token, - "expected {} parameter{}, found {}", - .{ some, if (some == 1) "" else "s", call.params_len }, - ); - } - const params = call.params(); - inline for (positionals.fields) |p, i| { - @field(value.positionals, p.name) = try expr(mod, scope, params[i]); - } - } else { - return mod.failTok(scope, call.builtin_token, "TODO var args builtin '{}'", .{builtin_name}); - } - - return mod.addZIRInst(scope, src, T, value.positionals, .{}); - } - } - return mod.failTok(scope, call.builtin_token, "TODO implement builtin call for '{}'", .{builtin_name}); + const s = if (count == 1) "" else "s"; + return mod.failTok(scope, call.builtin_token, "expected {} parameter{}, found {}", .{ count, s, call.params_len }); } -fn callExpr(mod: *Module, scope: *Scope, node: *ast.Node.Call) InnerError!*zir.Inst { +fn simpleCast( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + call: *ast.Node.BuiltinCall, + inst_tag: zir.Inst.Tag, +) InnerError!*zir.Inst { + try ensureBuiltinParamCount(mod, scope, call, 2); const tree = scope.tree(); - const lhs = try expr(mod, scope, node.lhs); + const src = tree.token_locs[call.builtin_token].start; + const type_type = try mod.addZIRInstConst(scope, src, .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.type_type), + }); + const params = call.params(); + const dest_type = try expr(mod, scope, .{ .ty = type_type }, params[0]); + const rhs = try expr(mod, scope, .none, params[1]); + const result = try mod.addZIRBinOp(scope, src, inst_tag, dest_type, rhs); + return rlWrap(mod, scope, rl, result); +} + +fn ptrToInt(mod: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { + try ensureBuiltinParamCount(mod, scope, call, 1); + const operand = try expr(mod, scope, .none, call.params()[0]); + const tree = scope.tree(); + const src = tree.token_locs[call.builtin_token].start; + return mod.addZIRUnOp(scope, src, .ptrtoint, operand); +} + +fn as(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { + try ensureBuiltinParamCount(mod, scope, call, 2); + const tree = scope.tree(); + const src = tree.token_locs[call.builtin_token].start; + const params = call.params(); + const dest_type = try typeExpr(mod, scope, params[0]); + switch (rl) { + .none => return try expr(mod, scope, .{ .ty = dest_type }, params[1]), + .discard => { + const result = try expr(mod, scope, .{ .ty = dest_type }, params[1]); + _ = try mod.addZIRUnOp(scope, result.src, .ensure_result_non_error, result); + return result; + }, + .ty => |result_ty| { + const result = try expr(mod, scope, .{ .ty = dest_type }, params[1]); + return mod.addZIRBinOp(scope, src, .as, result_ty, result); + }, + .ptr => |result_ptr| { + const casted_result_ptr = try mod.addZIRBinOp(scope, src, .coerce_result_ptr, dest_type, result_ptr); + return expr(mod, scope, .{ .ptr = casted_result_ptr }, params[1]); + }, + .bitcasted_ptr => |bitcasted_ptr| { + // TODO here we should be able to resolve the inference; we now have a type for the result. + return mod.failTok(scope, call.builtin_token, "TODO implement @as with result location @bitCast", .{}); + }, + .inferred_ptr => |result_alloc| { + // TODO here we should be able to resolve the inference; we now have a type for the result. + return mod.failTok(scope, call.builtin_token, "TODO implement @as with inferred-type result location pointer", .{}); + }, + .block_ptr => |block_ptr| { + const casted_block_ptr = try mod.addZIRInst(scope, src, zir.Inst.CoerceResultBlockPtr, .{ + .dest_type = dest_type, + .block = block_ptr, + }, .{}); + return expr(mod, scope, .{ .ptr = casted_block_ptr }, params[1]); + }, + } +} + +fn bitCast(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { + try ensureBuiltinParamCount(mod, scope, call, 2); + const tree = scope.tree(); + const src = tree.token_locs[call.builtin_token].start; + const type_type = try mod.addZIRInstConst(scope, src, .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.type_type), + }); + const params = call.params(); + const dest_type = try expr(mod, scope, .{ .ty = type_type }, params[0]); + switch (rl) { + .none => { + const operand = try expr(mod, scope, .none, params[1]); + return mod.addZIRBinOp(scope, src, .bitcast, dest_type, operand); + }, + .discard => { + const operand = try expr(mod, scope, .none, params[1]); + const result = try mod.addZIRBinOp(scope, src, .bitcast, dest_type, operand); + _ = try mod.addZIRUnOp(scope, result.src, .ensure_result_non_error, result); + return result; + }, + .ty => |result_ty| { + const result = try expr(mod, scope, .none, params[1]); + const bitcasted = try mod.addZIRBinOp(scope, src, .bitcast, dest_type, result); + return mod.addZIRBinOp(scope, src, .as, result_ty, bitcasted); + }, + .ptr => |result_ptr| { + const casted_result_ptr = try mod.addZIRUnOp(scope, src, .bitcast_result_ptr, result_ptr); + return expr(mod, scope, .{ .bitcasted_ptr = casted_result_ptr.castTag(.bitcast_result_ptr).? }, params[1]); + }, + .bitcasted_ptr => |bitcasted_ptr| { + return mod.failTok(scope, call.builtin_token, "TODO implement @bitCast with result location another @bitCast", .{}); + }, + .block_ptr => |block_ptr| { + return mod.failTok(scope, call.builtin_token, "TODO implement @bitCast with result location inferred peer types", .{}); + }, + .inferred_ptr => |result_alloc| { + // TODO here we should be able to resolve the inference; we now have a type for the result. + return mod.failTok(scope, call.builtin_token, "TODO implement @bitCast with inferred-type result location pointer", .{}); + }, + } +} + +fn builtinCall(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { + const tree = scope.tree(); + const builtin_name = tree.tokenSlice(call.builtin_token); + + // We handle the different builtins manually because they have different semantics depending + // on the function. For example, `@as` and others participate in result location semantics, + // and `@cImport` creates a special scope that collects a .c source code text buffer. + // Also, some builtins have a variable number of parameters. + + if (mem.eql(u8, builtin_name, "@ptrToInt")) { + return rlWrap(mod, scope, rl, try ptrToInt(mod, scope, call)); + } else if (mem.eql(u8, builtin_name, "@as")) { + return as(mod, scope, rl, call); + } else if (mem.eql(u8, builtin_name, "@floatCast")) { + return simpleCast(mod, scope, rl, call, .floatcast); + } else if (mem.eql(u8, builtin_name, "@intCast")) { + return simpleCast(mod, scope, rl, call, .intcast); + } else if (mem.eql(u8, builtin_name, "@bitCast")) { + return bitCast(mod, scope, rl, call); + } else { + return mod.failTok(scope, call.builtin_token, "invalid builtin function: '{}'", .{builtin_name}); + } +} + +fn callExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Call) InnerError!*zir.Inst { + const tree = scope.tree(); + const lhs = try expr(mod, scope, .none, node.lhs); const param_nodes = node.params(); const args = try scope.getGenZIR().arena.alloc(*zir.Inst, param_nodes.len); for (param_nodes) |param_node, i| { - args[i] = try expr(mod, scope, param_node); + const param_src = tree.token_locs[param_node.firstToken()].start; + const param_type = try mod.addZIRInst(scope, param_src, zir.Inst.ParamType, .{ + .func = lhs, + .arg_index = i, + }, .{}); + args[i] = try expr(mod, scope, .{ .ty = param_type }, param_node); } const src = tree.token_locs[node.lhs.firstToken()].start; - return mod.addZIRInst(scope, src, zir.Inst.Call, .{ + const result = try mod.addZIRInst(scope, src, zir.Inst.Call, .{ .func = lhs, .args = args, }, .{}); + // TODO function call with result location + return rlWrap(mod, scope, rl, result); } -fn unreach(mod: *Module, scope: *Scope, unreach_node: *ast.Node.Unreachable) InnerError!*zir.Inst { +fn unreach(mod: *Module, scope: *Scope, unreach_node: *ast.Node.OneToken) InnerError!*zir.Inst { const tree = scope.tree(); const src = tree.token_locs[unreach_node.token].start; return mod.addZIRNoOp(scope, src, .@"unreachable"); @@ -611,7 +877,9 @@ fn nodeMayNeedMemoryLocation(start_node: *ast.Node) bool { .FieldInitializer, => unreachable, - .ControlFlowExpression, + .Return, + .Break, + .Continue, .BitNot, .BoolNot, .VarDecl, @@ -722,3 +990,39 @@ fn nodeMayNeedMemoryLocation(start_node: *ast.Node) bool { } } } + +/// Applies `rl` semantics to `inst`. Expressions which do not do their own handling of +/// result locations must call this function on their result. +/// As an example, if the `ResultLoc` is `ptr`, it will write the result to the pointer. +/// If the `ResultLoc` is `ty`, it will coerce the result to the type. +fn rlWrap(mod: *Module, scope: *Scope, rl: ResultLoc, result: *zir.Inst) InnerError!*zir.Inst { + switch (rl) { + .none => return result, + .discard => { + // Emit a compile error for discarding error values. + _ = try mod.addZIRUnOp(scope, result.src, .ensure_result_non_error, result); + return result; + }, + .ty => |ty_inst| return mod.addZIRBinOp(scope, result.src, .as, ty_inst, result), + .ptr => |ptr_inst| { + const casted_result = try mod.addZIRInst(scope, result.src, zir.Inst.CoerceToPtrElem, .{ + .ptr = ptr_inst, + .value = result, + }, .{}); + _ = try mod.addZIRInst(scope, result.src, zir.Inst.Store, .{ + .ptr = ptr_inst, + .value = casted_result, + }, .{}); + return casted_result; + }, + .bitcasted_ptr => |bitcasted_ptr| { + return mod.fail(scope, result.src, "TODO implement rlWrap .bitcasted_ptr", .{}); + }, + .inferred_ptr => |alloc| { + return mod.fail(scope, result.src, "TODO implement rlWrap .inferred_ptr", .{}); + }, + .block_ptr => |block_ptr| { + return mod.fail(scope, result.src, "TODO implement rlWrap .block_ptr", .{}); + }, + } +} diff --git a/src-self-hosted/codegen/c.zig b/src-self-hosted/codegen/c.zig index ed3a5f73b4..509491677a 100644 --- a/src-self-hosted/codegen/c.zig +++ b/src-self-hosted/codegen/c.zig @@ -90,7 +90,7 @@ fn genFn(file: *C, decl: *Decl) !void { const instructions = func.analysis.success.instructions; if (instructions.len > 0) { for (instructions) |inst| { - try writer.writeAll("\n\t"); + try writer.writeAll("\n "); switch (inst.tag) { .assembly => try genAsm(file, inst.castTag(.assembly).?, decl), .call => try genCall(file, inst.castTag(.call).?, decl), @@ -106,21 +106,7 @@ fn genFn(file: *C, decl: *Decl) !void { } fn genRet(file: *C, inst: *Inst.UnOp, decl: *Decl, expected_return_type: Type) !void { - const writer = file.main.writer(); - const ret_value = inst.operand; - const value = ret_value.value().?; - if (expected_return_type.eql(ret_value.ty)) - return file.fail(decl.src(), "TODO return {}", .{expected_return_type}) - else if (expected_return_type.isInt() and ret_value.ty.tag() == .comptime_int) - if (value.intFitsInType(expected_return_type, file.options.target)) - if (expected_return_type.intInfo(file.options.target).bits <= 64) - try writer.print("return {};", .{value.toUnsignedInt()}) - else - return file.fail(decl.src(), "TODO return ints > 64 bits", .{}) - else - return file.fail(decl.src(), "comptime int {} does not fit in {}", .{ value.toUnsignedInt(), expected_return_type }) - else - return file.fail(decl.src(), "return type mismatch: expected {}, found {}", .{ expected_return_type, ret_value.ty }); + return file.fail(decl.src(), "TODO return {}", .{expected_return_type}); } fn genCall(file: *C, inst: *Inst.Call, decl: *Decl) !void { @@ -162,7 +148,7 @@ fn genAsm(file: *C, as: *Inst.Assembly, decl: *Decl) !void { if (c.val.tag() == .int_u64) { try writer.writeAll("register "); try renderType(file, writer, arg.ty, decl.src()); - try writer.print(" {}_constant __asm__(\"{}\") = {};\n\t", .{ reg, reg, c.val.toUnsignedInt() }); + try writer.print(" {}_constant __asm__(\"{}\") = {};\n ", .{ reg, reg, c.val.toUnsignedInt() }); } else { return file.fail(decl.src(), "TODO inline asm {} args", .{c.val.tag()}); } diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 776f6de31c..1e5c8d5cd7 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -1579,6 +1579,8 @@ pub fn createElfFile(allocator: *Allocator, file: fs.File, options: Options) !Fi .elf => {}, .macho => return error.TODOImplementWritingMachO, .wasm => return error.TODOImplementWritingWasmObjects, + .hex => return error.TODOImplementWritingHex, + .raw => return error.TODOImplementWritingRaw, } var self: File.Elf = .{ @@ -1638,6 +1640,8 @@ fn openBinFileInner(allocator: *Allocator, file: fs.File, options: Options) !Fil .elf => {}, .macho => return error.IncrFailed, .wasm => return error.IncrFailed, + .hex => return error.IncrFailed, + .raw => return error.IncrFailed, } var self: File.Elf = .{ .allocator = allocator, diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index f63e915c25..45c6c3f836 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -141,11 +141,19 @@ const usage_build_generic = \\ --name [name] Override output name \\ --mode [mode] Set the build mode \\ Debug (default) optimizations off, safety on - \\ ReleaseFast optimizations on, safety off - \\ ReleaseSafe optimizations on, safety on - \\ ReleaseSmall optimize for small binary, safety off + \\ ReleaseFast Optimizations on, safety off + \\ ReleaseSafe Optimizations on, safety on + \\ ReleaseSmall Optimize for small binary, safety off \\ --dynamic Force output to be dynamically linked \\ --strip Exclude debug symbols + \\ -ofmt=[mode] Override target object format + \\ elf Executable and Linking Format + \\ c Compile to C source code + \\ coff (planned) Common Object File Format (Windows) + \\ pe (planned) Portable Executable (Windows) + \\ macho (planned) macOS relocatables + \\ hex (planned) Intel IHEX + \\ raw (planned) Dump machine code directly \\ \\Link Options: \\ -l[lib], --library [lib] Link against system library @@ -195,7 +203,7 @@ fn buildOutputType( var target_arch_os_abi: []const u8 = "native"; var target_mcpu: ?[]const u8 = null; var target_dynamic_linker: ?[]const u8 = null; - var object_format: ?std.builtin.ObjectFormat = null; + var target_ofmt: ?[]const u8 = null; var system_libs = std.ArrayList([]const u8).init(gpa); defer system_libs.deinit(); @@ -282,12 +290,8 @@ fn buildOutputType( } i += 1; target_mcpu = args[i]; - } else if (mem.eql(u8, arg, "--c")) { - if (object_format) |old| { - std.debug.print("attempted to override object format {} with C\n", .{old}); - process.exit(1); - } - object_format = .c; + } else if (mem.startsWith(u8, arg, "-ofmt=")) { + target_ofmt = arg["-ofmt=".len..]; } else if (mem.startsWith(u8, arg, "-mcpu=")) { target_mcpu = arg["-mcpu=".len..]; } else if (mem.eql(u8, arg, "--dynamic-linker")) { @@ -434,6 +438,30 @@ fn buildOutputType( process.exit(1); }; + const object_format: ?std.Target.ObjectFormat = blk: { + const ofmt = target_ofmt orelse break :blk null; + if (mem.eql(u8, ofmt, "elf")) { + break :blk .elf; + } else if (mem.eql(u8, ofmt, "c")) { + break :blk .c; + } else if (mem.eql(u8, ofmt, "coff")) { + break :blk .coff; + } else if (mem.eql(u8, ofmt, "pe")) { + break :blk .coff; + } else if (mem.eql(u8, ofmt, "macho")) { + break :blk .macho; + } else if (mem.eql(u8, ofmt, "wasm")) { + break :blk .wasm; + } else if (mem.eql(u8, ofmt, "hex")) { + break :blk .hex; + } else if (mem.eql(u8, ofmt, "raw")) { + break :blk .raw; + } else { + std.debug.print("unsupported object format: {}", .{ofmt}); + process.exit(1); + } + }; + const bin_path = switch (emit_bin) { .no => { std.debug.print("-fno-emit-bin not supported yet", .{}); diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig index cc0587d92a..0b879cf350 100644 --- a/src-self-hosted/translate_c.zig +++ b/src-self-hosted/translate_c.zig @@ -1308,8 +1308,7 @@ fn transBinaryOperator( const rhs = try transExpr(rp, &block_scope.base, ZigClangBinaryOperator_getRHS(stmt), .used, .r_value); if (expr) { _ = try appendToken(rp.c, .Semicolon, ";"); - const break_node = try transCreateNodeBreakToken(rp.c, block_scope.label); - break_node.rhs = rhs; + const break_node = try transCreateNodeBreakToken(rp.c, block_scope.label, rhs); try block_scope.statements.append(&break_node.base); const block_node = try block_scope.complete(rp.c); const rparen = try appendToken(rp.c, .RParen, ")"); @@ -1881,12 +1880,19 @@ fn transReturnStmt( scope: *Scope, expr: *const ZigClangReturnStmt, ) TransError!*ast.Node { - const node = try transCreateNodeReturnExpr(rp.c); - if (ZigClangReturnStmt_getRetValue(expr)) |val_expr| { - node.rhs = try transExprCoercing(rp, scope, val_expr, .used, .r_value); - } + const return_kw = try appendToken(rp.c, .Keyword_return, "return"); + const rhs: ?*ast.Node = if (ZigClangReturnStmt_getRetValue(expr)) |val_expr| + try transExprCoercing(rp, scope, val_expr, .used, .r_value) + else + null; + const return_expr = try ast.Node.ControlFlowExpression.create(rp.c.arena, .{ + .ltoken = return_kw, + .tag = .Return, + }, .{ + .rhs = rhs, + }); _ = try appendToken(rp.c, .Semicolon, ";"); - return &node.base; + return &return_expr.base; } fn transStringLiteral( @@ -1912,8 +1918,9 @@ fn transStringLiteral( buf[buf.len - 1] = '"'; const token = try appendToken(rp.c, .StringLiteral, buf); - const node = try rp.c.arena.create(ast.Node.StringLiteral); + const node = try rp.c.arena.create(ast.Node.OneToken); node.* = .{ + .base = .{ .tag = .StringLiteral }, .token = token, }; return maybeSuppressResult(rp, scope, result_used, &node.base); @@ -2518,7 +2525,7 @@ fn transDoWhileLoop( prefix_op.rhs = try transBoolExpr(rp, &cond_scope.base, @ptrCast(*const ZigClangExpr, ZigClangDoStmt_getCond(stmt)), .used, .r_value, true); _ = try appendToken(rp.c, .RParen, ")"); if_node.condition = &prefix_op.base; - if_node.body = &(try transCreateNodeBreak(rp.c, null)).base; + if_node.body = &(try transCreateNodeBreak(rp.c, null, null)).base; _ = try appendToken(rp.c, .Semicolon, ";"); const body_node = if (ZigClangStmt_getStmtClass(ZigClangDoStmt_getBody(stmt)) == .CompoundStmtClass) blk: { @@ -2688,7 +2695,7 @@ fn transSwitch( _ = try appendToken(rp.c, .Colon, ":"); if (!switch_scope.has_default) { const else_prong = try transCreateNodeSwitchCase(rp.c, try transCreateNodeSwitchElse(rp.c)); - else_prong.expr = &(try transCreateNodeBreak(rp.c, "__switch")).base; + else_prong.expr = &(try transCreateNodeBreak(rp.c, "__switch", null)).base; _ = try appendToken(rp.c, .Comma, ","); if (switch_scope.case_index >= switch_scope.cases.len) @@ -2732,7 +2739,7 @@ fn transCase( try transExpr(rp, scope, ZigClangCaseStmt_getLHS(stmt), .used, .r_value); const switch_prong = try transCreateNodeSwitchCase(rp.c, expr); - switch_prong.expr = &(try transCreateNodeBreak(rp.c, label)).base; + switch_prong.expr = &(try transCreateNodeBreak(rp.c, label, null)).base; _ = try appendToken(rp.c, .Comma, ","); if (switch_scope.case_index >= switch_scope.cases.len) @@ -2768,7 +2775,7 @@ fn transDefault( _ = try appendToken(rp.c, .Semicolon, ";"); const else_prong = try transCreateNodeSwitchCase(rp.c, try transCreateNodeSwitchElse(rp.c)); - else_prong.expr = &(try transCreateNodeBreak(rp.c, label)).base; + else_prong.expr = &(try transCreateNodeBreak(rp.c, label, null)).base; _ = try appendToken(rp.c, .Comma, ","); if (switch_scope.case_index >= switch_scope.cases.len) @@ -2843,8 +2850,9 @@ fn transCharLiteral( } var char_buf: [4]u8 = undefined; const token = try appendTokenFmt(rp.c, .CharLiteral, "'{}'", .{escapeChar(@intCast(u8, val), &char_buf)}); - const node = try rp.c.arena.create(ast.Node.CharLiteral); + const node = try rp.c.arena.create(ast.Node.OneToken); node.* = .{ + .base = .{ .tag = .CharLiteral }, .token = token, }; break :blk &node.base; @@ -2889,8 +2897,11 @@ fn transStmtExpr(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangStmtExpr, const result = try transStmt(rp, &block_scope.base, it[0], .unused, .r_value); try block_scope.statements.append(result); } - const break_node = try transCreateNodeBreak(rp.c, "blk"); - break_node.rhs = try transStmt(rp, &block_scope.base, it[0], .used, .r_value); + const break_node = blk: { + var tmp = try CtrlFlow.init(rp.c, .Break, "blk"); + const rhs = try transStmt(rp, &block_scope.base, it[0], .used, .r_value); + break :blk try tmp.finish(rhs); + }; _ = try appendToken(rp.c, .Semicolon, ";"); try block_scope.statements.append(&break_node.base); const block_node = try block_scope.complete(rp.c); @@ -3205,8 +3216,7 @@ fn transCreatePreCrement( const assign = try transCreateNodeInfixOp(rp, scope, ref_node, op, token, one, .used, false); try block_scope.statements.append(assign); - const break_node = try transCreateNodeBreakToken(rp.c, block_scope.label); - break_node.rhs = ref_node; + const break_node = try transCreateNodeBreakToken(rp.c, block_scope.label, ref_node); try block_scope.statements.append(&break_node.base); const block_node = try block_scope.complete(rp.c); // semicolon must immediately follow rbrace because it is the last token in a block @@ -3297,8 +3307,11 @@ fn transCreatePostCrement( const assign = try transCreateNodeInfixOp(rp, scope, ref_node, op, token, one, .used, false); try block_scope.statements.append(assign); - const break_node = try transCreateNodeBreakToken(rp.c, block_scope.label); - break_node.rhs = try transCreateNodeIdentifier(rp.c, tmp); + const break_node = blk: { + var tmp_ctrl_flow = try CtrlFlow.initToken(rp.c, .Break, block_scope.label); + const rhs = try transCreateNodeIdentifier(rp.c, tmp); + break :blk try tmp_ctrl_flow.finish(rhs); + }; try block_scope.statements.append(&break_node.base); _ = try appendToken(rp.c, .Semicolon, ";"); const block_node = try block_scope.complete(rp.c); @@ -3490,8 +3503,7 @@ fn transCreateCompoundAssign( try block_scope.statements.append(assign); } - const break_node = try transCreateNodeBreakToken(rp.c, block_scope.label); - break_node.rhs = ref_node; + const break_node = try transCreateNodeBreakToken(rp.c, block_scope.label, ref_node); try block_scope.statements.append(&break_node.base); const block_node = try block_scope.complete(rp.c); const grouped_expr = try rp.c.arena.create(ast.Node.GroupedExpression); @@ -3567,10 +3579,8 @@ fn transCPtrCast( fn transBreak(rp: RestorePoint, scope: *Scope) TransError!*ast.Node { const break_scope = scope.getBreakableScope(); - const br = try transCreateNodeBreak(rp.c, if (break_scope.id == .Switch) - "__switch" - else - null); + const label_text: ?[]const u8 = if (break_scope.id == .Switch) "__switch" else null; + const br = try transCreateNodeBreak(rp.c, label_text, null); _ = try appendToken(rp.c, .Semicolon, ";"); return &br.base; } @@ -3578,8 +3588,9 @@ fn transBreak(rp: RestorePoint, scope: *Scope) TransError!*ast.Node { fn transFloatingLiteral(rp: RestorePoint, scope: *Scope, stmt: *const ZigClangFloatingLiteral, used: ResultUsed) TransError!*ast.Node { // TODO use something more accurate const dbl = ZigClangAPFloat_getValueAsApproximateDouble(stmt); - const node = try rp.c.arena.create(ast.Node.FloatLiteral); + const node = try rp.c.arena.create(ast.Node.OneToken); node.* = .{ + .base = .{ .tag = .FloatLiteral }, .token = try appendTokenFmt(rp.c, .FloatLiteral, "{d}", .{dbl}), }; return maybeSuppressResult(rp, scope, used, &node.base); @@ -3619,7 +3630,7 @@ fn transBinaryConditionalOperator(rp: RestorePoint, scope: *Scope, stmt: *const }); try block_scope.statements.append(&tmp_var.base); - const break_node = try transCreateNodeBreakToken(rp.c, block_scope.label); + var break_node_tmp = try CtrlFlow.initToken(rp.c, .Break, block_scope.label); const if_node = try transCreateNodeIf(rp.c); var cond_scope = Scope.Condition{ @@ -3641,7 +3652,7 @@ fn transBinaryConditionalOperator(rp: RestorePoint, scope: *Scope, stmt: *const if_node.@"else".?.body = try transExpr(rp, &block_scope.base, false_expr, .used, .r_value); _ = try appendToken(rp.c, .Semicolon, ";"); - break_node.rhs = &if_node.base; + const break_node = try break_node_tmp.finish(&if_node.base); _ = try appendToken(rp.c, .Semicolon, ";"); try block_scope.statements.append(&break_node.base); const block_node = try block_scope.complete(rp.c); @@ -3822,8 +3833,9 @@ fn qualTypeToLog2IntRef(rp: RestorePoint, qt: ZigClangQualType, source_loc: ZigC if (int_bit_width != 0) { // we can perform the log2 now. const cast_bit_width = math.log2_int(u64, int_bit_width); - const node = try rp.c.arena.create(ast.Node.IntegerLiteral); + const node = try rp.c.arena.create(ast.Node.OneToken); node.* = .{ + .base = .{ .tag = .IntegerLiteral }, .token = try appendTokenFmt(rp.c, .Identifier, "u{}", .{cast_bit_width}), }; return &node.base; @@ -3845,8 +3857,9 @@ fn qualTypeToLog2IntRef(rp: RestorePoint, qt: ZigClangQualType, source_loc: ZigC const import_fn_call = try rp.c.createBuiltinCall("@import", 1); const std_token = try appendToken(rp.c, .StringLiteral, "\"std\""); - const std_node = try rp.c.arena.create(ast.Node.StringLiteral); + const std_node = try rp.c.arena.create(ast.Node.OneToken); std_node.* = .{ + .base = .{ .tag = .StringLiteral }, .token = std_token, }; import_fn_call.params()[0] = &std_node.base; @@ -4081,8 +4094,11 @@ fn transCreateNodeAssign( const assign = try transCreateNodeInfixOp(rp, &block_scope.base, lhs_node, .Assign, lhs_eq_token, ident, .used, false); try block_scope.statements.append(assign); - const break_node = try transCreateNodeBreak(rp.c, label_name); - break_node.rhs = try transCreateNodeIdentifier(rp.c, tmp); + const break_node = blk: { + var tmp_ctrl_flow = try CtrlFlow.init(rp.c, .Break, label_name); + const rhs_expr = try transCreateNodeIdentifier(rp.c, tmp); + break :blk try tmp_ctrl_flow.finish(rhs_expr); + }; _ = try appendToken(rp.c, .Semicolon, ";"); try block_scope.statements.append(&break_node.base); const block_node = try block_scope.complete(rp.c); @@ -4255,28 +4271,19 @@ fn transCreateNodeAPInt(c: *Context, int: *const ZigClangAPSInt) !*ast.Node { }; defer c.arena.free(str); const token = try appendToken(c, .IntegerLiteral, str); - const node = try c.arena.create(ast.Node.IntegerLiteral); + const node = try c.arena.create(ast.Node.OneToken); node.* = .{ + .base = .{ .tag = .IntegerLiteral }, .token = token, }; return &node.base; } -fn transCreateNodeReturnExpr(c: *Context) !*ast.Node.ControlFlowExpression { - const ltoken = try appendToken(c, .Keyword_return, "return"); - const node = try c.arena.create(ast.Node.ControlFlowExpression); - node.* = .{ - .ltoken = ltoken, - .kind = .Return, - .rhs = null, - }; - return node; -} - fn transCreateNodeUndefinedLiteral(c: *Context) !*ast.Node { const token = try appendToken(c, .Keyword_undefined, "undefined"); - const node = try c.arena.create(ast.Node.UndefinedLiteral); + const node = try c.arena.create(ast.Node.OneToken); node.* = .{ + .base = .{ .tag = .UndefinedLiteral }, .token = token, }; return &node.base; @@ -4284,8 +4291,9 @@ fn transCreateNodeUndefinedLiteral(c: *Context) !*ast.Node { fn transCreateNodeNullLiteral(c: *Context) !*ast.Node { const token = try appendToken(c, .Keyword_null, "null"); - const node = try c.arena.create(ast.Node.NullLiteral); + const node = try c.arena.create(ast.Node.OneToken); node.* = .{ + .base = .{ .tag = .NullLiteral }, .token = token, }; return &node.base; @@ -4296,8 +4304,9 @@ fn transCreateNodeBoolLiteral(c: *Context, value: bool) !*ast.Node { try appendToken(c, .Keyword_true, "true") else try appendToken(c, .Keyword_false, "false"); - const node = try c.arena.create(ast.Node.BoolLiteral); + const node = try c.arena.create(ast.Node.OneToken); node.* = .{ + .base = .{ .tag = .BoolLiteral }, .token = token, }; return &node.base; @@ -4305,8 +4314,9 @@ fn transCreateNodeBoolLiteral(c: *Context, value: bool) !*ast.Node { fn transCreateNodeInt(c: *Context, int: anytype) !*ast.Node { const token = try appendTokenFmt(c, .IntegerLiteral, "{}", .{int}); - const node = try c.arena.create(ast.Node.IntegerLiteral); + const node = try c.arena.create(ast.Node.OneToken); node.* = .{ + .base = .{ .tag = .IntegerLiteral }, .token = token, }; return &node.base; @@ -4314,8 +4324,9 @@ fn transCreateNodeInt(c: *Context, int: anytype) !*ast.Node { fn transCreateNodeFloat(c: *Context, int: anytype) !*ast.Node { const token = try appendTokenFmt(c, .FloatLiteral, "{}", .{int}); - const node = try c.arena.create(ast.Node.FloatLiteral); + const node = try c.arena.create(ast.Node.OneToken); node.* = .{ + .base = .{ .tag = .FloatLiteral }, .token = token, }; return &node.base; @@ -4362,7 +4373,7 @@ fn transCreateNodeMacroFn(c: *Context, name: []const u8, ref: *ast.Node, proto_a const block_lbrace = try appendToken(c, .LBrace, "{"); - const return_expr = try transCreateNodeReturnExpr(c); + const return_kw = try appendToken(c, .Keyword_return, "return"); const unwrap_expr = try transCreateNodeUnwrapNull(c, ref.cast(ast.Node.VarDecl).?.getTrailer("init_node").?); const call_expr = try c.createCall(unwrap_expr, fn_params.items.len); @@ -4376,7 +4387,12 @@ fn transCreateNodeMacroFn(c: *Context, name: []const u8, ref: *ast.Node, proto_a } call_expr.rtoken = try appendToken(c, .RParen, ")"); - return_expr.rhs = &call_expr.base; + const return_expr = try ast.Node.ControlFlowExpression.create(c.arena, .{ + .ltoken = return_kw, + .tag = .Return, + }, .{ + .rhs = &call_expr.base, + }); _ = try appendToken(c, .Semicolon, ";"); const block = try ast.Node.Block.alloc(c.arena, 1); @@ -4424,8 +4440,9 @@ fn transCreateNodeEnumLiteral(c: *Context, name: []const u8) !*ast.Node { } fn transCreateNodeStringLiteral(c: *Context, str: []const u8) !*ast.Node { - const node = try c.arena.create(ast.Node.StringLiteral); + const node = try c.arena.create(ast.Node.OneToken); node.* = .{ + .base = .{ .tag = .StringLiteral }, .token = try appendToken(c, .StringLiteral, str), }; return &node.base; @@ -4455,28 +4472,77 @@ fn transCreateNodeElse(c: *Context) !*ast.Node.Else { return node; } -fn transCreateNodeBreakToken(c: *Context, label: ?ast.TokenIndex) !*ast.Node.ControlFlowExpression { - const other_token = label orelse return transCreateNodeBreak(c, null); +fn transCreateNodeBreakToken( + c: *Context, + label: ?ast.TokenIndex, + rhs: ?*ast.Node, +) !*ast.Node.ControlFlowExpression { + const other_token = label orelse return transCreateNodeBreak(c, null, rhs); const loc = c.token_locs.items[other_token]; const label_name = c.source_buffer.items[loc.start..loc.end]; - return transCreateNodeBreak(c, label_name); + return transCreateNodeBreak(c, label_name, rhs); } -fn transCreateNodeBreak(c: *Context, label: ?[]const u8) !*ast.Node.ControlFlowExpression { - const ltoken = try appendToken(c, .Keyword_break, "break"); - const label_node = if (label) |l| blk: { - _ = try appendToken(c, .Colon, ":"); - break :blk try transCreateNodeIdentifier(c, l); - } else null; - const node = try c.arena.create(ast.Node.ControlFlowExpression); - node.* = .{ - .ltoken = ltoken, - .kind = .{ .Break = label_node }, - .rhs = null, - }; - return node; +fn transCreateNodeBreak( + c: *Context, + label: ?[]const u8, + rhs: ?*ast.Node, +) !*ast.Node.ControlFlowExpression { + var ctrl_flow = try CtrlFlow.init(c, .Break, label); + return ctrl_flow.finish(rhs); } +const CtrlFlow = struct { + c: *Context, + ltoken: ast.TokenIndex, + label_token: ?ast.TokenIndex, + tag: ast.Node.Tag, + + /// Does everything except the RHS. + fn init(c: *Context, tag: ast.Node.Tag, label: ?[]const u8) !CtrlFlow { + const kw: Token.Id = switch (tag) { + .Break => .Keyword_break, + .Continue => .Keyword_continue, + .Return => .Keyword_return, + else => unreachable, + }; + const kw_text = switch (tag) { + .Break => "break", + .Continue => "continue", + .Return => "return", + else => unreachable, + }; + const ltoken = try appendToken(c, kw, kw_text); + const label_token = if (label) |l| blk: { + _ = try appendToken(c, .Colon, ":"); + break :blk try appendToken(c, .Identifier, l); + } else null; + return CtrlFlow{ + .c = c, + .ltoken = ltoken, + .label_token = label_token, + .tag = tag, + }; + } + + fn initToken(c: *Context, tag: ast.Node.Tag, label: ?ast.TokenIndex) !CtrlFlow { + const other_token = label orelse return init(c, tag, null); + const loc = c.token_locs.items[other_token]; + const label_name = c.source_buffer.items[loc.start..loc.end]; + return init(c, tag, label_name); + } + + fn finish(self: *CtrlFlow, rhs: ?*ast.Node) !*ast.Node.ControlFlowExpression { + return ast.Node.ControlFlowExpression.create(self.c.arena, .{ + .ltoken = self.ltoken, + .tag = self.tag, + }, .{ + .label = self.label_token, + .rhs = rhs, + }); + } +}; + fn transCreateNodeWhile(c: *Context) !*ast.Node.While { const while_tok = try appendToken(c, .Keyword_while, "while"); _ = try appendToken(c, .LParen, "("); @@ -4497,12 +4563,10 @@ fn transCreateNodeWhile(c: *Context) !*ast.Node.While { fn transCreateNodeContinue(c: *Context) !*ast.Node { const ltoken = try appendToken(c, .Keyword_continue, "continue"); - const node = try c.arena.create(ast.Node.ControlFlowExpression); - node.* = .{ + const node = try ast.Node.ControlFlowExpression.create(c.arena, .{ .ltoken = ltoken, - .kind = .{ .Continue = null }, - .rhs = null, - }; + .tag = .Continue, + }, .{}); _ = try appendToken(c, .Semicolon, ";"); return &node.base; } @@ -5006,8 +5070,9 @@ pub fn failDecl(c: *Context, loc: ZigClangSourceLocation, name: []const u8, comp const semi_tok = try appendToken(c, .Semicolon, ";"); _ = try appendTokenFmt(c, .LineComment, "// {}", .{c.locStr(loc)}); - const msg_node = try c.arena.create(ast.Node.StringLiteral); + const msg_node = try c.arena.create(ast.Node.OneToken); msg_node.* = .{ + .base = .{ .tag = .StringLiteral }, .token = msg_tok, }; @@ -5110,8 +5175,9 @@ fn appendIdentifier(c: *Context, name: []const u8) !ast.TokenIndex { fn transCreateNodeIdentifier(c: *Context, name: []const u8) !*ast.Node { const token_index = try appendIdentifier(c, name); - const identifier = try c.arena.create(ast.Node.Identifier); + const identifier = try c.arena.create(ast.Node.OneToken); identifier.* = .{ + .base = .{ .tag = .Identifier }, .token = token_index, }; return &identifier.base; @@ -5119,8 +5185,9 @@ fn transCreateNodeIdentifier(c: *Context, name: []const u8) !*ast.Node { fn transCreateNodeIdentifierUnchecked(c: *Context, name: []const u8) !*ast.Node { const token_index = try appendTokenFmt(c, .Identifier, "{}", .{name}); - const identifier = try c.arena.create(ast.Node.Identifier); + const identifier = try c.arena.create(ast.Node.OneToken); identifier.* = .{ + .base = .{ .tag = .Identifier }, .token = token_index, }; return &identifier.base; @@ -5289,8 +5356,9 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, const param_name_tok = try appendIdentifier(c, mangled_name); _ = try appendToken(c, .Colon, ":"); - const any_type = try c.arena.create(ast.Node.AnyType); + const any_type = try c.arena.create(ast.Node.OneToken); any_type.* = .{ + .base = .{ .tag = .AnyType }, .token = try appendToken(c, .Keyword_anytype, "anytype"), }; @@ -5322,7 +5390,7 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, const type_of = try c.createBuiltinCall("@TypeOf", 1); - const return_expr = try transCreateNodeReturnExpr(c); + const return_kw = try appendToken(c, .Keyword_return, "return"); const expr = try parseCExpr(c, it, source, source_loc, scope); const last = it.next().?; if (last.id != .Eof and last.id != .Nl) @@ -5337,13 +5405,17 @@ fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, const type_of_arg = if (expr.tag != .Block) expr else blk: { const blk = @fieldParentPtr(ast.Node.Block, "base", expr); const blk_last = blk.statements()[blk.statements_len - 1]; - std.debug.assert(blk_last.tag == .ControlFlowExpression); - const br = @fieldParentPtr(ast.Node.ControlFlowExpression, "base", blk_last); - break :blk br.rhs.?; + const br = blk_last.cast(ast.Node.ControlFlowExpression).?; + break :blk br.getRHS().?; }; type_of.params()[0] = type_of_arg; type_of.rparen_token = try appendToken(c, .RParen, ")"); - return_expr.rhs = expr; + const return_expr = try ast.Node.ControlFlowExpression.create(c.arena, .{ + .ltoken = return_kw, + .tag = .Return, + }, .{ + .rhs = expr, + }); try block_scope.statements.append(&return_expr.base); const block_node = try block_scope.complete(c); @@ -5416,8 +5488,7 @@ fn parseCExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_ } } - const break_node = try transCreateNodeBreak(c, label_name); - break_node.rhs = last; + const break_node = try transCreateNodeBreak(c, label_name, last); try block_scope.statements.append(&break_node.base); const block_node = try block_scope.complete(c); return &block_node.base; @@ -5656,15 +5727,17 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, const first_tok = it.list.at(0); if (source[tok.start] != '\'' or source[tok.start + 1] == '\\' or tok.end - tok.start == 3) { const token = try appendToken(c, .CharLiteral, try zigifyEscapeSequences(c, source[tok.start..tok.end], source[first_tok.start..first_tok.end], source_loc)); - const node = try c.arena.create(ast.Node.CharLiteral); + const node = try c.arena.create(ast.Node.OneToken); node.* = .{ + .base = .{ .tag = .CharLiteral }, .token = token, }; return &node.base; } else { const token = try appendTokenFmt(c, .IntegerLiteral, "0x{x}", .{source[tok.start + 1 .. tok.end - 1]}); - const node = try c.arena.create(ast.Node.IntegerLiteral); + const node = try c.arena.create(ast.Node.OneToken); node.* = .{ + .base = .{ .tag = .IntegerLiteral }, .token = token, }; return &node.base; @@ -5673,8 +5746,9 @@ fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, .StringLiteral => { const first_tok = it.list.at(0); const token = try appendToken(c, .StringLiteral, try zigifyEscapeSequences(c, source[tok.start..tok.end], source[first_tok.start..first_tok.end], source_loc)); - const node = try c.arena.create(ast.Node.StringLiteral); + const node = try c.arena.create(ast.Node.OneToken); node.* = .{ + .base = .{ .tag = .StringLiteral }, .token = token, }; return &node.base; @@ -6224,7 +6298,7 @@ fn getContainer(c: *Context, node: *ast.Node) ?*ast.Node { => return node, .Identifier => { - const ident = node.cast(ast.Node.Identifier).?; + const ident = node.castTag(.Identifier).?; if (c.global_scope.sym_table.get(tokenSlice(c, ident.token))) |value| { if (value.cast(ast.Node.VarDecl)) |var_decl| return getContainer(c, var_decl.getTrailer("init_node").?); @@ -6238,7 +6312,7 @@ fn getContainer(c: *Context, node: *ast.Node) ?*ast.Node { if (ty_node.cast(ast.Node.ContainerDecl)) |container| { for (container.fieldsAndDecls()) |field_ref| { const field = field_ref.cast(ast.Node.ContainerField).?; - const ident = infix.rhs.cast(ast.Node.Identifier).?; + const ident = infix.rhs.castTag(.Identifier).?; if (mem.eql(u8, tokenSlice(c, field.name_token), tokenSlice(c, ident.token))) { return getContainer(c, field.type_expr.?); } @@ -6253,7 +6327,7 @@ fn getContainer(c: *Context, node: *ast.Node) ?*ast.Node { } fn getContainerTypeOf(c: *Context, ref: *ast.Node) ?*ast.Node { - if (ref.cast(ast.Node.Identifier)) |ident| { + if (ref.castTag(.Identifier)) |ident| { if (c.global_scope.sym_table.get(tokenSlice(c, ident.token))) |value| { if (value.cast(ast.Node.VarDecl)) |var_decl| { if (var_decl.getTrailer("type_node")) |ty| @@ -6265,7 +6339,7 @@ fn getContainerTypeOf(c: *Context, ref: *ast.Node) ?*ast.Node { if (ty_node.cast(ast.Node.ContainerDecl)) |container| { for (container.fieldsAndDecls()) |field_ref| { const field = field_ref.cast(ast.Node.ContainerField).?; - const ident = infix.rhs.cast(ast.Node.Identifier).?; + const ident = infix.rhs.castTag(.Identifier).?; if (mem.eql(u8, tokenSlice(c, field.name_token), tokenSlice(c, ident.token))) { return getContainer(c, field.type_expr.?); } diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index d2df0afaf5..5da9f1c3a0 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -34,9 +34,17 @@ pub const Inst = struct { /// These names are used directly as the instruction names in the text format. pub const Tag = enum { + /// Allocates stack local memory. Its lifetime ends when the block ends that contains + /// this instruction. + alloc, + /// Same as `alloc` except the type is inferred. + alloc_inferred, /// Function parameter value. These must be first in a function's main block, /// in respective order with the parameters. arg, + /// A typed result location pointer is bitcasted to a new result location pointer. + /// The new result location pointer has an inferred type. + bitcast_result_ptr, /// A labeled block of code, which can return a value. block, /// Return a value from a `Block`. @@ -45,6 +53,17 @@ pub const Inst = struct { /// Same as `break` but without an operand; the operand is assumed to be the void value. breakvoid, call, + /// Coerces a result location pointer to a new element type. It is evaluated "backwards"- + /// as type coercion from the new element type to the old element type. + /// LHS is destination element type, RHS is result pointer. + coerce_result_ptr, + /// This instruction does a `coerce_result_ptr` operation on a `Block`'s + /// result location pointer, whose type is inferred by peer type resolution on the + /// `Block`'s corresponding `break` instructions. + coerce_result_block_ptr, + /// Equivalent to `as(ptr_child_type(typeof(ptr)), value)`. + coerce_to_ptr_elem, + /// Emit an error message and fail compilation. compileerror, /// Special case, has no textual representation. @"const", @@ -57,7 +76,17 @@ pub const Inst = struct { declval, /// Same as declval but the parameter is a `*Module.Decl` rather than a name. declval_in_module, + /// Emits a compile error if the operand is not `void`. + ensure_result_used, + /// Emits a compile error if an error is ignored. + ensure_result_non_error, boolnot, + /// Obtains a pointer to the return value. + ret_ptr, + /// Obtains the return type of the in-scope function. + ret_type, + /// Write a value to a pointer. + store, /// String Literal. Makes an anonymous Decl and then takes a pointer to it. str, int, @@ -73,6 +102,9 @@ pub const Inst = struct { @"fn", fntype, @"export", + /// Given a reference to a function and a parameter index, returns the + /// type of the parameter. TODO what happens when the parameter is `anytype`? + param_type, primitive, intcast, bitcast, @@ -96,6 +128,9 @@ pub const Inst = struct { .breakpoint, .@"unreachable", .returnvoid, + .alloc_inferred, + .ret_ptr, + .ret_type, => NoOp, .boolnot, @@ -103,6 +138,11 @@ pub const Inst = struct { .@"return", .isnull, .isnonnull, + .ptrtoint, + .alloc, + .ensure_result_used, + .ensure_result_non_error, + .bitcast_result_ptr, => UnOp, .add, @@ -113,32 +153,36 @@ pub const Inst = struct { .cmp_gte, .cmp_gt, .cmp_neq, + .as, + .floatcast, + .intcast, + .bitcast, + .coerce_result_ptr, => BinOp, .block => Block, .@"break" => Break, .breakvoid => BreakVoid, .call => Call, + .coerce_to_ptr_elem => CoerceToPtrElem, .declref => DeclRef, .declref_str => DeclRefStr, .declval => DeclVal, .declval_in_module => DeclValInModule, + .coerce_result_block_ptr => CoerceResultBlockPtr, .compileerror => CompileError, .@"const" => Const, + .store => Store, .str => Str, .int => Int, .inttype => IntType, - .ptrtoint => PtrToInt, .fieldptr => FieldPtr, - .as => As, .@"asm" => Asm, .@"fn" => Fn, .@"export" => Export, + .param_type => ParamType, .primitive => Primitive, .fntype => FnType, - .intcast => IntCast, - .bitcast => BitCast, - .floatcast => FloatCast, .elemptr => ElemPtr, .condbr => CondBr, }; @@ -148,15 +192,26 @@ pub const Inst = struct { /// Function calls do not count. pub fn isNoReturn(tag: Tag) bool { return switch (tag) { + .alloc, + .alloc_inferred, .arg, + .bitcast_result_ptr, .block, .breakpoint, .call, + .coerce_result_ptr, + .coerce_result_block_ptr, + .coerce_to_ptr_elem, .@"const", .declref, .declref_str, .declval, .declval_in_module, + .ensure_result_used, + .ensure_result_non_error, + .ret_ptr, + .ret_type, + .store, .str, .int, .inttype, @@ -168,6 +223,7 @@ pub const Inst = struct { .@"fn", .fntype, .@"export", + .param_type, .primitive, .intcast, .bitcast, @@ -292,6 +348,17 @@ pub const Inst = struct { }, }; + pub const CoerceToPtrElem = struct { + pub const base_tag = Tag.coerce_to_ptr_elem; + base: Inst, + + positionals: struct { + ptr: *Inst, + value: *Inst, + }, + kw_args: struct {}, + }; + pub const DeclRef = struct { pub const base_tag = Tag.declref; base: Inst, @@ -332,6 +399,17 @@ pub const Inst = struct { kw_args: struct {}, }; + pub const CoerceResultBlockPtr = struct { + pub const base_tag = Tag.coerce_result_block_ptr; + base: Inst, + + positionals: struct { + dest_type: *Inst, + block: *Block, + }, + kw_args: struct {}, + }; + pub const CompileError = struct { pub const base_tag = Tag.compileerror; base: Inst, @@ -352,6 +430,17 @@ pub const Inst = struct { kw_args: struct {}, }; + pub const Store = struct { + pub const base_tag = Tag.store; + base: Inst, + + positionals: struct { + ptr: *Inst, + value: *Inst, + }, + kw_args: struct {}, + }; + pub const Str = struct { pub const base_tag = Tag.str; base: Inst, @@ -372,17 +461,6 @@ pub const Inst = struct { kw_args: struct {}, }; - pub const PtrToInt = struct { - pub const builtin_name = "@ptrToInt"; - pub const base_tag = Tag.ptrtoint; - base: Inst, - - positionals: struct { - operand: *Inst, - }, - kw_args: struct {}, - }; - pub const FieldPtr = struct { pub const base_tag = Tag.fieldptr; base: Inst, @@ -394,18 +472,6 @@ pub const Inst = struct { kw_args: struct {}, }; - pub const As = struct { - pub const base_tag = Tag.as; - pub const builtin_name = "@as"; - base: Inst, - - positionals: struct { - dest_type: *Inst, - value: *Inst, - }, - kw_args: struct {}, - }; - pub const Asm = struct { pub const base_tag = Tag.@"asm"; base: Inst, @@ -469,6 +535,17 @@ pub const Inst = struct { kw_args: struct {}, }; + pub const ParamType = struct { + pub const base_tag = Tag.param_type; + base: Inst, + + positionals: struct { + func: *Inst, + arg_index: usize, + }, + kw_args: struct {}, + }; + pub const Primitive = struct { pub const base_tag = Tag.primitive; base: Inst, @@ -559,42 +636,6 @@ pub const Inst = struct { }; }; - pub const FloatCast = struct { - pub const base_tag = Tag.floatcast; - pub const builtin_name = "@floatCast"; - base: Inst, - - positionals: struct { - dest_type: *Inst, - operand: *Inst, - }, - kw_args: struct {}, - }; - - pub const IntCast = struct { - pub const base_tag = Tag.intcast; - pub const builtin_name = "@intCast"; - base: Inst, - - positionals: struct { - dest_type: *Inst, - operand: *Inst, - }, - kw_args: struct {}, - }; - - pub const BitCast = struct { - pub const base_tag = Tag.bitcast; - pub const builtin_name = "@bitCast"; - base: Inst, - - positionals: struct { - dest_type: *Inst, - operand: *Inst, - }, - kw_args: struct {}, - }; - pub const ElemPtr = struct { pub const base_tag = Tag.elemptr; base: Inst, @@ -1467,15 +1508,15 @@ const EmitZIR = struct { }, .ComptimeInt => return self.emitComptimeIntVal(src, typed_value.val), .Int => { - const as_inst = try self.arena.allocator.create(Inst.As); + const as_inst = try self.arena.allocator.create(Inst.BinOp); as_inst.* = .{ .base = .{ + .tag = .as, .src = src, - .tag = Inst.As.base_tag, }, .positionals = .{ - .dest_type = (try self.emitType(src, typed_value.ty)).inst, - .value = (try self.emitComptimeIntVal(src, typed_value.val)).inst, + .lhs = (try self.emitType(src, typed_value.ty)).inst, + .rhs = (try self.emitComptimeIntVal(src, typed_value.val)).inst, }, .kw_args = .{}, }; @@ -1640,17 +1681,17 @@ const EmitZIR = struct { src: usize, new_body: ZirBody, old_inst: *ir.Inst.UnOp, - comptime I: type, + tag: Inst.Tag, ) Allocator.Error!*Inst { - const new_inst = try self.arena.allocator.create(I); + const new_inst = try self.arena.allocator.create(Inst.BinOp); new_inst.* = .{ .base = .{ .src = src, - .tag = I.base_tag, + .tag = tag, }, .positionals = .{ - .dest_type = (try self.emitType(src, old_inst.base.ty)).inst, - .operand = try self.resolveInst(new_body, old_inst.operand), + .lhs = (try self.emitType(old_inst.base.src, old_inst.base.ty)).inst, + .rhs = try self.resolveInst(new_body, old_inst.operand), }, .kw_args = .{}, }; @@ -1691,9 +1732,9 @@ const EmitZIR = struct { .cmp_gt => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_gt).?, .cmp_gt), .cmp_neq => try self.emitBinOp(inst.src, new_body, inst.castTag(.cmp_neq).?, .cmp_neq), - .bitcast => try self.emitCast(inst.src, new_body, inst.castTag(.bitcast).?, Inst.BitCast), - .intcast => try self.emitCast(inst.src, new_body, inst.castTag(.intcast).?, Inst.IntCast), - .floatcast => try self.emitCast(inst.src, new_body, inst.castTag(.floatcast).?, Inst.FloatCast), + .bitcast => try self.emitCast(inst.src, new_body, inst.castTag(.bitcast).?, .bitcast), + .intcast => try self.emitCast(inst.src, new_body, inst.castTag(.intcast).?, .intcast), + .floatcast => try self.emitCast(inst.src, new_body, inst.castTag(.floatcast).?, .floatcast), .block => blk: { const old_inst = inst.castTag(.block).?; diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index e817029b83..2c3f0631c8 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -19,13 +19,13 @@ pub fn addCases(ctx: *TestContext) !void { \\fn main() noreturn {} \\ \\export fn _start() noreturn { - \\ main(); + \\ main(); \\} , \\noreturn void main(void); \\ \\noreturn void _start(void) { - \\ main(); + \\ main(); \\} \\ \\noreturn void main(void) {} @@ -35,15 +35,15 @@ pub fn addCases(ctx: *TestContext) !void { // TODO: figure out a way to prevent asm constants from being generated ctx.c("inline asm", linux_x64, \\fn exitGood() void { - \\ asm volatile ("syscall" - \\ : - \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (0) - \\ ); + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ ); \\} \\ \\export fn _start() noreturn { - \\ exitGood(); + \\ exitGood(); \\} , \\#include @@ -55,36 +55,14 @@ pub fn addCases(ctx: *TestContext) !void { \\const char *const exitGood__anon_2 = "syscall"; \\ \\noreturn void _start(void) { - \\ exitGood(); + \\ exitGood(); \\} \\ \\void exitGood(void) { - \\ register size_t rax_constant __asm__("rax") = 231; - \\ register size_t rdi_constant __asm__("rdi") = 0; - \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); - \\ return; - \\} - \\ - ); - ctx.c("basic return", linux_x64, - \\fn main() u8 { - \\ return 103; - \\} - \\ - \\export fn _start() noreturn { - \\ _ = main(); - \\} - , - \\#include - \\ - \\uint8_t main(void); - \\ - \\noreturn void _start(void) { - \\ (void)main(); - \\} - \\ - \\uint8_t main(void) { - \\ return 103; + \\ register size_t rax_constant __asm__("rax") = 231; + \\ register size_t rdi_constant __asm__("rdi") = 0; + \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); + \\ return; \\} \\ );