From a59bcae59f1b45ef6585793e6b90e490593c4d31 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 20 Apr 2021 16:32:10 -0700 Subject: [PATCH] AstGen: basic defer implementation --- BRANCH_TODO | 13 + src/AstGen.zig | 782 +++++++++++++++++++++++++++++-------------------- src/Module.zig | 63 ++-- src/Zir.zig | 37 ++- 4 files changed, 546 insertions(+), 349 deletions(-) diff --git a/BRANCH_TODO b/BRANCH_TODO index e1cddc607a..2e518f562c 100644 --- a/BRANCH_TODO +++ b/BRANCH_TODO @@ -1,3 +1,7 @@ + * defer + - `break` + - `continue` + * nested function decl: how to refer to params? * look for cached zir code * save zir code to cache * keep track of file dependencies/dependants @@ -13,6 +17,15 @@ on each usingnamespace decl * handle usingnamespace cycles + * compile error for return inside defer expression + + * when block has noreturn statement + - avoid emitting defers + - compile error for unreachable code + + * detect `return error.Foo` and emit ZIR that unconditionally generates errdefers + * `return`: check return operand and generate errdefers if necessary + * have failed_trees and just put the file in there - this way we can emit all the parse errors not just the first one - but maybe we want just the first one? diff --git a/src/AstGen.zig b/src/AstGen.zig index 68f85a6b20..5ce3581e9e 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -37,6 +37,8 @@ string_table: std.StringHashMapUnmanaged(u32) = .{}, compile_errors: ArrayListUnmanaged(Zir.Inst.CompileErrors.Item) = .{}, /// String table indexes, keeps track of all `@import` operands. imports: std.AutoArrayHashMapUnmanaged(u32, void) = .{}, +/// The topmost block of the current function. +fn_block: ?*GenZir = null, pub fn addExtra(astgen: *AstGen, extra: anytype) Allocator.Error!u32 { const fields = std.meta.fields(@TypeOf(extra)); @@ -82,7 +84,7 @@ pub fn generate(gpa: *Allocator, file: *Scope.File) InnerError!Zir { // First few indexes of extra are reserved and set at the end. try astgen.extra.resize(gpa, @typeInfo(Zir.ExtraIndex).Enum.fields.len); - var gen_scope: Scope.GenZir = .{ + var gen_scope: GenZir = .{ .force_comptime = true, .parent = &file.base, .decl_node_index = 0, @@ -461,6 +463,8 @@ pub fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) Inn .local_var_decl => unreachable, // Handled in `blockExpr`. .simple_var_decl => unreachable, // Handled in `blockExpr`. .aligned_var_decl => unreachable, // Handled in `blockExpr`. + .@"defer" => unreachable, // Handled in `blockExpr`. + .@"errdefer" => unreachable, // Handled in `blockExpr`. .switch_case => unreachable, // Handled in `switchExpr`. .switch_case_one => unreachable, // Handled in `switchExpr`. @@ -818,8 +822,6 @@ pub fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) Inn .@"await" => return astgen.failNode(node, "async and related features are not yet supported", .{}), .@"resume" => return astgen.failNode(node, "async and related features are not yet supported", .{}), - .@"defer" => return astgen.failNode(node, "TODO implement astgen.expr for .defer", .{}), - .@"errdefer" => return astgen.failNode(node, "TODO implement astgen.expr for .errdefer", .{}), .@"try" => return tryExpr(gz, scope, rl, node_datas[node].lhs), .array_init_one, .array_init_one_comma => { @@ -1245,6 +1247,8 @@ fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: ast.Node.Index) Inn }, .local_val => scope = scope.cast(Scope.LocalVal).?.parent, .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent, + .defer_normal => @panic("TODO break/defer"), + .defer_error => @panic("TODO break/defer"), else => if (break_label != 0) { const label_name = try astgen.identifierTokenString(break_label); return astgen.failTok(break_label, "label not found: '{s}'", .{label_name}); @@ -1290,6 +1294,8 @@ fn continueExpr(parent_gz: *GenZir, parent_scope: *Scope, node: ast.Node.Index) }, .local_val => scope = scope.cast(Scope.LocalVal).?.parent, .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent, + .defer_normal => @panic("TODO continue/defer"), + .defer_error => @panic("TODO continue/defer"), else => if (break_label != 0) { const label_name = try astgen.identifierTokenString(break_label); return astgen.failTok(break_label, "label not found: '{s}'", .{label_name}); @@ -1354,6 +1360,7 @@ fn checkLabelRedefinition(astgen: *AstGen, parent_scope: *Scope, label: ast.Toke }, .local_val => scope = scope.cast(Scope.LocalVal).?.parent, .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent, + .defer_normal, .defer_error => scope = scope.cast(Scope.Defer).?.parent, else => return, } } @@ -1393,6 +1400,7 @@ fn labeledBlockExpr( .decl_node_index = gz.decl_node_index, .astgen = gz.astgen, .force_comptime = gz.force_comptime, + .ref_start_index = gz.ref_start_index, .instructions = .{}, // TODO @as here is working around a stage1 miscompilation bug :( .label = @as(?GenZir.Label, GenZir.Label{ @@ -1463,16 +1471,16 @@ fn blockExprStmts( var scope = parent_scope; for (statements) |statement| { - if (!gz.force_comptime) { - _ = try gz.addNode(.dbg_stmt_node, statement); - } switch (node_tags[statement]) { - .global_var_decl => scope = try varDecl(gz, scope, statement, &block_arena.allocator, tree.globalVarDecl(statement)), - .local_var_decl => scope = try varDecl(gz, scope, statement, &block_arena.allocator, tree.localVarDecl(statement)), - .simple_var_decl => scope = try varDecl(gz, scope, statement, &block_arena.allocator, tree.simpleVarDecl(statement)), + // zig fmt: off + .global_var_decl => scope = try varDecl(gz, scope, statement, &block_arena.allocator, tree.globalVarDecl(statement)), + .local_var_decl => scope = try varDecl(gz, scope, statement, &block_arena.allocator, tree.localVarDecl(statement)), + .simple_var_decl => scope = try varDecl(gz, scope, statement, &block_arena.allocator, tree.simpleVarDecl(statement)), .aligned_var_decl => scope = try varDecl(gz, scope, statement, &block_arena.allocator, tree.alignedVarDecl(statement)), - // zig fmt: off + .@"defer" => scope = try deferStmt(gz, scope, statement, &block_arena.allocator, .defer_normal), + .@"errdefer" => scope = try deferStmt(gz, scope, statement, &block_arena.allocator, .defer_error), + .assign => try assign(gz, scope, statement), .assign_bit_shift_left => try assignShift(gz, scope, statement, .shl), @@ -1489,300 +1497,355 @@ fn blockExprStmts( .assign_add_wrap => try assignOp(gz, scope, statement, .addwrap), .assign_mul => try assignOp(gz, scope, statement, .mul), .assign_mul_wrap => try assignOp(gz, scope, statement, .mulwrap), + + else => try unusedResultExpr(gz, scope, statement), // zig fmt: on - - else => { - // We need to emit an error if the result is not `noreturn` or `void`, but - // we want to avoid adding the ZIR instruction if possible for performance. - const maybe_unused_result = try expr(gz, scope, .none, statement); - const elide_check = if (gz.refToIndex(maybe_unused_result)) |inst| b: { - // Note that this array becomes invalid after appending more items to it - // in the above while loop. - const zir_tags = gz.astgen.instructions.items(.tag); - switch (zir_tags[inst]) { - // For some instructions, swap in a slightly different ZIR tag - // so we can avoid a separate ensure_result_used instruction. - .call_none_chkused => unreachable, - .call_none => { - zir_tags[inst] = .call_none_chkused; - break :b true; - }, - .call_chkused => unreachable, - .call => { - zir_tags[inst] = .call_chkused; - break :b true; - }, - - // ZIR instructions that might be a type other than `noreturn` or `void`. - .add, - .addwrap, - .alloc, - .alloc_mut, - .alloc_inferred, - .alloc_inferred_mut, - .array_cat, - .array_mul, - .array_type, - .array_type_sentinel, - .elem_type, - .indexable_ptr_len, - .as, - .as_node, - .@"asm", - .asm_volatile, - .bit_and, - .bitcast, - .bitcast_result_ptr, - .bit_or, - .block, - .block_inline, - .block_inline_var, - .loop, - .bool_br_and, - .bool_br_or, - .bool_not, - .bool_and, - .bool_or, - .call_compile_time, - .cmp_lt, - .cmp_lte, - .cmp_eq, - .cmp_gte, - .cmp_gt, - .cmp_neq, - .coerce_result_ptr, - .decl_ref, - .decl_val, - .load, - .div, - .elem_ptr, - .elem_val, - .elem_ptr_node, - .elem_val_node, - .field_ptr, - .field_val, - .field_ptr_named, - .field_val_named, - .func, - .func_inferred, - .int, - .float, - .float128, - .intcast, - .int_type, - .is_non_null, - .is_null, - .is_non_null_ptr, - .is_null_ptr, - .is_err, - .is_err_ptr, - .mod_rem, - .mul, - .mulwrap, - .param_type, - .ptrtoint, - .ref, - .shl, - .shr, - .str, - .sub, - .subwrap, - .negate, - .negate_wrap, - .typeof, - .typeof_elem, - .xor, - .optional_type, - .optional_type_from_ptr_elem, - .optional_payload_safe, - .optional_payload_unsafe, - .optional_payload_safe_ptr, - .optional_payload_unsafe_ptr, - .err_union_payload_safe, - .err_union_payload_unsafe, - .err_union_payload_safe_ptr, - .err_union_payload_unsafe_ptr, - .err_union_code, - .err_union_code_ptr, - .ptr_type, - .ptr_type_simple, - .enum_literal, - .enum_literal_small, - .merge_error_sets, - .error_union_type, - .bit_not, - .error_value, - .error_to_int, - .int_to_error, - .slice_start, - .slice_end, - .slice_sentinel, - .import, - .typeof_peer, - .switch_block, - .switch_block_multi, - .switch_block_else, - .switch_block_else_multi, - .switch_block_under, - .switch_block_under_multi, - .switch_block_ref, - .switch_block_ref_multi, - .switch_block_ref_else, - .switch_block_ref_else_multi, - .switch_block_ref_under, - .switch_block_ref_under_multi, - .switch_capture, - .switch_capture_ref, - .switch_capture_multi, - .switch_capture_multi_ref, - .switch_capture_else, - .switch_capture_else_ref, - .struct_init_empty, - .struct_init, - .struct_init_anon, - .array_init, - .array_init_anon, - .array_init_ref, - .array_init_anon_ref, - .union_init_ptr, - .field_type, - .field_type_ref, - .struct_decl, - .struct_decl_packed, - .struct_decl_extern, - .union_decl, - .enum_decl, - .enum_decl_nonexhaustive, - .opaque_decl, - .error_set_decl, - .int_to_enum, - .enum_to_int, - .type_info, - .size_of, - .bit_size_of, - .add_with_overflow, - .sub_with_overflow, - .mul_with_overflow, - .shl_with_overflow, - .log2_int_type, - .typeof_log2_int_type, - .ptr_to_int, - .align_of, - .bool_to_int, - .embed_file, - .error_name, - .sqrt, - .sin, - .cos, - .exp, - .exp2, - .log, - .log2, - .log10, - .fabs, - .floor, - .ceil, - .trunc, - .round, - .tag_name, - .reify, - .type_name, - .frame_type, - .frame_size, - .float_to_int, - .int_to_float, - .int_to_ptr, - .float_cast, - .int_cast, - .err_set_cast, - .ptr_cast, - .truncate, - .align_cast, - .has_decl, - .has_field, - .clz, - .ctz, - .pop_count, - .byte_swap, - .bit_reverse, - .div_exact, - .div_floor, - .div_trunc, - .mod, - .rem, - .shl_exact, - .shr_exact, - .bit_offset_of, - .byte_offset_of, - .cmpxchg_strong, - .cmpxchg_weak, - .splat, - .reduce, - .shuffle, - .atomic_load, - .atomic_rmw, - .atomic_store, - .mul_add, - .builtin_call, - .field_ptr_type, - .field_parent_ptr, - .memcpy, - .memset, - .builtin_async_call, - .c_import, - .extended, - => break :b false, - - // ZIR instructions that are always either `noreturn` or `void`. - .breakpoint, - .fence, - .dbg_stmt_node, - .ensure_result_used, - .ensure_result_non_error, - .@"export", - .set_eval_branch_quota, - .compile_log, - .ensure_err_payload_void, - .@"break", - .break_inline, - .condbr, - .condbr_inline, - .compile_error, - .ret_node, - .ret_tok, - .ret_coerce, - .@"unreachable", - .store, - .store_node, - .store_to_block_ptr, - .store_to_inferred_ptr, - .resolve_inferred_alloc, - .repeat, - .repeat_inline, - .validate_struct_init_ptr, - .validate_array_init_ptr, - .panic, - .set_align_stack, - .set_cold, - .set_float_mode, - .set_runtime_safety, - => break :b true, - } - } else switch (maybe_unused_result) { - .none => unreachable, - - .void_value, - .unreachable_value, - => true, - - else => false, - }; - if (!elide_check) { - _ = try gz.addUnNode(.ensure_result_used, maybe_unused_result, statement); - } - }, } } + + try genDefers(gz, parent_scope, scope, .none); +} + +fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: ast.Node.Index) InnerError!void { + try emitDbgNode(gz, statement); + // We need to emit an error if the result is not `noreturn` or `void`, but + // we want to avoid adding the ZIR instruction if possible for performance. + const maybe_unused_result = try expr(gz, scope, .none, statement); + const elide_check = if (gz.refToIndex(maybe_unused_result)) |inst| b: { + // Note that this array becomes invalid after appending more items to it + // in the above while loop. + const zir_tags = gz.astgen.instructions.items(.tag); + switch (zir_tags[inst]) { + // For some instructions, swap in a slightly different ZIR tag + // so we can avoid a separate ensure_result_used instruction. + .call_none_chkused => unreachable, + .call_none => { + zir_tags[inst] = .call_none_chkused; + break :b true; + }, + .call_chkused => unreachable, + .call => { + zir_tags[inst] = .call_chkused; + break :b true; + }, + + // ZIR instructions that might be a type other than `noreturn` or `void`. + .add, + .addwrap, + .alloc, + .alloc_mut, + .alloc_inferred, + .alloc_inferred_mut, + .array_cat, + .array_mul, + .array_type, + .array_type_sentinel, + .elem_type, + .indexable_ptr_len, + .as, + .as_node, + .@"asm", + .asm_volatile, + .bit_and, + .bitcast, + .bitcast_result_ptr, + .bit_or, + .block, + .block_inline, + .block_inline_var, + .loop, + .bool_br_and, + .bool_br_or, + .bool_not, + .bool_and, + .bool_or, + .call_compile_time, + .cmp_lt, + .cmp_lte, + .cmp_eq, + .cmp_gte, + .cmp_gt, + .cmp_neq, + .coerce_result_ptr, + .decl_ref, + .decl_val, + .load, + .div, + .elem_ptr, + .elem_val, + .elem_ptr_node, + .elem_val_node, + .field_ptr, + .field_val, + .field_ptr_named, + .field_val_named, + .func, + .func_inferred, + .int, + .float, + .float128, + .intcast, + .int_type, + .is_non_null, + .is_null, + .is_non_null_ptr, + .is_null_ptr, + .is_err, + .is_err_ptr, + .mod_rem, + .mul, + .mulwrap, + .param_type, + .ptrtoint, + .ref, + .shl, + .shr, + .str, + .sub, + .subwrap, + .negate, + .negate_wrap, + .typeof, + .typeof_elem, + .xor, + .optional_type, + .optional_type_from_ptr_elem, + .optional_payload_safe, + .optional_payload_unsafe, + .optional_payload_safe_ptr, + .optional_payload_unsafe_ptr, + .err_union_payload_safe, + .err_union_payload_unsafe, + .err_union_payload_safe_ptr, + .err_union_payload_unsafe_ptr, + .err_union_code, + .err_union_code_ptr, + .ptr_type, + .ptr_type_simple, + .enum_literal, + .enum_literal_small, + .merge_error_sets, + .error_union_type, + .bit_not, + .error_value, + .error_to_int, + .int_to_error, + .slice_start, + .slice_end, + .slice_sentinel, + .import, + .typeof_peer, + .switch_block, + .switch_block_multi, + .switch_block_else, + .switch_block_else_multi, + .switch_block_under, + .switch_block_under_multi, + .switch_block_ref, + .switch_block_ref_multi, + .switch_block_ref_else, + .switch_block_ref_else_multi, + .switch_block_ref_under, + .switch_block_ref_under_multi, + .switch_capture, + .switch_capture_ref, + .switch_capture_multi, + .switch_capture_multi_ref, + .switch_capture_else, + .switch_capture_else_ref, + .struct_init_empty, + .struct_init, + .struct_init_anon, + .array_init, + .array_init_anon, + .array_init_ref, + .array_init_anon_ref, + .union_init_ptr, + .field_type, + .field_type_ref, + .struct_decl, + .struct_decl_packed, + .struct_decl_extern, + .union_decl, + .enum_decl, + .enum_decl_nonexhaustive, + .opaque_decl, + .error_set_decl, + .int_to_enum, + .enum_to_int, + .type_info, + .size_of, + .bit_size_of, + .add_with_overflow, + .sub_with_overflow, + .mul_with_overflow, + .shl_with_overflow, + .log2_int_type, + .typeof_log2_int_type, + .ptr_to_int, + .align_of, + .bool_to_int, + .embed_file, + .error_name, + .sqrt, + .sin, + .cos, + .exp, + .exp2, + .log, + .log2, + .log10, + .fabs, + .floor, + .ceil, + .trunc, + .round, + .tag_name, + .reify, + .type_name, + .frame_type, + .frame_size, + .float_to_int, + .int_to_float, + .int_to_ptr, + .float_cast, + .int_cast, + .err_set_cast, + .ptr_cast, + .truncate, + .align_cast, + .has_decl, + .has_field, + .clz, + .ctz, + .pop_count, + .byte_swap, + .bit_reverse, + .div_exact, + .div_floor, + .div_trunc, + .mod, + .rem, + .shl_exact, + .shr_exact, + .bit_offset_of, + .byte_offset_of, + .cmpxchg_strong, + .cmpxchg_weak, + .splat, + .reduce, + .shuffle, + .atomic_load, + .atomic_rmw, + .atomic_store, + .mul_add, + .builtin_call, + .field_ptr_type, + .field_parent_ptr, + .memcpy, + .memset, + .builtin_async_call, + .c_import, + .extended, + => break :b false, + + // ZIR instructions that are always either `noreturn` or `void`. + .breakpoint, + .fence, + .dbg_stmt_node, + .ensure_result_used, + .ensure_result_non_error, + .@"export", + .set_eval_branch_quota, + .compile_log, + .ensure_err_payload_void, + .@"break", + .break_inline, + .condbr, + .condbr_inline, + .compile_error, + .ret_node, + .ret_tok, + .ret_coerce, + .@"unreachable", + .store, + .store_node, + .store_to_block_ptr, + .store_to_inferred_ptr, + .resolve_inferred_alloc, + .repeat, + .repeat_inline, + .validate_struct_init_ptr, + .validate_array_init_ptr, + .panic, + .set_align_stack, + .set_cold, + .set_float_mode, + .set_runtime_safety, + => break :b true, + } + } else switch (maybe_unused_result) { + .none => unreachable, + + .void_value, + .unreachable_value, + => true, + + else => false, + }; + if (!elide_check) { + _ = try gz.addUnNode(.ensure_result_used, maybe_unused_result, statement); + } +} + +fn genDefers( + gz: *GenZir, + outer_scope: *Scope, + inner_scope: *Scope, + err_code: Zir.Inst.Ref, +) InnerError!void { + const astgen = gz.astgen; + const tree = &astgen.file.tree; + const node_datas = tree.nodes.items(.data); + + var scope = inner_scope; + while (scope != outer_scope) { + switch (scope.tag) { + .gen_zir => scope = scope.cast(GenZir).?.parent, + .local_val => scope = scope.cast(Scope.LocalVal).?.parent, + .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent, + .defer_normal => { + const defer_scope = scope.cast(Scope.Defer).?; + scope = defer_scope.parent; + const expr_node = node_datas[defer_scope.defer_node].rhs; + try unusedResultExpr(gz, defer_scope.parent, expr_node); + }, + .defer_error => { + const defer_scope = scope.cast(Scope.Defer).?; + scope = defer_scope.parent; + if (err_code == .none) continue; + const expr_node = node_datas[defer_scope.defer_node].rhs; + try unusedResultExpr(gz, defer_scope.parent, expr_node); + }, + else => unreachable, + } + } +} + +fn deferStmt( + gz: *GenZir, + scope: *Scope, + node: ast.Node.Index, + block_arena: *Allocator, + scope_tag: Scope.Tag, +) InnerError!*Scope { + const defer_scope = try block_arena.create(Scope.Defer); + defer_scope.* = .{ + .base = .{ .tag = scope_tag }, + .parent = scope, + .defer_node = node, + }; + return &defer_scope.base; } fn varDecl( @@ -1792,6 +1855,7 @@ fn varDecl( block_arena: *Allocator, var_decl: ast.full.VarDecl, ) InnerError!*Scope { + try emitDbgNode(gz, node); const astgen = gz.astgen; if (var_decl.comptime_token) |comptime_token| { return astgen.failTok(comptime_token, "TODO implement comptime locals", .{}); @@ -1841,6 +1905,7 @@ fn varDecl( s = local_ptr.parent; }, .gen_zir => s = s.cast(GenZir).?.parent, + .defer_normal, .defer_error => s = s.cast(Scope.Defer).?.parent, .file => break, else => unreachable, }; @@ -1877,6 +1942,7 @@ fn varDecl( .parent = scope, .decl_node_index = gz.decl_node_index, .force_comptime = gz.force_comptime, + .ref_start_index = gz.ref_start_index, .astgen = astgen, }; defer init_scope.instructions.deinit(gpa); @@ -1984,7 +2050,14 @@ fn varDecl( } } +fn emitDbgNode(gz: *GenZir, node: ast.Node.Index) !void { + if (!gz.force_comptime) { + _ = try gz.addNode(.dbg_stmt_node, node); + } +} + fn assign(gz: *GenZir, scope: *Scope, infix_node: ast.Node.Index) InnerError!void { + try emitDbgNode(gz, infix_node); const astgen = gz.astgen; const tree = &astgen.file.tree; const node_datas = tree.nodes.items(.data); @@ -2011,6 +2084,7 @@ fn assignOp( infix_node: ast.Node.Index, op_inst_tag: Zir.Inst.Tag, ) InnerError!void { + try emitDbgNode(gz, infix_node); const astgen = gz.astgen; const tree = &astgen.file.tree; const node_datas = tree.nodes.items(.data); @@ -2033,6 +2107,7 @@ fn assignShift( infix_node: ast.Node.Index, op_inst_tag: Zir.Inst.Tag, ) InnerError!void { + try emitDbgNode(gz, infix_node); const astgen = gz.astgen; const tree = &astgen.file.tree; const node_datas = tree.nodes.items(.data); @@ -2257,12 +2332,12 @@ fn fnDecl( const param_types = try gpa.alloc(Zir.Inst.Ref, param_count); defer gpa.free(param_types); - var decl_gz: Scope.GenZir = .{ + var decl_gz: GenZir = .{ .force_comptime = true, .decl_node_index = fn_proto.ast.proto_node, .parent = &gz.base, .astgen = astgen, - .ref_start_index = @intCast(u32, Zir.Inst.Ref.typed_value_map.len + param_count), + .ref_start_index = @intCast(u32, Zir.Inst.Ref.typed_value_map.len), }; defer decl_gz.instructions.deinit(gpa); @@ -2357,7 +2432,7 @@ fn fnDecl( return astgen.failTok(fn_proto.ast.fn_token, "non-extern function is variadic", .{}); } - var fn_gz: Scope.GenZir = .{ + var fn_gz: GenZir = .{ .force_comptime = false, .decl_node_index = fn_proto.ast.proto_node, .parent = &decl_gz.base, @@ -2366,6 +2441,9 @@ fn fnDecl( }; defer fn_gz.instructions.deinit(gpa); + const prev_fn_block = astgen.fn_block; + astgen.fn_block = &fn_gz; + // Iterate over the parameters. We put the param names as the first N // items inside `extra` so that debug info later can refer to the parameter names // even while the respective source code is unloaded. @@ -2406,6 +2484,8 @@ fn fnDecl( _ = try fn_gz.addUnTok(.ret_coerce, .void_value, tree.lastToken(body_node)); } + astgen.fn_block = prev_fn_block; + break :func try decl_gz.addFunc(.{ .src_node = fn_proto.ast.proto_node, .ret_ty = return_type_inst, @@ -2580,9 +2660,18 @@ fn testDecl( scope: *Scope, node: ast.Node.Index, ) InnerError!void { + const gpa = astgen.gpa; const tree = &astgen.file.tree; const node_datas = tree.nodes.items(.data); - const test_expr = node_datas[node].rhs; + const body_node = node_datas[node].rhs; + + var decl_block: GenZir = .{ + .force_comptime = true, + .decl_node_index = node, + .parent = &gz.base, + .astgen = astgen, + }; + defer decl_block.instructions.deinit(gpa); const test_name: u32 = blk: { const main_tokens = tree.nodes.items(.main_token); @@ -2590,13 +2679,49 @@ fn testDecl( const test_token = main_tokens[node]; const str_lit_token = test_token + 1; if (token_tags[str_lit_token] == .string_literal) { - break :blk (try gz.strLitAsString(str_lit_token)).index; + break :blk (try decl_block.strLitAsString(str_lit_token)).index; } break :blk 0; }; - // TODO probably we want to put these into a block and store a list of them - const block_inst = try expr(gz, scope, .none, test_expr); + var fn_block: GenZir = .{ + .force_comptime = false, + .decl_node_index = node, + .parent = &decl_block.base, + .astgen = astgen, + }; + defer fn_block.instructions.deinit(gpa); + + const prev_fn_block = astgen.fn_block; + astgen.fn_block = &fn_block; + + const block_result = try expr(&fn_block, &fn_block.base, .none, body_node); + if (fn_block.instructions.items.len == 0 or !fn_block.refIsNoReturn(block_result)) { + // Since we are adding the return instruction here, we must handle the coercion. + // We do this by using the `ret_coerce` instruction. + _ = try fn_block.addUnTok(.ret_coerce, .void_value, tree.lastToken(body_node)); + } + + astgen.fn_block = prev_fn_block; + + const func_inst = try decl_block.addFunc(.{ + .src_node = node, + .ret_ty = .void_type, + .param_types = &[0]Zir.Inst.Ref{}, + .body = fn_block.instructions.items, + .cc = .none, + .lib_name = 0, + .is_var_args = false, + .is_inferred_error = true, + }); + + const block_inst = try gz.addBlock(.block_inline, node); + _ = try decl_block.addBreak(.break_inline, block_inst, func_inst); + try decl_block.setBlockBody(block_inst); + + // TODO collect these into a test decl list + _ = test_name; + _ = block_inst; } fn structDeclInner( @@ -2628,6 +2753,7 @@ fn structDeclInner( .decl_node_index = node, .astgen = astgen, .force_comptime = true, + .ref_start_index = gz.ref_start_index, }; defer block_scope.instructions.deinit(gpa); @@ -2943,6 +3069,7 @@ fn containerDecl( .decl_node_index = node, .astgen = astgen, .force_comptime = true, + .ref_start_index = gz.ref_start_index, }; defer block_scope.instructions.deinit(gpa); @@ -3168,6 +3295,7 @@ fn tryExpr( .decl_node_index = parent_gz.decl_node_index, .astgen = astgen, .force_comptime = parent_gz.force_comptime, + .ref_start_index = parent_gz.ref_start_index, .instructions = .{}, }; block_scope.setBreakResultLoc(rl); @@ -3200,11 +3328,13 @@ fn tryExpr( .decl_node_index = parent_gz.decl_node_index, .astgen = astgen, .force_comptime = block_scope.force_comptime, + .ref_start_index = parent_gz.ref_start_index, .instructions = .{}, }; defer then_scope.instructions.deinit(astgen.gpa); const err_code = try then_scope.addUnNode(err_ops[1], operand, node); + try genDefers(&then_scope, &astgen.fn_block.?.base, scope, err_code); const then_result = try then_scope.addUnNode(.ret_node, err_code, node); var else_scope: GenZir = .{ @@ -3212,6 +3342,7 @@ fn tryExpr( .decl_node_index = parent_gz.decl_node_index, .astgen = astgen, .force_comptime = block_scope.force_comptime, + .ref_start_index = parent_gz.ref_start_index, .instructions = .{}, }; defer else_scope.instructions.deinit(astgen.gpa); @@ -3264,6 +3395,7 @@ fn orelseCatchExpr( .decl_node_index = parent_gz.decl_node_index, .astgen = astgen, .force_comptime = parent_gz.force_comptime, + .ref_start_index = parent_gz.ref_start_index, .instructions = .{}, }; block_scope.setBreakResultLoc(rl); @@ -3300,6 +3432,7 @@ fn orelseCatchExpr( .decl_node_index = parent_gz.decl_node_index, .astgen = astgen, .force_comptime = block_scope.force_comptime, + .ref_start_index = parent_gz.ref_start_index, .instructions = .{}, }; defer then_scope.instructions.deinit(astgen.gpa); @@ -3332,6 +3465,7 @@ fn orelseCatchExpr( .decl_node_index = parent_gz.decl_node_index, .astgen = astgen, .force_comptime = block_scope.force_comptime, + .ref_start_index = parent_gz.ref_start_index, .instructions = .{}, }; defer else_scope.instructions.deinit(astgen.gpa); @@ -3532,6 +3666,7 @@ fn boolBinOp( .decl_node_index = gz.decl_node_index, .astgen = gz.astgen, .force_comptime = gz.force_comptime, + .ref_start_index = gz.ref_start_index, }; defer rhs_scope.instructions.deinit(gz.astgen.gpa); const rhs = try expr(&rhs_scope, &rhs_scope.base, bool_rl, node_datas[node].rhs); @@ -3558,6 +3693,7 @@ fn ifExpr( .decl_node_index = parent_gz.decl_node_index, .astgen = astgen, .force_comptime = parent_gz.force_comptime, + .ref_start_index = parent_gz.ref_start_index, .instructions = .{}, }; block_scope.setBreakResultLoc(rl); @@ -3608,6 +3744,7 @@ fn ifExpr( .decl_node_index = parent_gz.decl_node_index, .astgen = astgen, .force_comptime = block_scope.force_comptime, + .ref_start_index = parent_gz.ref_start_index, .instructions = .{}, }; defer then_scope.instructions.deinit(astgen.gpa); @@ -3662,6 +3799,7 @@ fn ifExpr( .decl_node_index = parent_gz.decl_node_index, .astgen = astgen, .force_comptime = block_scope.force_comptime, + .ref_start_index = parent_gz.ref_start_index, .instructions = .{}, }; defer else_scope.instructions.deinit(astgen.gpa); @@ -3812,6 +3950,7 @@ fn whileExpr( .decl_node_index = parent_gz.decl_node_index, .astgen = astgen, .force_comptime = parent_gz.force_comptime, + .ref_start_index = parent_gz.ref_start_index, .instructions = .{}, }; loop_scope.setBreakResultLoc(rl); @@ -3822,6 +3961,7 @@ fn whileExpr( .decl_node_index = parent_gz.decl_node_index, .astgen = astgen, .force_comptime = loop_scope.force_comptime, + .ref_start_index = parent_gz.ref_start_index, .instructions = .{}, }; defer continue_scope.instructions.deinit(astgen.gpa); @@ -3891,6 +4031,7 @@ fn whileExpr( .decl_node_index = parent_gz.decl_node_index, .astgen = astgen, .force_comptime = continue_scope.force_comptime, + .ref_start_index = parent_gz.ref_start_index, .instructions = .{}, }; defer then_scope.instructions.deinit(astgen.gpa); @@ -3942,6 +4083,7 @@ fn whileExpr( .decl_node_index = parent_gz.decl_node_index, .astgen = astgen, .force_comptime = continue_scope.force_comptime, + .ref_start_index = parent_gz.ref_start_index, .instructions = .{}, }; defer else_scope.instructions.deinit(astgen.gpa); @@ -4043,6 +4185,7 @@ fn forExpr( .decl_node_index = parent_gz.decl_node_index, .astgen = astgen, .force_comptime = parent_gz.force_comptime, + .ref_start_index = parent_gz.ref_start_index, .instructions = .{}, }; loop_scope.setBreakResultLoc(rl); @@ -4053,6 +4196,7 @@ fn forExpr( .decl_node_index = parent_gz.decl_node_index, .astgen = astgen, .force_comptime = loop_scope.force_comptime, + .ref_start_index = parent_gz.ref_start_index, .instructions = .{}, }; defer cond_scope.instructions.deinit(astgen.gpa); @@ -4096,6 +4240,7 @@ fn forExpr( .decl_node_index = parent_gz.decl_node_index, .astgen = astgen, .force_comptime = cond_scope.force_comptime, + .ref_start_index = parent_gz.ref_start_index, .instructions = .{}, }; defer then_scope.instructions.deinit(astgen.gpa); @@ -4141,6 +4286,7 @@ fn forExpr( .decl_node_index = parent_gz.decl_node_index, .astgen = astgen, .force_comptime = cond_scope.force_comptime, + .ref_start_index = parent_gz.ref_start_index, .instructions = .{}, }; defer else_scope.instructions.deinit(astgen.gpa); @@ -4443,6 +4589,7 @@ fn switchExpr( .decl_node_index = parent_gz.decl_node_index, .astgen = astgen, .force_comptime = parent_gz.force_comptime, + .ref_start_index = parent_gz.ref_start_index, .instructions = .{}, }; block_scope.setBreakResultLoc(rl); @@ -4457,6 +4604,7 @@ fn switchExpr( .decl_node_index = parent_gz.decl_node_index, .astgen = astgen, .force_comptime = parent_gz.force_comptime, + .ref_start_index = parent_gz.ref_start_index, .instructions = .{}, }; defer case_scope.instructions.deinit(gpa); @@ -4900,15 +5048,21 @@ fn ret(gz: *GenZir, scope: *Scope, node: ast.Node.Index) InnerError!Zir.Inst.Ref const main_tokens = tree.nodes.items(.main_token); const operand_node = node_datas[node].lhs; - const operand: Zir.Inst.Ref = if (operand_node != 0) operand: { + if (operand_node != 0) { const rl: ResultLoc = if (nodeMayNeedMemoryLocation(tree, operand_node)) .{ .ptr = try gz.addNodeExtended(.ret_ptr, node), } else .{ .ty = try gz.addNodeExtended(.ret_type, node), }; - break :operand try expr(gz, scope, rl, operand_node); - } else .void_value; - _ = try gz.addUnNode(.ret_node, operand, node); + const operand = try expr(gz, scope, rl, operand_node); + // TODO check operand to see if we need to generate errdefers + try genDefers(gz, &astgen.fn_block.?.base, scope, .none); + _ = try gz.addUnNode(.ret_node, operand, node); + return Zir.Inst.Ref.unreachable_value; + } + // Returning a void value; skip error defers. + try genDefers(gz, &astgen.fn_block.?.base, scope, .none); + _ = try gz.addUnNode(.ret_node, .void_value, node); return Zir.Inst.Ref.unreachable_value; } @@ -5309,6 +5463,7 @@ fn asRlPtr( .decl_node_index = parent_gz.decl_node_index, .astgen = astgen, .force_comptime = parent_gz.force_comptime, + .ref_start_index = parent_gz.ref_start_index, .instructions = .{}, }; defer as_scope.instructions.deinit(astgen.gpa); @@ -5998,6 +6153,7 @@ fn cImport( .decl_node_index = gz.decl_node_index, .astgen = astgen, .force_comptime = true, + .ref_start_index = gz.ref_start_index, .instructions = .{}, }; defer block_scope.instructions.deinit(gpa); @@ -6454,7 +6610,7 @@ fn rvalue( /// Given an identifier token, obtain the string for it. /// If the token uses @"" syntax, parses as a string, reports errors if applicable, -/// and allocates the result within `scope.arena()`. +/// and allocates the result within `astgen.arena`. /// Otherwise, returns a reference to the source code bytes directly. /// See also `appendIdentStr` and `parseStrLit`. pub fn identifierTokenString(astgen: *AstGen, token: ast.TokenIndex) InnerError![]const u8 { diff --git a/src/Module.zig b/src/Module.zig index 660ef9125d..18992d6006 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -514,31 +514,26 @@ pub const Scope = struct { pub const NameHash = [16]u8; pub fn cast(base: *Scope, comptime T: type) ?*T { + if (T == Defer) { + switch (base.tag) { + .defer_normal, .defer_error => return @fieldParentPtr(T, "base", base), + else => return null, + } + } if (base.tag != T.base_tag) return null; return @fieldParentPtr(T, "base", base); } - /// Returns the arena Allocator associated with the Decl of the Scope. - pub fn arena(scope: *Scope) *Allocator { - switch (scope.tag) { - .block => return scope.cast(Block).?.sema.arena, - .gen_zir => return scope.cast(GenZir).?.astgen.arena, - .local_val => return scope.cast(LocalVal).?.gen_zir.astgen.arena, - .local_ptr => return scope.cast(LocalPtr).?.gen_zir.astgen.arena, - .file => unreachable, - .namespace => unreachable, - .decl_ref => unreachable, - } - } - pub fn ownerDecl(scope: *Scope) ?*Decl { return switch (scope.tag) { .block => scope.cast(Block).?.sema.owner_decl, .gen_zir => unreachable, .local_val => unreachable, .local_ptr => unreachable, + .defer_normal => unreachable, + .defer_error => unreachable, .file => null, .namespace => null, .decl_ref => scope.cast(DeclRef).?.decl, @@ -551,6 +546,8 @@ pub const Scope = struct { .gen_zir => unreachable, .local_val => unreachable, .local_ptr => unreachable, + .defer_normal => unreachable, + .defer_error => unreachable, .file => null, .namespace => null, .decl_ref => scope.cast(DeclRef).?.decl, @@ -564,25 +561,14 @@ pub const Scope = struct { .gen_zir => unreachable, .local_val => unreachable, .local_ptr => unreachable, + .defer_normal => unreachable, + .defer_error => unreachable, .file => return scope.cast(File).?.namespace, .namespace => return scope.cast(Namespace).?, .decl_ref => return scope.cast(DeclRef).?.decl.namespace, } } - /// Asserts the scope is a child of a `GenZir` and returns it. - pub fn getGenZir(scope: *Scope) *GenZir { - return switch (scope.tag) { - .block => unreachable, - .gen_zir => scope.cast(GenZir).?, - .local_val => return scope.cast(LocalVal).?.gen_zir, - .local_ptr => return scope.cast(LocalPtr).?.gen_zir, - .file => unreachable, - .namespace => unreachable, - .decl_ref => unreachable, - }; - } - /// Asserts the scope has a parent which is a Namespace or File and /// returns the sub_file_path field. pub fn subFilePath(base: *Scope) []const u8 { @@ -593,6 +579,8 @@ pub const Scope = struct { .gen_zir => unreachable, .local_val => unreachable, .local_ptr => unreachable, + .defer_normal => unreachable, + .defer_error => unreachable, .decl_ref => unreachable, } } @@ -604,9 +592,11 @@ pub const Scope = struct { cur = switch (cur.tag) { .namespace => return @fieldParentPtr(Namespace, "base", cur).file_scope, .file => return @fieldParentPtr(File, "base", cur), - .gen_zir => @fieldParentPtr(GenZir, "base", cur).parent, - .local_val => @fieldParentPtr(LocalVal, "base", cur).parent, - .local_ptr => @fieldParentPtr(LocalPtr, "base", cur).parent, + .gen_zir => return @fieldParentPtr(GenZir, "base", cur).astgen.file, + .local_val => return @fieldParentPtr(LocalVal, "base", cur).gen_zir.astgen.file, + .local_ptr => return @fieldParentPtr(LocalPtr, "base", cur).gen_zir.astgen.file, + .defer_normal => @fieldParentPtr(Defer, "base", cur).parent, + .defer_error => @fieldParentPtr(Defer, "base", cur).parent, .block => return @fieldParentPtr(Block, "base", cur).src_decl.namespace.file_scope, .decl_ref => return @fieldParentPtr(DeclRef, "base", cur).decl.namespace.file_scope, }; @@ -634,6 +624,8 @@ pub const Scope = struct { /// `Decl` for use with `srcDecl` and `ownerDecl`. /// Has no parents or children. decl_ref, + defer_normal, + defer_error, }; /// The container that structs, enums, unions, and opaques have. @@ -1709,7 +1701,7 @@ pub const Scope = struct { pub const LocalVal = struct { pub const base_tag: Tag = .local_val; base: Scope = Scope{ .tag = base_tag }, - /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`. + /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`, `Defer`. parent: *Scope, gen_zir: *GenZir, name: []const u8, @@ -1724,7 +1716,7 @@ pub const Scope = struct { pub const LocalPtr = struct { pub const base_tag: Tag = .local_ptr; base: Scope = Scope{ .tag = base_tag }, - /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`. + /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`, `Defer`. parent: *Scope, gen_zir: *GenZir, name: []const u8, @@ -1733,6 +1725,13 @@ pub const Scope = struct { token_src: ast.TokenIndex, }; + pub const Defer = struct { + base: Scope, + /// Parents can be: `LocalVal`, `LocalPtr`, `GenZir`, `Defer`. + parent: *Scope, + defer_node: ast.Node.Index, + }; + pub const DeclRef = struct { pub const base_tag: Tag = .decl_ref; base: Scope = Scope{ .tag = base_tag }, @@ -3821,7 +3820,7 @@ pub fn failWithOwnedErrorMsg(mod: *Module, scope: *Scope, err_msg: *ErrorMsg) In } mod.failed_decls.putAssumeCapacityNoClobber(block.sema.owner_decl, err_msg); }, - .gen_zir, .local_val, .local_ptr => unreachable, + .gen_zir, .local_val, .local_ptr, .defer_normal, .defer_error => unreachable, .file => unreachable, .namespace => unreachable, .decl_ref => { diff --git a/src/Zir.zig b/src/Zir.zig index e18396688d..32626d306a 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -2304,6 +2304,7 @@ const Writer = struct { .byte_swap, .bit_reverse, .elem_type, + .bitcast_result_ptr, => try self.writeUnNode(stream, inst), .ref, @@ -2424,6 +2425,7 @@ const Writer = struct { .splat, .reduce, .atomic_load, + .bitcast, => try self.writePlNodeBin(stream, inst), .call, @@ -2509,13 +2511,40 @@ const Writer = struct { .switch_capture_else_ref, => try self.writeSwitchCapture(stream, inst), - .bitcast, - .bitcast_result_ptr, - .extended, - => try stream.writeAll("TODO)"), + .extended => try self.writeExtended(stream, inst), } } + fn writeExtended(self: *Writer, stream: anytype, inst: Inst.Index) !void { + const extended = self.code.instructions.items(.data)[inst].extended; + try stream.print("{s}(", .{@tagName(extended.opcode)}); + switch (extended.opcode) { + .ret_ptr, + .ret_type, + .this, + .ret_addr, + .error_return_trace, + .frame, + .frame_address, + .builtin_src, + => try self.writeExtNode(stream, extended), + + .func, + .c_undef, + .c_include, + .c_define, + .wasm_memory_size, + .wasm_memory_grow, + => try stream.writeAll("TODO))"), + } + } + + fn writeExtNode(self: *Writer, stream: anytype, extended: Inst.Extended.InstData) !void { + const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; + try stream.writeAll(")) "); + try self.writeSrc(stream, src); + } + fn writeBin(self: *Writer, stream: anytype, inst: Inst.Index) !void { const inst_data = self.code.instructions.items(.data)[inst].bin; try self.writeInstRef(stream, inst_data.lhs);