Skip to content

Commit 6ffa285

Browse files
mluggjacobly0
authored andcommitted
compiler: fix @intFromFloat safety check
This safety check was completely broken; it triggered unchecked illegal behavior *in order to implement the safety check*. You definitely can't do that! Instead, we must explicitly check the boundaries. This is a tiny bit fiddly, because we need to make sure we do floating-point rounding in the correct direction, and also handle the fact that the operation truncates so the boundary works differently for min vs max. Instead of implementing this safety check in Sema, there are now dedicated AIR instructions for safety-checked intfromfloat (two instructions; which one is used depends on the float mode). Currently, no backend directly implements them; instead, a `Legalize.Feature` is added which expands the safety check, and this feature is enabled for all backends we currently test, including the LLVM backend. The `u0` case is still handled in Sema, because Sema needs to check for that anyway due to the comptime-known result. The old safety check here was also completely broken and has therefore been rewritten. In that case, we just check for 'abs(input) < 1.0'. I've added a bunch of test coverage for the boundary cases of `@intFromFloat`, both for successes (in `test/behavior/cast.zig`) and failures (in `test/cases/safety/`). Resolves: #24161
1 parent 6b41beb commit 6ffa285

27 files changed

+463
-32
lines changed

src/Air.zig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,10 @@ pub const Inst = struct {
683683
int_from_float,
684684
/// Same as `int_from_float` with optimized float mode.
685685
int_from_float_optimized,
686+
/// Same as `int_from_float`, but with a safety check that the operand is in bounds.
687+
int_from_float_safe,
688+
/// Same as `int_from_float_optimized`, but with a safety check that the operand is in bounds.
689+
int_from_float_optimized_safe,
686690
/// Given an integer operand, return the float with the closest mathematical meaning.
687691
/// Uses the `ty_op` field.
688692
float_from_int,
@@ -1612,6 +1616,8 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool)
16121616
.array_to_slice,
16131617
.int_from_float,
16141618
.int_from_float_optimized,
1619+
.int_from_float_safe,
1620+
.int_from_float_optimized_safe,
16151621
.float_from_int,
16161622
.splat,
16171623
.get_union_tag,
@@ -1842,6 +1848,8 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
18421848
.sub_safe,
18431849
.mul_safe,
18441850
.intcast_safe,
1851+
.int_from_float_safe,
1852+
.int_from_float_optimized_safe,
18451853
=> true,
18461854

18471855
.add,

