| ... | @@ -4453,14 +4453,14 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void | ... | @@ -4453,14 +4453,14 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void |
| 4453 | const switch_br = f.air.unwrapSwitch(inst); | 4453 | const switch_br = f.air.unwrapSwitch(inst); |
| 4454 | const init_condition = try f.resolveInst(switch_br.operand); | 4454 | const init_condition = try f.resolveInst(switch_br.operand); |
| 4455 | try reap(f, inst, &.{switch_br.operand}); | 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 | const w = &f.code.writer; | 4457 | const w = &f.code.writer; |
| 4458 | | 4458 | |
| 4459 | // For dispatches, we will create a local alloc to contain the condition value. | 4459 | // For dispatches, we will create a local alloc to contain the condition value. |
| 4460 | // This may not result in optimal codegen for switch loops, but it minimizes the | 4460 | // This may not result in optimal codegen for switch loops, but it minimizes the |
| 4461 | // amount of C code we generate, which is probably more desirable here (and is simpler). | 4461 | // amount of C code we generate, which is probably more desirable here (and is simpler). |
| 4462 | const condition = if (is_dispatch_loop) cond: { | 4462 | const cond_val = if (is_dispatch_loop) cond: { |
| 4463 | const new_local = try f.allocLocal(inst, condition_ty); | 4463 | const new_local = try f.allocLocal(inst, cond_ty); |
| 4464 | try f.copyCValue(new_local, init_condition); | 4464 | try f.copyCValue(new_local, init_condition); |
| 4465 | try w.print("zig_switch_{d}_loop:", .{@intFromEnum(inst)}); | 4465 | try w.print("zig_switch_{d}_loop:", .{@intFromEnum(inst)}); |
| 4466 | try f.newline(); | 4466 | try f.newline(); |
| ... | @@ -4472,26 +4472,38 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void | ... | @@ -4472,26 +4472,38 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void |
| 4472 | assert(f.loop_switch_conds.remove(inst)); | 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 | const lowered_cond_ty: Type = switch (cond_ty.zigTypeTag(zcu)) { |
| 4478 | .u1 | 4479 | .@"enum", .error_set, .int, .@"struct", .@"union" => cond_ty, |
| 4479 | else if (condition_ty.isPtrAtRuntime(zcu)) | 4480 | .bool => .u1, |
| 4480 | .usize | 4481 | .pointer => .usize, |
| 4481 | else | 4482 | .void => unreachable, // OPV type, always lowered to block/loop |
| 4482 | condition_ty; | 4483 | .comptime_int, .enum_literal, .@"fn", .type => unreachable, // comptime-only |
| 4483 | if (condition_ty.toIntern() != lowered_condition_ty.toIntern()) { | 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 | try w.writeByte('('); | 4499 | try w.writeByte('('); |
| 4485 | try f.renderType(w, lowered_condition_ty); | 4500 | try f.renderType(w, lowered_cond_ty); |
| 4486 | try w.writeByte(')'); | 4501 | try w.writeByte(')'); |
| 4487 | } | 4502 | } |
| 4488 | try f.writeCValue(w, condition, .other); | 4503 | try f.writeCValue(w, cond_val, .other); |
| 4489 | try w.writeAll(") {"); | 4504 | try w.writeAll(") {"); |
| 4490 | f.indent(); | 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 | var any_range_cases = false; | 4507 | var any_range_cases = false; |
| 4496 | var it = switch_br.iterateCases(); | 4508 | var it = switch_br.iterateCases(); |
| 4497 | while (it.next()) |case| { | 4509 | while (it.next()) |case| { |
| ... | @@ -4499,28 +4511,63 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void | ... | @@ -4499,28 +4511,63 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void |
| 4499 | any_range_cases = true; | 4511 | any_range_cases = true; |
| 4500 | continue; | 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 | for (case.items) |item| { | 4523 | for (case.items) |item| { |
| 4503 | try f.newline(); | 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 | const item_value = try f.air.value(item, pt); | 4542 | const item_value = try f.air.value(item, pt); |
| 4506 | // If `item_value` is a pointer with a known integer address, print the address | 4543 | // If `item_value` is a pointer with a known integer address, print the address |
| 4507 | // with no cast to avoid a warning. | 4544 | // with no cast to avoid a warning. |
| 4508 | write_val: { | 4545 | write_val: { |
| 4509 | if (condition_ty.isPtrAtRuntime(zcu)) { | 4546 | if (cond_ty.zigTypeTag(zcu) == .pointer) { |
| 4510 | if (item_value.?.getUnsignedInt(zcu)) |item_int| { | 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 | break :write_val; | 4549 | break :write_val; |
| 4513 | } | 4550 | } |
| 4514 | } | | |
| 4515 | if (condition_ty.isPtrAtRuntime(zcu)) { | | |
| 4516 | try w.writeByte('('); | 4551 | try w.writeByte('('); |
| 4517 | try f.renderType(w, .usize); | 4552 | try f.renderType(w, .usize); |
| 4518 | try w.writeByte(')'); | 4553 | try w.writeByte(')'); |
| 4519 | } | 4554 | } |
| 4520 | try f.dg.renderValue(w, (try f.air.value(item, pt)).?, .other); | 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 | try w.writeAll(" {"); | 4571 | try w.writeAll(" {"); |
| 4525 | f.indent(); | 4572 | f.indent(); |
| 4526 | try f.newline(); | 4573 | try f.newline(); |
| ... | @@ -4537,56 +4584,24 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void | ... | @@ -4537,56 +4584,24 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void |
| 4537 | // The case body must be noreturn so we don't need to insert a break. | 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 | try f.newline(); | 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 | if (any_range_cases) { | 4593 | if (any_range_cases) { |
| 4545 | // We will iterate the cases again to handle those with ranges, and generate | 4594 | // We will iterate the cases again to handle those with ranges, and generate |
| 4546 | // code using conditions rather than switch cases for such cases. | 4595 | // code using conditions rather than switch cases for such cases. |
| 4547 | it = switch_br.iterateCases(); | 4596 | try lowerSwitchToConditions(f, inst, cond_val, lowered_cond_ty, switch_br, liveness, is_dispatch_loop, true); |
| 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 | } | | |
| 4583 | } | 4597 | } |
| 4584 | if (is_dispatch_loop) { | 4598 | if (is_dispatch_loop) { |
| 4585 | try w.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), switch_br.cases_len }); | 4599 | try w.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), switch_br.cases_len }); |
| 4586 | } | 4600 | } |
| | 4601 | const else_body = it.elseBody(); |
| 4587 | if (else_body.len > 0) { | 4602 | if (else_body.len > 0) { |
| 4588 | // Note that this must be the last case, so we do not need to use `genBodyResolveState` since | 4603 | // Note that this must be the last case, so we do not need to use `genBodyResolveState` |
| 4589 | // the parent block will do it (because the case body is noreturn). | 4604 | // since the parent block will do it (because the case body is noreturn). |
| 4590 | for (liveness.deaths[liveness.deaths.len - 1]) |death| { | 4605 | for (liveness.deaths[liveness.deaths.len - 1]) |death| { |
| 4591 | try die(f, inst, death.toRef()); | 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,6 +4613,111 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void |
| 4598 | try f.outdent(); | 4613 | try f.outdent(); |
| 4599 | try w.writeAll("}\n"); | 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 | fn asmInputNeedsLocal(f: *Function, constraint: []const u8, value: CValue) bool { | 4722 | fn asmInputNeedsLocal(f: *Function, constraint: []const u8, value: CValue) bool { |
| 4603 | const dg = f.dg; | 4723 | const dg = f.dg; |