authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-06-14 11:21:28+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-06-24 10:20:55+01:00
logf9a6149b34af662a0d524f2f64f00e8857afc13b
treef3adc55f57964bc3db5bdb0c5c14c9424460ac9f
parent45833c031def1faa53f0715202b7eb3f23d326c1
signaturelock-open Commit is signed but in an unrecognized format.

llvm: use new `@bitCast` semantics


3 files changed, 88 insertions(+), 106 deletions(-)

src/Type.zig+1-1
......@@ -1257,7 +1257,7 @@ pub fn bitSize(ty: Type, zcu: *const Zcu) u64 {
12571257 const elem_ty: Type = .fromInterned(array_type.child);
12581258 const len = array_type.lenIncludingSentinel();
12591259 return switch (zcu.comp.getZigBackend()) {
1260 .stage2_x86_64 => len * elem_ty.bitSize(zcu),
1260 .stage2_x86_64, .stage2_llvm => len * elem_ty.bitSize(zcu),
12611261 // this case will be removed under #19755
12621262 else => switch (len) {
12631263 0 => 0,
src/codegen/llvm.zig+5
......@@ -36,6 +36,11 @@ pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
3636 return comptime &.initMany(&.{
3737 .expand_int_from_float_safe,
3838 .expand_int_from_float_optimized_safe,
39
40 .scalarize_bitcast_array,
41 // Needed because LLVM's `bitcast` on vectors is endian-specific unless the source and dest
42 // types are vectors with equal length (hence also with equal bits-per-element).
43 .scalarize_bitcast_vector_non_elementwise,
3944 });
4045}
4146
src/codegen/llvm/FuncGen.zig+82-105
......@@ -355,9 +355,10 @@ fn genBody(self: *FuncGen, body: []const Air.Inst.Index, coverage_point: Air.Cov
355355 const val: Builder.Value = switch (air_tags[@intFromEnum(inst)]) {
356356 // zig fmt: off
357357
358 // No "scalarize" legalizations are enabled, so these instructions never appear.
359 .legalize_vec_elem_val => unreachable,
360 .legalize_vec_store_elem => unreachable,
358 // Required due to `.scalarize_bitcast_vector_non_elementwise` being enabled.
359 .legalize_vec_elem_val => try self.airLegalizeVecElemVal(inst),
360 .legalize_vec_store_elem => try self.airLegalizeVecStoreElem(inst),
361
361362 // No soft float legalizations are enabled.
362363 .legalize_compiler_rt_call => unreachable,
363364
......@@ -2312,6 +2313,34 @@ fn airArrayElemVal(self: *FuncGen, inst: Air.Inst.Index) Allocator.Error!Builder
23122313 return self.wip.extractElement(array_llvm_val, rhs, "");
23132314}
23142315
2316fn airLegalizeVecElemVal(fg: *FuncGen, inst: Air.Inst.Index) Allocator.Error!Builder.Value {
2317 const bin_op = fg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
2318 const vec = try fg.resolveInst(bin_op.lhs);
2319 const index = try fg.resolveInst(bin_op.rhs);
2320 return fg.wip.extractElement(vec, index, "");
2321}
2322fn airLegalizeVecStoreElem(fg: *FuncGen, inst: Air.Inst.Index) Allocator.Error!Builder.Value {
2323 const zcu = fg.object.zcu;
2324
2325 const pl_op = fg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
2326 const extra = fg.air.extraData(Air.Bin, pl_op.payload).data;
2327
2328 const ptr_ty = fg.typeOf(pl_op.operand);
2329 const vec_ty = ptr_ty.childType(zcu);
2330
2331 const ptr_align = ptr_ty.ptrAlignment(zcu);
2332
2333 const vec_ptr = try fg.resolveInst(pl_op.operand);
2334 const index = try fg.resolveInst(extra.lhs);
2335 const elem = try fg.resolveInst(extra.rhs);
2336
2337 const old_vec = try fg.load(vec_ptr, ptr_align, vec_ty, .normal);
2338 const new_vec = try fg.wip.insertElement(old_vec, elem, index, "");
2339 try fg.store(vec_ptr, ptr_align, new_vec, vec_ty, .normal);
2340
2341 return .none;
2342}
2343
23152344fn airPtrElemVal(self: *FuncGen, inst: Air.Inst.Index) Allocator.Error!Builder.Value {
23162345 const zcu = self.object.zcu;
23172346 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
......@@ -4559,116 +4588,50 @@ fn airFpext(self: *FuncGen, inst: Air.Inst.Index) Allocator.Error!Builder.Value
45594588 }
45604589}
45614590
4562fn airBitCast(self: *FuncGen, inst: Air.Inst.Index) Allocator.Error!Builder.Value {
4563 const ty_op = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
4564 const operand_ty = self.typeOf(ty_op.operand);
4565 const inst_ty = self.typeOfIndex(inst);
4566 const operand = try self.resolveInst(ty_op.operand);
4567 return self.bitCast(operand, operand_ty, inst_ty);
4568}
4569
4570fn bitCast(self: *FuncGen, operand: Builder.Value, operand_ty: Type, inst_ty: Type) Allocator.Error!Builder.Value {
4571 const o = self.object;
4591fn airBitCast(fg: *FuncGen, inst: Air.Inst.Index) Allocator.Error!Builder.Value {
4592 const o = fg.object;
45724593 const zcu = o.zcu;
4573 const operand_is_ref = isByRef(operand_ty, zcu);
4574 const result_is_ref = isByRef(inst_ty, zcu);
4575 const llvm_dest_ty = try o.lowerType(inst_ty);
45764594
4577 if (operand_is_ref and result_is_ref) {
4578 // They are both pointers, so just return the same opaque pointer :)
4579 return operand;
4580 }
4595 const ty_op = fg.air.instructions.items(.data)[@intFromEnum(inst)].ty_op;
4596 const operand_ty = fg.typeOf(ty_op.operand);
4597 const dest_ty = fg.typeOfIndex(inst);
4598 const operand = try fg.resolveInst(ty_op.operand);
45814599
4582 if (inst_ty.isAbiInt(zcu) and operand_ty.isAbiInt(zcu)) {
4583 assert(inst_ty.bitSize(zcu) == operand_ty.bitSize(zcu));
4600 // We have the following `Air.Legalize` features enabled:
4601 //
4602 // * `.scalarize_bitcast_array`
4603 // * `.scalarize_bitcast_vector_non_elementwise`
4604 //
4605 // That means the set of bitcasts we might see is limited to the following:
4606 //
4607 // * bool/int/float <-> bool/int/float
4608 // * `@Vector(n, A)` <-> `@Vector(n, B)`
4609 // * pointer <-> pointer
4610 // * pointer <-> int
4611 // * slice <-> slice
4612 //
4613 // Most of these can be handled by LLVM's `bitcast` instruction. We will check for the few cases
4614 // that aren't, and otherwise use `bitcast`.
4615
4616 if (operand_ty.isSlice(zcu) and dest_ty.isSlice(zcu)) {
4617 // The slice types are the same type in LLVM IR, so this conversion is a nop.
45844618 return operand;
45854619 }
45864620
4587 const operand_scalar_ty = operand_ty.scalarType(zcu);
4588 const inst_scalar_ty = inst_ty.scalarType(zcu);
4589 if (operand_scalar_ty.zigTypeTag(zcu) == .int and inst_scalar_ty.isPtrAtRuntime(zcu)) {
4590 return self.wip.cast(.inttoptr, operand, llvm_dest_ty, "");
4591 }
4592 if (operand_scalar_ty.isPtrAtRuntime(zcu) and inst_scalar_ty.zigTypeTag(zcu) == .int) {
4593 return self.wip.cast(.ptrtoint, operand, llvm_dest_ty, "");
4594 }
4595
4596 if (operand_ty.zigTypeTag(zcu) == .vector and inst_ty.zigTypeTag(zcu) == .array) {
4597 const elem_ty = operand_scalar_ty;
4598 assert(result_is_ref); // arrays are always by-ref provided they have runtime bits
4599 const alignment = inst_ty.abiAlignment(zcu);
4600 const array_ptr = try self.buildAlloca(llvm_dest_ty, alignment.toLlvm());
4601 const bitcast_ok = elem_ty.bitSize(zcu) == elem_ty.abiSize(zcu) * 8;
4602 if (bitcast_ok) {
4603 try self.store(array_ptr, alignment, operand, operand_ty, .normal);
4604 } else {
4605 // If the ABI size of the element type is not evenly divisible by size in bits;
4606 // a simple bitcast will not work, and we fall back to extractelement.
4607 const elem_size = elem_ty.abiSize(zcu);
4608 const vector_len = operand_ty.arrayLen(zcu);
4609 var i: u64 = 0;
4610 while (i < vector_len) : (i += 1) {
4611 const arr_elem_ptr = try self.ptraddConst(array_ptr, i * elem_size);
4612 const vec_elem = try self.wip.extractElement(operand, try o.builder.intValue(.i32, i), "");
4613 try self.store(arr_elem_ptr, .none, vec_elem, elem_ty, .normal);
4614 }
4615 }
4616 return array_ptr;
4617 } else if (operand_ty.zigTypeTag(zcu) == .array and inst_ty.zigTypeTag(zcu) == .vector) {
4618 const elem_ty = operand_ty.childType(zcu);
4619 assert(operand_is_ref); // arrays are always by-ref provided they have runtime bits
4620 const llvm_vector_ty = try o.lowerType(inst_ty);
4621
4622 const bitcast_ok = elem_ty.bitSize(zcu) == elem_ty.abiSize(zcu) * 8;
4623 if (bitcast_ok) {
4624 // The array is aligned to the element's alignment, while the vector might have a completely
4625 // different alignment. This means we need to enforce the alignment of this load.
4626 return self.load(operand, elem_ty.abiAlignment(zcu), inst_ty, .normal);
4627 } else {
4628 // If the ABI size of the element type is not evenly divisible by size in bits;
4629 // a simple bitcast will not work, and we fall back to extractelement.
4630 const elem_size = elem_ty.abiSize(zcu);
4631 const vector_len = operand_ty.arrayLen(zcu);
4632 var vector = try o.builder.poisonValue(llvm_vector_ty);
4633 var i: u64 = 0;
4634 while (i < vector_len) : (i += 1) {
4635 const arr_elem_ptr = try self.ptraddConst(operand, i * elem_size);
4636 const arr_elem = try self.load(arr_elem_ptr, .none, elem_ty, .normal);
4637 vector = try self.wip.insertElement(vector, arr_elem, try o.builder.intValue(.i32, i), "");
4638 }
4639 return vector;
4640 }
4641 }
4621 assert(!isByRef(operand_ty, zcu));
4622 assert(!isByRef(dest_ty, zcu));
46424623
4643 if (operand_is_ref) {
4644 return self.load(operand, operand_ty.abiAlignment(zcu), inst_ty, .normal);
4645 }
4624 const llvm_dest_ty = try o.lowerType(dest_ty);
46464625
4647 if (result_is_ref) {
4648 const alignment = operand_ty.abiAlignment(zcu).max(inst_ty.abiAlignment(zcu));
4649 const llvm_alloc_ty = if (operand_ty.abiSize(zcu) > inst_ty.abiSize(zcu))
4650 try o.lowerType(operand_ty)
4651 else
4652 llvm_dest_ty;
4653 const result_ptr = try self.buildAlloca(llvm_alloc_ty, alignment.toLlvm());
4654 try self.store(result_ptr, alignment, operand, operand_ty, .normal);
4655 return result_ptr;
4626 if (operand_ty.scalarType(zcu).zigTypeTag(zcu) == .int and dest_ty.scalarType(zcu).isPtrAtRuntime(zcu)) {
4627 return fg.wip.cast(.inttoptr, operand, llvm_dest_ty, "");
46564628 }
46574629
4658 if (inst_ty.isSliceAtRuntime(zcu) or
4659 ((operand_ty.zigTypeTag(zcu) == .vector or inst_ty.zigTypeTag(zcu) == .vector) and
4660 operand_ty.bitSize(zcu) != inst_ty.bitSize(zcu)))
4661 {
4662 // Both our operand and our result are values, not pointers,
4663 // but LLVM won't let us bitcast struct values or vectors with padding bits.
4664 // Therefore, we store operand to alloca, then load for result.
4665 const alignment = operand_ty.abiAlignment(zcu).max(inst_ty.abiAlignment(zcu));
4666 const result_ptr = try self.buildAlloca(llvm_dest_ty, alignment.toLlvm());
4667 try self.store(result_ptr, alignment, operand, operand_ty, .normal);
4668 return self.load(result_ptr, alignment, inst_ty, .normal);
4630 if (operand_ty.scalarType(zcu).isPtrAtRuntime(zcu) and dest_ty.scalarType(zcu).zigTypeTag(zcu) == .int) {
4631 return fg.wip.cast(.ptrtoint, operand, llvm_dest_ty, "");
46694632 }
46704633
4671 return self.wip.cast(.bitcast, operand, llvm_dest_ty, "");
4634 return fg.wip.cast(.bitcast, operand, llvm_dest_ty, "");
46724635}
46734636
46744637fn airArg(self: *FuncGen, inst: Air.Inst.Index) Allocator.Error!Builder.Value {
......@@ -5333,11 +5296,25 @@ fn airMemset(self: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error
53335296 const value = try self.resolveInst(bin_op.rhs);
53345297 const elem_abi_size = elem_ty.abiSize(zcu);
53355298
5336 if (allow_byte_memset and elem_abi_size == 1 and elem_ty.bitSize(zcu) == 8) {
5337 // In this case we can take advantage of LLVM's intrinsic.
5338 const fill_byte = try self.bitCast(value, elem_ty, Type.u8);
5299 intrinsic: {
5300 if (!allow_byte_memset) break :intrinsic;
5301 if (elem_abi_size != 1) break :intrinsic;
5302 // To use LLVM's intrinsic, we need to convert the operand to a raw 8-bit integer value.
5303 const fill_byte: Builder.Value = byte: {
5304 if (isByRef(elem_ty, zcu)) {
5305 break :byte try self.load(value, elem_ty.abiAlignment(zcu), .u8, .normal);
5306 }
5307 if (elem_ty.isAbiInt(zcu)) {
5308 const info = elem_ty.intInfo(zcu);
5309 break :byte self.wip.conv(info.signedness, value, .i8, "");
5310 }
5311 if (elem_ty == .bool) {
5312 break :byte self.wip.cast(.zext, value, .i8, "");
5313 }
5314 break :intrinsic;
5315 };
5316 // Great, we can use the intrinsic!
53395317 const len = try self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);
5340
53415318 _ = try self.wip.callMemSet(
53425319 dest_ptr,
53435320 dest_ptr_align.toLlvm(),