src/Air/Legalize.zig

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ pub const Feature = enum {
112112
scalarize_trunc,
113113
scalarize_int_from_float,
114114
scalarize_int_from_float_optimized,
115+
scalarize_int_from_float_safe,
116+
scalarize_int_from_float_optimized_safe,
115117
scalarize_float_from_int,
116118
scalarize_shuffle_one,
117119
scalarize_shuffle_two,
@@ -126,6 +128,12 @@ pub const Feature = enum {
126128
/// Replace `intcast_safe` with an explicit safety check which `call`s the panic function on failure.
127129
/// Not compatible with `scalarize_intcast_safe`.
128130
expand_intcast_safe,
131+
/// Replace `int_from_float_safe` with an explicit safety check which `call`s the panic function on failure.
132+
/// Not compatible with `scalarize_int_from_float_safe`.
133+
expand_int_from_float_safe,
134+
/// Replace `int_from_float_optimized_safe` with an explicit safety check which `call`s the panic function on failure.
135+
/// Not compatible with `scalarize_int_from_float_optimized_safe`.
136+
expand_int_from_float_optimized_safe,
129137
/// Replace `add_safe` with an explicit safety check which `call`s the panic function on failure.
130138
/// Not compatible with `scalarize_add_safe`.
131139
expand_add_safe,
@@ -225,6 +233,8 @@ pub const Feature = enum {
225233
.trunc => .scalarize_trunc,
226234
.int_from_float => .scalarize_int_from_float,
227235
.int_from_float_optimized => .scalarize_int_from_float_optimized,
236+
.int_from_float_safe => .scalarize_int_from_float_safe,
237+
.int_from_float_optimized_safe => .scalarize_int_from_float_optimized_safe,
228238
.float_from_int => .scalarize_float_from_int,
229239
.shuffle_one => .scalarize_shuffle_one,
230240
.shuffle_two => .scalarize_shuffle_two,
@@ -439,6 +449,20 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
439449
const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
440450
if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
441451
},
452+
.int_from_float_safe => if (l.features.has(.expand_int_from_float_safe)) {
453+
assert(!l.features.has(.scalarize_int_from_float_safe));
454+
continue :inst l.replaceInst(inst, .block, try l.safeIntFromFloatBlockPayload(inst, false));
455+
} else if (l.features.has(.scalarize_int_from_float_safe)) {
456+
const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
457+
if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
458+
},
459+
.int_from_float_optimized_safe => if (l.features.has(.expand_int_from_float_optimized_safe)) {
460+
assert(!l.features.has(.scalarize_int_from_float_optimized_safe));
461+
continue :inst l.replaceInst(inst, .block, try l.safeIntFromFloatBlockPayload(inst, true));
462+
} else if (l.features.has(.scalarize_int_from_float_optimized_safe)) {
463+
const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op;
464+
if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op);
465+
},
442466
.block, .loop => {
443467
const ty_pl = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_pl;
444468
const extra = l.extraData(Air.Block, ty_pl.payload);
@@ -2001,6 +2025,115 @@ fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.In
20012025
.payload = try l.addBlockBody(main_block.body()),
20022026
} };
20032027
}
2028+
fn safeIntFromFloatBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, optimized: bool) Error!Air.Inst.Data {
2029+
const pt = l.pt;
2030+
const zcu = pt.zcu;
2031+
const gpa = zcu.gpa;
2032+
const ty_op = l.air_instructions.items(.data)[@intFromEnum(orig_inst)].ty_op;
2033+
2034+
const operand_ref = ty_op.operand;
2035+
const operand_ty = l.typeOf(operand_ref);
2036+
const dest_ty = ty_op.ty.toType();
2037+
2038+
const is_vector = operand_ty.zigTypeTag(zcu) == .vector;
2039+
const dest_scalar_ty = dest_ty.scalarType(zcu);
2040+
const int_info = dest_scalar_ty.intInfo(zcu);
2041+
2042+
// We emit 9 instructions in the worst case.
2043+
var inst_buf: [9]Air.Inst.Index = undefined;
2044+
try l.air_instructions.ensureUnusedCapacity(zcu.gpa, inst_buf.len);
2045+
var main_block: Block = .init(&inst_buf);
2046+
2047+
// This check is a bit annoying because of floating-point rounding and the fact that this
2048+
// builtin truncates. We'll use a bigint for our calculations, because we need to construct
2049+
// integers exceeding the bounds of the result integer type, and we need to convert it to a
2050+
// float with a specific rounding mode to avoid errors.
2051+
// Our bigint may exceed the twos complement limit by one, so add an extra limb.
2052+
const limbs = try gpa.alloc(
2053+
std.math.big.Limb,
2054+
std.math.big.int.calcTwosCompLimbCount(int_info.bits) + 1,
2055+
);
2056+
defer gpa.free(limbs);
2057+
var big: std.math.big.int.Mutable = .init(limbs, 0);
2058+
2059+
// Check if the operand is lower than `min_int` when truncated to an integer.
2060+
big.setTwosCompIntLimit(.min, int_info.signedness, int_info.bits);
2061+
const below_min_inst: Air.Inst.Index = if (!big.positive or big.eqlZero()) bad: {
2062+
// `min_int <= 0`, so check for `x <= min_int - 1`.
2063+
big.addScalar(big.toConst(), -1);
2064+
// For `<=`, we must round the RHS down, so that this value is the first `x` which returns `true`.
2065+
const limit_val = try floatFromBigIntVal(pt, is_vector, operand_ty, big.toConst(), .floor);
2066+
break :bad try main_block.addCmp(l, .lte, operand_ref, Air.internedToRef(limit_val.toIntern()), .{
2067+
.vector = is_vector,
2068+
.optimized = optimized,
2069+
});
2070+
} else {
2071+
// `min_int > 0`, which is currently impossible. It would become possible under #3806, in
2072+
// which case we must detect `x < min_int`.
2073+
unreachable;
2074+
};
2075+
2076+
// Check if the operand is greater than `max_int` when truncated to an integer.
2077+
big.setTwosCompIntLimit(.max, int_info.signedness, int_info.bits);
2078+
const above_max_inst: Air.Inst.Index = if (big.positive or big.eqlZero()) bad: {
2079+
// `max_int >= 0`, so check for `x >= max_int + 1`.
2080+
big.addScalar(big.toConst(), 1);
2081+
// For `>=`, we must round the RHS up, so that this value is the first `x` which returns `true`.
2082+
const limit_val = try floatFromBigIntVal(pt, is_vector, operand_ty, big.toConst(), .ceil);
2083+
break :bad try main_block.addCmp(l, .gte, operand_ref, Air.internedToRef(limit_val.toIntern()), .{
2084+
.vector = is_vector,
2085+
.optimized = optimized,
2086+
});
2087+
} else {
2088+
// `max_int < 0`, which is currently impossible. It would become possible under #3806, in
2089+
// which case we must detect `x > max_int`.
2090+
unreachable;
2091+
};
2092+
2093+
// Combine the conditions.
2094+
const out_of_bounds_inst: Air.Inst.Index = main_block.add(l, .{
2095+
.tag = .bool_or,
2096+
.data = .{ .bin_op = .{
2097+
.lhs = below_min_inst.toRef(),
2098+
.rhs = above_max_inst.toRef(),
2099+
} },
2100+
});
2101+
const scalar_out_of_bounds_inst: Air.Inst.Index = if (is_vector) main_block.add(l, .{
2102+
.tag = .reduce,
2103+
.data = .{ .reduce = .{
2104+
.operand = out_of_bounds_inst.toRef(),
2105+
.operation = .Or,
2106+
} },
2107+
}) else out_of_bounds_inst;
2108+
2109+
// Now emit the actual condbr. "true" will be safety panic. "false" will be "ok", meaning we do
2110+
// the `int_from_float` and `br` the result to `orig_inst`.
2111+
var condbr: CondBr = .init(l, scalar_out_of_bounds_inst.toRef(), &main_block, .{ .true = .cold });
2112+
condbr.then_block = .init(main_block.stealRemainingCapacity());
2113+
try condbr.then_block.addPanic(l, .integer_part_out_of_bounds);
2114+
condbr.else_block = .init(condbr.then_block.stealRemainingCapacity());
2115+
const cast_inst = condbr.else_block.add(l, .{
2116+
.tag = if (optimized) .int_from_float_optimized else .int_from_float,
2117+
.data = .{ .ty_op = .{
2118+
.ty = Air.internedToRef(dest_ty.toIntern()),
2119+
.operand = operand_ref,
2120+
} },
2121+
});
2122+
_ = condbr.else_block.add(l, .{
2123+
.tag = .br,
2124+
.data = .{ .br = .{
2125+
.block_inst = orig_inst,
2126+
.operand = cast_inst.toRef(),
2127+
} },
2128+
});
2129+
_ = condbr.else_block.stealRemainingCapacity(); // we might not have used it all
2130+
try condbr.finish(l);
2131+
2132+
return .{ .ty_pl = .{
2133+
.ty = Air.internedToRef(dest_ty.toIntern()),
2134+
.payload = try l.addBlockBody(main_block.body()),
2135+
} };
2136+
}
20042137
fn safeArithmeticBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, overflow_op_tag: Air.Inst.Tag) Error!Air.Inst.Data {
20052138
const pt = l.pt;
20062139
const zcu = pt.zcu;
@@ -2378,6 +2511,42 @@ fn packedAggregateInitBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Erro
23782511
} };
23792512
}
23802513

