diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/AstGen.zig | 397 | ||||
| -rw-r--r-- | src/Sema.zig | 180 | ||||
| -rw-r--r-- | src/zir.zig | 233 |
3 files changed, 675 insertions, 135 deletions
diff --git a/src/AstGen.zig b/src/AstGen.zig index 5afc49ca3b..2cd8fc25ba 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -22,6 +22,7 @@ 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) = .{}, @@ -1215,6 +1216,7 @@ fn blockExprStmts( .negate, .negate_wrap, .typeof, + .typeof_elem, .xor, .optional_type, .optional_type_from_ptr_elem, @@ -1243,6 +1245,24 @@ fn blockExprStmts( .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, => break :b false, // ZIR instructions that are always either `noreturn` or `void`. @@ -1257,18 +1277,6 @@ fn blockExprStmts( .break_inline, .condbr, .condbr_inline, - .switch_br, - .switch_br_multi, - .switch_br_else, - .switch_br_else_multi, - .switch_br_under, - .switch_br_under_multi, - .switch_br_ref, - .switch_br_ref_multi, - .switch_br_ref_else, - .switch_br_ref_else_multi, - .switch_br_ref_under, - .switch_br_ref_under_multi, .compile_error, .ret_node, .ret_tok, @@ -1543,7 +1551,7 @@ fn assignOp( 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.addUnTok(.typeof, lhs, 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{ @@ -2548,15 +2556,28 @@ fn switchExpr( 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), @@ -2568,22 +2589,346 @@ fn switchExpr( 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 rl_and_tag: struct { rl: ResultLoc, tag: zir.Inst.Tag } = if (any_payload_is_ref) .{ - .rl = .ref, - .tag = .switch_br_ref, - } else .{ - .rl = .none, - .tag = .switch_br, + 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 SwitchBr/SwitchBrMulti. + // 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 = std.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 = std.ArrayListUnmanaged(u32){}; + defer multi_cases_payload.deinit(gpa); + + var block_scope: GenZir = .{ + .parent = scope, + .astgen = astgen, + .force_comptime = parent_gz.force_comptime, + .instructions = .{}, }; - const operand = try expr(parent_gz, scope, rl_and_tag.rl, operand_node); + block_scope.setBreakResultLoc(rl); + defer block_scope.instructions.deinit(gpa); - const result = try parent_gz.addPlNode(.switch_br, switch_node, zir.Inst.SwitchBr{ - .operand = operand, - .cases_len = 0, - }); - return rvalue(parent_gz, scope, rl, result, switch_node); + // 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; + }; + block_scope.break_count += 1; + const case_result = try expr(&case_scope, sub_scope, block_scope.break_result_loc, case.ast.target_expr); + if (!astgen.refIsNoReturn(case_result)) { + _ = try case_scope.addBreak(.@"break", switch_block, case_result); + } + // Documentation for this: `zir.Inst.SwitchBr` and `zir.Inst.SwitchBrMulti`. + 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.SwitchBr` and `zir.Inst.SwitchBrMulti`. + 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. + 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) multi_cases_len else scalar_cases_len; + 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), + }); + } + + block_scope.break_count += 1; + const case_result = try expr(&case_scope, sub_scope, block_scope.break_result_loc, case.ast.target_expr); + if (!astgen.refIsNoReturn(case_result)) { + _ = 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); + block_scope.break_count += 1; + const case_result = try expr(&case_scope, sub_scope, block_scope.break_result_loc, case.ast.target_expr); + if (!astgen.refIsNoReturn(case_result)) { + _ = 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 zir_datas = astgen.instructions.items(.data); + zir_datas[switch_block].pl_node.payload_index = @intCast(u32, astgen.extra.items.len); + try astgen.extra.ensureCapacity(gpa, astgen.extra.items.len + + scalar_cases_payload.items.len + multi_cases_payload.items.len); + astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items); + astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items); + const strat = rl.strategy(&block_scope); + assert(strat.tag == .break_operand); // TODO + assert(!strat.elide_store_to_block_ptr_instructions); // TODO + assert(rl != .ref); // TODO + const switch_block_ref = astgen.indexToRef(switch_block); + return rvalue(parent_gz, scope, rl, switch_block_ref, switch_node); } fn ret(gz: *GenZir, scope: *Scope, node: ast.Node.Index) InnerError!zir.Inst.Ref { @@ -3021,7 +3366,7 @@ fn typeOf( return gz.astgen.mod.failTok(scope, builtin_token, "expected at least 1 argument, found 0", .{}); } if (params.len == 1) { - const result = try gz.addUnTok(.typeof, try expr(gz, scope, .none, params[0]), node); + 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; diff --git a/src/Sema.zig b/src/Sema.zig index 4cac97f731..ec6f8ff2a9 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -84,6 +84,15 @@ pub fn rootAsType(sema: *Sema, root_block: *Scope.Block) !Type { return sema.resolveType(root_block, .unneeded, zir_inst_ref); } +/// Returns only the result from the body that is specified. +/// Only appropriate to call when it is determined at comptime that this body +/// has no peers. +fn resolveBody(sema: *Sema, block: *Scope.Block, body: []const zir.Inst.Index) InnerError!*Inst { + const break_inst = try sema.analyzeBody(block, body); + const operand_ref = sema.code.instructions.items(.data)[break_inst].@"break".operand; + return sema.resolveInst(operand_ref); +} + /// ZIR instructions which are always `noreturn` return this. This matches the /// return type of `analyzeBody` so that we can tail call them. /// Only appropriate to return when the instruction is known to be NoReturn @@ -229,7 +238,26 @@ pub fn analyzeBody( .str => try sema.zirStr(block, inst), .sub => try sema.zirArithmetic(block, inst), .subwrap => try sema.zirArithmetic(block, inst), + .switch_block => try sema.zirSwitchBlock(block, inst, false, .none), + .switch_block_multi => try sema.zirSwitchBlockMulti(block, inst, false, .none), + .switch_block_else => try sema.zirSwitchBlock(block, inst, false, .@"else"), + .switch_block_else_multi => try sema.zirSwitchBlockMulti(block, inst, false, .@"else"), + .switch_block_under => try sema.zirSwitchBlock(block, inst, false, .under), + .switch_block_under_multi => try sema.zirSwitchBlockMulti(block, inst, false, .under), + .switch_block_ref => try sema.zirSwitchBlock(block, inst, true, .none), + .switch_block_ref_multi => try sema.zirSwitchBlockMulti(block, inst, true, .none), + .switch_block_ref_else => try sema.zirSwitchBlock(block, inst, true, .@"else"), + .switch_block_ref_else_multi => try sema.zirSwitchBlockMulti(block, inst, true, .@"else"), + .switch_block_ref_under => try sema.zirSwitchBlock(block, inst, true, .under), + .switch_block_ref_under_multi => try sema.zirSwitchBlockMulti(block, inst, true, .under), + .switch_capture => try sema.zirSwitchCapture(block, inst, false, false), + .switch_capture_ref => try sema.zirSwitchCapture(block, inst, false, true), + .switch_capture_multi => try sema.zirSwitchCapture(block, inst, true, false), + .switch_capture_multi_ref => try sema.zirSwitchCapture(block, inst, true, true), + .switch_capture_else => try sema.zirSwitchCaptureElse(block, inst, false), + .switch_capture_else_ref => try sema.zirSwitchCaptureElse(block, inst, true), .typeof => try sema.zirTypeof(block, inst), + .typeof_elem => try sema.zirTypeofElem(block, inst), .typeof_peer => try sema.zirTypeofPeer(block, inst), .xor => try sema.zirBitwise(block, inst, .xor), @@ -245,18 +273,6 @@ pub fn analyzeBody( .ret_tok => return sema.zirRetTok(block, inst, false), .@"unreachable" => return sema.zirUnreachable(block, inst), .repeat => return sema.zirRepeat(block, inst), - .switch_br => return sema.zirSwitchBr(block, inst, false, .none), - .switch_br_multi => return sema.zirSwitchBrMulti(block, inst, false, .none), - .switch_br_else => return sema.zirSwitchBr(block, inst, false, .@"else"), - .switch_br_else_multi => return sema.zirSwitchBrMulti(block, inst, false, .@"else"), - .switch_br_under => return sema.zirSwitchBr(block, inst, false, .under), - .switch_br_under_multi => return sema.zirSwitchBrMulti(block, inst, false, .under), - .switch_br_ref => return sema.zirSwitchBr(block, inst, true, .none), - .switch_br_ref_multi => return sema.zirSwitchBrMulti(block, inst, true, .none), - .switch_br_ref_else => return sema.zirSwitchBr(block, inst, true, .@"else"), - .switch_br_ref_else_multi => return sema.zirSwitchBrMulti(block, inst, true, .@"else"), - .switch_br_ref_under => return sema.zirSwitchBr(block, inst, true, .under), - .switch_br_ref_under_multi => return sema.zirSwitchBrMulti(block, inst, true, .under), // Instructions that we know can *never* be noreturn based solely on // their tag. We avoid needlessly checking if they are noreturn and @@ -1034,7 +1050,7 @@ fn analyzeBlockBody( } assert(coerce_block.instructions.items[coerce_block.instructions.items.len - 1] == coerced_operand); // Here we depend on the br instruction having been over-allocated (if necessary) - // inide analyzeBreak so that it can be converted into a br_block_flat instruction. + // inside zirBreak so that it can be converted into a br_block_flat instruction. const br_src = br.base.src; const br_ty = br.base.ty; const br_block_flat = @ptrCast(*Inst.BrBlockFlat, br); @@ -1063,22 +1079,15 @@ fn zirBreakpoint(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerEr _ = try block.addNoOp(src, Type.initTag(.void), .breakpoint); } -fn zirBreak(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Index { +fn zirBreak(sema: *Sema, start_block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Index { const tracy = trace(@src()); defer tracy.end(); const inst_data = sema.code.instructions.items(.data)[inst].@"break"; + const src = sema.src; const operand = try sema.resolveInst(inst_data.operand); - return sema.analyzeBreak(block, sema.src, inst_data.block_inst, operand); -} + const zir_block = inst_data.block_inst; -fn analyzeBreak( - sema: *Sema, - start_block: *Scope.Block, - src: LazySrcLoc, - zir_block: zir.Inst.Index, - operand: *Inst, -) InnerError!zir.Inst.Index { var block = start_block; while (true) { if (block.label) |*label| { @@ -1103,7 +1112,7 @@ fn analyzeBreak( try start_block.instructions.append(sema.gpa, &br.base); try label.merges.results.append(sema.gpa, operand); try label.merges.br_list.append(sema.gpa, br); - return always_noreturn; + return inst; } } block = block.parent.?; @@ -2208,15 +2217,38 @@ fn zirSliceSentinel(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) Inne return sema.analyzeSlice(block, src, array_ptr, start, end, sentinel, sentinel_src); } -const SpecialProng = enum { none, @"else", under }; +fn zirSwitchCapture( + sema: *Sema, + block: *Scope.Block, + inst: zir.Inst.Index, + is_multi: bool, + is_ref: bool, +) InnerError!*Inst { + const tracy = trace(@src()); + defer tracy.end(); + + @panic("TODO implement Sema for zirSwitchCapture"); +} -fn zirSwitchBr( +fn zirSwitchCaptureElse( sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index, is_ref: bool, - special_prong: SpecialProng, -) InnerError!zir.Inst.Index { +) InnerError!*Inst { + const tracy = trace(@src()); + defer tracy.end(); + + @panic("TODO implement Sema for zirSwitchCaptureElse"); +} + +fn zirSwitchBlock( + sema: *Sema, + block: *Scope.Block, + inst: zir.Inst.Index, + is_ref: bool, + special_prong: zir.SpecialProng, +) InnerError!*Inst { const tracy = trace(@src()); defer tracy.end(); @@ -2238,17 +2270,18 @@ fn zirSwitchBr( special_prong, extra.data.cases_len, 0, + inst, inst_data.src_node, ); } -fn zirSwitchBrMulti( +fn zirSwitchBlockMulti( sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index, is_ref: bool, - special_prong: SpecialProng, -) InnerError!zir.Inst.Index { + special_prong: zir.SpecialProng, +) InnerError!*Inst { const tracy = trace(@src()); defer tracy.end(); @@ -2270,6 +2303,7 @@ fn zirSwitchBrMulti( special_prong, extra.data.scalar_cases_len, extra.data.multi_cases_len, + inst, inst_data.src_node, ); } @@ -2279,11 +2313,12 @@ fn analyzeSwitch( block: *Scope.Block, operand: *Inst, extra_end: usize, - special_prong: SpecialProng, + special_prong: zir.SpecialProng, scalar_cases_len: usize, multi_cases_len: usize, + switch_inst: zir.Inst.Index, src_node_offset: i32, -) InnerError!zir.Inst.Index { +) InnerError!*Inst { const special: struct { body: []const zir.Inst.Index, end: usize } = switch (special_prong) { .none => .{ .body = &.{}, .end = extra_end }, .under, .@"else" => blk: { @@ -2584,7 +2619,7 @@ fn analyzeSwitch( const item = try sema.resolveInst(item_ref); const item_val = try sema.resolveConstValue(block, item.src, item); if (operand_val.eql(item_val)) { - return sema.analyzeBody(block, body); + return sema.resolveBody(block, body); } } } @@ -2605,7 +2640,7 @@ fn analyzeSwitch( const item = try sema.resolveInst(item_ref); const item_val = try sema.resolveConstValue(block, item.src, item); if (operand_val.eql(item_val)) { - return sema.analyzeBody(block, body); + return sema.resolveBody(block, body); } } @@ -2621,26 +2656,59 @@ fn analyzeSwitch( if (Value.compare(operand_val, .gte, first_tv.val) and Value.compare(operand_val, .lte, last_tv.val)) { - return sema.analyzeBody(block, body); + return sema.resolveBody(block, body); } } extra_index += body_len; } } - return sema.analyzeBody(block, special.body); + return sema.resolveBody(block, special.body); } if (scalar_cases_len + multi_cases_len == 0) { - return sema.analyzeBody(block, special.body); + return sema.resolveBody(block, special.body); } try sema.requireRuntimeBlock(block, src); + + const block_inst = try sema.arena.create(Inst.Block); + block_inst.* = .{ + .base = .{ + .tag = Inst.Block.base_tag, + .ty = undefined, // Set after analysis. + .src = src, + }, + .body = undefined, + }; + + var child_block: Scope.Block = .{ + .parent = block, + .sema = sema, + .src_decl = block.src_decl, + .instructions = .{}, + // TODO @as here is working around a stage1 miscompilation bug :( + .label = @as(?Scope.Block.Label, Scope.Block.Label{ + .zir_block = switch_inst, + .merges = .{ + .results = .{}, + .br_list = .{}, + .block_inst = block_inst, + }, + }), + .inlining = block.inlining, + .is_comptime = block.is_comptime, + }; + const merges = &child_block.label.?.merges; + defer child_block.instructions.deinit(sema.gpa); + defer merges.results.deinit(sema.gpa); + defer merges.br_list.deinit(sema.gpa); + // TODO when reworking TZIR memory layout make multi cases get generated as cases, // not as part of the "else" block. const cases = try sema.arena.alloc(Inst.SwitchBr.Case, scalar_cases_len); - var case_block = block.makeSubBlock(); + var case_block = child_block.makeSubBlock(); defer case_block.instructions.deinit(sema.gpa); var extra_index: usize = special.end; @@ -2656,7 +2724,7 @@ fn analyzeSwitch( case_block.instructions.shrinkRetainingCapacity(0); const item = try sema.resolveInst(item_ref); - const item_val = try sema.resolveConstValue(block, item.src, item); + const item_val = try sema.resolveConstValue(&case_block, item.src, item); _ = try sema.analyzeBody(&case_block, body); @@ -2687,7 +2755,7 @@ fn analyzeSwitch( for (items) |item_ref| { const item = try sema.resolveInst(item_ref); - _ = try sema.resolveConstValue(block, item.src, item); + _ = try sema.resolveConstValue(&child_block, item.src, item); const cmp_ok = try case_block.addBinOp(item.src, bool_ty, .cmp_eq, operand, item); if (any_ok) |some| { @@ -2707,8 +2775,8 @@ fn analyzeSwitch( const item_first = try sema.resolveInst(first_ref); const item_last = try sema.resolveInst(last_ref); - _ = try sema.resolveConstValue(block, item_first.src, item_first); - _ = try sema.resolveConstValue(block, item_last.src, item_last); + _ = try sema.resolveConstValue(&child_block, item_first.src, item_first); + _ = try sema.resolveConstValue(&child_block, item_last.src, item_last); const range_src = item_first.src; @@ -2779,8 +2847,8 @@ fn analyzeSwitch( .instructions = try sema.arena.dupe(*Inst, &[1]*Inst{&first_condbr.base}), }; - _ = try block.addSwitchBr(src, operand, cases, final_else_body); - return always_noreturn; + _ = try child_block.addSwitchBr(src, operand, cases, final_else_body); + return sema.analyzeBlockBody(block, &child_block, merges); } fn validateSwitchItem( @@ -3261,12 +3329,18 @@ fn zirCmp( } fn zirTypeof(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { - const tracy = trace(@src()); - defer tracy.end(); - - const inst_data = sema.code.instructions.items(.data)[inst].un_tok; + const inst_data = sema.code.instructions.items(.data)[inst].un_node; + const src = inst_data.src(); const operand = try sema.resolveInst(inst_data.operand); - return sema.mod.constType(sema.arena, inst_data.src(), operand.ty); + return sema.mod.constType(sema.arena, src, operand.ty); +} + +fn zirTypeofElem(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { + const inst_data = sema.code.instructions.items(.data)[inst].un_node; + const src = inst_data.src(); + const operand_ptr = try sema.resolveInst(inst_data.operand); + const elem_ty = operand_ptr.ty.elemType(); + return sema.mod.constType(sema.arena, src, elem_ty); } fn zirTypeofPeer(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { @@ -3360,8 +3434,7 @@ fn zirBoolBr( // comptime-known left-hand side. No need for a block here; the result // is simply the rhs expression. Here we rely on there only being 1 // break instruction (`break_inline`). - const break_inst = try sema.analyzeBody(parent_block, body); - return sema.resolveInst(datas[break_inst].@"break".operand); + return sema.resolveBody(parent_block, body); } const block_inst = try sema.arena.create(Inst.Block); @@ -3392,8 +3465,7 @@ fn zirBoolBr( }); _ = try lhs_block.addBr(src, block_inst, lhs_result); - const rhs_break_inst = try sema.analyzeBody(rhs_block, body); - const rhs_result = try sema.resolveInst(datas[rhs_break_inst].@"break".operand); + const rhs_result = try sema.resolveBody(rhs_block, body); _ = try rhs_block.addBr(src, block_inst, rhs_result); const tzir_then_body: ir.Body = .{ .instructions = try sema.arena.dupe(*Inst, then_block.instructions.items) }; diff --git a/src/zir.zig b/src/zir.zig index 744dedf4a7..772cb014b6 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -514,6 +514,9 @@ pub const Inst = struct { /// Returns the type of a value. /// Uses the `un_tok` field. typeof, + /// Given a value which is a pointer, returns the element type. + /// Uses the `un_node` field. + typeof_elem, /// The builtin `@TypeOf` which returns the type after Peer Type Resolution /// of one or more params. /// Uses the `pl_node` field. AST node is the `@TypeOf` call. Payload is `MultiOp`. @@ -588,32 +591,55 @@ pub const Inst = struct { /// A switch expression. Uses the `pl_node` union field. /// AST node is the switch, payload is `SwitchBr`. /// All prongs of target handled. - switch_br, - /// Same as switch_br, except one or more prongs have multiple items. - switch_br_multi, - /// Same as switch_br, except has an else prong. - switch_br_else, - /// Same as switch_br_else, except one or more prongs have multiple items. - switch_br_else_multi, - /// Same as switch_br, except has an underscore prong. - switch_br_under, - /// Same as switch_br, except one or more prongs have multiple items. - switch_br_under_multi, - /// Same as `switch_br` but the target is a pointer to the value being switched on. - switch_br_ref, - /// Same as `switch_br_multi` but the target is a pointer to the value being switched on. - switch_br_ref_multi, - /// Same as `switch_br_else` but the target is a pointer to the value being switched on. - switch_br_ref_else, - /// Same as `switch_br_else_multi` but the target is a pointer to the + switch_block, + /// Same as switch_block, except one or more prongs have multiple items. + switch_block_multi, + /// Same as switch_block, except has an else prong. + switch_block_else, + /// Same as switch_block_else, except one or more prongs have multiple items. + switch_block_else_multi, + /// Same as switch_block, except has an underscore prong. + switch_block_under, + /// Same as switch_block, except one or more prongs have multiple items. + switch_block_under_multi, + /// Same as `switch_block` but the target is a pointer to the value being switched on. + switch_block_ref, + /// Same as `switch_block_multi` but the target is a pointer to the value being switched on. + switch_block_ref_multi, + /// Same as `switch_block_else` but the target is a pointer to the value being switched on. + switch_block_ref_else, + /// Same as `switch_block_else_multi` but the target is a pointer to the /// value being switched on. - switch_br_ref_else_multi, - /// Same as `switch_br_under` but the target is a pointer to the value + switch_block_ref_else_multi, + /// Same as `switch_block_under` but the target is a pointer to the value /// being switched on. - switch_br_ref_under, - /// Same as `switch_br_under_multi` but the target is a pointer to + switch_block_ref_under, + /// Same as `switch_block_under_multi` but the target is a pointer to /// the value being switched on. - switch_br_ref_under_multi, + switch_block_ref_under_multi, + /// Produces the capture value for a switch prong. + /// Uses the `switch_capture` field. + switch_capture, + /// Produces the capture value for a switch prong. + /// Result is a pointer to the value. + /// Uses the `switch_capture` field. + switch_capture_ref, + /// Produces the capture value for a switch prong. + /// The prong is one of the multi cases. + /// Uses the `switch_capture` field. + switch_capture_multi, + /// Produces the capture value for a switch prong. + /// The prong is one of the multi cases. + /// Result is a pointer to the value. + /// Uses the `switch_capture` field. + switch_capture_multi_ref, + /// Produces the capture value for the else/'_' switch prong. + /// Uses the `switch_capture` field. + switch_capture_else, + /// Produces the capture value for the else/'_' switch prong. + /// Result is a pointer to the value. + /// Uses the `switch_capture` field. + switch_capture_else_ref, /// Returns whether the instruction is one of the control flow "noreturn" types. /// Function calls do not count. @@ -710,6 +736,7 @@ pub const Inst = struct { .negate, .negate_wrap, .typeof, + .typeof_elem, .xor, .optional_type, .optional_type_from_ptr_elem, @@ -743,6 +770,24 @@ pub const Inst = struct { .set_eval_branch_quota, .compile_log, .elided, + .switch_capture, + .switch_capture_ref, + .switch_capture_multi, + .switch_capture_multi_ref, + .switch_capture_else, + .switch_capture_else_ref, + .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, => false, .@"break", @@ -756,18 +801,6 @@ pub const Inst = struct { .@"unreachable", .repeat, .repeat_inline, - .switch_br, - .switch_br_multi, - .switch_br_else, - .switch_br_else_multi, - .switch_br_under, - .switch_br_under_multi, - .switch_br_ref, - .switch_br_ref_multi, - .switch_br_ref_else, - .switch_br_ref_else_multi, - .switch_br_ref_under, - .switch_br_ref_under_multi, => true, }; } @@ -1223,6 +1256,10 @@ pub const Inst = struct { block_inst: Index, operand: Ref, }, + switch_capture: struct { + switch_inst: Index, + prong_index: u32, + }, // Make sure we don't accidentally add a field to make this union // bigger than expected. Note that in Debug builds, Zig is allowed @@ -1394,6 +1431,8 @@ pub const Inst = struct { }; }; +pub const SpecialProng = enum { none, @"else", under }; + const Writer = struct { gpa: *Allocator, arena: *Allocator, @@ -1461,12 +1500,13 @@ const Writer = struct { .is_null_ptr, .is_err, .is_err_ptr, + .typeof, + .typeof_elem, => try self.writeUnNode(stream, inst), .ref, .ret_tok, .ret_coerce, - .typeof, .ensure_err_payload_void, => try self.writeUnTok(stream, inst), @@ -1542,21 +1582,19 @@ const Writer = struct { .condbr_inline, => try self.writePlNodeCondBr(stream, inst), - .switch_br, - .switch_br_else, - .switch_br_under, - .switch_br_ref, - .switch_br_ref_else, - .switch_br_ref_under, - => try self.writePlNodeSwitchBr(stream, inst), - - .switch_br_multi, - .switch_br_else_multi, - .switch_br_under_multi, - .switch_br_ref_multi, - .switch_br_ref_else_multi, - .switch_br_ref_under_multi, - => try self.writePlNodeSwitchBrMulti(stream, inst), + .switch_block => try self.writePlNodeSwitchBr(stream, inst, .none), + .switch_block_else => try self.writePlNodeSwitchBr(stream, inst, .@"else"), + .switch_block_under => try self.writePlNodeSwitchBr(stream, inst, .under), + .switch_block_ref => try self.writePlNodeSwitchBr(stream, inst, .none), + .switch_block_ref_else => try self.writePlNodeSwitchBr(stream, inst, .@"else"), + .switch_block_ref_under => try self.writePlNodeSwitchBr(stream, inst, .under), + + .switch_block_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .none), + .switch_block_else_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .@"else"), + .switch_block_under_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .under), + .switch_block_ref_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .none), + .switch_block_ref_else_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .@"else"), + .switch_block_ref_under_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .under), .compile_log, .typeof_peer, @@ -1588,10 +1626,19 @@ const Writer = struct { .fn_type_cc => try self.writeFnTypeCc(stream, inst, false), .fn_type_var_args => try self.writeFnType(stream, inst, true), .fn_type_cc_var_args => try self.writeFnTypeCc(stream, inst, true), + .@"unreachable" => try self.writeUnreachable(stream, inst), .enum_literal_small => try self.writeSmallStr(stream, inst), + .switch_capture, + .switch_capture_ref, + .switch_capture_multi, + .switch_capture_multi_ref, + .switch_capture_else, + .switch_capture_else_ref, + => try self.writeSwitchCapture(stream, inst), + .bitcast, .bitcast_ref, .bitcast_result_ptr, @@ -1763,11 +1810,46 @@ const Writer = struct { try self.writeSrc(stream, inst_data.src()); } - fn writePlNodeSwitchBr(self: *Writer, stream: anytype, inst: Inst.Index) !void { + fn writePlNodeSwitchBr( + self: *Writer, + stream: anytype, + inst: Inst.Index, + special_prong: SpecialProng, + ) !void { const inst_data = self.code.instructions.items(.data)[inst].pl_node; const extra = self.code.extraData(Inst.SwitchBr, inst_data.payload_index); + const special: struct { + body: []const Inst.Index, + end: usize, + } = switch (special_prong) { + .none => .{ .body = &.{}, .end = extra.end }, + .under, .@"else" => blk: { + const body_len = self.code.extra[extra.end]; + const extra_body_start = extra.end + 1; + break :blk .{ + .body = self.code.extra[extra_body_start..][0..body_len], + .end = extra_body_start + body_len, + }; + }, + }; + try self.writeInstRef(stream, extra.data.operand); - var extra_index: usize = extra.end; + + if (special.body.len != 0) { + const prong_name = switch (special_prong) { + .@"else" => "else", + .under => "_", + else => unreachable, + }; + try stream.print(", {s} => {{\n", .{prong_name}); + self.indent += 2; + try self.writeBody(stream, special.body); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("}"); + } + + var extra_index: usize = special.end; { var scalar_i: usize = 0; while (scalar_i < extra.data.cases_len) : (scalar_i += 1) { @@ -1792,11 +1874,46 @@ const Writer = struct { try self.writeSrc(stream, inst_data.src()); } - fn writePlNodeSwitchBrMulti(self: *Writer, stream: anytype, inst: Inst.Index) !void { + fn writePlNodeSwitchBlockMulti( + self: *Writer, + stream: anytype, + inst: Inst.Index, + special_prong: SpecialProng, + ) !void { const inst_data = self.code.instructions.items(.data)[inst].pl_node; const extra = self.code.extraData(Inst.SwitchBrMulti, inst_data.payload_index); + const special: struct { + body: []const Inst.Index, + end: usize, + } = switch (special_prong) { + .none => .{ .body = &.{}, .end = extra.end }, + .under, .@"else" => blk: { + const body_len = self.code.extra[extra.end]; + const extra_body_start = extra.end + 1; + break :blk .{ + .body = self.code.extra[extra_body_start..][0..body_len], + .end = extra_body_start + body_len, + }; + }, + }; + try self.writeInstRef(stream, extra.data.operand); - var extra_index: usize = extra.end; + + if (special.body.len != 0) { + const prong_name = switch (special_prong) { + .@"else" => "else", + .under => "_", + else => unreachable, + }; + try stream.print(", {s} => {{\n", .{prong_name}); + self.indent += 2; + try self.writeBody(stream, special.body); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("}"); + } + + var extra_index: usize = special.end; { var scalar_i: usize = 0; while (scalar_i < extra.data.scalar_cases_len) : (scalar_i += 1) { @@ -2015,6 +2132,12 @@ const Writer = struct { try stream.print("\"{}\")", .{std.zig.fmtEscapes(str)}); } + fn writeSwitchCapture(self: *Writer, stream: anytype, inst: Inst.Index) !void { + const inst_data = self.code.instructions.items(.data)[inst].switch_capture; + try self.writeInstIndex(stream, inst_data.switch_inst); + try stream.print(", {d})", .{inst_data.prong_index}); + } + fn writeInstRef(self: *Writer, stream: anytype, ref: Inst.Ref) !void { var i: usize = @enumToInt(ref); |
