authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-10-20 19:38:22-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-10-21 18:58:31+02:00
loge5e0b6ae5238cc78d335cc0a9cac7ba4250cb9ca
treeb89d73a0d13f898d5f13e72b8d92748ecce078de
parentc79058895bce4ccacbe9f018404dd715e4cf2778

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!...@@ -4670,38 +4670,38 @@ fn airBitCast(fg: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error!
4670 // We have the following `Air.Legalize` features enabled:4670 // We have the following `Air.Legalize` features enabled:
4671 //4671 //
4672 // * `.scalarize_bit_cast_array`4672 // * `.scalarize_bit_cast_array`
4673 // * `.scalarize_bit_cast_vector_non_elementwise`4673 // * `.scalarize_bit_cast_vector_non_elementwise` (big-endian only)
4674 //4674 //
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:
4676 //4676 //
4677 // * bool/int/float <-> bool/int/float4677 // * bool/int/float <-> bool/int/float
4678 // * `@Vector(n, A)` <-> `@Vector(n, B)`4678 // * `@Vector(n, A)` <-> `@Vector(n, B)`
4679 //4679 //
4680 // Most of these cases can be handled by LLVM's `bitcast` instruction, except when4680 // While on little-endian, the `bit_cast` instructions we might see can be any of the following:
4681 // a non-native type like `f80` is used.4681 //
4682 // * bool/int/float/`@Vector(n, A)` <-> bool/int/float/`@Vector(m, B)`
46824683
4683 if (isByRef(operand_ty, zcu)) {4684 if (isByRef(operand_ty, zcu)) {
4684 const operand_scalar_ty = operand_ty.scalarType(zcu);4685 const operand_scalar_ty = operand_ty.scalarType(zcu);
4685 const target = zcu.getTarget();4686 const target = zcu.getTarget();
4686 const bits = operand_scalar_ty.floatBits(target);4687 const scalar_bits = operand_scalar_ty.floatBits(target);
4687 const dest_scalar_ty = dest_ty.scalarType(zcu);
4688 if (isByRef(dest_ty, zcu)) {4688 if (isByRef(dest_ty, zcu)) {
4689 assert(dest_scalar_ty.floatBits(target) == bits);4689 const dest_scalar_ty = dest_ty.scalarType(zcu);
4690 return operand;4690 // Converting between equivalent floats with the same repr.
4691 if (dest_scalar_ty.floatBits(target) == scalar_bits) return operand;
4691 }4692 }
4692 assert(dest_scalar_ty.intInfo(zcu).bits == bits);
46934693
4694 const len = if (operand_ty.toIntern() != operand_scalar_ty.toIntern())4694 const len = if (operand_ty.toIntern() != operand_scalar_ty.toIntern())
4695 operand_ty.vectorLen(zcu)4695 operand_ty.vectorLen(zcu)
4696 else4696 else
4697 null;4697 null;
4698 const operand_scalar_size = operand_scalar_ty.abiSize(zcu);4698 const operand_scalar_size = operand_scalar_ty.abiSize(zcu);
4699 var result = if (len) |_|4699 var result = if (len) |l|
4700 try o.builder.poisonValue(try o.lowerType(dest_ty, .as_value))4700 try o.builder.poisonValue(try o.builder.vectorType(.normal, l, try o.intType(scalar_bits, .as_value)))
4701 else4701 else
4702 undefined;4702 undefined;
4703 for (0..len orelse 1) |index| {4703 for (0..len orelse 1) |index| {
4704 const result_elem = result_elem: switch (bits) {4704 const result_elem = result_elem: switch (scalar_bits) {
4705 else => unreachable,4705 else => unreachable,
4706 80 => {4706 80 => {
4707 const f80_layout = o.softF80Layout(.{}) catch unreachable;4707 const f80_layout = o.softF80Layout(.{}) catch unreachable;
...@@ -4747,28 +4747,30 @@ fn airBitCast(fg: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error!...@@ -4747,28 +4747,30 @@ fn airBitCast(fg: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error!
4747 else4747 else
4748 result_elem;4748 result_elem;
4749 }4749 }
4750 return result;4750 return fg.wip.cast(.bitcast, result, try o.lowerType(dest_ty, .as_value), "");
4751 }4751 }
47524752
4753 if (isByRef(dest_ty, zcu)) {4753 if (isByRef(dest_ty, zcu)) {
4754 const dest_scalar_ty = dest_ty.scalarType(zcu);4754 const dest_scalar_ty = dest_ty.scalarType(zcu);
4755 const bits = dest_scalar_ty.floatBits(zcu.getTarget());4755 const scalar_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);
47594756
4760 const len = if (operand_ty.toIntern() != operand_scalar_ty.toIntern())4757 const len = if (dest_ty.toIntern() != dest_scalar_ty.toIntern())
4761 operand_ty.vectorLen(zcu)4758 operand_ty.vectorLen(zcu)
4762 else4759 else
4763 null;4760 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);
4765 const result_ptr = try fg.buildZigAlloca(dest_ty, .none);4767 const result_ptr = try fg.buildZigAlloca(dest_ty, .none);
4766 for (0..len orelse 1) |index| {4768 for (0..len orelse 1) |index| {
4767 const operand_elem = if (len) |_|4769 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")
4769 else4771 else
4770 operand;4772 bitcasted_operand;
4771 switch (bits) {4773 switch (scalar_bits) {
4772 else => unreachable,4774 else => unreachable,
4773 80 => {4775 80 => {
4774 const f80_layout = o.softF80Layout(.{}) catch unreachable;4776 const f80_layout = o.softF80Layout(.{}) catch unreachable;
test/behavior/bitcast.zig+33
...@@ -631,3 +631,36 @@ test "@bitCast deeply nested arrays to scalar" {...@@ -631,3 +631,36 @@ test "@bitCast deeply nested arrays to scalar" {
631 try static.doTheTest(src);631 try static.doTheTest(src);
632 try comptime static.doTheTest(src);632 try comptime static.doTheTest(src);
633}633}
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}