diff options
Diffstat (limited to 'src/codegen/llvm.zig')
| -rw-r--r-- | src/codegen/llvm.zig | 193 |
1 files changed, 118 insertions, 75 deletions
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 5331862a14..b727404bfb 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -4568,14 +4568,14 @@ pub const FuncGen = struct { .ret_addr => try self.airRetAddr(inst), .frame_addr => try self.airFrameAddress(inst), .cond_br => try self.airCondBr(inst), - .@"try" => try self.airTry(inst), + .@"try" => try self.airTry(body[i..]), .try_ptr => try self.airTryPtr(inst), .intcast => try self.airIntCast(inst), .trunc => try self.airTrunc(inst), .fptrunc => try self.airFptrunc(inst), .fpext => try self.airFpext(inst), .ptrtoint => try self.airPtrToInt(inst), - .load => try self.airLoad(inst, body, i + 1), + .load => try self.airLoad(body[i..]), .loop => try self.airLoop(inst), .not => try self.airNot(inst), .ret => try self.airRet(inst), @@ -4634,7 +4634,7 @@ pub const FuncGen = struct { .atomic_store_seq_cst => try self.airAtomicStore(inst, .SequentiallyConsistent), .struct_field_ptr => try self.airStructFieldPtr(inst), - .struct_field_val => try self.airStructFieldVal(inst), + .struct_field_val => try self.airStructFieldVal(body[i..]), .struct_field_ptr_index_0 => try self.airStructFieldPtrIndex(inst, 0), .struct_field_ptr_index_1 => try self.airStructFieldPtrIndex(inst, 1), @@ -4643,18 +4643,18 @@ pub const FuncGen = struct { .field_parent_ptr => try self.airFieldParentPtr(inst), - .array_elem_val => try self.airArrayElemVal(inst), - .slice_elem_val => try self.airSliceElemVal(inst), + .array_elem_val => try self.airArrayElemVal(body[i..]), + .slice_elem_val => try self.airSliceElemVal(body[i..]), .slice_elem_ptr => try self.airSliceElemPtr(inst), - .ptr_elem_val => try self.airPtrElemVal(inst), + .ptr_elem_val => try self.airPtrElemVal(body[i..]), .ptr_elem_ptr => try self.airPtrElemPtr(inst), - .optional_payload => try self.airOptionalPayload(inst), + .optional_payload => try self.airOptionalPayload(body[i..]), .optional_payload_ptr => try self.airOptionalPayloadPtr(inst), .optional_payload_ptr_set => try self.airOptionalPayloadPtrSet(inst), - .unwrap_errunion_payload => try self.airErrUnionPayload(inst, false), - .unwrap_errunion_payload_ptr => try self.airErrUnionPayload(inst, true), + .unwrap_errunion_payload => try self.airErrUnionPayload(body[i..], false), + .unwrap_errunion_payload_ptr => try self.airErrUnionPayload(body[i..], true), .unwrap_errunion_err => try self.airErrUnionErr(inst, false), .unwrap_errunion_err_ptr => try self.airErrUnionErr(inst, true), .errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst), @@ -5159,8 +5159,8 @@ pub const FuncGen = struct { _ = self.builder.buildBr(end_block); self.builder.positionBuilderAtEnd(both_pl_block); - const lhs_payload = try self.optPayloadHandle(opt_llvm_ty, lhs, scalar_ty); - const rhs_payload = try self.optPayloadHandle(opt_llvm_ty, rhs, scalar_ty); + const lhs_payload = try self.optPayloadHandle(opt_llvm_ty, lhs, scalar_ty, true); + const rhs_payload = try self.optPayloadHandle(opt_llvm_ty, rhs, scalar_ty, true); const payload_cmp = try self.cmp(lhs_payload, rhs_payload, payload_ty, op); _ = self.builder.buildBr(end_block); const both_pl_block_end = self.builder.getInsertBlock(); @@ -5305,14 +5305,16 @@ pub const FuncGen = struct { return null; } - fn airTry(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value { + fn airTry(self: *FuncGen, body_tail: []const Air.Inst.Index) !?*llvm.Value { + const inst = body_tail[0]; const pl_op = self.air.instructions.items(.data)[inst].pl_op; const err_union = try self.resolveInst(pl_op.operand); const extra = self.air.extraData(Air.Try, pl_op.payload); const body = self.air.extra[extra.end..][0..extra.data.body_len]; const err_union_ty = self.air.typeOf(pl_op.operand); - const result_ty = self.air.typeOfIndex(inst); - return lowerTry(self, err_union, body, err_union_ty, false, result_ty); + const payload_ty = self.air.typeOfIndex(inst); + const can_elide_load = if (isByRef(payload_ty)) self.canElideLoad(body_tail) else false; + return lowerTry(self, err_union, body, err_union_ty, false, can_elide_load, payload_ty); } fn airTryPtr(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value { @@ -5321,8 +5323,8 @@ pub const FuncGen = struct { const err_union_ptr = try self.resolveInst(extra.data.ptr); const body = self.air.extra[extra.end..][0..extra.data.body_len]; const err_union_ty = self.air.typeOf(extra.data.ptr).childType(); - const result_ty = self.air.typeOfIndex(inst); - return lowerTry(self, err_union_ptr, body, err_union_ty, true, result_ty); + const payload_ty = self.air.typeOfIndex(inst); + return lowerTry(self, err_union_ptr, body, err_union_ty, true, true, payload_ty); } fn lowerTry( @@ -5331,6 +5333,7 @@ pub const FuncGen = struct { body: []const Air.Inst.Index, err_union_ty: Type, operand_is_ptr: bool, + can_elide_load: bool, result_ty: Type, ) !?*llvm.Value { const payload_ty = err_union_ty.errorUnionPayload(); @@ -5379,12 +5382,15 @@ pub const FuncGen = struct { return fg.builder.buildBitCast(err_union, res_ptr_ty, ""); } const offset = errUnionPayloadOffset(payload_ty, target); - if (operand_is_ptr or isByRef(payload_ty)) { + if (operand_is_ptr) { return fg.builder.buildStructGEP(err_union_llvm_ty, err_union, offset, ""); } else if (isByRef(err_union_ty)) { const payload_ptr = fg.builder.buildStructGEP(err_union_llvm_ty, err_union, offset, ""); if (isByRef(payload_ty)) { - return payload_ptr; + if (can_elide_load) + return payload_ptr; + + return fg.loadByRef(payload_ptr, payload_ty, payload_ty.abiAlignment(target), false); } const load_inst = fg.builder.buildLoad(payload_ptr.getGEPResultElementType(), payload_ptr, ""); load_inst.setAlignment(payload_ty.abiAlignment(target)); @@ -5625,14 +5631,16 @@ pub const FuncGen = struct { return self.builder.buildStructGEP(slice_llvm_ty, slice_ptr, index, ""); } - fn airSliceElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value { + fn airSliceElemVal(self: *FuncGen, body_tail: []const Air.Inst.Index) !?*llvm.Value { + const inst = body_tail[0]; const bin_op = self.air.instructions.items(.data)[inst].bin_op; const slice_ty = self.air.typeOf(bin_op.lhs); if (!slice_ty.isVolatilePtr() and self.liveness.isUnused(inst)) return null; const slice = try self.resolveInst(bin_op.lhs); const index = try self.resolveInst(bin_op.rhs); - const llvm_elem_ty = try self.dg.lowerPtrElemTy(slice_ty.childType()); + const elem_ty = slice_ty.childType(); + const llvm_elem_ty = try self.dg.lowerPtrElemTy(elem_ty); const base_ptr = self.builder.buildExtractValue(slice, 0, ""); const indices: [1]*llvm.Value = .{index}; const ptr = self.builder.buildInBoundsGEP(llvm_elem_ty, base_ptr, &indices, indices.len, ""); @@ -5653,7 +5661,8 @@ pub const FuncGen = struct { return self.builder.buildInBoundsGEP(llvm_elem_ty, base_ptr, &indices, indices.len, ""); } - fn airArrayElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value { + fn airArrayElemVal(self: *FuncGen, body_tail: []const Air.Inst.Index) !?*llvm.Value { + const inst = body_tail[0]; if (self.liveness.isUnused(inst)) return null; const bin_op = self.air.instructions.items(.data)[inst].bin_op; @@ -5666,7 +5675,11 @@ pub const FuncGen = struct { const elem_ptr = self.builder.buildInBoundsGEP(array_llvm_ty, array_llvm_val, &indices, indices.len, ""); const elem_ty = array_ty.childType(); if (isByRef(elem_ty)) { - return elem_ptr; + if (canElideLoad(self, body_tail)) + return elem_ptr; + + const target = self.dg.module.getTarget(); + return self.loadByRef(elem_ptr, elem_ty, elem_ty.abiAlignment(target), false); } else { const elem_llvm_ty = try self.dg.lowerType(elem_ty); return self.builder.buildLoad(elem_llvm_ty, elem_ptr, ""); @@ -5677,12 +5690,14 @@ pub const FuncGen = struct { return self.builder.buildExtractElement(array_llvm_val, rhs, ""); } - fn airPtrElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value { + fn airPtrElemVal(self: *FuncGen, body_tail: []const Air.Inst.Index) !?*llvm.Value { + const inst = body_tail[0]; const bin_op = self.air.instructions.items(.data)[inst].bin_op; const ptr_ty = self.air.typeOf(bin_op.lhs); if (!ptr_ty.isVolatilePtr() and self.liveness.isUnused(inst)) return null; - const llvm_elem_ty = try self.dg.lowerPtrElemTy(ptr_ty.childType()); + const elem_ty = ptr_ty.childType(); + const llvm_elem_ty = try self.dg.lowerPtrElemTy(elem_ty); const base_ptr = try self.resolveInst(bin_op.lhs); const rhs = try self.resolveInst(bin_op.rhs); // TODO: when we go fully opaque pointers in LLVM 16 we can remove this branch @@ -5743,7 +5758,8 @@ pub const FuncGen = struct { return self.fieldPtr(inst, struct_ptr, struct_ptr_ty, field_index); } - fn airStructFieldVal(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value { + fn airStructFieldVal(self: *FuncGen, body_tail: []const Air.Inst.Index) !?*llvm.Value { + const inst = body_tail[0]; if (self.liveness.isUnused(inst)) return null; const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; @@ -5826,7 +5842,10 @@ pub const FuncGen = struct { const llvm_field_ty = try self.dg.lowerType(field_ty); const field_ptr = self.builder.buildBitCast(union_field_ptr, llvm_field_ty.pointerType(0), ""); if (isByRef(field_ty)) { - return field_ptr; + if (canElideLoad(self, body_tail)) + return field_ptr; + + return self.loadByRef(field_ptr, field_ty, layout.payload_align, false); } else { return self.builder.buildLoad(llvm_field_ty, field_ptr, ""); } @@ -6516,7 +6535,8 @@ pub const FuncGen = struct { return self.builder.buildStructGEP(optional_llvm_ty, operand, 0, ""); } - fn airOptionalPayload(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value { + fn airOptionalPayload(self: *FuncGen, body_tail: []const Air.Inst.Index) !?*llvm.Value { + const inst = body_tail[0]; if (self.liveness.isUnused(inst)) return null; const ty_op = self.air.instructions.items(.data)[inst].ty_op; @@ -6531,14 +6551,16 @@ pub const FuncGen = struct { } const opt_llvm_ty = try self.dg.lowerType(optional_ty); - return self.optPayloadHandle(opt_llvm_ty, operand, optional_ty); + const can_elide_load = if (isByRef(payload_ty)) self.canElideLoad(body_tail) else false; + return self.optPayloadHandle(opt_llvm_ty, operand, optional_ty, can_elide_load); } fn airErrUnionPayload( self: *FuncGen, - inst: Air.Inst.Index, + body_tail: []const Air.Inst.Index, operand_is_ptr: bool, ) !?*llvm.Value { + const inst = body_tail[0]; if (self.liveness.isUnused(inst)) return null; const ty_op = self.air.instructions.items(.data)[inst].ty_op; @@ -6558,12 +6580,15 @@ pub const FuncGen = struct { } const offset = errUnionPayloadOffset(payload_ty, target); const err_union_llvm_ty = try self.dg.lowerType(err_union_ty); - if (operand_is_ptr or isByRef(payload_ty)) { + if (operand_is_ptr) { return self.builder.buildStructGEP(err_union_llvm_ty, operand, offset, ""); } else if (isByRef(err_union_ty)) { const payload_ptr = self.builder.buildStructGEP(err_union_llvm_ty, operand, offset, ""); if (isByRef(payload_ty)) { - return payload_ptr; + if (self.canElideLoad(body_tail)) + return payload_ptr; + + return self.loadByRef(payload_ptr, payload_ty, payload_ty.abiAlignment(target), false); } const load_inst = self.builder.buildLoad(payload_ptr.getGEPResultElementType(), payload_ptr, ""); load_inst.setAlignment(payload_ty.abiAlignment(target)); @@ -8064,35 +8089,37 @@ pub const FuncGen = struct { return null; } - fn airLoad( - self: *FuncGen, - inst: Air.Inst.Index, - body: []const Air.Inst.Index, - body_i: usize, - ) !?*llvm.Value { - const ty_op = self.air.instructions.items(.data)[inst].ty_op; - const ptr_ty = self.air.typeOf(ty_op.operand); + /// As an optimization, we want to avoid unnecessary copies of isByRef=true + /// types. Here, we scan forward in the current block, looking to see if + /// this load dies before any side effects occur. In such case, we can + /// safely return the operand without making a copy. + /// + /// The first instruction of `body_tail` is the one whose copy we want to elide. + fn canElideLoad(fg: *FuncGen, body_tail: []const Air.Inst.Index) bool { + for (body_tail[1..]) |body_inst| { + switch (fg.liveness.categorizeOperand(fg.air, body_inst, body_tail[0])) { + .none => continue, + .write, .noret, .complex => return false, + .tomb => return true, + } + } else unreachable; + } + + fn airLoad(fg: *FuncGen, body_tail: []const Air.Inst.Index) !?*llvm.Value { + const inst = body_tail[0]; + const ty_op = fg.air.instructions.items(.data)[inst].ty_op; + const ptr_ty = fg.air.typeOf(ty_op.operand); + const ptr_info = ptr_ty.ptrInfo().data; + const ptr = try fg.resolveInst(ty_op.operand); + elide: { - const ptr_info = ptr_ty.ptrInfo().data; if (ptr_info.@"volatile") break :elide; - if (self.liveness.isUnused(inst)) return null; + if (fg.liveness.isUnused(inst)) return null; if (!isByRef(ptr_info.pointee_type)) break :elide; - - // It would be valid to fall back to the code below here that simply calls - // load(). However, as an optimization, we want to avoid unnecessary copies - // of isByRef=true types. Here, we scan forward in the current block, - // looking to see if this load dies before any side effects occur. - // In such case, we can safely return the operand without making a copy. - for (body[body_i..]) |body_inst| { - switch (self.liveness.categorizeOperand(self.air, body_inst, inst)) { - .none => continue, - .write, .noret, .complex => break :elide, - .tomb => return try self.resolveInst(ty_op.operand), - } - } else unreachable; + if (!canElideLoad(fg, body_tail)) break :elide; + return ptr; } - const ptr = try self.resolveInst(ty_op.operand); - return self.load(ptr, ptr_ty); + return fg.load(ptr, ptr_ty); } fn airBreakpoint(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value { @@ -9412,6 +9439,7 @@ pub const FuncGen = struct { opt_llvm_ty: *llvm.Type, opt_handle: *llvm.Value, opt_ty: Type, + can_elide_load: bool, ) !*llvm.Value { var buf: Type.Payload.ElemType = undefined; const payload_ty = opt_ty.optionalChild(&buf); @@ -9420,11 +9448,14 @@ pub const FuncGen = struct { // We have a pointer and we need to return a pointer to the first field. const payload_ptr = fg.builder.buildStructGEP(opt_llvm_ty, opt_handle, 0, ""); - if (isByRef(payload_ty)) { - return payload_ptr; - } const target = fg.dg.module.getTarget(); const payload_alignment = payload_ty.abiAlignment(target); + if (isByRef(payload_ty)) { + if (can_elide_load) + return payload_ptr; + + return fg.loadByRef(payload_ptr, payload_ty, payload_alignment, false); + } const payload_llvm_ty = try fg.dg.lowerType(payload_ty); const load_inst = fg.builder.buildLoad(payload_llvm_ty, payload_ptr, ""); load_inst.setAlignment(payload_alignment); @@ -9559,6 +9590,32 @@ pub const FuncGen = struct { return self.llvmModule().getIntrinsicDeclaration(id, types.ptr, types.len); } + /// Load a by-ref type by constructing a new alloca and performing a memcpy. + fn loadByRef( + fg: *FuncGen, + ptr: *llvm.Value, + pointee_type: Type, + ptr_alignment: u32, + is_volatile: bool, + ) !*llvm.Value { + const pointee_llvm_ty = try fg.dg.lowerType(pointee_type); + const target = fg.dg.module.getTarget(); + const result_align = @max(ptr_alignment, pointee_type.abiAlignment(target)); + const result_ptr = fg.buildAlloca(pointee_llvm_ty, result_align); + const llvm_ptr_u8 = fg.context.intType(8).pointerType(0); + const llvm_usize = fg.context.intType(Type.usize.intInfo(target).bits); + const size_bytes = pointee_type.abiSize(target); + _ = fg.builder.buildMemCpy( + fg.builder.buildBitCast(result_ptr, llvm_ptr_u8, ""), + result_align, + fg.builder.buildBitCast(ptr, llvm_ptr_u8, ""), + ptr_alignment, + llvm_usize.constInt(size_bytes, .False), + is_volatile, + ); + return result_ptr; + } + /// This function always performs a copy. For isByRef=true types, it creates a new /// alloca and copies the value into it, then returns the alloca instruction. /// For isByRef=false types, it creates a load instruction and returns it. @@ -9570,24 +9627,10 @@ pub const FuncGen = struct { const ptr_alignment = info.alignment(target); const ptr_volatile = llvm.Bool.fromBool(ptr_ty.isVolatilePtr()); if (info.host_size == 0) { - const elem_llvm_ty = try self.dg.lowerType(info.pointee_type); if (isByRef(info.pointee_type)) { - const result_align = info.pointee_type.abiAlignment(target); - const max_align = @max(result_align, ptr_alignment); - const result_ptr = self.buildAlloca(elem_llvm_ty, max_align); - const llvm_ptr_u8 = self.context.intType(8).pointerType(0); - const llvm_usize = self.context.intType(Type.usize.intInfo(target).bits); - const size_bytes = info.pointee_type.abiSize(target); - _ = self.builder.buildMemCpy( - self.builder.buildBitCast(result_ptr, llvm_ptr_u8, ""), - max_align, - self.builder.buildBitCast(ptr, llvm_ptr_u8, ""), - max_align, - llvm_usize.constInt(size_bytes, .False), - info.@"volatile", - ); - return result_ptr; + return self.loadByRef(ptr, info.pointee_type, ptr_alignment, info.@"volatile"); } + const elem_llvm_ty = try self.dg.lowerType(info.pointee_type); const llvm_inst = self.builder.buildLoad(elem_llvm_ty, ptr, ""); llvm_inst.setAlignment(ptr_alignment); llvm_inst.setVolatile(ptr_volatile); |
