| ... | ... | @@ -4453,14 +4453,14 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void |
| 4453 | 4453 | const switch_br = f.air.unwrapSwitch(inst); |
| 4454 | 4454 | const init_condition = try f.resolveInst(switch_br.operand); |
| 4455 | 4455 | try reap(f, inst, &.{switch_br.operand}); |
| 4456 | | const condition_ty = f.typeOf(switch_br.operand); |
| 4456 | const cond_ty = f.typeOf(switch_br.operand); |
| 4457 | 4457 | const w = &f.code.writer; |
| 4458 | 4458 | |
| 4459 | 4459 | // For dispatches, we will create a local alloc to contain the condition value. |
| 4460 | 4460 | // This may not result in optimal codegen for switch loops, but it minimizes the |
| 4461 | 4461 | // amount of C code we generate, which is probably more desirable here (and is simpler). |
| 4462 | | const condition = if (is_dispatch_loop) cond: { |
| 4463 | | const new_local = try f.allocLocal(inst, condition_ty); |
| 4462 | const cond_val = if (is_dispatch_loop) cond: { |
| 4463 | const new_local = try f.allocLocal(inst, cond_ty); |
| 4464 | 4464 | try f.copyCValue(new_local, init_condition); |
| 4465 | 4465 | try w.print("zig_switch_{d}_loop:", .{@intFromEnum(inst)}); |
| 4466 | 4466 | try f.newline(); |
| ... | ... | @@ -4472,26 +4472,38 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void |
| 4472 | 4472 | assert(f.loop_switch_conds.remove(inst)); |
| 4473 | 4473 | }; |
| 4474 | 4474 | |
| 4475 | | try w.writeAll("switch ("); |
| 4475 | const liveness = try f.liveness.getSwitchBr(gpa, inst, switch_br.cases_len + 1); |
| 4476 | defer gpa.free(liveness.deaths); |
| 4476 | 4477 | |
| 4477 | | const lowered_condition_ty: Type = if (condition_ty.toIntern() == .bool_type) |
| 4478 | | .u1 |
| 4479 | | else if (condition_ty.isPtrAtRuntime(zcu)) |
| 4480 | | .usize |
| 4481 | | else |
| 4482 | | condition_ty; |
| 4483 | | if (condition_ty.toIntern() != lowered_condition_ty.toIntern()) { |
| 4478 | const lowered_cond_ty: Type = switch (cond_ty.zigTypeTag(zcu)) { |
| 4479 | .@"enum", .error_set, .int, .@"struct", .@"union" => cond_ty, |
| 4480 | .bool => .u1, |
| 4481 | .pointer => .usize, |
| 4482 | .void => unreachable, // OPV type, always lowered to block/loop |
| 4483 | .comptime_int, .enum_literal, .@"fn", .type => unreachable, // comptime-only |
| 4484 | else => unreachable, // not supported by switch statement |
| 4485 | }; |
| 4486 | const cond_cint = switch (CType.classifyInt(lowered_cond_ty, zcu)) { |
| 4487 | .void => unreachable, // OPV type, always lowered to block/loop |
| 4488 | .small => |small| small, |
| 4489 | .big => { |
| 4490 | return lowerSwitchToConditions(f, inst, cond_val, lowered_cond_ty, switch_br, liveness, is_dispatch_loop, false); |
| 4491 | }, |
| 4492 | }; |
| 4493 | |
| 4494 | switch (cond_cint) { |
| 4495 | .zig_u128, .zig_i128 => try w.writeAll("zig_switch_int128("), |
| 4496 | else => try w.writeAll("switch ("), |
| 4497 | } |
| 4498 | if (cond_ty.toIntern() != lowered_cond_ty.toIntern()) { |
| 4484 | 4499 | try w.writeByte('('); |
| 4485 | | try f.renderType(w, lowered_condition_ty); |
| 4500 | try f.renderType(w, lowered_cond_ty); |
| 4486 | 4501 | try w.writeByte(')'); |
| 4487 | 4502 | } |
| 4488 | | try f.writeCValue(w, condition, .other); |
| 4503 | try f.writeCValue(w, cond_val, .other); |
| 4489 | 4504 | try w.writeAll(") {"); |
| 4490 | 4505 | f.indent(); |
| 4491 | 4506 | |
| 4492 | | const liveness = try f.liveness.getSwitchBr(gpa, inst, switch_br.cases_len + 1); |
| 4493 | | defer gpa.free(liveness.deaths); |
| 4494 | | |
| 4495 | 4507 | var any_range_cases = false; |
| 4496 | 4508 | var it = switch_br.iterateCases(); |
| 4497 | 4509 | while (it.next()) |case| { |
| ... | ... | @@ -4499,28 +4511,63 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void |
| 4499 | 4511 | any_range_cases = true; |
| 4500 | 4512 | continue; |
| 4501 | 4513 | } |
| 4514 | |
| 4515 | switch (cond_cint) { |
| 4516 | .zig_u128, .zig_i128 => { |
| 4517 | try f.newline(); |
| 4518 | try w.writeAll("zig_switch_prong_begin_int128()"); |
| 4519 | }, |
| 4520 | else => {}, |
| 4521 | } |
| 4522 | |
| 4502 | 4523 | for (case.items) |item| { |
| 4503 | 4524 | try f.newline(); |
| 4504 | | try w.writeAll("case "); |
| 4525 | case: { |
| 4526 | switch (cond_cint) { |
| 4527 | .zig_u128 => try w.writeAll(" zig_switch_case_int128(u128, "), |
| 4528 | .zig_i128 => try w.writeAll(" zig_switch_case_int128(i128, "), |
| 4529 | else => { |
| 4530 | try w.writeAll("case "); |
| 4531 | break :case; |
| 4532 | }, |
| 4533 | } |
| 4534 | if (cond_ty.toIntern() != lowered_cond_ty.toIntern()) { |
| 4535 | try w.writeByte('('); |
| 4536 | try f.renderType(w, lowered_cond_ty); |
| 4537 | try w.writeByte(')'); |
| 4538 | } |
| 4539 | try f.writeCValue(w, cond_val, .other); |
| 4540 | try w.writeAll(", "); |
| 4541 | } |
| 4505 | 4542 | const item_value = try f.air.value(item, pt); |
| 4506 | 4543 | // If `item_value` is a pointer with a known integer address, print the address |
| 4507 | 4544 | // with no cast to avoid a warning. |
| 4508 | 4545 | write_val: { |
| 4509 | | if (condition_ty.isPtrAtRuntime(zcu)) { |
| 4546 | if (cond_ty.zigTypeTag(zcu) == .pointer) { |
| 4510 | 4547 | if (item_value.?.getUnsignedInt(zcu)) |item_int| { |
| 4511 | | try w.print("{f}", .{try f.fmtIntLiteralDec(try pt.intValue(lowered_condition_ty, item_int))}); |
| 4548 | try w.print("{f}", .{try f.fmtIntLiteralDec(try pt.intValue(lowered_cond_ty, item_int))}); |
| 4512 | 4549 | break :write_val; |
| 4513 | 4550 | } |
| 4514 | | } |
| 4515 | | if (condition_ty.isPtrAtRuntime(zcu)) { |
| 4516 | 4551 | try w.writeByte('('); |
| 4517 | 4552 | try f.renderType(w, .usize); |
| 4518 | 4553 | try w.writeByte(')'); |
| 4519 | 4554 | } |
| 4520 | 4555 | try f.dg.renderValue(w, (try f.air.value(item, pt)).?, .other); |
| 4521 | 4556 | } |
| 4522 | | try w.writeByte(':'); |
| 4557 | switch (cond_cint) { |
| 4558 | .zig_u128, .zig_i128 => try w.writeByte(')'), |
| 4559 | else => try w.writeByte(':'), |
| 4560 | } |
| 4523 | 4561 | } |
| 4562 | |
| 4563 | switch (cond_cint) { |
| 4564 | .zig_u128, .zig_i128 => { |
| 4565 | try f.newline(); |
| 4566 | try w.writeAll("zig_switch_prong_end_int128()"); |
| 4567 | }, |
| 4568 | else => {}, |
| 4569 | } |
| 4570 | |
| 4524 | 4571 | try w.writeAll(" {"); |
| 4525 | 4572 | f.indent(); |
| 4526 | 4573 | try f.newline(); |
| ... | ... | @@ -4537,56 +4584,24 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void |
| 4537 | 4584 | // The case body must be noreturn so we don't need to insert a break. |
| 4538 | 4585 | } |
| 4539 | 4586 | |
| 4540 | | const else_body = it.elseBody(); |
| 4541 | 4587 | try f.newline(); |
| 4542 | 4588 | |
| 4543 | | try w.writeAll("default: "); |
| 4589 | switch (cond_cint) { |
| 4590 | .zig_u128, .zig_i128 => try w.writeAll("zig_switch_default_int128() "), |
| 4591 | else => try w.writeAll("default: "), |
| 4592 | } |
| 4544 | 4593 | if (any_range_cases) { |
| 4545 | 4594 | // We will iterate the cases again to handle those with ranges, and generate |
| 4546 | 4595 | // code using conditions rather than switch cases for such cases. |
| 4547 | | it = switch_br.iterateCases(); |
| 4548 | | while (it.next()) |case| { |
| 4549 | | if (case.ranges.len == 0) continue; // handled above |
| 4550 | | |
| 4551 | | try w.writeAll("if ("); |
| 4552 | | for (case.items, 0..) |item, item_i| { |
| 4553 | | if (item_i != 0) try w.writeAll(" || "); |
| 4554 | | try f.writeCValue(w, condition, .other); |
| 4555 | | try w.writeAll(" == "); |
| 4556 | | try f.dg.renderValue(w, (try f.air.value(item, pt)).?, .other); |
| 4557 | | } |
| 4558 | | for (case.ranges, 0..) |range, range_i| { |
| 4559 | | if (case.items.len != 0 or range_i != 0) try w.writeAll(" || "); |
| 4560 | | // "(x >= lower && x <= upper)" |
| 4561 | | try w.writeByte('('); |
| 4562 | | try f.writeCValue(w, condition, .other); |
| 4563 | | try w.writeAll(" >= "); |
| 4564 | | try f.dg.renderValue(w, (try f.air.value(range[0], pt)).?, .other); |
| 4565 | | try w.writeAll(" && "); |
| 4566 | | try f.writeCValue(w, condition, .other); |
| 4567 | | try w.writeAll(" <= "); |
| 4568 | | try f.dg.renderValue(w, (try f.air.value(range[1], pt)).?, .other); |
| 4569 | | try w.writeByte(')'); |
| 4570 | | } |
| 4571 | | try w.writeAll(") {"); |
| 4572 | | f.indent(); |
| 4573 | | try f.newline(); |
| 4574 | | if (is_dispatch_loop) { |
| 4575 | | try w.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), case.idx }); |
| 4576 | | } |
| 4577 | | try genBodyResolveState(f, inst, liveness.deaths[case.idx], case.body, true); |
| 4578 | | try f.outdent(); |
| 4579 | | try w.writeByte('}'); |
| 4580 | | if (f.dg.expected_block) |_| |
| 4581 | | return f.fail("runtime code not allowed in naked function", .{}); |
| 4582 | | } |
| 4596 | try lowerSwitchToConditions(f, inst, cond_val, lowered_cond_ty, switch_br, liveness, is_dispatch_loop, true); |
| 4583 | 4597 | } |
| 4584 | 4598 | if (is_dispatch_loop) { |
| 4585 | 4599 | try w.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), switch_br.cases_len }); |
| 4586 | 4600 | } |
| 4601 | const else_body = it.elseBody(); |
| 4587 | 4602 | if (else_body.len > 0) { |
| 4588 | | // Note that this must be the last case, so we do not need to use `genBodyResolveState` since |
| 4589 | | // the parent block will do it (because the case body is noreturn). |
| 4603 | // Note that this must be the last case, so we do not need to use `genBodyResolveState` |
| 4604 | // since the parent block will do it (because the case body is noreturn). |
| 4590 | 4605 | for (liveness.deaths[liveness.deaths.len - 1]) |death| { |
| 4591 | 4606 | try die(f, inst, death.toRef()); |
| 4592 | 4607 | } |
| ... | ... | @@ -4598,6 +4613,111 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void |
| 4598 | 4613 | try f.outdent(); |
| 4599 | 4614 | try w.writeAll("}\n"); |
| 4600 | 4615 | } |
| 4616 | fn lowerSwitchToConditions( |
| 4617 | f: *Function, |
| 4618 | inst: Air.Inst.Index, |
| 4619 | cond_val: CValue, |
| 4620 | cond_ty: Type, |
| 4621 | switch_br: Air.UnwrappedSwitch, |
| 4622 | liveness: Air.Liveness.SwitchBrTable, |
| 4623 | is_dispatch_loop: bool, |
| 4624 | only_ranges: bool, |
| 4625 | ) !void { |
| 4626 | const w = &f.code.writer; |
| 4627 | |
| 4628 | var it = switch_br.iterateCases(); |
| 4629 | while (it.next()) |case| { |
| 4630 | if (case.ranges.len == 0 and only_ranges) continue; |
| 4631 | |
| 4632 | try w.writeAll("if ("); |
| 4633 | for (case.items, 0..) |item, item_i| { |
| 4634 | if (item_i != 0) { |
| 4635 | try f.newline(); |
| 4636 | try w.writeAll(" || "); |
| 4637 | } |
| 4638 | try lowerSwitchCmp(f, cond_val, .eq, item, cond_ty); |
| 4639 | } |
| 4640 | for (case.ranges, 0..) |range, range_i| { |
| 4641 | if (case.items.len != 0 or range_i != 0) { |
| 4642 | try f.newline(); |
| 4643 | try w.writeAll(" || "); |
| 4644 | } |
| 4645 | // "(x >= lower && x <= upper)" |
| 4646 | try w.writeByte('('); |
| 4647 | try lowerSwitchCmp(f, cond_val, .gte, range[0], cond_ty); |
| 4648 | try w.writeAll(" && "); |
| 4649 | try lowerSwitchCmp(f, cond_val, .lte, range[1], cond_ty); |
| 4650 | try w.writeByte(')'); |
| 4651 | } |
| 4652 | try w.writeAll(") {"); |
| 4653 | f.indent(); |
| 4654 | try f.newline(); |
| 4655 | if (is_dispatch_loop) { |
| 4656 | try w.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), case.idx }); |
| 4657 | } |
| 4658 | try genBodyResolveState(f, inst, liveness.deaths[case.idx], case.body, true); |
| 4659 | try f.outdent(); |
| 4660 | try w.writeByte('}'); |
| 4661 | try f.newline(); |
| 4662 | if (f.dg.expected_block) |_| |
| 4663 | return f.fail("runtime code not allowed in naked function", .{}); |
| 4664 | } |
| 4665 | |
| 4666 | if (!only_ranges) { |
| 4667 | if (is_dispatch_loop) { |
| 4668 | try w.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), switch_br.cases_len }); |
| 4669 | } |
| 4670 | const else_body = it.elseBody(); |
| 4671 | if (else_body.len > 0) { |
| 4672 | // Note that this must be the last case, so we do not need to use `genBodyResolveState` |
| 4673 | // since the parent block will do it (because the case body is noreturn). |
| 4674 | for (liveness.deaths[liveness.deaths.len - 1]) |death| { |
| 4675 | try die(f, inst, death.toRef()); |
| 4676 | } |
| 4677 | try genBody(f, else_body); |
| 4678 | if (f.dg.expected_block) |_| |
| 4679 | return f.fail("runtime code not allowed in naked function", .{}); |
| 4680 | } else try airUnreach(f); |
| 4681 | try f.newline(); |
| 4682 | } |
| 4683 | } |
| 4684 | fn lowerSwitchCmp( |
| 4685 | f: *Function, |
| 4686 | cond_val: CValue, |
| 4687 | operator: std.math.CompareOperator, |
| 4688 | case_inst: Air.Inst.Ref, |
| 4689 | ty: Type, |
| 4690 | ) !void { |
| 4691 | const pt = f.dg.pt; |
| 4692 | const zcu = pt.zcu; |
| 4693 | const w = &f.code.writer; |
| 4694 | |
| 4695 | const class = CType.classifyInt(ty, zcu); |
| 4696 | const use_builtin = switch (class) { |
| 4697 | .void => unreachable, // assertion failure |
| 4698 | .small => |small| switch (small) { |
| 4699 | .zig_u128, .zig_i128 => true, |
| 4700 | else => false, |
| 4701 | }, |
| 4702 | .big => true, |
| 4703 | }; |
| 4704 | if (use_builtin) { |
| 4705 | try w.writeAll("zig_cmp_"); |
| 4706 | try f.dg.renderTypeForBuiltinFnName(w, ty); |
| 4707 | try w.writeByte('('); |
| 4708 | } |
| 4709 | if (class == .big) try w.writeByte('&'); |
| 4710 | try f.writeCValue(w, cond_val, .other); |
| 4711 | try w.writeAll(if (use_builtin) ", " else compareOperatorC(operator)); |
| 4712 | if (class == .big) try w.writeByte('&'); |
| 4713 | try f.dg.renderValue(w, (try f.air.value(case_inst, pt)).?, .other); |
| 4714 | if (use_builtin) { |
| 4715 | try f.dg.renderBuiltinInfo(w, ty, if (class == .big) .bits else .none); |
| 4716 | try w.writeByte(')'); |
| 4717 | try w.writeAll(compareOperatorC(operator)); |
| 4718 | try w.writeByte('0'); |
| 4719 | } |
| 4720 | } |
| 4601 | 4721 | |
| 4602 | 4722 | fn asmInputNeedsLocal(f: *Function, constraint: []const u8, value: CValue) bool { |
| 4603 | 4723 | const dg = f.dg; |