authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-10-20 19:38:22-04:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2026-10-21 20:10:42+02:00
logcff70a4050a4ca0ef5ef6be5ee72b425e56ad2d4
tree2be048fa682c91062bae18663453c91313081ca0
parentff08725400468a106c29a6281f718a94fc57fac5
signature Signed by SSH key SHA256:7B/LJ7bpR1eX8aCXSr4mtd5M45VMPKcx9zY8e95b5QM

llvm: implement bitcast without the help of a legalize feature

Closes #37188

2 files changed, 57 insertions(+), 22 deletions(-)

src/codegen/llvm/FuncGen.zig+24-22
......@@ -4670,38 +4670,38 @@ fn airBitCast(fg: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error!
46704670 // We have the following `Air.Legalize` features enabled:
46714671 //
46724672 // * `.scalarize_bit_cast_array`
4673 // * `.scalarize_bit_cast_vector_non_elementwise`
4673 // * `.scalarize_bit_cast_vector_non_elementwise` (big-endian only)
46744674 //
4675 // That means the `bit_cast` instructions we might see are limited to the following:
4675 // That means that on big-endian, the `bit_cast` instructions we might see are limited to the following:
46764676 //
46774677 // * bool/int/float <-> bool/int/float
46784678 // * `@Vector(n, A)` <-> `@Vector(n, B)`
46794679 //
4680 // Most of these cases can be handled by LLVM's `bitcast` instruction, except when
4681 // a non-native type like `f80` is used.
4680 // While on little-endian, the `bit_cast` instructions we might see can be any of the following:
4681 //
4682 // * bool/int/float/`@Vector(n, A)` <-> bool/int/float/`@Vector(m, B)`
46824683
46834684 if (isByRef(operand_ty, zcu)) {
46844685 const operand_scalar_ty = operand_ty.scalarType(zcu);
46854686 const target = zcu.getTarget();
4686 const bits = operand_scalar_ty.floatBits(target);
4687 const dest_scalar_ty = dest_ty.scalarType(zcu);
4687 const scalar_bits = operand_scalar_ty.floatBits(target);
46884688 if (isByRef(dest_ty, zcu)) {
4689 assert(dest_scalar_ty.floatBits(target) == bits);
4690 return operand;
4689 const dest_scalar_ty = dest_ty.scalarType(zcu);
4690 // Converting between equivalent floats with the same repr.
4691 if (dest_scalar_ty.floatBits(target) == scalar_bits) return operand;
46914692 }
4692 assert(dest_scalar_ty.intInfo(zcu).bits == bits);
46934693
46944694 const len = if (operand_ty.toIntern() != operand_scalar_ty.toIntern())
46954695 operand_ty.vectorLen(zcu)
46964696 else
46974697 null;
46984698 const operand_scalar_size = operand_scalar_ty.abiSize(zcu);
4699 var result = if (len) |_|
4700 try o.builder.poisonValue(try o.lowerType(dest_ty, .as_value))
4699 var result = if (len) |l|
4700 try o.builder.poisonValue(try o.builder.vectorType(.normal, l, try o.intType(scalar_bits, .as_value)))
47014701 else
47024702 undefined;
47034703 for (0..len orelse 1) |index| {
4704 const result_elem = result_elem: switch (bits) {
4704 const result_elem = result_elem: switch (scalar_bits) {
47054705 else => unreachable,
47064706 80 => {
47074707 const f80_layout = o.softF80Layout(.{}) catch unreachable;
......@@ -4747,28 +4747,30 @@ fn airBitCast(fg: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error!
47474747 else
47484748 result_elem;
47494749 }
4750 return result;
4750 return fg.wip.cast(.bitcast, result, try o.lowerType(dest_ty, .as_value), "");
47514751 }
47524752
47534753 if (isByRef(dest_ty, zcu)) {
47544754 const dest_scalar_ty = dest_ty.scalarType(zcu);
4755 const bits = dest_scalar_ty.floatBits(zcu.getTarget());
4756 assert(dest_scalar_ty.isRuntimeFloat());
4757 const operand_scalar_ty = operand_ty.scalarType(zcu);
4758 assert(operand_scalar_ty.intInfo(zcu).bits == bits);
4755 const scalar_bits = dest_scalar_ty.floatBits(zcu.getTarget());
47594756
4760 const len = if (operand_ty.toIntern() != operand_scalar_ty.toIntern())
4757 const len = if (dest_ty.toIntern() != dest_scalar_ty.toIntern())
47614758 operand_ty.vectorLen(zcu)
47624759 else
47634760 null;
4764 const operand_scalar_size = operand_scalar_ty.abiSize(zcu);
4761 const operand_scalar_int_ty = try o.intType(scalar_bits, .as_value);
4762 const bitcasted_operand = try fg.wip.cast(.bitcast, operand, if (len) |l|
4763 try o.builder.vectorType(.normal, l, operand_scalar_int_ty)
4764 else
4765 operand_scalar_int_ty, "");
4766 const operand_scalar_size = operand_ty.scalarType(zcu).abiSize(zcu);
47654767 const result_ptr = try fg.buildZigAlloca(dest_ty, .none);
47664768 for (0..len orelse 1) |index| {
47674769 const operand_elem = if (len) |_|
4768 try fg.wip.extractElement(operand, try o.builder.intValue(.i32, index), "elementwise.operand_elem")
4770 try fg.wip.extractElement(bitcasted_operand, try o.builder.intValue(.i32, index), "elementwise.operand_elem")
47694771 else
4770 operand;
4771 switch (bits) {
4772 bitcasted_operand;
4773 switch (scalar_bits) {
47724774 else => unreachable,
47734775 80 => {
47744776 const f80_layout = o.softF80Layout(.{}) catch unreachable;
test/behavior/bitcast.zig+33
......@@ -631,3 +631,36 @@ test "@bitCast deeply nested arrays to scalar" {
631631 try static.doTheTest(src);
632632 try comptime static.doTheTest(src);
633633}
634
635test "@bitCast between float and vector" {
636 const static = struct {
637 fn doTheTest() !void {
638 {
639 var v: @Vector(5, u16) = undefined;
640 v = .{ 0xf5c3, 0x5c28, 0xc28f, 0xc8f5, 0x4000 };
641 const f: f80 = @bitCast(v);
642 try expect(f == 3.14);
643 }
644 {
645 var f: f80 = undefined;
646 f = 3.14;
647 const v: @Vector(5, u16) = @bitCast(f);
648 try expect(std.mem.eql(u16, &@as([5]u16, v), &.{ 0xf5c3, 0x5c28, 0xc28f, 0xc8f5, 0x4000 }));
649 }
650 {
651 var v: @Vector(4, u32) = undefined;
652 v = .{ 0x51eb851f, 0xeb851eb8, 0x851eb851, 0x400191eb };
653 const f: f128 = @bitCast(v);
654 try expect(f == 6.28);
655 }
656 {
657 var f: f128 = undefined;
658 f = 6.28;
659 const v: @Vector(4, u32) = @bitCast(f);
660 try expect(std.mem.eql(u32, &@as([4]u32, v), &.{ 0x51eb851f, 0xeb851eb8, 0x851eb851, 0x400191eb }));
661 }
662 }
663 };
664 try static.doTheTest();
665 try comptime static.doTheTest();
666}