2514+
/// Given a `std.math.big.int.Const`, converts it to a `Value` which is a float of type `float_ty`
2515+
/// representing the same numeric value. If the integer cannot be exactly represented, `round`
2516+
/// decides whether the value should be rounded up or down. If `is_vector`, then `float_ty` is
2517+
/// instead a vector of floats, and the result value is a vector containing the converted scalar
2518+
/// repeated N times.
2519+
fn floatFromBigIntVal(
2520+
pt: Zcu.PerThread,
2521+
is_vector: bool,
2522+
float_ty: Type,
2523+
x: std.math.big.int.Const,
2524+
round: std.math.big.int.Round,
2525+
) Error!Value {
2526+
const zcu = pt.zcu;
2527+
const scalar_ty = switch (is_vector) {
2528+
true => float_ty.childType(zcu),
2529+
false => float_ty,
2530+
};
2531+
assert(scalar_ty.zigTypeTag(zcu) == .float);
2532+
const scalar_val: Value = switch (scalar_ty.floatBits(zcu.getTarget())) {
2533+
16 => try pt.floatValue(scalar_ty, x.toFloat(f16, round)[0]),
2534+
32 => try pt.floatValue(scalar_ty, x.toFloat(f32, round)[0]),
2535+
64 => try pt.floatValue(scalar_ty, x.toFloat(f64, round)[0]),
2536+
80 => try pt.floatValue(scalar_ty, x.toFloat(f80, round)[0]),
2537+
128 => try pt.floatValue(scalar_ty, x.toFloat(f128, round)[0]),
2538+
else => unreachable,
2539+
};
2540+
if (is_vector) {
2541+
return .fromInterned(try pt.intern(.{ .aggregate = .{
2542+
.ty = float_ty.toIntern(),
2543+
.storage = .{ .repeated_elem = scalar_val.toIntern() },
2544+
} }));
2545+
} else {
2546+
return scalar_val;
2547+
}
2548+
}
2549+
23812550
const Block = struct {
23822551
instructions: []Air.Inst.Index,
23832552
len: usize,
@@ -2735,4 +2904,5 @@ const InternPool = @import("../InternPool.zig");
27352904
const Legalize = @This();
27362905
const std = @import("std");
27372906
const Type = @import("../Type.zig");
2907+
const Value = @import("../Value.zig");
27382908
const Zcu = @import("../Zcu.zig");

src/Air/Liveness.zig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,8 @@ pub fn categorizeOperand(
374374
.array_to_slice,
375375
.int_from_float,
376376
.int_from_float_optimized,
377+
.int_from_float_safe,
378+
.int_from_float_optimized_safe,
377379
.float_from_int,
378380
.get_union_tag,
379381
.clz,
@@ -1015,6 +1017,8 @@ fn analyzeInst(
10151017
.array_to_slice,
10161018
.int_from_float,
10171019
.int_from_float_optimized,
1020+
.int_from_float_safe,
1021+
.int_from_float_optimized_safe,
10181022
.float_from_int,
10191023
.get_union_tag,
10201024
.clz,

src/Air/Liveness/Verify.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
107107
.array_to_slice,
108108
.int_from_float,
109109
.int_from_float_optimized,
110+
.int_from_float_safe,
111+
.int_from_float_optimized_safe,
110112
.float_from_int,
111113
.get_union_tag,
112114
.clz,

src/Air/print.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,8 @@ const Writer = struct {
250250
.splat,
251251
.int_from_float,
252252
.int_from_float_optimized,
253+
.int_from_float_safe,
254+
.int_from_float_optimized_safe,
253255
.get_union_tag,
254256
.clz,
255257
.ctz,

src/Air/types_resolved.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool {
130130
.array_to_slice,
131131
.int_from_float,
132132
.int_from_float_optimized,
133+
.int_from_float_safe,
134+
.int_from_float_optimized_safe,
133135
.float_from_int,
134136
.splat,
135137
.error_set_has_value,

src/Sema.zig

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22178,44 +22178,34 @@ fn zirIntFromFloat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro
2217822178

2217922179
try sema.requireRuntimeBlock(block, src, operand_src);
2218022180
if (dest_scalar_ty.intInfo(zcu).bits == 0) {
22181-
if (!is_vector) {
22182-
if (block.wantSafety()) {
22183-
const ok = try block.addBinOp(if (block.float_mode == .optimized) .cmp_eq_optimized else .cmp_eq, operand, Air.internedToRef((try pt.floatValue(operand_ty, 0.0)).toIntern()));
22184-
try sema.addSafetyCheck(block, src, ok, .integer_part_out_of_bounds);
22185-
}
22186-
return Air.internedToRef((try pt.intValue(dest_ty, 0)).toIntern());
22187-
}
2218822181
if (block.wantSafety()) {
22189-
const len = dest_ty.vectorLen(zcu);
22190-
for (0..len) |i| {
22191-
const idx_ref = try pt.intRef(.usize, i);
22192-
const elem_ref = try block.addBinOp(.array_elem_val, operand, idx_ref);
22193-
const ok = try block.addBinOp(if (block.float_mode == .optimized) .cmp_eq_optimized else .cmp_eq, elem_ref, Air.internedToRef((try pt.floatValue(operand_scalar_ty, 0.0)).toIntern()));
22194-
try sema.addSafetyCheck(block, src, ok, .integer_part_out_of_bounds);
22195-
}
22196-
}
22182+
// Emit an explicit safety check. We can do this one like `abs(x) < 1`.
22183+
const abs_ref = try block.addTyOp(.abs, operand_ty, operand);
22184+
const max_abs_ref = if (is_vector) try block.addReduce(abs_ref, .Max) else abs_ref;
22185+
const one_ref = Air.internedToRef((try pt.floatValue(operand_scalar_ty, 1.0)).toIntern());
22186+
const ok_ref = try block.addBinOp(.cmp_lt, max_abs_ref, one_ref);
22187+
try sema.addSafetyCheck(block, src, ok_ref, .integer_part_out_of_bounds);
22188+
}
22189+
const scalar_val = try pt.intValue(dest_scalar_ty, 0);
22190+
if (!is_vector) return Air.internedToRef(scalar_val.toIntern());
2219722191
return Air.internedToRef(try pt.intern(.{ .aggregate = .{
2219822192
.ty = dest_ty.toIntern(),
22199-
.storage = .{ .repeated_elem = (try pt.intValue(dest_scalar_ty, 0)).toIntern() },
22193+
.storage = .{ .repeated_elem = scalar_val.toIntern() },
2220022194
} }));
2220122195
}
22202-
const result = try block.addTyOp(if (block.float_mode == .optimized) .int_from_float_optimized else .int_from_float, dest_ty, operand);
2220322196
if (block.wantSafety()) {
22204-
const back = try block.addTyOp(.float_from_int, operand_ty, result);
22205-
const diff = try block.addBinOp(if (block.float_mode == .optimized) .sub_optimized else .sub, operand, back);
22206-
const ok = if (is_vector) ok: {
22207-
const ok_pos = try block.addCmpVector(diff, Air.internedToRef((try sema.splat(operand_ty, try pt.floatValue(operand_scalar_ty, 1.0))).toIntern()), .lt);
22208-
const ok_neg = try block.addCmpVector(diff, Air.internedToRef((try sema.splat(operand_ty, try pt.floatValue(operand_scalar_ty, -1.0))).toIntern()), .gt);
22209-
const ok = try block.addBinOp(.bit_and, ok_pos, ok_neg);
22210-
break :ok try block.addReduce(ok, .And);
22211-
} else ok: {
22212-
const ok_pos = try block.addBinOp(if (block.float_mode == .optimized) .cmp_lt_optimized else .cmp_lt, diff, Air.internedToRef((try pt.floatValue(operand_ty, 1.0)).toIntern()));
22213-
const ok_neg = try block.addBinOp(if (block.float_mode == .optimized) .cmp_gt_optimized else .cmp_gt, diff, Air.internedToRef((try pt.floatValue(operand_ty, -1.0)).toIntern()));
22214-
break :ok try block.addBinOp(.bool_and, ok_pos, ok_neg);
22215-
};
22216-
try sema.addSafetyCheck(block, src, ok, .integer_part_out_of_bounds);
22197+
if (zcu.backendSupportsFeature(.panic_fn)) {
22198+
_ = try sema.preparePanicId(src, .integer_part_out_of_bounds);
22199+
}
22200+
return block.addTyOp(switch (block.float_mode) {
22201+
.optimized => .int_from_float_optimized_safe,
22202+
.strict => .int_from_float_safe,
22203+
}, dest_ty, operand);
2221722204
}
22218-
return result;
22205+
return block.addTyOp(switch (block.float_mode) {
22206+
.optimized => .int_from_float_optimized,
22207+
.strict => .int_from_float,
22208+
}, dest_ty, operand);
2221922209
}
2222022210

2222122211
fn zirFloatFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {

src/arch/aarch64/CodeGen.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
861861
.sub_safe,
862862
.mul_safe,
863863
.intcast_safe,
864+
.int_from_float_safe,
865+
.int_from_float_optimized_safe,
864866
=> return self.fail("TODO implement safety_checked_instructions", .{}),
865867

866868
.is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),

src/arch/arm/CodeGen.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
850850
.sub_safe,
851851
.mul_safe,
852852
.intcast_safe,
853+
.int_from_float_safe,
854+
.int_from_float_optimized_safe,
853855
=> return self.fail("TODO implement safety_checked_instructions", .{}),
854856

855857
.is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}),

src/arch/riscv64/CodeGen.zig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ const InnerError = CodeGenError || error{OutOfRegisters};
5454
pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
5555
return comptime &.initMany(&.{
5656
.expand_intcast_safe,
57+
.expand_int_from_float_safe,
58+
.expand_int_from_float_optimized_safe,
5759
.expand_add_safe,
5860
.expand_sub_safe,
5961
.expand_mul_safe,
@@ -1474,6 +1476,8 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {
14741476
.sub_safe,
14751477
.mul_safe,
14761478
.intcast_safe,
1479+
.int_from_float_safe,
1480+
.int_from_float_optimized_safe,
14771481
=> return func.fail("TODO implement safety_checked_instructions", .{}),
14781482

14791483
.cmp_lt,

0 commit comments

Comments
 (0)