aboutsummaryrefslogtreecommitdiff
path: root/lib/std
diff options
context:
space:
mode:
authorMeghan Denny <hello@nektro.net>2023-11-24 17:09:08 -0800
committerMeghan Denny <hello@nektro.net>2023-11-24 17:09:08 -0800
commit121d995fcb061272202649a964f4788a3c9c8305 (patch)
tree197f5ff8d36fef30457a3566e6791719be723c29 /lib/std
parent84d58aaa1fc8508cb6ec9a455e8127ff13f17e16 (diff)
downloadzig-121d995fcb061272202649a964f4788a3c9c8305.tar.gz
zig-121d995fcb061272202649a964f4788a3c9c8305.zip
frontend: move AstRlAnnotate to std.zig namespace
Diffstat (limited to 'lib/std')
-rw-r--r--lib/std/zig.zig1
-rw-r--r--lib/std/zig/AstRlAnnotate.zig1105
2 files changed, 1106 insertions, 0 deletions
diff --git a/lib/std/zig.zig b/lib/std/zig.zig
index bd8cd26be1..2d2aaf17ba 100644
--- a/lib/std/zig.zig
+++ b/lib/std/zig.zig
@@ -18,6 +18,7 @@ pub const Ast = @import("zig/Ast.zig");
pub const system = @import("zig/system.zig");
pub const CrossTarget = @import("zig/CrossTarget.zig");
pub const BuiltinFn = @import("zig/BuiltinFn.zig");
+pub const AstRlAnnotate = @import("zig/AstRlAnnotate.zig");
// Character literal parsing
pub const ParsedCharLiteral = string_literal.ParsedCharLiteral;
diff --git a/lib/std/zig/AstRlAnnotate.zig b/lib/std/zig/AstRlAnnotate.zig
new file mode 100644
index 0000000000..a43de68951
--- /dev/null
+++ b/lib/std/zig/AstRlAnnotate.zig
@@ -0,0 +1,1105 @@
+//! AstRlAnnotate is a simple pass which runs over the AST before AstGen to
+//! determine which expressions require result locations.
+//!
+//! In some cases, AstGen can choose whether to provide a result pointer or to
+//! just use standard `break` instructions from a block. The latter choice can
+//! result in more efficient ZIR and runtime code, but does not allow for RLS to
+//! occur. Thus, we want to provide a real result pointer (from an alloc) only
+//! when necessary.
+//!
+//! To achive this, we need to determine which expressions require a result
+//! pointer. This pass is reponsible for analyzing all syntax forms which may
+//! provide a result location and, if sub-expressions consume this result
+//! pointer non-trivially (e.g. writing through field pointers), marking the
+//! node as requiring a result location.
+
+const std = @import("std");
+const AstRlAnnotate = @This();
+const Ast = std.zig.Ast;
+const Allocator = std.mem.Allocator;
+const AutoHashMapUnmanaged = std.AutoHashMapUnmanaged;
+const BuiltinFn = std.zig.BuiltinFn;
+const assert = std.debug.assert;
+
+gpa: Allocator,
+arena: Allocator,
+tree: *const Ast,
+
+/// Certain nodes are placed in this set under the following conditions:
+/// * if-else: either branch consumes the result location
+/// * labeled block: any break consumes the result location
+/// * switch: any prong consumes the result location
+/// * orelse/catch: the RHS expression consumes the result location
+/// * while/for: any break consumes the result location
+/// * @as: the second operand consumes the result location
+/// * const: the init expression consumes the result location
+/// * return: the return expression consumes the result location
+nodes_need_rl: RlNeededSet = .{},
+
+pub const RlNeededSet = AutoHashMapUnmanaged(Ast.Node.Index, void);
+
+const ResultInfo = packed struct {
+ /// Do we have a known result type?
+ have_type: bool,
+ /// Do we (potentially) have a result pointer? Note that this pointer's type
+ /// may not be known due to it being an inferred alloc.
+ have_ptr: bool,
+
+ const none: ResultInfo = .{ .have_type = false, .have_ptr = false };
+ const typed_ptr: ResultInfo = .{ .have_type = true, .have_ptr = true };
+ const inferred_ptr: ResultInfo = .{ .have_type = false, .have_ptr = true };
+ const type_only: ResultInfo = .{ .have_type = true, .have_ptr = false };
+};
+
+/// A labeled block or a loop. When this block is broken from, `consumes_res_ptr`
+/// should be set if the break expression consumed the result pointer.
+const Block = struct {
+ parent: ?*Block,
+ label: ?[]const u8,
+ is_loop: bool,
+ ri: ResultInfo,
+ consumes_res_ptr: bool,
+};
+
+pub fn annotate(gpa: Allocator, arena: Allocator, tree: Ast) Allocator.Error!RlNeededSet {
+ var astrl: AstRlAnnotate = .{
+ .gpa = gpa,
+ .arena = arena,
+ .tree = &tree,
+ };
+ defer astrl.deinit(gpa);
+
+ if (tree.errors.len != 0) {
+ // We can't perform analysis on a broken AST. AstGen will not run in
+ // this case.
+ return .{};
+ }
+
+ for (tree.containerDeclRoot().ast.members) |member_node| {
+ _ = try astrl.expr(member_node, null, ResultInfo.none);
+ }
+
+ return astrl.nodes_need_rl.move();
+}
+
+fn deinit(astrl: *AstRlAnnotate, gpa: Allocator) void {
+ astrl.nodes_need_rl.deinit(gpa);
+}
+
+fn containerDecl(
+ astrl: *AstRlAnnotate,
+ block: ?*Block,
+ full: Ast.full.ContainerDecl,
+) !void {
+ const tree = astrl.tree;
+ const token_tags = tree.tokens.items(.tag);
+ switch (token_tags[full.ast.main_token]) {
+ .keyword_struct => {
+ if (full.ast.arg != 0) {
+ _ = try astrl.expr(full.ast.arg, block, ResultInfo.type_only);
+ }
+ for (full.ast.members) |member_node| {
+ _ = try astrl.expr(member_node, block, ResultInfo.none);
+ }
+ },
+ .keyword_union => {
+ if (full.ast.arg != 0) {
+ _ = try astrl.expr(full.ast.arg, block, ResultInfo.type_only);
+ }
+ for (full.ast.members) |member_node| {
+ _ = try astrl.expr(member_node, block, ResultInfo.none);
+ }
+ },
+ .keyword_enum => {
+ if (full.ast.arg != 0) {
+ _ = try astrl.expr(full.ast.arg, block, ResultInfo.type_only);
+ }
+ for (full.ast.members) |member_node| {
+ _ = try astrl.expr(member_node, block, ResultInfo.none);
+ }
+ },
+ .keyword_opaque => {
+ for (full.ast.members) |member_node| {
+ _ = try astrl.expr(member_node, block, ResultInfo.none);
+ }
+ },
+ else => unreachable,
+ }
+}
+
+/// Returns true if `rl` provides a result pointer and the expression consumes it.
+fn expr(astrl: *AstRlAnnotate, node: Ast.Node.Index, block: ?*Block, ri: ResultInfo) Allocator.Error!bool {
+ const tree = astrl.tree;
+ const token_tags = tree.tokens.items(.tag);
+ const node_datas = tree.nodes.items(.data);
+ const node_tags = tree.nodes.items(.tag);
+ switch (node_tags[node]) {
+ .root,
+ .switch_case_one,
+ .switch_case_inline_one,
+ .switch_case,
+ .switch_case_inline,
+ .switch_range,
+ .for_range,
+ .asm_output,
+ .asm_input,
+ => unreachable,
+
+ .@"errdefer", .@"defer" => {
+ _ = try astrl.expr(node_datas[node].rhs, block, ResultInfo.none);
+ return false;
+ },
+
+ .container_field_init,
+ .container_field_align,
+ .container_field,
+ => {
+ const full = tree.fullContainerField(node).?;
+ _ = try astrl.expr(full.ast.type_expr, block, ResultInfo.type_only);
+ if (full.ast.align_expr != 0) {
+ _ = try astrl.expr(full.ast.align_expr, block, ResultInfo.type_only);
+ }
+ if (full.ast.value_expr != 0) {
+ _ = try astrl.expr(full.ast.value_expr, block, ResultInfo.type_only);
+ }
+ return false;
+ },
+ .@"usingnamespace" => {
+ _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.type_only);
+ return false;
+ },
+ .test_decl => {
+ _ = try astrl.expr(node_datas[node].rhs, block, ResultInfo.none);
+ return false;
+ },
+ .global_var_decl,
+ .local_var_decl,
+ .simple_var_decl,
+ .aligned_var_decl,
+ => {
+ const full = tree.fullVarDecl(node).?;
+ const init_ri = if (full.ast.type_node != 0) init_ri: {
+ _ = try astrl.expr(full.ast.type_node, block, ResultInfo.type_only);
+ break :init_ri ResultInfo.typed_ptr;
+ } else ResultInfo.inferred_ptr;
+ if (full.ast.init_node == 0) {
+ // No init node, so we're done.
+ return false;
+ }
+ switch (token_tags[full.ast.mut_token]) {
+ .keyword_const => {
+ const init_consumes_rl = try astrl.expr(full.ast.init_node, block, init_ri);
+ if (init_consumes_rl) {
+ try astrl.nodes_need_rl.putNoClobber(astrl.gpa, node, {});
+ }
+ return false;
+ },
+ .keyword_var => {
+ // We'll create an alloc either way, so don't care if the
+ // result pointer is consumed.
+ _ = try astrl.expr(full.ast.init_node, block, init_ri);
+ return false;
+ },
+ else => unreachable,
+ }
+ },
+ .assign_destructure => {
+ const lhs_count = tree.extra_data[node_datas[node].lhs];
+ const all_lhs = tree.extra_data[node_datas[node].lhs + 1 ..][0..lhs_count];
+ for (all_lhs) |lhs| {
+ _ = try astrl.expr(lhs, block, ResultInfo.none);
+ }
+ // We don't need to gather any meaningful data here, because destructures always use RLS
+ _ = try astrl.expr(node_datas[node].rhs, block, ResultInfo.none);
+ return false;
+ },
+ .assign => {
+ _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.none);
+ _ = try astrl.expr(node_datas[node].rhs, block, ResultInfo.typed_ptr);
+ return false;
+ },
+ .assign_shl,
+ .assign_shl_sat,
+ .assign_shr,
+ .assign_bit_and,
+ .assign_bit_or,
+ .assign_bit_xor,
+ .assign_div,
+ .assign_sub,
+ .assign_sub_wrap,
+ .assign_sub_sat,
+ .assign_mod,
+ .assign_add,
+ .assign_add_wrap,
+ .assign_add_sat,
+ .assign_mul,
+ .assign_mul_wrap,
+ .assign_mul_sat,
+ => {
+ _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.none);
+ _ = try astrl.expr(node_datas[node].rhs, block, ResultInfo.none);
+ return false;
+ },
+ .shl, .shr => {
+ _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.none);
+ _ = try astrl.expr(node_datas[node].rhs, block, ResultInfo.type_only);
+ return false;
+ },
+ .add,
+ .add_wrap,
+ .add_sat,
+ .sub,
+ .sub_wrap,
+ .sub_sat,
+ .mul,
+ .mul_wrap,
+ .mul_sat,
+ .div,
+ .mod,
+ .shl_sat,
+ .bit_and,
+ .bit_or,
+ .bit_xor,
+ .bang_equal,
+ .equal_equal,
+ .greater_than,
+ .greater_or_equal,
+ .less_than,
+ .less_or_equal,
+ .array_cat,
+ => {
+ _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.none);
+ _ = try astrl.expr(node_datas[node].rhs, block, ResultInfo.none);
+ return false;
+ },
+ .array_mult => {
+ _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.none);
+ _ = try astrl.expr(node_datas[node].rhs, block, ResultInfo.type_only);
+ return false;
+ },
+ .error_union, .merge_error_sets => {
+ _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.none);
+ _ = try astrl.expr(node_datas[node].rhs, block, ResultInfo.none);
+ return false;
+ },
+ .bool_and,
+ .bool_or,
+ => {
+ _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.type_only);
+ _ = try astrl.expr(node_datas[node].rhs, block, ResultInfo.type_only);
+ return false;
+ },
+ .bool_not => {
+ _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.type_only);
+ return false;
+ },
+ .bit_not, .negation, .negation_wrap => {
+ _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.none);
+ return false;
+ },
+
+ // These nodes are leaves and never consume a result location.
+ .identifier,
+ .string_literal,
+ .multiline_string_literal,
+ .number_literal,
+ .unreachable_literal,
+ .asm_simple,
+ .@"asm",
+ .enum_literal,
+ .error_value,
+ .anyframe_literal,
+ .@"continue",
+ .char_literal,
+ .error_set_decl,
+ => return false,
+
+ .builtin_call_two, .builtin_call_two_comma => {
+ if (node_datas[node].lhs == 0) {
+ return astrl.builtinCall(block, ri, node, &.{});
+ } else if (node_datas[node].rhs == 0) {
+ return astrl.builtinCall(block, ri, node, &.{node_datas[node].lhs});
+ } else {
+ return astrl.builtinCall(block, ri, node, &.{ node_datas[node].lhs, node_datas[node].rhs });
+ }
+ },
+ .builtin_call, .builtin_call_comma => {
+ const params = tree.extra_data[node_datas[node].lhs..node_datas[node].rhs];
+ return astrl.builtinCall(block, ri, node, params);
+ },
+
+ .call_one,
+ .call_one_comma,
+ .async_call_one,
+ .async_call_one_comma,
+ .call,
+ .call_comma,
+ .async_call,
+ .async_call_comma,
+ => {
+ var buf: [1]Ast.Node.Index = undefined;
+ const full = tree.fullCall(&buf, node).?;
+ _ = try astrl.expr(full.ast.fn_expr, block, ResultInfo.none);
+ for (full.ast.params) |param_node| {
+ _ = try astrl.expr(param_node, block, ResultInfo.type_only);
+ }
+ return switch (node_tags[node]) {
+ .call_one,
+ .call_one_comma,
+ .call,
+ .call_comma,
+ => false, // TODO: once function calls are passed result locations this will change
+ .async_call_one,
+ .async_call_one_comma,
+ .async_call,
+ .async_call_comma,
+ => ri.have_ptr, // always use result ptr for frames
+ else => unreachable,
+ };
+ },
+
+ .@"return" => {
+ if (node_datas[node].lhs != 0) {
+ const ret_val_consumes_rl = try astrl.expr(node_datas[node].lhs, block, ResultInfo.typed_ptr);
+ if (ret_val_consumes_rl) {
+ try astrl.nodes_need_rl.putNoClobber(astrl.gpa, node, {});
+ }
+ }
+ return false;
+ },
+
+ .field_access => {
+ _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.none);
+ return false;
+ },
+
+ .if_simple, .@"if" => {
+ const full = tree.fullIf(node).?;
+ if (full.error_token != null or full.payload_token != null) {
+ _ = try astrl.expr(full.ast.cond_expr, block, ResultInfo.none);
+ } else {
+ _ = try astrl.expr(full.ast.cond_expr, block, ResultInfo.type_only); // bool
+ }
+
+ if (full.ast.else_expr == 0) {
+ _ = try astrl.expr(full.ast.then_expr, block, ResultInfo.none);
+ return false;
+ } else {
+ const then_uses_rl = try astrl.expr(full.ast.then_expr, block, ri);
+ const else_uses_rl = try astrl.expr(full.ast.else_expr, block, ri);
+ const uses_rl = then_uses_rl or else_uses_rl;
+ if (uses_rl) try astrl.nodes_need_rl.putNoClobber(astrl.gpa, node, {});
+ return uses_rl;
+ }
+ },
+
+ .while_simple, .while_cont, .@"while" => {
+ const full = tree.fullWhile(node).?;
+ const label: ?[]const u8 = if (full.label_token) |label_token| label: {
+ break :label try astrl.identString(label_token);
+ } else null;
+ if (full.error_token != null or full.payload_token != null) {
+ _ = try astrl.expr(full.ast.cond_expr, block, ResultInfo.none);
+ } else {
+ _ = try astrl.expr(full.ast.cond_expr, block, ResultInfo.type_only); // bool
+ }
+ var new_block: Block = .{
+ .parent = block,
+ .label = label,
+ .is_loop = true,
+ .ri = ri,
+ .consumes_res_ptr = false,
+ };
+ if (full.ast.cont_expr != 0) {
+ _ = try astrl.expr(full.ast.cont_expr, &new_block, ResultInfo.none);
+ }
+ _ = try astrl.expr(full.ast.then_expr, &new_block, ResultInfo.none);
+ const else_consumes_rl = if (full.ast.else_expr != 0) else_rl: {
+ break :else_rl try astrl.expr(full.ast.else_expr, block, ri);
+ } else false;
+ if (new_block.consumes_res_ptr or else_consumes_rl) {
+ try astrl.nodes_need_rl.putNoClobber(astrl.gpa, node, {});
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ .for_simple, .@"for" => {
+ const full = tree.fullFor(node).?;
+ const label: ?[]const u8 = if (full.label_token) |label_token| label: {
+ break :label try astrl.identString(label_token);
+ } else null;
+ for (full.ast.inputs) |input| {
+ if (node_tags[input] == .for_range) {
+ _ = try astrl.expr(node_datas[input].lhs, block, ResultInfo.type_only);
+ if (node_datas[input].rhs != 0) {
+ _ = try astrl.expr(node_datas[input].rhs, block, ResultInfo.type_only);
+ }
+ } else {
+ _ = try astrl.expr(input, block, ResultInfo.none);
+ }
+ }
+ var new_block: Block = .{
+ .parent = block,
+ .label = label,
+ .is_loop = true,
+ .ri = ri,
+ .consumes_res_ptr = false,
+ };
+ _ = try astrl.expr(full.ast.then_expr, &new_block, ResultInfo.none);
+ const else_consumes_rl = if (full.ast.else_expr != 0) else_rl: {
+ break :else_rl try astrl.expr(full.ast.else_expr, block, ri);
+ } else false;
+ if (new_block.consumes_res_ptr or else_consumes_rl) {
+ try astrl.nodes_need_rl.putNoClobber(astrl.gpa, node, {});
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ .slice_open => {
+ _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.none);
+ _ = try astrl.expr(node_datas[node].rhs, block, ResultInfo.type_only);
+ return false;
+ },
+ .slice => {
+ const extra = tree.extraData(node_datas[node].rhs, Ast.Node.Slice);
+ _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.none);
+ _ = try astrl.expr(extra.start, block, ResultInfo.type_only);
+ _ = try astrl.expr(extra.end, block, ResultInfo.type_only);
+ return false;
+ },
+ .slice_sentinel => {
+ const extra = tree.extraData(node_datas[node].rhs, Ast.Node.SliceSentinel);
+ _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.none);
+ _ = try astrl.expr(extra.start, block, ResultInfo.type_only);
+ if (extra.end != 0) {
+ _ = try astrl.expr(extra.end, block, ResultInfo.type_only);
+ }
+ _ = try astrl.expr(extra.sentinel, block, ResultInfo.none);
+ return false;
+ },
+ .deref => {
+ _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.none);
+ return false;
+ },
+ .address_of => {
+ _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.none);
+ return false;
+ },
+ .optional_type => {
+ _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.type_only);
+ return false;
+ },
+ .grouped_expression,
+ .@"try",
+ .@"await",
+ .@"nosuspend",
+ .unwrap_optional,
+ => return astrl.expr(node_datas[node].lhs, block, ri),
+
+ .block_two, .block_two_semicolon => {
+ if (node_datas[node].lhs == 0) {
+ return astrl.blockExpr(block, ri, node, &.{});
+ } else if (node_datas[node].rhs == 0) {
+ return astrl.blockExpr(block, ri, node, &.{node_datas[node].lhs});
+ } else {
+ return astrl.blockExpr(block, ri, node, &.{ node_datas[node].lhs, node_datas[node].rhs });
+ }
+ },
+ .block, .block_semicolon => {
+ const statements = tree.extra_data[node_datas[node].lhs..node_datas[node].rhs];
+ return astrl.blockExpr(block, ri, node, statements);
+ },
+ .anyframe_type => {
+ _ = try astrl.expr(node_datas[node].rhs, block, ResultInfo.type_only);
+ return false;
+ },
+ .@"catch", .@"orelse" => {
+ _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.none);
+ const rhs_consumes_rl = try astrl.expr(node_datas[node].rhs, block, ri);
+ if (rhs_consumes_rl) {
+ try astrl.nodes_need_rl.putNoClobber(astrl.gpa, node, {});
+ }
+ return rhs_consumes_rl;
+ },
+
+ .ptr_type_aligned,
+ .ptr_type_sentinel,
+ .ptr_type,
+ .ptr_type_bit_range,
+ => {
+ const full = tree.fullPtrType(node).?;
+ _ = try astrl.expr(full.ast.child_type, block, ResultInfo.type_only);
+ if (full.ast.sentinel != 0) {
+ _ = try astrl.expr(full.ast.sentinel, block, ResultInfo.type_only);
+ }
+ if (full.ast.addrspace_node != 0) {
+ _ = try astrl.expr(full.ast.addrspace_node, block, ResultInfo.type_only);
+ }
+ if (full.ast.align_node != 0) {
+ _ = try astrl.expr(full.ast.align_node, block, ResultInfo.type_only);
+ }
+ if (full.ast.bit_range_start != 0) {
+ assert(full.ast.bit_range_end != 0);
+ _ = try astrl.expr(full.ast.bit_range_start, block, ResultInfo.type_only);
+ _ = try astrl.expr(full.ast.bit_range_end, block, ResultInfo.type_only);
+ }
+ return false;
+ },
+
+ .container_decl,
+ .container_decl_trailing,
+ .container_decl_arg,
+ .container_decl_arg_trailing,
+ .container_decl_two,
+ .container_decl_two_trailing,
+ .tagged_union,
+ .tagged_union_trailing,
+ .tagged_union_enum_tag,
+ .tagged_union_enum_tag_trailing,
+ .tagged_union_two,
+ .tagged_union_two_trailing,
+ => {
+ var buf: [2]Ast.Node.Index = undefined;
+ try astrl.containerDecl(block, tree.fullContainerDecl(&buf, node).?);
+ return false;
+ },
+
+ .@"break" => {
+ if (node_datas[node].rhs == 0) {
+ // Breaks with void are not interesting
+ return false;
+ }
+
+ var opt_cur_block = block;
+ if (node_datas[node].lhs == 0) {
+ // No label - we're breaking from a loop.
+ while (opt_cur_block) |cur_block| : (opt_cur_block = cur_block.parent) {
+ if (cur_block.is_loop) break;
+ }
+ } else {
+ const break_label = try astrl.identString(node_datas[node].lhs);
+ while (opt_cur_block) |cur_block| : (opt_cur_block = cur_block.parent) {
+ const block_label = cur_block.label orelse continue;
+ if (std.mem.eql(u8, block_label, break_label)) break;
+ }
+ }
+
+ if (opt_cur_block) |target_block| {
+ const consumes_break_rl = try astrl.expr(node_datas[node].rhs, block, target_block.ri);
+ if (consumes_break_rl) target_block.consumes_res_ptr = true;
+ } else {
+ // No corresponding scope to break from - AstGen will emit an error.
+ _ = try astrl.expr(node_datas[node].rhs, block, ResultInfo.none);
+ }
+
+ return false;
+ },
+
+ .array_type => {
+ _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.type_only);
+ _ = try astrl.expr(node_datas[node].rhs, block, ResultInfo.type_only);
+ return false;
+ },
+ .array_type_sentinel => {
+ const extra = tree.extraData(node_datas[node].rhs, Ast.Node.ArrayTypeSentinel);
+ _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.type_only);
+ _ = try astrl.expr(extra.elem_type, block, ResultInfo.type_only);
+ _ = try astrl.expr(extra.sentinel, block, ResultInfo.type_only);
+ return false;
+ },
+ .array_access => {
+ _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.none);
+ _ = try astrl.expr(node_datas[node].rhs, block, ResultInfo.type_only);
+ return false;
+ },
+ .@"comptime" => {
+ // AstGen will emit an error if the scope is already comptime, so we can assume it is
+ // not. This means the result location is not forwarded.
+ _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.none);
+ return false;
+ },
+ .@"switch", .switch_comma => {
+ const operand_node = node_datas[node].lhs;
+ const extra = tree.extraData(node_datas[node].rhs, Ast.Node.SubRange);
+ const case_nodes = tree.extra_data[extra.start..extra.end];
+
+ _ = try astrl.expr(operand_node, block, ResultInfo.none);
+
+ var any_prong_consumed_rl = false;
+ for (case_nodes) |case_node| {
+ const case = tree.fullSwitchCase(case_node).?;
+ for (case.ast.values) |item_node| {
+ if (node_tags[item_node] == .switch_range) {
+ _ = try astrl.expr(node_datas[item_node].lhs, block, ResultInfo.none);
+ _ = try astrl.expr(node_datas[item_node].rhs, block, ResultInfo.none);
+ } else {
+ _ = try astrl.expr(item_node, block, ResultInfo.none);
+ }
+ }
+ if (try astrl.expr(case.ast.target_expr, block, ri)) {
+ any_prong_consumed_rl = true;
+ }
+ }
+ if (any_prong_consumed_rl) {
+ try astrl.nodes_need_rl.putNoClobber(astrl.gpa, node, {});
+ }
+ return any_prong_consumed_rl;
+ },
+ .@"suspend" => {
+ _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.none);
+ return false;
+ },
+ .@"resume" => {
+ _ = try astrl.expr(node_datas[node].lhs, block, ResultInfo.none);
+ return false;
+ },
+
+ .array_init_one,
+ .array_init_one_comma,
+ .array_init_dot_two,
+ .array_init_dot_two_comma,
+ .array_init_dot,
+ .array_init_dot_comma,
+ .array_init,
+ .array_init_comma,
+ => {
+ var buf: [2]Ast.Node.Index = undefined;
+ const full = tree.fullArrayInit(&buf, node).?;
+
+ if (full.ast.type_expr != 0) {
+ // Explicitly typed init does not participate in RLS
+ _ = try astrl.expr(full.ast.type_expr, block, ResultInfo.none);
+ for (full.ast.elements) |elem_init| {
+ _ = try astrl.expr(elem_init, block, ResultInfo.type_only);
+ }
+ return false;
+ }
+
+ if (ri.have_type) {
+ // Always forward type information
+ // If we have a result pointer, we use and forward it
+ for (full.ast.elements) |elem_init| {
+ _ = try astrl.expr(elem_init, block, ri);
+ }
+ return ri.have_ptr;
+ } else {
+ // Untyped init does not consume result location
+ for (full.ast.elements) |elem_init| {
+ _ = try astrl.expr(elem_init, block, ResultInfo.none);
+ }
+ return false;
+ }
+ },
+
+ .struct_init_one,
+ .struct_init_one_comma,
+ .struct_init_dot_two,
+ .struct_init_dot_two_comma,
+ .struct_init_dot,
+ .struct_init_dot_comma,
+ .struct_init,
+ .struct_init_comma,
+ => {
+ var buf: [2]Ast.Node.Index = undefined;
+ const full = tree.fullStructInit(&buf, node).?;
+
+ if (full.ast.type_expr != 0) {
+ // Explicitly typed init does not participate in RLS
+ _ = try astrl.expr(full.ast.type_expr, block, ResultInfo.none);
+ for (full.ast.fields) |field_init| {
+ _ = try astrl.expr(field_init, block, ResultInfo.type_only);
+ }
+ return false;
+ }
+
+ if (ri.have_type) {
+ // Always forward type information
+ // If we have a result pointer, we use and forward it
+ for (full.ast.fields) |field_init| {
+ _ = try astrl.expr(field_init, block, ri);
+ }
+ return ri.have_ptr;
+ } else {
+ // Untyped init does not consume result location
+ for (full.ast.fields) |field_init| {
+ _ = try astrl.expr(field_init, block, ResultInfo.none);
+ }
+ return false;
+ }
+ },
+
+ .fn_proto_simple,
+ .fn_proto_multi,
+ .fn_proto_one,
+ .fn_proto,
+ .fn_decl,
+ => {
+ var buf: [1]Ast.Node.Index = undefined;
+ const full = tree.fullFnProto(&buf, node).?;
+ const body_node = if (node_tags[node] == .fn_decl) node_datas[node].rhs else 0;
+ {
+ var it = full.iterate(tree);
+ while (it.next()) |param| {
+ if (param.anytype_ellipsis3 == null) {
+ _ = try astrl.expr(param.type_expr, block, ResultInfo.type_only);
+ }
+ }
+ }
+ if (full.ast.align_expr != 0) {
+ _ = try astrl.expr(full.ast.align_expr, block, ResultInfo.type_only);
+ }
+ if (full.ast.addrspace_expr != 0) {
+ _ = try astrl.expr(full.ast.addrspace_expr, block, ResultInfo.type_only);
+ }
+ if (full.ast.section_expr != 0) {
+ _ = try astrl.expr(full.ast.section_expr, block, ResultInfo.type_only);
+ }
+ if (full.ast.callconv_expr != 0) {
+ _ = try astrl.expr(full.ast.callconv_expr, block, ResultInfo.type_only);
+ }
+ _ = try astrl.expr(full.ast.return_type, block, ResultInfo.type_only);
+ if (body_node != 0) {
+ _ = try astrl.expr(body_node, block, ResultInfo.none);
+ }
+ return false;
+ },
+ }
+}
+
+fn identString(astrl: *AstRlAnnotate, token: Ast.TokenIndex) ![]const u8 {
+ const tree = astrl.tree;
+ const token_tags = tree.tokens.items(.tag);
+ assert(token_tags[token] == .identifier);
+ const ident_name = tree.tokenSlice(token);
+ if (!std.mem.startsWith(u8, ident_name, "@")) {
+ return ident_name;
+ }
+ return std.zig.string_literal.parseAlloc(astrl.arena, ident_name[1..]) catch |err| switch (err) {
+ error.OutOfMemory => error.OutOfMemory,
+ error.InvalidLiteral => "", // This pass can safely return garbage on invalid AST
+ };
+}
+
+fn blockExpr(astrl: *AstRlAnnotate, parent_block: ?*Block, ri: ResultInfo, node: Ast.Node.Index, statements: []const Ast.Node.Index) !bool {
+ const tree = astrl.tree;
+ const token_tags = tree.tokens.items(.tag);
+ const main_tokens = tree.nodes.items(.main_token);
+
+ const lbrace = main_tokens[node];
+ if (token_tags[lbrace - 1] == .colon and
+ token_tags[lbrace - 2] == .identifier)
+ {
+ // Labeled block
+ var new_block: Block = .{
+ .parent = parent_block,
+ .label = try astrl.identString(lbrace - 2),
+ .is_loop = false,
+ .ri = ri,
+ .consumes_res_ptr = false,
+ };
+ for (statements) |statement| {
+ _ = try astrl.expr(statement, &new_block, ResultInfo.none);
+ }
+ if (new_block.consumes_res_ptr) {
+ try astrl.nodes_need_rl.putNoClobber(astrl.gpa, node, {});
+ }
+ return new_block.consumes_res_ptr;
+ } else {
+ // Unlabeled block
+ for (statements) |statement| {
+ _ = try astrl.expr(statement, parent_block, ResultInfo.none);
+ }
+ return false;
+ }
+}
+
+fn builtinCall(astrl: *AstRlAnnotate, block: ?*Block, ri: ResultInfo, node: Ast.Node.Index, args: []const Ast.Node.Index) !bool {
+ _ = ri; // Currently, no builtin consumes its result location.
+
+ const tree = astrl.tree;
+ const main_tokens = tree.nodes.items(.main_token);
+ const builtin_token = main_tokens[node];
+ const builtin_name = tree.tokenSlice(builtin_token);
+ const info = BuiltinFn.list.get(builtin_name) orelse return false;
+ if (info.param_count) |expected| {
+ if (expected != args.len) return false;
+ }
+ switch (info.tag) {
+ .import => return false,
+ .compile_log, .TypeOf => {
+ for (args) |arg_node| {
+ _ = try astrl.expr(arg_node, block, ResultInfo.none);
+ }
+ return false;
+ },
+ .as => {
+ _ = try astrl.expr(args[0], block, ResultInfo.type_only);
+ _ = try astrl.expr(args[1], block, ResultInfo.type_only);
+ return false;
+ },
+ .bit_cast => {
+ _ = try astrl.expr(args[0], block, ResultInfo.none);
+ return false;
+ },
+ .union_init => {
+ _ = try astrl.expr(args[0], block, ResultInfo.type_only);
+ _ = try astrl.expr(args[1], block, ResultInfo.type_only);
+ _ = try astrl.expr(args[2], block, ResultInfo.type_only);
+ return false;
+ },
+ .c_import => {
+ _ = try astrl.expr(args[0], block, ResultInfo.none);
+ return false;
+ },
+ .min, .max => {
+ for (args) |arg_node| {
+ _ = try astrl.expr(arg_node, block, ResultInfo.none);
+ }
+ return false;
+ },
+ .@"export" => {
+ _ = try astrl.expr(args[0], block, ResultInfo.none);
+ _ = try astrl.expr(args[1], block, ResultInfo.type_only);
+ return false;
+ },
+ .@"extern" => {
+ _ = try astrl.expr(args[0], block, ResultInfo.type_only);
+ _ = try astrl.expr(args[1], block, ResultInfo.type_only);
+ return false;
+ },
+ // These builtins take no args and do not consume the result pointer.
+ .src,
+ .This,
+ .return_address,
+ .error_return_trace,
+ .frame,
+ .breakpoint,
+ .in_comptime,
+ .panic,
+ .trap,
+ .c_va_start,
+ => return false,
+ // TODO: this is a workaround for llvm/llvm-project#68409
+ // Zig tracking issue: #16876
+ .frame_address => return true,
+ // These builtins take a single argument with a known result type, but do not consume their
+ // result pointer.
+ .size_of,
+ .bit_size_of,
+ .align_of,
+ .compile_error,
+ .set_eval_branch_quota,
+ .int_from_bool,
+ .int_from_error,
+ .error_from_int,
+ .embed_file,
+ .error_name,
+ .set_runtime_safety,
+ .Type,
+ .c_undef,
+ .c_include,
+ .wasm_memory_size,
+ .splat,
+ .fence,
+ .set_float_mode,
+ .set_align_stack,
+ .set_cold,
+ .type_info,
+ .work_item_id,
+ .work_group_size,
+ .work_group_id,
+ => {
+ _ = try astrl.expr(args[0], block, ResultInfo.type_only);
+ return false;
+ },
+ // These builtins take a single argument with no result information and do not consume their
+ // result pointer.
+ .int_from_ptr,
+ .int_from_enum,
+ .sqrt,
+ .sin,
+ .cos,
+ .tan,
+ .exp,
+ .exp2,
+ .log,
+ .log2,
+ .log10,
+ .abs,
+ .floor,
+ .ceil,
+ .trunc,
+ .round,
+ .tag_name,
+ .type_name,
+ .Frame,
+ .frame_size,
+ .int_from_float,
+ .float_from_int,
+ .ptr_from_int,
+ .enum_from_int,
+ .float_cast,
+ .int_cast,
+ .truncate,
+ .error_cast,
+ .ptr_cast,
+ .align_cast,
+ .addrspace_cast,
+ .const_cast,
+ .volatile_cast,
+ .clz,
+ .ctz,
+ .pop_count,
+ .byte_swap,
+ .bit_reverse,
+ => {
+ _ = try astrl.expr(args[0], block, ResultInfo.none);
+ return false;
+ },
+ .div_exact,
+ .div_floor,
+ .div_trunc,
+ .mod,
+ .rem,
+ => {
+ _ = try astrl.expr(args[0], block, ResultInfo.none);
+ _ = try astrl.expr(args[1], block, ResultInfo.none);
+ return false;
+ },
+ .shl_exact, .shr_exact => {
+ _ = try astrl.expr(args[0], block, ResultInfo.none);
+ _ = try astrl.expr(args[1], block, ResultInfo.type_only);
+ return false;
+ },
+ .bit_offset_of,
+ .offset_of,
+ .field_parent_ptr,
+ .has_decl,
+ .has_field,
+ .field,
+ => {
+ _ = try astrl.expr(args[0], block, ResultInfo.type_only);
+ _ = try astrl.expr(args[1], block, ResultInfo.type_only);
+ return false;
+ },
+ .wasm_memory_grow => {
+ _ = try astrl.expr(args[0], block, ResultInfo.type_only);
+ _ = try astrl.expr(args[1], block, ResultInfo.type_only);
+ return false;
+ },
+ .c_define => {
+ _ = try astrl.expr(args[0], block, ResultInfo.type_only);
+ _ = try astrl.expr(args[1], block, ResultInfo.none);
+ return false;
+ },
+ .reduce => {
+ _ = try astrl.expr(args[0], block, ResultInfo.type_only);
+ _ = try astrl.expr(args[1], block, ResultInfo.none);
+ return false;
+ },
+ .add_with_overflow, .sub_with_overflow, .mul_with_overflow, .shl_with_overflow => {
+ _ = try astrl.expr(args[0], block, ResultInfo.none);
+ _ = try astrl.expr(args[1], block, ResultInfo.none);
+ return false;
+ },
+ .atomic_load => {
+ _ = try astrl.expr(args[0], block, ResultInfo.type_only);
+ _ = try astrl.expr(args[1], block, ResultInfo.none);
+ _ = try astrl.expr(args[2], block, ResultInfo.type_only);
+ return false;
+ },
+ .atomic_rmw => {
+ _ = try astrl.expr(args[0], block, ResultInfo.type_only);
+ _ = try astrl.expr(args[1], block, ResultInfo.none);
+ _ = try astrl.expr(args[2], block, ResultInfo.type_only);
+ _ = try astrl.expr(args[3], block, ResultInfo.type_only);
+ _ = try astrl.expr(args[4], block, ResultInfo.type_only);
+ return false;
+ },
+ .atomic_store => {
+ _ = try astrl.expr(args[0], block, ResultInfo.type_only);
+ _ = try astrl.expr(args[1], block, ResultInfo.none);
+ _ = try astrl.expr(args[2], block, ResultInfo.type_only);
+ _ = try astrl.expr(args[3], block, ResultInfo.type_only);
+ return false;
+ },
+ .mul_add => {
+ _ = try astrl.expr(args[0], block, ResultInfo.type_only);
+ _ = try astrl.expr(args[1], block, ResultInfo.type_only);
+ _ = try astrl.expr(args[2], block, ResultInfo.type_only);
+ return false;
+ },
+ .call => {
+ _ = try astrl.expr(args[0], block, ResultInfo.type_only);
+ _ = try astrl.expr(args[1], block, ResultInfo.none);
+ _ = try astrl.expr(args[2], block, ResultInfo.none);
+ return false;
+ },
+ .memcpy => {
+ _ = try astrl.expr(args[0], block, ResultInfo.none);
+ _ = try astrl.expr(args[1], block, ResultInfo.none);
+ return false;
+ },
+ .memset => {
+ _ = try astrl.expr(args[0], block, ResultInfo.none);
+ _ = try astrl.expr(args[1], block, ResultInfo.type_only);
+ return false;
+ },
+ .shuffle => {
+ _ = try astrl.expr(args[0], block, ResultInfo.type_only);
+ _ = try astrl.expr(args[1], block, ResultInfo.none);
+ _ = try astrl.expr(args[2], block, ResultInfo.none);
+ _ = try astrl.expr(args[3], block, ResultInfo.none);
+ return false;
+ },
+ .select => {
+ _ = try astrl.expr(args[0], block, ResultInfo.type_only);
+ _ = try astrl.expr(args[1], block, ResultInfo.none);
+ _ = try astrl.expr(args[2], block, ResultInfo.none);
+ _ = try astrl.expr(args[3], block, ResultInfo.none);
+ return false;
+ },
+ .async_call => {
+ _ = try astrl.expr(args[0], block, ResultInfo.none);
+ _ = try astrl.expr(args[1], block, ResultInfo.none);
+ _ = try astrl.expr(args[2], block, ResultInfo.none);
+ _ = try astrl.expr(args[3], block, ResultInfo.none);
+ return false; // buffer passed as arg for frame data
+ },
+ .Vector => {
+ _ = try astrl.expr(args[0], block, ResultInfo.type_only);
+ _ = try astrl.expr(args[1], block, ResultInfo.type_only);
+ return false;
+ },
+ .prefetch => {
+ _ = try astrl.expr(args[0], block, ResultInfo.none);
+ _ = try astrl.expr(args[1], block, ResultInfo.type_only);
+ return false;
+ },
+ .c_va_arg => {
+ _ = try astrl.expr(args[0], block, ResultInfo.none);
+ _ = try astrl.expr(args[1], block, ResultInfo.type_only);
+ return false;
+ },
+ .c_va_copy => {
+ _ = try astrl.expr(args[0], block, ResultInfo.none);
+ return false;
+ },
+ .c_va_end => {
+ _ = try astrl.expr(args[0], block, ResultInfo.none);
+ return false;
+ },
+ .cmpxchg_strong, .cmpxchg_weak => {
+ _ = try astrl.expr(args[0], block, ResultInfo.none);
+ _ = try astrl.expr(args[1], block, ResultInfo.type_only);
+ _ = try astrl.expr(args[2], block, ResultInfo.type_only);
+ _ = try astrl.expr(args[3], block, ResultInfo.type_only);
+ _ = try astrl.expr(args[4], block, ResultInfo.type_only);
+ return false;
+ },
+ }
+}