diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/RangeSet.zig | 6 | ||||
| -rw-r--r-- | src/Sema.zig | 367 | ||||
| -rw-r--r-- | src/codegen/llvm.zig | 218 | ||||
| -rw-r--r-- | src/type.zig | 8 | ||||
| -rw-r--r-- | src/value.zig | 20 |
5 files changed, 367 insertions, 252 deletions
diff --git a/src/RangeSet.zig b/src/RangeSet.zig index 84cae34365..a5007ef7c8 100644 --- a/src/RangeSet.zig +++ b/src/RangeSet.zig @@ -35,8 +35,8 @@ pub fn add( src: SwitchProngSrc, ) !?SwitchProngSrc { for (self.ranges.items) |range| { - if (last.compare(.gte, range.first, ty, self.module) and - first.compare(.lte, range.last, ty, self.module)) + if (last.compareAll(.gte, range.first, ty, self.module) and + first.compareAll(.lte, range.last, ty, self.module)) { return range.src; // They overlap. } @@ -53,7 +53,7 @@ const LessThanContext = struct { ty: Type, module: *Module }; /// Assumes a and b do not overlap fn lessThan(ctx: LessThanContext, a: Range, b: Range) bool { - return a.first.compare(.lt, b.first, ctx.ty, ctx.module); + return a.first.compareAll(.lt, b.first, ctx.ty, ctx.module); } pub fn spans(self: *RangeSet, first: Value, last: Value, ty: Type) !bool { diff --git a/src/Sema.zig b/src/Sema.zig index d24fe4caa9..0ca92fb649 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -4164,6 +4164,7 @@ fn validateStructInit( // We expect to see something like this in the current block AIR: // %a = field_ptr(...) // store(%a, %b) + // With an optional bitcast between the store and the field_ptr. // If %b is a comptime operand, this field is comptime. // // However, in the case of a comptime-known pointer to a struct, the @@ -4374,75 +4375,65 @@ fn zirValidateArrayInit( const elem_ptr_air_ref = sema.inst_map.get(elem_ptr).?; const elem_ptr_air_inst = Air.refToIndex(elem_ptr_air_ref).?; - // Find the block index of the elem_ptr so that we can look at the next - // instruction after it within the same block. + + // We expect to see something like this in the current block AIR: + // %a = elem_ptr(...) + // store(%a, %b) + // With an optional bitcast between the store and the elem_ptr. + // If %b is a comptime operand, this element is comptime. + // + // However, in the case of a comptime-known pointer to an array, the + // the elem_ptr instruction is missing, so we have to pattern-match + // based only on the store instructions. + // `first_block_index` needs to point to the `elem_ptr` if it exists; + // the `store` otherwise. + // + // It's also possible for there to be no store instruction, in the case + // of nested `coerce_result_ptr` instructions. If we see the `elem_ptr` + // but we have not found a `store`, treat as a runtime-known element. + // + // This is nearly identical to similar logic in `validateStructInit`. + // Possible performance enhancement: save the `block_index` between iterations // of the for loop. var block_index = block.instructions.items.len - 1; - while (block.instructions.items[block_index] != elem_ptr_air_inst) { - if (block_index == 0) { + while (block_index > 0) : (block_index -= 1) { + const store_inst = block.instructions.items[block_index]; + if (store_inst == elem_ptr_air_inst) { array_is_comptime = false; continue :outer; } - block_index -= 1; - } - first_block_index = @min(first_block_index, block_index); - - // If the next instructon is a store with a comptime operand, this element - // is comptime. - const next_air_inst = block.instructions.items[block_index + 1]; - switch (air_tags[next_air_inst]) { - .store => { - const bin_op = air_datas[next_air_inst].bin_op; - var lhs = bin_op.lhs; - if (Air.refToIndex(lhs)) |lhs_index| { - if (air_tags[lhs_index] == .bitcast) { - lhs = air_datas[lhs_index].ty_op.operand; - block_index -= 1; - } - } - if (lhs != elem_ptr_air_ref) { - array_is_comptime = false; - continue; - } - if (try sema.resolveMaybeUndefValAllowVariablesMaybeRuntime(block, elem_src, bin_op.rhs, &make_runtime)) |val| { - element_vals[i] = val; - } else { - array_is_comptime = false; - } - continue; - }, - .bitcast => { - // %a = bitcast(*arr_ty, %array_base) - // %b = ptr_elem_ptr(%a, %index) - // %c = bitcast(*elem_ty, %b) - // %d = store(%c, %val) - if (air_datas[next_air_inst].ty_op.operand != elem_ptr_air_ref) { - array_is_comptime = false; - continue; - } - const store_inst = block.instructions.items[block_index + 2]; - if (air_tags[store_inst] != .store) { - array_is_comptime = false; - continue; - } - const bin_op = air_datas[store_inst].bin_op; - if (bin_op.lhs != Air.indexToRef(next_air_inst)) { - array_is_comptime = false; - continue; - } - if (try sema.resolveMaybeUndefValAllowVariablesMaybeRuntime(block, elem_src, bin_op.rhs, &make_runtime)) |val| { - element_vals[i] = val; - } else { - array_is_comptime = false; + if (air_tags[store_inst] != .store) continue; + const bin_op = air_datas[store_inst].bin_op; + var lhs = bin_op.lhs; + { + const lhs_index = Air.refToIndex(lhs) orelse continue; + if (air_tags[lhs_index] == .bitcast) { + lhs = air_datas[lhs_index].ty_op.operand; + block_index -= 1; } - continue; - }, - else => { + } + if (lhs != elem_ptr_air_ref) continue; + while (block_index > 0) : (block_index -= 1) { + const block_inst = block.instructions.items[block_index - 1]; + if (air_tags[block_inst] != .dbg_stmt) break; + } + if (block_index > 0 and + elem_ptr_air_inst == block.instructions.items[block_index - 1]) + { + first_block_index = @min(first_block_index, block_index - 1); + } else { + first_block_index = @min(first_block_index, block_index); + } + if (try sema.resolveMaybeUndefValAllowVariablesMaybeRuntime(block, elem_src, bin_op.rhs, &make_runtime)) |val| { + element_vals[i] = val; + } else { array_is_comptime = false; - continue; - }, + } + continue :outer; } + array_is_comptime = false; + continue :outer; } if (array_is_comptime) { @@ -8966,9 +8957,21 @@ fn intCast( const wanted_bits = wanted_info.bits; if (wanted_bits == 0) { - const zero_inst = try sema.addConstant(sema.typeOf(operand), Value.zero); - const is_in_range = try block.addBinOp(.cmp_eq, operand, zero_inst); - try sema.addSafetyCheck(block, is_in_range, .cast_truncated_data); + const ok = if (is_vector) ok: { + const zeros = try Value.Tag.repeated.create(sema.arena, Value.zero); + const zero_inst = try sema.addConstant(sema.typeOf(operand), zeros); + const is_in_range = try block.addCmpVector(operand, zero_inst, .eq, try sema.addType(operand_ty)); + const all_in_range = try block.addInst(.{ + .tag = .reduce, + .data = .{ .reduce = .{ .operand = is_in_range, .operation = .And } }, + }); + break :ok all_in_range; + } else ok: { + const zero_inst = try sema.addConstant(sema.typeOf(operand), Value.zero); + const is_in_range = try block.addBinOp(.cmp_lte, operand, zero_inst); + break :ok is_in_range; + }; + try sema.addSafetyCheck(block, ok, .cast_truncated_data); } } @@ -10330,8 +10333,8 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError // Validation above ensured these will succeed. const first_tv = sema.resolveInstConst(&child_block, .unneeded, item_first, "") catch unreachable; const last_tv = sema.resolveInstConst(&child_block, .unneeded, item_last, "") catch unreachable; - if ((try sema.compare(block, src, operand_val, .gte, first_tv.val, operand_ty)) and - (try sema.compare(block, src, operand_val, .lte, last_tv.val, operand_ty))) + if ((try sema.compareAll(block, src, operand_val, .gte, first_tv.val, operand_ty)) and + (try sema.compareAll(block, src, operand_val, .lte, last_tv.val, operand_ty))) { if (is_inline) child_block.inline_case_capture = operand; if (err_set) try sema.maybeErrorUnwrapComptime(&child_block, body, operand); @@ -10479,7 +10482,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const item_last_ref = try sema.resolveInst(last_ref); const item_last = sema.resolveConstValue(block, .unneeded, item_last_ref, undefined) catch unreachable; - while (item.compare(.lte, item_last, operand_ty, sema.mod)) : ({ + while (item.compareAll(.lte, item_last, operand_ty, sema.mod)) : ({ // Previous validation has resolved any possible lazy values. item = try sema.intAddScalar(block, .unneeded, item, Value.one); }) { @@ -10934,7 +10937,7 @@ const RangeSetUnhandledIterator = struct { it.cur = try it.sema.intAdd(it.block, it.src, it.cur, Value.one, it.ty); } it.first = false; - if (it.cur.compare(.lt, it.ranges[it.range_i].first, it.ty, it.sema.mod)) { + if (it.cur.compareAll(.lt, it.ranges[it.range_i].first, it.ty, it.sema.mod)) { return it.cur; } it.cur = it.ranges[it.range_i].last; @@ -10943,7 +10946,7 @@ const RangeSetUnhandledIterator = struct { it.cur = try it.sema.intAdd(it.block, it.src, it.cur, Value.one, it.ty); } it.first = false; - if (it.cur.compare(.lte, it.max, it.ty, it.sema.mod)) { + if (it.cur.compareAll(.lte, it.max, it.ty, it.sema.mod)) { return it.cur; } return null; @@ -10989,7 +10992,7 @@ fn validateSwitchRange( ) CompileError!void { const first_val = (try sema.resolveSwitchItemVal(block, first_ref, src_node_offset, switch_prong_src, .first)).val; const last_val = (try sema.resolveSwitchItemVal(block, last_ref, src_node_offset, switch_prong_src, .last)).val; - if (first_val.compare(.gt, last_val, operand_ty, sema.mod)) { + if (first_val.compareAll(.gt, last_val, operand_ty, sema.mod)) { const src = switch_prong_src.resolve(sema.gpa, sema.mod.declPtr(block.src_decl), src_node_offset, .first); return sema.fail(block, src, "range start value is greater than the end value", .{}); } @@ -11453,7 +11456,7 @@ fn zirShl( return sema.addConstUndef(sema.typeOf(lhs)); } // If rhs is 0, return lhs without doing any calculations. - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + if (try rhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src))) { return lhs; } if (scalar_ty.zigTypeTag() != .ComptimeInt and air_tag != .shl_sat) { @@ -11497,7 +11500,7 @@ fn zirShl( if (scalar_ty.zigTypeTag() == .ComptimeInt) { break :val shifted.wrapped_result; } - if (shifted.overflowed.compareWithZero(.eq)) { + if (shifted.overflowed.compareAllWithZero(.eq)) { break :val shifted.wrapped_result; } return sema.fail(block, src, "operation caused overflow", .{}); @@ -11622,7 +11625,7 @@ fn zirShr( return sema.addConstUndef(lhs_ty); } // If rhs is 0, return lhs without doing any calculations. - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + if (try rhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src))) { return lhs; } if (scalar_ty.zigTypeTag() != .ComptimeInt) { @@ -11656,7 +11659,7 @@ fn zirShr( if (air_tag == .shr_exact) { // Detect if any ones would be shifted out. const truncated = try lhs_val.intTruncBitsAsValue(lhs_ty, sema.arena, .unsigned, rhs_val, target); - if (!(try truncated.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { + if (!(try truncated.compareAllWithZeroAdvanced(.eq, sema.kit(block, src)))) { return sema.fail(block, src, "exact shift shifted out 1 bits", .{}); } } @@ -12385,6 +12388,8 @@ fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins .override = &[_]LazySrcLoc{ lhs_src, rhs_src }, }); + const is_vector = resolved_type.zigTypeTag() == .Vector; + const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); @@ -12409,7 +12414,7 @@ fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins const lhs_val = maybe_lhs_val orelse unreachable; const rhs_val = maybe_rhs_val orelse unreachable; const rem = lhs_val.floatRem(rhs_val, resolved_type, sema.arena, target) catch unreachable; - if (rem.compareWithZero(.neq)) { + if (!rem.compareAllWithZero(.eq)) { return sema.fail(block, src, "ambiguous coercion of division operands '{s}' and '{s}'; non-zero remainder '{}'", .{ @tagName(lhs_ty.tag()), @tagName(rhs_ty.tag()), rem.fmtValue(resolved_type, sema.mod), }); @@ -12447,8 +12452,11 @@ fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins .Int, .ComptimeInt, .ComptimeFloat => { if (maybe_lhs_val) |lhs_val| { if (!lhs_val.isUndef()) { - if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.addConstant(resolved_type, Value.zero); + if (try lhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src))) { + const zero_val = if (is_vector) b: { + break :b try Value.Tag.repeated.create(sema.arena, Value.zero); + } else Value.zero; + return sema.addConstant(resolved_type, zero_val); } } } @@ -12456,7 +12464,7 @@ fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins if (rhs_val.isUndef()) { return sema.failWithUseOfUndef(block, rhs_src); } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + if (!(try rhs_val.compareAllWithZeroAdvanced(.neq, sema.kit(block, src)))) { return sema.failWithDivideByZero(block, rhs_src); } // TODO: if the RHS is one, return the LHS directly @@ -12470,7 +12478,7 @@ fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins if (lhs_val.isUndef()) { if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) { if (maybe_rhs_val) |rhs_val| { - if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) { + if (try sema.compareAll(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) { return sema.addConstUndef(resolved_type); } } @@ -12541,6 +12549,8 @@ fn zirDivExact(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai .override = &[_]LazySrcLoc{ lhs_src, rhs_src }, }); + const is_vector = resolved_type.zigTypeTag() == .Vector; + const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); @@ -12577,8 +12587,11 @@ fn zirDivExact(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai if (lhs_val.isUndef()) { return sema.failWithUseOfUndef(block, rhs_src); } else { - if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.addConstant(resolved_type, Value.zero); + if (try lhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src))) { + const zero_val = if (is_vector) b: { + break :b try Value.Tag.repeated.create(sema.arena, Value.zero); + } else Value.zero; + return sema.addConstant(resolved_type, zero_val); } } } @@ -12586,7 +12599,7 @@ fn zirDivExact(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai if (rhs_val.isUndef()) { return sema.failWithUseOfUndef(block, rhs_src); } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + if (!(try rhs_val.compareAllWithZeroAdvanced(.neq, sema.kit(block, src)))) { return sema.failWithDivideByZero(block, rhs_src); } // TODO: if the RHS is one, return the LHS directly @@ -12595,7 +12608,7 @@ fn zirDivExact(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai if (maybe_rhs_val) |rhs_val| { if (is_int) { const modulus_val = try lhs_val.intMod(rhs_val, resolved_type, sema.arena, target); - if (modulus_val.compareWithZero(.neq)) { + if (!(modulus_val.compareAllWithZero(.eq))) { return sema.fail(block, src, "exact division produced remainder", .{}); } const res = try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target); @@ -12606,7 +12619,7 @@ fn zirDivExact(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai return sema.addConstant(resolved_type, res); } else { const modulus_val = try lhs_val.floatMod(rhs_val, resolved_type, sema.arena, target); - if (modulus_val.compareWithZero(.neq)) { + if (!(modulus_val.compareAllWithZero(.eq))) { return sema.fail(block, src, "exact division produced remainder", .{}); } return sema.addConstant( @@ -12700,6 +12713,8 @@ fn zirDivFloor(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai .override = &[_]LazySrcLoc{ lhs_src, rhs_src }, }); + const is_vector = resolved_type.zigTypeTag() == .Vector; + const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); @@ -12738,8 +12753,11 @@ fn zirDivFloor(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai // If the lhs is undefined, result is undefined. if (maybe_lhs_val) |lhs_val| { if (!lhs_val.isUndef()) { - if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.addConstant(resolved_type, Value.zero); + if (try lhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src))) { + const zero_val = if (is_vector) b: { + break :b try Value.Tag.repeated.create(sema.arena, Value.zero); + } else Value.zero; + return sema.addConstant(resolved_type, zero_val); } } } @@ -12747,7 +12765,7 @@ fn zirDivFloor(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai if (rhs_val.isUndef()) { return sema.failWithUseOfUndef(block, rhs_src); } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + if (!(try rhs_val.compareAllWithZeroAdvanced(.neq, sema.kit(block, src)))) { return sema.failWithDivideByZero(block, rhs_src); } // TODO: if the RHS is one, return the LHS directly @@ -12756,7 +12774,7 @@ fn zirDivFloor(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai if (lhs_val.isUndef()) { if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) { if (maybe_rhs_val) |rhs_val| { - if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) { + if (try sema.compareAll(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) { return sema.addConstUndef(resolved_type); } } @@ -12812,6 +12830,8 @@ fn zirDivTrunc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai .override = &[_]LazySrcLoc{ lhs_src, rhs_src }, }); + const is_vector = resolved_type.zigTypeTag() == .Vector; + const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); @@ -12850,8 +12870,11 @@ fn zirDivTrunc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai // If the lhs is undefined, result is undefined. if (maybe_lhs_val) |lhs_val| { if (!lhs_val.isUndef()) { - if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.addConstant(resolved_type, Value.zero); + if (try lhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src))) { + const zero_val = if (is_vector) b: { + break :b try Value.Tag.repeated.create(sema.arena, Value.zero); + } else Value.zero; + return sema.addConstant(resolved_type, zero_val); } } } @@ -12859,7 +12882,7 @@ fn zirDivTrunc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai if (rhs_val.isUndef()) { return sema.failWithUseOfUndef(block, rhs_src); } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + if (!(try rhs_val.compareAllWithZeroAdvanced(.neq, sema.kit(block, src)))) { return sema.failWithDivideByZero(block, rhs_src); } } @@ -12867,7 +12890,7 @@ fn zirDivTrunc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai if (lhs_val.isUndef()) { if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) { if (maybe_rhs_val) |rhs_val| { - if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) { + if (try sema.compareAll(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) { return sema.addConstUndef(resolved_type); } } @@ -12938,12 +12961,12 @@ fn addDivIntOverflowSafety( // If the LHS is comptime-known to be not equal to the min int, // no overflow is possible. if (maybe_lhs_val) |lhs_val| { - if (!lhs_val.compare(.eq, min_int, resolved_type, mod)) return; + if (lhs_val.compareAll(.neq, min_int, resolved_type, mod)) return; } // If the RHS is comptime-known to not be equal to -1, no overflow is possible. if (maybe_rhs_val) |rhs_val| { - if (!rhs_val.compare(.eq, neg_one, resolved_type, mod)) return; + if (rhs_val.compareAll(.neq, neg_one, resolved_type, mod)) return; } var ok: Air.Inst.Ref = .none; @@ -13051,6 +13074,8 @@ fn zirModRem(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. .override = &[_]LazySrcLoc{ lhs_src, rhs_src }, }); + const is_vector = resolved_type.zigTypeTag() == .Vector; + const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); @@ -13086,8 +13111,11 @@ fn zirModRem(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. if (lhs_val.isUndef()) { return sema.failWithUseOfUndef(block, lhs_src); } - if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.addConstant(resolved_type, Value.zero); + if (try lhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src))) { + const zero_val = if (is_vector) b: { + break :b try Value.Tag.repeated.create(sema.arena, Value.zero); + } else Value.zero; + return sema.addConstant(resolved_type, zero_val); } } else if (lhs_scalar_ty.isSignedInt()) { return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty); @@ -13096,25 +13124,20 @@ fn zirModRem(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. if (rhs_val.isUndef()) { return sema.failWithUseOfUndef(block, rhs_src); } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + if (!(try rhs_val.compareAllWithZeroAdvanced(.neq, sema.kit(block, src)))) { return sema.failWithDivideByZero(block, rhs_src); } + if (!(try rhs_val.compareAllWithZeroAdvanced(.gte, sema.kit(block, src)))) { + return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty); + } if (maybe_lhs_val) |lhs_val| { const rem_result = try sema.intRem(block, resolved_type, lhs_val, lhs_src, rhs_val, rhs_src); // If this answer could possibly be different by doing `intMod`, // we must emit a compile error. Otherwise, it's OK. - if ((try rhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) != (try lhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) and - !(try rem_result.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) + if (!(try lhs_val.compareAllWithZeroAdvanced(.gte, sema.kit(block, src))) and + !(try rem_result.compareAllWithZeroAdvanced(.eq, sema.kit(block, src)))) { - const bad_src = if (try lhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) - lhs_src - else - rhs_src; - return sema.failWithModRemNegative(block, bad_src, lhs_ty, rhs_ty); - } - if (try lhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) { - // Negative - return sema.addConstant(resolved_type, Value.zero); + return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty); } return sema.addConstant(resolved_type, rem_result); } @@ -13130,14 +13153,14 @@ fn zirModRem(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. if (rhs_val.isUndef()) { return sema.failWithUseOfUndef(block, rhs_src); } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + if (!(try rhs_val.compareAllWithZeroAdvanced(.neq, sema.kit(block, src)))) { return sema.failWithDivideByZero(block, rhs_src); } - if (try rhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) { + if (!(try rhs_val.compareAllWithZeroAdvanced(.gte, sema.kit(block, src)))) { return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty); } if (maybe_lhs_val) |lhs_val| { - if (lhs_val.isUndef() or (try lhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src)))) { + if (lhs_val.isUndef() or !(try lhs_val.compareAllWithZeroAdvanced(.gte, sema.kit(block, src)))) { return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty); } return sema.addConstant( @@ -13273,7 +13296,7 @@ fn zirMod(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins if (rhs_val.isUndef()) { return sema.failWithUseOfUndef(block, rhs_src); } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + if (!(try rhs_val.compareAllWithZeroAdvanced(.neq, sema.kit(block, src)))) { return sema.failWithDivideByZero(block, rhs_src); } if (maybe_lhs_val) |lhs_val| { @@ -13292,7 +13315,7 @@ fn zirMod(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins if (rhs_val.isUndef()) { return sema.failWithUseOfUndef(block, rhs_src); } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + if (!(try rhs_val.compareAllWithZeroAdvanced(.neq, sema.kit(block, src)))) { return sema.failWithDivideByZero(block, rhs_src); } } @@ -13376,7 +13399,7 @@ fn zirRem(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins if (rhs_val.isUndef()) { return sema.failWithUseOfUndef(block, rhs_src); } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + if (!(try rhs_val.compareAllWithZeroAdvanced(.neq, sema.kit(block, src)))) { return sema.failWithDivideByZero(block, rhs_src); } if (maybe_lhs_val) |lhs_val| { @@ -13395,7 +13418,7 @@ fn zirRem(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins if (rhs_val.isUndef()) { return sema.failWithUseOfUndef(block, rhs_src); } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + if (!(try rhs_val.compareAllWithZeroAdvanced(.neq, sema.kit(block, src)))) { return sema.failWithDivideByZero(block, rhs_src); } } @@ -13474,12 +13497,12 @@ fn zirOverflowArithmetic( // to the result, even if it is undefined.. // Otherwise, if either of the argument is undefined, undefined is returned. if (maybe_lhs_val) |lhs_val| { - if (!lhs_val.isUndef() and (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { + if (!lhs_val.isUndef() and (try lhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src)))) { break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = rhs }; } } if (maybe_rhs_val) |rhs_val| { - if (!rhs_val.isUndef() and (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { + if (!rhs_val.isUndef() and (try rhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src)))) { break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = lhs }; } } @@ -13502,7 +13525,7 @@ fn zirOverflowArithmetic( if (maybe_rhs_val) |rhs_val| { if (rhs_val.isUndef()) { break :result .{ .overflowed = try sema.addConstUndef(overflowed_ty), .wrapped = try sema.addConstUndef(dest_ty) }; - } else if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + } else if (try rhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src))) { break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = lhs }; } else if (maybe_lhs_val) |lhs_val| { if (lhs_val.isUndef()) { @@ -13522,9 +13545,9 @@ fn zirOverflowArithmetic( // Otherwise, if either of the arguments is undefined, both results are undefined. if (maybe_lhs_val) |lhs_val| { if (!lhs_val.isUndef()) { - if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + if (try lhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src))) { break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = lhs }; - } else if (try sema.compare(block, src, lhs_val, .eq, Value.one, dest_ty)) { + } else if (try sema.compareAll(block, src, lhs_val, .eq, Value.one, dest_ty)) { break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = rhs }; } } @@ -13532,9 +13555,9 @@ fn zirOverflowArithmetic( if (maybe_rhs_val) |rhs_val| { if (!rhs_val.isUndef()) { - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + if (try rhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src))) { break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = rhs }; - } else if (try sema.compare(block, src, rhs_val, .eq, Value.one, dest_ty)) { + } else if (try sema.compareAll(block, src, rhs_val, .eq, Value.one, dest_ty)) { break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = lhs }; } } @@ -13558,12 +13581,12 @@ fn zirOverflowArithmetic( // If rhs is zero, the result is lhs (even if undefined) and no overflow occurred. // Oterhwise if either of the arguments is undefined, both results are undefined. if (maybe_lhs_val) |lhs_val| { - if (!lhs_val.isUndef() and (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { + if (!lhs_val.isUndef() and (try lhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src)))) { break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = lhs }; } } if (maybe_rhs_val) |rhs_val| { - if (!rhs_val.isUndef() and (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { + if (!rhs_val.isUndef() and (try rhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src)))) { break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = lhs }; } } @@ -13680,6 +13703,8 @@ fn analyzeArithmetic( .override = &[_]LazySrcLoc{ lhs_src, rhs_src }, }); + const is_vector = resolved_type.zigTypeTag() == .Vector; + const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); @@ -13704,7 +13729,7 @@ fn analyzeArithmetic( // overflow (max_int), causing illegal behavior. // For floats: either operand being undef makes the result undef. if (maybe_lhs_val) |lhs_val| { - if (!lhs_val.isUndef() and (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { + if (!lhs_val.isUndef() and (try lhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src)))) { return casted_rhs; } } @@ -13716,7 +13741,7 @@ fn analyzeArithmetic( return sema.addConstUndef(resolved_type); } } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + if (try rhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src))) { return casted_lhs; } } @@ -13751,7 +13776,7 @@ fn analyzeArithmetic( // If either of the operands are zero, the other operand is returned. // If either of the operands are undefined, the result is undefined. if (maybe_lhs_val) |lhs_val| { - if (!lhs_val.isUndef() and (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { + if (!lhs_val.isUndef() and (try lhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src)))) { return casted_rhs; } } @@ -13760,7 +13785,7 @@ fn analyzeArithmetic( if (rhs_val.isUndef()) { return sema.addConstUndef(resolved_type); } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + if (try rhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src))) { return casted_lhs; } if (maybe_lhs_val) |lhs_val| { @@ -13776,7 +13801,7 @@ fn analyzeArithmetic( // If either of the operands are zero, then the other operand is returned. // If either of the operands are undefined, the result is undefined. if (maybe_lhs_val) |lhs_val| { - if (!lhs_val.isUndef() and (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { + if (!lhs_val.isUndef() and (try lhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src)))) { return casted_rhs; } } @@ -13784,7 +13809,7 @@ fn analyzeArithmetic( if (rhs_val.isUndef()) { return sema.addConstUndef(resolved_type); } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + if (try rhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src))) { return casted_lhs; } if (maybe_lhs_val) |lhs_val| { @@ -13813,7 +13838,7 @@ fn analyzeArithmetic( return sema.addConstUndef(resolved_type); } } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + if (try rhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src))) { return casted_lhs; } } @@ -13851,7 +13876,7 @@ fn analyzeArithmetic( if (rhs_val.isUndef()) { return sema.addConstUndef(resolved_type); } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + if (try rhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src))) { return casted_lhs; } } @@ -13876,7 +13901,7 @@ fn analyzeArithmetic( if (rhs_val.isUndef()) { return sema.addConstUndef(resolved_type); } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + if (try rhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src))) { return casted_lhs; } } @@ -13905,10 +13930,13 @@ fn analyzeArithmetic( // For floats: either operand being undef makes the result undef. if (maybe_lhs_val) |lhs_val| { if (!lhs_val.isUndef()) { - if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.addConstant(resolved_type, Value.zero); + if (try lhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src))) { + const zero_val = if (is_vector) b: { + break :b try Value.Tag.repeated.create(sema.arena, Value.zero); + } else Value.zero; + return sema.addConstant(resolved_type, zero_val); } - if (try sema.compare(block, src, lhs_val, .eq, Value.one, resolved_type)) { + if (try sema.compareAll(block, src, lhs_val, .eq, Value.one, resolved_type)) { return casted_rhs; } } @@ -13922,10 +13950,13 @@ fn analyzeArithmetic( return sema.addConstUndef(resolved_type); } } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.addConstant(resolved_type, Value.zero); + if (try rhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src))) { + const zero_val = if (is_vector) b: { + break :b try Value.Tag.repeated.create(sema.arena, Value.zero); + } else Value.zero; + return sema.addConstant(resolved_type, zero_val); } - if (try sema.compare(block, src, rhs_val, .eq, Value.one, resolved_type)) { + if (try sema.compareAll(block, src, rhs_val, .eq, Value.one, resolved_type)) { return casted_lhs; } if (maybe_lhs_val) |lhs_val| { @@ -13959,10 +13990,13 @@ fn analyzeArithmetic( // If either of the operands are undefined, result is undefined. if (maybe_lhs_val) |lhs_val| { if (!lhs_val.isUndef()) { - if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.addConstant(resolved_type, Value.zero); + if (try lhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src))) { + const zero_val = if (is_vector) b: { + break :b try Value.Tag.repeated.create(sema.arena, Value.zero); + } else Value.zero; + return sema.addConstant(resolved_type, zero_val); } - if (try sema.compare(block, src, lhs_val, .eq, Value.one, resolved_type)) { + if (try sema.compareAll(block, src, lhs_val, .eq, Value.one, resolved_type)) { return casted_rhs; } } @@ -13972,10 +14006,13 @@ fn analyzeArithmetic( if (rhs_val.isUndef()) { return sema.addConstUndef(resolved_type); } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.addConstant(resolved_type, Value.zero); + if (try rhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src))) { + const zero_val = if (is_vector) b: { + break :b try Value.Tag.repeated.create(sema.arena, Value.zero); + } else Value.zero; + return sema.addConstant(resolved_type, zero_val); } - if (try sema.compare(block, src, rhs_val, .eq, Value.one, resolved_type)) { + if (try sema.compareAll(block, src, rhs_val, .eq, Value.one, resolved_type)) { return casted_lhs; } if (maybe_lhs_val) |lhs_val| { @@ -13996,10 +14033,13 @@ fn analyzeArithmetic( // If either of the operands are undefined, result is undefined. if (maybe_lhs_val) |lhs_val| { if (!lhs_val.isUndef()) { - if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.addConstant(resolved_type, Value.zero); + if (try lhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src))) { + const zero_val = if (is_vector) b: { + break :b try Value.Tag.repeated.create(sema.arena, Value.zero); + } else Value.zero; + return sema.addConstant(resolved_type, zero_val); } - if (try sema.compare(block, src, lhs_val, .eq, Value.one, resolved_type)) { + if (try sema.compareAll(block, src, lhs_val, .eq, Value.one, resolved_type)) { return casted_rhs; } } @@ -14008,10 +14048,13 @@ fn analyzeArithmetic( if (rhs_val.isUndef()) { return sema.addConstUndef(resolved_type); } - if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { - return sema.addConstant(resolved_type, Value.zero); + if (try rhs_val.compareAllWithZeroAdvanced(.eq, sema.kit(block, src))) { + const zero_val = if (is_vector) b: { + break :b try Value.Tag.repeated.create(sema.arena, Value.zero); + } else Value.zero; + return sema.addConstant(resolved_type, zero_val); } - if (try sema.compare(block, src, rhs_val, .eq, Value.one, resolved_type)) { + if (try sema.compareAll(block, src, rhs_val, .eq, Value.one, resolved_type)) { return casted_lhs; } if (maybe_lhs_val) |lhs_val| { @@ -14563,7 +14606,7 @@ fn cmpSelf( return sema.addConstant(result_ty, cmp_val); } - if (try sema.compare(block, lhs_src, lhs_val, op, rhs_val, resolved_type)) { + if (try sema.compareAll(block, lhs_src, lhs_val, op, rhs_val, resolved_type)) { return Air.Inst.Ref.bool_true; } else { return Air.Inst.Ref.bool_false; @@ -27769,7 +27812,7 @@ fn analyzeSlice( sema.arena, array_ty.arrayLenIncludingSentinel(), ); - if (try sema.compare(block, src, end_val, .gt, len_s_val, Type.usize)) { + if (!(try sema.compareAll(block, src, end_val, .lte, len_s_val, Type.usize))) { const sentinel_label: []const u8 = if (array_ty.sentinel() != null) " +1 (sentinel)" else @@ -27812,7 +27855,7 @@ fn analyzeSlice( .data = slice_val.sliceLen(mod) + @boolToInt(has_sentinel), }; const slice_len_val = Value.initPayload(&int_payload.base); - if (try sema.compare(block, src, end_val, .gt, slice_len_val, Type.usize)) { + if (!(try sema.compareAll(block, src, end_val, .lte, slice_len_val, Type.usize))) { const sentinel_label: []const u8 = if (has_sentinel) " +1 (sentinel)" else @@ -27871,7 +27914,7 @@ fn analyzeSlice( // requirement: start <= end if (try sema.resolveDefinedValue(block, end_src, end)) |end_val| { if (try sema.resolveDefinedValue(block, start_src, start)) |start_val| { - if (try sema.compare(block, src, start_val, .gt, end_val, Type.usize)) { + if (!(try sema.compareAll(block, src, start_val, .lte, end_val, Type.usize))) { return sema.fail( block, start_src, @@ -28160,11 +28203,11 @@ fn cmpNumeric( // a signed integer with mantissa bits + 1, and if there was any non-integral part of the float, // add/subtract 1. const lhs_is_signed = if (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val| - (try lhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) + !(try lhs_val.compareAllWithZeroAdvanced(.gte, sema.kit(block, src))) else (lhs_ty.isRuntimeFloat() or lhs_ty.isSignedInt()); const rhs_is_signed = if (try sema.resolveDefinedValue(block, rhs_src, rhs)) |rhs_val| - (try rhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) + !(try rhs_val.compareAllWithZeroAdvanced(.gte, sema.kit(block, src))) else (rhs_ty.isRuntimeFloat() or rhs_ty.isSignedInt()); const dest_int_is_signed = lhs_is_signed or rhs_is_signed; @@ -31744,6 +31787,8 @@ fn floatToIntScalar( /// Asserts the value is an integer, and the destination type is ComptimeInt or Int. /// Vectors are also accepted. Vector results are reduced with AND. +/// +/// If provided, `vector_index` reports the first element that failed the range check. fn intFitsInType( sema: *Sema, block: *Block, @@ -31889,13 +31934,13 @@ fn intInRange( int_val: Value, end: usize, ) !bool { - if (try int_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) return false; + if (!(try int_val.compareAllWithZeroAdvanced(.gte, sema.kit(block, src)))) return false; var end_payload: Value.Payload.U64 = .{ .base = .{ .tag = .int_u64 }, .data = end, }; const end_val = Value.initPayload(&end_payload.base); - if (try sema.compare(block, src, int_val, .gte, end_val, tag_ty)) return false; + if (!(try sema.compareAll(block, src, int_val, .lt, end_val, tag_ty))) return false; return true; } @@ -32013,8 +32058,10 @@ fn intAddWithOverflowScalar( } /// Asserts the values are comparable. Both operands have type `ty`. -/// Vector results will be reduced with AND. -fn compare( +/// For vectors, returns true if the comparison is true for ALL elements. +/// +/// Note that `!compareAll(.eq, ...) != compareAll(.neq, ...)` +fn compareAll( sema: *Sema, block: *Block, src: LazySrcLoc, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 5331862a14..e93152df02 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,17 +5631,27 @@ 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, ""); + if (isByRef(elem_ty)) { + if (self.canElideLoad(body_tail)) + return ptr; + + const target = self.dg.module.getTarget(); + return self.loadByRef(ptr, elem_ty, elem_ty.abiAlignment(target), false); + } + return self.load(ptr, slice_ty); } @@ -5653,7 +5669,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 +5683,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 +5698,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 @@ -5694,6 +5717,14 @@ pub const FuncGen = struct { const indices: [1]*llvm.Value = .{rhs}; break :ptr self.builder.buildInBoundsGEP(llvm_elem_ty, base_ptr, &indices, indices.len, ""); }; + if (isByRef(elem_ty)) { + if (self.canElideLoad(body_tail)) + return ptr; + + const target = self.dg.module.getTarget(); + return self.loadByRef(ptr, elem_ty, elem_ty.abiAlignment(target), false); + } + return self.load(ptr, ptr_ty); } @@ -5743,7 +5774,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; @@ -5816,7 +5848,14 @@ pub const FuncGen = struct { const struct_llvm_ty = try self.dg.lowerType(struct_ty); const field_ptr = self.builder.buildStructGEP(struct_llvm_ty, struct_llvm_val, llvm_field_index, ""); const field_ptr_ty = Type.initPayload(&ptr_ty_buf.base); - return self.load(field_ptr, field_ptr_ty); + if (isByRef(field_ty)) { + if (canElideLoad(self, body_tail)) + return field_ptr; + + return self.loadByRef(field_ptr, field_ty, ptr_ty_buf.data.alignment(target), false); + } else { + return self.load(field_ptr, field_ptr_ty); + } }, .Union => { const union_llvm_ty = try self.dg.lowerType(struct_ty); @@ -5826,7 +5865,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 +6558,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 +6574,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 +6603,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 +8112,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 +9462,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 +9471,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 +9613,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 +9650,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); diff --git a/src/type.zig b/src/type.zig index 12c969eaec..f24c89ef6f 100644 --- a/src/type.zig +++ b/src/type.zig @@ -5463,13 +5463,13 @@ pub const Type = extern union { } const S = struct { fn fieldWithRange(int_ty: Type, int_val: Value, end: usize, m: *Module) ?usize { - if (int_val.compareWithZero(.lt)) return null; + if (int_val.compareAllWithZero(.lt)) return null; var end_payload: Value.Payload.U64 = .{ .base = .{ .tag = .int_u64 }, .data = end, }; const end_val = Value.initPayload(&end_payload.base); - if (int_val.compare(.gte, end_val, int_ty, m)) return null; + if (int_val.compareAll(.gte, end_val, int_ty, m)) return null; return @intCast(usize, int_val.toUnsignedInt(m.getTarget())); } }; @@ -6455,12 +6455,12 @@ pub const Type = extern union { if (!d.mutable and d.pointee_type.eql(Type.u8, mod)) { switch (d.size) { .Slice => { - if (sent.compareWithZero(.eq)) { + if (sent.compareAllWithZero(.eq)) { return Type.initTag(.const_slice_u8_sentinel_0); } }, .Many => { - if (sent.compareWithZero(.eq)) { + if (sent.compareAllWithZero(.eq)) { return Type.initTag(.manyptr_const_u8_sentinel_0); } }, diff --git a/src/value.zig b/src/value.zig index 0d2e56ab7a..ddff0da58c 100644 --- a/src/value.zig +++ b/src/value.zig @@ -2039,8 +2039,8 @@ pub const Value = extern union { } /// Asserts the values are comparable. Both operands have type `ty`. - /// Vector results will be reduced with AND. - pub fn compare(lhs: Value, op: std.math.CompareOperator, rhs: Value, ty: Type, mod: *Module) bool { + /// For vectors, returns true if comparison is true for ALL elements. + pub fn compareAll(lhs: Value, op: std.math.CompareOperator, rhs: Value, ty: Type, mod: *Module) bool { if (ty.zigTypeTag() == .Vector) { var i: usize = 0; while (i < ty.vectorLen()) : (i += 1) { @@ -2069,21 +2069,23 @@ pub const Value = extern union { } /// Asserts the value is comparable. - /// Vector results will be reduced with AND. - pub fn compareWithZero(lhs: Value, op: std.math.CompareOperator) bool { - return compareWithZeroAdvanced(lhs, op, null) catch unreachable; + /// For vectors, returns true if comparison is true for ALL elements. + /// + /// Note that `!compareAllWithZero(.eq, ...) != compareAllWithZero(.neq, ...)` + pub fn compareAllWithZero(lhs: Value, op: std.math.CompareOperator) bool { + return compareAllWithZeroAdvanced(lhs, op, null) catch unreachable; } - pub fn compareWithZeroAdvanced( + pub fn compareAllWithZeroAdvanced( lhs: Value, op: std.math.CompareOperator, sema_kit: ?Module.WipAnalysis, ) Module.CompileError!bool { switch (lhs.tag()) { - .repeated => return lhs.castTag(.repeated).?.data.compareWithZeroAdvanced(op, sema_kit), + .repeated => return lhs.castTag(.repeated).?.data.compareAllWithZeroAdvanced(op, sema_kit), .aggregate => { for (lhs.castTag(.aggregate).?.data) |elem_val| { - if (!(try elem_val.compareWithZeroAdvanced(op, sema_kit))) return false; + if (!(try elem_val.compareAllWithZeroAdvanced(op, sema_kit))) return false; } return true; }, @@ -3081,7 +3083,7 @@ pub const Value = extern union { .int_i64, .int_big_positive, .int_big_negative, - => compareWithZero(self, .eq), + => compareAllWithZero(self, .eq), .undef => unreachable, .unreachable_value => unreachable, |
