//! 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 GenZir = Scope.GenZir; const InnerError = Module.InnerError; const Decl = Module.Decl; const LazySrcLoc = Module.LazySrcLoc; const BuiltinFn = @import("BuiltinFn.zig"); instructions: std.MultiArrayList(zir.Inst) = .{}, string_bytes: ArrayListUnmanaged(u8) = .{}, extra: ArrayListUnmanaged(u32) = .{}, /// 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); } 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 callee will accept a ref, but it is not necessary, and the `ResultLoc` /// may be treated as `none` instead. none_or_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, /// 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: *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, }; }; fn strategy(rl: ResultLoc, block_scope: *GenZir) 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, .none_or_ref, .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, .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, }; } }, } } }; pub fn typeExpr(gz: *GenZir, scope: *Scope, type_node: ast.Node.Index) InnerError!zir.Inst.Ref { return expr(gz, scope, .{ .ty = .type_type }, type_node); } fn lvalExpr(gz: *GenZir, scope: *Scope, node: ast.Node.Index) InnerError!zir.Inst.Ref { const tree = gz.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 gz.astgen.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 gz.astgen.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(gz, scope, .ref, node); } /// Turn Zig AST into untyped ZIR istructions. /// When `rl` is discard, ptr, inferred_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(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) InnerError!zir.Inst.Ref { const mod = gz.astgen.mod; const tree = gz.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); 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(gz, scope, node); return rvalue(gz, scope, rl, .void_value, node); }, .assign_bit_and => { try assignOp(gz, scope, node, .bit_and); return rvalue(gz, scope, rl, .void_value, node); }, .assign_bit_or => { try assignOp(gz, scope, node, .bit_or); return rvalue(gz, scope, rl, .void_value, node); }, .assign_bit_shift_left => { try assignOp(gz, scope, node, .shl); return rvalue(gz, scope, rl, .void_value, node); }, .assign_bit_shift_right => { try assignOp(gz, scope, node, .shr); return rvalue(gz, scope, rl, .void_value, node); }, .assign_bit_xor => { try assignOp(gz, scope, node, .xor); return rvalue(gz, scope, rl, .void_value, node); }, .assign_div => { try assignOp(gz, scope, node, .div); return rvalue(gz, scope, rl, .void_value, node); }, .assign_sub => { try assignOp(gz, scope, node, .sub); return rvalue(gz, scope, rl, .void_value, node); }, .assign_sub_wrap => { try assignOp(gz, scope, node, .subwrap); return rvalue(gz, scope, rl, .void_value, node); }, .assign_mod => { try assignOp(gz, scope, node, .mod_rem); return rvalue(gz, scope, rl, .void_value, node); }, .assign_add => { try assignOp(gz, scope, node, .add); return rvalue(gz, scope, rl, .void_value, node); }, .assign_add_wrap => { try assignOp(gz, scope, node, .addwrap); return rvalue(gz, scope, rl, .void_value, node); }, .assign_mul => { try assignOp(gz, scope, node, .mul); return rvalue(gz, scope, rl, .void_value, node); }, .assign_mul_wrap => { try assignOp(gz, scope, node, .mulwrap); return rvalue(gz, scope, rl, .void_value, node); }, .add => return simpleBinOp(gz, scope, rl, node, .add), .add_wrap => return simpleBinOp(gz, scope, rl, node, .addwrap), .sub => return simpleBinOp(gz, scope, rl, node, .sub), .sub_wrap => return simpleBinOp(gz, scope, rl, node, .subwrap), .mul => return simpleBinOp(gz, scope, rl, node, .mul), .mul_wrap => return simpleBinOp(gz, scope, rl, node, .mulwrap), .div => return simpleBinOp(gz, scope, rl, node, .div), .mod => return simpleBinOp(gz, scope, rl, node, .mod_rem), .bit_and => return simpleBinOp(gz, scope, rl, node, .bit_and), .bit_or => return simpleBinOp(gz, scope, rl, node, .bit_or), .bit_shift_left => return simpleBinOp(gz, scope, rl, node, .shl), .bit_shift_right => return simpleBinOp(gz, scope, rl, node, .shr), .bit_xor => return simpleBinOp(gz, scope, rl, node, .xor), .bang_equal => return simpleBinOp(gz, scope, rl, node, .cmp_neq), .equal_equal => return simpleBinOp(gz, scope, rl, node, .cmp_eq), .greater_than => return simpleBinOp(gz, scope, rl, node, .cmp_gt), .greater_or_equal => return simpleBinOp(gz, scope, rl, node, .cmp_gte), .less_than => return simpleBinOp(gz, scope, rl, node, .cmp_lt), .less_or_equal => return simpleBinOp(gz, scope, rl, node, .cmp_lte), .array_cat => return simpleBinOp(gz, scope, rl, node, .array_cat), .array_mult => return simpleBinOp(gz, scope, rl, node, .array_mul), .error_union => return simpleBinOp(gz, scope, rl, node, .error_union_type), .merge_error_sets => return simpleBinOp(gz, scope, rl, node, .merge_error_sets), .bool_and => return boolBinOp(gz, scope, rl, node, .bool_br_and), .bool_or => return boolBinOp(gz, scope, rl, node, .bool_br_or), .bool_not => return boolNot(gz, scope, rl, node), .bit_not => return bitNot(gz, scope, rl, node), .negation => return negation(gz, scope, rl, node, .negate), .negation_wrap => return negation(gz, scope, rl, node, .negate_wrap), .identifier => return identifier(gz, scope, rl, node), .asm_simple => return asmExpr(gz, scope, rl, node, tree.asmSimple(node)), .@"asm" => return asmExpr(gz, scope, rl, node, tree.asmFull(node)), .string_literal => return stringLiteral(gz, scope, rl, node), .multiline_string_literal => return multilineStringLiteral(gz, scope, rl, node), .integer_literal => return integerLiteral(gz, scope, rl, node), .builtin_call_two, .builtin_call_two_comma => { if (node_datas[node].lhs == 0) { const params = [_]ast.Node.Index{}; return builtinCall(gz, scope, rl, node, ¶ms); } else if (node_datas[node].rhs == 0) { const params = [_]ast.Node.Index{node_datas[node].lhs}; return builtinCall(gz, scope, rl, node, ¶ms); } else { const params = [_]ast.Node.Index{ node_datas[node].lhs, node_datas[node].rhs }; return builtinCall(gz, scope, rl, node, ¶ms); } }, .builtin_call, .builtin_call_comma => { const params = tree.extra_data[node_datas[node].lhs..node_datas[node].rhs]; return builtinCall(gz, 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(gz, scope, rl, node, tree.callOne(¶ms, node)); }, .call, .call_comma, .async_call, .async_call_comma => { return callExpr(gz, 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(gz, scope, node), .field_access => return fieldAccess(gz, scope, rl, node), .float_literal => return floatLiteral(gz, scope, rl, node), .if_simple => return ifExpr(gz, scope, rl, node, tree.ifSimple(node)), .@"if" => return ifExpr(gz, scope, rl, node, tree.ifFull(node)), .while_simple => return whileExpr(gz, scope, rl, node, tree.whileSimple(node)), .while_cont => return whileExpr(gz, scope, rl, node, tree.whileCont(node)), .@"while" => return whileExpr(gz, scope, rl, node, tree.whileFull(node)), .for_simple => return forExpr(gz, scope, rl, node, tree.forSimple(node)), .@"for" => return forExpr(gz, scope, rl, node, tree.forFull(node)), .slice_open => { const lhs = try expr(gz, scope, .ref, node_datas[node].lhs); const start = try expr(gz, 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(gz, scope, rl, result, node); }, .slice => { const lhs = try expr(gz, scope, .ref, node_datas[node].lhs); const extra = tree.extraData(node_datas[node].rhs, ast.Node.Slice); const start = try expr(gz, scope, .{ .ty = .usize_type }, extra.start); const end = try expr(gz, 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(gz, scope, rl, result, node); }, .slice_sentinel => { const lhs = try expr(gz, scope, .ref, node_datas[node].lhs); const extra = tree.extraData(node_datas[node].rhs, ast.Node.SliceSentinel); const start = try expr(gz, scope, .{ .ty = .usize_type }, extra.start); const end = try expr(gz, scope, .{ .ty = .usize_type }, extra.end); const sentinel = try expr(gz, 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(gz, scope, rl, result, node); }, .deref => { const lhs = try expr(gz, scope, .none, node_datas[node].lhs); switch (rl) { .ref, .none_or_ref => return lhs, else => { const result = try gz.addUnNode(.load, lhs, node); return rvalue(gz, scope, rl, result, node); }, } }, .address_of => { const result = try expr(gz, scope, .ref, node_datas[node].lhs); return rvalue(gz, scope, rl, result, node); }, .undefined_literal => return rvalue(gz, scope, rl, .undef, node), .true_literal => return rvalue(gz, scope, rl, .bool_true, node), .false_literal => return rvalue(gz, scope, rl, .bool_false, node), .null_literal => return rvalue(gz, scope, rl, .null_value, node), .optional_type => { const operand = try typeExpr(gz, scope, node_datas[node].lhs); const result = try gz.addUnNode(.optional_type, operand, node); return rvalue(gz, scope, rl, result, node); }, .unwrap_optional => switch (rl) { .ref => return gz.addUnNode( .optional_payload_safe_ptr, try expr(gz, scope, .ref, node_datas[node].lhs), node, ), else => return rvalue(gz, scope, rl, try gz.addUnNode( .optional_payload_safe, try expr(gz, 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(gz, scope, rl, node, statements[0..0]); } else if (node_datas[node].rhs == 0) { return blockExpr(gz, scope, rl, node, statements[0..1]); } else { return blockExpr(gz, scope, rl, node, statements[0..2]); } }, .block, .block_semicolon => { const statements = tree.extra_data[node_datas[node].lhs..node_datas[node].rhs]; return blockExpr(gz, scope, rl, node, statements); }, .enum_literal => return simpleStrTok(gz, scope, rl, main_tokens[node], node, .enum_literal), .error_value => return simpleStrTok(gz, 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( gz, 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( gz, 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( gz, scope, rl, node, node_datas[node].lhs, .is_null_ptr, .optional_payload_unsafe_ptr, undefined, node_datas[node].rhs, null, ), else => return orelseCatchExpr( gz, scope, rl, node, node_datas[node].lhs, .is_null, .optional_payload_unsafe, undefined, node_datas[node].rhs, null, ), }, .ptr_type_aligned => return ptrType(gz, scope, rl, node, tree.ptrTypeAligned(node)), .ptr_type_sentinel => return ptrType(gz, scope, rl, node, tree.ptrTypeSentinel(node)), .ptr_type => return ptrType(gz, scope, rl, node, tree.ptrType(node)), .ptr_type_bit_range => return ptrType(gz, scope, rl, node, tree.ptrTypeBitRange(node)), .container_decl, .container_decl_trailing, => return containerDecl(gz, scope, rl, node, tree.containerDecl(node)), .container_decl_two, .container_decl_two_trailing => { var buffer: [2]ast.Node.Index = undefined; return containerDecl(gz, scope, rl, node, tree.containerDeclTwo(&buffer, node)); }, .container_decl_arg, .container_decl_arg_trailing, => return containerDecl(gz, scope, rl, node, tree.containerDeclArg(node)), .tagged_union, .tagged_union_trailing, => return containerDecl(gz, scope, rl, node, tree.taggedUnion(node)), .tagged_union_two, .tagged_union_two_trailing => { var buffer: [2]ast.Node.Index = undefined; return containerDecl(gz, scope, rl, node, tree.taggedUnionTwo(&buffer, node)); }, .tagged_union_enum_tag, .tagged_union_enum_tag_trailing, => return containerDecl(gz, scope, rl, node, tree.taggedUnionEnumTag(node)), .@"break" => return breakExpr(gz, scope, node), .@"continue" => return continueExpr(gz, scope, node), .grouped_expression => return expr(gz, scope, rl, node_datas[node].lhs), .array_type => return arrayType(gz, scope, rl, node), .array_type_sentinel => return arrayTypeSentinel(gz, scope, rl, node), .char_literal => return charLiteral(gz, scope, rl, node), .error_set_decl => return errorSetDecl(gz, scope, rl, node), .array_access => return arrayAccess(gz, scope, rl, node), .@"comptime" => return comptimeExpr(gz, scope, rl, node_datas[node].lhs), .@"switch", .switch_comma => return switchExpr(gz, 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 => { var fields: [1]ast.Node.Index = undefined; return structInitExpr(gz, scope, rl, node, tree.structInitOne(&fields, node)); }, .struct_init_dot_two, .struct_init_dot_two_comma => { var fields: [2]ast.Node.Index = undefined; return structInitExpr(gz, scope, rl, node, tree.structInitDotTwo(&fields, node)); }, .struct_init_dot, .struct_init_dot_comma, => return structInitExpr(gz, scope, rl, node, tree.structInitDot(node)), .struct_init, .struct_init_comma, => return structInitExpr(gz, scope, rl, node, tree.structInit(node)), .@"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 structInitExpr( gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index, struct_init: ast.full.StructInit, ) InnerError!zir.Inst.Ref { const tree = gz.tree(); const astgen = gz.astgen; const mod = astgen.mod; const gpa = mod.gpa; if (struct_init.ast.fields.len == 0) { if (struct_init.ast.type_expr == 0) { return rvalue(gz, scope, rl, .empty_struct, node); } else { const ty_inst = try typeExpr(gz, scope, struct_init.ast.type_expr); const result = try gz.addUnNode(.struct_init_empty, ty_inst, node); return rvalue(gz, scope, rl, result, node); } } switch (rl) { .discard => return mod.failNode(scope, node, "TODO implement structInitExpr discard", .{}), .none, .none_or_ref => return mod.failNode(scope, node, "TODO implement structInitExpr none", .{}), .ref => unreachable, // struct literal not valid as l-value .ty => |ty_inst| { const fields_list = try gpa.alloc(zir.Inst.StructInit.Item, struct_init.ast.fields.len); defer gpa.free(fields_list); for (struct_init.ast.fields) |field_init, i| { const name_token = tree.firstToken(field_init) - 2; const str_index = try gz.identAsString(name_token); const field_ty_inst = try gz.addPlNode(.field_type, field_init, zir.Inst.FieldType{ .container_type = ty_inst, .name_start = str_index, }); fields_list[i] = .{ .field_type = astgen.refToIndex(field_ty_inst).?, .init = try expr(gz, scope, .{ .ty = field_ty_inst }, field_init), }; } const init_inst = try gz.addPlNode(.struct_init, node, zir.Inst.StructInit{ .fields_len = @intCast(u32, fields_list.len), }); try astgen.extra.ensureCapacity(gpa, astgen.extra.items.len + fields_list.len * @typeInfo(zir.Inst.StructInit.Item).Struct.fields.len); for (fields_list) |field| { _ = gz.astgen.addExtraAssumeCapacity(field); } return rvalue(gz, scope, rl, init_inst, node); }, .ptr => |ptr_inst| { const field_ptr_list = try gpa.alloc(zir.Inst.Index, struct_init.ast.fields.len); defer gpa.free(field_ptr_list); for (struct_init.ast.fields) |field_init, i| { const name_token = tree.firstToken(field_init) - 2; const str_index = try gz.identAsString(name_token); const field_ptr = try gz.addPlNode(.field_ptr, field_init, zir.Inst.Field{ .lhs = ptr_inst, .field_name_start = str_index, }); field_ptr_list[i] = astgen.refToIndex(field_ptr).?; _ = try expr(gz, scope, .{ .ptr = field_ptr }, field_init); } const validate_inst = try gz.addPlNode(.validate_struct_init_ptr, node, zir.Inst.Block{ .body_len = @intCast(u32, field_ptr_list.len), }); try astgen.extra.appendSlice(gpa, field_ptr_list); return validate_inst; }, .inferred_ptr => |ptr_inst| { return mod.failNode(scope, node, "TODO implement structInitExpr inferred_ptr", .{}); }, .block_ptr => |block_gz| { return mod.failNode(scope, node, "TODO implement structInitExpr block", .{}); }, } } pub fn comptimeExpr( gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index, ) InnerError!zir.Inst.Ref { const prev_force_comptime = gz.force_comptime; gz.force_comptime = true; const result = try expr(gz, scope, rl, node); gz.force_comptime = prev_force_comptime; return result; } fn breakExpr(parent_gz: *GenZir, parent_scope: *Scope, node: ast.Node.Index) InnerError!zir.Inst.Ref { const mod = parent_gz.astgen.mod; 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(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(parent_gz, 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(parent_gz: *GenZir, parent_scope: *Scope, node: ast.Node.Index) InnerError!zir.Inst.Ref { const mod = parent_gz.astgen.mod; 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(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( gz: *GenZir, 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 = gz.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(gz, scope, rl, block_node, statements, .block); } try blockExprStmts(gz, scope, block_node, statements); return rvalue(gz, 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(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( gz: *GenZir, 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 mod = gz.astgen.mod; const tree = gz.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 block_inst = try gz.addBlock(zir_tag, block_node); try gz.instructions.append(mod.gpa, block_inst); var block_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(?GenZir.Label, GenZir.Label{ .token = label_token, .block_inst = block_inst, }), }; block_scope.setBreakResultLoc(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(&block_scope, &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 = rl.strategy(&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(gz, parent_scope, rl, block_ref, block_node), } }, } } fn blockExprStmts( gz: *GenZir, parent_scope: *Scope, node: ast.Node.Index, statements: []const ast.Node.Index, ) !void { const tree = gz.tree(); const main_tokens = tree.nodes.items(.main_token); const node_tags = tree.nodes.items(.tag); var block_arena = std.heap.ArenaAllocator.init(gz.astgen.mod.gpa); defer block_arena.deinit(); var scope = parent_scope; for (statements) |statement| { if (!gz.force_comptime) { _ = try gz.addNode(.dbg_stmt_node, statement); } switch (node_tags[statement]) { .global_var_decl => scope = try varDecl(gz, scope, statement, &block_arena.allocator, tree.globalVarDecl(statement)), .local_var_decl => scope = try varDecl(gz, scope, statement, &block_arena.allocator, tree.localVarDecl(statement)), .simple_var_decl => scope = try varDecl(gz, scope, statement, &block_arena.allocator, tree.simpleVarDecl(statement)), .aligned_var_decl => scope = try varDecl(gz, scope, statement, &block_arena.allocator, tree.alignedVarDecl(statement)), .assign => try assign(gz, scope, statement), .assign_bit_and => try assignOp(gz, scope, statement, .bit_and), .assign_bit_or => try assignOp(gz, scope, statement, .bit_or), .assign_bit_shift_left => try assignOp(gz, scope, statement, .shl), .assign_bit_shift_right => try assignOp(gz, scope, statement, .shr), .assign_bit_xor => try assignOp(gz, scope, statement, .xor), .assign_div => try assignOp(gz, scope, statement, .div), .assign_sub => try assignOp(gz, scope, statement, .sub), .assign_sub_wrap => try assignOp(gz, scope, statement, .subwrap), .assign_mod => try assignOp(gz, scope, statement, .mod_rem), .assign_add => try assignOp(gz, scope, statement, .add), .assign_add_wrap => try assignOp(gz, scope, statement, .addwrap), .assign_mul => try assignOp(gz, scope, statement, .mul), .assign_mul_wrap => try assignOp(gz, scope, statement, .mulwrap), else => { // We need to emit an error if the result is not `noreturn` or `void`, but // we want to avoid adding the ZIR instruction if possible for performance. const maybe_unused_result = try expr(gz, scope, .none, statement); const elide_check = if (gz.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]) { // 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_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, .has_decl, .int, .float, .float128, .intcast, .int_type, .is_non_null, .is_null, .is_non_null_ptr, .is_null_ptr, .is_err, .is_err_ptr, .mod_rem, .mul, .mulwrap, .param_type, .ptrtoint, .ref, .ret_ptr, .ret_type, .shl, .shr, .str, .sub, .subwrap, .negate, .negate_wrap, .typeof, .typeof_elem, .xor, .optional_type, .optional_type_from_ptr_elem, .optional_payload_safe, .optional_payload_unsafe, .optional_payload_safe_ptr, .optional_payload_unsafe_ptr, .err_union_payload_safe, .err_union_payload_unsafe, .err_union_payload_safe_ptr, .err_union_payload_unsafe_ptr, .err_union_code, .err_union_code_ptr, .ptr_type, .ptr_type_simple, .enum_literal, .enum_literal_small, .merge_error_sets, .error_union_type, .bit_not, .error_value, .error_to_int, .int_to_error, .slice_start, .slice_end, .slice_sentinel, .import, .typeof_peer, .switch_block, .switch_block_multi, .switch_block_else, .switch_block_else_multi, .switch_block_under, .switch_block_under_multi, .switch_block_ref, .switch_block_ref_multi, .switch_block_ref_else, .switch_block_ref_else_multi, .switch_block_ref_under, .switch_block_ref_under_multi, .switch_capture, .switch_capture_ref, .switch_capture_multi, .switch_capture_multi_ref, .switch_capture_else, .switch_capture_else_ref, .struct_init_empty, .struct_init, .field_type, .struct_decl, .struct_decl_packed, .struct_decl_extern, .union_decl, .enum_decl, .enum_decl_nonexhaustive, .opaque_decl, .int_to_enum, .enum_to_int, .type_info, => break :b false, // ZIR instructions that are always either `noreturn` or `void`. .breakpoint, .dbg_stmt_node, .ensure_result_used, .ensure_result_non_error, .@"export", .set_eval_branch_quota, .compile_log, .ensure_err_payload_void, .@"break", .break_inline, .condbr, .condbr_inline, .compile_error, .ret_node, .ret_tok, .ret_coerce, .@"unreachable", .elided, .store, .store_node, .store_to_block_ptr, .store_to_inferred_ptr, .resolve_inferred_alloc, .repeat, .repeat_inline, .validate_struct_init_ptr, => 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( gz: *GenZir, scope: *Scope, node: ast.Node.Index, block_arena: *Allocator, var_decl: ast.full.VarDecl, ) InnerError!*Scope { const mod = gz.astgen.mod; 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 astgen = gz.astgen; const tree = gz.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(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(tree, var_decl.ast.init_node)) { const result_loc: ResultLoc = if (var_decl.ast.type_node != 0) .{ .ty = try typeExpr(gz, scope, var_decl.ast.type_node), } else .none; const init_inst = try expr(gz, 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: 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(gz, &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); init_scope.rl_ty_inst = type_inst; } else { const alloc = try init_scope.addNode(.alloc_inferred, node); resolve_inferred_alloc = alloc; init_scope.rl_ptr = alloc; } const init_result_loc: ResultLoc = .{ .block_ptr = &init_scope }; const init_inst = try expr(&init_scope, &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 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; } // 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(gz, 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.addNode(.alloc_inferred_mut, node); resolve_inferred_alloc = alloc; break :a .{ .alloc = alloc, .result_loc = .{ .inferred_ptr = alloc } }; }; const init_inst = try expr(gz, 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(gz: *GenZir, scope: *Scope, infix_node: ast.Node.Index) InnerError!void { const tree = gz.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(gz, scope, .discard, rhs); return; } } const lvalue = try lvalExpr(gz, scope, lhs); _ = try expr(gz, scope, .{ .ptr = lvalue }, rhs); } fn assignOp( gz: *GenZir, scope: *Scope, infix_node: ast.Node.Index, op_inst_tag: zir.Inst.Tag, ) InnerError!void { const tree = gz.tree(); const node_datas = tree.nodes.items(.data); const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs); const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node); const lhs_type = try gz.addUnNode(.typeof, lhs, infix_node); const rhs = try expr(gz, 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(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) InnerError!zir.Inst.Ref { const tree = gz.tree(); const node_datas = tree.nodes.items(.data); const operand = try expr(gz, scope, .{ .ty = .bool_type }, node_datas[node].lhs); const result = try gz.addUnNode(.bool_not, operand, node); return rvalue(gz, scope, rl, result, node); } fn bitNot(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) InnerError!zir.Inst.Ref { const tree = gz.tree(); const node_datas = tree.nodes.items(.data); const operand = try expr(gz, scope, .none, node_datas[node].lhs); const result = try gz.addUnNode(.bit_not, operand, node); return rvalue(gz, scope, rl, result, node); } fn negation( gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index, tag: zir.Inst.Tag, ) InnerError!zir.Inst.Ref { const tree = gz.tree(); const node_datas = tree.nodes.items(.data); const operand = try expr(gz, scope, .none, node_datas[node].lhs); const result = try gz.addUnNode(tag, operand, node); return rvalue(gz, scope, rl, result, node); } fn ptrType( gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index, ptr_info: ast.full.PtrType, ) InnerError!zir.Inst.Ref { const tree = gz.tree(); const elem_type = try typeExpr(gz, 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(gz, 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(gz, scope, .{ .ty = elem_type }, ptr_info.ast.sentinel); trailing_count += 1; } if (ptr_info.ast.align_node != 0) { align_ref = try expr(gz, 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(gz, scope, .none, ptr_info.ast.bit_range_start); bit_end_ref = try expr(gz, 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(gz, scope, rl, result, node); } fn arrayType(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) !zir.Inst.Ref { const tree = gz.tree(); const node_datas = tree.nodes.items(.data); // TODO check for [_]T const len = try expr(gz, scope, .{ .ty = .usize_type }, node_datas[node].lhs); const elem_type = try typeExpr(gz, scope, node_datas[node].rhs); const result = try gz.addBin(.array_type, len, elem_type); return rvalue(gz, scope, rl, result, node); } fn arrayTypeSentinel(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) !zir.Inst.Ref { const tree = gz.tree(); const node_datas = tree.nodes.items(.data); const extra = tree.extraData(node_datas[node].rhs, ast.Node.ArrayTypeSentinel); // TODO check for [_]T const len = try expr(gz, scope, .{ .ty = .usize_type }, node_datas[node].lhs); const elem_type = try typeExpr(gz, scope, extra.elem_type); const sentinel = try expr(gz, scope, .{ .ty = elem_type }, extra.sentinel); const result = try gz.addArrayTypeSentinel(len, elem_type, sentinel); return rvalue(gz, scope, rl, result, node); } fn containerDecl( gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index, container_decl: ast.full.ContainerDecl, ) InnerError!zir.Inst.Ref { const astgen = gz.astgen; const mod = astgen.mod; const gpa = mod.gpa; const tree = gz.tree(); const token_tags = tree.tokens.items(.tag); const node_tags = tree.nodes.items(.tag); // We must not create any types until Sema. Here the goal is only to generate // ZIR for all the field types, alignments, and default value expressions. const arg_inst: zir.Inst.Ref = if (container_decl.ast.arg != 0) try comptimeExpr(gz, scope, .none, container_decl.ast.arg) else .none; switch (token_tags[container_decl.ast.main_token]) { .keyword_struct => { const tag = if (container_decl.layout_token) |t| switch (token_tags[t]) { .keyword_packed => zir.Inst.Tag.struct_decl_packed, .keyword_extern => zir.Inst.Tag.struct_decl_extern, else => unreachable, } else zir.Inst.Tag.struct_decl; if (container_decl.ast.members.len == 0) { const result = try gz.addPlNode(tag, node, zir.Inst.StructDecl{ .fields_len = 0, }); return rvalue(gz, scope, rl, result, node); } assert(arg_inst == .none); var fields_data = ArrayListUnmanaged(u32){}; defer fields_data.deinit(gpa); // field_name and field_type are both mandatory try fields_data.ensureCapacity(gpa, container_decl.ast.members.len * 2); // We only need this if there are greater than 16 fields. var bit_bag = ArrayListUnmanaged(u32){}; defer bit_bag.deinit(gpa); var cur_bit_bag: u32 = 0; var field_index: usize = 0; for (container_decl.ast.members) |member_node| { const member = switch (node_tags[member_node]) { .container_field_init => tree.containerFieldInit(member_node), .container_field_align => tree.containerFieldAlign(member_node), .container_field => tree.containerField(member_node), else => continue, }; if (field_index % 16 == 0 and field_index != 0) { try bit_bag.append(gpa, cur_bit_bag); cur_bit_bag = 0; } if (member.comptime_token) |comptime_token| { return mod.failTok(scope, comptime_token, "TODO implement comptime struct fields", .{}); } try fields_data.ensureCapacity(gpa, fields_data.items.len + 4); const field_name = try gz.identAsString(member.ast.name_token); fields_data.appendAssumeCapacity(field_name); const field_type = try typeExpr(gz, scope, member.ast.type_expr); fields_data.appendAssumeCapacity(@enumToInt(field_type)); const have_align = member.ast.align_expr != 0; const have_value = member.ast.value_expr != 0; cur_bit_bag = (cur_bit_bag >> 2) | (@as(u32, @boolToInt(have_align)) << 30) | (@as(u32, @boolToInt(have_value)) << 31); if (have_align) { const align_inst = try comptimeExpr(gz, scope, .{ .ty = .u32_type }, member.ast.align_expr); fields_data.appendAssumeCapacity(@enumToInt(align_inst)); } if (have_value) { const default_inst = try comptimeExpr(gz, scope, .{ .ty = field_type }, member.ast.value_expr); fields_data.appendAssumeCapacity(@enumToInt(default_inst)); } field_index += 1; } const empty_slot_count = 16 - (field_index % 16); cur_bit_bag >>= @intCast(u5, empty_slot_count * 2); const result = try gz.addPlNode(tag, node, zir.Inst.StructDecl{ .fields_len = @intCast(u32, container_decl.ast.members.len), }); try astgen.extra.ensureCapacity(gpa, astgen.extra.items.len + bit_bag.items.len + 1 + fields_data.items.len); astgen.extra.appendSliceAssumeCapacity(bit_bag.items); // Likely empty. astgen.extra.appendAssumeCapacity(cur_bit_bag); astgen.extra.appendSliceAssumeCapacity(fields_data.items); return rvalue(gz, scope, rl, result, node); }, .keyword_union => { return mod.failTok(scope, container_decl.ast.main_token, "TODO AstGen for union decl", .{}); }, .keyword_enum => { if (container_decl.layout_token) |t| { return mod.failTok(scope, t, "enums do not support 'packed' or 'extern'; instead provide an explicit integer tag type", .{}); } // Count total fields as well as how many have explicitly provided tag values. const counts = blk: { var values: usize = 0; var total_fields: usize = 0; var decls: usize = 0; var nonexhaustive_node: ast.Node.Index = 0; for (container_decl.ast.members) |member_node| { const member = switch (node_tags[member_node]) { .container_field_init => tree.containerFieldInit(member_node), .container_field_align => tree.containerFieldAlign(member_node), .container_field => tree.containerField(member_node), else => { decls += 1; continue; }, }; if (member.comptime_token) |comptime_token| { return mod.failTok(scope, comptime_token, "enum fields cannot be marked comptime", .{}); } if (member.ast.type_expr != 0) { return mod.failNode(scope, member.ast.type_expr, "enum fields do not have types", .{}); } // Alignment expressions in enums are caught by the parser. assert(member.ast.align_expr == 0); const name_token = member.ast.name_token; if (mem.eql(u8, tree.tokenSlice(name_token), "_")) { if (nonexhaustive_node != 0) { const msg = msg: { const msg = try mod.errMsg( scope, gz.nodeSrcLoc(member_node), "redundant non-exhaustive enum mark", .{}, ); errdefer msg.destroy(gpa); const other_src = gz.nodeSrcLoc(nonexhaustive_node); try mod.errNote(scope, other_src, msg, "other mark here", .{}); break :msg msg; }; return mod.failWithOwnedErrorMsg(scope, msg); } nonexhaustive_node = member_node; if (member.ast.value_expr != 0) { return mod.failNode(scope, member.ast.value_expr, "'_' is used to mark an enum as non-exhaustive and cannot be assigned a value", .{}); } continue; } total_fields += 1; if (member.ast.value_expr != 0) { values += 1; } } break :blk .{ .total_fields = total_fields, .values = values, .decls = decls, .nonexhaustive_node = nonexhaustive_node, }; }; if (counts.total_fields == 0) { // One can construct an enum with no tags, and it functions the same as `noreturn`. But // this is only useful for generic code; when explicitly using `enum {}` syntax, there // must be at least one tag. return mod.failNode(scope, node, "enum declarations must have at least one tag", .{}); } if (counts.nonexhaustive_node != 0 and arg_inst == .none) { const msg = msg: { const msg = try mod.errMsg( scope, gz.nodeSrcLoc(node), "non-exhaustive enum missing integer tag type", .{}, ); errdefer msg.destroy(gpa); const other_src = gz.nodeSrcLoc(counts.nonexhaustive_node); try mod.errNote(scope, other_src, msg, "marked non-exhaustive here", .{}); break :msg msg; }; return mod.failWithOwnedErrorMsg(scope, msg); } if (counts.values == 0 and counts.decls == 0 and arg_inst == .none) { // No explicitly provided tag values and no top level declarations! In this case, // we can construct the enum type in AstGen and it will be correctly shared by all // generic function instantiations and comptime function calls. var new_decl_arena = std.heap.ArenaAllocator.init(gpa); errdefer new_decl_arena.deinit(); const arena = &new_decl_arena.allocator; var fields_map: std.StringArrayHashMapUnmanaged(void) = .{}; try fields_map.ensureCapacity(arena, counts.total_fields); for (container_decl.ast.members) |member_node| { if (member_node == counts.nonexhaustive_node) continue; const member = switch (node_tags[member_node]) { .container_field_init => tree.containerFieldInit(member_node), .container_field_align => tree.containerFieldAlign(member_node), .container_field => tree.containerField(member_node), else => unreachable, // We checked earlier. }; const name_token = member.ast.name_token; const tag_name = try mod.identifierTokenStringTreeArena( scope, name_token, tree, arena, ); const gop = fields_map.getOrPutAssumeCapacity(tag_name); if (gop.found_existing) { const msg = msg: { const msg = try mod.errMsg( scope, gz.tokSrcLoc(name_token), "duplicate enum tag", .{}, ); errdefer msg.destroy(gpa); // Iterate to find the other tag. We don't eagerly store it in a hash // map because in the hot path there will be no compile error and we // don't need to waste time with a hash map. const bad_node = for (container_decl.ast.members) |other_member_node| { const other_member = switch (node_tags[other_member_node]) { .container_field_init => tree.containerFieldInit(other_member_node), .container_field_align => tree.containerFieldAlign(other_member_node), .container_field => tree.containerField(other_member_node), else => unreachable, // We checked earlier. }; const other_tag_name = try mod.identifierTokenStringTreeArena( scope, other_member.ast.name_token, tree, arena, ); if (mem.eql(u8, tag_name, other_tag_name)) break other_member_node; } else unreachable; const other_src = gz.nodeSrcLoc(bad_node); try mod.errNote(scope, other_src, msg, "other tag here", .{}); break :msg msg; }; return mod.failWithOwnedErrorMsg(scope, msg); } } const enum_simple = try arena.create(Module.EnumSimple); enum_simple.* = .{ .owner_decl = astgen.decl, .node_offset = astgen.decl.nodeIndexToRelative(node), .fields = fields_map, }; const enum_ty = try Type.Tag.enum_simple.create(arena, enum_simple); const enum_val = try Value.Tag.ty.create(arena, enum_ty); const new_decl = try mod.createAnonymousDecl(scope, &new_decl_arena, .{ .ty = Type.initTag(.type), .val = enum_val, }); const decl_index = try mod.declareDeclDependency(astgen.decl, new_decl); const result = try gz.addDecl(.decl_val, decl_index, node); return rvalue(gz, scope, rl, result, node); } // In this case we must generate ZIR code for the tag values, similar to // how structs are handled above. The new anonymous Decl will be created in // Sema, not AstGen. return mod.failNode(scope, node, "TODO AstGen for enum decl with decls or explicitly provided field values", .{}); }, .keyword_opaque => { const result = try gz.addNode(.opaque_decl, node); return rvalue(gz, scope, rl, result, node); }, else => unreachable, } } fn errorSetDecl( gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index, ) InnerError!zir.Inst.Ref { const astgen = gz.astgen; const mod = astgen.mod; 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 gpa = mod.gpa; var new_decl_arena = std.heap.ArenaAllocator.init(gpa); errdefer new_decl_arena.deinit(); const arena = &new_decl_arena.allocator; const fields = try 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.identifierTokenStringTreeArena(scope, tok_i, tree, arena); field_i += 1; }, .r_brace => break, else => unreachable, } } } const error_set = try arena.create(Module.ErrorSet); error_set.* = .{ .owner_decl = astgen.decl, .node_offset = astgen.decl.nodeIndexToRelative(node), .names_ptr = fields.ptr, .names_len = @intCast(u32, fields.len), }; const error_set_ty = try Type.Tag.error_set.create(arena, error_set); const error_set_val = try Value.Tag.ty.create(arena, error_set_ty); const new_decl = try mod.createAnonymousDecl(scope, &new_decl_arena, .{ .ty = Type.initTag(.type), .val = error_set_val, }); const decl_index = try mod.declareDeclDependency(astgen.decl, new_decl); const result = try gz.addDecl(.decl_val, decl_index, node); return rvalue(gz, scope, rl, result, node); } fn orelseCatchExpr( parent_gz: *GenZir, 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 mod = parent_gz.astgen.mod; const tree = parent_gz.tree(); var block_scope: GenZir = .{ .parent = scope, .astgen = parent_gz.astgen, .force_comptime = parent_gz.force_comptime, .instructions = .{}, }; block_scope.setBreakResultLoc(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, .none_or_ref, .block_ptr, .inferred_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(&block_scope, &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: 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(&then_scope, 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: 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(&else_scope, &else_scope.base, block_scope.break_result_loc, unwrapped_payload, node), }; return finishThenElseBlock( parent_gz, scope, rl, node, &block_scope, &then_scope, &else_scope, condbr, cond, node, node, then_result, else_result, block, block, .@"break", ); } fn finishThenElseBlock( parent_gz: *GenZir, parent_scope: *Scope, rl: ResultLoc, node: ast.Node.Index, block_scope: *GenZir, then_scope: *GenZir, else_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 = rl.strategy(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(parent_gz, 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( gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index, ) InnerError!zir.Inst.Ref { const astgen = gz.astgen; const mod = astgen.mod; 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 str_index = try gz.identAsString(field_ident); switch (rl) { .ref => return gz.addPlNode(.field_ptr, node, zir.Inst.Field{ .lhs = try expr(gz, scope, .ref, object_node), .field_name_start = str_index, }), else => return rvalue(gz, scope, rl, try gz.addPlNode(.field_val, node, zir.Inst.Field{ .lhs = try expr(gz, scope, .none_or_ref, object_node), .field_name_start = str_index, }), node), } } fn arrayAccess( gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index, ) InnerError!zir.Inst.Ref { 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(gz, scope, .ref, node_datas[node].lhs), try expr(gz, scope, .{ .ty = .usize_type }, node_datas[node].rhs), ), else => return rvalue(gz, scope, rl, try gz.addBin( .elem_val, try expr(gz, scope, .none_or_ref, node_datas[node].lhs), try expr(gz, scope, .{ .ty = .usize_type }, node_datas[node].rhs), ), node), } } fn simpleBinOp( gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index, op_inst_tag: zir.Inst.Tag, ) InnerError!zir.Inst.Ref { 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(gz, scope, .none, node_datas[node].lhs), .rhs = try expr(gz, scope, .none, node_datas[node].rhs), }); return rvalue(gz, scope, rl, result, node); } fn simpleStrTok( gz: *GenZir, scope: *Scope, rl: ResultLoc, ident_token: ast.TokenIndex, node: ast.Node.Index, op_inst_tag: zir.Inst.Tag, ) InnerError!zir.Inst.Ref { const str_index = try gz.identAsString(ident_token); const result = try gz.addStrTok(op_inst_tag, str_index, ident_token); return rvalue(gz, scope, rl, result, node); } fn boolBinOp( gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index, zir_tag: zir.Inst.Tag, ) InnerError!zir.Inst.Ref { const node_datas = gz.tree().nodes.items(.data); const lhs = try expr(gz, scope, .{ .ty = .bool_type }, node_datas[node].lhs); const bool_br = try gz.addBoolBr(zir_tag, lhs); var rhs_scope: GenZir = .{ .parent = scope, .astgen = gz.astgen, .force_comptime = gz.force_comptime, }; defer rhs_scope.instructions.deinit(gz.astgen.mod.gpa); const rhs = try expr(&rhs_scope, &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(gz, scope, rl, block_ref, node); } fn ifExpr( parent_gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index, if_full: ast.full.If, ) InnerError!zir.Inst.Ref { const mod = parent_gz.astgen.mod; var block_scope: GenZir = .{ .parent = scope, .astgen = parent_gz.astgen, .force_comptime = parent_gz.force_comptime, .instructions = .{}, }; block_scope.setBreakResultLoc(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(&block_scope, &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: 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(&then_scope, 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: 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(&else_scope, sub_scope, block_scope.break_result_loc, else_node), }; } else .{ .src = if_full.ast.then_expr, .result = .none, }; return finishThenElseBlock( parent_gz, 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: *GenZir, else_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: *GenZir, else_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 ([_]*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( parent_gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index, while_full: ast.full.While, ) InnerError!zir.Inst.Ref { const mod = parent_gz.astgen.mod; if (while_full.label_token) |label_token| { try checkLabelRedefinition(mod, scope, label_token); } 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: GenZir = .{ .parent = scope, .astgen = parent_gz.astgen, .force_comptime = parent_gz.force_comptime, .instructions = .{}, }; loop_scope.setBreakResultLoc(rl); defer loop_scope.instructions.deinit(mod.gpa); var continue_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(&continue_scope, &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(&loop_scope, &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(?GenZir.Label, GenZir.Label{ .token = label_token, .block_inst = loop_block, }); } var then_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(&then_scope, then_sub_scope, loop_scope.break_result_loc, while_full.ast.then_expr); var else_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(&else_scope, 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( parent_gz, 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( parent_gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index, for_full: ast.full.While, ) InnerError!zir.Inst.Ref { const mod = parent_gz.astgen.mod; if (for_full.label_token) |label_token| { try checkLabelRedefinition(mod, scope, label_token); } // Set up variables and constants. 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(parent_gz, 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: GenZir = .{ .parent = scope, .astgen = parent_gz.astgen, .force_comptime = parent_gz.force_comptime, .instructions = .{}, }; loop_scope.setBreakResultLoc(rl); defer loop_scope.instructions.deinit(mod.gpa); var cond_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(?GenZir.Label, GenZir.Label{ .token = label_token, .block_inst = loop_block, }); } var then_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(&then_scope, then_sub_scope, loop_scope.break_result_loc, for_full.ast.then_expr); var else_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(&else_scope, 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( parent_gz, 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, node: ast.Node.Index, ) ?ast.Node.Index { switch (node_tags[node]) { .switch_range => return node, .grouped_expression => unreachable, else => return null, } } pub const SwitchProngSrc = union(enum) { scalar: u32, multi: Multi, range: Multi, pub const Multi = struct { prong: u32, item: u32, }; pub const RangeExpand = enum { none, first, last }; /// This function is intended to be called only when it is certain that we need /// the LazySrcLoc in order to emit a compile error. pub fn resolve( prong_src: SwitchProngSrc, decl: *Decl, switch_node_offset: i32, range_expand: RangeExpand, ) LazySrcLoc { @setCold(true); const switch_node = decl.relativeToNodeIndex(switch_node_offset); const tree = decl.container.file_scope.base.tree(); const main_tokens = tree.nodes.items(.main_token); const node_datas = tree.nodes.items(.data); const node_tags = tree.nodes.items(.tag); const extra = tree.extraData(node_datas[switch_node].rhs, ast.Node.SubRange); const case_nodes = tree.extra_data[extra.start..extra.end]; var multi_i: u32 = 0; var scalar_i: u32 = 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, }; if (case.ast.values.len == 0) continue; 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]]), "_")) { continue; } const is_multi = case.ast.values.len != 1 or getRangeNode(node_tags, node_datas, case.ast.values[0]) != null; switch (prong_src) { .scalar => |i| if (!is_multi and i == scalar_i) return LazySrcLoc{ .node_offset = decl.nodeIndexToRelative(case.ast.values[0]), }, .multi => |s| if (is_multi and s.prong == multi_i) { var item_i: u32 = 0; for (case.ast.values) |item_node| { if (getRangeNode(node_tags, node_datas, item_node) != null) continue; if (item_i == s.item) return LazySrcLoc{ .node_offset = decl.nodeIndexToRelative(item_node), }; item_i += 1; } else unreachable; }, .range => |s| if (is_multi and s.prong == multi_i) { var range_i: u32 = 0; for (case.ast.values) |item_node| { const range = getRangeNode(node_tags, node_datas, item_node) orelse continue; if (range_i == s.item) switch (range_expand) { .none => return LazySrcLoc{ .node_offset = decl.nodeIndexToRelative(item_node), }, .first => return LazySrcLoc{ .node_offset = decl.nodeIndexToRelative(node_datas[range].lhs), }, .last => return LazySrcLoc{ .node_offset = decl.nodeIndexToRelative(node_datas[range].rhs), }, }; range_i += 1; } else unreachable; }, } if (is_multi) { multi_i += 1; } else { scalar_i += 1; } } else unreachable; } }; fn switchExpr( parent_gz: *GenZir, scope: *Scope, rl: ResultLoc, switch_node: ast.Node.Index, ) InnerError!zir.Inst.Ref { const astgen = parent_gz.astgen; const mod = astgen.mod; const gpa = mod.gpa; const tree = parent_gz.tree(); const node_datas = tree.nodes.items(.data); const node_tags = tree.nodes.items(.tag); const main_tokens = tree.nodes.items(.main_token); const token_tags = tree.tokens.items(.tag); const operand_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]; // We perform two passes over the AST. This first pass is to collect information // for the following variables, make note of the special prong AST node index, // and bail out with a compile error if there are multiple special prongs present. var any_payload_is_ref = false; var scalar_cases_len: u32 = 0; var multi_cases_len: u32 = 0; var special_prong: zir.SpecialProng = .none; var special_node: ast.Node.Index = 0; var else_src: ?LazySrcLoc = null; var underscore_src: ?LazySrcLoc = null; 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. if (case.ast.values.len == 0) { const case_src = parent_gz.tokSrcLoc(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(gpa); try mod.errNote(scope, src, msg, "previous else prong is here", .{}); break :msg msg; }; return mod.failWithOwnedErrorMsg(scope, msg); } else if (underscore_src) |some_underscore| { const msg = msg: { const msg = try mod.errMsg( scope, parent_gz.nodeSrcLoc(switch_node), "else and '_' prong in switch expression", .{}, ); errdefer msg.destroy(gpa); try mod.errNote(scope, case_src, msg, "else prong is here", .{}); try mod.errNote(scope, some_underscore, msg, "'_' prong is here", .{}); break :msg msg; }; return mod.failWithOwnedErrorMsg(scope, msg); } special_node = case_node; special_prong = .@"else"; 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 = parent_gz.tokSrcLoc(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(gpa); try mod.errNote(scope, src, msg, "previous '_' prong is here", .{}); break :msg msg; }; return mod.failWithOwnedErrorMsg(scope, msg); } else if (else_src) |some_else| { const msg = msg: { const msg = try mod.errMsg( scope, parent_gz.nodeSrcLoc(switch_node), "else and '_' prong in switch expression", .{}, ); errdefer msg.destroy(gpa); try mod.errNote(scope, some_else, msg, "else prong is here", .{}); try mod.errNote(scope, case_src, msg, "'_' prong is here", .{}); break :msg msg; }; return mod.failWithOwnedErrorMsg(scope, msg); } special_node = case_node; special_prong = .under; underscore_src = case_src; continue; } if (case.ast.values.len == 1 and getRangeNode(node_tags, node_datas, case.ast.values[0]) == null) { scalar_cases_len += 1; } else { multi_cases_len += 1; } } const operand_rl: ResultLoc = if (any_payload_is_ref) .ref else .none; const operand = try expr(parent_gz, scope, operand_rl, operand_node); // We need the type of the operand to use as the result location for all the prong items. const typeof_tag: zir.Inst.Tag = if (any_payload_is_ref) .typeof_elem else .typeof; const operand_ty_inst = try parent_gz.addUnNode(typeof_tag, operand, operand_node); const item_rl: ResultLoc = .{ .ty = operand_ty_inst }; // Contains the data that goes into the `extra` array for the SwitchBlock/SwitchBlockMulti. // This is the header as well as the optional else prong body, as well as all the // scalar cases. // At the end we will memcpy this into place. var scalar_cases_payload = ArrayListUnmanaged(u32){}; defer scalar_cases_payload.deinit(gpa); // Same deal, but this is only the `extra` data for the multi cases. var multi_cases_payload = ArrayListUnmanaged(u32){}; defer multi_cases_payload.deinit(gpa); var block_scope: GenZir = .{ .parent = scope, .astgen = astgen, .force_comptime = parent_gz.force_comptime, .instructions = .{}, }; block_scope.setBreakResultLoc(rl); defer block_scope.instructions.deinit(gpa); // This gets added to the parent block later, after the item expressions. const switch_block = try parent_gz.addBlock(undefined, switch_node); // We re-use this same scope for all cases, including the special prong, if any. var case_scope: GenZir = .{ .parent = &block_scope.base, .astgen = astgen, .force_comptime = parent_gz.force_comptime, .instructions = .{}, }; defer case_scope.instructions.deinit(gpa); // Do the else/`_` first because it goes first in the payload. var capture_val_scope: Scope.LocalVal = undefined; if (special_node != 0) { const case = switch (node_tags[special_node]) { .switch_case_one => tree.switchCaseOne(special_node), .switch_case => tree.switchCase(special_node), else => unreachable, }; const sub_scope = blk: { const payload_token = case.payload_token orelse break :blk &case_scope.base; const ident = if (token_tags[payload_token] == .asterisk) payload_token + 1 else payload_token; const is_ptr = ident != payload_token; if (mem.eql(u8, tree.tokenSlice(ident), "_")) { if (is_ptr) { return mod.failTok(&case_scope.base, payload_token, "pointer modifier invalid on discard", .{}); } break :blk &case_scope.base; } const capture_tag: zir.Inst.Tag = if (is_ptr) .switch_capture_else_ref else .switch_capture_else; const capture = try case_scope.add(.{ .tag = capture_tag, .data = .{ .switch_capture = .{ .switch_inst = switch_block, .prong_index = undefined, } }, }); const capture_name = try mod.identifierTokenString(&parent_gz.base, payload_token); capture_val_scope = .{ .parent = &case_scope.base, .gen_zir = &case_scope, .name = capture_name, .inst = capture, .src = parent_gz.tokSrcLoc(payload_token), }; break :blk &capture_val_scope.base; }; const case_result = try expr(&case_scope, sub_scope, block_scope.break_result_loc, case.ast.target_expr); if (!astgen.refIsNoReturn(case_result)) { block_scope.break_count += 1; _ = try case_scope.addBreak(.@"break", switch_block, case_result); } // Documentation for this: `zir.Inst.SwitchBlock` and `zir.Inst.SwitchBlockMulti`. try scalar_cases_payload.ensureCapacity(gpa, scalar_cases_payload.items.len + 3 + // operand, scalar_cases_len, else body len @boolToInt(multi_cases_len != 0) + case_scope.instructions.items.len); scalar_cases_payload.appendAssumeCapacity(@enumToInt(operand)); scalar_cases_payload.appendAssumeCapacity(scalar_cases_len); if (multi_cases_len != 0) { scalar_cases_payload.appendAssumeCapacity(multi_cases_len); } scalar_cases_payload.appendAssumeCapacity(@intCast(u32, case_scope.instructions.items.len)); scalar_cases_payload.appendSliceAssumeCapacity(case_scope.instructions.items); } else { // Documentation for this: `zir.Inst.SwitchBlock` and `zir.Inst.SwitchBlockMulti`. try scalar_cases_payload.ensureCapacity(gpa, scalar_cases_payload.items.len + 2 + // operand, scalar_cases_len @boolToInt(multi_cases_len != 0)); scalar_cases_payload.appendAssumeCapacity(@enumToInt(operand)); scalar_cases_payload.appendAssumeCapacity(scalar_cases_len); if (multi_cases_len != 0) { scalar_cases_payload.appendAssumeCapacity(multi_cases_len); } } // In this pass we generate all the item and prong expressions except the special case. var multi_case_index: u32 = 0; var scalar_case_index: u32 = 0; for (case_nodes) |case_node| { if (case_node == special_node) continue; const case = switch (node_tags[case_node]) { .switch_case_one => tree.switchCaseOne(case_node), .switch_case => tree.switchCase(case_node), else => unreachable, }; // Reset the scope. case_scope.instructions.shrinkRetainingCapacity(0); const is_multi_case = case.ast.values.len != 1 or getRangeNode(node_tags, node_datas, case.ast.values[0]) != null; const sub_scope = blk: { const payload_token = case.payload_token orelse break :blk &case_scope.base; const ident = if (token_tags[payload_token] == .asterisk) payload_token + 1 else payload_token; const is_ptr = ident != payload_token; if (mem.eql(u8, tree.tokenSlice(ident), "_")) { if (is_ptr) { return mod.failTok(&case_scope.base, payload_token, "pointer modifier invalid on discard", .{}); } break :blk &case_scope.base; } const is_multi_case_bits: u2 = @boolToInt(is_multi_case); const is_ptr_bits: u2 = @boolToInt(is_ptr); const capture_tag: zir.Inst.Tag = switch ((is_multi_case_bits << 1) | is_ptr_bits) { 0b00 => .switch_capture, 0b01 => .switch_capture_ref, 0b10 => .switch_capture_multi, 0b11 => .switch_capture_multi_ref, }; const capture_index = if (is_multi_case) ci: { multi_case_index += 1; break :ci multi_case_index - 1; } else ci: { scalar_case_index += 1; break :ci scalar_case_index - 1; }; const capture = try case_scope.add(.{ .tag = capture_tag, .data = .{ .switch_capture = .{ .switch_inst = switch_block, .prong_index = capture_index, } }, }); const capture_name = try mod.identifierTokenString(&parent_gz.base, payload_token); capture_val_scope = .{ .parent = &case_scope.base, .gen_zir = &case_scope, .name = capture_name, .inst = capture, .src = parent_gz.tokSrcLoc(payload_token), }; break :blk &capture_val_scope.base; }; if (is_multi_case) { // items_len, ranges_len, body_len const header_index = multi_cases_payload.items.len; try multi_cases_payload.resize(gpa, multi_cases_payload.items.len + 3); // items var items_len: u32 = 0; for (case.ast.values) |item_node| { if (getRangeNode(node_tags, node_datas, item_node) != null) continue; items_len += 1; const item_inst = try comptimeExpr(parent_gz, scope, item_rl, item_node); try multi_cases_payload.append(gpa, @enumToInt(item_inst)); } // ranges var ranges_len: u32 = 0; for (case.ast.values) |item_node| { const range = getRangeNode(node_tags, node_datas, item_node) orelse continue; ranges_len += 1; const first = try comptimeExpr(parent_gz, scope, item_rl, node_datas[range].lhs); const last = try comptimeExpr(parent_gz, scope, item_rl, node_datas[range].rhs); try multi_cases_payload.appendSlice(gpa, &[_]u32{ @enumToInt(first), @enumToInt(last), }); } const case_result = try expr(&case_scope, sub_scope, block_scope.break_result_loc, case.ast.target_expr); if (!astgen.refIsNoReturn(case_result)) { block_scope.break_count += 1; _ = try case_scope.addBreak(.@"break", switch_block, case_result); } multi_cases_payload.items[header_index + 0] = items_len; multi_cases_payload.items[header_index + 1] = ranges_len; multi_cases_payload.items[header_index + 2] = @intCast(u32, case_scope.instructions.items.len); try multi_cases_payload.appendSlice(gpa, case_scope.instructions.items); } else { const item_node = case.ast.values[0]; const item_inst = try comptimeExpr(parent_gz, scope, item_rl, item_node); const case_result = try expr(&case_scope, sub_scope, block_scope.break_result_loc, case.ast.target_expr); if (!astgen.refIsNoReturn(case_result)) { block_scope.break_count += 1; _ = try case_scope.addBreak(.@"break", switch_block, case_result); } try scalar_cases_payload.ensureCapacity(gpa, scalar_cases_payload.items.len + 2 + case_scope.instructions.items.len); scalar_cases_payload.appendAssumeCapacity(@enumToInt(item_inst)); scalar_cases_payload.appendAssumeCapacity(@intCast(u32, case_scope.instructions.items.len)); scalar_cases_payload.appendSliceAssumeCapacity(case_scope.instructions.items); } } // Now that the item expressions are generated we can add this. try parent_gz.instructions.append(gpa, switch_block); const ref_bit: u4 = @boolToInt(any_payload_is_ref); const multi_bit: u4 = @boolToInt(multi_cases_len != 0); const special_prong_bits: u4 = @enumToInt(special_prong); comptime { assert(@enumToInt(zir.SpecialProng.none) == 0b00); assert(@enumToInt(zir.SpecialProng.@"else") == 0b01); assert(@enumToInt(zir.SpecialProng.under) == 0b10); } const zir_tags = astgen.instructions.items(.tag); zir_tags[switch_block] = switch ((ref_bit << 3) | (special_prong_bits << 1) | multi_bit) { 0b0_00_0 => .switch_block, 0b0_00_1 => .switch_block_multi, 0b0_01_0 => .switch_block_else, 0b0_01_1 => .switch_block_else_multi, 0b0_10_0 => .switch_block_under, 0b0_10_1 => .switch_block_under_multi, 0b1_00_0 => .switch_block_ref, 0b1_00_1 => .switch_block_ref_multi, 0b1_01_0 => .switch_block_ref_else, 0b1_01_1 => .switch_block_ref_else_multi, 0b1_10_0 => .switch_block_ref_under, 0b1_10_1 => .switch_block_ref_under_multi, else => unreachable, }; const payload_index = astgen.extra.items.len; const zir_datas = astgen.instructions.items(.data); zir_datas[switch_block].pl_node.payload_index = @intCast(u32, payload_index); try astgen.extra.ensureCapacity(gpa, astgen.extra.items.len + scalar_cases_payload.items.len + multi_cases_payload.items.len); const strat = rl.strategy(&block_scope); switch (strat.tag) { .break_operand => { // Switch expressions return `true` for `nodeMayNeedMemoryLocation` thus // `elide_store_to_block_ptr_instructions` will either be true, // or all prongs are noreturn. if (!strat.elide_store_to_block_ptr_instructions) { astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items); astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items); return astgen.indexToRef(switch_block); } // There will necessarily be a store_to_block_ptr for // all prongs, except for prongs that ended with a noreturn instruction. // Elide all the `store_to_block_ptr` instructions. // The break instructions need to have their operands coerced if the // switch's result location is a `ty`. In this case we overwrite the // `store_to_block_ptr` instruction with an `as` instruction and repurpose // it as the break operand. var extra_index: usize = 0; extra_index += 2; extra_index += @boolToInt(multi_cases_len != 0); if (special_prong != .none) special_prong: { const body_len_index = extra_index; const body_len = scalar_cases_payload.items[extra_index]; extra_index += 1; if (body_len < 2) { extra_index += body_len; astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]); break :special_prong; } extra_index += body_len - 2; const store_inst = scalar_cases_payload.items[extra_index]; if (zir_tags[store_inst] != .store_to_block_ptr) { extra_index += 2; astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]); break :special_prong; } assert(zir_datas[store_inst].bin.lhs == block_scope.rl_ptr); if (block_scope.rl_ty_inst != .none) { extra_index += 1; const break_inst = scalar_cases_payload.items[extra_index]; extra_index += 1; astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]); zir_tags[store_inst] = .as; zir_datas[store_inst].bin = .{ .lhs = block_scope.rl_ty_inst, .rhs = zir_datas[break_inst].@"break".operand, }; zir_datas[break_inst].@"break".operand = astgen.indexToRef(store_inst); } else { scalar_cases_payload.items[body_len_index] -= 1; astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]); extra_index += 1; astgen.extra.appendAssumeCapacity(scalar_cases_payload.items[extra_index]); extra_index += 1; } } else { astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]); } var scalar_i: u32 = 0; while (scalar_i < scalar_cases_len) : (scalar_i += 1) { const start_index = extra_index; extra_index += 1; const body_len_index = extra_index; const body_len = scalar_cases_payload.items[extra_index]; extra_index += 1; if (body_len < 2) { extra_index += body_len; astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[start_index..extra_index]); continue; } extra_index += body_len - 2; const store_inst = scalar_cases_payload.items[extra_index]; if (zir_tags[store_inst] != .store_to_block_ptr) { extra_index += 2; astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[start_index..extra_index]); continue; } if (block_scope.rl_ty_inst != .none) { extra_index += 1; const break_inst = scalar_cases_payload.items[extra_index]; extra_index += 1; astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[start_index..extra_index]); zir_tags[store_inst] = .as; zir_datas[store_inst].bin = .{ .lhs = block_scope.rl_ty_inst, .rhs = zir_datas[break_inst].@"break".operand, }; zir_datas[break_inst].@"break".operand = astgen.indexToRef(store_inst); } else { assert(zir_datas[store_inst].bin.lhs == block_scope.rl_ptr); scalar_cases_payload.items[body_len_index] -= 1; astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[start_index..extra_index]); extra_index += 1; astgen.extra.appendAssumeCapacity(scalar_cases_payload.items[extra_index]); extra_index += 1; } } extra_index = 0; var multi_i: u32 = 0; while (multi_i < multi_cases_len) : (multi_i += 1) { const start_index = extra_index; const items_len = multi_cases_payload.items[extra_index]; extra_index += 1; const ranges_len = multi_cases_payload.items[extra_index]; extra_index += 1; const body_len_index = extra_index; const body_len = multi_cases_payload.items[extra_index]; extra_index += 1; extra_index += items_len; extra_index += 2 * ranges_len; if (body_len < 2) { extra_index += body_len; astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items[start_index..extra_index]); continue; } extra_index += body_len - 2; const store_inst = multi_cases_payload.items[extra_index]; if (zir_tags[store_inst] != .store_to_block_ptr) { extra_index += 2; astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items[start_index..extra_index]); continue; } if (block_scope.rl_ty_inst != .none) { extra_index += 1; const break_inst = multi_cases_payload.items[extra_index]; extra_index += 1; astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items[start_index..extra_index]); zir_tags[store_inst] = .as; zir_datas[store_inst].bin = .{ .lhs = block_scope.rl_ty_inst, .rhs = zir_datas[break_inst].@"break".operand, }; zir_datas[break_inst].@"break".operand = astgen.indexToRef(store_inst); } else { assert(zir_datas[store_inst].bin.lhs == block_scope.rl_ptr); multi_cases_payload.items[body_len_index] -= 1; astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items[start_index..extra_index]); extra_index += 1; astgen.extra.appendAssumeCapacity(multi_cases_payload.items[extra_index]); extra_index += 1; } } const block_ref = astgen.indexToRef(switch_block); switch (rl) { .ref => return block_ref, else => return rvalue(parent_gz, scope, rl, block_ref, switch_node), } }, .break_void => { assert(!strat.elide_store_to_block_ptr_instructions); astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items); astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items); // Modify all the terminating instruction tags to become `break` variants. var extra_index: usize = payload_index; extra_index += 2; extra_index += @boolToInt(multi_cases_len != 0); if (special_prong != .none) { const body_len = astgen.extra.items[extra_index]; extra_index += 1; const body = astgen.extra.items[extra_index..][0..body_len]; extra_index += body_len; const last = body[body.len - 1]; if (zir_tags[last] == .@"break" and zir_datas[last].@"break".block_inst == switch_block) { zir_datas[last].@"break".operand = .void_value; } } var scalar_i: u32 = 0; while (scalar_i < scalar_cases_len) : (scalar_i += 1) { extra_index += 1; const body_len = astgen.extra.items[extra_index]; extra_index += 1; const body = astgen.extra.items[extra_index..][0..body_len]; extra_index += body_len; const last = body[body.len - 1]; if (zir_tags[last] == .@"break" and zir_datas[last].@"break".block_inst == switch_block) { zir_datas[last].@"break".operand = .void_value; } } var multi_i: u32 = 0; while (multi_i < multi_cases_len) : (multi_i += 1) { const items_len = astgen.extra.items[extra_index]; extra_index += 1; const ranges_len = astgen.extra.items[extra_index]; extra_index += 1; const body_len = astgen.extra.items[extra_index]; extra_index += 1; extra_index += items_len; extra_index += 2 * ranges_len; const body = astgen.extra.items[extra_index..][0..body_len]; extra_index += body_len; const last = body[body.len - 1]; if (zir_tags[last] == .@"break" and zir_datas[last].@"break".block_inst == switch_block) { zir_datas[last].@"break".operand = .void_value; } } return astgen.indexToRef(switch_block); }, } } fn ret(gz: *GenZir, scope: *Scope, node: ast.Node.Index) InnerError!zir.Inst.Ref { const tree = gz.tree(); const node_datas = tree.nodes.items(.data); const main_tokens = tree.nodes.items(.main_token); const operand_node = node_datas[node].lhs; const operand: zir.Inst.Ref = if (operand_node != 0) operand: { const rl: ResultLoc = if (nodeMayNeedMemoryLocation(tree, operand_node)) .{ .ptr = try gz.addNode(.ret_ptr, node), } else .{ .ty = try gz.addNode(.ret_type, node), }; break :operand try expr(gz, scope, rl, operand_node); } else .void_value; _ = try gz.addUnNode(.ret_node, operand, node); return zir.Inst.Ref.unreachable_value; } fn identifier( gz: *GenZir, scope: *Scope, rl: ResultLoc, ident: ast.Node.Index, ) InnerError!zir.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); const astgen = gz.astgen; const mod = astgen.mod; const tree = gz.tree(); const main_tokens = tree.nodes.items(.main_token); 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(gz, 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 = astgen.decl.nodeIndexToRelative(ident), .signedness = signedness, .bit_count = bit_count, } }, }); return rvalue(gz, 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(gz, 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)) { switch (rl) { .ref, .none_or_ref => return local_ptr.ptr, else => { const loaded = try gz.addUnNode(.load, local_ptr.ptr, ident); return rvalue(gz, scope, rl, loaded, ident); }, } } s = local_ptr.parent; }, .gen_zir => s = s.cast(GenZir).?.parent, else => break, }; } const decl = mod.lookupDeclName(scope, ident_name) orelse { // TODO insert a "dependency on the non-existence of a decl" here to make this // compile error go away when the decl is introduced. This data should be in a global // sparse map since it is only relevant when a compile error occurs. return mod.failNode(scope, ident, "use of undeclared identifier '{s}'", .{ident_name}); }; const decl_index = try mod.declareDeclDependency(astgen.decl, decl); switch (rl) { .ref, .none_or_ref => return gz.addDecl(.decl_ref, decl_index, ident), else => return rvalue(gz, scope, rl, try gz.addDecl(.decl_val, decl_index, ident), ident), } } fn stringLiteral( gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index, ) InnerError!zir.Inst.Ref { const tree = gz.tree(); const main_tokens = tree.nodes.items(.main_token); 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 gz.astgen.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(gz, scope, rl, result, node); } fn multilineStringLiteral( gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index, ) InnerError!zir.Inst.Ref { 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 gpa = gz.astgen.mod.gpa; 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(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(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(gz, scope, rl, result, node); } fn charLiteral(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) !zir.Inst.Ref { const mod = gz.astgen.mod; 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(gz, scope, rl, result, node); } fn integerLiteral( gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index, ) InnerError!zir.Inst.Ref { const tree = gz.tree(); const main_tokens = tree.nodes.items(.main_token); const int_token = main_tokens[node]; const prefixed_bytes = tree.tokenSlice(int_token); 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(gz, scope, rl, result, node); } else |err| { return gz.astgen.mod.failNode(scope, node, "TODO implement int literals that don't fit in a u64", .{}); } } fn floatLiteral( gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index, ) InnerError!zir.Inst.Ref { const arena = gz.astgen.arena; const tree = gz.tree(); const main_tokens = tree.nodes.items(.main_token); 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 gz.astgen.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 }; // If the value fits into a f32 without losing any precision, store it that way. @setFloatMode(.Strict); const smaller_float = @floatCast(f32, float_number); const bigger_again: f128 = smaller_float; if (bigger_again == float_number) { const result = try gz.addFloat(smaller_float, node); return rvalue(gz, scope, rl, result, node); } // We need to use 128 bits. Break the float into 4 u32 values so we can // put it into the `extra` array. const int_bits = @bitCast(u128, float_number); const result = try gz.addPlNode(.float128, node, zir.Inst.Float128{ .piece0 = @truncate(u32, int_bits), .piece1 = @truncate(u32, int_bits >> 32), .piece2 = @truncate(u32, int_bits >> 64), .piece3 = @truncate(u32, int_bits >> 96), }); return rvalue(gz, scope, rl, result, node); } fn asmExpr( gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index, full: ast.full.Asm, ) InnerError!zir.Inst.Ref { const mod = gz.astgen.mod; const arena = gz.astgen.arena; const tree = gz.tree(); const main_tokens = tree.nodes.items(.main_token); const node_datas = tree.nodes.items(.data); const asm_source = try expr(gz, scope, .{ .ty = .const_slice_u8_type }, full.ast.template); if (full.outputs.len != 0) { // when implementing this be sure to add test coverage for the asm return type // not resolving into a type (the node_offset_asm_ret_ty field of LazySrcLoc) 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(gz, 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(gz, scope, rl, result, node); } fn as( gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index, lhs: ast.Node.Index, rhs: ast.Node.Index, ) InnerError!zir.Inst.Ref { const dest_type = try typeExpr(gz, scope, lhs); switch (rl) { .none, .none_or_ref, .discard, .ref, .ty => { const result = try expr(gz, scope, .{ .ty = dest_type }, rhs); return rvalue(gz, scope, rl, result, node); }, .ptr => |result_ptr| { return asRlPtr(gz, scope, rl, result_ptr, rhs, dest_type); }, .block_ptr => |block_scope| { return asRlPtr(gz, scope, rl, block_scope.rl_ptr, rhs, dest_type); }, .inferred_ptr => |result_alloc| { // TODO here we should be able to resolve the inference; we now have a type for the result. return gz.astgen.mod.failNode(scope, node, "TODO implement @as with inferred-type result location pointer", .{}); }, } } fn asRlPtr( parent_gz: *GenZir, 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 astgen = parent_gz.astgen; var as_scope: GenZir = .{ .parent = scope, .astgen = astgen, .force_comptime = parent_gz.force_comptime, .instructions = .{}, }; defer as_scope.instructions.deinit(astgen.mod.gpa); as_scope.rl_ptr = try as_scope.addBin(.coerce_result_ptr, dest_type, result_ptr); const result = try expr(&as_scope, &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(astgen.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(parent_gz, scope, rl, casted_result, operand_node); } else { try parent_zir.appendSlice(astgen.mod.gpa, as_scope.instructions.items); return result; } } fn bitCast( gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index, lhs: ast.Node.Index, rhs: ast.Node.Index, ) InnerError!zir.Inst.Ref { const mod = gz.astgen.mod; const dest_type = try typeExpr(gz, scope, lhs); switch (rl) { .none, .discard, .ty => { const operand = try expr(gz, scope, .none, rhs); const result = try gz.addPlNode(.bitcast, node, zir.Inst.Bin{ .lhs = dest_type, .rhs = operand, }); return rvalue(gz, scope, rl, result, node); }, .ref, .none_or_ref => unreachable, // `@bitCast` is not allowed as an r-value. .ptr => |result_ptr| { const casted_result_ptr = try gz.addUnNode(.bitcast_result_ptr, result_ptr, node); return expr(gz, scope, .{ .ptr = casted_result_ptr }, rhs); }, .block_ptr => |block_ptr| { return mod.failNode(scope, node, "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.failNode(scope, node, "TODO implement @bitCast with inferred-type result location pointer", .{}); }, } } fn typeOf( gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index, params: []const ast.Node.Index, ) InnerError!zir.Inst.Ref { if (params.len < 1) { return gz.astgen.mod.failNode(scope, node, "expected at least 1 argument, found 0", .{}); } if (params.len == 1) { const result = try gz.addUnNode(.typeof, try expr(gz, scope, .none, params[0]), node); return rvalue(gz, scope, rl, result, node); } const arena = gz.astgen.arena; var items = try arena.alloc(zir.Inst.Ref, params.len); for (params) |param, param_i| { items[param_i] = try expr(gz, 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(gz, scope, rl, result, node); } fn builtinCall( gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index, params: []const ast.Node.Index, ) InnerError!zir.Inst.Ref { const mod = gz.astgen.mod; const tree = gz.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.failNode(scope, node, "invalid builtin function: '{s}'", .{ builtin_name, }); }; if (info.param_count) |expected| { if (expected != params.len) { const s = if (expected == 1) "" else "s"; return mod.failNode(scope, node, "expected {d} parameter{s}, found {d}", .{ expected, s, params.len, }); } } switch (info.tag) { .ptr_to_int => { const operand = try expr(gz, scope, .none, params[0]); const result = try gz.addUnNode(.ptrtoint, operand, node); return rvalue(gz, scope, rl, result, node); }, .float_cast => { const dest_type = try typeExpr(gz, scope, params[0]); const rhs = try expr(gz, scope, .none, params[1]); const result = try gz.addPlNode(.floatcast, node, zir.Inst.Bin{ .lhs = dest_type, .rhs = rhs, }); return rvalue(gz, scope, rl, result, node); }, .int_cast => { const dest_type = try typeExpr(gz, scope, params[0]); const rhs = try expr(gz, scope, .none, params[1]); const result = try gz.addPlNode(.intcast, node, zir.Inst.Bin{ .lhs = dest_type, .rhs = rhs, }); return rvalue(gz, scope, rl, result, node); }, .breakpoint => { _ = try gz.add(.{ .tag = .breakpoint, .data = .{ .node = gz.astgen.decl.nodeIndexToRelative(node) }, }); return rvalue(gz, scope, rl, .void_value, node); }, .import => { const target = try expr(gz, scope, .none, params[0]); const result = try gz.addUnNode(.import, target, node); return rvalue(gz, scope, rl, result, node); }, .error_to_int => { const target = try expr(gz, scope, .none, params[0]); const result = try gz.addUnNode(.error_to_int, target, node); return rvalue(gz, scope, rl, result, node); }, .int_to_error => { const target = try expr(gz, scope, .{ .ty = .u16_type }, params[0]); const result = try gz.addUnNode(.int_to_error, target, node); return rvalue(gz, scope, rl, result, node); }, .compile_error => { const target = try expr(gz, scope, .none, params[0]); const result = try gz.addUnNode(.compile_error, target, node); return rvalue(gz, scope, rl, result, node); }, .set_eval_branch_quota => { const quota = try expr(gz, scope, .{ .ty = .u32_type }, params[0]); const result = try gz.addUnNode(.set_eval_branch_quota, quota, node); return rvalue(gz, 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(gz, 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(gz, scope, rl, result, node); }, .field => { const field_name = try comptimeExpr(gz, 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(gz, scope, .ref, params[0]), .field_name = field_name, }); } const result = try gz.addPlNode(.field_val_named, node, zir.Inst.FieldNamed{ .lhs = try expr(gz, scope, .none, params[0]), .field_name = field_name, }); return rvalue(gz, scope, rl, result, node); }, .as => return as(gz, scope, rl, node, params[0], params[1]), .bit_cast => return bitCast(gz, scope, rl, node, params[0], params[1]), .TypeOf => return typeOf(gz, scope, rl, node, params), .int_to_enum => { const result = try gz.addPlNode(.int_to_enum, node, zir.Inst.Bin{ .lhs = try typeExpr(gz, scope, params[0]), .rhs = try expr(gz, scope, .none, params[1]), }); return rvalue(gz, scope, rl, result, node); }, .enum_to_int => { const operand = try expr(gz, scope, .none, params[0]); const result = try gz.addUnNode(.enum_to_int, operand, node); return rvalue(gz, scope, rl, result, node); }, .@"export" => { // TODO: @export is supposed to be able to export things other than functions. // Instead of `comptimeExpr` here we need `decl_ref`. const fn_to_export = try comptimeExpr(gz, scope, .none, params[0]); // TODO: the second parameter here is supposed to be // `std.builtin.ExportOptions`, not a string. const export_name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]); _ = try gz.addPlNode(.@"export", node, zir.Inst.Bin{ .lhs = fn_to_export, .rhs = export_name, }); return rvalue(gz, scope, rl, .void_value, node); }, .has_decl => { const container_type = try typeExpr(gz, scope, params[0]); const name = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, params[1]); const result = try gz.addPlNode(.has_decl, node, zir.Inst.Bin{ .lhs = container_type, .rhs = name, }); return rvalue(gz, scope, rl, result, node); }, .type_info => { const operand = try typeExpr(gz, scope, params[0]); const result = try gz.addUnNode(.type_info, operand, node); return rvalue(gz, scope, rl, result, node); }, .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, .error_name, .error_return_trace, .err_set_cast, .fence, .field_parent_ptr, .float_to_int, .has_field, .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_name, .union_init, => return mod.failNode(scope, node, "TODO: implement builtin function {s}", .{ builtin_name, }), .async_call, .frame, .Frame, .frame_address, .frame_size, => return mod.failNode(scope, node, "async and related features are not yet supported", .{}), } } fn callExpr( gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index, call: ast.full.Call, ) InnerError!zir.Inst.Ref { const mod = gz.astgen.mod; if (call.async_token) |async_token| { return mod.failTok(scope, async_token, "async and related features are not yet supported", .{}); } const lhs = try expr(gz, scope, .none, call.ast.fn_expr); const args = try mod.gpa.alloc(zir.Inst.Ref, call.ast.params.len); defer mod.gpa.free(args); 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(gz, 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(gz, 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(tree: *const ast.Tree, start_node: ast.Node.Index) bool { 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( gz: *GenZir, scope: *Scope, rl: ResultLoc, result: zir.Inst.Ref, src_node: ast.Node.Index, ) InnerError!zir.Inst.Ref { switch (rl) { .none, .none_or_ref => 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 = gz.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; }, .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; }, } }