zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit 402f87a213b9f3d5e19d5a1d412d8963957d8849 (tree)
parent 68f4eb0f67b6e5f7c20332e79c8e8decb07748ee
Author: Isaac Freund <ifreund@ifreund.xyz>
Date:   Sun, 28 Mar 2021 19:08:42 +0200

stage2: rename WipZirCode => AstGen, astgen.zig => AstGen.zig

Diffstat:
MCMakeLists.txt | 2+-
Asrc/AstGen.zig | 3979+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Module.zig | 370++++++++++++++++++++++++++++++-------------------------------------------------
Dsrc/astgen.zig | 3881-------------------------------------------------------------------------------
Msrc/translate_c.zig | 2+-
Msrc/zir.zig | 4++--
6 files changed, 4123 insertions(+), 4115 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -538,7 +538,7 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/ThreadPool.zig" "${CMAKE_SOURCE_DIR}/src/TypedValue.zig" "${CMAKE_SOURCE_DIR}/src/WaitGroup.zig" - "${CMAKE_SOURCE_DIR}/src/astgen.zig" + "${CMAKE_SOURCE_DIR}/src/AstGen.zig" "${CMAKE_SOURCE_DIR}/src/clang.zig" "${CMAKE_SOURCE_DIR}/src/clang_options.zig" "${CMAKE_SOURCE_DIR}/src/clang_options_data.zig" diff --git a/src/AstGen.zig b/src/AstGen.zig @@ -0,0 +1,3979 @@ +//! A Work-In-Progress `zir.Code`. This is a shared parent of all +//! `GenZir` scopes. Once the `zir.Code` is produced, this struct +//! is deinitialized. +//! The `GenZir.finish` function converts this to a `zir.Code`. + +const AstGen = @This(); + +const std = @import("std"); +const ast = std.zig.ast; +const mem = std.mem; +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; +const ArrayListUnmanaged = std.ArrayListUnmanaged; + +const Value = @import("value.zig").Value; +const Type = @import("type.zig").Type; +const TypedValue = @import("TypedValue.zig"); +const zir = @import("zir.zig"); +const Module = @import("Module.zig"); +const trace = @import("tracy.zig").trace; +const Scope = Module.Scope; +const InnerError = Module.InnerError; +const Decl = Module.Decl; +const BuiltinFn = @import("BuiltinFn.zig"); + +instructions: std.MultiArrayList(zir.Inst) = .{}, +string_bytes: ArrayListUnmanaged(u8) = .{}, +extra: ArrayListUnmanaged(u32) = .{}, +decl_map: std.StringArrayHashMapUnmanaged(void) = .{}, +decls: ArrayListUnmanaged(*Decl) = .{}, +/// The end of special indexes. `zir.Inst.Ref` subtracts against this number to convert +/// to `zir.Inst.Index`. The default here is correct if there are 0 parameters. +ref_start_index: u32 = zir.Inst.Ref.typed_value_map.len, +mod: *Module, +decl: *Decl, +arena: *Allocator, + +/// Call `deinit` on the result. +pub fn init(mod: *Module, decl: *Decl, arena: *Allocator) !AstGen { + var astgen: AstGen = .{ + .mod = mod, + .decl = decl, + .arena = arena, + }; + // Must be a block instruction at index 0 with the root body. + try astgen.instructions.append(mod.gpa, .{ + .tag = .block, + .data = .{ .pl_node = .{ + .src_node = 0, + .payload_index = undefined, + } }, + }); + return astgen; +} + +pub fn addExtra(astgen: *AstGen, extra: anytype) Allocator.Error!u32 { + const fields = std.meta.fields(@TypeOf(extra)); + try astgen.extra.ensureCapacity(astgen.mod.gpa, astgen.extra.items.len + fields.len); + return addExtraAssumeCapacity(astgen, extra); +} + +pub fn addExtraAssumeCapacity(astgen: *AstGen, extra: anytype) u32 { + const fields = std.meta.fields(@TypeOf(extra)); + const result = @intCast(u32, astgen.extra.items.len); + inline for (fields) |field| { + astgen.extra.appendAssumeCapacity(switch (field.field_type) { + u32 => @field(extra, field.name), + zir.Inst.Ref => @enumToInt(@field(extra, field.name)), + else => @compileError("bad field type"), + }); + } + return result; +} + +pub fn appendRefs(astgen: *AstGen, refs: []const zir.Inst.Ref) !void { + const coerced = @bitCast([]const u32, refs); + return astgen.extra.appendSlice(astgen.mod.gpa, coerced); +} + +pub fn appendRefsAssumeCapacity(astgen: *AstGen, refs: []const zir.Inst.Ref) void { + const coerced = @bitCast([]const u32, refs); + astgen.extra.appendSliceAssumeCapacity(coerced); +} + +pub fn refIsNoReturn(astgen: AstGen, inst_ref: zir.Inst.Ref) bool { + if (inst_ref == .unreachable_value) return true; + if (astgen.refToIndex(inst_ref)) |inst_index| { + return astgen.instructions.items(.tag)[inst_index].isNoReturn(); + } + return false; +} + +pub fn indexToRef(astgen: AstGen, inst: zir.Inst.Index) zir.Inst.Ref { + return @intToEnum(zir.Inst.Ref, astgen.ref_start_index + inst); +} + +pub fn refToIndex(astgen: AstGen, inst: zir.Inst.Ref) ?zir.Inst.Index { + const ref_int = @enumToInt(inst); + if (ref_int >= astgen.ref_start_index) { + return ref_int - astgen.ref_start_index; + } else { + return null; + } +} + +pub fn deinit(astgen: *AstGen) void { + const gpa = astgen.mod.gpa; + astgen.instructions.deinit(gpa); + astgen.extra.deinit(gpa); + astgen.string_bytes.deinit(gpa); + astgen.decl_map.deinit(gpa); + astgen.decls.deinit(gpa); +} + +pub const ResultLoc = union(enum) { + /// The expression is the right-hand side of assignment to `_`. Only the side-effects of the + /// expression should be generated. The result instruction from the expression must + /// be ignored. + discard, + /// The expression has an inferred type, and it will be evaluated as an rvalue. + none, + /// The expression must generate a pointer rather than a value. For example, the left hand side + /// of an assignment uses this kind of result location. + ref, + /// The expression will be coerced into this type, but it will be evaluated as an rvalue. + ty: zir.Inst.Ref, + /// The expression must store its result into this typed pointer. The result instruction + /// from the expression must be ignored. + ptr: zir.Inst.Ref, + /// The expression must store its result into this allocation, which has an inferred type. + /// The result instruction from the expression must be ignored. + /// Always an instruction with tag `alloc_inferred`. + inferred_ptr: zir.Inst.Ref, + /// 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. + /// The result instruction from the expression must be ignored. + bitcasted_ptr: zir.Inst.Ref, + /// 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`. + /// The result instruction from the expression must be ignored. + block_ptr: *Module.Scope.GenZir, + + pub const Strategy = struct { + elide_store_to_block_ptr_instructions: bool, + tag: Tag, + + pub const Tag = enum { + /// Both branches will use break_void; result location is used to communicate the + /// result instruction. + break_void, + /// Use break statements to pass the block result value, and call rvalue() at + /// the end depending on rl. Also elide the store_to_block_ptr instructions + /// depending on rl. + break_operand, + }; + }; +}; + +pub fn typeExpr(mod: *Module, scope: *Scope, type_node: ast.Node.Index) InnerError!zir.Inst.Ref { + return expr(mod, scope, .{ .ty = .type_type }, type_node); +} + +fn lvalExpr(mod: *Module, scope: *Scope, node: ast.Node.Index) InnerError!zir.Inst.Ref { + const tree = scope.tree(); + const node_tags = tree.nodes.items(.tag); + const main_tokens = tree.nodes.items(.main_token); + switch (node_tags[node]) { + .root => unreachable, + .@"usingnamespace" => unreachable, + .test_decl => unreachable, + .global_var_decl => unreachable, + .local_var_decl => unreachable, + .simple_var_decl => unreachable, + .aligned_var_decl => unreachable, + .switch_case => unreachable, + .switch_case_one => unreachable, + .container_field_init => unreachable, + .container_field_align => unreachable, + .container_field => unreachable, + .asm_output => unreachable, + .asm_input => unreachable, + + .assign, + .assign_bit_and, + .assign_bit_or, + .assign_bit_shift_left, + .assign_bit_shift_right, + .assign_bit_xor, + .assign_div, + .assign_sub, + .assign_sub_wrap, + .assign_mod, + .assign_add, + .assign_add_wrap, + .assign_mul, + .assign_mul_wrap, + .add, + .add_wrap, + .sub, + .sub_wrap, + .mul, + .mul_wrap, + .div, + .mod, + .bit_and, + .bit_or, + .bit_shift_left, + .bit_shift_right, + .bit_xor, + .bang_equal, + .equal_equal, + .greater_than, + .greater_or_equal, + .less_than, + .less_or_equal, + .array_cat, + .array_mult, + .bool_and, + .bool_or, + .@"asm", + .asm_simple, + .string_literal, + .integer_literal, + .call, + .call_comma, + .async_call, + .async_call_comma, + .call_one, + .call_one_comma, + .async_call_one, + .async_call_one_comma, + .unreachable_literal, + .@"return", + .@"if", + .if_simple, + .@"while", + .while_simple, + .while_cont, + .bool_not, + .address_of, + .float_literal, + .undefined_literal, + .true_literal, + .false_literal, + .null_literal, + .optional_type, + .block, + .block_semicolon, + .block_two, + .block_two_semicolon, + .@"break", + .ptr_type_aligned, + .ptr_type_sentinel, + .ptr_type, + .ptr_type_bit_range, + .array_type, + .array_type_sentinel, + .enum_literal, + .multiline_string_literal, + .char_literal, + .@"defer", + .@"errdefer", + .@"catch", + .error_union, + .merge_error_sets, + .switch_range, + .@"await", + .bit_not, + .negation, + .negation_wrap, + .@"resume", + .@"try", + .slice, + .slice_open, + .slice_sentinel, + .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, + .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, + .@"switch", + .switch_comma, + .@"for", + .for_simple, + .@"suspend", + .@"continue", + .@"anytype", + .fn_proto_simple, + .fn_proto_multi, + .fn_proto_one, + .fn_proto, + .fn_decl, + .anyframe_type, + .anyframe_literal, + .error_set_decl, + .container_decl, + .container_decl_trailing, + .container_decl_two, + .container_decl_two_trailing, + .container_decl_arg, + .container_decl_arg_trailing, + .tagged_union, + .tagged_union_trailing, + .tagged_union_two, + .tagged_union_two_trailing, + .tagged_union_enum_tag, + .tagged_union_enum_tag_trailing, + .@"comptime", + .@"nosuspend", + .error_value, + => return mod.failNode(scope, node, "invalid left-hand side to assignment", .{}), + + .builtin_call, + .builtin_call_comma, + .builtin_call_two, + .builtin_call_two_comma, + => { + const builtin_token = main_tokens[node]; + const builtin_name = tree.tokenSlice(builtin_token); + // If the builtin is an invalid name, we don't cause an error here; instead + // let it pass, and the error will be "invalid builtin function" later. + if (BuiltinFn.list.get(builtin_name)) |info| { + if (!info.allows_lvalue) { + return mod.failNode(scope, node, "invalid left-hand side to assignment", .{}); + } + } + }, + + // These can be assigned to. + .unwrap_optional, + .deref, + .field_access, + .array_access, + .identifier, + .grouped_expression, + .@"orelse", + => {}, + } + return expr(mod, scope, .ref, node); +} + +/// Turn Zig AST into untyped ZIR istructions. +/// When `rl` is discard, ptr, inferred_ptr, bitcasted_ptr, or inferred_ptr, the +/// result instruction can be used to inspect whether it is isNoReturn() but that is it, +/// it must otherwise not be used. +pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) InnerError!zir.Inst.Ref { + const tree = scope.tree(); + const main_tokens = tree.nodes.items(.main_token); + const token_tags = tree.tokens.items(.tag); + const node_datas = tree.nodes.items(.data); + const node_tags = tree.nodes.items(.tag); + + const gz = scope.getGenZir(); + + switch (node_tags[node]) { + .root => unreachable, // Top-level declaration. + .@"usingnamespace" => unreachable, // Top-level declaration. + .test_decl => unreachable, // Top-level declaration. + .container_field_init => unreachable, // Top-level declaration. + .container_field_align => unreachable, // Top-level declaration. + .container_field => unreachable, // Top-level declaration. + .fn_decl => unreachable, // Top-level declaration. + + .global_var_decl => unreachable, // Handled in `blockExpr`. + .local_var_decl => unreachable, // Handled in `blockExpr`. + .simple_var_decl => unreachable, // Handled in `blockExpr`. + .aligned_var_decl => unreachable, // Handled in `blockExpr`. + + .switch_case => unreachable, // Handled in `switchExpr`. + .switch_case_one => unreachable, // Handled in `switchExpr`. + .switch_range => unreachable, // Handled in `switchExpr`. + + .asm_output => unreachable, // Handled in `asmExpr`. + .asm_input => unreachable, // Handled in `asmExpr`. + + .assign => { + try assign(mod, scope, node); + return rvalue(mod, scope, rl, .void_value, node); + }, + .assign_bit_and => { + try assignOp(mod, scope, node, .bit_and); + return rvalue(mod, scope, rl, .void_value, node); + }, + .assign_bit_or => { + try assignOp(mod, scope, node, .bit_or); + return rvalue(mod, scope, rl, .void_value, node); + }, + .assign_bit_shift_left => { + try assignOp(mod, scope, node, .shl); + return rvalue(mod, scope, rl, .void_value, node); + }, + .assign_bit_shift_right => { + try assignOp(mod, scope, node, .shr); + return rvalue(mod, scope, rl, .void_value, node); + }, + .assign_bit_xor => { + try assignOp(mod, scope, node, .xor); + return rvalue(mod, scope, rl, .void_value, node); + }, + .assign_div => { + try assignOp(mod, scope, node, .div); + return rvalue(mod, scope, rl, .void_value, node); + }, + .assign_sub => { + try assignOp(mod, scope, node, .sub); + return rvalue(mod, scope, rl, .void_value, node); + }, + .assign_sub_wrap => { + try assignOp(mod, scope, node, .subwrap); + return rvalue(mod, scope, rl, .void_value, node); + }, + .assign_mod => { + try assignOp(mod, scope, node, .mod_rem); + return rvalue(mod, scope, rl, .void_value, node); + }, + .assign_add => { + try assignOp(mod, scope, node, .add); + return rvalue(mod, scope, rl, .void_value, node); + }, + .assign_add_wrap => { + try assignOp(mod, scope, node, .addwrap); + return rvalue(mod, scope, rl, .void_value, node); + }, + .assign_mul => { + try assignOp(mod, scope, node, .mul); + return rvalue(mod, scope, rl, .void_value, node); + }, + .assign_mul_wrap => { + try assignOp(mod, scope, node, .mulwrap); + return rvalue(mod, scope, rl, .void_value, node); + }, + + .add => return simpleBinOp(mod, scope, rl, node, .add), + .add_wrap => return simpleBinOp(mod, scope, rl, node, .addwrap), + .sub => return simpleBinOp(mod, scope, rl, node, .sub), + .sub_wrap => return simpleBinOp(mod, scope, rl, node, .subwrap), + .mul => return simpleBinOp(mod, scope, rl, node, .mul), + .mul_wrap => return simpleBinOp(mod, scope, rl, node, .mulwrap), + .div => return simpleBinOp(mod, scope, rl, node, .div), + .mod => return simpleBinOp(mod, scope, rl, node, .mod_rem), + .bit_and => return simpleBinOp(mod, scope, rl, node, .bit_and), + .bit_or => return simpleBinOp(mod, scope, rl, node, .bit_or), + .bit_shift_left => return simpleBinOp(mod, scope, rl, node, .shl), + .bit_shift_right => return simpleBinOp(mod, scope, rl, node, .shr), + .bit_xor => return simpleBinOp(mod, scope, rl, node, .xor), + + .bang_equal => return simpleBinOp(mod, scope, rl, node, .cmp_neq), + .equal_equal => return simpleBinOp(mod, scope, rl, node, .cmp_eq), + .greater_than => return simpleBinOp(mod, scope, rl, node, .cmp_gt), + .greater_or_equal => return simpleBinOp(mod, scope, rl, node, .cmp_gte), + .less_than => return simpleBinOp(mod, scope, rl, node, .cmp_lt), + .less_or_equal => return simpleBinOp(mod, scope, rl, node, .cmp_lte), + + .array_cat => return simpleBinOp(mod, scope, rl, node, .array_cat), + .array_mult => return simpleBinOp(mod, scope, rl, node, .array_mul), + + .error_union => return simpleBinOp(mod, scope, rl, node, .error_union_type), + .merge_error_sets => return simpleBinOp(mod, scope, rl, node, .merge_error_sets), + + .bool_and => return boolBinOp(mod, scope, rl, node, .bool_br_and), + .bool_or => return boolBinOp(mod, scope, rl, node, .bool_br_or), + + .bool_not => return boolNot(mod, scope, rl, node), + .bit_not => return bitNot(mod, scope, rl, node), + + .negation => return negation(mod, scope, rl, node, .negate), + .negation_wrap => return negation(mod, scope, rl, node, .negate_wrap), + + .identifier => return identifier(mod, scope, rl, node), + + .asm_simple => return asmExpr(mod, scope, rl, node, tree.asmSimple(node)), + .@"asm" => return asmExpr(mod, scope, rl, node, tree.asmFull(node)), + + .string_literal => return stringLiteral(mod, scope, rl, node), + .multiline_string_literal => return multilineStringLiteral(mod, scope, rl, node), + + .integer_literal => return integerLiteral(mod, scope, rl, node), + + .builtin_call_two, .builtin_call_two_comma => { + if (node_datas[node].lhs == 0) { + const params = [_]ast.Node.Index{}; + return builtinCall(mod, scope, rl, node, &params); + } else if (node_datas[node].rhs == 0) { + const params = [_]ast.Node.Index{node_datas[node].lhs}; + return builtinCall(mod, scope, rl, node, &params); + } else { + const params = [_]ast.Node.Index{ node_datas[node].lhs, node_datas[node].rhs }; + return builtinCall(mod, scope, rl, node, &params); + } + }, + .builtin_call, .builtin_call_comma => { + const params = tree.extra_data[node_datas[node].lhs..node_datas[node].rhs]; + return builtinCall(mod, scope, rl, node, params); + }, + + .call_one, .call_one_comma, .async_call_one, .async_call_one_comma => { + var params: [1]ast.Node.Index = undefined; + return callExpr(mod, scope, rl, node, tree.callOne(&params, node)); + }, + .call, .call_comma, .async_call, .async_call_comma => { + return callExpr(mod, scope, rl, node, tree.callFull(node)); + }, + + .unreachable_literal => { + _ = try gz.addAsIndex(.{ + .tag = .@"unreachable", + .data = .{ .@"unreachable" = .{ + .safety = true, + .src_node = gz.astgen.decl.nodeIndexToRelative(node), + } }, + }); + return zir.Inst.Ref.unreachable_value; + }, + .@"return" => return ret(mod, scope, node), + .field_access => return fieldAccess(mod, scope, rl, node), + .float_literal => return floatLiteral(mod, scope, rl, node), + + .if_simple => return ifExpr(mod, scope, rl, node, tree.ifSimple(node)), + .@"if" => return ifExpr(mod, scope, rl, node, tree.ifFull(node)), + + .while_simple => return whileExpr(mod, scope, rl, node, tree.whileSimple(node)), + .while_cont => return whileExpr(mod, scope, rl, node, tree.whileCont(node)), + .@"while" => return whileExpr(mod, scope, rl, node, tree.whileFull(node)), + + .for_simple => return forExpr(mod, scope, rl, node, tree.forSimple(node)), + .@"for" => return forExpr(mod, scope, rl, node, tree.forFull(node)), + + .slice_open => { + const lhs = try expr(mod, scope, .ref, node_datas[node].lhs); + const start = try expr(mod, scope, .{ .ty = .usize_type }, node_datas[node].rhs); + const result = try gz.addPlNode(.slice_start, node, zir.Inst.SliceStart{ + .lhs = lhs, + .start = start, + }); + return rvalue(mod, scope, rl, result, node); + }, + .slice => { + const lhs = try expr(mod, scope, .ref, node_datas[node].lhs); + const extra = tree.extraData(node_datas[node].rhs, ast.Node.Slice); + const start = try expr(mod, scope, .{ .ty = .usize_type }, extra.start); + const end = try expr(mod, scope, .{ .ty = .usize_type }, extra.end); + const result = try gz.addPlNode(.slice_end, node, zir.Inst.SliceEnd{ + .lhs = lhs, + .start = start, + .end = end, + }); + return rvalue(mod, scope, rl, result, node); + }, + .slice_sentinel => { + const lhs = try expr(mod, scope, .ref, node_datas[node].lhs); + const extra = tree.extraData(node_datas[node].rhs, ast.Node.SliceSentinel); + const start = try expr(mod, scope, .{ .ty = .usize_type }, extra.start); + const end = try expr(mod, scope, .{ .ty = .usize_type }, extra.end); + const sentinel = try expr(mod, scope, .{ .ty = .usize_type }, extra.sentinel); + const result = try gz.addPlNode(.slice_sentinel, node, zir.Inst.SliceSentinel{ + .lhs = lhs, + .start = start, + .end = end, + .sentinel = sentinel, + }); + return rvalue(mod, scope, rl, result, node); + }, + + .deref => { + const lhs = try expr(mod, scope, .none, node_datas[node].lhs); + const result = try gz.addUnNode(.load, lhs, node); + return rvalue(mod, scope, rl, result, node); + }, + .address_of => { + const result = try expr(mod, scope, .ref, node_datas[node].lhs); + return rvalue(mod, scope, rl, result, node); + }, + .undefined_literal => return rvalue(mod, scope, rl, .undef, node), + .true_literal => return rvalue(mod, scope, rl, .bool_true, node), + .false_literal => return rvalue(mod, scope, rl, .bool_false, node), + .null_literal => return rvalue(mod, scope, rl, .null_value, node), + .optional_type => { + const operand = try typeExpr(mod, scope, node_datas[node].lhs); + const result = try gz.addUnNode(.optional_type, operand, node); + return rvalue(mod, scope, rl, result, node); + }, + .unwrap_optional => switch (rl) { + .ref => return gz.addUnNode( + .optional_payload_safe_ptr, + try expr(mod, scope, .ref, node_datas[node].lhs), + node, + ), + else => return rvalue(mod, scope, rl, try gz.addUnNode( + .optional_payload_safe, + try expr(mod, scope, .none, node_datas[node].lhs), + node, + ), node), + }, + .block_two, .block_two_semicolon => { + const statements = [2]ast.Node.Index{ node_datas[node].lhs, node_datas[node].rhs }; + if (node_datas[node].lhs == 0) { + return blockExpr(mod, scope, rl, node, statements[0..0]); + } else if (node_datas[node].rhs == 0) { + return blockExpr(mod, scope, rl, node, statements[0..1]); + } else { + return blockExpr(mod, scope, rl, node, statements[0..2]); + } + }, + .block, .block_semicolon => { + const statements = tree.extra_data[node_datas[node].lhs..node_datas[node].rhs]; + return blockExpr(mod, scope, rl, node, statements); + }, + .enum_literal => return simpleStrTok(mod, scope, rl, main_tokens[node], node, .enum_literal), + .error_value => return simpleStrTok(mod, scope, rl, node_datas[node].rhs, node, .error_value), + .anyframe_literal => return mod.failNode(scope, node, "async and related features are not yet supported", .{}), + .anyframe_type => return mod.failNode(scope, node, "async and related features are not yet supported", .{}), + .@"catch" => { + const catch_token = main_tokens[node]; + const payload_token: ?ast.TokenIndex = if (token_tags[catch_token + 1] == .pipe) + catch_token + 2 + else + null; + switch (rl) { + .ref => return orelseCatchExpr( + mod, + scope, + rl, + node, + node_datas[node].lhs, + .is_err_ptr, + .err_union_payload_unsafe_ptr, + .err_union_code_ptr, + node_datas[node].rhs, + payload_token, + ), + else => return orelseCatchExpr( + mod, + scope, + rl, + node, + node_datas[node].lhs, + .is_err, + .err_union_payload_unsafe, + .err_union_code, + node_datas[node].rhs, + payload_token, + ), + } + }, + .@"orelse" => switch (rl) { + .ref => return orelseCatchExpr( + mod, + scope, + rl, + node, + node_datas[node].lhs, + .is_null_ptr, + .optional_payload_unsafe_ptr, + undefined, + node_datas[node].rhs, + null, + ), + else => return orelseCatchExpr( + mod, + scope, + rl, + node, + node_datas[node].lhs, + .is_null, + .optional_payload_unsafe, + undefined, + node_datas[node].rhs, + null, + ), + }, + + .ptr_type_aligned => return ptrType(mod, scope, rl, node, tree.ptrTypeAligned(node)), + .ptr_type_sentinel => return ptrType(mod, scope, rl, node, tree.ptrTypeSentinel(node)), + .ptr_type => return ptrType(mod, scope, rl, node, tree.ptrType(node)), + .ptr_type_bit_range => return ptrType(mod, scope, rl, node, tree.ptrTypeBitRange(node)), + + .container_decl, + .container_decl_trailing, + => return containerDecl(mod, scope, rl, tree.containerDecl(node)), + .container_decl_two, .container_decl_two_trailing => { + var buffer: [2]ast.Node.Index = undefined; + return containerDecl(mod, scope, rl, tree.containerDeclTwo(&buffer, node)); + }, + .container_decl_arg, + .container_decl_arg_trailing, + => return containerDecl(mod, scope, rl, tree.containerDeclArg(node)), + + .tagged_union, + .tagged_union_trailing, + => return containerDecl(mod, scope, rl, tree.taggedUnion(node)), + .tagged_union_two, .tagged_union_two_trailing => { + var buffer: [2]ast.Node.Index = undefined; + return containerDecl(mod, scope, rl, tree.taggedUnionTwo(&buffer, node)); + }, + .tagged_union_enum_tag, + .tagged_union_enum_tag_trailing, + => return containerDecl(mod, scope, rl, tree.taggedUnionEnumTag(node)), + + .@"break" => return breakExpr(mod, scope, node), + .@"continue" => return continueExpr(mod, scope, node), + .grouped_expression => return expr(mod, scope, rl, node_datas[node].lhs), + .array_type => return arrayType(mod, scope, rl, node), + .array_type_sentinel => return arrayTypeSentinel(mod, scope, rl, node), + .char_literal => return charLiteral(mod, scope, rl, node), + .error_set_decl => return errorSetDecl(mod, scope, rl, node), + .array_access => return arrayAccess(mod, scope, rl, node), + .@"comptime" => return comptimeExpr(mod, scope, rl, node_datas[node].lhs), + .@"switch", .switch_comma => return switchExpr(mod, scope, rl, node), + + .@"nosuspend" => return mod.failNode(scope, node, "async and related features are not yet supported", .{}), + .@"suspend" => return mod.failNode(scope, node, "async and related features are not yet supported", .{}), + .@"await" => return mod.failNode(scope, node, "async and related features are not yet supported", .{}), + .@"resume" => return mod.failNode(scope, node, "async and related features are not yet supported", .{}), + + .@"defer" => return mod.failNode(scope, node, "TODO implement astgen.expr for .defer", .{}), + .@"errdefer" => return mod.failNode(scope, node, "TODO implement astgen.expr for .errdefer", .{}), + .@"try" => return mod.failNode(scope, node, "TODO implement astgen.expr for .Try", .{}), + + .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, + => return mod.failNode(scope, node, "TODO implement astgen.expr for array literals", .{}), + + .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, + => return mod.failNode(scope, node, "TODO implement astgen.expr for struct literals", .{}), + + .@"anytype" => return mod.failNode(scope, node, "TODO implement astgen.expr for .anytype", .{}), + .fn_proto_simple, + .fn_proto_multi, + .fn_proto_one, + .fn_proto, + => return mod.failNode(scope, node, "TODO implement astgen.expr for function prototypes", .{}), + } +} + +pub fn comptimeExpr( + mod: *Module, + parent_scope: *Scope, + rl: ResultLoc, + node: ast.Node.Index, +) InnerError!zir.Inst.Ref { + const gz = parent_scope.getGenZir(); + + const prev_force_comptime = gz.force_comptime; + gz.force_comptime = true; + const result = try expr(mod, parent_scope, rl, node); + gz.force_comptime = prev_force_comptime; + return result; +} + +fn breakExpr(mod: *Module, parent_scope: *Scope, node: ast.Node.Index) InnerError!zir.Inst.Ref { + const parent_gz = parent_scope.getGenZir(); + const tree = parent_gz.tree(); + const node_datas = tree.nodes.items(.data); + const break_label = node_datas[node].lhs; + const rhs = node_datas[node].rhs; + + // Look for the label in the scope. + var scope = parent_scope; + while (true) { + switch (scope.tag) { + .gen_zir => { + const block_gz = scope.cast(Scope.GenZir).?; + + const block_inst = blk: { + if (break_label != 0) { + if (block_gz.label) |*label| { + if (try tokenIdentEql(mod, parent_scope, label.token, break_label)) { + label.used = true; + break :blk label.block_inst; + } + } + } else if (block_gz.break_block != 0) { + break :blk block_gz.break_block; + } + scope = block_gz.parent; + continue; + }; + + if (rhs == 0) { + _ = try parent_gz.addBreak(.@"break", block_inst, .void_value); + return zir.Inst.Ref.unreachable_value; + } + block_gz.break_count += 1; + const prev_rvalue_rl_count = block_gz.rvalue_rl_count; + const operand = try expr(mod, parent_scope, block_gz.break_result_loc, rhs); + const have_store_to_block = block_gz.rvalue_rl_count != prev_rvalue_rl_count; + + const br = try parent_gz.addBreak(.@"break", block_inst, operand); + + if (block_gz.break_result_loc == .block_ptr) { + try block_gz.labeled_breaks.append(mod.gpa, br); + + if (have_store_to_block) { + const zir_tags = parent_gz.astgen.instructions.items(.tag); + const zir_datas = parent_gz.astgen.instructions.items(.data); + const store_inst = @intCast(u32, zir_tags.len - 2); + assert(zir_tags[store_inst] == .store_to_block_ptr); + assert(zir_datas[store_inst].bin.lhs == block_gz.rl_ptr); + try block_gz.labeled_store_to_block_ptr_list.append(mod.gpa, store_inst); + } + } + return zir.Inst.Ref.unreachable_value; + }, + .local_val => scope = scope.cast(Scope.LocalVal).?.parent, + .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent, + else => if (break_label != 0) { + const label_name = try mod.identifierTokenString(parent_scope, break_label); + return mod.failTok(parent_scope, break_label, "label not found: '{s}'", .{label_name}); + } else { + return mod.failNode(parent_scope, node, "break expression outside loop", .{}); + }, + } + } +} + +fn continueExpr(mod: *Module, parent_scope: *Scope, node: ast.Node.Index) InnerError!zir.Inst.Ref { + const parent_gz = parent_scope.getGenZir(); + const tree = parent_gz.tree(); + const node_datas = tree.nodes.items(.data); + const break_label = node_datas[node].lhs; + + // Look for the label in the scope. + var scope = parent_scope; + while (true) { + switch (scope.tag) { + .gen_zir => { + const gen_zir = scope.cast(Scope.GenZir).?; + const continue_block = gen_zir.continue_block; + if (continue_block == 0) { + scope = gen_zir.parent; + continue; + } + if (break_label != 0) blk: { + if (gen_zir.label) |*label| { + if (try tokenIdentEql(mod, parent_scope, label.token, break_label)) { + label.used = true; + break :blk; + } + } + // found continue but either it has a different label, or no label + scope = gen_zir.parent; + continue; + } + + // TODO emit a break_inline if the loop being continued is inline + _ = try parent_gz.addBreak(.@"break", continue_block, .void_value); + return zir.Inst.Ref.unreachable_value; + }, + .local_val => scope = scope.cast(Scope.LocalVal).?.parent, + .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent, + else => if (break_label != 0) { + const label_name = try mod.identifierTokenString(parent_scope, break_label); + return mod.failTok(parent_scope, break_label, "label not found: '{s}'", .{label_name}); + } else { + return mod.failNode(parent_scope, node, "continue expression outside loop", .{}); + }, + } + } +} + +pub fn blockExpr( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + block_node: ast.Node.Index, + statements: []const ast.Node.Index, +) InnerError!zir.Inst.Ref { + const tracy = trace(@src()); + defer tracy.end(); + + const tree = scope.tree(); + const main_tokens = tree.nodes.items(.main_token); + const token_tags = tree.tokens.items(.tag); + + const lbrace = main_tokens[block_node]; + if (token_tags[lbrace - 1] == .colon and + token_tags[lbrace - 2] == .identifier) + { + return labeledBlockExpr(mod, scope, rl, block_node, statements, .block); + } + + try blockExprStmts(mod, scope, block_node, statements); + return rvalue(mod, scope, rl, .void_value, block_node); +} + +fn checkLabelRedefinition(mod: *Module, parent_scope: *Scope, label: ast.TokenIndex) !void { + // Look for the label in the scope. + var scope = parent_scope; + while (true) { + switch (scope.tag) { + .gen_zir => { + const gen_zir = scope.cast(Scope.GenZir).?; + if (gen_zir.label) |prev_label| { + if (try tokenIdentEql(mod, parent_scope, label, prev_label.token)) { + const tree = parent_scope.tree(); + const main_tokens = tree.nodes.items(.main_token); + + const label_name = try mod.identifierTokenString(parent_scope, label); + const msg = msg: { + const msg = try mod.errMsg( + parent_scope, + gen_zir.tokSrcLoc(label), + "redefinition of label '{s}'", + .{label_name}, + ); + errdefer msg.destroy(mod.gpa); + try mod.errNote( + parent_scope, + gen_zir.tokSrcLoc(prev_label.token), + msg, + "previous definition is here", + .{}, + ); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(parent_scope, msg); + } + } + scope = gen_zir.parent; + }, + .local_val => scope = scope.cast(Scope.LocalVal).?.parent, + .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent, + else => return, + } + } +} + +fn labeledBlockExpr( + mod: *Module, + parent_scope: *Scope, + rl: ResultLoc, + block_node: ast.Node.Index, + statements: []const ast.Node.Index, + zir_tag: zir.Inst.Tag, +) InnerError!zir.Inst.Ref { + const tracy = trace(@src()); + defer tracy.end(); + + assert(zir_tag == .block); + + const tree = parent_scope.tree(); + const main_tokens = tree.nodes.items(.main_token); + const token_tags = tree.tokens.items(.tag); + + const lbrace = main_tokens[block_node]; + const label_token = lbrace - 2; + assert(token_tags[label_token] == .identifier); + + try checkLabelRedefinition(mod, parent_scope, label_token); + + // Reserve the Block ZIR instruction index so that we can put it into the GenZir struct + // so that break statements can reference it. + const gz = parent_scope.getGenZir(); + const block_inst = try gz.addBlock(zir_tag, block_node); + try gz.instructions.append(mod.gpa, block_inst); + + var block_scope: Scope.GenZir = .{ + .parent = parent_scope, + .astgen = gz.astgen, + .force_comptime = gz.force_comptime, + .instructions = .{}, + // TODO @as here is working around a stage1 miscompilation bug :( + .label = @as(?Scope.GenZir.Label, Scope.GenZir.Label{ + .token = label_token, + .block_inst = block_inst, + }), + }; + setBlockResultLoc(&block_scope, rl); + defer block_scope.instructions.deinit(mod.gpa); + defer block_scope.labeled_breaks.deinit(mod.gpa); + defer block_scope.labeled_store_to_block_ptr_list.deinit(mod.gpa); + + try blockExprStmts(mod, &block_scope.base, block_node, statements); + + if (!block_scope.label.?.used) { + return mod.failTok(parent_scope, label_token, "unused block label", .{}); + } + + const zir_tags = gz.astgen.instructions.items(.tag); + const zir_datas = gz.astgen.instructions.items(.data); + + const strat = rlStrategy(rl, &block_scope); + switch (strat.tag) { + .break_void => { + // The code took advantage of the result location as a pointer. + // Turn the break instruction operands into void. + for (block_scope.labeled_breaks.items) |br| { + zir_datas[br].@"break".operand = .void_value; + } + try block_scope.setBlockBody(block_inst); + + return gz.astgen.indexToRef(block_inst); + }, + .break_operand => { + // All break operands are values that did not use the result location pointer. + if (strat.elide_store_to_block_ptr_instructions) { + for (block_scope.labeled_store_to_block_ptr_list.items) |inst| { + zir_tags[inst] = .elided; + zir_datas[inst] = undefined; + } + // TODO technically not needed since we changed the tag to elided but + // would be better still to elide the ones that are in this list. + } + try block_scope.setBlockBody(block_inst); + const block_ref = gz.astgen.indexToRef(block_inst); + switch (rl) { + .ref => return block_ref, + else => return rvalue(mod, parent_scope, rl, block_ref, block_node), + } + }, + } +} + +fn blockExprStmts( + mod: *Module, + parent_scope: *Scope, + node: ast.Node.Index, + statements: []const ast.Node.Index, +) !void { + const tree = parent_scope.tree(); + const main_tokens = tree.nodes.items(.main_token); + const node_tags = tree.nodes.items(.tag); + + var block_arena = std.heap.ArenaAllocator.init(mod.gpa); + defer block_arena.deinit(); + + const gz = parent_scope.getGenZir(); + + 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(mod, scope, statement, &block_arena.allocator, tree.globalVarDecl(statement)), + .local_var_decl => scope = try varDecl(mod, scope, statement, &block_arena.allocator, tree.localVarDecl(statement)), + .simple_var_decl => scope = try varDecl(mod, scope, statement, &block_arena.allocator, tree.simpleVarDecl(statement)), + .aligned_var_decl => scope = try varDecl(mod, scope, statement, &block_arena.allocator, tree.alignedVarDecl(statement)), + + .assign => try assign(mod, scope, statement), + .assign_bit_and => try assignOp(mod, scope, statement, .bit_and), + .assign_bit_or => try assignOp(mod, scope, statement, .bit_or), + .assign_bit_shift_left => try assignOp(mod, scope, statement, .shl), + .assign_bit_shift_right => try assignOp(mod, scope, statement, .shr), + .assign_bit_xor => try assignOp(mod, scope, statement, .xor), + .assign_div => try assignOp(mod, scope, statement, .div), + .assign_sub => try assignOp(mod, scope, statement, .sub), + .assign_sub_wrap => try assignOp(mod, scope, statement, .subwrap), + .assign_mod => try assignOp(mod, scope, statement, .mod_rem), + .assign_add => try assignOp(mod, scope, statement, .add), + .assign_add_wrap => try assignOp(mod, scope, statement, .addwrap), + .assign_mul => try assignOp(mod, scope, statement, .mul), + .assign_mul_wrap => try assignOp(mod, scope, statement, .mulwrap), + + 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(mod, scope, .none, statement); + const elide_check = if (gz.astgen.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]) { + .@"const" => { + const tv = gz.astgen.instructions.items(.data)[inst].@"const"; + break :b switch (tv.ty.zigTypeTag()) { + .NoReturn, .Void => true, + else => false, + }; + }, + // 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, + .indexable_ptr_len, + .as, + .as_node, + .@"asm", + .asm_volatile, + .bit_and, + .bitcast, + .bitcast_ref, + .bitcast_result_ptr, + .bit_or, + .block, + .block_inline, + .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, + .floatcast, + .field_ptr, + .field_val, + .field_ptr_named, + .field_val_named, + .fn_type, + .fn_type_var_args, + .fn_type_cc, + .fn_type_cc_var_args, + .int, + .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, + .ret_ptr, + .ret_type, + .shl, + .shr, + .str, + .sub, + .subwrap, + .negate, + .negate_wrap, + .typeof, + .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_set, + .error_value, + .slice_start, + .slice_end, + .slice_sentinel, + .import, + .typeof_peer, + => break :b false, + + // ZIR instructions that are always either `noreturn` or `void`. + .breakpoint, + .dbg_stmt_node, + .ensure_result_used, + .ensure_result_non_error, + .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", + .elided, + .store, + .store_node, + .store_to_block_ptr, + .store_to_inferred_ptr, + .resolve_inferred_alloc, + .repeat, + .repeat_inline, + => 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 varDecl( + mod: *Module, + scope: *Scope, + node: ast.Node.Index, + block_arena: *Allocator, + var_decl: ast.full.VarDecl, +) InnerError!*Scope { + if (var_decl.comptime_token) |comptime_token| { + return mod.failTok(scope, comptime_token, "TODO implement comptime locals", .{}); + } + if (var_decl.ast.align_node != 0) { + return mod.failNode(scope, var_decl.ast.align_node, "TODO implement alignment on locals", .{}); + } + const gz = scope.getGenZir(); + const astgen = gz.astgen; + const tree = scope.tree(); + const token_tags = tree.tokens.items(.tag); + + const name_token = var_decl.ast.mut_token + 1; + const name_src = gz.tokSrcLoc(name_token); + const ident_name = try mod.identifierTokenString(scope, name_token); + + // Local variables shadowing detection, including function parameters. + { + var s = scope; + while (true) switch (s.tag) { + .local_val => { + const local_val = s.cast(Scope.LocalVal).?; + if (mem.eql(u8, local_val.name, ident_name)) { + const msg = msg: { + const msg = try mod.errMsg(scope, name_src, "redefinition of '{s}'", .{ + ident_name, + }); + errdefer msg.destroy(mod.gpa); + try mod.errNote(scope, local_val.src, msg, "previous definition is here", .{}); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(scope, msg); + } + s = local_val.parent; + }, + .local_ptr => { + const local_ptr = s.cast(Scope.LocalPtr).?; + if (mem.eql(u8, local_ptr.name, ident_name)) { + const msg = msg: { + const msg = try mod.errMsg(scope, name_src, "redefinition of '{s}'", .{ + ident_name, + }); + errdefer msg.destroy(mod.gpa); + try mod.errNote(scope, local_ptr.src, msg, "previous definition is here", .{}); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(scope, msg); + } + s = local_ptr.parent; + }, + .gen_zir => s = s.cast(Scope.GenZir).?.parent, + else => break, + }; + } + + // Namespace vars shadowing detection + if (mod.lookupDeclName(scope, ident_name)) |_| { + // TODO add note for other definition + return mod.fail(scope, name_src, "redefinition of '{s}'", .{ident_name}); + } + if (var_decl.ast.init_node == 0) { + return mod.fail(scope, name_src, "variables must be initialized", .{}); + } + + switch (token_tags[var_decl.ast.mut_token]) { + .keyword_const => { + // 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. + if (!nodeMayNeedMemoryLocation(scope, var_decl.ast.init_node)) { + const result_loc: ResultLoc = if (var_decl.ast.type_node != 0) .{ + .ty = try typeExpr(mod, scope, var_decl.ast.type_node), + } else .none; + const init_inst = try expr(mod, scope, result_loc, var_decl.ast.init_node); + const sub_scope = try block_arena.create(Scope.LocalVal); + sub_scope.* = .{ + .parent = scope, + .gen_zir = gz, + .name = ident_name, + .inst = init_inst, + .src = name_src, + }; + return &sub_scope.base; + } + + // Detect whether the initialization expression actually uses the + // result location pointer. + var init_scope: Scope.GenZir = .{ + .parent = scope, + .force_comptime = gz.force_comptime, + .astgen = astgen, + }; + defer init_scope.instructions.deinit(mod.gpa); + + var resolve_inferred_alloc: zir.Inst.Ref = .none; + var opt_type_inst: zir.Inst.Ref = .none; + if (var_decl.ast.type_node != 0) { + const type_inst = try typeExpr(mod, &init_scope.base, var_decl.ast.type_node); + opt_type_inst = type_inst; + init_scope.rl_ptr = try init_scope.addUnNode(.alloc, type_inst, node); + } else { + const alloc = try init_scope.addUnNode(.alloc_inferred, undefined, node); + resolve_inferred_alloc = alloc; + init_scope.rl_ptr = alloc; + } + const init_result_loc: ResultLoc = .{ .block_ptr = &init_scope }; + const init_inst = try expr(mod, &init_scope.base, init_result_loc, var_decl.ast.init_node); + const zir_tags = astgen.instructions.items(.tag); + const zir_datas = astgen.instructions.items(.data); + + const parent_zir = &gz.instructions; + if (init_scope.rvalue_rl_count == 1) { + // Result location pointer not used. We don't need an alloc for this + // const local, and type inference becomes trivial. + // Move the init_scope instructions into the parent scope, eliding + // the alloc instruction and the store_to_block_ptr instruction. + const expected_len = parent_zir.items.len + init_scope.instructions.items.len - 2; + try parent_zir.ensureCapacity(mod.gpa, expected_len); + for (init_scope.instructions.items) |src_inst| { + if (astgen.indexToRef(src_inst) == init_scope.rl_ptr) continue; + if (zir_tags[src_inst] == .store_to_block_ptr) { + if (zir_datas[src_inst].bin.lhs == init_scope.rl_ptr) continue; + } + parent_zir.appendAssumeCapacity(src_inst); + } + assert(parent_zir.items.len == expected_len); + const casted_init = if (opt_type_inst != .none) + try gz.addPlNode(.as_node, var_decl.ast.type_node, zir.Inst.As{ + .dest_type = opt_type_inst, + .operand = init_inst, + }) + else + init_inst; + + const sub_scope = try block_arena.create(Scope.LocalVal); + sub_scope.* = .{ + .parent = scope, + .gen_zir = gz, + .name = ident_name, + .inst = casted_init, + .src = name_src, + }; + return &sub_scope.base; + } + // The initialization expression took advantage of the result location + // of the const local. In this case we will create an alloc and a LocalPtr for it. + // Move the init_scope instructions into the parent scope, swapping + // store_to_block_ptr for store_to_inferred_ptr. + const expected_len = parent_zir.items.len + init_scope.instructions.items.len; + try parent_zir.ensureCapacity(mod.gpa, expected_len); + for (init_scope.instructions.items) |src_inst| { + if (zir_tags[src_inst] == .store_to_block_ptr) { + if (zir_datas[src_inst].bin.lhs == init_scope.rl_ptr) { + zir_tags[src_inst] = .store_to_inferred_ptr; + } + } + parent_zir.appendAssumeCapacity(src_inst); + } + assert(parent_zir.items.len == expected_len); + if (resolve_inferred_alloc != .none) { + _ = try gz.addUnNode(.resolve_inferred_alloc, resolve_inferred_alloc, node); + } + const sub_scope = try block_arena.create(Scope.LocalPtr); + sub_scope.* = .{ + .parent = scope, + .gen_zir = gz, + .name = ident_name, + .ptr = init_scope.rl_ptr, + .src = name_src, + }; + return &sub_scope.base; + }, + .keyword_var => { + var resolve_inferred_alloc: zir.Inst.Ref = .none; + const var_data: struct { + result_loc: ResultLoc, + alloc: zir.Inst.Ref, + } = if (var_decl.ast.type_node != 0) a: { + const type_inst = try typeExpr(mod, scope, var_decl.ast.type_node); + + const alloc = try gz.addUnNode(.alloc_mut, type_inst, node); + break :a .{ .alloc = alloc, .result_loc = .{ .ptr = alloc } }; + } else a: { + const alloc = try gz.addUnNode(.alloc_inferred_mut, undefined, node); + resolve_inferred_alloc = alloc; + break :a .{ .alloc = alloc, .result_loc = .{ .inferred_ptr = alloc } }; + }; + const init_inst = try expr(mod, scope, var_data.result_loc, var_decl.ast.init_node); + if (resolve_inferred_alloc != .none) { + _ = try gz.addUnNode(.resolve_inferred_alloc, resolve_inferred_alloc, node); + } + const sub_scope = try block_arena.create(Scope.LocalPtr); + sub_scope.* = .{ + .parent = scope, + .gen_zir = gz, + .name = ident_name, + .ptr = var_data.alloc, + .src = name_src, + }; + return &sub_scope.base; + }, + else => unreachable, + } +} + +fn assign(mod: *Module, scope: *Scope, infix_node: ast.Node.Index) InnerError!void { + const tree = scope.tree(); + const node_datas = tree.nodes.items(.data); + const main_tokens = tree.nodes.items(.main_token); + const node_tags = tree.nodes.items(.tag); + + const lhs = node_datas[infix_node].lhs; + const rhs = node_datas[infix_node].rhs; + if (node_tags[lhs] == .identifier) { + // This intentionally does not support `@"_"` syntax. + const ident_name = tree.tokenSlice(main_tokens[lhs]); + if (mem.eql(u8, ident_name, "_")) { + _ = try expr(mod, scope, .discard, rhs); + return; + } + } + const lvalue = try lvalExpr(mod, scope, lhs); + _ = try expr(mod, scope, .{ .ptr = lvalue }, rhs); +} + +fn assignOp( + mod: *Module, + scope: *Scope, + infix_node: ast.Node.Index, + op_inst_tag: zir.Inst.Tag, +) InnerError!void { + const tree = scope.tree(); + const node_datas = tree.nodes.items(.data); + const gz = scope.getGenZir(); + + const lhs_ptr = try lvalExpr(mod, scope, node_datas[infix_node].lhs); + const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node); + const lhs_type = try gz.addUnTok(.typeof, lhs, infix_node); + const rhs = try expr(mod, scope, .{ .ty = lhs_type }, node_datas[infix_node].rhs); + + const result = try gz.addPlNode(op_inst_tag, infix_node, zir.Inst.Bin{ + .lhs = lhs, + .rhs = rhs, + }); + _ = try gz.addBin(.store, lhs_ptr, result); +} + +fn boolNot(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) InnerError!zir.Inst.Ref { + const tree = scope.tree(); + const node_datas = tree.nodes.items(.data); + + const operand = try expr(mod, scope, .{ .ty = .bool_type }, node_datas[node].lhs); + const gz = scope.getGenZir(); + const result = try gz.addUnNode(.bool_not, operand, node); + return rvalue(mod, scope, rl, result, node); +} + +fn bitNot(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) InnerError!zir.Inst.Ref { + const tree = scope.tree(); + const node_datas = tree.nodes.items(.data); + + const gz = scope.getGenZir(); + const operand = try expr(mod, scope, .none, node_datas[node].lhs); + const result = try gz.addUnNode(.bit_not, operand, node); + return rvalue(mod, scope, rl, result, node); +} + +fn negation( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + node: ast.Node.Index, + tag: zir.Inst.Tag, +) InnerError!zir.Inst.Ref { + const tree = scope.tree(); + const node_datas = tree.nodes.items(.data); + + const gz = scope.getGenZir(); + const operand = try expr(mod, scope, .none, node_datas[node].lhs); + const result = try gz.addUnNode(tag, operand, node); + return rvalue(mod, scope, rl, result, node); +} + +fn ptrType( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + node: ast.Node.Index, + ptr_info: ast.full.PtrType, +) InnerError!zir.Inst.Ref { + const tree = scope.tree(); + const gz = scope.getGenZir(); + + const elem_type = try typeExpr(mod, scope, ptr_info.ast.child_type); + + const simple = ptr_info.ast.align_node == 0 and + ptr_info.ast.sentinel == 0 and + ptr_info.ast.bit_range_start == 0; + + if (simple) { + const result = try gz.add(.{ .tag = .ptr_type_simple, .data = .{ + .ptr_type_simple = .{ + .is_allowzero = ptr_info.allowzero_token != null, + .is_mutable = ptr_info.const_token == null, + .is_volatile = ptr_info.volatile_token != null, + .size = ptr_info.size, + .elem_type = elem_type, + }, + } }); + return rvalue(mod, scope, rl, result, node); + } + + var sentinel_ref: zir.Inst.Ref = .none; + var align_ref: zir.Inst.Ref = .none; + var bit_start_ref: zir.Inst.Ref = .none; + var bit_end_ref: zir.Inst.Ref = .none; + var trailing_count: u32 = 0; + + if (ptr_info.ast.sentinel != 0) { + sentinel_ref = try expr(mod, scope, .{ .ty = elem_type }, ptr_info.ast.sentinel); + trailing_count += 1; + } + if (ptr_info.ast.align_node != 0) { + align_ref = try expr(mod, scope, .none, ptr_info.ast.align_node); + trailing_count += 1; + } + if (ptr_info.ast.bit_range_start != 0) { + assert(ptr_info.ast.bit_range_end != 0); + bit_start_ref = try expr(mod, scope, .none, ptr_info.ast.bit_range_start); + bit_end_ref = try expr(mod, scope, .none, ptr_info.ast.bit_range_end); + trailing_count += 2; + } + + const gpa = gz.astgen.mod.gpa; + try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1); + try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1); + try gz.astgen.extra.ensureCapacity(gpa, gz.astgen.extra.items.len + + @typeInfo(zir.Inst.PtrType).Struct.fields.len + trailing_count); + + const payload_index = gz.astgen.addExtraAssumeCapacity(zir.Inst.PtrType{ .elem_type = elem_type }); + if (sentinel_ref != .none) { + gz.astgen.extra.appendAssumeCapacity(@enumToInt(sentinel_ref)); + } + if (align_ref != .none) { + gz.astgen.extra.appendAssumeCapacity(@enumToInt(align_ref)); + } + if (bit_start_ref != .none) { + gz.astgen.extra.appendAssumeCapacity(@enumToInt(bit_start_ref)); + gz.astgen.extra.appendAssumeCapacity(@enumToInt(bit_end_ref)); + } + + const new_index = @intCast(zir.Inst.Index, gz.astgen.instructions.len); + const result = gz.astgen.indexToRef(new_index); + gz.astgen.instructions.appendAssumeCapacity(.{ .tag = .ptr_type, .data = .{ + .ptr_type = .{ + .flags = .{ + .is_allowzero = ptr_info.allowzero_token != null, + .is_mutable = ptr_info.const_token == null, + .is_volatile = ptr_info.volatile_token != null, + .has_sentinel = sentinel_ref != .none, + .has_align = align_ref != .none, + .has_bit_range = bit_start_ref != .none, + }, + .size = ptr_info.size, + .payload_index = payload_index, + }, + } }); + gz.instructions.appendAssumeCapacity(new_index); + + return rvalue(mod, scope, rl, result, node); +} + +fn arrayType(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) !zir.Inst.Ref { + const tree = scope.tree(); + const node_datas = tree.nodes.items(.data); + const gz = scope.getGenZir(); + + // TODO check for [_]T + const len = try expr(mod, scope, .{ .ty = .usize_type }, node_datas[node].lhs); + const elem_type = try typeExpr(mod, scope, node_datas[node].rhs); + + const result = try gz.addBin(.array_type, len, elem_type); + return rvalue(mod, scope, rl, result, node); +} + +fn arrayTypeSentinel(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) !zir.Inst.Ref { + const tree = scope.tree(); + const node_datas = tree.nodes.items(.data); + const extra = tree.extraData(node_datas[node].rhs, ast.Node.ArrayTypeSentinel); + const gz = scope.getGenZir(); + + // TODO check for [_]T + const len = try expr(mod, scope, .{ .ty = .usize_type }, node_datas[node].lhs); + const elem_type = try typeExpr(mod, scope, extra.elem_type); + const sentinel = try expr(mod, scope, .{ .ty = elem_type }, extra.sentinel); + + const result = try gz.addArrayTypeSentinel(len, elem_type, sentinel); + return rvalue(mod, scope, rl, result, node); +} + +fn containerDecl( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + container_decl: ast.full.ContainerDecl, +) InnerError!zir.Inst.Ref { + return mod.failTok(scope, container_decl.ast.main_token, "TODO implement container decls", .{}); +} + +fn errorSetDecl( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + node: ast.Node.Index, +) InnerError!zir.Inst.Ref { + if (true) @panic("TODO update for zir-memory-layout branch"); + const gz = scope.getGenZir(); + const tree = gz.tree(); + const main_tokens = tree.nodes.items(.main_token); + const token_tags = tree.tokens.items(.tag); + + // Count how many fields there are. + const error_token = main_tokens[node]; + const count: usize = count: { + var tok_i = error_token + 2; + var count: usize = 0; + while (true) : (tok_i += 1) { + switch (token_tags[tok_i]) { + .doc_comment, .comma => {}, + .identifier => count += 1, + .r_brace => break :count count, + else => unreachable, + } + } else unreachable; // TODO should not need else unreachable here + }; + + const fields = try scope.arena().alloc([]const u8, count); + { + var tok_i = error_token + 2; + var field_i: usize = 0; + while (true) : (tok_i += 1) { + switch (token_tags[tok_i]) { + .doc_comment, .comma => {}, + .identifier => { + fields[field_i] = try mod.identifierTokenString(scope, tok_i); + field_i += 1; + }, + .r_brace => break, + else => unreachable, + } + } + } + const result = try addZIRInst(mod, scope, src, zir.Inst.ErrorSet, .{ .fields = fields }, .{}); + return rvalue(mod, scope, rl, result); +} + +fn orelseCatchExpr( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + node: ast.Node.Index, + lhs: ast.Node.Index, + cond_op: zir.Inst.Tag, + unwrap_op: zir.Inst.Tag, + unwrap_code_op: zir.Inst.Tag, + rhs: ast.Node.Index, + payload_token: ?ast.TokenIndex, +) InnerError!zir.Inst.Ref { + const parent_gz = scope.getGenZir(); + const tree = parent_gz.tree(); + + var block_scope: Scope.GenZir = .{ + .parent = scope, + .astgen = parent_gz.astgen, + .force_comptime = parent_gz.force_comptime, + .instructions = .{}, + }; + setBlockResultLoc(&block_scope, rl); + defer block_scope.instructions.deinit(mod.gpa); + + // This could be a pointer or value depending on the `operand_rl` parameter. + // We cannot use `block_scope.break_result_loc` because that has the bare + // type, whereas this expression has the optional type. Later we make + // up for this fact by calling rvalue on the else branch. + block_scope.break_count += 1; + + // TODO handle catch + const operand_rl: ResultLoc = switch (block_scope.break_result_loc) { + .ref => .ref, + .discard, .none, .block_ptr, .inferred_ptr, .bitcasted_ptr => .none, + .ty => |elem_ty| blk: { + const wrapped_ty = try block_scope.addUnNode(.optional_type, elem_ty, node); + break :blk .{ .ty = wrapped_ty }; + }, + .ptr => |ptr_ty| blk: { + const wrapped_ty = try block_scope.addUnNode(.optional_type_from_ptr_elem, ptr_ty, node); + break :blk .{ .ty = wrapped_ty }; + }, + }; + const operand = try expr(mod, &block_scope.base, operand_rl, lhs); + const cond = try block_scope.addUnNode(cond_op, operand, node); + const condbr = try block_scope.addCondBr(.condbr, node); + + const block = try parent_gz.addBlock(.block, node); + try parent_gz.instructions.append(mod.gpa, block); + try block_scope.setBlockBody(block); + + var then_scope: Scope.GenZir = .{ + .parent = scope, + .astgen = parent_gz.astgen, + .force_comptime = block_scope.force_comptime, + .instructions = .{}, + }; + defer then_scope.instructions.deinit(mod.gpa); + + var err_val_scope: Scope.LocalVal = undefined; + const then_sub_scope = blk: { + const payload = payload_token orelse break :blk &then_scope.base; + if (mem.eql(u8, tree.tokenSlice(payload), "_")) { + return mod.failTok(&then_scope.base, payload, "discard of error capture; omit it instead", .{}); + } + const err_name = try mod.identifierTokenString(scope, payload); + err_val_scope = .{ + .parent = &then_scope.base, + .gen_zir = &then_scope, + .name = err_name, + .inst = try then_scope.addUnNode(unwrap_code_op, operand, node), + .src = parent_gz.tokSrcLoc(payload), + }; + break :blk &err_val_scope.base; + }; + + block_scope.break_count += 1; + const then_result = try expr(mod, then_sub_scope, block_scope.break_result_loc, rhs); + // We hold off on the break instructions as well as copying the then/else + // instructions into place until we know whether to keep store_to_block_ptr + // instructions or not. + + var else_scope: Scope.GenZir = .{ + .parent = scope, + .astgen = parent_gz.astgen, + .force_comptime = block_scope.force_comptime, + .instructions = .{}, + }; + defer else_scope.instructions.deinit(mod.gpa); + + // This could be a pointer or value depending on `unwrap_op`. + const unwrapped_payload = try else_scope.addUnNode(unwrap_op, operand, node); + const else_result = switch (rl) { + .ref => unwrapped_payload, + else => try rvalue(mod, &else_scope.base, block_scope.break_result_loc, unwrapped_payload, node), + }; + + return finishThenElseBlock( + mod, + scope, + rl, + node, + &block_scope, + &then_scope, + &else_scope, + condbr, + cond, + node, + node, + then_result, + else_result, + block, + block, + .@"break", + ); +} + +fn finishThenElseBlock( + mod: *Module, + parent_scope: *Scope, + rl: ResultLoc, + node: ast.Node.Index, + block_scope: *Scope.GenZir, + then_scope: *Scope.GenZir, + else_scope: *Scope.GenZir, + condbr: zir.Inst.Index, + cond: zir.Inst.Ref, + then_src: ast.Node.Index, + else_src: ast.Node.Index, + then_result: zir.Inst.Ref, + else_result: zir.Inst.Ref, + main_block: zir.Inst.Index, + then_break_block: zir.Inst.Index, + break_tag: zir.Inst.Tag, +) InnerError!zir.Inst.Ref { + // We now have enough information to decide whether the result instruction should + // be communicated via result location pointer or break instructions. + const strat = rlStrategy(rl, block_scope); + const astgen = block_scope.astgen; + switch (strat.tag) { + .break_void => { + if (!astgen.refIsNoReturn(then_result)) { + _ = try then_scope.addBreak(break_tag, then_break_block, .void_value); + } + const elide_else = if (else_result != .none) astgen.refIsNoReturn(else_result) else false; + if (!elide_else) { + _ = try else_scope.addBreak(break_tag, main_block, .void_value); + } + assert(!strat.elide_store_to_block_ptr_instructions); + try setCondBrPayload(condbr, cond, then_scope, else_scope); + return astgen.indexToRef(main_block); + }, + .break_operand => { + if (!astgen.refIsNoReturn(then_result)) { + _ = try then_scope.addBreak(break_tag, then_break_block, then_result); + } + if (else_result != .none) { + if (!astgen.refIsNoReturn(else_result)) { + _ = try else_scope.addBreak(break_tag, main_block, else_result); + } + } else { + _ = try else_scope.addBreak(break_tag, main_block, .void_value); + } + if (strat.elide_store_to_block_ptr_instructions) { + try setCondBrPayloadElideBlockStorePtr(condbr, cond, then_scope, else_scope); + } else { + try setCondBrPayload(condbr, cond, then_scope, else_scope); + } + const block_ref = astgen.indexToRef(main_block); + switch (rl) { + .ref => return block_ref, + else => return rvalue(mod, parent_scope, rl, block_ref, node), + } + }, + } +} + +/// Return whether the identifier names of two tokens are equal. Resolves @"" +/// tokens without allocating. +/// OK in theory it could do it without allocating. This implementation +/// allocates when the @"" form is used. +fn tokenIdentEql(mod: *Module, scope: *Scope, token1: ast.TokenIndex, token2: ast.TokenIndex) !bool { + const ident_name_1 = try mod.identifierTokenString(scope, token1); + const ident_name_2 = try mod.identifierTokenString(scope, token2); + return mem.eql(u8, ident_name_1, ident_name_2); +} + +pub fn fieldAccess( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + node: ast.Node.Index, +) InnerError!zir.Inst.Ref { + const gz = scope.getGenZir(); + const tree = gz.tree(); + const main_tokens = tree.nodes.items(.main_token); + const node_datas = tree.nodes.items(.data); + const object_node = node_datas[node].lhs; + const dot_token = main_tokens[node]; + const field_ident = dot_token + 1; + const string_bytes = &gz.astgen.string_bytes; + const str_index = @intCast(u32, string_bytes.items.len); + try mod.appendIdentStr(scope, field_ident, string_bytes); + try string_bytes.append(mod.gpa, 0); + switch (rl) { + .ref => return gz.addPlNode(.field_ptr, node, zir.Inst.Field{ + .lhs = try expr(mod, scope, .ref, object_node), + .field_name_start = str_index, + }), + else => return rvalue(mod, scope, rl, try gz.addPlNode(.field_val, node, zir.Inst.Field{ + .lhs = try expr(mod, scope, .none, object_node), + .field_name_start = str_index, + }), node), + } +} + +fn arrayAccess( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + node: ast.Node.Index, +) InnerError!zir.Inst.Ref { + const gz = scope.getGenZir(); + const tree = gz.tree(); + const main_tokens = tree.nodes.items(.main_token); + const node_datas = tree.nodes.items(.data); + switch (rl) { + .ref => return gz.addBin( + .elem_ptr, + try expr(mod, scope, .ref, node_datas[node].lhs), + try expr(mod, scope, .{ .ty = .usize_type }, node_datas[node].rhs), + ), + else => return rvalue(mod, scope, rl, try gz.addBin( + .elem_val, + try expr(mod, scope, .none, node_datas[node].lhs), + try expr(mod, scope, .{ .ty = .usize_type }, node_datas[node].rhs), + ), node), + } +} + +fn simpleBinOp( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + node: ast.Node.Index, + op_inst_tag: zir.Inst.Tag, +) InnerError!zir.Inst.Ref { + const gz = scope.getGenZir(); + const tree = gz.tree(); + const node_datas = tree.nodes.items(.data); + + const result = try gz.addPlNode(op_inst_tag, node, zir.Inst.Bin{ + .lhs = try expr(mod, scope, .none, node_datas[node].lhs), + .rhs = try expr(mod, scope, .none, node_datas[node].rhs), + }); + return rvalue(mod, scope, rl, result, node); +} + +fn simpleStrTok( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + ident_token: ast.TokenIndex, + node: ast.Node.Index, + op_inst_tag: zir.Inst.Tag, +) InnerError!zir.Inst.Ref { + const gz = scope.getGenZir(); + const string_bytes = &gz.astgen.string_bytes; + const str_index = @intCast(u32, string_bytes.items.len); + try mod.appendIdentStr(scope, ident_token, string_bytes); + try string_bytes.append(mod.gpa, 0); + const result = try gz.addStrTok(op_inst_tag, str_index, ident_token); + return rvalue(mod, scope, rl, result, node); +} + +fn boolBinOp( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + node: ast.Node.Index, + zir_tag: zir.Inst.Tag, +) InnerError!zir.Inst.Ref { + const gz = scope.getGenZir(); + const node_datas = gz.tree().nodes.items(.data); + + const lhs = try expr(mod, scope, .{ .ty = .bool_type }, node_datas[node].lhs); + const bool_br = try gz.addBoolBr(zir_tag, lhs); + + var rhs_scope: Scope.GenZir = .{ + .parent = scope, + .astgen = gz.astgen, + .force_comptime = gz.force_comptime, + }; + defer rhs_scope.instructions.deinit(mod.gpa); + const rhs = try expr(mod, &rhs_scope.base, .{ .ty = .bool_type }, node_datas[node].rhs); + _ = try rhs_scope.addBreak(.break_inline, bool_br, rhs); + try rhs_scope.setBoolBrBody(bool_br); + + const block_ref = gz.astgen.indexToRef(bool_br); + return rvalue(mod, scope, rl, block_ref, node); +} + +fn ifExpr( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + node: ast.Node.Index, + if_full: ast.full.If, +) InnerError!zir.Inst.Ref { + const parent_gz = scope.getGenZir(); + var block_scope: Scope.GenZir = .{ + .parent = scope, + .astgen = parent_gz.astgen, + .force_comptime = parent_gz.force_comptime, + .instructions = .{}, + }; + setBlockResultLoc(&block_scope, rl); + defer block_scope.instructions.deinit(mod.gpa); + + const cond = c: { + // TODO https://github.com/ziglang/zig/issues/7929 + if (if_full.error_token) |error_token| { + return mod.failTok(scope, error_token, "TODO implement if error union", .{}); + } else if (if_full.payload_token) |payload_token| { + return mod.failTok(scope, payload_token, "TODO implement if optional", .{}); + } else { + break :c try expr(mod, &block_scope.base, .{ .ty = .bool_type }, if_full.ast.cond_expr); + } + }; + + const condbr = try block_scope.addCondBr(.condbr, node); + + const block = try parent_gz.addBlock(.block, node); + try parent_gz.instructions.append(mod.gpa, block); + try block_scope.setBlockBody(block); + + var then_scope: Scope.GenZir = .{ + .parent = scope, + .astgen = parent_gz.astgen, + .force_comptime = block_scope.force_comptime, + .instructions = .{}, + }; + defer then_scope.instructions.deinit(mod.gpa); + + // declare payload to the then_scope + const then_sub_scope = &then_scope.base; + + block_scope.break_count += 1; + const then_result = try expr(mod, then_sub_scope, block_scope.break_result_loc, if_full.ast.then_expr); + // We hold off on the break instructions as well as copying the then/else + // instructions into place until we know whether to keep store_to_block_ptr + // instructions or not. + + var else_scope: Scope.GenZir = .{ + .parent = scope, + .astgen = parent_gz.astgen, + .force_comptime = block_scope.force_comptime, + .instructions = .{}, + }; + defer else_scope.instructions.deinit(mod.gpa); + + const else_node = if_full.ast.else_expr; + const else_info: struct { + src: ast.Node.Index, + result: zir.Inst.Ref, + } = if (else_node != 0) blk: { + block_scope.break_count += 1; + const sub_scope = &else_scope.base; + break :blk .{ + .src = else_node, + .result = try expr(mod, sub_scope, block_scope.break_result_loc, else_node), + }; + } else .{ + .src = if_full.ast.then_expr, + .result = .none, + }; + + return finishThenElseBlock( + mod, + scope, + rl, + node, + &block_scope, + &then_scope, + &else_scope, + condbr, + cond, + if_full.ast.then_expr, + else_info.src, + then_result, + else_info.result, + block, + block, + .@"break", + ); +} + +fn setCondBrPayload( + condbr: zir.Inst.Index, + cond: zir.Inst.Ref, + then_scope: *Scope.GenZir, + else_scope: *Scope.GenZir, +) !void { + const astgen = then_scope.astgen; + + try astgen.extra.ensureCapacity(astgen.mod.gpa, astgen.extra.items.len + + @typeInfo(zir.Inst.CondBr).Struct.fields.len + + then_scope.instructions.items.len + else_scope.instructions.items.len); + + const zir_datas = astgen.instructions.items(.data); + zir_datas[condbr].pl_node.payload_index = astgen.addExtraAssumeCapacity(zir.Inst.CondBr{ + .condition = cond, + .then_body_len = @intCast(u32, then_scope.instructions.items.len), + .else_body_len = @intCast(u32, else_scope.instructions.items.len), + }); + astgen.extra.appendSliceAssumeCapacity(then_scope.instructions.items); + astgen.extra.appendSliceAssumeCapacity(else_scope.instructions.items); +} + +/// If `elide_block_store_ptr` is set, expects to find exactly 1 .store_to_block_ptr instruction. +fn setCondBrPayloadElideBlockStorePtr( + condbr: zir.Inst.Index, + cond: zir.Inst.Ref, + then_scope: *Scope.GenZir, + else_scope: *Scope.GenZir, +) !void { + const astgen = then_scope.astgen; + + try astgen.extra.ensureCapacity(astgen.mod.gpa, astgen.extra.items.len + + @typeInfo(zir.Inst.CondBr).Struct.fields.len + + then_scope.instructions.items.len + else_scope.instructions.items.len - 2); + + const zir_datas = astgen.instructions.items(.data); + zir_datas[condbr].pl_node.payload_index = astgen.addExtraAssumeCapacity(zir.Inst.CondBr{ + .condition = cond, + .then_body_len = @intCast(u32, then_scope.instructions.items.len - 1), + .else_body_len = @intCast(u32, else_scope.instructions.items.len - 1), + }); + + const zir_tags = astgen.instructions.items(.tag); + for ([_]*Scope.GenZir{ then_scope, else_scope }) |scope| { + for (scope.instructions.items) |src_inst| { + if (zir_tags[src_inst] != .store_to_block_ptr) { + astgen.extra.appendAssumeCapacity(src_inst); + } + } + } +} + +fn whileExpr( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + node: ast.Node.Index, + while_full: ast.full.While, +) InnerError!zir.Inst.Ref { + if (while_full.label_token) |label_token| { + try checkLabelRedefinition(mod, scope, label_token); + } + const parent_gz = scope.getGenZir(); + const is_inline = parent_gz.force_comptime or while_full.inline_token != null; + const loop_tag: zir.Inst.Tag = if (is_inline) .block_inline else .loop; + const loop_block = try parent_gz.addBlock(loop_tag, node); + try parent_gz.instructions.append(mod.gpa, loop_block); + + var loop_scope: Scope.GenZir = .{ + .parent = scope, + .astgen = parent_gz.astgen, + .force_comptime = parent_gz.force_comptime, + .instructions = .{}, + }; + setBlockResultLoc(&loop_scope, rl); + defer loop_scope.instructions.deinit(mod.gpa); + + var continue_scope: Scope.GenZir = .{ + .parent = &loop_scope.base, + .astgen = parent_gz.astgen, + .force_comptime = loop_scope.force_comptime, + .instructions = .{}, + }; + defer continue_scope.instructions.deinit(mod.gpa); + + const cond = c: { + // TODO https://github.com/ziglang/zig/issues/7929 + if (while_full.error_token) |error_token| { + return mod.failTok(scope, error_token, "TODO implement while error union", .{}); + } else if (while_full.payload_token) |payload_token| { + return mod.failTok(scope, payload_token, "TODO implement while optional", .{}); + } else { + const bool_type_rl: ResultLoc = .{ .ty = .bool_type }; + break :c try expr(mod, &continue_scope.base, bool_type_rl, while_full.ast.cond_expr); + } + }; + + const condbr_tag: zir.Inst.Tag = if (is_inline) .condbr_inline else .condbr; + const condbr = try continue_scope.addCondBr(condbr_tag, node); + const block_tag: zir.Inst.Tag = if (is_inline) .block_inline else .block; + const cond_block = try loop_scope.addBlock(block_tag, node); + try loop_scope.instructions.append(mod.gpa, cond_block); + try continue_scope.setBlockBody(cond_block); + + // TODO avoid emitting the continue expr when there + // are no jumps to it. This happens when the last statement of a while body is noreturn + // and there are no `continue` statements. + if (while_full.ast.cont_expr != 0) { + _ = try expr(mod, &loop_scope.base, .{ .ty = .void_type }, while_full.ast.cont_expr); + } + const repeat_tag: zir.Inst.Tag = if (is_inline) .repeat_inline else .repeat; + _ = try loop_scope.addNode(repeat_tag, node); + + try loop_scope.setBlockBody(loop_block); + loop_scope.break_block = loop_block; + loop_scope.continue_block = cond_block; + if (while_full.label_token) |label_token| { + loop_scope.label = @as(?Scope.GenZir.Label, Scope.GenZir.Label{ + .token = label_token, + .block_inst = loop_block, + }); + } + + var then_scope: Scope.GenZir = .{ + .parent = &continue_scope.base, + .astgen = parent_gz.astgen, + .force_comptime = continue_scope.force_comptime, + .instructions = .{}, + }; + defer then_scope.instructions.deinit(mod.gpa); + + const then_sub_scope = &then_scope.base; + + loop_scope.break_count += 1; + const then_result = try expr(mod, then_sub_scope, loop_scope.break_result_loc, while_full.ast.then_expr); + + var else_scope: Scope.GenZir = .{ + .parent = &continue_scope.base, + .astgen = parent_gz.astgen, + .force_comptime = continue_scope.force_comptime, + .instructions = .{}, + }; + defer else_scope.instructions.deinit(mod.gpa); + + const else_node = while_full.ast.else_expr; + const else_info: struct { + src: ast.Node.Index, + result: zir.Inst.Ref, + } = if (else_node != 0) blk: { + loop_scope.break_count += 1; + const sub_scope = &else_scope.base; + break :blk .{ + .src = else_node, + .result = try expr(mod, sub_scope, loop_scope.break_result_loc, else_node), + }; + } else .{ + .src = while_full.ast.then_expr, + .result = .none, + }; + + if (loop_scope.label) |some| { + if (!some.used) { + return mod.failTok(scope, some.token, "unused while loop label", .{}); + } + } + const break_tag: zir.Inst.Tag = if (is_inline) .break_inline else .@"break"; + return finishThenElseBlock( + mod, + scope, + rl, + node, + &loop_scope, + &then_scope, + &else_scope, + condbr, + cond, + while_full.ast.then_expr, + else_info.src, + then_result, + else_info.result, + loop_block, + cond_block, + break_tag, + ); +} + +fn forExpr( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + node: ast.Node.Index, + for_full: ast.full.While, +) InnerError!zir.Inst.Ref { + if (for_full.label_token) |label_token| { + try checkLabelRedefinition(mod, scope, label_token); + } + // Set up variables and constants. + const parent_gz = scope.getGenZir(); + const is_inline = parent_gz.force_comptime or for_full.inline_token != null; + const tree = parent_gz.tree(); + const token_tags = tree.tokens.items(.tag); + + const array_ptr = try expr(mod, scope, .ref, for_full.ast.cond_expr); + const len = try parent_gz.addUnNode(.indexable_ptr_len, array_ptr, for_full.ast.cond_expr); + + const index_ptr = blk: { + const index_ptr = try parent_gz.addUnNode(.alloc, .usize_type, node); + // initialize to zero + _ = try parent_gz.addBin(.store, index_ptr, .zero_usize); + break :blk index_ptr; + }; + + const loop_tag: zir.Inst.Tag = if (is_inline) .block_inline else .loop; + const loop_block = try parent_gz.addBlock(loop_tag, node); + try parent_gz.instructions.append(mod.gpa, loop_block); + + var loop_scope: Scope.GenZir = .{ + .parent = scope, + .astgen = parent_gz.astgen, + .force_comptime = parent_gz.force_comptime, + .instructions = .{}, + }; + setBlockResultLoc(&loop_scope, rl); + defer loop_scope.instructions.deinit(mod.gpa); + + var cond_scope: Scope.GenZir = .{ + .parent = &loop_scope.base, + .astgen = parent_gz.astgen, + .force_comptime = loop_scope.force_comptime, + .instructions = .{}, + }; + defer cond_scope.instructions.deinit(mod.gpa); + + // check condition i < array_expr.len + const index = try cond_scope.addUnNode(.load, index_ptr, for_full.ast.cond_expr); + const cond = try cond_scope.addPlNode(.cmp_lt, for_full.ast.cond_expr, zir.Inst.Bin{ + .lhs = index, + .rhs = len, + }); + + const condbr_tag: zir.Inst.Tag = if (is_inline) .condbr_inline else .condbr; + const condbr = try cond_scope.addCondBr(condbr_tag, node); + const block_tag: zir.Inst.Tag = if (is_inline) .block_inline else .block; + const cond_block = try loop_scope.addBlock(block_tag, node); + try loop_scope.instructions.append(mod.gpa, cond_block); + try cond_scope.setBlockBody(cond_block); + + // Increment the index variable. + const index_2 = try loop_scope.addUnNode(.load, index_ptr, for_full.ast.cond_expr); + const index_plus_one = try loop_scope.addPlNode(.add, node, zir.Inst.Bin{ + .lhs = index_2, + .rhs = .one_usize, + }); + _ = try loop_scope.addBin(.store, index_ptr, index_plus_one); + const repeat_tag: zir.Inst.Tag = if (is_inline) .repeat_inline else .repeat; + _ = try loop_scope.addNode(repeat_tag, node); + + try loop_scope.setBlockBody(loop_block); + loop_scope.break_block = loop_block; + loop_scope.continue_block = cond_block; + if (for_full.label_token) |label_token| { + loop_scope.label = @as(?Scope.GenZir.Label, Scope.GenZir.Label{ + .token = label_token, + .block_inst = loop_block, + }); + } + + var then_scope: Scope.GenZir = .{ + .parent = &cond_scope.base, + .astgen = parent_gz.astgen, + .force_comptime = cond_scope.force_comptime, + .instructions = .{}, + }; + defer then_scope.instructions.deinit(mod.gpa); + + var index_scope: Scope.LocalPtr = undefined; + const then_sub_scope = blk: { + const payload_token = for_full.payload_token.?; + const ident = if (token_tags[payload_token] == .asterisk) + payload_token + 1 + else + payload_token; + const is_ptr = ident != payload_token; + const value_name = tree.tokenSlice(ident); + if (!mem.eql(u8, value_name, "_")) { + return mod.failNode(&then_scope.base, ident, "TODO implement for loop value payload", .{}); + } else if (is_ptr) { + return mod.failTok(&then_scope.base, payload_token, "pointer modifier invalid on discard", .{}); + } + + const index_token = if (token_tags[ident + 1] == .comma) + ident + 2 + else + break :blk &then_scope.base; + if (mem.eql(u8, tree.tokenSlice(index_token), "_")) { + return mod.failTok(&then_scope.base, index_token, "discard of index capture; omit it instead", .{}); + } + const index_name = try mod.identifierTokenString(&then_scope.base, index_token); + index_scope = .{ + .parent = &then_scope.base, + .gen_zir = &then_scope, + .name = index_name, + .ptr = index_ptr, + .src = parent_gz.tokSrcLoc(index_token), + }; + break :blk &index_scope.base; + }; + + loop_scope.break_count += 1; + const then_result = try expr(mod, then_sub_scope, loop_scope.break_result_loc, for_full.ast.then_expr); + + var else_scope: Scope.GenZir = .{ + .parent = &cond_scope.base, + .astgen = parent_gz.astgen, + .force_comptime = cond_scope.force_comptime, + .instructions = .{}, + }; + defer else_scope.instructions.deinit(mod.gpa); + + const else_node = for_full.ast.else_expr; + const else_info: struct { + src: ast.Node.Index, + result: zir.Inst.Ref, + } = if (else_node != 0) blk: { + loop_scope.break_count += 1; + const sub_scope = &else_scope.base; + break :blk .{ + .src = else_node, + .result = try expr(mod, sub_scope, loop_scope.break_result_loc, else_node), + }; + } else .{ + .src = for_full.ast.then_expr, + .result = .none, + }; + + if (loop_scope.label) |some| { + if (!some.used) { + return mod.failTok(scope, some.token, "unused for loop label", .{}); + } + } + const break_tag: zir.Inst.Tag = if (is_inline) .break_inline else .@"break"; + return finishThenElseBlock( + mod, + scope, + rl, + node, + &loop_scope, + &then_scope, + &else_scope, + condbr, + cond, + for_full.ast.then_expr, + else_info.src, + then_result, + else_info.result, + loop_block, + cond_block, + break_tag, + ); +} + +fn getRangeNode( + node_tags: []const ast.Node.Tag, + node_datas: []const ast.Node.Data, + start_node: ast.Node.Index, +) ?ast.Node.Index { + var node = start_node; + while (true) { + switch (node_tags[node]) { + .switch_range => return node, + .grouped_expression => node = node_datas[node].lhs, + else => return null, + } + } +} + +fn switchExpr( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + switch_node: ast.Node.Index, +) InnerError!zir.Inst.Ref { + if (true) @panic("TODO update for zir-memory-layout"); + const parent_gz = scope.getGenZir(); + const tree = parent_gz.tree(); + const node_datas = tree.nodes.items(.data); + const main_tokens = tree.nodes.items(.main_token); + const token_tags = tree.tokens.items(.tag); + const node_tags = tree.nodes.items(.tag); + + const switch_token = main_tokens[switch_node]; + const target_node = node_datas[switch_node].lhs; + const extra = tree.extraData(node_datas[switch_node].rhs, ast.Node.SubRange); + const case_nodes = tree.extra_data[extra.start..extra.end]; + + const switch_src = token_starts[switch_token]; + + var block_scope: Scope.GenZir = .{ + .parent = scope, + .decl = scope.ownerDecl().?, + .arena = scope.arena(), + .force_comptime = parent_gz.force_comptime, + .instructions = .{}, + }; + setBlockResultLoc(&block_scope, rl); + defer block_scope.instructions.deinit(mod.gpa); + + var items = std.ArrayList(zir.Inst.Ref).init(mod.gpa); + defer items.deinit(); + + // First we gather all the switch items and check else/'_' prongs. + var else_src: ?usize = null; + var underscore_src: ?usize = null; + var first_range: ?*zir.Inst = null; + var simple_case_count: usize = 0; + var any_payload_is_ref = false; + for (case_nodes) |case_node| { + const case = switch (node_tags[case_node]) { + .switch_case_one => tree.switchCaseOne(case_node), + .switch_case => tree.switchCase(case_node), + else => unreachable, + }; + if (case.payload_token) |payload_token| { + if (token_tags[payload_token] == .asterisk) { + any_payload_is_ref = true; + } + } + // Check for else/_ prong, those are handled last. + if (case.ast.values.len == 0) { + const case_src = token_starts[case.ast.arrow_token - 1]; + if (else_src) |src| { + const msg = msg: { + const msg = try mod.errMsg( + scope, + case_src, + "multiple else prongs in switch expression", + .{}, + ); + errdefer msg.destroy(mod.gpa); + try mod.errNote(scope, src, msg, "previous else prong is here", .{}); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(scope, msg); + } + else_src = case_src; + continue; + } else if (case.ast.values.len == 1 and + node_tags[case.ast.values[0]] == .identifier and + mem.eql(u8, tree.tokenSlice(main_tokens[case.ast.values[0]]), "_")) + { + const case_src = token_starts[case.ast.arrow_token - 1]; + if (underscore_src) |src| { + const msg = msg: { + const msg = try mod.errMsg( + scope, + case_src, + "multiple '_' prongs in switch expression", + .{}, + ); + errdefer msg.destroy(mod.gpa); + try mod.errNote(scope, src, msg, "previous '_' prong is here", .{}); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(scope, msg); + } + underscore_src = case_src; + continue; + } + + if (else_src) |some_else| { + if (underscore_src) |some_underscore| { + const msg = msg: { + const msg = try mod.errMsg( + scope, + switch_src, + "else and '_' prong in switch expression", + .{}, + ); + errdefer msg.destroy(mod.gpa); + try mod.errNote(scope, some_else, msg, "else prong is here", .{}); + try mod.errNote(scope, some_underscore, msg, "'_' prong is here", .{}); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(scope, msg); + } + } + + if (case.ast.values.len == 1 and + getRangeNode(node_tags, node_datas, case.ast.values[0]) == null) + { + simple_case_count += 1; + } + + // Generate all the switch items as comptime expressions. + for (case.ast.values) |item| { + if (getRangeNode(node_tags, node_datas, item)) |range| { + const start = try comptimeExpr(mod, &block_scope.base, .none, node_datas[range].lhs); + const end = try comptimeExpr(mod, &block_scope.base, .none, node_datas[range].rhs); + const range_src = token_starts[main_tokens[range]]; + const range_inst = try addZIRBinOp(mod, &block_scope.base, range_src, .switch_range, start, end); + try items.append(range_inst); + } else { + const item_inst = try comptimeExpr(mod, &block_scope.base, .none, item); + try items.append(item_inst); + } + } + } + + var special_prong: zir.Inst.SwitchBr.SpecialProng = .none; + if (else_src != null) special_prong = .@"else"; + if (underscore_src != null) special_prong = .underscore; + var cases = try block_scope.arena.alloc(zir.Inst.SwitchBr.Case, simple_case_count); + + const rl_and_tag: struct { rl: ResultLoc, tag: zir.Inst.Tag } = if (any_payload_is_ref) .{ + .rl = .ref, + .tag = .switchbr_ref, + } else .{ + .rl = .none, + .tag = .switchbr, + }; + const target = try expr(mod, &block_scope.base, rl_and_tag.rl, target_node); + const switch_inst = try addZirInstT(mod, &block_scope.base, switch_src, zir.Inst.SwitchBr, rl_and_tag.tag, .{ + .target = target, + .cases = cases, + .items = try block_scope.arena.dupe(zir.Inst.Ref, items.items), + .else_body = undefined, // populated below + .range = first_range, + .special_prong = special_prong, + }); + const block = try addZIRInstBlock(mod, scope, switch_src, .block, .{ + .instructions = try block_scope.arena.dupe(zir.Inst.Ref, block_scope.instructions.items), + }); + + var case_scope: Scope.GenZir = .{ + .parent = scope, + .decl = block_scope.decl, + .arena = block_scope.arena, + .force_comptime = block_scope.force_comptime, + .instructions = .{}, + }; + defer case_scope.instructions.deinit(mod.gpa); + + var else_scope: Scope.GenZir = .{ + .parent = scope, + .decl = case_scope.decl, + .arena = case_scope.arena, + .force_comptime = case_scope.force_comptime, + .instructions = .{}, + }; + defer else_scope.instructions.deinit(mod.gpa); + + // Now generate all but the special cases. + var special_case: ?ast.full.SwitchCase = null; + var items_index: usize = 0; + var case_index: usize = 0; + for (case_nodes) |case_node| { + const case = switch (node_tags[case_node]) { + .switch_case_one => tree.switchCaseOne(case_node), + .switch_case => tree.switchCase(case_node), + else => unreachable, + }; + const case_src = token_starts[main_tokens[case_node]]; + case_scope.instructions.shrinkRetainingCapacity(0); + + // Check for else/_ prong, those are handled last. + if (case.ast.values.len == 0) { + special_case = case; + continue; + } else if (case.ast.values.len == 1 and + node_tags[case.ast.values[0]] == .identifier and + mem.eql(u8, tree.tokenSlice(main_tokens[case.ast.values[0]]), "_")) + { + special_case = case; + continue; + } + + // If this is a simple one item prong then it is handled by the switchbr. + if (case.ast.values.len == 1 and + getRangeNode(node_tags, node_datas, case.ast.values[0]) == null) + { + const item = items.items[items_index]; + items_index += 1; + try switchCaseExpr(mod, &case_scope.base, block_scope.break_result_loc, block, case, target); + + cases[case_index] = .{ + .item = item, + .body = .{ .instructions = try scope.arena().dupe(zir.Inst.Ref, case_scope.instructions.items) }, + }; + case_index += 1; + continue; + } + + // Check if the target matches any of the items. + // 1, 2, 3..6 will result in + // target == 1 or target == 2 or (target >= 3 and target <= 6) + // TODO handle multiple items as switch prongs rather than along with ranges. + var any_ok: ?*zir.Inst = null; + for (case.ast.values) |item| { + if (getRangeNode(node_tags, node_datas, item)) |range| { + const range_src = token_starts[main_tokens[range]]; + const range_inst = items.items[items_index].castTag(.switch_range).?; + items_index += 1; + + // target >= start and target <= end + const range_start_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_gte, target, range_inst.positionals.lhs); + const range_end_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_lte, target, range_inst.positionals.rhs); + const range_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .bool_and, range_start_ok, range_end_ok); + + if (any_ok) |some| { + any_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .bool_or, some, range_ok); + } else { + any_ok = range_ok; + } + continue; + } + + const item_inst = items.items[items_index]; + items_index += 1; + const cpm_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .cmp_eq, target, item_inst); + + if (any_ok) |some| { + any_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .bool_or, some, cpm_ok); + } else { + any_ok = cpm_ok; + } + } + + const condbr = try addZIRInstSpecial(mod, &case_scope.base, case_src, zir.Inst.CondBr, .{ + .condition = any_ok.?, + .then_body = undefined, // populated below + .else_body = undefined, // populated below + }, .{}); + const cond_block = try addZIRInstBlock(mod, &else_scope.base, case_src, .block, .{ + .instructions = try scope.arena().dupe(zir.Inst.Ref, case_scope.instructions.items), + }); + + // reset cond_scope for then_body + case_scope.instructions.items.len = 0; + try switchCaseExpr(mod, &case_scope.base, block_scope.break_result_loc, block, case, target); + condbr.positionals.then_body = .{ + .instructions = try scope.arena().dupe(zir.Inst.Ref, case_scope.instructions.items), + }; + + // reset cond_scope for else_body + case_scope.instructions.items.len = 0; + _ = try addZIRInst(mod, &case_scope.base, case_src, zir.Inst.BreakVoid, .{ + .block = cond_block, + }, .{}); + condbr.positionals.else_body = .{ + .instructions = try scope.arena().dupe(zir.Inst.Ref, case_scope.instructions.items), + }; + } + + // Finally generate else block or a break. + if (special_case) |case| { + try switchCaseExpr(mod, &else_scope.base, block_scope.break_result_loc, block, case, target); + } else { + // Not handling all possible cases is a compile error. + _ = try addZIRNoOp(mod, &else_scope.base, switch_src, .unreachable_unsafe); + } + switch_inst.positionals.else_body = .{ + .instructions = try block_scope.arena.dupe(zir.Inst.Ref, else_scope.instructions.items), + }; + + return &block.base; +} + +fn switchCaseExpr( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + block: *zir.Inst.Block, + case: ast.full.SwitchCase, + target: zir.Inst.Ref, +) !void { + const tree = scope.tree(); + const node_datas = tree.nodes.items(.data); + const main_tokens = tree.nodes.items(.main_token); + const token_tags = tree.tokens.items(.tag); + + const case_src = token_starts[case.ast.arrow_token]; + const sub_scope = blk: { + const payload_token = case.payload_token orelse break :blk scope; + const ident = if (token_tags[payload_token] == .asterisk) + payload_token + 1 + else + payload_token; + const is_ptr = ident != payload_token; + const value_name = tree.tokenSlice(ident); + if (mem.eql(u8, value_name, "_")) { + if (is_ptr) { + return mod.failTok(scope, payload_token, "pointer modifier invalid on discard", .{}); + } + break :blk scope; + } + return mod.failTok(scope, ident, "TODO implement switch value payload", .{}); + }; + + const case_body = try expr(mod, sub_scope, rl, case.ast.target_expr); + if (!case_body.tag.isNoReturn()) { + _ = try addZIRInst(mod, sub_scope, case_src, zir.Inst.Break, .{ + .block = block, + .operand = case_body, + }, .{}); + } +} + +fn ret(mod: *Module, scope: *Scope, node: ast.Node.Index) InnerError!zir.Inst.Ref { + const tree = scope.tree(); + const node_datas = tree.nodes.items(.data); + const main_tokens = tree.nodes.items(.main_token); + + const operand_node = node_datas[node].lhs; + const gz = scope.getGenZir(); + const operand: zir.Inst.Ref = if (operand_node != 0) operand: { + const rl: ResultLoc = if (nodeMayNeedMemoryLocation(scope, operand_node)) .{ + .ptr = try gz.addNode(.ret_ptr, node), + } else .{ + .ty = try gz.addNode(.ret_type, node), + }; + break :operand try expr(mod, scope, rl, operand_node); + } else .void_value; + _ = try gz.addUnNode(.ret_node, operand, node); + return zir.Inst.Ref.unreachable_value; +} + +fn identifier( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + ident: ast.Node.Index, +) InnerError!zir.Inst.Ref { + const tracy = trace(@src()); + defer tracy.end(); + + const tree = scope.tree(); + const main_tokens = tree.nodes.items(.main_token); + + const gz = scope.getGenZir(); + + const ident_token = main_tokens[ident]; + const ident_name = try mod.identifierTokenString(scope, ident_token); + if (mem.eql(u8, ident_name, "_")) { + return mod.failNode(scope, ident, "TODO implement '_' identifier", .{}); + } + + if (simple_types.get(ident_name)) |zir_const_ref| { + return rvalue(mod, scope, rl, zir_const_ref, ident); + } + + if (ident_name.len >= 2) integer: { + const first_c = ident_name[0]; + if (first_c == 'i' or first_c == 'u') { + const signedness: std.builtin.Signedness = switch (first_c == 'i') { + true => .signed, + false => .unsigned, + }; + const bit_count = std.fmt.parseInt(u16, ident_name[1..], 10) catch |err| switch (err) { + error.Overflow => return mod.failNode( + scope, + ident, + "primitive integer type '{s}' exceeds maximum bit width of 65535", + .{ident_name}, + ), + error.InvalidCharacter => break :integer, + }; + const result = try gz.add(.{ + .tag = .int_type, + .data = .{ .int_type = .{ + .src_node = gz.astgen.decl.nodeIndexToRelative(ident), + .signedness = signedness, + .bit_count = bit_count, + } }, + }); + return rvalue(mod, scope, rl, result, ident); + } + } + + // Local variables, including function parameters. + { + var s = scope; + while (true) switch (s.tag) { + .local_val => { + const local_val = s.cast(Scope.LocalVal).?; + if (mem.eql(u8, local_val.name, ident_name)) { + return rvalue(mod, scope, rl, local_val.inst, ident); + } + s = local_val.parent; + }, + .local_ptr => { + const local_ptr = s.cast(Scope.LocalPtr).?; + if (mem.eql(u8, local_ptr.name, ident_name)) { + if (rl == .ref) return local_ptr.ptr; + const loaded = try gz.addUnNode(.load, local_ptr.ptr, ident); + return rvalue(mod, scope, rl, loaded, ident); + } + s = local_ptr.parent; + }, + .gen_zir => s = s.cast(Scope.GenZir).?.parent, + else => break, + }; + } + + const gop = try gz.astgen.decl_map.getOrPut(mod.gpa, ident_name); + if (!gop.found_existing) { + const decl = mod.lookupDeclName(scope, ident_name) orelse + return mod.failNode(scope, ident, "use of undeclared identifier '{s}'", .{ident_name}); + try gz.astgen.decls.append(mod.gpa, decl); + } + const decl_index = @intCast(u32, gop.index); + switch (rl) { + .ref => return gz.addDecl(.decl_ref, decl_index, ident), + else => return rvalue(mod, scope, rl, try gz.addDecl(.decl_val, decl_index, ident), ident), + } +} + +fn stringLiteral( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + node: ast.Node.Index, +) InnerError!zir.Inst.Ref { + const tree = scope.tree(); + const main_tokens = tree.nodes.items(.main_token); + const gz = scope.getGenZir(); + const string_bytes = &gz.astgen.string_bytes; + const str_index = string_bytes.items.len; + const str_lit_token = main_tokens[node]; + const token_bytes = tree.tokenSlice(str_lit_token); + try mod.parseStrLit(scope, str_lit_token, string_bytes, token_bytes, 0); + const str_len = string_bytes.items.len - str_index; + const result = try gz.add(.{ + .tag = .str, + .data = .{ .str = .{ + .start = @intCast(u32, str_index), + .len = @intCast(u32, str_len), + } }, + }); + return rvalue(mod, scope, rl, result, node); +} + +fn multilineStringLiteral( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + node: ast.Node.Index, +) InnerError!zir.Inst.Ref { + const gz = scope.getGenZir(); + const tree = gz.tree(); + const node_datas = tree.nodes.items(.data); + const main_tokens = tree.nodes.items(.main_token); + + const start = node_datas[node].lhs; + const end = node_datas[node].rhs; + const string_bytes = &gz.astgen.string_bytes; + const str_index = string_bytes.items.len; + + // First line: do not append a newline. + var tok_i = start; + { + const slice = tree.tokenSlice(tok_i); + const line_bytes = slice[2 .. slice.len - 1]; + try string_bytes.appendSlice(mod.gpa, line_bytes); + tok_i += 1; + } + // Following lines: each line prepends a newline. + while (tok_i <= end) : (tok_i += 1) { + const slice = tree.tokenSlice(tok_i); + const line_bytes = slice[2 .. slice.len - 1]; + try string_bytes.ensureCapacity(mod.gpa, string_bytes.items.len + line_bytes.len + 1); + string_bytes.appendAssumeCapacity('\n'); + string_bytes.appendSliceAssumeCapacity(line_bytes); + } + const result = try gz.add(.{ + .tag = .str, + .data = .{ .str = .{ + .start = @intCast(u32, str_index), + .len = @intCast(u32, string_bytes.items.len - str_index), + } }, + }); + return rvalue(mod, scope, rl, result, node); +} + +fn charLiteral(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) !zir.Inst.Ref { + const gz = scope.getGenZir(); + const tree = gz.tree(); + const main_tokens = tree.nodes.items(.main_token); + const main_token = main_tokens[node]; + const slice = tree.tokenSlice(main_token); + + var bad_index: usize = undefined; + const value = std.zig.parseCharLiteral(slice, &bad_index) catch |err| switch (err) { + error.InvalidCharacter => { + const bad_byte = slice[bad_index]; + const token_starts = tree.tokens.items(.start); + const src_off = @intCast(u32, token_starts[main_token] + bad_index); + return mod.failOff(scope, src_off, "invalid character: '{c}'\n", .{bad_byte}); + }, + }; + const result = try gz.addInt(value); + return rvalue(mod, scope, rl, result, node); +} + +fn integerLiteral( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + node: ast.Node.Index, +) InnerError!zir.Inst.Ref { + const tree = scope.tree(); + const main_tokens = tree.nodes.items(.main_token); + const int_token = main_tokens[node]; + const prefixed_bytes = tree.tokenSlice(int_token); + const gz = scope.getGenZir(); + if (std.fmt.parseInt(u64, prefixed_bytes, 0)) |small_int| { + const result: zir.Inst.Ref = switch (small_int) { + 0 => .zero, + 1 => .one, + else => try gz.addInt(small_int), + }; + return rvalue(mod, scope, rl, result, node); + } else |err| { + return mod.failNode(scope, node, "TODO implement int literals that don't fit in a u64", .{}); + } +} + +fn floatLiteral( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + node: ast.Node.Index, +) InnerError!zir.Inst.Ref { + const arena = scope.arena(); + const tree = scope.tree(); + const main_tokens = tree.nodes.items(.main_token); + const gz = scope.getGenZir(); + + const main_token = main_tokens[node]; + const bytes = tree.tokenSlice(main_token); + if (bytes.len > 2 and bytes[1] == 'x') { + assert(bytes[0] == '0'); // validated by tokenizer + return mod.failTok(scope, main_token, "TODO implement hex floats", .{}); + } + const float_number = std.fmt.parseFloat(f128, bytes) catch |e| switch (e) { + error.InvalidCharacter => unreachable, // validated by tokenizer + }; + const typed_value = try arena.create(TypedValue); + typed_value.* = .{ + .ty = Type.initTag(.comptime_float), + .val = try Value.Tag.float_128.create(arena, float_number), + }; + const result = try gz.add(.{ + .tag = .@"const", + .data = .{ .@"const" = typed_value }, + }); + return rvalue(mod, scope, rl, result, node); +} + +fn asmExpr( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + node: ast.Node.Index, + full: ast.full.Asm, +) InnerError!zir.Inst.Ref { + const arena = scope.arena(); + const tree = scope.tree(); + const main_tokens = tree.nodes.items(.main_token); + const node_datas = tree.nodes.items(.data); + const gz = scope.getGenZir(); + + const asm_source = try expr(mod, scope, .{ .ty = .const_slice_u8_type }, full.ast.template); + + if (full.outputs.len != 0) { + return mod.failTok(scope, full.ast.asm_token, "TODO implement asm with an output", .{}); + } + + const constraints = try arena.alloc(u32, full.inputs.len); + const args = try arena.alloc(zir.Inst.Ref, full.inputs.len); + + for (full.inputs) |input, i| { + const constraint_token = main_tokens[input] + 2; + const string_bytes = &gz.astgen.string_bytes; + constraints[i] = @intCast(u32, string_bytes.items.len); + const token_bytes = tree.tokenSlice(constraint_token); + try mod.parseStrLit(scope, constraint_token, string_bytes, token_bytes, 0); + try string_bytes.append(mod.gpa, 0); + + args[i] = try expr(mod, scope, .{ .ty = .usize_type }, node_datas[input].lhs); + } + + const tag: zir.Inst.Tag = if (full.volatile_token != null) .asm_volatile else .@"asm"; + const result = try gz.addPlNode(tag, node, zir.Inst.Asm{ + .asm_source = asm_source, + .return_type = .void_type, + .output = .none, + .args_len = @intCast(u32, full.inputs.len), + .clobbers_len = 0, // TODO implement asm clobbers + }); + + try gz.astgen.extra.ensureCapacity(mod.gpa, gz.astgen.extra.items.len + + args.len + constraints.len); + gz.astgen.appendRefsAssumeCapacity(args); + gz.astgen.extra.appendSliceAssumeCapacity(constraints); + + return rvalue(mod, scope, rl, result, node); +} + +fn as( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + builtin_token: ast.TokenIndex, + node: ast.Node.Index, + lhs: ast.Node.Index, + rhs: ast.Node.Index, +) InnerError!zir.Inst.Ref { + const dest_type = try typeExpr(mod, scope, lhs); + switch (rl) { + .none, .discard, .ref, .ty => { + const result = try expr(mod, scope, .{ .ty = dest_type }, rhs); + return rvalue(mod, scope, rl, result, node); + }, + + .ptr => |result_ptr| { + return asRlPtr(mod, scope, rl, result_ptr, rhs, dest_type); + }, + .block_ptr => |block_scope| { + return asRlPtr(mod, scope, rl, block_scope.rl_ptr, rhs, dest_type); + }, + + .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, 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, builtin_token, "TODO implement @as with inferred-type result location pointer", .{}); + }, + } +} + +fn asRlPtr( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + result_ptr: zir.Inst.Ref, + operand_node: ast.Node.Index, + dest_type: zir.Inst.Ref, +) InnerError!zir.Inst.Ref { + // Detect whether this expr() call goes into rvalue() to store the result into the + // result location. If it does, elide the coerce_result_ptr instruction + // as well as the store instruction, instead passing the result as an rvalue. + const parent_gz = scope.getGenZir(); + const astgen = parent_gz.astgen; + + var as_scope: Scope.GenZir = .{ + .parent = scope, + .astgen = astgen, + .force_comptime = parent_gz.force_comptime, + .instructions = .{}, + }; + defer as_scope.instructions.deinit(mod.gpa); + + as_scope.rl_ptr = try as_scope.addBin(.coerce_result_ptr, dest_type, result_ptr); + const result = try expr(mod, &as_scope.base, .{ .block_ptr = &as_scope }, operand_node); + const parent_zir = &parent_gz.instructions; + if (as_scope.rvalue_rl_count == 1) { + // Busted! This expression didn't actually need a pointer. + const zir_tags = astgen.instructions.items(.tag); + const zir_datas = astgen.instructions.items(.data); + const expected_len = parent_zir.items.len + as_scope.instructions.items.len - 2; + try parent_zir.ensureCapacity(mod.gpa, expected_len); + for (as_scope.instructions.items) |src_inst| { + if (astgen.indexToRef(src_inst) == as_scope.rl_ptr) continue; + if (zir_tags[src_inst] == .store_to_block_ptr) { + if (zir_datas[src_inst].bin.lhs == as_scope.rl_ptr) continue; + } + parent_zir.appendAssumeCapacity(src_inst); + } + assert(parent_zir.items.len == expected_len); + const casted_result = try parent_gz.addBin(.as, dest_type, result); + return rvalue(mod, scope, rl, casted_result, operand_node); + } else { + try parent_zir.appendSlice(mod.gpa, as_scope.instructions.items); + return result; + } +} + +fn bitCast( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + builtin_token: ast.TokenIndex, + node: ast.Node.Index, + lhs: ast.Node.Index, + rhs: ast.Node.Index, +) InnerError!zir.Inst.Ref { + if (true) @panic("TODO update for zir-memory-layout"); + const dest_type = try typeExpr(mod, scope, lhs); + switch (rl) { + .none => { + const operand = try expr(mod, scope, .none, rhs); + return addZIRBinOp(mod, scope, src, .bitcast, dest_type, operand); + }, + .discard => { + const operand = try expr(mod, scope, .none, rhs); + const result = try addZIRBinOp(mod, scope, src, .bitcast, dest_type, operand); + _ = try addZIRUnOp(mod, scope, result.src, .ensure_result_non_error, result); + return result; + }, + .ref => { + const operand = try expr(mod, scope, .ref, rhs); + const result = try addZIRBinOp(mod, scope, src, .bitcast_ref, dest_type, operand); + return result; + }, + .ty => |result_ty| { + const result = try expr(mod, scope, .none, rhs); + const bitcasted = try addZIRBinOp(mod, scope, src, .bitcast, dest_type, result); + return addZIRBinOp(mod, scope, src, .as, result_ty, bitcasted); + }, + .ptr => |result_ptr| { + const casted_result_ptr = try addZIRUnOp(mod, scope, src, .bitcast_result_ptr, result_ptr); + return expr(mod, scope, .{ .bitcasted_ptr = casted_result_ptr.castTag(.bitcast_result_ptr).? }, rhs); + }, + .bitcasted_ptr => |bitcasted_ptr| { + return mod.failTok(scope, builtin_token, "TODO implement @bitCast with result location another @bitCast", .{}); + }, + .block_ptr => |block_ptr| { + return mod.failTok(scope, 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, builtin_token, "TODO implement @bitCast with inferred-type result location pointer", .{}); + }, + } +} + +fn typeOf( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + builtin_token: ast.TokenIndex, + node: ast.Node.Index, + params: []const ast.Node.Index, +) InnerError!zir.Inst.Ref { + if (params.len < 1) { + return mod.failTok(scope, builtin_token, "expected at least 1 argument, found 0", .{}); + } + const gz = scope.getGenZir(); + if (params.len == 1) { + return rvalue( + mod, + scope, + rl, + try gz.addUnTok(.typeof, try expr(mod, scope, .none, params[0]), node), + node, + ); + } + const arena = scope.arena(); + var items = try arena.alloc(zir.Inst.Ref, params.len); + for (params) |param, param_i| { + items[param_i] = try expr(mod, scope, .none, param); + } + + const result = try gz.addPlNode(.typeof_peer, node, zir.Inst.MultiOp{ + .operands_len = @intCast(u32, params.len), + }); + try gz.astgen.appendRefs(items); + + return rvalue(mod, scope, rl, result, node); +} + +fn builtinCall( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + node: ast.Node.Index, + params: []const ast.Node.Index, +) InnerError!zir.Inst.Ref { + const tree = scope.tree(); + const main_tokens = tree.nodes.items(.main_token); + + const builtin_token = main_tokens[node]; + const builtin_name = tree.tokenSlice(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. + + const info = BuiltinFn.list.get(builtin_name) orelse { + return mod.failTok(scope, builtin_token, "invalid builtin function: '{s}'", .{ + builtin_name, + }); + }; + if (info.param_count) |expected| { + if (expected != params.len) { + const s = if (expected == 1) "" else "s"; + return mod.failTok(scope, builtin_token, "expected {d} parameter{s}, found {d}", .{ + expected, s, params.len, + }); + } + } + + const gz = scope.getGenZir(); + + switch (info.tag) { + .ptr_to_int => { + const operand = try expr(mod, scope, .none, params[0]); + const result = try gz.addUnNode(.ptrtoint, operand, node); + return rvalue(mod, scope, rl, result, node); + }, + .float_cast => { + const dest_type = try typeExpr(mod, scope, params[0]); + const rhs = try expr(mod, scope, .none, params[1]); + const result = try gz.addPlNode(.floatcast, node, zir.Inst.Bin{ + .lhs = dest_type, + .rhs = rhs, + }); + return rvalue(mod, scope, rl, result, node); + }, + .int_cast => { + const dest_type = try typeExpr(mod, scope, params[0]); + const rhs = try expr(mod, scope, .none, params[1]); + const result = try gz.addPlNode(.intcast, node, zir.Inst.Bin{ + .lhs = dest_type, + .rhs = rhs, + }); + return rvalue(mod, scope, rl, result, node); + }, + .breakpoint => { + const result = try gz.add(.{ + .tag = .breakpoint, + .data = .{ .node = gz.astgen.decl.nodeIndexToRelative(node) }, + }); + return rvalue(mod, scope, rl, result, node); + }, + .import => { + const target = try expr(mod, scope, .none, params[0]); + const result = try gz.addUnNode(.import, target, node); + return rvalue(mod, scope, rl, result, node); + }, + .compile_error => { + const target = try expr(mod, scope, .none, params[0]); + const result = try gz.addUnNode(.compile_error, target, node); + return rvalue(mod, scope, rl, result, node); + }, + .set_eval_branch_quota => { + const quota = try expr(mod, scope, .{ .ty = .u32_type }, params[0]); + const result = try gz.addUnNode(.set_eval_branch_quota, quota, node); + return rvalue(mod, scope, rl, result, node); + }, + .compile_log => { + const arg_refs = try mod.gpa.alloc(zir.Inst.Ref, params.len); + defer mod.gpa.free(arg_refs); + + for (params) |param, i| arg_refs[i] = try expr(mod, scope, .none, param); + + const result = try gz.addPlNode(.compile_log, node, zir.Inst.MultiOp{ + .operands_len = @intCast(u32, params.len), + }); + try gz.astgen.appendRefs(arg_refs); + return rvalue(mod, scope, rl, result, node); + }, + .field => { + const field_name = try comptimeExpr(mod, scope, .{ .ty = .const_slice_u8_type }, params[1]); + if (rl == .ref) { + return try gz.addPlNode(.field_ptr_named, node, zir.Inst.FieldNamed{ + .lhs = try expr(mod, scope, .ref, params[0]), + .field_name = field_name, + }); + } + const result = try gz.addPlNode(.field_val_named, node, zir.Inst.FieldNamed{ + .lhs = try expr(mod, scope, .none, params[0]), + .field_name = field_name, + }); + return rvalue(mod, scope, rl, result, node); + }, + .as => return as(mod, scope, rl, builtin_token, node, params[0], params[1]), + .bit_cast => return bitCast(mod, scope, rl, builtin_token, node, params[0], params[1]), + .TypeOf => return typeOf(mod, scope, rl, builtin_token, node, params), + + .add_with_overflow, + .align_cast, + .align_of, + .atomic_load, + .atomic_rmw, + .atomic_store, + .bit_offset_of, + .bool_to_int, + .bit_size_of, + .mul_add, + .byte_swap, + .bit_reverse, + .byte_offset_of, + .call, + .c_define, + .c_import, + .c_include, + .clz, + .cmpxchg_strong, + .cmpxchg_weak, + .ctz, + .c_undef, + .div_exact, + .div_floor, + .div_trunc, + .embed_file, + .enum_to_int, + .error_name, + .error_return_trace, + .error_to_int, + .err_set_cast, + .@"export", + .fence, + .field_parent_ptr, + .float_to_int, + .has_decl, + .has_field, + .int_to_enum, + .int_to_error, + .int_to_float, + .int_to_ptr, + .memcpy, + .memset, + .wasm_memory_size, + .wasm_memory_grow, + .mod, + .mul_with_overflow, + .panic, + .pop_count, + .ptr_cast, + .rem, + .return_address, + .set_align_stack, + .set_cold, + .set_float_mode, + .set_runtime_safety, + .shl_exact, + .shl_with_overflow, + .shr_exact, + .shuffle, + .size_of, + .splat, + .reduce, + .src, + .sqrt, + .sin, + .cos, + .exp, + .exp2, + .log, + .log2, + .log10, + .fabs, + .floor, + .ceil, + .trunc, + .round, + .sub_with_overflow, + .tag_name, + .This, + .truncate, + .Type, + .type_info, + .type_name, + .union_init, + => return mod.failTok(scope, builtin_token, "TODO: implement builtin function {s}", .{ + builtin_name, + }), + + .async_call, + .frame, + .Frame, + .frame_address, + .frame_size, + => return mod.failTok(scope, builtin_token, "async and related features are not yet supported", .{}), + } +} + +fn callExpr( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + node: ast.Node.Index, + call: ast.full.Call, +) InnerError!zir.Inst.Ref { + if (call.async_token) |async_token| { + return mod.failTok(scope, async_token, "async and related features are not yet supported", .{}); + } + const lhs = try expr(mod, scope, .none, call.ast.fn_expr); + + const args = try mod.gpa.alloc(zir.Inst.Ref, call.ast.params.len); + defer mod.gpa.free(args); + + const gz = scope.getGenZir(); + for (call.ast.params) |param_node, i| { + const param_type = try gz.add(.{ + .tag = .param_type, + .data = .{ .param_type = .{ + .callee = lhs, + .param_index = @intCast(u32, i), + } }, + }); + args[i] = try expr(mod, scope, .{ .ty = param_type }, param_node); + } + + const modifier: std.builtin.CallOptions.Modifier = switch (call.async_token != null) { + true => .async_kw, + false => .auto, + }; + const result: zir.Inst.Ref = res: { + const tag: zir.Inst.Tag = switch (modifier) { + .auto => switch (args.len == 0) { + true => break :res try gz.addUnNode(.call_none, lhs, node), + false => .call, + }, + .async_kw => return mod.failNode(scope, node, "async and related features are not yet supported", .{}), + .never_tail => unreachable, + .never_inline => unreachable, + .no_async => return mod.failNode(scope, node, "async and related features are not yet supported", .{}), + .always_tail => unreachable, + .always_inline => unreachable, + .compile_time => .call_compile_time, + }; + break :res try gz.addCall(tag, lhs, args, node); + }; + return rvalue(mod, scope, rl, result, node); // TODO function call with result location +} + +pub const simple_types = std.ComptimeStringMap(zir.Inst.Ref, .{ + .{ "u8", .u8_type }, + .{ "i8", .i8_type }, + .{ "u16", .u16_type }, + .{ "i16", .i16_type }, + .{ "u32", .u32_type }, + .{ "i32", .i32_type }, + .{ "u64", .u64_type }, + .{ "i64", .i64_type }, + .{ "usize", .usize_type }, + .{ "isize", .isize_type }, + .{ "c_short", .c_short_type }, + .{ "c_ushort", .c_ushort_type }, + .{ "c_int", .c_int_type }, + .{ "c_uint", .c_uint_type }, + .{ "c_long", .c_long_type }, + .{ "c_ulong", .c_ulong_type }, + .{ "c_longlong", .c_longlong_type }, + .{ "c_ulonglong", .c_ulonglong_type }, + .{ "c_longdouble", .c_longdouble_type }, + .{ "f16", .f16_type }, + .{ "f32", .f32_type }, + .{ "f64", .f64_type }, + .{ "f128", .f128_type }, + .{ "c_void", .c_void_type }, + .{ "bool", .bool_type }, + .{ "void", .void_type }, + .{ "type", .type_type }, + .{ "anyerror", .anyerror_type }, + .{ "comptime_int", .comptime_int_type }, + .{ "comptime_float", .comptime_float_type }, + .{ "noreturn", .noreturn_type }, + .{ "null", .null_type }, + .{ "undefined", .undefined_type }, + .{ "undefined", .undef }, + .{ "null", .null_value }, + .{ "true", .bool_true }, + .{ "false", .bool_false }, +}); + +fn nodeMayNeedMemoryLocation(scope: *Scope, start_node: ast.Node.Index) bool { + const tree = scope.tree(); + const node_tags = tree.nodes.items(.tag); + const node_datas = tree.nodes.items(.data); + const main_tokens = tree.nodes.items(.main_token); + const token_tags = tree.tokens.items(.tag); + + var node = start_node; + while (true) { + switch (node_tags[node]) { + .root, + .@"usingnamespace", + .test_decl, + .switch_case, + .switch_case_one, + .container_field_init, + .container_field_align, + .container_field, + .asm_output, + .asm_input, + => unreachable, + + .@"return", + .@"break", + .@"continue", + .bit_not, + .bool_not, + .global_var_decl, + .local_var_decl, + .simple_var_decl, + .aligned_var_decl, + .@"defer", + .@"errdefer", + .address_of, + .optional_type, + .negation, + .negation_wrap, + .@"resume", + .array_type, + .array_type_sentinel, + .ptr_type_aligned, + .ptr_type_sentinel, + .ptr_type, + .ptr_type_bit_range, + .@"suspend", + .@"anytype", + .fn_proto_simple, + .fn_proto_multi, + .fn_proto_one, + .fn_proto, + .fn_decl, + .anyframe_type, + .anyframe_literal, + .integer_literal, + .float_literal, + .enum_literal, + .string_literal, + .multiline_string_literal, + .char_literal, + .true_literal, + .false_literal, + .null_literal, + .undefined_literal, + .unreachable_literal, + .identifier, + .error_set_decl, + .container_decl, + .container_decl_trailing, + .container_decl_two, + .container_decl_two_trailing, + .container_decl_arg, + .container_decl_arg_trailing, + .tagged_union, + .tagged_union_trailing, + .tagged_union_two, + .tagged_union_two_trailing, + .tagged_union_enum_tag, + .tagged_union_enum_tag_trailing, + .@"asm", + .asm_simple, + .add, + .add_wrap, + .array_cat, + .array_mult, + .assign, + .assign_bit_and, + .assign_bit_or, + .assign_bit_shift_left, + .assign_bit_shift_right, + .assign_bit_xor, + .assign_div, + .assign_sub, + .assign_sub_wrap, + .assign_mod, + .assign_add, + .assign_add_wrap, + .assign_mul, + .assign_mul_wrap, + .bang_equal, + .bit_and, + .bit_or, + .bit_shift_left, + .bit_shift_right, + .bit_xor, + .bool_and, + .bool_or, + .div, + .equal_equal, + .error_union, + .greater_or_equal, + .greater_than, + .less_or_equal, + .less_than, + .merge_error_sets, + .mod, + .mul, + .mul_wrap, + .switch_range, + .field_access, + .sub, + .sub_wrap, + .slice, + .slice_open, + .slice_sentinel, + .deref, + .array_access, + .error_value, + .while_simple, // This variant cannot have an else expression. + .while_cont, // This variant cannot have an else expression. + .for_simple, // This variant cannot have an else expression. + .if_simple, // This variant cannot have an else expression. + => return false, + + // Forward the question to the LHS sub-expression. + .grouped_expression, + .@"try", + .@"await", + .@"comptime", + .@"nosuspend", + .unwrap_optional, + => node = node_datas[node].lhs, + + // Forward the question to the RHS sub-expression. + .@"catch", + .@"orelse", + => node = node_datas[node].rhs, + + // True because these are exactly the expressions we need memory locations for. + .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, + .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, + => return true, + + // True because depending on comptime conditions, sub-expressions + // may be the kind that need memory locations. + .@"while", // This variant always has an else expression. + .@"if", // This variant always has an else expression. + .@"for", // This variant always has an else expression. + .@"switch", + .switch_comma, + .call_one, + .call_one_comma, + .async_call_one, + .async_call_one_comma, + .call, + .call_comma, + .async_call, + .async_call_comma, + => return true, + + .block_two, + .block_two_semicolon, + .block, + .block_semicolon, + => { + const lbrace = main_tokens[node]; + if (token_tags[lbrace - 1] == .colon) { + // Labeled blocks may need a memory location to forward + // to their break statements. + return true; + } else { + return false; + } + }, + + .builtin_call, + .builtin_call_comma, + .builtin_call_two, + .builtin_call_two_comma, + => { + const builtin_token = main_tokens[node]; + const builtin_name = tree.tokenSlice(builtin_token); + // If the builtin is an invalid name, we don't cause an error here; instead + // let it pass, and the error will be "invalid builtin function" later. + const builtin_info = BuiltinFn.list.get(builtin_name) orelse return false; + return builtin_info.needs_mem_loc; + }, + } + } +} + +/// 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 rvalue( + mod: *Module, + scope: *Scope, + rl: ResultLoc, + result: zir.Inst.Ref, + src_node: ast.Node.Index, +) InnerError!zir.Inst.Ref { + const gz = scope.getGenZir(); + switch (rl) { + .none => return result, + .discard => { + // Emit a compile error for discarding error values. + _ = try gz.addUnNode(.ensure_result_non_error, result, src_node); + return result; + }, + .ref => { + // We need a pointer but we have a value. + const tree = scope.tree(); + const src_token = tree.firstToken(src_node); + return gz.addUnTok(.ref, result, src_token); + }, + .ty => |ty_inst| { + // Quickly eliminate some common, unnecessary type coercion. + const as_ty = @as(u64, @enumToInt(zir.Inst.Ref.type_type)) << 32; + const as_comptime_int = @as(u64, @enumToInt(zir.Inst.Ref.comptime_int_type)) << 32; + const as_bool = @as(u64, @enumToInt(zir.Inst.Ref.bool_type)) << 32; + const as_usize = @as(u64, @enumToInt(zir.Inst.Ref.usize_type)) << 32; + const as_void = @as(u64, @enumToInt(zir.Inst.Ref.void_type)) << 32; + switch ((@as(u64, @enumToInt(ty_inst)) << 32) | @as(u64, @enumToInt(result))) { + as_ty | @enumToInt(zir.Inst.Ref.u8_type), + as_ty | @enumToInt(zir.Inst.Ref.i8_type), + as_ty | @enumToInt(zir.Inst.Ref.u16_type), + as_ty | @enumToInt(zir.Inst.Ref.i16_type), + as_ty | @enumToInt(zir.Inst.Ref.u32_type), + as_ty | @enumToInt(zir.Inst.Ref.i32_type), + as_ty | @enumToInt(zir.Inst.Ref.u64_type), + as_ty | @enumToInt(zir.Inst.Ref.i64_type), + as_ty | @enumToInt(zir.Inst.Ref.usize_type), + as_ty | @enumToInt(zir.Inst.Ref.isize_type), + as_ty | @enumToInt(zir.Inst.Ref.c_short_type), + as_ty | @enumToInt(zir.Inst.Ref.c_ushort_type), + as_ty | @enumToInt(zir.Inst.Ref.c_int_type), + as_ty | @enumToInt(zir.Inst.Ref.c_uint_type), + as_ty | @enumToInt(zir.Inst.Ref.c_long_type), + as_ty | @enumToInt(zir.Inst.Ref.c_ulong_type), + as_ty | @enumToInt(zir.Inst.Ref.c_longlong_type), + as_ty | @enumToInt(zir.Inst.Ref.c_ulonglong_type), + as_ty | @enumToInt(zir.Inst.Ref.c_longdouble_type), + as_ty | @enumToInt(zir.Inst.Ref.f16_type), + as_ty | @enumToInt(zir.Inst.Ref.f32_type), + as_ty | @enumToInt(zir.Inst.Ref.f64_type), + as_ty | @enumToInt(zir.Inst.Ref.f128_type), + as_ty | @enumToInt(zir.Inst.Ref.c_void_type), + as_ty | @enumToInt(zir.Inst.Ref.bool_type), + as_ty | @enumToInt(zir.Inst.Ref.void_type), + as_ty | @enumToInt(zir.Inst.Ref.type_type), + as_ty | @enumToInt(zir.Inst.Ref.anyerror_type), + as_ty | @enumToInt(zir.Inst.Ref.comptime_int_type), + as_ty | @enumToInt(zir.Inst.Ref.comptime_float_type), + as_ty | @enumToInt(zir.Inst.Ref.noreturn_type), + as_ty | @enumToInt(zir.Inst.Ref.null_type), + as_ty | @enumToInt(zir.Inst.Ref.undefined_type), + as_ty | @enumToInt(zir.Inst.Ref.fn_noreturn_no_args_type), + as_ty | @enumToInt(zir.Inst.Ref.fn_void_no_args_type), + as_ty | @enumToInt(zir.Inst.Ref.fn_naked_noreturn_no_args_type), + as_ty | @enumToInt(zir.Inst.Ref.fn_ccc_void_no_args_type), + as_ty | @enumToInt(zir.Inst.Ref.single_const_pointer_to_comptime_int_type), + as_ty | @enumToInt(zir.Inst.Ref.const_slice_u8_type), + as_ty | @enumToInt(zir.Inst.Ref.enum_literal_type), + as_comptime_int | @enumToInt(zir.Inst.Ref.zero), + as_comptime_int | @enumToInt(zir.Inst.Ref.one), + as_bool | @enumToInt(zir.Inst.Ref.bool_true), + as_bool | @enumToInt(zir.Inst.Ref.bool_false), + as_usize | @enumToInt(zir.Inst.Ref.zero_usize), + as_usize | @enumToInt(zir.Inst.Ref.one_usize), + as_void | @enumToInt(zir.Inst.Ref.void_value), + => return result, // type of result is already correct + + // Need an explicit type coercion instruction. + else => return gz.addPlNode(.as_node, src_node, zir.Inst.As{ + .dest_type = ty_inst, + .operand = result, + }), + } + }, + .ptr => |ptr_inst| { + _ = try gz.addPlNode(.store_node, src_node, zir.Inst.Bin{ + .lhs = ptr_inst, + .rhs = result, + }); + return result; + }, + .bitcasted_ptr => |bitcasted_ptr| { + return mod.failNode(scope, src_node, "TODO implement rvalue .bitcasted_ptr", .{}); + }, + .inferred_ptr => |alloc| { + _ = try gz.addBin(.store_to_inferred_ptr, alloc, result); + return result; + }, + .block_ptr => |block_scope| { + block_scope.rvalue_rl_count += 1; + _ = try gz.addBin(.store_to_block_ptr, block_scope.rl_ptr, result); + return result; + }, + } +} + +fn rlStrategy(rl: ResultLoc, block_scope: *Scope.GenZir) ResultLoc.Strategy { + var elide_store_to_block_ptr_instructions = false; + switch (rl) { + // In this branch there will not be any store_to_block_ptr instructions. + .discard, .none, .ty, .ref => return .{ + .tag = .break_operand, + .elide_store_to_block_ptr_instructions = false, + }, + // The pointer got passed through to the sub-expressions, so we will use + // break_void here. + // In this branch there will not be any store_to_block_ptr instructions. + .ptr => return .{ + .tag = .break_void, + .elide_store_to_block_ptr_instructions = false, + }, + .inferred_ptr, .bitcasted_ptr, .block_ptr => { + if (block_scope.rvalue_rl_count == block_scope.break_count) { + // Neither prong of the if consumed the result location, so we can + // use break instructions to create an rvalue. + return .{ + .tag = .break_operand, + .elide_store_to_block_ptr_instructions = true, + }; + } else { + // Allow the store_to_block_ptr instructions to remain so that + // semantic analysis can turn them into bitcasts. + return .{ + .tag = .break_void, + .elide_store_to_block_ptr_instructions = false, + }; + } + }, + } +} + +fn setBlockResultLoc(block_scope: *Scope.GenZir, parent_rl: ResultLoc) void { + // Depending on whether the result location is a pointer or value, different + // ZIR needs to be generated. In the former case we rely on storing to the + // pointer to communicate the result, and use breakvoid; in the latter case + // the block break instructions will have the result values. + // One more complication: when the result location is a pointer, we detect + // the scenario where the result location is not consumed. In this case + // we emit ZIR for the block break instructions to have the result values, + // and then rvalue() on that to pass the value to the result location. + switch (parent_rl) { + .discard, .none, .ty, .ptr, .ref => { + block_scope.break_result_loc = parent_rl; + }, + + .inferred_ptr => |ptr| { + block_scope.rl_ptr = ptr; + block_scope.break_result_loc = .{ .block_ptr = block_scope }; + }, + + .bitcasted_ptr => |ptr| { + block_scope.rl_ptr = ptr; + block_scope.break_result_loc = .{ .block_ptr = block_scope }; + }, + + .block_ptr => |parent_block_scope| { + block_scope.rl_ptr = parent_block_scope.rl_ptr; + block_scope.break_result_loc = .{ .block_ptr = block_scope }; + }, + } +} diff --git a/src/Module.zig b/src/Module.zig @@ -23,7 +23,7 @@ const link = @import("link.zig"); const ir = @import("ir.zig"); const zir = @import("zir.zig"); const trace = @import("tracy.zig").trace; -const astgen = @import("astgen.zig"); +const AstGen = @import("AstGen.zig"); const Sema = @import("Sema.zig"); const target_util = @import("target.zig"); @@ -407,9 +407,9 @@ pub const Scope = struct { pub fn arena(scope: *Scope) *Allocator { switch (scope.tag) { .block => return scope.cast(Block).?.sema.arena, - .gen_zir => return scope.cast(GenZir).?.zir_code.arena, - .local_val => return scope.cast(LocalVal).?.gen_zir.zir_code.arena, - .local_ptr => return scope.cast(LocalPtr).?.gen_zir.zir_code.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, .container => unreachable, .decl_ref => unreachable, @@ -419,9 +419,9 @@ pub const Scope = struct { pub fn ownerDecl(scope: *Scope) ?*Decl { return switch (scope.tag) { .block => scope.cast(Block).?.sema.owner_decl, - .gen_zir => scope.cast(GenZir).?.zir_code.decl, - .local_val => scope.cast(LocalVal).?.gen_zir.zir_code.decl, - .local_ptr => scope.cast(LocalPtr).?.gen_zir.zir_code.decl, + .gen_zir => scope.cast(GenZir).?.astgen.decl, + .local_val => scope.cast(LocalVal).?.gen_zir.astgen.decl, + .local_ptr => scope.cast(LocalPtr).?.gen_zir.astgen.decl, .file => null, .container => null, .decl_ref => scope.cast(DeclRef).?.decl, @@ -431,9 +431,9 @@ pub const Scope = struct { pub fn srcDecl(scope: *Scope) ?*Decl { return switch (scope.tag) { .block => scope.cast(Block).?.src_decl, - .gen_zir => scope.cast(GenZir).?.zir_code.decl, - .local_val => scope.cast(LocalVal).?.gen_zir.zir_code.decl, - .local_ptr => scope.cast(LocalPtr).?.gen_zir.zir_code.decl, + .gen_zir => scope.cast(GenZir).?.astgen.decl, + .local_val => scope.cast(LocalVal).?.gen_zir.astgen.decl, + .local_ptr => scope.cast(LocalPtr).?.gen_zir.astgen.decl, .file => null, .container => null, .decl_ref => scope.cast(DeclRef).?.decl, @@ -444,9 +444,9 @@ pub const Scope = struct { pub fn namespace(scope: *Scope) *Container { switch (scope.tag) { .block => return scope.cast(Block).?.sema.owner_decl.container, - .gen_zir => return scope.cast(GenZir).?.zir_code.decl.container, - .local_val => return scope.cast(LocalVal).?.gen_zir.zir_code.decl.container, - .local_ptr => return scope.cast(LocalPtr).?.gen_zir.zir_code.decl.container, + .gen_zir => return scope.cast(GenZir).?.astgen.decl.container, + .local_val => return scope.cast(LocalVal).?.gen_zir.astgen.decl.container, + .local_ptr => return scope.cast(LocalPtr).?.gen_zir.astgen.decl.container, .file => return &scope.cast(File).?.root_container, .container => return scope.cast(Container).?, .decl_ref => return scope.cast(DeclRef).?.decl.container, @@ -474,8 +474,8 @@ pub const Scope = struct { .file => return &scope.cast(File).?.tree, .block => return &scope.cast(Block).?.src_decl.container.file_scope.tree, .gen_zir => return scope.cast(GenZir).?.tree(), - .local_val => return &scope.cast(LocalVal).?.gen_zir.zir_code.decl.container.file_scope.tree, - .local_ptr => return &scope.cast(LocalPtr).?.gen_zir.zir_code.decl.container.file_scope.tree, + .local_val => return &scope.cast(LocalVal).?.gen_zir.astgen.decl.container.file_scope.tree, + .local_ptr => return &scope.cast(LocalPtr).?.gen_zir.astgen.decl.container.file_scope.tree, .container => return &scope.cast(Container).?.file_scope.tree, .decl_ref => return &scope.cast(DeclRef).?.decl.container.file_scope.tree, } @@ -913,15 +913,15 @@ pub const Scope = struct { /// Parents can be: `GenZir`, `File` parent: *Scope, /// All `GenZir` scopes for the same ZIR share this. - zir_code: *WipZirCode, + astgen: *AstGen, /// Keeps track of the list of instructions in this scope only. Indexes - /// to instructions in `zir_code`. + /// to instructions in `astgen`. instructions: ArrayListUnmanaged(zir.Inst.Index) = .{}, label: ?Label = null, break_block: zir.Inst.Index = 0, continue_block: zir.Inst.Index = 0, /// Only valid when setBlockResultLoc is called. - break_result_loc: astgen.ResultLoc = undefined, + break_result_loc: AstGen.ResultLoc = undefined, /// When a block has a pointer result location, here it is. rl_ptr: zir.Inst.Ref = .none, /// Keeps track of how many branches of a block did not actually @@ -948,49 +948,51 @@ pub const Scope = struct { }; /// Only valid to call on the top of the `GenZir` stack. Completes the - /// `WipZirCode` into a `zir.Code`. Leaves the `WipZirCode` in an + /// `AstGen` into a `zir.Code`. Leaves the `AstGen` in an /// initialized, but empty, state. pub fn finish(gz: *GenZir) !zir.Code { - const gpa = gz.zir_code.gpa; + const gpa = gz.astgen.mod.gpa; try gz.setBlockBody(0); return zir.Code{ - .instructions = gz.zir_code.instructions.toOwnedSlice(), - .string_bytes = gz.zir_code.string_bytes.toOwnedSlice(gpa), - .extra = gz.zir_code.extra.toOwnedSlice(gpa), - .decls = gz.zir_code.decls.toOwnedSlice(gpa), + .instructions = gz.astgen.instructions.toOwnedSlice(), + .string_bytes = gz.astgen.string_bytes.toOwnedSlice(gpa), + .extra = gz.astgen.extra.toOwnedSlice(gpa), + .decls = gz.astgen.decls.toOwnedSlice(gpa), }; } pub fn tokSrcLoc(gz: GenZir, token_index: ast.TokenIndex) LazySrcLoc { - return gz.zir_code.decl.tokSrcLoc(token_index); + return gz.astgen.decl.tokSrcLoc(token_index); } pub fn nodeSrcLoc(gz: GenZir, node_index: ast.Node.Index) LazySrcLoc { - return gz.zir_code.decl.nodeSrcLoc(node_index); + return gz.astgen.decl.nodeSrcLoc(node_index); } pub fn tree(gz: *const GenZir) *const ast.Tree { - return &gz.zir_code.decl.container.file_scope.tree; + return &gz.astgen.decl.container.file_scope.tree; } pub fn setBoolBrBody(gz: GenZir, inst: zir.Inst.Index) !void { - try gz.zir_code.extra.ensureCapacity(gz.zir_code.gpa, gz.zir_code.extra.items.len + + const gpa = gz.astgen.mod.gpa; + try gz.astgen.extra.ensureCapacity(gpa, gz.astgen.extra.items.len + @typeInfo(zir.Inst.Block).Struct.fields.len + gz.instructions.items.len); - const zir_datas = gz.zir_code.instructions.items(.data); - zir_datas[inst].bool_br.payload_index = gz.zir_code.addExtraAssumeCapacity( + const zir_datas = gz.astgen.instructions.items(.data); + zir_datas[inst].bool_br.payload_index = gz.astgen.addExtraAssumeCapacity( zir.Inst.Block{ .body_len = @intCast(u32, gz.instructions.items.len) }, ); - gz.zir_code.extra.appendSliceAssumeCapacity(gz.instructions.items); + gz.astgen.extra.appendSliceAssumeCapacity(gz.instructions.items); } pub fn setBlockBody(gz: GenZir, inst: zir.Inst.Index) !void { - try gz.zir_code.extra.ensureCapacity(gz.zir_code.gpa, gz.zir_code.extra.items.len + + const gpa = gz.astgen.mod.gpa; + try gz.astgen.extra.ensureCapacity(gpa, gz.astgen.extra.items.len + @typeInfo(zir.Inst.Block).Struct.fields.len + gz.instructions.items.len); - const zir_datas = gz.zir_code.instructions.items(.data); - zir_datas[inst].pl_node.payload_index = gz.zir_code.addExtraAssumeCapacity( + const zir_datas = gz.astgen.instructions.items(.data); + zir_datas[inst].pl_node.payload_index = gz.astgen.addExtraAssumeCapacity( zir.Inst.Block{ .body_len = @intCast(u32, gz.instructions.items.len) }, ); - gz.zir_code.extra.appendSliceAssumeCapacity(gz.instructions.items); + gz.astgen.extra.appendSliceAssumeCapacity(gz.instructions.items); } pub fn addFnTypeCc(gz: *GenZir, tag: zir.Inst.Tag, args: struct { @@ -1000,20 +1002,20 @@ pub const Scope = struct { }) !zir.Inst.Ref { assert(args.ret_ty != .none); assert(args.cc != .none); - const gpa = gz.zir_code.gpa; + const gpa = gz.astgen.mod.gpa; try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1); - try gz.zir_code.instructions.ensureCapacity(gpa, gz.zir_code.instructions.len + 1); - try gz.zir_code.extra.ensureCapacity(gpa, gz.zir_code.extra.items.len + + try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1); + try gz.astgen.extra.ensureCapacity(gpa, gz.astgen.extra.items.len + @typeInfo(zir.Inst.FnTypeCc).Struct.fields.len + args.param_types.len); - const payload_index = gz.zir_code.addExtraAssumeCapacity(zir.Inst.FnTypeCc{ + const payload_index = gz.astgen.addExtraAssumeCapacity(zir.Inst.FnTypeCc{ .cc = args.cc, .param_types_len = @intCast(u32, args.param_types.len), }); - gz.zir_code.appendRefsAssumeCapacity(args.param_types); + gz.astgen.appendRefsAssumeCapacity(args.param_types); - const new_index = @intCast(zir.Inst.Index, gz.zir_code.instructions.len); - gz.zir_code.instructions.appendAssumeCapacity(.{ + const new_index = @intCast(zir.Inst.Index, gz.astgen.instructions.len); + gz.astgen.instructions.appendAssumeCapacity(.{ .tag = tag, .data = .{ .fn_type = .{ .return_type = args.ret_ty, @@ -1021,7 +1023,7 @@ pub const Scope = struct { } }, }); gz.instructions.appendAssumeCapacity(new_index); - return gz.zir_code.indexToRef(new_index); + return gz.astgen.indexToRef(new_index); } pub fn addFnType( @@ -1031,19 +1033,19 @@ pub const Scope = struct { param_types: []const zir.Inst.Ref, ) !zir.Inst.Ref { assert(ret_ty != .none); - const gpa = gz.zir_code.gpa; + const gpa = gz.astgen.mod.gpa; try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1); - try gz.zir_code.instructions.ensureCapacity(gpa, gz.zir_code.instructions.len + 1); - try gz.zir_code.extra.ensureCapacity(gpa, gz.zir_code.extra.items.len + + try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1); + try gz.astgen.extra.ensureCapacity(gpa, gz.astgen.extra.items.len + @typeInfo(zir.Inst.FnType).Struct.fields.len + param_types.len); - const payload_index = gz.zir_code.addExtraAssumeCapacity(zir.Inst.FnType{ + const payload_index = gz.astgen.addExtraAssumeCapacity(zir.Inst.FnType{ .param_types_len = @intCast(u32, param_types.len), }); - gz.zir_code.appendRefsAssumeCapacity(param_types); + gz.astgen.appendRefsAssumeCapacity(param_types); - const new_index = @intCast(zir.Inst.Index, gz.zir_code.instructions.len); - gz.zir_code.instructions.appendAssumeCapacity(.{ + const new_index = @intCast(zir.Inst.Index, gz.astgen.instructions.len); + gz.astgen.instructions.appendAssumeCapacity(.{ .tag = tag, .data = .{ .fn_type = .{ .return_type = ret_ty, @@ -1051,7 +1053,7 @@ pub const Scope = struct { } }, }); gz.instructions.appendAssumeCapacity(new_index); - return gz.zir_code.indexToRef(new_index); + return gz.astgen.indexToRef(new_index); } pub fn addCall( @@ -1064,28 +1066,28 @@ pub const Scope = struct { ) !zir.Inst.Ref { assert(callee != .none); assert(src_node != 0); - const gpa = gz.zir_code.gpa; + const gpa = gz.astgen.mod.gpa; try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1); - try gz.zir_code.instructions.ensureCapacity(gpa, gz.zir_code.instructions.len + 1); - try gz.zir_code.extra.ensureCapacity(gpa, gz.zir_code.extra.items.len + + try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1); + try gz.astgen.extra.ensureCapacity(gpa, gz.astgen.extra.items.len + @typeInfo(zir.Inst.Call).Struct.fields.len + args.len); - const payload_index = gz.zir_code.addExtraAssumeCapacity(zir.Inst.Call{ + const payload_index = gz.astgen.addExtraAssumeCapacity(zir.Inst.Call{ .callee = callee, .args_len = @intCast(u32, args.len), }); - gz.zir_code.appendRefsAssumeCapacity(args); + gz.astgen.appendRefsAssumeCapacity(args); - const new_index = @intCast(zir.Inst.Index, gz.zir_code.instructions.len); - gz.zir_code.instructions.appendAssumeCapacity(.{ + const new_index = @intCast(zir.Inst.Index, gz.astgen.instructions.len); + gz.astgen.instructions.appendAssumeCapacity(.{ .tag = tag, .data = .{ .pl_node = .{ - .src_node = gz.zir_code.decl.nodeIndexToRelative(src_node), + .src_node = gz.astgen.decl.nodeIndexToRelative(src_node), .payload_index = payload_index, } }, }); gz.instructions.appendAssumeCapacity(new_index); - return gz.zir_code.indexToRef(new_index); + return gz.astgen.indexToRef(new_index); } /// Note that this returns a `zir.Inst.Index` not a ref. @@ -1096,12 +1098,12 @@ pub const Scope = struct { lhs: zir.Inst.Ref, ) !zir.Inst.Index { assert(lhs != .none); - const gpa = gz.zir_code.gpa; + const gpa = gz.astgen.mod.gpa; try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1); - try gz.zir_code.instructions.ensureCapacity(gpa, gz.zir_code.instructions.len + 1); + try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1); - const new_index = @intCast(zir.Inst.Index, gz.zir_code.instructions.len); - gz.zir_code.instructions.appendAssumeCapacity(.{ + const new_index = @intCast(zir.Inst.Index, gz.astgen.instructions.len); + gz.astgen.instructions.appendAssumeCapacity(.{ .tag = tag, .data = .{ .bool_br = .{ .lhs = lhs, @@ -1131,7 +1133,7 @@ pub const Scope = struct { .tag = tag, .data = .{ .un_node = .{ .operand = operand, - .src_node = gz.zir_code.decl.nodeIndexToRelative(src_node), + .src_node = gz.astgen.decl.nodeIndexToRelative(src_node), } }, }); } @@ -1143,21 +1145,21 @@ pub const Scope = struct { src_node: ast.Node.Index, extra: anytype, ) !zir.Inst.Ref { - const gpa = gz.zir_code.gpa; + const gpa = gz.astgen.mod.gpa; try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1); - try gz.zir_code.instructions.ensureCapacity(gpa, gz.zir_code.instructions.len + 1); + try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1); - const payload_index = try gz.zir_code.addExtra(extra); - const new_index = @intCast(zir.Inst.Index, gz.zir_code.instructions.len); - gz.zir_code.instructions.appendAssumeCapacity(.{ + const payload_index = try gz.astgen.addExtra(extra); + const new_index = @intCast(zir.Inst.Index, gz.astgen.instructions.len); + gz.astgen.instructions.appendAssumeCapacity(.{ .tag = tag, .data = .{ .pl_node = .{ - .src_node = gz.zir_code.decl.nodeIndexToRelative(src_node), + .src_node = gz.astgen.decl.nodeIndexToRelative(src_node), .payload_index = payload_index, } }, }); gz.instructions.appendAssumeCapacity(new_index); - return gz.zir_code.indexToRef(new_index); + return gz.astgen.indexToRef(new_index); } pub fn addArrayTypeSentinel( @@ -1166,16 +1168,16 @@ pub const Scope = struct { sentinel: zir.Inst.Ref, elem_type: zir.Inst.Ref, ) !zir.Inst.Ref { - const gpa = gz.zir_code.gpa; + const gpa = gz.astgen.mod.gpa; try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1); - try gz.zir_code.instructions.ensureCapacity(gpa, gz.zir_code.instructions.len + 1); + try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1); - const payload_index = try gz.zir_code.addExtra(zir.Inst.ArrayTypeSentinel{ + const payload_index = try gz.astgen.addExtra(zir.Inst.ArrayTypeSentinel{ .sentinel = sentinel, .elem_type = elem_type, }); - const new_index = @intCast(zir.Inst.Index, gz.zir_code.instructions.len); - gz.zir_code.instructions.appendAssumeCapacity(.{ + const new_index = @intCast(zir.Inst.Index, gz.astgen.instructions.len); + gz.astgen.instructions.appendAssumeCapacity(.{ .tag = .array_type_sentinel, .data = .{ .array_type_sentinel = .{ .len = len, @@ -1183,7 +1185,7 @@ pub const Scope = struct { } }, }); gz.instructions.appendAssumeCapacity(new_index); - return gz.zir_code.indexToRef(new_index); + return gz.astgen.indexToRef(new_index); } pub fn addUnTok( @@ -1198,7 +1200,7 @@ pub const Scope = struct { .tag = tag, .data = .{ .un_tok = .{ .operand = operand, - .src_tok = abs_tok_index - gz.zir_code.decl.srcToken(), + .src_tok = abs_tok_index - gz.astgen.decl.srcToken(), } }, }); } @@ -1214,7 +1216,7 @@ pub const Scope = struct { .tag = tag, .data = .{ .str_tok = .{ .start = str_index, - .src_tok = abs_tok_index - gz.zir_code.decl.srcToken(), + .src_tok = abs_tok_index - gz.astgen.decl.srcToken(), } }, }); } @@ -1260,7 +1262,7 @@ pub const Scope = struct { return gz.add(.{ .tag = tag, .data = .{ .pl_node = .{ - .src_node = gz.zir_code.decl.nodeIndexToRelative(src_node), + .src_node = gz.astgen.decl.nodeIndexToRelative(src_node), .payload_index = decl_index, } }, }); @@ -1274,7 +1276,7 @@ pub const Scope = struct { ) !zir.Inst.Ref { return gz.add(.{ .tag = tag, - .data = .{ .node = gz.zir_code.decl.nodeIndexToRelative(src_node) }, + .data = .{ .node = gz.astgen.decl.nodeIndexToRelative(src_node) }, }); } @@ -1298,11 +1300,12 @@ pub const Scope = struct { /// Does *not* append the block instruction to the scope. /// Leaves the `payload_index` field undefined. pub fn addBlock(gz: *GenZir, tag: zir.Inst.Tag, node: ast.Node.Index) !zir.Inst.Index { - const new_index = @intCast(zir.Inst.Index, gz.zir_code.instructions.len); - try gz.zir_code.instructions.append(gz.zir_code.gpa, .{ + const new_index = @intCast(zir.Inst.Index, gz.astgen.instructions.len); + const gpa = gz.astgen.mod.gpa; + try gz.astgen.instructions.append(gpa, .{ .tag = tag, .data = .{ .pl_node = .{ - .src_node = gz.zir_code.decl.nodeIndexToRelative(node), + .src_node = gz.astgen.decl.nodeIndexToRelative(node), .payload_index = undefined, } }, }); @@ -1312,12 +1315,13 @@ pub const Scope = struct { /// Note that this returns a `zir.Inst.Index` not a ref. /// Leaves the `payload_index` field undefined. pub fn addCondBr(gz: *GenZir, tag: zir.Inst.Tag, node: ast.Node.Index) !zir.Inst.Index { - try gz.instructions.ensureCapacity(gz.zir_code.gpa, gz.instructions.items.len + 1); - const new_index = @intCast(zir.Inst.Index, gz.zir_code.instructions.len); - try gz.zir_code.instructions.append(gz.zir_code.gpa, .{ + const gpa = gz.astgen.mod.gpa; + try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1); + const new_index = @intCast(zir.Inst.Index, gz.astgen.instructions.len); + try gz.astgen.instructions.append(gpa, .{ .tag = tag, .data = .{ .pl_node = .{ - .src_node = gz.zir_code.decl.nodeIndexToRelative(node), + .src_node = gz.astgen.decl.nodeIndexToRelative(node), .payload_index = undefined, } }, }); @@ -1326,16 +1330,16 @@ pub const Scope = struct { } pub fn add(gz: *GenZir, inst: zir.Inst) !zir.Inst.Ref { - return gz.zir_code.indexToRef(try gz.addAsIndex(inst)); + return gz.astgen.indexToRef(try gz.addAsIndex(inst)); } pub fn addAsIndex(gz: *GenZir, inst: zir.Inst) !zir.Inst.Index { - const gpa = gz.zir_code.gpa; + const gpa = gz.astgen.mod.gpa; try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1); - try gz.zir_code.instructions.ensureCapacity(gpa, gz.zir_code.instructions.len + 1); + try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1); - const new_index = @intCast(zir.Inst.Index, gz.zir_code.instructions.len); - gz.zir_code.instructions.appendAssumeCapacity(inst); + const new_index = @intCast(zir.Inst.Index, gz.astgen.instructions.len); + gz.astgen.instructions.appendAssumeCapacity(inst); gz.instructions.appendAssumeCapacity(new_index); return new_index; } @@ -1378,100 +1382,6 @@ pub const Scope = struct { }; }; -/// A Work-In-Progress `zir.Code`. This is a shared parent of all -/// `GenZir` scopes. Once the `zir.Code` is produced, this struct -/// is deinitialized. -/// The `GenZir.finish` function converts this to a `zir.Code`. -pub const WipZirCode = struct { - instructions: std.MultiArrayList(zir.Inst) = .{}, - string_bytes: ArrayListUnmanaged(u8) = .{}, - extra: ArrayListUnmanaged(u32) = .{}, - decl_map: std.StringArrayHashMapUnmanaged(void) = .{}, - decls: ArrayListUnmanaged(*Decl) = .{}, - /// The end of special indexes. `zir.Inst.Ref` subtracts against this number to convert - /// to `zir.Inst.Index`. The default here is correct if there are 0 parameters. - ref_start_index: u32 = zir.Inst.Ref.typed_value_map.len, - decl: *Decl, - gpa: *Allocator, - arena: *Allocator, - - pub fn addExtra(wzc: *WipZirCode, extra: anytype) Allocator.Error!u32 { - const fields = std.meta.fields(@TypeOf(extra)); - try wzc.extra.ensureCapacity(wzc.gpa, wzc.extra.items.len + fields.len); - return addExtraAssumeCapacity(wzc, extra); - } - - pub fn addExtraAssumeCapacity(wzc: *WipZirCode, extra: anytype) u32 { - const fields = std.meta.fields(@TypeOf(extra)); - const result = @intCast(u32, wzc.extra.items.len); - inline for (fields) |field| { - wzc.extra.appendAssumeCapacity(switch (field.field_type) { - u32 => @field(extra, field.name), - zir.Inst.Ref => @enumToInt(@field(extra, field.name)), - else => @compileError("bad field type"), - }); - } - return result; - } - - pub fn appendRefs(wzc: *WipZirCode, refs: []const zir.Inst.Ref) !void { - const coerced = @bitCast([]const u32, refs); - return wzc.extra.appendSlice(wzc.gpa, coerced); - } - - pub fn appendRefsAssumeCapacity(wzc: *WipZirCode, refs: []const zir.Inst.Ref) void { - const coerced = @bitCast([]const u32, refs); - wzc.extra.appendSliceAssumeCapacity(coerced); - } - - pub fn refIsNoReturn(wzc: WipZirCode, inst_ref: zir.Inst.Ref) bool { - if (inst_ref == .unreachable_value) return true; - if (wzc.refToIndex(inst_ref)) |inst_index| { - return wzc.instructions.items(.tag)[inst_index].isNoReturn(); - } - return false; - } - - pub fn indexToRef(wzc: WipZirCode, inst: zir.Inst.Index) zir.Inst.Ref { - return @intToEnum(zir.Inst.Ref, wzc.ref_start_index + inst); - } - - pub fn refToIndex(wzc: WipZirCode, inst: zir.Inst.Ref) ?zir.Inst.Index { - const ref_int = @enumToInt(inst); - if (ref_int >= wzc.ref_start_index) { - return ref_int - wzc.ref_start_index; - } else { - return null; - } - } - - pub fn deinit(wzc: *WipZirCode) void { - wzc.instructions.deinit(wzc.gpa); - wzc.extra.deinit(wzc.gpa); - wzc.string_bytes.deinit(wzc.gpa); - wzc.decl_map.deinit(wzc.gpa); - wzc.decls.deinit(wzc.gpa); - } -}; - -/// Call `deinit` on the result. -fn initAstGen(mod: *Module, decl: *Decl, arena: *Allocator) !WipZirCode { - var wzc: WipZirCode = .{ - .decl = decl, - .arena = arena, - .gpa = mod.gpa, - }; - // Must be a block instruction at index 0 with the root body. - try wzc.instructions.append(mod.gpa, .{ - .tag = .block, - .data = .{ .pl_node = .{ - .src_node = 0, - .payload_index = undefined, - } }, - }); - return wzc; -} - /// This struct holds data necessary to construct API-facing `AllErrors.Message`. /// Its memory is managed with the general purpose allocator so that they /// can be created and destroyed in response to incremental updates. @@ -2102,18 +2012,18 @@ fn astgenAndSemaDecl(mod: *Module, decl: *Decl) !bool { defer analysis_arena.deinit(); var code: zir.Code = blk: { - var wip_zir_code = try mod.initAstGen(decl, &analysis_arena.allocator); - defer wip_zir_code.deinit(); + var astgen = try AstGen.init(mod, decl, &analysis_arena.allocator); + defer astgen.deinit(); var gen_scope: Scope.GenZir = .{ .force_comptime = true, .parent = &decl.container.base, - .zir_code = &wip_zir_code, + .astgen = &astgen, }; defer gen_scope.instructions.deinit(mod.gpa); const block_expr = node_datas[decl_node].lhs; - _ = try astgen.comptimeExpr(mod, &gen_scope.base, .none, block_expr); + _ = try AstGen.comptimeExpr(mod, &gen_scope.base, .none, block_expr); const code = try gen_scope.finish(); if (std.builtin.mode == .Debug and mod.comp.verbose_ir) { @@ -2175,13 +2085,13 @@ fn astgenAndSemaFn( var fn_type_scope_arena = std.heap.ArenaAllocator.init(mod.gpa); defer fn_type_scope_arena.deinit(); - var fn_type_wip_zir_code = try mod.initAstGen(decl, &fn_type_scope_arena.allocator); - defer fn_type_wip_zir_code.deinit(); + var fn_type_astgen = try AstGen.init(mod, decl, &fn_type_scope_arena.allocator); + defer fn_type_astgen.deinit(); var fn_type_scope: Scope.GenZir = .{ .force_comptime = true, .parent = &decl.container.base, - .zir_code = &fn_type_wip_zir_code, + .astgen = &fn_type_astgen, }; defer fn_type_scope.instructions.deinit(mod.gpa); @@ -2223,7 +2133,7 @@ fn astgenAndSemaFn( const param_type_node = param.type_expr; assert(param_type_node != 0); param_types[param_type_i] = - try astgen.expr(mod, &fn_type_scope.base, .{ .ty = .type_type }, param_type_node); + try AstGen.expr(mod, &fn_type_scope.base, .{ .ty = .type_type }, param_type_node); } assert(param_type_i == param_count); } @@ -2292,7 +2202,7 @@ fn astgenAndSemaFn( if (token_tags[maybe_bang] == .bang) { return mod.failTok(&fn_type_scope.base, maybe_bang, "TODO implement inferred error sets", .{}); } - const return_type_inst = try astgen.expr( + const return_type_inst = try AstGen.expr( mod, &fn_type_scope.base, .{ .ty = .type_type }, @@ -2308,7 +2218,7 @@ fn astgenAndSemaFn( // TODO instead of enum literal type, this needs to be the // std.builtin.CallingConvention enum. We need to implement importing other files // and enums in order to fix this. - try astgen.comptimeExpr( + try AstGen.comptimeExpr( mod, &fn_type_scope.base, .{ .ty = .enum_literal_type }, @@ -2408,21 +2318,21 @@ fn astgenAndSemaFn( const fn_zir: zir.Code = blk: { // We put the ZIR inside the Decl arena. - var wip_zir_code = try mod.initAstGen(decl, &decl_arena.allocator); - wip_zir_code.ref_start_index = @intCast(u32, zir.Inst.Ref.typed_value_map.len + param_count); - defer wip_zir_code.deinit(); + var astgen = try AstGen.init(mod, decl, &decl_arena.allocator); + astgen.ref_start_index = @intCast(u32, zir.Inst.Ref.typed_value_map.len + param_count); + defer astgen.deinit(); var gen_scope: Scope.GenZir = .{ .force_comptime = false, .parent = &decl.container.base, - .zir_code = &wip_zir_code, + .astgen = &astgen, }; defer gen_scope.instructions.deinit(mod.gpa); // 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. - try wip_zir_code.extra.ensureCapacity(mod.gpa, param_count); + try astgen.extra.ensureCapacity(mod.gpa, param_count); var params_scope = &gen_scope.base; var i: usize = 0; @@ -2443,18 +2353,18 @@ fn astgenAndSemaFn( // Additionally put the param name into `string_bytes` and reference it with // `extra` so that we have access to the data in codegen, for debug info. - const str_index = @intCast(u32, wip_zir_code.string_bytes.items.len); - wip_zir_code.extra.appendAssumeCapacity(str_index); - const used_bytes = wip_zir_code.string_bytes.items.len; - try wip_zir_code.string_bytes.ensureCapacity(mod.gpa, used_bytes + param_name.len + 1); - wip_zir_code.string_bytes.appendSliceAssumeCapacity(param_name); - wip_zir_code.string_bytes.appendAssumeCapacity(0); + const str_index = @intCast(u32, astgen.string_bytes.items.len); + astgen.extra.appendAssumeCapacity(str_index); + const used_bytes = astgen.string_bytes.items.len; + try astgen.string_bytes.ensureCapacity(mod.gpa, used_bytes + param_name.len + 1); + astgen.string_bytes.appendSliceAssumeCapacity(param_name); + astgen.string_bytes.appendAssumeCapacity(0); } - _ = try astgen.expr(mod, params_scope, .none, body_node); + _ = try AstGen.expr(mod, params_scope, .none, body_node); if (gen_scope.instructions.items.len == 0 or - !wip_zir_code.instructions.items(.tag)[gen_scope.instructions.items.len - 1] + !astgen.instructions.items(.tag)[gen_scope.instructions.items.len - 1] .isNoReturn()) { // astgen uses result location semantics to coerce return operands. @@ -2615,21 +2525,21 @@ fn astgenAndSemaVarDecl( var gen_scope_arena = std.heap.ArenaAllocator.init(mod.gpa); defer gen_scope_arena.deinit(); - var wip_zir_code = try mod.initAstGen(decl, &gen_scope_arena.allocator); - defer wip_zir_code.deinit(); + var astgen = try AstGen.init(mod, decl, &gen_scope_arena.allocator); + defer astgen.deinit(); var gen_scope: Scope.GenZir = .{ .force_comptime = true, .parent = &decl.container.base, - .zir_code = &wip_zir_code, + .astgen = &astgen, }; defer gen_scope.instructions.deinit(mod.gpa); - const init_result_loc: astgen.ResultLoc = if (var_decl.ast.type_node != 0) .{ - .ty = try astgen.expr(mod, &gen_scope.base, .{ .ty = .type_type }, var_decl.ast.type_node), + const init_result_loc: AstGen.ResultLoc = if (var_decl.ast.type_node != 0) .{ + .ty = try AstGen.expr(mod, &gen_scope.base, .{ .ty = .type_type }, var_decl.ast.type_node), } else .none; - const init_inst = try astgen.comptimeExpr( + const init_inst = try AstGen.comptimeExpr( mod, &gen_scope.base, init_result_loc, @@ -2684,17 +2594,17 @@ fn astgenAndSemaVarDecl( var type_scope_arena = std.heap.ArenaAllocator.init(mod.gpa); defer type_scope_arena.deinit(); - var wip_zir_code = try mod.initAstGen(decl, &type_scope_arena.allocator); - defer wip_zir_code.deinit(); + var astgen = try AstGen.init(mod, decl, &type_scope_arena.allocator); + defer astgen.deinit(); var type_scope: Scope.GenZir = .{ .force_comptime = true, .parent = &decl.container.base, - .zir_code = &wip_zir_code, + .astgen = &astgen, }; defer type_scope.instructions.deinit(mod.gpa); - const var_type = try astgen.typeExpr(mod, &type_scope.base, var_decl.ast.type_node); + const var_type = try AstGen.typeExpr(mod, &type_scope.base, var_decl.ast.type_node); _ = try type_scope.addBreak(.break_inline, 0, var_type); var code = try type_scope.finish(); @@ -3796,21 +3706,21 @@ pub fn failWithOwnedErrorMsg(mod: *Module, scope: *Scope, err_msg: *ErrorMsg) In }, .gen_zir => { const gen_zir = scope.cast(Scope.GenZir).?; - gen_zir.zir_code.decl.analysis = .sema_failure; - gen_zir.zir_code.decl.generation = mod.generation; - mod.failed_decls.putAssumeCapacityNoClobber(gen_zir.zir_code.decl, err_msg); + gen_zir.astgen.decl.analysis = .sema_failure; + gen_zir.astgen.decl.generation = mod.generation; + mod.failed_decls.putAssumeCapacityNoClobber(gen_zir.astgen.decl, err_msg); }, .local_val => { const gen_zir = scope.cast(Scope.LocalVal).?.gen_zir; - gen_zir.zir_code.decl.analysis = .sema_failure; - gen_zir.zir_code.decl.generation = mod.generation; - mod.failed_decls.putAssumeCapacityNoClobber(gen_zir.zir_code.decl, err_msg); + gen_zir.astgen.decl.analysis = .sema_failure; + gen_zir.astgen.decl.generation = mod.generation; + mod.failed_decls.putAssumeCapacityNoClobber(gen_zir.astgen.decl, err_msg); }, .local_ptr => { const gen_zir = scope.cast(Scope.LocalPtr).?.gen_zir; - gen_zir.zir_code.decl.analysis = .sema_failure; - gen_zir.zir_code.decl.generation = mod.generation; - mod.failed_decls.putAssumeCapacityNoClobber(gen_zir.zir_code.decl, err_msg); + gen_zir.astgen.decl.analysis = .sema_failure; + gen_zir.astgen.decl.generation = mod.generation; + mod.failed_decls.putAssumeCapacityNoClobber(gen_zir.astgen.decl, err_msg); }, .file => unreachable, .container => unreachable, diff --git a/src/astgen.zig b/src/astgen.zig @@ -1,3881 +0,0 @@ -const std = @import("std"); -const mem = std.mem; -const Allocator = std.mem.Allocator; -const assert = std.debug.assert; - -const Value = @import("value.zig").Value; -const Type = @import("type.zig").Type; -const TypedValue = @import("TypedValue.zig"); -const zir = @import("zir.zig"); -const Module = @import("Module.zig"); -const ast = std.zig.ast; -const trace = @import("tracy.zig").trace; -const Scope = Module.Scope; -const InnerError = Module.InnerError; -const BuiltinFn = @import("BuiltinFn.zig"); - -pub const ResultLoc = union(enum) { - /// The expression is the right-hand side of assignment to `_`. Only the side-effects of the - /// expression should be generated. The result instruction from the expression must - /// be ignored. - discard, - /// The expression has an inferred type, and it will be evaluated as an rvalue. - none, - /// The expression must generate a pointer rather than a value. For example, the left hand side - /// of an assignment uses this kind of result location. - ref, - /// The expression will be coerced into this type, but it will be evaluated as an rvalue. - ty: zir.Inst.Ref, - /// The expression must store its result into this typed pointer. The result instruction - /// from the expression must be ignored. - ptr: zir.Inst.Ref, - /// The expression must store its result into this allocation, which has an inferred type. - /// The result instruction from the expression must be ignored. - /// Always an instruction with tag `alloc_inferred`. - inferred_ptr: zir.Inst.Ref, - /// 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. - /// The result instruction from the expression must be ignored. - bitcasted_ptr: zir.Inst.Ref, - /// 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`. - /// The result instruction from the expression must be ignored. - block_ptr: *Module.Scope.GenZir, - - pub const Strategy = struct { - elide_store_to_block_ptr_instructions: bool, - tag: Tag, - - pub const Tag = enum { - /// Both branches will use break_void; result location is used to communicate the - /// result instruction. - break_void, - /// Use break statements to pass the block result value, and call rvalue() at - /// the end depending on rl. Also elide the store_to_block_ptr instructions - /// depending on rl. - break_operand, - }; - }; -}; - -pub fn typeExpr(mod: *Module, scope: *Scope, type_node: ast.Node.Index) InnerError!zir.Inst.Ref { - return expr(mod, scope, .{ .ty = .type_type }, type_node); -} - -fn lvalExpr(mod: *Module, scope: *Scope, node: ast.Node.Index) InnerError!zir.Inst.Ref { - const tree = scope.tree(); - const node_tags = tree.nodes.items(.tag); - const main_tokens = tree.nodes.items(.main_token); - switch (node_tags[node]) { - .root => unreachable, - .@"usingnamespace" => unreachable, - .test_decl => unreachable, - .global_var_decl => unreachable, - .local_var_decl => unreachable, - .simple_var_decl => unreachable, - .aligned_var_decl => unreachable, - .switch_case => unreachable, - .switch_case_one => unreachable, - .container_field_init => unreachable, - .container_field_align => unreachable, - .container_field => unreachable, - .asm_output => unreachable, - .asm_input => unreachable, - - .assign, - .assign_bit_and, - .assign_bit_or, - .assign_bit_shift_left, - .assign_bit_shift_right, - .assign_bit_xor, - .assign_div, - .assign_sub, - .assign_sub_wrap, - .assign_mod, - .assign_add, - .assign_add_wrap, - .assign_mul, - .assign_mul_wrap, - .add, - .add_wrap, - .sub, - .sub_wrap, - .mul, - .mul_wrap, - .div, - .mod, - .bit_and, - .bit_or, - .bit_shift_left, - .bit_shift_right, - .bit_xor, - .bang_equal, - .equal_equal, - .greater_than, - .greater_or_equal, - .less_than, - .less_or_equal, - .array_cat, - .array_mult, - .bool_and, - .bool_or, - .@"asm", - .asm_simple, - .string_literal, - .integer_literal, - .call, - .call_comma, - .async_call, - .async_call_comma, - .call_one, - .call_one_comma, - .async_call_one, - .async_call_one_comma, - .unreachable_literal, - .@"return", - .@"if", - .if_simple, - .@"while", - .while_simple, - .while_cont, - .bool_not, - .address_of, - .float_literal, - .undefined_literal, - .true_literal, - .false_literal, - .null_literal, - .optional_type, - .block, - .block_semicolon, - .block_two, - .block_two_semicolon, - .@"break", - .ptr_type_aligned, - .ptr_type_sentinel, - .ptr_type, - .ptr_type_bit_range, - .array_type, - .array_type_sentinel, - .enum_literal, - .multiline_string_literal, - .char_literal, - .@"defer", - .@"errdefer", - .@"catch", - .error_union, - .merge_error_sets, - .switch_range, - .@"await", - .bit_not, - .negation, - .negation_wrap, - .@"resume", - .@"try", - .slice, - .slice_open, - .slice_sentinel, - .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, - .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, - .@"switch", - .switch_comma, - .@"for", - .for_simple, - .@"suspend", - .@"continue", - .@"anytype", - .fn_proto_simple, - .fn_proto_multi, - .fn_proto_one, - .fn_proto, - .fn_decl, - .anyframe_type, - .anyframe_literal, - .error_set_decl, - .container_decl, - .container_decl_trailing, - .container_decl_two, - .container_decl_two_trailing, - .container_decl_arg, - .container_decl_arg_trailing, - .tagged_union, - .tagged_union_trailing, - .tagged_union_two, - .tagged_union_two_trailing, - .tagged_union_enum_tag, - .tagged_union_enum_tag_trailing, - .@"comptime", - .@"nosuspend", - .error_value, - => return mod.failNode(scope, node, "invalid left-hand side to assignment", .{}), - - .builtin_call, - .builtin_call_comma, - .builtin_call_two, - .builtin_call_two_comma, - => { - const builtin_token = main_tokens[node]; - const builtin_name = tree.tokenSlice(builtin_token); - // If the builtin is an invalid name, we don't cause an error here; instead - // let it pass, and the error will be "invalid builtin function" later. - if (BuiltinFn.list.get(builtin_name)) |info| { - if (!info.allows_lvalue) { - return mod.failNode(scope, node, "invalid left-hand side to assignment", .{}); - } - } - }, - - // These can be assigned to. - .unwrap_optional, - .deref, - .field_access, - .array_access, - .identifier, - .grouped_expression, - .@"orelse", - => {}, - } - return expr(mod, scope, .ref, node); -} - -/// Turn Zig AST into untyped ZIR istructions. -/// When `rl` is discard, ptr, inferred_ptr, bitcasted_ptr, or inferred_ptr, the -/// result instruction can be used to inspect whether it is isNoReturn() but that is it, -/// it must otherwise not be used. -pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) InnerError!zir.Inst.Ref { - const tree = scope.tree(); - const main_tokens = tree.nodes.items(.main_token); - const token_tags = tree.tokens.items(.tag); - const node_datas = tree.nodes.items(.data); - const node_tags = tree.nodes.items(.tag); - - const gz = scope.getGenZir(); - - switch (node_tags[node]) { - .root => unreachable, // Top-level declaration. - .@"usingnamespace" => unreachable, // Top-level declaration. - .test_decl => unreachable, // Top-level declaration. - .container_field_init => unreachable, // Top-level declaration. - .container_field_align => unreachable, // Top-level declaration. - .container_field => unreachable, // Top-level declaration. - .fn_decl => unreachable, // Top-level declaration. - - .global_var_decl => unreachable, // Handled in `blockExpr`. - .local_var_decl => unreachable, // Handled in `blockExpr`. - .simple_var_decl => unreachable, // Handled in `blockExpr`. - .aligned_var_decl => unreachable, // Handled in `blockExpr`. - - .switch_case => unreachable, // Handled in `switchExpr`. - .switch_case_one => unreachable, // Handled in `switchExpr`. - .switch_range => unreachable, // Handled in `switchExpr`. - - .asm_output => unreachable, // Handled in `asmExpr`. - .asm_input => unreachable, // Handled in `asmExpr`. - - .assign => { - try assign(mod, scope, node); - return rvalue(mod, scope, rl, .void_value, node); - }, - .assign_bit_and => { - try assignOp(mod, scope, node, .bit_and); - return rvalue(mod, scope, rl, .void_value, node); - }, - .assign_bit_or => { - try assignOp(mod, scope, node, .bit_or); - return rvalue(mod, scope, rl, .void_value, node); - }, - .assign_bit_shift_left => { - try assignOp(mod, scope, node, .shl); - return rvalue(mod, scope, rl, .void_value, node); - }, - .assign_bit_shift_right => { - try assignOp(mod, scope, node, .shr); - return rvalue(mod, scope, rl, .void_value, node); - }, - .assign_bit_xor => { - try assignOp(mod, scope, node, .xor); - return rvalue(mod, scope, rl, .void_value, node); - }, - .assign_div => { - try assignOp(mod, scope, node, .div); - return rvalue(mod, scope, rl, .void_value, node); - }, - .assign_sub => { - try assignOp(mod, scope, node, .sub); - return rvalue(mod, scope, rl, .void_value, node); - }, - .assign_sub_wrap => { - try assignOp(mod, scope, node, .subwrap); - return rvalue(mod, scope, rl, .void_value, node); - }, - .assign_mod => { - try assignOp(mod, scope, node, .mod_rem); - return rvalue(mod, scope, rl, .void_value, node); - }, - .assign_add => { - try assignOp(mod, scope, node, .add); - return rvalue(mod, scope, rl, .void_value, node); - }, - .assign_add_wrap => { - try assignOp(mod, scope, node, .addwrap); - return rvalue(mod, scope, rl, .void_value, node); - }, - .assign_mul => { - try assignOp(mod, scope, node, .mul); - return rvalue(mod, scope, rl, .void_value, node); - }, - .assign_mul_wrap => { - try assignOp(mod, scope, node, .mulwrap); - return rvalue(mod, scope, rl, .void_value, node); - }, - - .add => return simpleBinOp(mod, scope, rl, node, .add), - .add_wrap => return simpleBinOp(mod, scope, rl, node, .addwrap), - .sub => return simpleBinOp(mod, scope, rl, node, .sub), - .sub_wrap => return simpleBinOp(mod, scope, rl, node, .subwrap), - .mul => return simpleBinOp(mod, scope, rl, node, .mul), - .mul_wrap => return simpleBinOp(mod, scope, rl, node, .mulwrap), - .div => return simpleBinOp(mod, scope, rl, node, .div), - .mod => return simpleBinOp(mod, scope, rl, node, .mod_rem), - .bit_and => return simpleBinOp(mod, scope, rl, node, .bit_and), - .bit_or => return simpleBinOp(mod, scope, rl, node, .bit_or), - .bit_shift_left => return simpleBinOp(mod, scope, rl, node, .shl), - .bit_shift_right => return simpleBinOp(mod, scope, rl, node, .shr), - .bit_xor => return simpleBinOp(mod, scope, rl, node, .xor), - - .bang_equal => return simpleBinOp(mod, scope, rl, node, .cmp_neq), - .equal_equal => return simpleBinOp(mod, scope, rl, node, .cmp_eq), - .greater_than => return simpleBinOp(mod, scope, rl, node, .cmp_gt), - .greater_or_equal => return simpleBinOp(mod, scope, rl, node, .cmp_gte), - .less_than => return simpleBinOp(mod, scope, rl, node, .cmp_lt), - .less_or_equal => return simpleBinOp(mod, scope, rl, node, .cmp_lte), - - .array_cat => return simpleBinOp(mod, scope, rl, node, .array_cat), - .array_mult => return simpleBinOp(mod, scope, rl, node, .array_mul), - - .error_union => return simpleBinOp(mod, scope, rl, node, .error_union_type), - .merge_error_sets => return simpleBinOp(mod, scope, rl, node, .merge_error_sets), - - .bool_and => return boolBinOp(mod, scope, rl, node, .bool_br_and), - .bool_or => return boolBinOp(mod, scope, rl, node, .bool_br_or), - - .bool_not => return boolNot(mod, scope, rl, node), - .bit_not => return bitNot(mod, scope, rl, node), - - .negation => return negation(mod, scope, rl, node, .negate), - .negation_wrap => return negation(mod, scope, rl, node, .negate_wrap), - - .identifier => return identifier(mod, scope, rl, node), - - .asm_simple => return asmExpr(mod, scope, rl, node, tree.asmSimple(node)), - .@"asm" => return asmExpr(mod, scope, rl, node, tree.asmFull(node)), - - .string_literal => return stringLiteral(mod, scope, rl, node), - .multiline_string_literal => return multilineStringLiteral(mod, scope, rl, node), - - .integer_literal => return integerLiteral(mod, scope, rl, node), - - .builtin_call_two, .builtin_call_two_comma => { - if (node_datas[node].lhs == 0) { - const params = [_]ast.Node.Index{}; - return builtinCall(mod, scope, rl, node, &params); - } else if (node_datas[node].rhs == 0) { - const params = [_]ast.Node.Index{node_datas[node].lhs}; - return builtinCall(mod, scope, rl, node, &params); - } else { - const params = [_]ast.Node.Index{ node_datas[node].lhs, node_datas[node].rhs }; - return builtinCall(mod, scope, rl, node, &params); - } - }, - .builtin_call, .builtin_call_comma => { - const params = tree.extra_data[node_datas[node].lhs..node_datas[node].rhs]; - return builtinCall(mod, scope, rl, node, params); - }, - - .call_one, .call_one_comma, .async_call_one, .async_call_one_comma => { - var params: [1]ast.Node.Index = undefined; - return callExpr(mod, scope, rl, node, tree.callOne(&params, node)); - }, - .call, .call_comma, .async_call, .async_call_comma => { - return callExpr(mod, scope, rl, node, tree.callFull(node)); - }, - - .unreachable_literal => { - _ = try gz.addAsIndex(.{ - .tag = .@"unreachable", - .data = .{ .@"unreachable" = .{ - .safety = true, - .src_node = gz.zir_code.decl.nodeIndexToRelative(node), - } }, - }); - return zir.Inst.Ref.unreachable_value; - }, - .@"return" => return ret(mod, scope, node), - .field_access => return fieldAccess(mod, scope, rl, node), - .float_literal => return floatLiteral(mod, scope, rl, node), - - .if_simple => return ifExpr(mod, scope, rl, node, tree.ifSimple(node)), - .@"if" => return ifExpr(mod, scope, rl, node, tree.ifFull(node)), - - .while_simple => return whileExpr(mod, scope, rl, node, tree.whileSimple(node)), - .while_cont => return whileExpr(mod, scope, rl, node, tree.whileCont(node)), - .@"while" => return whileExpr(mod, scope, rl, node, tree.whileFull(node)), - - .for_simple => return forExpr(mod, scope, rl, node, tree.forSimple(node)), - .@"for" => return forExpr(mod, scope, rl, node, tree.forFull(node)), - - .slice_open => { - const lhs = try expr(mod, scope, .ref, node_datas[node].lhs); - const start = try expr(mod, scope, .{ .ty = .usize_type }, node_datas[node].rhs); - const result = try gz.addPlNode(.slice_start, node, zir.Inst.SliceStart{ - .lhs = lhs, - .start = start, - }); - return rvalue(mod, scope, rl, result, node); - }, - .slice => { - const lhs = try expr(mod, scope, .ref, node_datas[node].lhs); - const extra = tree.extraData(node_datas[node].rhs, ast.Node.Slice); - const start = try expr(mod, scope, .{ .ty = .usize_type }, extra.start); - const end = try expr(mod, scope, .{ .ty = .usize_type }, extra.end); - const result = try gz.addPlNode(.slice_end, node, zir.Inst.SliceEnd{ - .lhs = lhs, - .start = start, - .end = end, - }); - return rvalue(mod, scope, rl, result, node); - }, - .slice_sentinel => { - const lhs = try expr(mod, scope, .ref, node_datas[node].lhs); - const extra = tree.extraData(node_datas[node].rhs, ast.Node.SliceSentinel); - const start = try expr(mod, scope, .{ .ty = .usize_type }, extra.start); - const end = try expr(mod, scope, .{ .ty = .usize_type }, extra.end); - const sentinel = try expr(mod, scope, .{ .ty = .usize_type }, extra.sentinel); - const result = try gz.addPlNode(.slice_sentinel, node, zir.Inst.SliceSentinel{ - .lhs = lhs, - .start = start, - .end = end, - .sentinel = sentinel, - }); - return rvalue(mod, scope, rl, result, node); - }, - - .deref => { - const lhs = try expr(mod, scope, .none, node_datas[node].lhs); - const result = try gz.addUnNode(.load, lhs, node); - return rvalue(mod, scope, rl, result, node); - }, - .address_of => { - const result = try expr(mod, scope, .ref, node_datas[node].lhs); - return rvalue(mod, scope, rl, result, node); - }, - .undefined_literal => return rvalue(mod, scope, rl, .undef, node), - .true_literal => return rvalue(mod, scope, rl, .bool_true, node), - .false_literal => return rvalue(mod, scope, rl, .bool_false, node), - .null_literal => return rvalue(mod, scope, rl, .null_value, node), - .optional_type => { - const operand = try typeExpr(mod, scope, node_datas[node].lhs); - const result = try gz.addUnNode(.optional_type, operand, node); - return rvalue(mod, scope, rl, result, node); - }, - .unwrap_optional => switch (rl) { - .ref => return gz.addUnNode( - .optional_payload_safe_ptr, - try expr(mod, scope, .ref, node_datas[node].lhs), - node, - ), - else => return rvalue(mod, scope, rl, try gz.addUnNode( - .optional_payload_safe, - try expr(mod, scope, .none, node_datas[node].lhs), - node, - ), node), - }, - .block_two, .block_two_semicolon => { - const statements = [2]ast.Node.Index{ node_datas[node].lhs, node_datas[node].rhs }; - if (node_datas[node].lhs == 0) { - return blockExpr(mod, scope, rl, node, statements[0..0]); - } else if (node_datas[node].rhs == 0) { - return blockExpr(mod, scope, rl, node, statements[0..1]); - } else { - return blockExpr(mod, scope, rl, node, statements[0..2]); - } - }, - .block, .block_semicolon => { - const statements = tree.extra_data[node_datas[node].lhs..node_datas[node].rhs]; - return blockExpr(mod, scope, rl, node, statements); - }, - .enum_literal => return simpleStrTok(mod, scope, rl, main_tokens[node], node, .enum_literal), - .error_value => return simpleStrTok(mod, scope, rl, node_datas[node].rhs, node, .error_value), - .anyframe_literal => return mod.failNode(scope, node, "async and related features are not yet supported", .{}), - .anyframe_type => return mod.failNode(scope, node, "async and related features are not yet supported", .{}), - .@"catch" => { - const catch_token = main_tokens[node]; - const payload_token: ?ast.TokenIndex = if (token_tags[catch_token + 1] == .pipe) - catch_token + 2 - else - null; - switch (rl) { - .ref => return orelseCatchExpr( - mod, - scope, - rl, - node, - node_datas[node].lhs, - .is_err_ptr, - .err_union_payload_unsafe_ptr, - .err_union_code_ptr, - node_datas[node].rhs, - payload_token, - ), - else => return orelseCatchExpr( - mod, - scope, - rl, - node, - node_datas[node].lhs, - .is_err, - .err_union_payload_unsafe, - .err_union_code, - node_datas[node].rhs, - payload_token, - ), - } - }, - .@"orelse" => switch (rl) { - .ref => return orelseCatchExpr( - mod, - scope, - rl, - node, - node_datas[node].lhs, - .is_null_ptr, - .optional_payload_unsafe_ptr, - undefined, - node_datas[node].rhs, - null, - ), - else => return orelseCatchExpr( - mod, - scope, - rl, - node, - node_datas[node].lhs, - .is_null, - .optional_payload_unsafe, - undefined, - node_datas[node].rhs, - null, - ), - }, - - .ptr_type_aligned => return ptrType(mod, scope, rl, node, tree.ptrTypeAligned(node)), - .ptr_type_sentinel => return ptrType(mod, scope, rl, node, tree.ptrTypeSentinel(node)), - .ptr_type => return ptrType(mod, scope, rl, node, tree.ptrType(node)), - .ptr_type_bit_range => return ptrType(mod, scope, rl, node, tree.ptrTypeBitRange(node)), - - .container_decl, - .container_decl_trailing, - => return containerDecl(mod, scope, rl, tree.containerDecl(node)), - .container_decl_two, .container_decl_two_trailing => { - var buffer: [2]ast.Node.Index = undefined; - return containerDecl(mod, scope, rl, tree.containerDeclTwo(&buffer, node)); - }, - .container_decl_arg, - .container_decl_arg_trailing, - => return containerDecl(mod, scope, rl, tree.containerDeclArg(node)), - - .tagged_union, - .tagged_union_trailing, - => return containerDecl(mod, scope, rl, tree.taggedUnion(node)), - .tagged_union_two, .tagged_union_two_trailing => { - var buffer: [2]ast.Node.Index = undefined; - return containerDecl(mod, scope, rl, tree.taggedUnionTwo(&buffer, node)); - }, - .tagged_union_enum_tag, - .tagged_union_enum_tag_trailing, - => return containerDecl(mod, scope, rl, tree.taggedUnionEnumTag(node)), - - .@"break" => return breakExpr(mod, scope, node), - .@"continue" => return continueExpr(mod, scope, node), - .grouped_expression => return expr(mod, scope, rl, node_datas[node].lhs), - .array_type => return arrayType(mod, scope, rl, node), - .array_type_sentinel => return arrayTypeSentinel(mod, scope, rl, node), - .char_literal => return charLiteral(mod, scope, rl, node), - .error_set_decl => return errorSetDecl(mod, scope, rl, node), - .array_access => return arrayAccess(mod, scope, rl, node), - .@"comptime" => return comptimeExpr(mod, scope, rl, node_datas[node].lhs), - .@"switch", .switch_comma => return switchExpr(mod, scope, rl, node), - - .@"nosuspend" => return mod.failNode(scope, node, "async and related features are not yet supported", .{}), - .@"suspend" => return mod.failNode(scope, node, "async and related features are not yet supported", .{}), - .@"await" => return mod.failNode(scope, node, "async and related features are not yet supported", .{}), - .@"resume" => return mod.failNode(scope, node, "async and related features are not yet supported", .{}), - - .@"defer" => return mod.failNode(scope, node, "TODO implement astgen.expr for .defer", .{}), - .@"errdefer" => return mod.failNode(scope, node, "TODO implement astgen.expr for .errdefer", .{}), - .@"try" => return mod.failNode(scope, node, "TODO implement astgen.expr for .Try", .{}), - - .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, - => return mod.failNode(scope, node, "TODO implement astgen.expr for array literals", .{}), - - .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, - => return mod.failNode(scope, node, "TODO implement astgen.expr for struct literals", .{}), - - .@"anytype" => return mod.failNode(scope, node, "TODO implement astgen.expr for .anytype", .{}), - .fn_proto_simple, - .fn_proto_multi, - .fn_proto_one, - .fn_proto, - => return mod.failNode(scope, node, "TODO implement astgen.expr for function prototypes", .{}), - } -} - -pub fn comptimeExpr( - mod: *Module, - parent_scope: *Scope, - rl: ResultLoc, - node: ast.Node.Index, -) InnerError!zir.Inst.Ref { - const gz = parent_scope.getGenZir(); - - const prev_force_comptime = gz.force_comptime; - gz.force_comptime = true; - const result = try expr(mod, parent_scope, rl, node); - gz.force_comptime = prev_force_comptime; - return result; -} - -fn breakExpr(mod: *Module, parent_scope: *Scope, node: ast.Node.Index) InnerError!zir.Inst.Ref { - const parent_gz = parent_scope.getGenZir(); - const tree = parent_gz.tree(); - const node_datas = tree.nodes.items(.data); - const break_label = node_datas[node].lhs; - const rhs = node_datas[node].rhs; - - // Look for the label in the scope. - var scope = parent_scope; - while (true) { - switch (scope.tag) { - .gen_zir => { - const block_gz = scope.cast(Scope.GenZir).?; - - const block_inst = blk: { - if (break_label != 0) { - if (block_gz.label) |*label| { - if (try tokenIdentEql(mod, parent_scope, label.token, break_label)) { - label.used = true; - break :blk label.block_inst; - } - } - } else if (block_gz.break_block != 0) { - break :blk block_gz.break_block; - } - scope = block_gz.parent; - continue; - }; - - if (rhs == 0) { - _ = try parent_gz.addBreak(.@"break", block_inst, .void_value); - return zir.Inst.Ref.unreachable_value; - } - block_gz.break_count += 1; - const prev_rvalue_rl_count = block_gz.rvalue_rl_count; - const operand = try expr(mod, parent_scope, block_gz.break_result_loc, rhs); - const have_store_to_block = block_gz.rvalue_rl_count != prev_rvalue_rl_count; - - const br = try parent_gz.addBreak(.@"break", block_inst, operand); - - if (block_gz.break_result_loc == .block_ptr) { - try block_gz.labeled_breaks.append(mod.gpa, br); - - if (have_store_to_block) { - const zir_tags = parent_gz.zir_code.instructions.items(.tag); - const zir_datas = parent_gz.zir_code.instructions.items(.data); - const store_inst = @intCast(u32, zir_tags.len - 2); - assert(zir_tags[store_inst] == .store_to_block_ptr); - assert(zir_datas[store_inst].bin.lhs == block_gz.rl_ptr); - try block_gz.labeled_store_to_block_ptr_list.append(mod.gpa, store_inst); - } - } - return zir.Inst.Ref.unreachable_value; - }, - .local_val => scope = scope.cast(Scope.LocalVal).?.parent, - .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent, - else => if (break_label != 0) { - const label_name = try mod.identifierTokenString(parent_scope, break_label); - return mod.failTok(parent_scope, break_label, "label not found: '{s}'", .{label_name}); - } else { - return mod.failNode(parent_scope, node, "break expression outside loop", .{}); - }, - } - } -} - -fn continueExpr(mod: *Module, parent_scope: *Scope, node: ast.Node.Index) InnerError!zir.Inst.Ref { - const parent_gz = parent_scope.getGenZir(); - const tree = parent_gz.tree(); - const node_datas = tree.nodes.items(.data); - const break_label = node_datas[node].lhs; - - // Look for the label in the scope. - var scope = parent_scope; - while (true) { - switch (scope.tag) { - .gen_zir => { - const gen_zir = scope.cast(Scope.GenZir).?; - const continue_block = gen_zir.continue_block; - if (continue_block == 0) { - scope = gen_zir.parent; - continue; - } - if (break_label != 0) blk: { - if (gen_zir.label) |*label| { - if (try tokenIdentEql(mod, parent_scope, label.token, break_label)) { - label.used = true; - break :blk; - } - } - // found continue but either it has a different label, or no label - scope = gen_zir.parent; - continue; - } - - // TODO emit a break_inline if the loop being continued is inline - _ = try parent_gz.addBreak(.@"break", continue_block, .void_value); - return zir.Inst.Ref.unreachable_value; - }, - .local_val => scope = scope.cast(Scope.LocalVal).?.parent, - .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent, - else => if (break_label != 0) { - const label_name = try mod.identifierTokenString(parent_scope, break_label); - return mod.failTok(parent_scope, break_label, "label not found: '{s}'", .{label_name}); - } else { - return mod.failNode(parent_scope, node, "continue expression outside loop", .{}); - }, - } - } -} - -pub fn blockExpr( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - block_node: ast.Node.Index, - statements: []const ast.Node.Index, -) InnerError!zir.Inst.Ref { - const tracy = trace(@src()); - defer tracy.end(); - - const tree = scope.tree(); - const main_tokens = tree.nodes.items(.main_token); - const token_tags = tree.tokens.items(.tag); - - const lbrace = main_tokens[block_node]; - if (token_tags[lbrace - 1] == .colon and - token_tags[lbrace - 2] == .identifier) - { - return labeledBlockExpr(mod, scope, rl, block_node, statements, .block); - } - - try blockExprStmts(mod, scope, block_node, statements); - return rvalue(mod, scope, rl, .void_value, block_node); -} - -fn checkLabelRedefinition(mod: *Module, parent_scope: *Scope, label: ast.TokenIndex) !void { - // Look for the label in the scope. - var scope = parent_scope; - while (true) { - switch (scope.tag) { - .gen_zir => { - const gen_zir = scope.cast(Scope.GenZir).?; - if (gen_zir.label) |prev_label| { - if (try tokenIdentEql(mod, parent_scope, label, prev_label.token)) { - const tree = parent_scope.tree(); - const main_tokens = tree.nodes.items(.main_token); - - const label_name = try mod.identifierTokenString(parent_scope, label); - const msg = msg: { - const msg = try mod.errMsg( - parent_scope, - gen_zir.tokSrcLoc(label), - "redefinition of label '{s}'", - .{label_name}, - ); - errdefer msg.destroy(mod.gpa); - try mod.errNote( - parent_scope, - gen_zir.tokSrcLoc(prev_label.token), - msg, - "previous definition is here", - .{}, - ); - break :msg msg; - }; - return mod.failWithOwnedErrorMsg(parent_scope, msg); - } - } - scope = gen_zir.parent; - }, - .local_val => scope = scope.cast(Scope.LocalVal).?.parent, - .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent, - else => return, - } - } -} - -fn labeledBlockExpr( - mod: *Module, - parent_scope: *Scope, - rl: ResultLoc, - block_node: ast.Node.Index, - statements: []const ast.Node.Index, - zir_tag: zir.Inst.Tag, -) InnerError!zir.Inst.Ref { - const tracy = trace(@src()); - defer tracy.end(); - - assert(zir_tag == .block); - - const tree = parent_scope.tree(); - const main_tokens = tree.nodes.items(.main_token); - const token_tags = tree.tokens.items(.tag); - - const lbrace = main_tokens[block_node]; - const label_token = lbrace - 2; - assert(token_tags[label_token] == .identifier); - - try checkLabelRedefinition(mod, parent_scope, label_token); - - // Reserve the Block ZIR instruction index so that we can put it into the GenZir struct - // so that break statements can reference it. - const gz = parent_scope.getGenZir(); - const block_inst = try gz.addBlock(zir_tag, block_node); - try gz.instructions.append(mod.gpa, block_inst); - - var block_scope: Scope.GenZir = .{ - .parent = parent_scope, - .zir_code = gz.zir_code, - .force_comptime = gz.force_comptime, - .instructions = .{}, - // TODO @as here is working around a stage1 miscompilation bug :( - .label = @as(?Scope.GenZir.Label, Scope.GenZir.Label{ - .token = label_token, - .block_inst = block_inst, - }), - }; - setBlockResultLoc(&block_scope, rl); - defer block_scope.instructions.deinit(mod.gpa); - defer block_scope.labeled_breaks.deinit(mod.gpa); - defer block_scope.labeled_store_to_block_ptr_list.deinit(mod.gpa); - - try blockExprStmts(mod, &block_scope.base, block_node, statements); - - if (!block_scope.label.?.used) { - return mod.failTok(parent_scope, label_token, "unused block label", .{}); - } - - const zir_tags = gz.zir_code.instructions.items(.tag); - const zir_datas = gz.zir_code.instructions.items(.data); - - const strat = rlStrategy(rl, &block_scope); - switch (strat.tag) { - .break_void => { - // The code took advantage of the result location as a pointer. - // Turn the break instruction operands into void. - for (block_scope.labeled_breaks.items) |br| { - zir_datas[br].@"break".operand = .void_value; - } - try block_scope.setBlockBody(block_inst); - - return gz.zir_code.indexToRef(block_inst); - }, - .break_operand => { - // All break operands are values that did not use the result location pointer. - if (strat.elide_store_to_block_ptr_instructions) { - for (block_scope.labeled_store_to_block_ptr_list.items) |inst| { - zir_tags[inst] = .elided; - zir_datas[inst] = undefined; - } - // TODO technically not needed since we changed the tag to elided but - // would be better still to elide the ones that are in this list. - } - try block_scope.setBlockBody(block_inst); - const block_ref = gz.zir_code.indexToRef(block_inst); - switch (rl) { - .ref => return block_ref, - else => return rvalue(mod, parent_scope, rl, block_ref, block_node), - } - }, - } -} - -fn blockExprStmts( - mod: *Module, - parent_scope: *Scope, - node: ast.Node.Index, - statements: []const ast.Node.Index, -) !void { - const tree = parent_scope.tree(); - const main_tokens = tree.nodes.items(.main_token); - const node_tags = tree.nodes.items(.tag); - - var block_arena = std.heap.ArenaAllocator.init(mod.gpa); - defer block_arena.deinit(); - - const gz = parent_scope.getGenZir(); - - 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(mod, scope, statement, &block_arena.allocator, tree.globalVarDecl(statement)), - .local_var_decl => scope = try varDecl(mod, scope, statement, &block_arena.allocator, tree.localVarDecl(statement)), - .simple_var_decl => scope = try varDecl(mod, scope, statement, &block_arena.allocator, tree.simpleVarDecl(statement)), - .aligned_var_decl => scope = try varDecl(mod, scope, statement, &block_arena.allocator, tree.alignedVarDecl(statement)), - - .assign => try assign(mod, scope, statement), - .assign_bit_and => try assignOp(mod, scope, statement, .bit_and), - .assign_bit_or => try assignOp(mod, scope, statement, .bit_or), - .assign_bit_shift_left => try assignOp(mod, scope, statement, .shl), - .assign_bit_shift_right => try assignOp(mod, scope, statement, .shr), - .assign_bit_xor => try assignOp(mod, scope, statement, .xor), - .assign_div => try assignOp(mod, scope, statement, .div), - .assign_sub => try assignOp(mod, scope, statement, .sub), - .assign_sub_wrap => try assignOp(mod, scope, statement, .subwrap), - .assign_mod => try assignOp(mod, scope, statement, .mod_rem), - .assign_add => try assignOp(mod, scope, statement, .add), - .assign_add_wrap => try assignOp(mod, scope, statement, .addwrap), - .assign_mul => try assignOp(mod, scope, statement, .mul), - .assign_mul_wrap => try assignOp(mod, scope, statement, .mulwrap), - - 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(mod, scope, .none, statement); - const elide_check = if (gz.zir_code.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.zir_code.instructions.items(.tag); - switch (zir_tags[inst]) { - .@"const" => { - const tv = gz.zir_code.instructions.items(.data)[inst].@"const"; - break :b switch (tv.ty.zigTypeTag()) { - .NoReturn, .Void => true, - else => false, - }; - }, - // 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, - .indexable_ptr_len, - .as, - .as_node, - .@"asm", - .asm_volatile, - .bit_and, - .bitcast, - .bitcast_ref, - .bitcast_result_ptr, - .bit_or, - .block, - .block_inline, - .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, - .floatcast, - .field_ptr, - .field_val, - .field_ptr_named, - .field_val_named, - .fn_type, - .fn_type_var_args, - .fn_type_cc, - .fn_type_cc_var_args, - .int, - .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, - .ret_ptr, - .ret_type, - .shl, - .shr, - .str, - .sub, - .subwrap, - .negate, - .negate_wrap, - .typeof, - .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_set, - .error_value, - .slice_start, - .slice_end, - .slice_sentinel, - .import, - .typeof_peer, - => break :b false, - - // ZIR instructions that are always either `noreturn` or `void`. - .breakpoint, - .dbg_stmt_node, - .ensure_result_used, - .ensure_result_non_error, - .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", - .elided, - .store, - .store_node, - .store_to_block_ptr, - .store_to_inferred_ptr, - .resolve_inferred_alloc, - .repeat, - .repeat_inline, - => 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 varDecl( - mod: *Module, - scope: *Scope, - node: ast.Node.Index, - block_arena: *Allocator, - var_decl: ast.full.VarDecl, -) InnerError!*Scope { - if (var_decl.comptime_token) |comptime_token| { - return mod.failTok(scope, comptime_token, "TODO implement comptime locals", .{}); - } - if (var_decl.ast.align_node != 0) { - return mod.failNode(scope, var_decl.ast.align_node, "TODO implement alignment on locals", .{}); - } - const gz = scope.getGenZir(); - const wzc = gz.zir_code; - const tree = scope.tree(); - const token_tags = tree.tokens.items(.tag); - - const name_token = var_decl.ast.mut_token + 1; - const name_src = gz.tokSrcLoc(name_token); - const ident_name = try mod.identifierTokenString(scope, name_token); - - // Local variables shadowing detection, including function parameters. - { - var s = scope; - while (true) switch (s.tag) { - .local_val => { - const local_val = s.cast(Scope.LocalVal).?; - if (mem.eql(u8, local_val.name, ident_name)) { - const msg = msg: { - const msg = try mod.errMsg(scope, name_src, "redefinition of '{s}'", .{ - ident_name, - }); - errdefer msg.destroy(mod.gpa); - try mod.errNote(scope, local_val.src, msg, "previous definition is here", .{}); - break :msg msg; - }; - return mod.failWithOwnedErrorMsg(scope, msg); - } - s = local_val.parent; - }, - .local_ptr => { - const local_ptr = s.cast(Scope.LocalPtr).?; - if (mem.eql(u8, local_ptr.name, ident_name)) { - const msg = msg: { - const msg = try mod.errMsg(scope, name_src, "redefinition of '{s}'", .{ - ident_name, - }); - errdefer msg.destroy(mod.gpa); - try mod.errNote(scope, local_ptr.src, msg, "previous definition is here", .{}); - break :msg msg; - }; - return mod.failWithOwnedErrorMsg(scope, msg); - } - s = local_ptr.parent; - }, - .gen_zir => s = s.cast(Scope.GenZir).?.parent, - else => break, - }; - } - - // Namespace vars shadowing detection - if (mod.lookupDeclName(scope, ident_name)) |_| { - // TODO add note for other definition - return mod.fail(scope, name_src, "redefinition of '{s}'", .{ident_name}); - } - if (var_decl.ast.init_node == 0) { - return mod.fail(scope, name_src, "variables must be initialized", .{}); - } - - switch (token_tags[var_decl.ast.mut_token]) { - .keyword_const => { - // 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. - if (!nodeMayNeedMemoryLocation(scope, var_decl.ast.init_node)) { - const result_loc: ResultLoc = if (var_decl.ast.type_node != 0) .{ - .ty = try typeExpr(mod, scope, var_decl.ast.type_node), - } else .none; - const init_inst = try expr(mod, scope, result_loc, var_decl.ast.init_node); - const sub_scope = try block_arena.create(Scope.LocalVal); - sub_scope.* = .{ - .parent = scope, - .gen_zir = gz, - .name = ident_name, - .inst = init_inst, - .src = name_src, - }; - return &sub_scope.base; - } - - // Detect whether the initialization expression actually uses the - // result location pointer. - var init_scope: Scope.GenZir = .{ - .parent = scope, - .force_comptime = gz.force_comptime, - .zir_code = wzc, - }; - defer init_scope.instructions.deinit(mod.gpa); - - var resolve_inferred_alloc: zir.Inst.Ref = .none; - var opt_type_inst: zir.Inst.Ref = .none; - if (var_decl.ast.type_node != 0) { - const type_inst = try typeExpr(mod, &init_scope.base, var_decl.ast.type_node); - opt_type_inst = type_inst; - init_scope.rl_ptr = try init_scope.addUnNode(.alloc, type_inst, node); - } else { - const alloc = try init_scope.addUnNode(.alloc_inferred, undefined, node); - resolve_inferred_alloc = alloc; - init_scope.rl_ptr = alloc; - } - const init_result_loc: ResultLoc = .{ .block_ptr = &init_scope }; - const init_inst = try expr(mod, &init_scope.base, init_result_loc, var_decl.ast.init_node); - const zir_tags = wzc.instructions.items(.tag); - const zir_datas = wzc.instructions.items(.data); - - const parent_zir = &gz.instructions; - if (init_scope.rvalue_rl_count == 1) { - // Result location pointer not used. We don't need an alloc for this - // const local, and type inference becomes trivial. - // Move the init_scope instructions into the parent scope, eliding - // the alloc instruction and the store_to_block_ptr instruction. - const expected_len = parent_zir.items.len + init_scope.instructions.items.len - 2; - try parent_zir.ensureCapacity(mod.gpa, expected_len); - for (init_scope.instructions.items) |src_inst| { - if (wzc.indexToRef(src_inst) == init_scope.rl_ptr) continue; - if (zir_tags[src_inst] == .store_to_block_ptr) { - if (zir_datas[src_inst].bin.lhs == init_scope.rl_ptr) continue; - } - parent_zir.appendAssumeCapacity(src_inst); - } - assert(parent_zir.items.len == expected_len); - const casted_init = if (opt_type_inst != .none) - try gz.addPlNode(.as_node, var_decl.ast.type_node, zir.Inst.As{ - .dest_type = opt_type_inst, - .operand = init_inst, - }) - else - init_inst; - - const sub_scope = try block_arena.create(Scope.LocalVal); - sub_scope.* = .{ - .parent = scope, - .gen_zir = gz, - .name = ident_name, - .inst = casted_init, - .src = name_src, - }; - return &sub_scope.base; - } - // The initialization expression took advantage of the result location - // of the const local. In this case we will create an alloc and a LocalPtr for it. - // Move the init_scope instructions into the parent scope, swapping - // store_to_block_ptr for store_to_inferred_ptr. - const expected_len = parent_zir.items.len + init_scope.instructions.items.len; - try parent_zir.ensureCapacity(mod.gpa, expected_len); - for (init_scope.instructions.items) |src_inst| { - if (zir_tags[src_inst] == .store_to_block_ptr) { - if (zir_datas[src_inst].bin.lhs == init_scope.rl_ptr) { - zir_tags[src_inst] = .store_to_inferred_ptr; - } - } - parent_zir.appendAssumeCapacity(src_inst); - } - assert(parent_zir.items.len == expected_len); - if (resolve_inferred_alloc != .none) { - _ = try gz.addUnNode(.resolve_inferred_alloc, resolve_inferred_alloc, node); - } - const sub_scope = try block_arena.create(Scope.LocalPtr); - sub_scope.* = .{ - .parent = scope, - .gen_zir = gz, - .name = ident_name, - .ptr = init_scope.rl_ptr, - .src = name_src, - }; - return &sub_scope.base; - }, - .keyword_var => { - var resolve_inferred_alloc: zir.Inst.Ref = .none; - const var_data: struct { - result_loc: ResultLoc, - alloc: zir.Inst.Ref, - } = if (var_decl.ast.type_node != 0) a: { - const type_inst = try typeExpr(mod, scope, var_decl.ast.type_node); - - const alloc = try gz.addUnNode(.alloc_mut, type_inst, node); - break :a .{ .alloc = alloc, .result_loc = .{ .ptr = alloc } }; - } else a: { - const alloc = try gz.addUnNode(.alloc_inferred_mut, undefined, node); - resolve_inferred_alloc = alloc; - break :a .{ .alloc = alloc, .result_loc = .{ .inferred_ptr = alloc } }; - }; - const init_inst = try expr(mod, scope, var_data.result_loc, var_decl.ast.init_node); - if (resolve_inferred_alloc != .none) { - _ = try gz.addUnNode(.resolve_inferred_alloc, resolve_inferred_alloc, node); - } - const sub_scope = try block_arena.create(Scope.LocalPtr); - sub_scope.* = .{ - .parent = scope, - .gen_zir = gz, - .name = ident_name, - .ptr = var_data.alloc, - .src = name_src, - }; - return &sub_scope.base; - }, - else => unreachable, - } -} - -fn assign(mod: *Module, scope: *Scope, infix_node: ast.Node.Index) InnerError!void { - const tree = scope.tree(); - const node_datas = tree.nodes.items(.data); - const main_tokens = tree.nodes.items(.main_token); - const node_tags = tree.nodes.items(.tag); - - const lhs = node_datas[infix_node].lhs; - const rhs = node_datas[infix_node].rhs; - if (node_tags[lhs] == .identifier) { - // This intentionally does not support `@"_"` syntax. - const ident_name = tree.tokenSlice(main_tokens[lhs]); - if (mem.eql(u8, ident_name, "_")) { - _ = try expr(mod, scope, .discard, rhs); - return; - } - } - const lvalue = try lvalExpr(mod, scope, lhs); - _ = try expr(mod, scope, .{ .ptr = lvalue }, rhs); -} - -fn assignOp( - mod: *Module, - scope: *Scope, - infix_node: ast.Node.Index, - op_inst_tag: zir.Inst.Tag, -) InnerError!void { - const tree = scope.tree(); - const node_datas = tree.nodes.items(.data); - const gz = scope.getGenZir(); - - const lhs_ptr = try lvalExpr(mod, scope, node_datas[infix_node].lhs); - const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node); - const lhs_type = try gz.addUnTok(.typeof, lhs, infix_node); - const rhs = try expr(mod, scope, .{ .ty = lhs_type }, node_datas[infix_node].rhs); - - const result = try gz.addPlNode(op_inst_tag, infix_node, zir.Inst.Bin{ - .lhs = lhs, - .rhs = rhs, - }); - _ = try gz.addBin(.store, lhs_ptr, result); -} - -fn boolNot(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) InnerError!zir.Inst.Ref { - const tree = scope.tree(); - const node_datas = tree.nodes.items(.data); - - const operand = try expr(mod, scope, .{ .ty = .bool_type }, node_datas[node].lhs); - const gz = scope.getGenZir(); - const result = try gz.addUnNode(.bool_not, operand, node); - return rvalue(mod, scope, rl, result, node); -} - -fn bitNot(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) InnerError!zir.Inst.Ref { - const tree = scope.tree(); - const node_datas = tree.nodes.items(.data); - - const gz = scope.getGenZir(); - const operand = try expr(mod, scope, .none, node_datas[node].lhs); - const result = try gz.addUnNode(.bit_not, operand, node); - return rvalue(mod, scope, rl, result, node); -} - -fn negation( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - node: ast.Node.Index, - tag: zir.Inst.Tag, -) InnerError!zir.Inst.Ref { - const tree = scope.tree(); - const node_datas = tree.nodes.items(.data); - - const gz = scope.getGenZir(); - const operand = try expr(mod, scope, .none, node_datas[node].lhs); - const result = try gz.addUnNode(tag, operand, node); - return rvalue(mod, scope, rl, result, node); -} - -fn ptrType( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - node: ast.Node.Index, - ptr_info: ast.full.PtrType, -) InnerError!zir.Inst.Ref { - const tree = scope.tree(); - const gz = scope.getGenZir(); - - const elem_type = try typeExpr(mod, scope, ptr_info.ast.child_type); - - const simple = ptr_info.ast.align_node == 0 and - ptr_info.ast.sentinel == 0 and - ptr_info.ast.bit_range_start == 0; - - if (simple) { - const result = try gz.add(.{ .tag = .ptr_type_simple, .data = .{ - .ptr_type_simple = .{ - .is_allowzero = ptr_info.allowzero_token != null, - .is_mutable = ptr_info.const_token == null, - .is_volatile = ptr_info.volatile_token != null, - .size = ptr_info.size, - .elem_type = elem_type, - }, - } }); - return rvalue(mod, scope, rl, result, node); - } - - var sentinel_ref: zir.Inst.Ref = .none; - var align_ref: zir.Inst.Ref = .none; - var bit_start_ref: zir.Inst.Ref = .none; - var bit_end_ref: zir.Inst.Ref = .none; - var trailing_count: u32 = 0; - - if (ptr_info.ast.sentinel != 0) { - sentinel_ref = try expr(mod, scope, .{ .ty = elem_type }, ptr_info.ast.sentinel); - trailing_count += 1; - } - if (ptr_info.ast.align_node != 0) { - align_ref = try expr(mod, scope, .none, ptr_info.ast.align_node); - trailing_count += 1; - } - if (ptr_info.ast.bit_range_start != 0) { - assert(ptr_info.ast.bit_range_end != 0); - bit_start_ref = try expr(mod, scope, .none, ptr_info.ast.bit_range_start); - bit_end_ref = try expr(mod, scope, .none, ptr_info.ast.bit_range_end); - trailing_count += 2; - } - - const gpa = gz.zir_code.gpa; - try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1); - try gz.zir_code.instructions.ensureCapacity(gpa, gz.zir_code.instructions.len + 1); - try gz.zir_code.extra.ensureCapacity(gpa, gz.zir_code.extra.items.len + - @typeInfo(zir.Inst.PtrType).Struct.fields.len + trailing_count); - - const payload_index = gz.zir_code.addExtraAssumeCapacity(zir.Inst.PtrType{ .elem_type = elem_type }); - if (sentinel_ref != .none) { - gz.zir_code.extra.appendAssumeCapacity(@enumToInt(sentinel_ref)); - } - if (align_ref != .none) { - gz.zir_code.extra.appendAssumeCapacity(@enumToInt(align_ref)); - } - if (bit_start_ref != .none) { - gz.zir_code.extra.appendAssumeCapacity(@enumToInt(bit_start_ref)); - gz.zir_code.extra.appendAssumeCapacity(@enumToInt(bit_end_ref)); - } - - const new_index = @intCast(zir.Inst.Index, gz.zir_code.instructions.len); - const result = gz.zir_code.indexToRef(new_index); - gz.zir_code.instructions.appendAssumeCapacity(.{ .tag = .ptr_type, .data = .{ - .ptr_type = .{ - .flags = .{ - .is_allowzero = ptr_info.allowzero_token != null, - .is_mutable = ptr_info.const_token == null, - .is_volatile = ptr_info.volatile_token != null, - .has_sentinel = sentinel_ref != .none, - .has_align = align_ref != .none, - .has_bit_range = bit_start_ref != .none, - }, - .size = ptr_info.size, - .payload_index = payload_index, - }, - } }); - gz.instructions.appendAssumeCapacity(new_index); - - return rvalue(mod, scope, rl, result, node); -} - -fn arrayType(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) !zir.Inst.Ref { - const tree = scope.tree(); - const node_datas = tree.nodes.items(.data); - const gz = scope.getGenZir(); - - // TODO check for [_]T - const len = try expr(mod, scope, .{ .ty = .usize_type }, node_datas[node].lhs); - const elem_type = try typeExpr(mod, scope, node_datas[node].rhs); - - const result = try gz.addBin(.array_type, len, elem_type); - return rvalue(mod, scope, rl, result, node); -} - -fn arrayTypeSentinel(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) !zir.Inst.Ref { - const tree = scope.tree(); - const node_datas = tree.nodes.items(.data); - const extra = tree.extraData(node_datas[node].rhs, ast.Node.ArrayTypeSentinel); - const gz = scope.getGenZir(); - - // TODO check for [_]T - const len = try expr(mod, scope, .{ .ty = .usize_type }, node_datas[node].lhs); - const elem_type = try typeExpr(mod, scope, extra.elem_type); - const sentinel = try expr(mod, scope, .{ .ty = elem_type }, extra.sentinel); - - const result = try gz.addArrayTypeSentinel(len, elem_type, sentinel); - return rvalue(mod, scope, rl, result, node); -} - -fn containerDecl( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - container_decl: ast.full.ContainerDecl, -) InnerError!zir.Inst.Ref { - return mod.failTok(scope, container_decl.ast.main_token, "TODO implement container decls", .{}); -} - -fn errorSetDecl( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - node: ast.Node.Index, -) InnerError!zir.Inst.Ref { - if (true) @panic("TODO update for zir-memory-layout branch"); - const gz = scope.getGenZir(); - const tree = gz.tree(); - const main_tokens = tree.nodes.items(.main_token); - const token_tags = tree.tokens.items(.tag); - - // Count how many fields there are. - const error_token = main_tokens[node]; - const count: usize = count: { - var tok_i = error_token + 2; - var count: usize = 0; - while (true) : (tok_i += 1) { - switch (token_tags[tok_i]) { - .doc_comment, .comma => {}, - .identifier => count += 1, - .r_brace => break :count count, - else => unreachable, - } - } else unreachable; // TODO should not need else unreachable here - }; - - const fields = try scope.arena().alloc([]const u8, count); - { - var tok_i = error_token + 2; - var field_i: usize = 0; - while (true) : (tok_i += 1) { - switch (token_tags[tok_i]) { - .doc_comment, .comma => {}, - .identifier => { - fields[field_i] = try mod.identifierTokenString(scope, tok_i); - field_i += 1; - }, - .r_brace => break, - else => unreachable, - } - } - } - const result = try addZIRInst(mod, scope, src, zir.Inst.ErrorSet, .{ .fields = fields }, .{}); - return rvalue(mod, scope, rl, result); -} - -fn orelseCatchExpr( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - node: ast.Node.Index, - lhs: ast.Node.Index, - cond_op: zir.Inst.Tag, - unwrap_op: zir.Inst.Tag, - unwrap_code_op: zir.Inst.Tag, - rhs: ast.Node.Index, - payload_token: ?ast.TokenIndex, -) InnerError!zir.Inst.Ref { - const parent_gz = scope.getGenZir(); - const tree = parent_gz.tree(); - - var block_scope: Scope.GenZir = .{ - .parent = scope, - .zir_code = parent_gz.zir_code, - .force_comptime = parent_gz.force_comptime, - .instructions = .{}, - }; - setBlockResultLoc(&block_scope, rl); - defer block_scope.instructions.deinit(mod.gpa); - - // This could be a pointer or value depending on the `operand_rl` parameter. - // We cannot use `block_scope.break_result_loc` because that has the bare - // type, whereas this expression has the optional type. Later we make - // up for this fact by calling rvalue on the else branch. - block_scope.break_count += 1; - - // TODO handle catch - const operand_rl: ResultLoc = switch (block_scope.break_result_loc) { - .ref => .ref, - .discard, .none, .block_ptr, .inferred_ptr, .bitcasted_ptr => .none, - .ty => |elem_ty| blk: { - const wrapped_ty = try block_scope.addUnNode(.optional_type, elem_ty, node); - break :blk .{ .ty = wrapped_ty }; - }, - .ptr => |ptr_ty| blk: { - const wrapped_ty = try block_scope.addUnNode(.optional_type_from_ptr_elem, ptr_ty, node); - break :blk .{ .ty = wrapped_ty }; - }, - }; - const operand = try expr(mod, &block_scope.base, operand_rl, lhs); - const cond = try block_scope.addUnNode(cond_op, operand, node); - const condbr = try block_scope.addCondBr(.condbr, node); - - const block = try parent_gz.addBlock(.block, node); - try parent_gz.instructions.append(mod.gpa, block); - try block_scope.setBlockBody(block); - - var then_scope: Scope.GenZir = .{ - .parent = scope, - .zir_code = parent_gz.zir_code, - .force_comptime = block_scope.force_comptime, - .instructions = .{}, - }; - defer then_scope.instructions.deinit(mod.gpa); - - var err_val_scope: Scope.LocalVal = undefined; - const then_sub_scope = blk: { - const payload = payload_token orelse break :blk &then_scope.base; - if (mem.eql(u8, tree.tokenSlice(payload), "_")) { - return mod.failTok(&then_scope.base, payload, "discard of error capture; omit it instead", .{}); - } - const err_name = try mod.identifierTokenString(scope, payload); - err_val_scope = .{ - .parent = &then_scope.base, - .gen_zir = &then_scope, - .name = err_name, - .inst = try then_scope.addUnNode(unwrap_code_op, operand, node), - .src = parent_gz.tokSrcLoc(payload), - }; - break :blk &err_val_scope.base; - }; - - block_scope.break_count += 1; - const then_result = try expr(mod, then_sub_scope, block_scope.break_result_loc, rhs); - // We hold off on the break instructions as well as copying the then/else - // instructions into place until we know whether to keep store_to_block_ptr - // instructions or not. - - var else_scope: Scope.GenZir = .{ - .parent = scope, - .zir_code = parent_gz.zir_code, - .force_comptime = block_scope.force_comptime, - .instructions = .{}, - }; - defer else_scope.instructions.deinit(mod.gpa); - - // This could be a pointer or value depending on `unwrap_op`. - const unwrapped_payload = try else_scope.addUnNode(unwrap_op, operand, node); - const else_result = switch (rl) { - .ref => unwrapped_payload, - else => try rvalue(mod, &else_scope.base, block_scope.break_result_loc, unwrapped_payload, node), - }; - - return finishThenElseBlock( - mod, - scope, - rl, - node, - &block_scope, - &then_scope, - &else_scope, - condbr, - cond, - node, - node, - then_result, - else_result, - block, - block, - .@"break", - ); -} - -fn finishThenElseBlock( - mod: *Module, - parent_scope: *Scope, - rl: ResultLoc, - node: ast.Node.Index, - block_scope: *Scope.GenZir, - then_scope: *Scope.GenZir, - else_scope: *Scope.GenZir, - condbr: zir.Inst.Index, - cond: zir.Inst.Ref, - then_src: ast.Node.Index, - else_src: ast.Node.Index, - then_result: zir.Inst.Ref, - else_result: zir.Inst.Ref, - main_block: zir.Inst.Index, - then_break_block: zir.Inst.Index, - break_tag: zir.Inst.Tag, -) InnerError!zir.Inst.Ref { - // We now have enough information to decide whether the result instruction should - // be communicated via result location pointer or break instructions. - const strat = rlStrategy(rl, block_scope); - const wzc = block_scope.zir_code; - switch (strat.tag) { - .break_void => { - if (!wzc.refIsNoReturn(then_result)) { - _ = try then_scope.addBreak(break_tag, then_break_block, .void_value); - } - const elide_else = if (else_result != .none) wzc.refIsNoReturn(else_result) else false; - if (!elide_else) { - _ = try else_scope.addBreak(break_tag, main_block, .void_value); - } - assert(!strat.elide_store_to_block_ptr_instructions); - try setCondBrPayload(condbr, cond, then_scope, else_scope); - return wzc.indexToRef(main_block); - }, - .break_operand => { - if (!wzc.refIsNoReturn(then_result)) { - _ = try then_scope.addBreak(break_tag, then_break_block, then_result); - } - if (else_result != .none) { - if (!wzc.refIsNoReturn(else_result)) { - _ = try else_scope.addBreak(break_tag, main_block, else_result); - } - } else { - _ = try else_scope.addBreak(break_tag, main_block, .void_value); - } - if (strat.elide_store_to_block_ptr_instructions) { - try setCondBrPayloadElideBlockStorePtr(condbr, cond, then_scope, else_scope); - } else { - try setCondBrPayload(condbr, cond, then_scope, else_scope); - } - const block_ref = wzc.indexToRef(main_block); - switch (rl) { - .ref => return block_ref, - else => return rvalue(mod, parent_scope, rl, block_ref, node), - } - }, - } -} - -/// Return whether the identifier names of two tokens are equal. Resolves @"" -/// tokens without allocating. -/// OK in theory it could do it without allocating. This implementation -/// allocates when the @"" form is used. -fn tokenIdentEql(mod: *Module, scope: *Scope, token1: ast.TokenIndex, token2: ast.TokenIndex) !bool { - const ident_name_1 = try mod.identifierTokenString(scope, token1); - const ident_name_2 = try mod.identifierTokenString(scope, token2); - return mem.eql(u8, ident_name_1, ident_name_2); -} - -pub fn fieldAccess( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - node: ast.Node.Index, -) InnerError!zir.Inst.Ref { - const gz = scope.getGenZir(); - const tree = gz.tree(); - const main_tokens = tree.nodes.items(.main_token); - const node_datas = tree.nodes.items(.data); - const object_node = node_datas[node].lhs; - const dot_token = main_tokens[node]; - const field_ident = dot_token + 1; - const string_bytes = &gz.zir_code.string_bytes; - const str_index = @intCast(u32, string_bytes.items.len); - try mod.appendIdentStr(scope, field_ident, string_bytes); - try string_bytes.append(mod.gpa, 0); - switch (rl) { - .ref => return gz.addPlNode(.field_ptr, node, zir.Inst.Field{ - .lhs = try expr(mod, scope, .ref, object_node), - .field_name_start = str_index, - }), - else => return rvalue(mod, scope, rl, try gz.addPlNode(.field_val, node, zir.Inst.Field{ - .lhs = try expr(mod, scope, .none, object_node), - .field_name_start = str_index, - }), node), - } -} - -fn arrayAccess( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - node: ast.Node.Index, -) InnerError!zir.Inst.Ref { - const gz = scope.getGenZir(); - const tree = gz.tree(); - const main_tokens = tree.nodes.items(.main_token); - const node_datas = tree.nodes.items(.data); - switch (rl) { - .ref => return gz.addBin( - .elem_ptr, - try expr(mod, scope, .ref, node_datas[node].lhs), - try expr(mod, scope, .{ .ty = .usize_type }, node_datas[node].rhs), - ), - else => return rvalue(mod, scope, rl, try gz.addBin( - .elem_val, - try expr(mod, scope, .none, node_datas[node].lhs), - try expr(mod, scope, .{ .ty = .usize_type }, node_datas[node].rhs), - ), node), - } -} - -fn simpleBinOp( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - node: ast.Node.Index, - op_inst_tag: zir.Inst.Tag, -) InnerError!zir.Inst.Ref { - const gz = scope.getGenZir(); - const tree = gz.tree(); - const node_datas = tree.nodes.items(.data); - - const result = try gz.addPlNode(op_inst_tag, node, zir.Inst.Bin{ - .lhs = try expr(mod, scope, .none, node_datas[node].lhs), - .rhs = try expr(mod, scope, .none, node_datas[node].rhs), - }); - return rvalue(mod, scope, rl, result, node); -} - -fn simpleStrTok( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - ident_token: ast.TokenIndex, - node: ast.Node.Index, - op_inst_tag: zir.Inst.Tag, -) InnerError!zir.Inst.Ref { - const gz = scope.getGenZir(); - const string_bytes = &gz.zir_code.string_bytes; - const str_index = @intCast(u32, string_bytes.items.len); - try mod.appendIdentStr(scope, ident_token, string_bytes); - try string_bytes.append(mod.gpa, 0); - const result = try gz.addStrTok(op_inst_tag, str_index, ident_token); - return rvalue(mod, scope, rl, result, node); -} - -fn boolBinOp( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - node: ast.Node.Index, - zir_tag: zir.Inst.Tag, -) InnerError!zir.Inst.Ref { - const gz = scope.getGenZir(); - const node_datas = gz.tree().nodes.items(.data); - - const lhs = try expr(mod, scope, .{ .ty = .bool_type }, node_datas[node].lhs); - const bool_br = try gz.addBoolBr(zir_tag, lhs); - - var rhs_scope: Scope.GenZir = .{ - .parent = scope, - .zir_code = gz.zir_code, - .force_comptime = gz.force_comptime, - }; - defer rhs_scope.instructions.deinit(mod.gpa); - const rhs = try expr(mod, &rhs_scope.base, .{ .ty = .bool_type }, node_datas[node].rhs); - _ = try rhs_scope.addBreak(.break_inline, bool_br, rhs); - try rhs_scope.setBoolBrBody(bool_br); - - const block_ref = gz.zir_code.indexToRef(bool_br); - return rvalue(mod, scope, rl, block_ref, node); -} - -fn ifExpr( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - node: ast.Node.Index, - if_full: ast.full.If, -) InnerError!zir.Inst.Ref { - const parent_gz = scope.getGenZir(); - var block_scope: Scope.GenZir = .{ - .parent = scope, - .zir_code = parent_gz.zir_code, - .force_comptime = parent_gz.force_comptime, - .instructions = .{}, - }; - setBlockResultLoc(&block_scope, rl); - defer block_scope.instructions.deinit(mod.gpa); - - const cond = c: { - // TODO https://github.com/ziglang/zig/issues/7929 - if (if_full.error_token) |error_token| { - return mod.failTok(scope, error_token, "TODO implement if error union", .{}); - } else if (if_full.payload_token) |payload_token| { - return mod.failTok(scope, payload_token, "TODO implement if optional", .{}); - } else { - break :c try expr(mod, &block_scope.base, .{ .ty = .bool_type }, if_full.ast.cond_expr); - } - }; - - const condbr = try block_scope.addCondBr(.condbr, node); - - const block = try parent_gz.addBlock(.block, node); - try parent_gz.instructions.append(mod.gpa, block); - try block_scope.setBlockBody(block); - - var then_scope: Scope.GenZir = .{ - .parent = scope, - .zir_code = parent_gz.zir_code, - .force_comptime = block_scope.force_comptime, - .instructions = .{}, - }; - defer then_scope.instructions.deinit(mod.gpa); - - // declare payload to the then_scope - const then_sub_scope = &then_scope.base; - - block_scope.break_count += 1; - const then_result = try expr(mod, then_sub_scope, block_scope.break_result_loc, if_full.ast.then_expr); - // We hold off on the break instructions as well as copying the then/else - // instructions into place until we know whether to keep store_to_block_ptr - // instructions or not. - - var else_scope: Scope.GenZir = .{ - .parent = scope, - .zir_code = parent_gz.zir_code, - .force_comptime = block_scope.force_comptime, - .instructions = .{}, - }; - defer else_scope.instructions.deinit(mod.gpa); - - const else_node = if_full.ast.else_expr; - const else_info: struct { - src: ast.Node.Index, - result: zir.Inst.Ref, - } = if (else_node != 0) blk: { - block_scope.break_count += 1; - const sub_scope = &else_scope.base; - break :blk .{ - .src = else_node, - .result = try expr(mod, sub_scope, block_scope.break_result_loc, else_node), - }; - } else .{ - .src = if_full.ast.then_expr, - .result = .none, - }; - - return finishThenElseBlock( - mod, - scope, - rl, - node, - &block_scope, - &then_scope, - &else_scope, - condbr, - cond, - if_full.ast.then_expr, - else_info.src, - then_result, - else_info.result, - block, - block, - .@"break", - ); -} - -fn setCondBrPayload( - condbr: zir.Inst.Index, - cond: zir.Inst.Ref, - then_scope: *Scope.GenZir, - else_scope: *Scope.GenZir, -) !void { - const wzc = then_scope.zir_code; - - try wzc.extra.ensureCapacity(wzc.gpa, wzc.extra.items.len + - @typeInfo(zir.Inst.CondBr).Struct.fields.len + - then_scope.instructions.items.len + else_scope.instructions.items.len); - - const zir_datas = wzc.instructions.items(.data); - zir_datas[condbr].pl_node.payload_index = wzc.addExtraAssumeCapacity(zir.Inst.CondBr{ - .condition = cond, - .then_body_len = @intCast(u32, then_scope.instructions.items.len), - .else_body_len = @intCast(u32, else_scope.instructions.items.len), - }); - wzc.extra.appendSliceAssumeCapacity(then_scope.instructions.items); - wzc.extra.appendSliceAssumeCapacity(else_scope.instructions.items); -} - -/// If `elide_block_store_ptr` is set, expects to find exactly 1 .store_to_block_ptr instruction. -fn setCondBrPayloadElideBlockStorePtr( - condbr: zir.Inst.Index, - cond: zir.Inst.Ref, - then_scope: *Scope.GenZir, - else_scope: *Scope.GenZir, -) !void { - const wzc = then_scope.zir_code; - - try wzc.extra.ensureCapacity(wzc.gpa, wzc.extra.items.len + - @typeInfo(zir.Inst.CondBr).Struct.fields.len + - then_scope.instructions.items.len + else_scope.instructions.items.len - 2); - - const zir_datas = wzc.instructions.items(.data); - zir_datas[condbr].pl_node.payload_index = wzc.addExtraAssumeCapacity(zir.Inst.CondBr{ - .condition = cond, - .then_body_len = @intCast(u32, then_scope.instructions.items.len - 1), - .else_body_len = @intCast(u32, else_scope.instructions.items.len - 1), - }); - - const zir_tags = wzc.instructions.items(.tag); - for ([_]*Scope.GenZir{ then_scope, else_scope }) |scope| { - for (scope.instructions.items) |src_inst| { - if (zir_tags[src_inst] != .store_to_block_ptr) { - wzc.extra.appendAssumeCapacity(src_inst); - } - } - } -} - -fn whileExpr( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - node: ast.Node.Index, - while_full: ast.full.While, -) InnerError!zir.Inst.Ref { - if (while_full.label_token) |label_token| { - try checkLabelRedefinition(mod, scope, label_token); - } - const parent_gz = scope.getGenZir(); - const is_inline = parent_gz.force_comptime or while_full.inline_token != null; - const loop_tag: zir.Inst.Tag = if (is_inline) .block_inline else .loop; - const loop_block = try parent_gz.addBlock(loop_tag, node); - try parent_gz.instructions.append(mod.gpa, loop_block); - - var loop_scope: Scope.GenZir = .{ - .parent = scope, - .zir_code = parent_gz.zir_code, - .force_comptime = parent_gz.force_comptime, - .instructions = .{}, - }; - setBlockResultLoc(&loop_scope, rl); - defer loop_scope.instructions.deinit(mod.gpa); - - var continue_scope: Scope.GenZir = .{ - .parent = &loop_scope.base, - .zir_code = parent_gz.zir_code, - .force_comptime = loop_scope.force_comptime, - .instructions = .{}, - }; - defer continue_scope.instructions.deinit(mod.gpa); - - const cond = c: { - // TODO https://github.com/ziglang/zig/issues/7929 - if (while_full.error_token) |error_token| { - return mod.failTok(scope, error_token, "TODO implement while error union", .{}); - } else if (while_full.payload_token) |payload_token| { - return mod.failTok(scope, payload_token, "TODO implement while optional", .{}); - } else { - const bool_type_rl: ResultLoc = .{ .ty = .bool_type }; - break :c try expr(mod, &continue_scope.base, bool_type_rl, while_full.ast.cond_expr); - } - }; - - const condbr_tag: zir.Inst.Tag = if (is_inline) .condbr_inline else .condbr; - const condbr = try continue_scope.addCondBr(condbr_tag, node); - const block_tag: zir.Inst.Tag = if (is_inline) .block_inline else .block; - const cond_block = try loop_scope.addBlock(block_tag, node); - try loop_scope.instructions.append(mod.gpa, cond_block); - try continue_scope.setBlockBody(cond_block); - - // TODO avoid emitting the continue expr when there - // are no jumps to it. This happens when the last statement of a while body is noreturn - // and there are no `continue` statements. - if (while_full.ast.cont_expr != 0) { - _ = try expr(mod, &loop_scope.base, .{ .ty = .void_type }, while_full.ast.cont_expr); - } - const repeat_tag: zir.Inst.Tag = if (is_inline) .repeat_inline else .repeat; - _ = try loop_scope.addNode(repeat_tag, node); - - try loop_scope.setBlockBody(loop_block); - loop_scope.break_block = loop_block; - loop_scope.continue_block = cond_block; - if (while_full.label_token) |label_token| { - loop_scope.label = @as(?Scope.GenZir.Label, Scope.GenZir.Label{ - .token = label_token, - .block_inst = loop_block, - }); - } - - var then_scope: Scope.GenZir = .{ - .parent = &continue_scope.base, - .zir_code = parent_gz.zir_code, - .force_comptime = continue_scope.force_comptime, - .instructions = .{}, - }; - defer then_scope.instructions.deinit(mod.gpa); - - const then_sub_scope = &then_scope.base; - - loop_scope.break_count += 1; - const then_result = try expr(mod, then_sub_scope, loop_scope.break_result_loc, while_full.ast.then_expr); - - var else_scope: Scope.GenZir = .{ - .parent = &continue_scope.base, - .zir_code = parent_gz.zir_code, - .force_comptime = continue_scope.force_comptime, - .instructions = .{}, - }; - defer else_scope.instructions.deinit(mod.gpa); - - const else_node = while_full.ast.else_expr; - const else_info: struct { - src: ast.Node.Index, - result: zir.Inst.Ref, - } = if (else_node != 0) blk: { - loop_scope.break_count += 1; - const sub_scope = &else_scope.base; - break :blk .{ - .src = else_node, - .result = try expr(mod, sub_scope, loop_scope.break_result_loc, else_node), - }; - } else .{ - .src = while_full.ast.then_expr, - .result = .none, - }; - - if (loop_scope.label) |some| { - if (!some.used) { - return mod.failTok(scope, some.token, "unused while loop label", .{}); - } - } - const break_tag: zir.Inst.Tag = if (is_inline) .break_inline else .@"break"; - return finishThenElseBlock( - mod, - scope, - rl, - node, - &loop_scope, - &then_scope, - &else_scope, - condbr, - cond, - while_full.ast.then_expr, - else_info.src, - then_result, - else_info.result, - loop_block, - cond_block, - break_tag, - ); -} - -fn forExpr( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - node: ast.Node.Index, - for_full: ast.full.While, -) InnerError!zir.Inst.Ref { - if (for_full.label_token) |label_token| { - try checkLabelRedefinition(mod, scope, label_token); - } - // Set up variables and constants. - const parent_gz = scope.getGenZir(); - const is_inline = parent_gz.force_comptime or for_full.inline_token != null; - const tree = parent_gz.tree(); - const token_tags = tree.tokens.items(.tag); - - const array_ptr = try expr(mod, scope, .ref, for_full.ast.cond_expr); - const len = try parent_gz.addUnNode(.indexable_ptr_len, array_ptr, for_full.ast.cond_expr); - - const index_ptr = blk: { - const index_ptr = try parent_gz.addUnNode(.alloc, .usize_type, node); - // initialize to zero - _ = try parent_gz.addBin(.store, index_ptr, .zero_usize); - break :blk index_ptr; - }; - - const loop_tag: zir.Inst.Tag = if (is_inline) .block_inline else .loop; - const loop_block = try parent_gz.addBlock(loop_tag, node); - try parent_gz.instructions.append(mod.gpa, loop_block); - - var loop_scope: Scope.GenZir = .{ - .parent = scope, - .zir_code = parent_gz.zir_code, - .force_comptime = parent_gz.force_comptime, - .instructions = .{}, - }; - setBlockResultLoc(&loop_scope, rl); - defer loop_scope.instructions.deinit(mod.gpa); - - var cond_scope: Scope.GenZir = .{ - .parent = &loop_scope.base, - .zir_code = parent_gz.zir_code, - .force_comptime = loop_scope.force_comptime, - .instructions = .{}, - }; - defer cond_scope.instructions.deinit(mod.gpa); - - // check condition i < array_expr.len - const index = try cond_scope.addUnNode(.load, index_ptr, for_full.ast.cond_expr); - const cond = try cond_scope.addPlNode(.cmp_lt, for_full.ast.cond_expr, zir.Inst.Bin{ - .lhs = index, - .rhs = len, - }); - - const condbr_tag: zir.Inst.Tag = if (is_inline) .condbr_inline else .condbr; - const condbr = try cond_scope.addCondBr(condbr_tag, node); - const block_tag: zir.Inst.Tag = if (is_inline) .block_inline else .block; - const cond_block = try loop_scope.addBlock(block_tag, node); - try loop_scope.instructions.append(mod.gpa, cond_block); - try cond_scope.setBlockBody(cond_block); - - // Increment the index variable. - const index_2 = try loop_scope.addUnNode(.load, index_ptr, for_full.ast.cond_expr); - const index_plus_one = try loop_scope.addPlNode(.add, node, zir.Inst.Bin{ - .lhs = index_2, - .rhs = .one_usize, - }); - _ = try loop_scope.addBin(.store, index_ptr, index_plus_one); - const repeat_tag: zir.Inst.Tag = if (is_inline) .repeat_inline else .repeat; - _ = try loop_scope.addNode(repeat_tag, node); - - try loop_scope.setBlockBody(loop_block); - loop_scope.break_block = loop_block; - loop_scope.continue_block = cond_block; - if (for_full.label_token) |label_token| { - loop_scope.label = @as(?Scope.GenZir.Label, Scope.GenZir.Label{ - .token = label_token, - .block_inst = loop_block, - }); - } - - var then_scope: Scope.GenZir = .{ - .parent = &cond_scope.base, - .zir_code = parent_gz.zir_code, - .force_comptime = cond_scope.force_comptime, - .instructions = .{}, - }; - defer then_scope.instructions.deinit(mod.gpa); - - var index_scope: Scope.LocalPtr = undefined; - const then_sub_scope = blk: { - const payload_token = for_full.payload_token.?; - const ident = if (token_tags[payload_token] == .asterisk) - payload_token + 1 - else - payload_token; - const is_ptr = ident != payload_token; - const value_name = tree.tokenSlice(ident); - if (!mem.eql(u8, value_name, "_")) { - return mod.failNode(&then_scope.base, ident, "TODO implement for loop value payload", .{}); - } else if (is_ptr) { - return mod.failTok(&then_scope.base, payload_token, "pointer modifier invalid on discard", .{}); - } - - const index_token = if (token_tags[ident + 1] == .comma) - ident + 2 - else - break :blk &then_scope.base; - if (mem.eql(u8, tree.tokenSlice(index_token), "_")) { - return mod.failTok(&then_scope.base, index_token, "discard of index capture; omit it instead", .{}); - } - const index_name = try mod.identifierTokenString(&then_scope.base, index_token); - index_scope = .{ - .parent = &then_scope.base, - .gen_zir = &then_scope, - .name = index_name, - .ptr = index_ptr, - .src = parent_gz.tokSrcLoc(index_token), - }; - break :blk &index_scope.base; - }; - - loop_scope.break_count += 1; - const then_result = try expr(mod, then_sub_scope, loop_scope.break_result_loc, for_full.ast.then_expr); - - var else_scope: Scope.GenZir = .{ - .parent = &cond_scope.base, - .zir_code = parent_gz.zir_code, - .force_comptime = cond_scope.force_comptime, - .instructions = .{}, - }; - defer else_scope.instructions.deinit(mod.gpa); - - const else_node = for_full.ast.else_expr; - const else_info: struct { - src: ast.Node.Index, - result: zir.Inst.Ref, - } = if (else_node != 0) blk: { - loop_scope.break_count += 1; - const sub_scope = &else_scope.base; - break :blk .{ - .src = else_node, - .result = try expr(mod, sub_scope, loop_scope.break_result_loc, else_node), - }; - } else .{ - .src = for_full.ast.then_expr, - .result = .none, - }; - - if (loop_scope.label) |some| { - if (!some.used) { - return mod.failTok(scope, some.token, "unused for loop label", .{}); - } - } - const break_tag: zir.Inst.Tag = if (is_inline) .break_inline else .@"break"; - return finishThenElseBlock( - mod, - scope, - rl, - node, - &loop_scope, - &then_scope, - &else_scope, - condbr, - cond, - for_full.ast.then_expr, - else_info.src, - then_result, - else_info.result, - loop_block, - cond_block, - break_tag, - ); -} - -fn getRangeNode( - node_tags: []const ast.Node.Tag, - node_datas: []const ast.Node.Data, - start_node: ast.Node.Index, -) ?ast.Node.Index { - var node = start_node; - while (true) { - switch (node_tags[node]) { - .switch_range => return node, - .grouped_expression => node = node_datas[node].lhs, - else => return null, - } - } -} - -fn switchExpr( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - switch_node: ast.Node.Index, -) InnerError!zir.Inst.Ref { - if (true) @panic("TODO update for zir-memory-layout"); - const parent_gz = scope.getGenZir(); - const tree = parent_gz.tree(); - const node_datas = tree.nodes.items(.data); - const main_tokens = tree.nodes.items(.main_token); - const token_tags = tree.tokens.items(.tag); - const node_tags = tree.nodes.items(.tag); - - const switch_token = main_tokens[switch_node]; - const target_node = node_datas[switch_node].lhs; - const extra = tree.extraData(node_datas[switch_node].rhs, ast.Node.SubRange); - const case_nodes = tree.extra_data[extra.start..extra.end]; - - const switch_src = token_starts[switch_token]; - - var block_scope: Scope.GenZir = .{ - .parent = scope, - .decl = scope.ownerDecl().?, - .arena = scope.arena(), - .force_comptime = parent_gz.force_comptime, - .instructions = .{}, - }; - setBlockResultLoc(&block_scope, rl); - defer block_scope.instructions.deinit(mod.gpa); - - var items = std.ArrayList(zir.Inst.Ref).init(mod.gpa); - defer items.deinit(); - - // First we gather all the switch items and check else/'_' prongs. - var else_src: ?usize = null; - var underscore_src: ?usize = null; - var first_range: ?*zir.Inst = null; - var simple_case_count: usize = 0; - var any_payload_is_ref = false; - for (case_nodes) |case_node| { - const case = switch (node_tags[case_node]) { - .switch_case_one => tree.switchCaseOne(case_node), - .switch_case => tree.switchCase(case_node), - else => unreachable, - }; - if (case.payload_token) |payload_token| { - if (token_tags[payload_token] == .asterisk) { - any_payload_is_ref = true; - } - } - // Check for else/_ prong, those are handled last. - if (case.ast.values.len == 0) { - const case_src = token_starts[case.ast.arrow_token - 1]; - if (else_src) |src| { - const msg = msg: { - const msg = try mod.errMsg( - scope, - case_src, - "multiple else prongs in switch expression", - .{}, - ); - errdefer msg.destroy(mod.gpa); - try mod.errNote(scope, src, msg, "previous else prong is here", .{}); - break :msg msg; - }; - return mod.failWithOwnedErrorMsg(scope, msg); - } - else_src = case_src; - continue; - } else if (case.ast.values.len == 1 and - node_tags[case.ast.values[0]] == .identifier and - mem.eql(u8, tree.tokenSlice(main_tokens[case.ast.values[0]]), "_")) - { - const case_src = token_starts[case.ast.arrow_token - 1]; - if (underscore_src) |src| { - const msg = msg: { - const msg = try mod.errMsg( - scope, - case_src, - "multiple '_' prongs in switch expression", - .{}, - ); - errdefer msg.destroy(mod.gpa); - try mod.errNote(scope, src, msg, "previous '_' prong is here", .{}); - break :msg msg; - }; - return mod.failWithOwnedErrorMsg(scope, msg); - } - underscore_src = case_src; - continue; - } - - if (else_src) |some_else| { - if (underscore_src) |some_underscore| { - const msg = msg: { - const msg = try mod.errMsg( - scope, - switch_src, - "else and '_' prong in switch expression", - .{}, - ); - errdefer msg.destroy(mod.gpa); - try mod.errNote(scope, some_else, msg, "else prong is here", .{}); - try mod.errNote(scope, some_underscore, msg, "'_' prong is here", .{}); - break :msg msg; - }; - return mod.failWithOwnedErrorMsg(scope, msg); - } - } - - if (case.ast.values.len == 1 and - getRangeNode(node_tags, node_datas, case.ast.values[0]) == null) - { - simple_case_count += 1; - } - - // Generate all the switch items as comptime expressions. - for (case.ast.values) |item| { - if (getRangeNode(node_tags, node_datas, item)) |range| { - const start = try comptimeExpr(mod, &block_scope.base, .none, node_datas[range].lhs); - const end = try comptimeExpr(mod, &block_scope.base, .none, node_datas[range].rhs); - const range_src = token_starts[main_tokens[range]]; - const range_inst = try addZIRBinOp(mod, &block_scope.base, range_src, .switch_range, start, end); - try items.append(range_inst); - } else { - const item_inst = try comptimeExpr(mod, &block_scope.base, .none, item); - try items.append(item_inst); - } - } - } - - var special_prong: zir.Inst.SwitchBr.SpecialProng = .none; - if (else_src != null) special_prong = .@"else"; - if (underscore_src != null) special_prong = .underscore; - var cases = try block_scope.arena.alloc(zir.Inst.SwitchBr.Case, simple_case_count); - - const rl_and_tag: struct { rl: ResultLoc, tag: zir.Inst.Tag } = if (any_payload_is_ref) .{ - .rl = .ref, - .tag = .switchbr_ref, - } else .{ - .rl = .none, - .tag = .switchbr, - }; - const target = try expr(mod, &block_scope.base, rl_and_tag.rl, target_node); - const switch_inst = try addZirInstT(mod, &block_scope.base, switch_src, zir.Inst.SwitchBr, rl_and_tag.tag, .{ - .target = target, - .cases = cases, - .items = try block_scope.arena.dupe(zir.Inst.Ref, items.items), - .else_body = undefined, // populated below - .range = first_range, - .special_prong = special_prong, - }); - const block = try addZIRInstBlock(mod, scope, switch_src, .block, .{ - .instructions = try block_scope.arena.dupe(zir.Inst.Ref, block_scope.instructions.items), - }); - - var case_scope: Scope.GenZir = .{ - .parent = scope, - .decl = block_scope.decl, - .arena = block_scope.arena, - .force_comptime = block_scope.force_comptime, - .instructions = .{}, - }; - defer case_scope.instructions.deinit(mod.gpa); - - var else_scope: Scope.GenZir = .{ - .parent = scope, - .decl = case_scope.decl, - .arena = case_scope.arena, - .force_comptime = case_scope.force_comptime, - .instructions = .{}, - }; - defer else_scope.instructions.deinit(mod.gpa); - - // Now generate all but the special cases. - var special_case: ?ast.full.SwitchCase = null; - var items_index: usize = 0; - var case_index: usize = 0; - for (case_nodes) |case_node| { - const case = switch (node_tags[case_node]) { - .switch_case_one => tree.switchCaseOne(case_node), - .switch_case => tree.switchCase(case_node), - else => unreachable, - }; - const case_src = token_starts[main_tokens[case_node]]; - case_scope.instructions.shrinkRetainingCapacity(0); - - // Check for else/_ prong, those are handled last. - if (case.ast.values.len == 0) { - special_case = case; - continue; - } else if (case.ast.values.len == 1 and - node_tags[case.ast.values[0]] == .identifier and - mem.eql(u8, tree.tokenSlice(main_tokens[case.ast.values[0]]), "_")) - { - special_case = case; - continue; - } - - // If this is a simple one item prong then it is handled by the switchbr. - if (case.ast.values.len == 1 and - getRangeNode(node_tags, node_datas, case.ast.values[0]) == null) - { - const item = items.items[items_index]; - items_index += 1; - try switchCaseExpr(mod, &case_scope.base, block_scope.break_result_loc, block, case, target); - - cases[case_index] = .{ - .item = item, - .body = .{ .instructions = try scope.arena().dupe(zir.Inst.Ref, case_scope.instructions.items) }, - }; - case_index += 1; - continue; - } - - // Check if the target matches any of the items. - // 1, 2, 3..6 will result in - // target == 1 or target == 2 or (target >= 3 and target <= 6) - // TODO handle multiple items as switch prongs rather than along with ranges. - var any_ok: ?*zir.Inst = null; - for (case.ast.values) |item| { - if (getRangeNode(node_tags, node_datas, item)) |range| { - const range_src = token_starts[main_tokens[range]]; - const range_inst = items.items[items_index].castTag(.switch_range).?; - items_index += 1; - - // target >= start and target <= end - const range_start_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_gte, target, range_inst.positionals.lhs); - const range_end_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_lte, target, range_inst.positionals.rhs); - const range_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .bool_and, range_start_ok, range_end_ok); - - if (any_ok) |some| { - any_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .bool_or, some, range_ok); - } else { - any_ok = range_ok; - } - continue; - } - - const item_inst = items.items[items_index]; - items_index += 1; - const cpm_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .cmp_eq, target, item_inst); - - if (any_ok) |some| { - any_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .bool_or, some, cpm_ok); - } else { - any_ok = cpm_ok; - } - } - - const condbr = try addZIRInstSpecial(mod, &case_scope.base, case_src, zir.Inst.CondBr, .{ - .condition = any_ok.?, - .then_body = undefined, // populated below - .else_body = undefined, // populated below - }, .{}); - const cond_block = try addZIRInstBlock(mod, &else_scope.base, case_src, .block, .{ - .instructions = try scope.arena().dupe(zir.Inst.Ref, case_scope.instructions.items), - }); - - // reset cond_scope for then_body - case_scope.instructions.items.len = 0; - try switchCaseExpr(mod, &case_scope.base, block_scope.break_result_loc, block, case, target); - condbr.positionals.then_body = .{ - .instructions = try scope.arena().dupe(zir.Inst.Ref, case_scope.instructions.items), - }; - - // reset cond_scope for else_body - case_scope.instructions.items.len = 0; - _ = try addZIRInst(mod, &case_scope.base, case_src, zir.Inst.BreakVoid, .{ - .block = cond_block, - }, .{}); - condbr.positionals.else_body = .{ - .instructions = try scope.arena().dupe(zir.Inst.Ref, case_scope.instructions.items), - }; - } - - // Finally generate else block or a break. - if (special_case) |case| { - try switchCaseExpr(mod, &else_scope.base, block_scope.break_result_loc, block, case, target); - } else { - // Not handling all possible cases is a compile error. - _ = try addZIRNoOp(mod, &else_scope.base, switch_src, .unreachable_unsafe); - } - switch_inst.positionals.else_body = .{ - .instructions = try block_scope.arena.dupe(zir.Inst.Ref, else_scope.instructions.items), - }; - - return &block.base; -} - -fn switchCaseExpr( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - block: *zir.Inst.Block, - case: ast.full.SwitchCase, - target: zir.Inst.Ref, -) !void { - const tree = scope.tree(); - const node_datas = tree.nodes.items(.data); - const main_tokens = tree.nodes.items(.main_token); - const token_tags = tree.tokens.items(.tag); - - const case_src = token_starts[case.ast.arrow_token]; - const sub_scope = blk: { - const payload_token = case.payload_token orelse break :blk scope; - const ident = if (token_tags[payload_token] == .asterisk) - payload_token + 1 - else - payload_token; - const is_ptr = ident != payload_token; - const value_name = tree.tokenSlice(ident); - if (mem.eql(u8, value_name, "_")) { - if (is_ptr) { - return mod.failTok(scope, payload_token, "pointer modifier invalid on discard", .{}); - } - break :blk scope; - } - return mod.failTok(scope, ident, "TODO implement switch value payload", .{}); - }; - - const case_body = try expr(mod, sub_scope, rl, case.ast.target_expr); - if (!case_body.tag.isNoReturn()) { - _ = try addZIRInst(mod, sub_scope, case_src, zir.Inst.Break, .{ - .block = block, - .operand = case_body, - }, .{}); - } -} - -fn ret(mod: *Module, scope: *Scope, node: ast.Node.Index) InnerError!zir.Inst.Ref { - const tree = scope.tree(); - const node_datas = tree.nodes.items(.data); - const main_tokens = tree.nodes.items(.main_token); - - const operand_node = node_datas[node].lhs; - const gz = scope.getGenZir(); - const operand: zir.Inst.Ref = if (operand_node != 0) operand: { - const rl: ResultLoc = if (nodeMayNeedMemoryLocation(scope, operand_node)) .{ - .ptr = try gz.addNode(.ret_ptr, node), - } else .{ - .ty = try gz.addNode(.ret_type, node), - }; - break :operand try expr(mod, scope, rl, operand_node); - } else .void_value; - _ = try gz.addUnNode(.ret_node, operand, node); - return zir.Inst.Ref.unreachable_value; -} - -fn identifier( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - ident: ast.Node.Index, -) InnerError!zir.Inst.Ref { - const tracy = trace(@src()); - defer tracy.end(); - - const tree = scope.tree(); - const main_tokens = tree.nodes.items(.main_token); - - const gz = scope.getGenZir(); - - const ident_token = main_tokens[ident]; - const ident_name = try mod.identifierTokenString(scope, ident_token); - if (mem.eql(u8, ident_name, "_")) { - return mod.failNode(scope, ident, "TODO implement '_' identifier", .{}); - } - - if (simple_types.get(ident_name)) |zir_const_ref| { - return rvalue(mod, scope, rl, zir_const_ref, ident); - } - - if (ident_name.len >= 2) integer: { - const first_c = ident_name[0]; - if (first_c == 'i' or first_c == 'u') { - const signedness: std.builtin.Signedness = switch (first_c == 'i') { - true => .signed, - false => .unsigned, - }; - const bit_count = std.fmt.parseInt(u16, ident_name[1..], 10) catch |err| switch (err) { - error.Overflow => return mod.failNode( - scope, - ident, - "primitive integer type '{s}' exceeds maximum bit width of 65535", - .{ident_name}, - ), - error.InvalidCharacter => break :integer, - }; - const result = try gz.add(.{ - .tag = .int_type, - .data = .{ .int_type = .{ - .src_node = gz.zir_code.decl.nodeIndexToRelative(ident), - .signedness = signedness, - .bit_count = bit_count, - } }, - }); - return rvalue(mod, scope, rl, result, ident); - } - } - - // Local variables, including function parameters. - { - var s = scope; - while (true) switch (s.tag) { - .local_val => { - const local_val = s.cast(Scope.LocalVal).?; - if (mem.eql(u8, local_val.name, ident_name)) { - return rvalue(mod, scope, rl, local_val.inst, ident); - } - s = local_val.parent; - }, - .local_ptr => { - const local_ptr = s.cast(Scope.LocalPtr).?; - if (mem.eql(u8, local_ptr.name, ident_name)) { - if (rl == .ref) return local_ptr.ptr; - const loaded = try gz.addUnNode(.load, local_ptr.ptr, ident); - return rvalue(mod, scope, rl, loaded, ident); - } - s = local_ptr.parent; - }, - .gen_zir => s = s.cast(Scope.GenZir).?.parent, - else => break, - }; - } - - const gop = try gz.zir_code.decl_map.getOrPut(mod.gpa, ident_name); - if (!gop.found_existing) { - const decl = mod.lookupDeclName(scope, ident_name) orelse - return mod.failNode(scope, ident, "use of undeclared identifier '{s}'", .{ident_name}); - try gz.zir_code.decls.append(mod.gpa, decl); - } - const decl_index = @intCast(u32, gop.index); - switch (rl) { - .ref => return gz.addDecl(.decl_ref, decl_index, ident), - else => return rvalue(mod, scope, rl, try gz.addDecl(.decl_val, decl_index, ident), ident), - } -} - -fn stringLiteral( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - node: ast.Node.Index, -) InnerError!zir.Inst.Ref { - const tree = scope.tree(); - const main_tokens = tree.nodes.items(.main_token); - const gz = scope.getGenZir(); - const string_bytes = &gz.zir_code.string_bytes; - const str_index = string_bytes.items.len; - const str_lit_token = main_tokens[node]; - const token_bytes = tree.tokenSlice(str_lit_token); - try mod.parseStrLit(scope, str_lit_token, string_bytes, token_bytes, 0); - const str_len = string_bytes.items.len - str_index; - const result = try gz.add(.{ - .tag = .str, - .data = .{ .str = .{ - .start = @intCast(u32, str_index), - .len = @intCast(u32, str_len), - } }, - }); - return rvalue(mod, scope, rl, result, node); -} - -fn multilineStringLiteral( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - node: ast.Node.Index, -) InnerError!zir.Inst.Ref { - const gz = scope.getGenZir(); - const tree = gz.tree(); - const node_datas = tree.nodes.items(.data); - const main_tokens = tree.nodes.items(.main_token); - - const start = node_datas[node].lhs; - const end = node_datas[node].rhs; - const string_bytes = &gz.zir_code.string_bytes; - const str_index = string_bytes.items.len; - - // First line: do not append a newline. - var tok_i = start; - { - const slice = tree.tokenSlice(tok_i); - const line_bytes = slice[2 .. slice.len - 1]; - try string_bytes.appendSlice(mod.gpa, line_bytes); - tok_i += 1; - } - // Following lines: each line prepends a newline. - while (tok_i <= end) : (tok_i += 1) { - const slice = tree.tokenSlice(tok_i); - const line_bytes = slice[2 .. slice.len - 1]; - try string_bytes.ensureCapacity(mod.gpa, string_bytes.items.len + line_bytes.len + 1); - string_bytes.appendAssumeCapacity('\n'); - string_bytes.appendSliceAssumeCapacity(line_bytes); - } - const result = try gz.add(.{ - .tag = .str, - .data = .{ .str = .{ - .start = @intCast(u32, str_index), - .len = @intCast(u32, string_bytes.items.len - str_index), - } }, - }); - return rvalue(mod, scope, rl, result, node); -} - -fn charLiteral(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) !zir.Inst.Ref { - const gz = scope.getGenZir(); - const tree = gz.tree(); - const main_tokens = tree.nodes.items(.main_token); - const main_token = main_tokens[node]; - const slice = tree.tokenSlice(main_token); - - var bad_index: usize = undefined; - const value = std.zig.parseCharLiteral(slice, &bad_index) catch |err| switch (err) { - error.InvalidCharacter => { - const bad_byte = slice[bad_index]; - const token_starts = tree.tokens.items(.start); - const src_off = @intCast(u32, token_starts[main_token] + bad_index); - return mod.failOff(scope, src_off, "invalid character: '{c}'\n", .{bad_byte}); - }, - }; - const result = try gz.addInt(value); - return rvalue(mod, scope, rl, result, node); -} - -fn integerLiteral( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - node: ast.Node.Index, -) InnerError!zir.Inst.Ref { - const tree = scope.tree(); - const main_tokens = tree.nodes.items(.main_token); - const int_token = main_tokens[node]; - const prefixed_bytes = tree.tokenSlice(int_token); - const gz = scope.getGenZir(); - if (std.fmt.parseInt(u64, prefixed_bytes, 0)) |small_int| { - const result: zir.Inst.Ref = switch (small_int) { - 0 => .zero, - 1 => .one, - else => try gz.addInt(small_int), - }; - return rvalue(mod, scope, rl, result, node); - } else |err| { - return mod.failNode(scope, node, "TODO implement int literals that don't fit in a u64", .{}); - } -} - -fn floatLiteral( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - node: ast.Node.Index, -) InnerError!zir.Inst.Ref { - const arena = scope.arena(); - const tree = scope.tree(); - const main_tokens = tree.nodes.items(.main_token); - const gz = scope.getGenZir(); - - const main_token = main_tokens[node]; - const bytes = tree.tokenSlice(main_token); - if (bytes.len > 2 and bytes[1] == 'x') { - assert(bytes[0] == '0'); // validated by tokenizer - return mod.failTok(scope, main_token, "TODO implement hex floats", .{}); - } - const float_number = std.fmt.parseFloat(f128, bytes) catch |e| switch (e) { - error.InvalidCharacter => unreachable, // validated by tokenizer - }; - const typed_value = try arena.create(TypedValue); - typed_value.* = .{ - .ty = Type.initTag(.comptime_float), - .val = try Value.Tag.float_128.create(arena, float_number), - }; - const result = try gz.add(.{ - .tag = .@"const", - .data = .{ .@"const" = typed_value }, - }); - return rvalue(mod, scope, rl, result, node); -} - -fn asmExpr( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - node: ast.Node.Index, - full: ast.full.Asm, -) InnerError!zir.Inst.Ref { - const arena = scope.arena(); - const tree = scope.tree(); - const main_tokens = tree.nodes.items(.main_token); - const node_datas = tree.nodes.items(.data); - const gz = scope.getGenZir(); - - const asm_source = try expr(mod, scope, .{ .ty = .const_slice_u8_type }, full.ast.template); - - if (full.outputs.len != 0) { - return mod.failTok(scope, full.ast.asm_token, "TODO implement asm with an output", .{}); - } - - const constraints = try arena.alloc(u32, full.inputs.len); - const args = try arena.alloc(zir.Inst.Ref, full.inputs.len); - - for (full.inputs) |input, i| { - const constraint_token = main_tokens[input] + 2; - const string_bytes = &gz.zir_code.string_bytes; - constraints[i] = @intCast(u32, string_bytes.items.len); - const token_bytes = tree.tokenSlice(constraint_token); - try mod.parseStrLit(scope, constraint_token, string_bytes, token_bytes, 0); - try string_bytes.append(mod.gpa, 0); - - args[i] = try expr(mod, scope, .{ .ty = .usize_type }, node_datas[input].lhs); - } - - const tag: zir.Inst.Tag = if (full.volatile_token != null) .asm_volatile else .@"asm"; - const result = try gz.addPlNode(tag, node, zir.Inst.Asm{ - .asm_source = asm_source, - .return_type = .void_type, - .output = .none, - .args_len = @intCast(u32, full.inputs.len), - .clobbers_len = 0, // TODO implement asm clobbers - }); - - try gz.zir_code.extra.ensureCapacity(mod.gpa, gz.zir_code.extra.items.len + - args.len + constraints.len); - gz.zir_code.appendRefsAssumeCapacity(args); - gz.zir_code.extra.appendSliceAssumeCapacity(constraints); - - return rvalue(mod, scope, rl, result, node); -} - -fn as( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - builtin_token: ast.TokenIndex, - node: ast.Node.Index, - lhs: ast.Node.Index, - rhs: ast.Node.Index, -) InnerError!zir.Inst.Ref { - const dest_type = try typeExpr(mod, scope, lhs); - switch (rl) { - .none, .discard, .ref, .ty => { - const result = try expr(mod, scope, .{ .ty = dest_type }, rhs); - return rvalue(mod, scope, rl, result, node); - }, - - .ptr => |result_ptr| { - return asRlPtr(mod, scope, rl, result_ptr, rhs, dest_type); - }, - .block_ptr => |block_scope| { - return asRlPtr(mod, scope, rl, block_scope.rl_ptr, rhs, dest_type); - }, - - .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, 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, builtin_token, "TODO implement @as with inferred-type result location pointer", .{}); - }, - } -} - -fn asRlPtr( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - result_ptr: zir.Inst.Ref, - operand_node: ast.Node.Index, - dest_type: zir.Inst.Ref, -) InnerError!zir.Inst.Ref { - // Detect whether this expr() call goes into rvalue() to store the result into the - // result location. If it does, elide the coerce_result_ptr instruction - // as well as the store instruction, instead passing the result as an rvalue. - const parent_gz = scope.getGenZir(); - const wzc = parent_gz.zir_code; - - var as_scope: Scope.GenZir = .{ - .parent = scope, - .zir_code = wzc, - .force_comptime = parent_gz.force_comptime, - .instructions = .{}, - }; - defer as_scope.instructions.deinit(mod.gpa); - - as_scope.rl_ptr = try as_scope.addBin(.coerce_result_ptr, dest_type, result_ptr); - const result = try expr(mod, &as_scope.base, .{ .block_ptr = &as_scope }, operand_node); - const parent_zir = &parent_gz.instructions; - if (as_scope.rvalue_rl_count == 1) { - // Busted! This expression didn't actually need a pointer. - const zir_tags = wzc.instructions.items(.tag); - const zir_datas = wzc.instructions.items(.data); - const expected_len = parent_zir.items.len + as_scope.instructions.items.len - 2; - try parent_zir.ensureCapacity(mod.gpa, expected_len); - for (as_scope.instructions.items) |src_inst| { - if (wzc.indexToRef(src_inst) == as_scope.rl_ptr) continue; - if (zir_tags[src_inst] == .store_to_block_ptr) { - if (zir_datas[src_inst].bin.lhs == as_scope.rl_ptr) continue; - } - parent_zir.appendAssumeCapacity(src_inst); - } - assert(parent_zir.items.len == expected_len); - const casted_result = try parent_gz.addBin(.as, dest_type, result); - return rvalue(mod, scope, rl, casted_result, operand_node); - } else { - try parent_zir.appendSlice(mod.gpa, as_scope.instructions.items); - return result; - } -} - -fn bitCast( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - builtin_token: ast.TokenIndex, - node: ast.Node.Index, - lhs: ast.Node.Index, - rhs: ast.Node.Index, -) InnerError!zir.Inst.Ref { - if (true) @panic("TODO update for zir-memory-layout"); - const dest_type = try typeExpr(mod, scope, lhs); - switch (rl) { - .none => { - const operand = try expr(mod, scope, .none, rhs); - return addZIRBinOp(mod, scope, src, .bitcast, dest_type, operand); - }, - .discard => { - const operand = try expr(mod, scope, .none, rhs); - const result = try addZIRBinOp(mod, scope, src, .bitcast, dest_type, operand); - _ = try addZIRUnOp(mod, scope, result.src, .ensure_result_non_error, result); - return result; - }, - .ref => { - const operand = try expr(mod, scope, .ref, rhs); - const result = try addZIRBinOp(mod, scope, src, .bitcast_ref, dest_type, operand); - return result; - }, - .ty => |result_ty| { - const result = try expr(mod, scope, .none, rhs); - const bitcasted = try addZIRBinOp(mod, scope, src, .bitcast, dest_type, result); - return addZIRBinOp(mod, scope, src, .as, result_ty, bitcasted); - }, - .ptr => |result_ptr| { - const casted_result_ptr = try addZIRUnOp(mod, scope, src, .bitcast_result_ptr, result_ptr); - return expr(mod, scope, .{ .bitcasted_ptr = casted_result_ptr.castTag(.bitcast_result_ptr).? }, rhs); - }, - .bitcasted_ptr => |bitcasted_ptr| { - return mod.failTok(scope, builtin_token, "TODO implement @bitCast with result location another @bitCast", .{}); - }, - .block_ptr => |block_ptr| { - return mod.failTok(scope, 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, builtin_token, "TODO implement @bitCast with inferred-type result location pointer", .{}); - }, - } -} - -fn typeOf( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - builtin_token: ast.TokenIndex, - node: ast.Node.Index, - params: []const ast.Node.Index, -) InnerError!zir.Inst.Ref { - if (params.len < 1) { - return mod.failTok(scope, builtin_token, "expected at least 1 argument, found 0", .{}); - } - const gz = scope.getGenZir(); - if (params.len == 1) { - return rvalue( - mod, - scope, - rl, - try gz.addUnTok(.typeof, try expr(mod, scope, .none, params[0]), node), - node, - ); - } - const arena = scope.arena(); - var items = try arena.alloc(zir.Inst.Ref, params.len); - for (params) |param, param_i| { - items[param_i] = try expr(mod, scope, .none, param); - } - - const result = try gz.addPlNode(.typeof_peer, node, zir.Inst.MultiOp{ - .operands_len = @intCast(u32, params.len), - }); - try gz.zir_code.appendRefs(items); - - return rvalue(mod, scope, rl, result, node); -} - -fn builtinCall( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - node: ast.Node.Index, - params: []const ast.Node.Index, -) InnerError!zir.Inst.Ref { - const tree = scope.tree(); - const main_tokens = tree.nodes.items(.main_token); - - const builtin_token = main_tokens[node]; - const builtin_name = tree.tokenSlice(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. - - const info = BuiltinFn.list.get(builtin_name) orelse { - return mod.failTok(scope, builtin_token, "invalid builtin function: '{s}'", .{ - builtin_name, - }); - }; - if (info.param_count) |expected| { - if (expected != params.len) { - const s = if (expected == 1) "" else "s"; - return mod.failTok(scope, builtin_token, "expected {d} parameter{s}, found {d}", .{ - expected, s, params.len, - }); - } - } - - const gz = scope.getGenZir(); - - switch (info.tag) { - .ptr_to_int => { - const operand = try expr(mod, scope, .none, params[0]); - const result = try gz.addUnNode(.ptrtoint, operand, node); - return rvalue(mod, scope, rl, result, node); - }, - .float_cast => { - const dest_type = try typeExpr(mod, scope, params[0]); - const rhs = try expr(mod, scope, .none, params[1]); - const result = try gz.addPlNode(.floatcast, node, zir.Inst.Bin{ - .lhs = dest_type, - .rhs = rhs, - }); - return rvalue(mod, scope, rl, result, node); - }, - .int_cast => { - const dest_type = try typeExpr(mod, scope, params[0]); - const rhs = try expr(mod, scope, .none, params[1]); - const result = try gz.addPlNode(.intcast, node, zir.Inst.Bin{ - .lhs = dest_type, - .rhs = rhs, - }); - return rvalue(mod, scope, rl, result, node); - }, - .breakpoint => { - const result = try gz.add(.{ - .tag = .breakpoint, - .data = .{ .node = gz.zir_code.decl.nodeIndexToRelative(node) }, - }); - return rvalue(mod, scope, rl, result, node); - }, - .import => { - const target = try expr(mod, scope, .none, params[0]); - const result = try gz.addUnNode(.import, target, node); - return rvalue(mod, scope, rl, result, node); - }, - .compile_error => { - const target = try expr(mod, scope, .none, params[0]); - const result = try gz.addUnNode(.compile_error, target, node); - return rvalue(mod, scope, rl, result, node); - }, - .set_eval_branch_quota => { - const quota = try expr(mod, scope, .{ .ty = .u32_type }, params[0]); - const result = try gz.addUnNode(.set_eval_branch_quota, quota, node); - return rvalue(mod, scope, rl, result, node); - }, - .compile_log => { - const arg_refs = try mod.gpa.alloc(zir.Inst.Ref, params.len); - defer mod.gpa.free(arg_refs); - - for (params) |param, i| arg_refs[i] = try expr(mod, scope, .none, param); - - const result = try gz.addPlNode(.compile_log, node, zir.Inst.MultiOp{ - .operands_len = @intCast(u32, params.len), - }); - try gz.zir_code.appendRefs(arg_refs); - return rvalue(mod, scope, rl, result, node); - }, - .field => { - const field_name = try comptimeExpr(mod, scope, .{ .ty = .const_slice_u8_type }, params[1]); - if (rl == .ref) { - return try gz.addPlNode(.field_ptr_named, node, zir.Inst.FieldNamed{ - .lhs = try expr(mod, scope, .ref, params[0]), - .field_name = field_name, - }); - } - const result = try gz.addPlNode(.field_val_named, node, zir.Inst.FieldNamed{ - .lhs = try expr(mod, scope, .none, params[0]), - .field_name = field_name, - }); - return rvalue(mod, scope, rl, result, node); - }, - .as => return as(mod, scope, rl, builtin_token, node, params[0], params[1]), - .bit_cast => return bitCast(mod, scope, rl, builtin_token, node, params[0], params[1]), - .TypeOf => return typeOf(mod, scope, rl, builtin_token, node, params), - - .add_with_overflow, - .align_cast, - .align_of, - .atomic_load, - .atomic_rmw, - .atomic_store, - .bit_offset_of, - .bool_to_int, - .bit_size_of, - .mul_add, - .byte_swap, - .bit_reverse, - .byte_offset_of, - .call, - .c_define, - .c_import, - .c_include, - .clz, - .cmpxchg_strong, - .cmpxchg_weak, - .ctz, - .c_undef, - .div_exact, - .div_floor, - .div_trunc, - .embed_file, - .enum_to_int, - .error_name, - .error_return_trace, - .error_to_int, - .err_set_cast, - .@"export", - .fence, - .field_parent_ptr, - .float_to_int, - .has_decl, - .has_field, - .int_to_enum, - .int_to_error, - .int_to_float, - .int_to_ptr, - .memcpy, - .memset, - .wasm_memory_size, - .wasm_memory_grow, - .mod, - .mul_with_overflow, - .panic, - .pop_count, - .ptr_cast, - .rem, - .return_address, - .set_align_stack, - .set_cold, - .set_float_mode, - .set_runtime_safety, - .shl_exact, - .shl_with_overflow, - .shr_exact, - .shuffle, - .size_of, - .splat, - .reduce, - .src, - .sqrt, - .sin, - .cos, - .exp, - .exp2, - .log, - .log2, - .log10, - .fabs, - .floor, - .ceil, - .trunc, - .round, - .sub_with_overflow, - .tag_name, - .This, - .truncate, - .Type, - .type_info, - .type_name, - .union_init, - => return mod.failTok(scope, builtin_token, "TODO: implement builtin function {s}", .{ - builtin_name, - }), - - .async_call, - .frame, - .Frame, - .frame_address, - .frame_size, - => return mod.failTok(scope, builtin_token, "async and related features are not yet supported", .{}), - } -} - -fn callExpr( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - node: ast.Node.Index, - call: ast.full.Call, -) InnerError!zir.Inst.Ref { - if (call.async_token) |async_token| { - return mod.failTok(scope, async_token, "async and related features are not yet supported", .{}); - } - const lhs = try expr(mod, scope, .none, call.ast.fn_expr); - - const args = try mod.gpa.alloc(zir.Inst.Ref, call.ast.params.len); - defer mod.gpa.free(args); - - const gz = scope.getGenZir(); - for (call.ast.params) |param_node, i| { - const param_type = try gz.add(.{ - .tag = .param_type, - .data = .{ .param_type = .{ - .callee = lhs, - .param_index = @intCast(u32, i), - } }, - }); - args[i] = try expr(mod, scope, .{ .ty = param_type }, param_node); - } - - const modifier: std.builtin.CallOptions.Modifier = switch (call.async_token != null) { - true => .async_kw, - false => .auto, - }; - const result: zir.Inst.Ref = res: { - const tag: zir.Inst.Tag = switch (modifier) { - .auto => switch (args.len == 0) { - true => break :res try gz.addUnNode(.call_none, lhs, node), - false => .call, - }, - .async_kw => return mod.failNode(scope, node, "async and related features are not yet supported", .{}), - .never_tail => unreachable, - .never_inline => unreachable, - .no_async => return mod.failNode(scope, node, "async and related features are not yet supported", .{}), - .always_tail => unreachable, - .always_inline => unreachable, - .compile_time => .call_compile_time, - }; - break :res try gz.addCall(tag, lhs, args, node); - }; - return rvalue(mod, scope, rl, result, node); // TODO function call with result location -} - -pub const simple_types = std.ComptimeStringMap(zir.Inst.Ref, .{ - .{ "u8", .u8_type }, - .{ "i8", .i8_type }, - .{ "u16", .u16_type }, - .{ "i16", .i16_type }, - .{ "u32", .u32_type }, - .{ "i32", .i32_type }, - .{ "u64", .u64_type }, - .{ "i64", .i64_type }, - .{ "usize", .usize_type }, - .{ "isize", .isize_type }, - .{ "c_short", .c_short_type }, - .{ "c_ushort", .c_ushort_type }, - .{ "c_int", .c_int_type }, - .{ "c_uint", .c_uint_type }, - .{ "c_long", .c_long_type }, - .{ "c_ulong", .c_ulong_type }, - .{ "c_longlong", .c_longlong_type }, - .{ "c_ulonglong", .c_ulonglong_type }, - .{ "c_longdouble", .c_longdouble_type }, - .{ "f16", .f16_type }, - .{ "f32", .f32_type }, - .{ "f64", .f64_type }, - .{ "f128", .f128_type }, - .{ "c_void", .c_void_type }, - .{ "bool", .bool_type }, - .{ "void", .void_type }, - .{ "type", .type_type }, - .{ "anyerror", .anyerror_type }, - .{ "comptime_int", .comptime_int_type }, - .{ "comptime_float", .comptime_float_type }, - .{ "noreturn", .noreturn_type }, - .{ "null", .null_type }, - .{ "undefined", .undefined_type }, - .{ "undefined", .undef }, - .{ "null", .null_value }, - .{ "true", .bool_true }, - .{ "false", .bool_false }, -}); - -fn nodeMayNeedMemoryLocation(scope: *Scope, start_node: ast.Node.Index) bool { - const tree = scope.tree(); - const node_tags = tree.nodes.items(.tag); - const node_datas = tree.nodes.items(.data); - const main_tokens = tree.nodes.items(.main_token); - const token_tags = tree.tokens.items(.tag); - - var node = start_node; - while (true) { - switch (node_tags[node]) { - .root, - .@"usingnamespace", - .test_decl, - .switch_case, - .switch_case_one, - .container_field_init, - .container_field_align, - .container_field, - .asm_output, - .asm_input, - => unreachable, - - .@"return", - .@"break", - .@"continue", - .bit_not, - .bool_not, - .global_var_decl, - .local_var_decl, - .simple_var_decl, - .aligned_var_decl, - .@"defer", - .@"errdefer", - .address_of, - .optional_type, - .negation, - .negation_wrap, - .@"resume", - .array_type, - .array_type_sentinel, - .ptr_type_aligned, - .ptr_type_sentinel, - .ptr_type, - .ptr_type_bit_range, - .@"suspend", - .@"anytype", - .fn_proto_simple, - .fn_proto_multi, - .fn_proto_one, - .fn_proto, - .fn_decl, - .anyframe_type, - .anyframe_literal, - .integer_literal, - .float_literal, - .enum_literal, - .string_literal, - .multiline_string_literal, - .char_literal, - .true_literal, - .false_literal, - .null_literal, - .undefined_literal, - .unreachable_literal, - .identifier, - .error_set_decl, - .container_decl, - .container_decl_trailing, - .container_decl_two, - .container_decl_two_trailing, - .container_decl_arg, - .container_decl_arg_trailing, - .tagged_union, - .tagged_union_trailing, - .tagged_union_two, - .tagged_union_two_trailing, - .tagged_union_enum_tag, - .tagged_union_enum_tag_trailing, - .@"asm", - .asm_simple, - .add, - .add_wrap, - .array_cat, - .array_mult, - .assign, - .assign_bit_and, - .assign_bit_or, - .assign_bit_shift_left, - .assign_bit_shift_right, - .assign_bit_xor, - .assign_div, - .assign_sub, - .assign_sub_wrap, - .assign_mod, - .assign_add, - .assign_add_wrap, - .assign_mul, - .assign_mul_wrap, - .bang_equal, - .bit_and, - .bit_or, - .bit_shift_left, - .bit_shift_right, - .bit_xor, - .bool_and, - .bool_or, - .div, - .equal_equal, - .error_union, - .greater_or_equal, - .greater_than, - .less_or_equal, - .less_than, - .merge_error_sets, - .mod, - .mul, - .mul_wrap, - .switch_range, - .field_access, - .sub, - .sub_wrap, - .slice, - .slice_open, - .slice_sentinel, - .deref, - .array_access, - .error_value, - .while_simple, // This variant cannot have an else expression. - .while_cont, // This variant cannot have an else expression. - .for_simple, // This variant cannot have an else expression. - .if_simple, // This variant cannot have an else expression. - => return false, - - // Forward the question to the LHS sub-expression. - .grouped_expression, - .@"try", - .@"await", - .@"comptime", - .@"nosuspend", - .unwrap_optional, - => node = node_datas[node].lhs, - - // Forward the question to the RHS sub-expression. - .@"catch", - .@"orelse", - => node = node_datas[node].rhs, - - // True because these are exactly the expressions we need memory locations for. - .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, - .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, - => return true, - - // True because depending on comptime conditions, sub-expressions - // may be the kind that need memory locations. - .@"while", // This variant always has an else expression. - .@"if", // This variant always has an else expression. - .@"for", // This variant always has an else expression. - .@"switch", - .switch_comma, - .call_one, - .call_one_comma, - .async_call_one, - .async_call_one_comma, - .call, - .call_comma, - .async_call, - .async_call_comma, - => return true, - - .block_two, - .block_two_semicolon, - .block, - .block_semicolon, - => { - const lbrace = main_tokens[node]; - if (token_tags[lbrace - 1] == .colon) { - // Labeled blocks may need a memory location to forward - // to their break statements. - return true; - } else { - return false; - } - }, - - .builtin_call, - .builtin_call_comma, - .builtin_call_two, - .builtin_call_two_comma, - => { - const builtin_token = main_tokens[node]; - const builtin_name = tree.tokenSlice(builtin_token); - // If the builtin is an invalid name, we don't cause an error here; instead - // let it pass, and the error will be "invalid builtin function" later. - const builtin_info = BuiltinFn.list.get(builtin_name) orelse return false; - return builtin_info.needs_mem_loc; - }, - } - } -} - -/// 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 rvalue( - mod: *Module, - scope: *Scope, - rl: ResultLoc, - result: zir.Inst.Ref, - src_node: ast.Node.Index, -) InnerError!zir.Inst.Ref { - const gz = scope.getGenZir(); - switch (rl) { - .none => return result, - .discard => { - // Emit a compile error for discarding error values. - _ = try gz.addUnNode(.ensure_result_non_error, result, src_node); - return result; - }, - .ref => { - // We need a pointer but we have a value. - const tree = scope.tree(); - const src_token = tree.firstToken(src_node); - return gz.addUnTok(.ref, result, src_token); - }, - .ty => |ty_inst| { - // Quickly eliminate some common, unnecessary type coercion. - const as_ty = @as(u64, @enumToInt(zir.Inst.Ref.type_type)) << 32; - const as_comptime_int = @as(u64, @enumToInt(zir.Inst.Ref.comptime_int_type)) << 32; - const as_bool = @as(u64, @enumToInt(zir.Inst.Ref.bool_type)) << 32; - const as_usize = @as(u64, @enumToInt(zir.Inst.Ref.usize_type)) << 32; - const as_void = @as(u64, @enumToInt(zir.Inst.Ref.void_type)) << 32; - switch ((@as(u64, @enumToInt(ty_inst)) << 32) | @as(u64, @enumToInt(result))) { - as_ty | @enumToInt(zir.Inst.Ref.u8_type), - as_ty | @enumToInt(zir.Inst.Ref.i8_type), - as_ty | @enumToInt(zir.Inst.Ref.u16_type), - as_ty | @enumToInt(zir.Inst.Ref.i16_type), - as_ty | @enumToInt(zir.Inst.Ref.u32_type), - as_ty | @enumToInt(zir.Inst.Ref.i32_type), - as_ty | @enumToInt(zir.Inst.Ref.u64_type), - as_ty | @enumToInt(zir.Inst.Ref.i64_type), - as_ty | @enumToInt(zir.Inst.Ref.usize_type), - as_ty | @enumToInt(zir.Inst.Ref.isize_type), - as_ty | @enumToInt(zir.Inst.Ref.c_short_type), - as_ty | @enumToInt(zir.Inst.Ref.c_ushort_type), - as_ty | @enumToInt(zir.Inst.Ref.c_int_type), - as_ty | @enumToInt(zir.Inst.Ref.c_uint_type), - as_ty | @enumToInt(zir.Inst.Ref.c_long_type), - as_ty | @enumToInt(zir.Inst.Ref.c_ulong_type), - as_ty | @enumToInt(zir.Inst.Ref.c_longlong_type), - as_ty | @enumToInt(zir.Inst.Ref.c_ulonglong_type), - as_ty | @enumToInt(zir.Inst.Ref.c_longdouble_type), - as_ty | @enumToInt(zir.Inst.Ref.f16_type), - as_ty | @enumToInt(zir.Inst.Ref.f32_type), - as_ty | @enumToInt(zir.Inst.Ref.f64_type), - as_ty | @enumToInt(zir.Inst.Ref.f128_type), - as_ty | @enumToInt(zir.Inst.Ref.c_void_type), - as_ty | @enumToInt(zir.Inst.Ref.bool_type), - as_ty | @enumToInt(zir.Inst.Ref.void_type), - as_ty | @enumToInt(zir.Inst.Ref.type_type), - as_ty | @enumToInt(zir.Inst.Ref.anyerror_type), - as_ty | @enumToInt(zir.Inst.Ref.comptime_int_type), - as_ty | @enumToInt(zir.Inst.Ref.comptime_float_type), - as_ty | @enumToInt(zir.Inst.Ref.noreturn_type), - as_ty | @enumToInt(zir.Inst.Ref.null_type), - as_ty | @enumToInt(zir.Inst.Ref.undefined_type), - as_ty | @enumToInt(zir.Inst.Ref.fn_noreturn_no_args_type), - as_ty | @enumToInt(zir.Inst.Ref.fn_void_no_args_type), - as_ty | @enumToInt(zir.Inst.Ref.fn_naked_noreturn_no_args_type), - as_ty | @enumToInt(zir.Inst.Ref.fn_ccc_void_no_args_type), - as_ty | @enumToInt(zir.Inst.Ref.single_const_pointer_to_comptime_int_type), - as_ty | @enumToInt(zir.Inst.Ref.const_slice_u8_type), - as_ty | @enumToInt(zir.Inst.Ref.enum_literal_type), - as_comptime_int | @enumToInt(zir.Inst.Ref.zero), - as_comptime_int | @enumToInt(zir.Inst.Ref.one), - as_bool | @enumToInt(zir.Inst.Ref.bool_true), - as_bool | @enumToInt(zir.Inst.Ref.bool_false), - as_usize | @enumToInt(zir.Inst.Ref.zero_usize), - as_usize | @enumToInt(zir.Inst.Ref.one_usize), - as_void | @enumToInt(zir.Inst.Ref.void_value), - => return result, // type of result is already correct - - // Need an explicit type coercion instruction. - else => return gz.addPlNode(.as_node, src_node, zir.Inst.As{ - .dest_type = ty_inst, - .operand = result, - }), - } - }, - .ptr => |ptr_inst| { - _ = try gz.addPlNode(.store_node, src_node, zir.Inst.Bin{ - .lhs = ptr_inst, - .rhs = result, - }); - return result; - }, - .bitcasted_ptr => |bitcasted_ptr| { - return mod.failNode(scope, src_node, "TODO implement rvalue .bitcasted_ptr", .{}); - }, - .inferred_ptr => |alloc| { - _ = try gz.addBin(.store_to_inferred_ptr, alloc, result); - return result; - }, - .block_ptr => |block_scope| { - block_scope.rvalue_rl_count += 1; - _ = try gz.addBin(.store_to_block_ptr, block_scope.rl_ptr, result); - return result; - }, - } -} - -fn rlStrategy(rl: ResultLoc, block_scope: *Scope.GenZir) ResultLoc.Strategy { - var elide_store_to_block_ptr_instructions = false; - switch (rl) { - // In this branch there will not be any store_to_block_ptr instructions. - .discard, .none, .ty, .ref => return .{ - .tag = .break_operand, - .elide_store_to_block_ptr_instructions = false, - }, - // The pointer got passed through to the sub-expressions, so we will use - // break_void here. - // In this branch there will not be any store_to_block_ptr instructions. - .ptr => return .{ - .tag = .break_void, - .elide_store_to_block_ptr_instructions = false, - }, - .inferred_ptr, .bitcasted_ptr, .block_ptr => { - if (block_scope.rvalue_rl_count == block_scope.break_count) { - // Neither prong of the if consumed the result location, so we can - // use break instructions to create an rvalue. - return .{ - .tag = .break_operand, - .elide_store_to_block_ptr_instructions = true, - }; - } else { - // Allow the store_to_block_ptr instructions to remain so that - // semantic analysis can turn them into bitcasts. - return .{ - .tag = .break_void, - .elide_store_to_block_ptr_instructions = false, - }; - } - }, - } -} - -fn setBlockResultLoc(block_scope: *Scope.GenZir, parent_rl: ResultLoc) void { - // Depending on whether the result location is a pointer or value, different - // ZIR needs to be generated. In the former case we rely on storing to the - // pointer to communicate the result, and use breakvoid; in the latter case - // the block break instructions will have the result values. - // One more complication: when the result location is a pointer, we detect - // the scenario where the result location is not consumed. In this case - // we emit ZIR for the block break instructions to have the result values, - // and then rvalue() on that to pass the value to the result location. - switch (parent_rl) { - .discard, .none, .ty, .ptr, .ref => { - block_scope.break_result_loc = parent_rl; - }, - - .inferred_ptr => |ptr| { - block_scope.rl_ptr = ptr; - block_scope.break_result_loc = .{ .block_ptr = block_scope }; - }, - - .bitcasted_ptr => |ptr| { - block_scope.rl_ptr = ptr; - block_scope.break_result_loc = .{ .block_ptr = block_scope }; - }, - - .block_ptr => |parent_block_scope| { - block_scope.rl_ptr = parent_block_scope.rl_ptr; - block_scope.break_result_loc = .{ .block_ptr = block_scope }; - }, - } -} diff --git a/src/translate_c.zig b/src/translate_c.zig @@ -4266,7 +4266,7 @@ fn isZigPrimitiveType(name: []const u8) bool { } return true; } - return @import("astgen.zig").simple_types.has(name); + return @import("Astgen.zig").simple_types.has(name); } const MacroCtx = struct { diff --git a/src/zir.zig b/src/zir.zig @@ -1,4 +1,4 @@ -//! Zig Intermediate Representation. astgen.zig converts AST nodes to these +//! Zig Intermediate Representation. Astgen.zig converts AST nodes to these //! untyped IR instructions. Next, Sema.zig processes these into TZIR. const std = @import("std"); @@ -491,7 +491,7 @@ pub const Inst = struct { store_to_block_ptr, /// Same as `store` but the type of the value being stored will be used to infer /// the pointer type. - /// Uses the `bin` union field - astgen.zig depends on the ability to change + /// Uses the `bin` union field - Astgen.zig depends on the ability to change /// the tag of an instruction from `store_to_block_ptr` to `store_to_inferred_ptr` /// without changing the data. store_to_inferred_ptr,