mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
compiler: audit uses of ptr.addr in the frontend
This commit also performs some refactors to `TypedValue.print` in preparation for improved comptime pointer access logic. Once that logic exists, `TypedValue.print` can use Sema to access pointers for more helpful printing. This commit also implements proposal #19435, because the existing logic there relied on some blatantly incorrect code in `Value.sliceLen`. Resolves: #19435
This commit is contained in:
parent
884d957b6c
commit
152a2ceaf7
182
src/Sema.zig
182
src/Sema.zig
@ -1881,10 +1881,10 @@ pub fn toConstString(
|
||||
air_inst: Air.Inst.Ref,
|
||||
reason: NeededComptimeReason,
|
||||
) ![]u8 {
|
||||
const wanted_type = Type.slice_const_u8;
|
||||
const coerced_inst = try sema.coerce(block, wanted_type, air_inst, src);
|
||||
const val = try sema.resolveConstDefinedValue(block, src, coerced_inst, reason);
|
||||
return val.toAllocatedBytes(wanted_type, sema.arena, sema.mod);
|
||||
const coerced_inst = try sema.coerce(block, Type.slice_const_u8, air_inst, src);
|
||||
const slice_val = try sema.resolveConstDefinedValue(block, src, coerced_inst, reason);
|
||||
const arr_val = try sema.derefSliceAsArray(block, src, slice_val, reason);
|
||||
return arr_val.toAllocatedBytes(arr_val.typeOf(sema.mod), sema.arena, sema.mod);
|
||||
}
|
||||
|
||||
pub fn resolveConstStringIntern(
|
||||
@ -14498,12 +14498,16 @@ fn zirArrayCat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
|
||||
else => unreachable,
|
||||
}) |rhs_val| {
|
||||
const lhs_sub_val = if (lhs_ty.isSinglePointer(mod))
|
||||
(try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty)).?
|
||||
try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty) orelse break :rs lhs_src
|
||||
else if (lhs_ty.isSlice(mod))
|
||||
try sema.maybeDerefSliceAsArray(block, lhs_src, lhs_val) orelse break :rs lhs_src
|
||||
else
|
||||
lhs_val;
|
||||
|
||||
const rhs_sub_val = if (rhs_ty.isSinglePointer(mod))
|
||||
(try sema.pointerDeref(block, rhs_src, rhs_val, rhs_ty)).?
|
||||
try sema.pointerDeref(block, rhs_src, rhs_val, rhs_ty) orelse break :rs rhs_src
|
||||
else if (rhs_ty.isSlice(mod))
|
||||
try sema.maybeDerefSliceAsArray(block, rhs_src, rhs_val) orelse break :rs rhs_src
|
||||
else
|
||||
rhs_val;
|
||||
|
||||
@ -14623,10 +14627,7 @@ fn getArrayCatInfo(sema: *Sema, block: *Block, src: LazySrcLoc, operand: Air.Ins
|
||||
.Pointer => {
|
||||
const ptr_info = operand_ty.ptrInfo(mod);
|
||||
switch (ptr_info.flags.size) {
|
||||
// TODO: in the Many case here this should only work if the type
|
||||
// has a sentinel, and this code should compute the length based
|
||||
// on the sentinel value.
|
||||
.Slice, .Many => {
|
||||
.Slice => {
|
||||
const val = try sema.resolveConstDefinedValue(block, src, operand, .{
|
||||
.needed_comptime_reason = "slice value being concatenated must be comptime-known",
|
||||
});
|
||||
@ -14636,7 +14637,7 @@ fn getArrayCatInfo(sema: *Sema, block: *Block, src: LazySrcLoc, operand: Air.Ins
|
||||
.none => null,
|
||||
else => Value.fromInterned(ptr_info.sentinel),
|
||||
},
|
||||
.len = val.sliceLen(mod),
|
||||
.len = try val.sliceLen(sema),
|
||||
};
|
||||
},
|
||||
.One => {
|
||||
@ -14644,7 +14645,7 @@ fn getArrayCatInfo(sema: *Sema, block: *Block, src: LazySrcLoc, operand: Air.Ins
|
||||
return Type.fromInterned(ptr_info.child).arrayInfo(mod);
|
||||
}
|
||||
},
|
||||
.C => {},
|
||||
.C, .Many => {},
|
||||
}
|
||||
},
|
||||
.Struct => {
|
||||
@ -14830,9 +14831,11 @@ fn zirArrayMul(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
|
||||
const ptr_addrspace = if (lhs_ty.zigTypeTag(mod) == .Pointer) lhs_ty.ptrAddressSpace(mod) else null;
|
||||
const lhs_len = try sema.usizeCast(block, lhs_src, lhs_info.len);
|
||||
|
||||
if (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val| {
|
||||
if (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val| ct: {
|
||||
const lhs_sub_val = if (lhs_ty.isSinglePointer(mod))
|
||||
(try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty)).?
|
||||
try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty) orelse break :ct
|
||||
else if (lhs_ty.isSlice(mod))
|
||||
try sema.maybeDerefSliceAsArray(block, lhs_src, lhs_val) orelse break :ct
|
||||
else
|
||||
lhs_val;
|
||||
|
||||
@ -14840,7 +14843,7 @@ fn zirArrayMul(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
|
||||
// Optimization for the common pattern of a single element repeated N times, such
|
||||
// as zero-filling a byte array.
|
||||
if (lhs_len == 1 and lhs_info.sentinel == null) {
|
||||
const elem_val = (try lhs_sub_val.maybeElemValueFull(sema, mod, 0)).?;
|
||||
const elem_val = try lhs_sub_val.elemValue(mod, 0);
|
||||
break :v try mod.intern(.{ .aggregate = .{
|
||||
.ty = result_ty.toIntern(),
|
||||
.storage = .{ .repeated_elem = elem_val.toIntern() },
|
||||
@ -14852,7 +14855,7 @@ fn zirArrayMul(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
|
||||
while (elem_i < result_len) {
|
||||
var lhs_i: usize = 0;
|
||||
while (lhs_i < lhs_len) : (lhs_i += 1) {
|
||||
const elem_val = (try lhs_sub_val.maybeElemValueFull(sema, mod, lhs_i)).?;
|
||||
const elem_val = try lhs_sub_val.elemValue(mod, lhs_i);
|
||||
element_vals[elem_i] = elem_val.toIntern();
|
||||
elem_i += 1;
|
||||
}
|
||||
@ -21124,7 +21127,9 @@ fn zirReify(
|
||||
.needed_comptime_reason = "operand to @Type must be comptime-known",
|
||||
});
|
||||
const union_val = ip.indexToKey(val.toIntern()).un;
|
||||
if (try sema.anyUndef(Value.fromInterned(union_val.val))) return sema.failWithUseOfUndef(block, src);
|
||||
if (try sema.anyUndef(block, operand_src, Value.fromInterned(union_val.val))) {
|
||||
return sema.failWithUseOfUndef(block, operand_src);
|
||||
}
|
||||
const tag_index = type_info_ty.unionTagFieldIndex(Value.fromInterned(union_val.tag), mod).?;
|
||||
switch (@as(std.builtin.TypeId, @enumFromInt(tag_index))) {
|
||||
.Type => return .type_type,
|
||||
@ -21365,11 +21370,15 @@ fn zirReify(
|
||||
const payload_val = Value.fromInterned(union_val.val).optionalValue(mod) orelse
|
||||
return Air.internedToRef(Type.anyerror.toIntern());
|
||||
|
||||
const len = try sema.usizeCast(block, src, payload_val.sliceLen(mod));
|
||||
const names_val = try sema.derefSliceAsArray(block, src, payload_val, .{
|
||||
.needed_comptime_reason = "error set contents must be comptime-known",
|
||||
});
|
||||
|
||||
const len = try sema.usizeCast(block, src, names_val.typeOf(mod).arrayLen(mod));
|
||||
var names: InferredErrorSet.NameMap = .{};
|
||||
try names.ensureUnusedCapacity(sema.arena, len);
|
||||
for (0..len) |i| {
|
||||
const elem_val = (try payload_val.maybeElemValueFull(sema, mod, i)).?;
|
||||
const elem_val = try names_val.elemValue(mod, i);
|
||||
const elem_struct_type = ip.loadStructType(ip.typeOf(elem_val.toIntern()));
|
||||
const name_val = try elem_val.fieldValue(mod, elem_struct_type.nameIndex(
|
||||
ip,
|
||||
@ -21417,7 +21426,7 @@ fn zirReify(
|
||||
const layout = mod.toEnum(std.builtin.Type.ContainerLayout, layout_val);
|
||||
|
||||
// Decls
|
||||
if (decls_val.sliceLen(mod) > 0) {
|
||||
if (try decls_val.sliceLen(sema) > 0) {
|
||||
return sema.fail(block, src, "reified structs must have no decls", .{});
|
||||
}
|
||||
|
||||
@ -21425,7 +21434,11 @@ fn zirReify(
|
||||
return sema.fail(block, src, "non-packed struct does not support backing integer type", .{});
|
||||
}
|
||||
|
||||
return try sema.reifyStruct(block, inst, src, layout, backing_integer_val, fields_val, name_strategy, is_tuple_val.toBool());
|
||||
const fields_arr = try sema.derefSliceAsArray(block, operand_src, fields_val, .{
|
||||
.needed_comptime_reason = "struct fields must be comptime-known",
|
||||
});
|
||||
|
||||
return try sema.reifyStruct(block, inst, src, layout, backing_integer_val, fields_arr, name_strategy, is_tuple_val.toBool());
|
||||
},
|
||||
.Enum => {
|
||||
const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
|
||||
@ -21446,11 +21459,15 @@ fn zirReify(
|
||||
try ip.getOrPutString(gpa, "is_exhaustive"),
|
||||
).?);
|
||||
|
||||
if (decls_val.sliceLen(mod) > 0) {
|
||||
if (try decls_val.sliceLen(sema) > 0) {
|
||||
return sema.fail(block, src, "reified enums must have no decls", .{});
|
||||
}
|
||||
|
||||
return sema.reifyEnum(block, inst, src, tag_type_val.toType(), is_exhaustive_val.toBool(), fields_val, name_strategy);
|
||||
const fields_arr = try sema.derefSliceAsArray(block, operand_src, fields_val, .{
|
||||
.needed_comptime_reason = "enum fields must be comptime-known",
|
||||
});
|
||||
|
||||
return sema.reifyEnum(block, inst, src, tag_type_val.toType(), is_exhaustive_val.toBool(), fields_arr, name_strategy);
|
||||
},
|
||||
.Opaque => {
|
||||
const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
|
||||
@ -21460,7 +21477,7 @@ fn zirReify(
|
||||
).?);
|
||||
|
||||
// Decls
|
||||
if (decls_val.sliceLen(mod) > 0) {
|
||||
if (try decls_val.sliceLen(sema) > 0) {
|
||||
return sema.fail(block, src, "reified opaque must have no decls", .{});
|
||||
}
|
||||
|
||||
@ -21505,12 +21522,16 @@ fn zirReify(
|
||||
try ip.getOrPutString(gpa, "decls"),
|
||||
).?);
|
||||
|
||||
if (decls_val.sliceLen(mod) > 0) {
|
||||
if (try decls_val.sliceLen(sema) > 0) {
|
||||
return sema.fail(block, src, "reified unions must have no decls", .{});
|
||||
}
|
||||
const layout = mod.toEnum(std.builtin.Type.ContainerLayout, layout_val);
|
||||
|
||||
return sema.reifyUnion(block, inst, src, layout, tag_type_val, fields_val, name_strategy);
|
||||
const fields_arr = try sema.derefSliceAsArray(block, operand_src, fields_val, .{
|
||||
.needed_comptime_reason = "union fields must be comptime-known",
|
||||
});
|
||||
|
||||
return sema.reifyUnion(block, inst, src, layout, tag_type_val, fields_arr, name_strategy);
|
||||
},
|
||||
.Fn => {
|
||||
const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
|
||||
@ -21530,7 +21551,7 @@ fn zirReify(
|
||||
ip,
|
||||
try ip.getOrPutString(gpa, "return_type"),
|
||||
).?);
|
||||
const params_val = try Value.fromInterned(union_val.val).fieldValue(mod, struct_type.nameIndex(
|
||||
const params_slice_val = try Value.fromInterned(union_val.val).fieldValue(mod, struct_type.nameIndex(
|
||||
ip,
|
||||
try ip.getOrPutString(gpa, "params"),
|
||||
).?);
|
||||
@ -21549,12 +21570,16 @@ fn zirReify(
|
||||
const return_type = return_type_val.optionalValue(mod) orelse
|
||||
return sema.fail(block, src, "Type.Fn.return_type must be non-null for @Type", .{});
|
||||
|
||||
const args_len = try sema.usizeCast(block, src, params_val.sliceLen(mod));
|
||||
const params_val = try sema.derefSliceAsArray(block, operand_src, params_slice_val, .{
|
||||
.needed_comptime_reason = "function parameters must be comptime-known",
|
||||
});
|
||||
|
||||
const args_len = try sema.usizeCast(block, src, params_val.typeOf(mod).arrayLen(mod));
|
||||
const param_types = try sema.arena.alloc(InternPool.Index, args_len);
|
||||
|
||||
var noalias_bits: u32 = 0;
|
||||
for (param_types, 0..) |*param_type, i| {
|
||||
const elem_val = (try params_val.maybeElemValueFull(sema, mod, i)).?;
|
||||
const elem_val = try params_val.elemValue(mod, i);
|
||||
const elem_struct_type = ip.loadStructType(ip.typeOf(elem_val.toIntern()));
|
||||
const param_is_generic_val = try elem_val.fieldValue(mod, elem_struct_type.nameIndex(
|
||||
ip,
|
||||
@ -21615,7 +21640,7 @@ fn reifyEnum(
|
||||
|
||||
// This logic must stay in sync with the structure of `std.builtin.Type.Enum` - search for `fieldValue`.
|
||||
|
||||
const fields_len: u32 = @intCast(fields_val.sliceLen(mod));
|
||||
const fields_len: u32 = @intCast(fields_val.typeOf(mod).arrayLen(mod));
|
||||
|
||||
// The validation work here is non-trivial, and it's possible the type already exists.
|
||||
// So in this first pass, let's just construct a hash to optimize for this case. If the
|
||||
@ -21629,7 +21654,7 @@ fn reifyEnum(
|
||||
std.hash.autoHash(&hasher, fields_len);
|
||||
|
||||
for (0..fields_len) |field_idx| {
|
||||
const field_info = (try fields_val.maybeElemValueFull(sema, mod, field_idx)).?;
|
||||
const field_info = try fields_val.elemValue(mod, field_idx);
|
||||
|
||||
const field_name_val = try field_info.fieldValue(mod, 0);
|
||||
const field_value_val = try sema.resolveLazyValue(try field_info.fieldValue(mod, 1));
|
||||
@ -21674,7 +21699,7 @@ fn reifyEnum(
|
||||
wip_ty.setTagTy(ip, tag_ty.toIntern());
|
||||
|
||||
for (0..fields_len) |field_idx| {
|
||||
const field_info = (try fields_val.maybeElemValueFull(sema, mod, field_idx)).?;
|
||||
const field_info = try fields_val.elemValue(mod, field_idx);
|
||||
|
||||
const field_name_val = try field_info.fieldValue(mod, 0);
|
||||
const field_value_val = try sema.resolveLazyValue(try field_info.fieldValue(mod, 1));
|
||||
@ -21736,7 +21761,7 @@ fn reifyUnion(
|
||||
|
||||
// This logic must stay in sync with the structure of `std.builtin.Type.Union` - search for `fieldValue`.
|
||||
|
||||
const fields_len: u32 = @intCast(fields_val.sliceLen(mod));
|
||||
const fields_len: u32 = @intCast(fields_val.typeOf(mod).arrayLen(mod));
|
||||
|
||||
// The validation work here is non-trivial, and it's possible the type already exists.
|
||||
// So in this first pass, let's just construct a hash to optimize for this case. If the
|
||||
@ -21752,7 +21777,7 @@ fn reifyUnion(
|
||||
var any_aligns = false;
|
||||
|
||||
for (0..fields_len) |field_idx| {
|
||||
const field_info = (try fields_val.maybeElemValueFull(sema, mod, field_idx)).?;
|
||||
const field_info = try fields_val.elemValue(mod, field_idx);
|
||||
|
||||
const field_name_val = try field_info.fieldValue(mod, 0);
|
||||
const field_type_val = try field_info.fieldValue(mod, 1);
|
||||
@ -21828,7 +21853,7 @@ fn reifyUnion(
|
||||
var seen_tags = try std.DynamicBitSetUnmanaged.initEmpty(sema.arena, tag_ty_fields_len);
|
||||
|
||||
for (field_types, 0..) |*field_ty, field_idx| {
|
||||
const field_info = (try fields_val.maybeElemValueFull(sema, mod, field_idx)).?;
|
||||
const field_info = try fields_val.elemValue(mod, field_idx);
|
||||
|
||||
const field_name_val = try field_info.fieldValue(mod, 0);
|
||||
const field_type_val = try field_info.fieldValue(mod, 1);
|
||||
@ -21880,7 +21905,7 @@ fn reifyUnion(
|
||||
try field_names.ensureTotalCapacity(sema.arena, fields_len);
|
||||
|
||||
for (field_types, 0..) |*field_ty, field_idx| {
|
||||
const field_info = (try fields_val.maybeElemValueFull(sema, mod, field_idx)).?;
|
||||
const field_info = try fields_val.elemValue(mod, field_idx);
|
||||
|
||||
const field_name_val = try field_info.fieldValue(mod, 0);
|
||||
const field_type_val = try field_info.fieldValue(mod, 1);
|
||||
@ -21974,7 +21999,7 @@ fn reifyStruct(
|
||||
|
||||
// This logic must stay in sync with the structure of `std.builtin.Type.Struct` - search for `fieldValue`.
|
||||
|
||||
const fields_len: u32 = @intCast(fields_val.sliceLen(mod));
|
||||
const fields_len: u32 = @intCast(fields_val.typeOf(mod).arrayLen(mod));
|
||||
|
||||
// The validation work here is non-trivial, and it's possible the type already exists.
|
||||
// So in this first pass, let's just construct a hash to optimize for this case. If the
|
||||
@ -21993,7 +22018,7 @@ fn reifyStruct(
|
||||
var any_aligned_fields = false;
|
||||
|
||||
for (0..fields_len) |field_idx| {
|
||||
const field_info = (try fields_val.maybeElemValueFull(sema, mod, field_idx)).?;
|
||||
const field_info = try fields_val.elemValue(mod, field_idx);
|
||||
|
||||
const field_name_val = try field_info.fieldValue(mod, 0);
|
||||
const field_type_val = try field_info.fieldValue(mod, 1);
|
||||
@ -22071,7 +22096,7 @@ fn reifyStruct(
|
||||
const struct_type = ip.loadStructType(wip_ty.index);
|
||||
|
||||
for (0..fields_len) |field_idx| {
|
||||
const field_info = (try fields_val.maybeElemValueFull(sema, mod, field_idx)).?;
|
||||
const field_info = try fields_val.elemValue(mod, field_idx);
|
||||
|
||||
const field_name_val = try field_info.fieldValue(mod, 0);
|
||||
const field_type_val = try field_info.fieldValue(mod, 1);
|
||||
@ -23892,11 +23917,9 @@ fn resolveExportOptions(
|
||||
const visibility_src = sema.maybeOptionsSrc(block, src, "visibility");
|
||||
|
||||
const name_operand = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "name"), name_src);
|
||||
const name_val = try sema.resolveConstDefinedValue(block, name_src, name_operand, .{
|
||||
const name = try sema.toConstString(block, name_src, name_operand, .{
|
||||
.needed_comptime_reason = "name of exported value must be comptime-known",
|
||||
});
|
||||
const name_ty = Type.slice_const_u8;
|
||||
const name = try name_val.toAllocatedBytes(name_ty, sema.arena, mod);
|
||||
|
||||
const linkage_operand = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "linkage"), linkage_src);
|
||||
const linkage_val = try sema.resolveConstDefinedValue(block, linkage_src, linkage_operand, .{
|
||||
@ -23908,9 +23931,10 @@ fn resolveExportOptions(
|
||||
const section_opt_val = try sema.resolveConstDefinedValue(block, section_src, section_operand, .{
|
||||
.needed_comptime_reason = "linksection of exported value must be comptime-known",
|
||||
});
|
||||
const section_ty = Type.slice_const_u8;
|
||||
const section = if (section_opt_val.optionalValue(mod)) |section_val|
|
||||
try section_val.toAllocatedBytes(section_ty, sema.arena, mod)
|
||||
try sema.toConstString(block, section_src, Air.internedToRef(section_val.toIntern()), .{
|
||||
.needed_comptime_reason = "linksection of exported value must be comptime-known",
|
||||
})
|
||||
else
|
||||
null;
|
||||
|
||||
@ -26028,10 +26052,9 @@ fn resolveExternOptions(
|
||||
const thread_local_src = sema.maybeOptionsSrc(block, src, "thread_local");
|
||||
|
||||
const name_ref = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "name"), name_src);
|
||||
const name_val = try sema.resolveConstDefinedValue(block, name_src, name_ref, .{
|
||||
const name = try sema.toConstString(block, name_src, name_ref, .{
|
||||
.needed_comptime_reason = "name of the extern symbol must be comptime-known",
|
||||
});
|
||||
const name = try name_val.toAllocatedBytes(Type.slice_const_u8, sema.arena, mod);
|
||||
|
||||
const library_name_inst = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "library_name"), library_src);
|
||||
const library_name_val = try sema.resolveConstDefinedValue(block, library_src, library_name_inst, .{
|
||||
@ -26050,7 +26073,9 @@ fn resolveExternOptions(
|
||||
});
|
||||
|
||||
const library_name = if (library_name_val.optionalValue(mod)) |library_name_payload| library_name: {
|
||||
const library_name = try library_name_payload.toAllocatedBytes(Type.slice_const_u8, sema.arena, mod);
|
||||
const library_name = try sema.toConstString(block, library_src, Air.internedToRef(library_name_payload.toIntern()), .{
|
||||
.needed_comptime_reason = "library in which extern symbol is must be comptime-known",
|
||||
});
|
||||
if (library_name.len == 0) {
|
||||
return sema.fail(block, library_src, "library name cannot be empty", .{});
|
||||
}
|
||||
@ -28564,7 +28589,7 @@ fn elemValSlice(
|
||||
|
||||
if (maybe_slice_val) |slice_val| {
|
||||
runtime_src = elem_index_src;
|
||||
const slice_len = slice_val.sliceLen(mod);
|
||||
const slice_len = try slice_val.sliceLen(sema);
|
||||
const slice_len_s = slice_len + @intFromBool(slice_sent);
|
||||
if (slice_len_s == 0) {
|
||||
return sema.fail(block, slice_src, "indexing into empty slice is not allowed", .{});
|
||||
@ -28589,7 +28614,7 @@ fn elemValSlice(
|
||||
try sema.requireRuntimeBlock(block, src, runtime_src);
|
||||
if (oob_safety and block.wantSafety()) {
|
||||
const len_inst = if (maybe_slice_val) |slice_val|
|
||||
try mod.intRef(Type.usize, slice_val.sliceLen(mod))
|
||||
try mod.intRef(Type.usize, try slice_val.sliceLen(sema))
|
||||
else
|
||||
try block.addTyOp(.slice_len, Type.usize, slice);
|
||||
const cmp_op: Air.Inst.Tag = if (slice_sent) .cmp_lte else .cmp_lt;
|
||||
@ -28626,7 +28651,7 @@ fn elemPtrSlice(
|
||||
if (slice_val.isUndef(mod)) {
|
||||
return mod.undefRef(elem_ptr_ty);
|
||||
}
|
||||
const slice_len = slice_val.sliceLen(mod);
|
||||
const slice_len = try slice_val.sliceLen(sema);
|
||||
const slice_len_s = slice_len + @intFromBool(slice_sent);
|
||||
if (slice_len_s == 0) {
|
||||
return sema.fail(block, slice_src, "indexing into empty slice is not allowed", .{});
|
||||
@ -28649,7 +28674,7 @@ fn elemPtrSlice(
|
||||
const len_inst = len: {
|
||||
if (maybe_undef_slice_val) |slice_val|
|
||||
if (!slice_val.isUndef(mod))
|
||||
break :len try mod.intRef(Type.usize, slice_val.sliceLen(mod));
|
||||
break :len try mod.intRef(Type.usize, try slice_val.sliceLen(sema));
|
||||
break :len try block.addTyOp(.slice_len, Type.usize, slice);
|
||||
};
|
||||
const cmp_op: Air.Inst.Tag = if (slice_sent) .cmp_lte else .cmp_lt;
|
||||
@ -31523,16 +31548,11 @@ fn coerceArrayPtrToSlice(
|
||||
if (try sema.resolveValue(inst)) |val| {
|
||||
const ptr_array_ty = sema.typeOf(inst);
|
||||
const array_ty = ptr_array_ty.childType(mod);
|
||||
const slice_ptr_ty = dest_ty.slicePtrFieldType(mod);
|
||||
const slice_ptr = try mod.getCoerced(val, slice_ptr_ty);
|
||||
const slice_val = try mod.intern(.{ .slice = .{
|
||||
.ty = dest_ty.toIntern(),
|
||||
.ptr = try mod.intern(.{ .ptr = .{
|
||||
.ty = dest_ty.slicePtrFieldType(mod).toIntern(),
|
||||
.addr = switch (mod.intern_pool.indexToKey(val.toIntern())) {
|
||||
.undef => .{ .int = try mod.intern(.{ .undef = .usize_type }) },
|
||||
.ptr => |ptr| ptr.addr,
|
||||
else => unreachable,
|
||||
},
|
||||
} }),
|
||||
.ptr = slice_ptr.toIntern(),
|
||||
.len = (try mod.intValue(Type.usize, array_ty.arrayLen(mod))).toIntern(),
|
||||
} });
|
||||
return Air.internedToRef(slice_val);
|
||||
@ -32602,7 +32622,7 @@ fn analyzeSliceLen(
|
||||
if (slice_val.isUndef(mod)) {
|
||||
return mod.undefRef(Type.usize);
|
||||
}
|
||||
return mod.intRef(Type.usize, slice_val.sliceLen(sema.mod));
|
||||
return mod.intRef(Type.usize, try slice_val.sliceLen(sema));
|
||||
}
|
||||
try sema.requireRuntimeBlock(block, src, null);
|
||||
return block.addTyOp(.slice_len, Type.usize, slice_inst);
|
||||
@ -33041,7 +33061,7 @@ fn analyzeSlice(
|
||||
return sema.fail(block, src, "slice of undefined", .{});
|
||||
}
|
||||
const has_sentinel = slice_ty.sentinel(mod) != null;
|
||||
const slice_len = slice_val.sliceLen(mod);
|
||||
const slice_len = try slice_val.sliceLen(sema);
|
||||
const len_plus_sent = slice_len + @intFromBool(has_sentinel);
|
||||
const slice_len_val_with_sentinel = try mod.intValue(Type.usize, len_plus_sent);
|
||||
if (!(try sema.compareAll(end_val, .lte, slice_len_val_with_sentinel, Type.usize))) {
|
||||
@ -33056,7 +33076,7 @@ fn analyzeSlice(
|
||||
"end index {} out of bounds for slice of length {d}{s}",
|
||||
.{
|
||||
end_val.fmtValue(Type.usize, mod),
|
||||
slice_val.sliceLen(mod),
|
||||
try slice_val.sliceLen(sema),
|
||||
sentinel_label,
|
||||
},
|
||||
);
|
||||
@ -33285,7 +33305,7 @@ fn analyzeSlice(
|
||||
if (try sema.resolveDefinedValue(block, src, ptr_or_slice)) |slice_val| {
|
||||
// we don't need to add one for sentinels because the
|
||||
// underlying value data includes the sentinel
|
||||
break :blk try mod.intRef(Type.usize, slice_val.sliceLen(mod));
|
||||
break :blk try mod.intRef(Type.usize, try slice_val.sliceLen(sema));
|
||||
}
|
||||
|
||||
const slice_len_inst = try block.addTyOp(.slice_len, Type.usize, ptr_or_slice);
|
||||
@ -39003,22 +39023,22 @@ fn validateRuntimeValue(sema: *Sema, block: *Block, val_src: LazySrcLoc, val: Ai
|
||||
}
|
||||
|
||||
/// Returns true if any value contained in `val` is undefined.
|
||||
fn anyUndef(sema: *Sema, val: Value) !bool {
|
||||
fn anyUndef(sema: *Sema, block: *Block, src: LazySrcLoc, val: Value) !bool {
|
||||
const mod = sema.mod;
|
||||
return switch (val.toIntern()) {
|
||||
.undef => true,
|
||||
else => switch (mod.intern_pool.indexToKey(val.toIntern())) {
|
||||
return switch (mod.intern_pool.indexToKey(val.toIntern())) {
|
||||
.undef => true,
|
||||
.simple_value => |v| v == .undefined,
|
||||
.slice => |slice| for (0..@intCast(Value.fromInterned(slice.len).toUnsignedInt(mod))) |idx| {
|
||||
if (try sema.anyUndef((try val.maybeElemValueFull(sema, mod, idx)).?)) break true;
|
||||
} else false,
|
||||
.slice => {
|
||||
// If the slice contents are runtime-known, reification will fail later on with a
|
||||
// specific error message.
|
||||
const arr = try sema.maybeDerefSliceAsArray(block, src, val) orelse return false;
|
||||
return sema.anyUndef(block, src, arr);
|
||||
},
|
||||
.aggregate => |aggregate| for (0..aggregate.storage.values().len) |i| {
|
||||
const elem = mod.intern_pool.indexToKey(val.toIntern()).aggregate.storage.values()[i];
|
||||
if (try sema.anyUndef(Value.fromInterned(elem))) break true;
|
||||
if (try sema.anyUndef(block, src, Value.fromInterned(elem))) break true;
|
||||
} else false,
|
||||
else => false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -39050,6 +39070,20 @@ fn derefSliceAsArray(
|
||||
slice_val: Value,
|
||||
reason: NeededComptimeReason,
|
||||
) CompileError!Value {
|
||||
return try sema.maybeDerefSliceAsArray(block, src, slice_val) orelse {
|
||||
return sema.failWithNeededComptime(block, src, reason);
|
||||
};
|
||||
}
|
||||
|
||||
/// Given a slice value, attempts to dereference it into a comptime-known array.
|
||||
/// Returns `null` if the contents of the slice are not comptime-known.
|
||||
/// Asserts that `slice_val` is a slice.
|
||||
fn maybeDerefSliceAsArray(
|
||||
sema: *Sema,
|
||||
block: *Block,
|
||||
src: LazySrcLoc,
|
||||
slice_val: Value,
|
||||
) CompileError!?Value {
|
||||
const zcu = sema.mod;
|
||||
const ip = &zcu.intern_pool;
|
||||
assert(Type.fromInterned(ip.typeOf(slice_val.toIntern())).isSlice(zcu));
|
||||
@ -39072,7 +39106,5 @@ fn derefSliceAsArray(
|
||||
break :p p;
|
||||
});
|
||||
const casted_ptr = try zcu.getCoerced(Value.fromInterned(slice.ptr), ptr_ty);
|
||||
return try sema.pointerDeref(block, src, casted_ptr, ptr_ty) orelse {
|
||||
return sema.failWithNeededComptime(block, src, reason);
|
||||
};
|
||||
return sema.pointerDeref(block, src, casted_ptr, ptr_ty);
|
||||
}
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
const std = @import("std");
|
||||
const Type = @import("type.zig").Type;
|
||||
const Value = @import("Value.zig");
|
||||
const Module = @import("Module.zig");
|
||||
const Zcu = @import("Module.zig");
|
||||
const Module = Zcu;
|
||||
const Sema = @import("Sema.zig");
|
||||
const InternPool = @import("InternPool.zig");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const TypedValue = @This();
|
||||
const Target = std.Target;
|
||||
@ -61,8 +64,10 @@ pub fn format(
|
||||
) !void {
|
||||
_ = options;
|
||||
comptime std.debug.assert(fmt.len == 0);
|
||||
return ctx.tv.print(writer, 3, ctx.mod) catch |err| switch (err) {
|
||||
return ctx.tv.print(writer, 3, ctx.mod, null) catch |err| switch (err) {
|
||||
error.OutOfMemory => @panic("OOM"), // We're not allowed to return this from a format function
|
||||
error.ComptimeBreak, error.ComptimeReturn => unreachable,
|
||||
error.AnalysisFail, error.NeededSourceLocation => unreachable, // TODO: re-evaluate when we actually pass `opt_sema`
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
@ -73,11 +78,12 @@ pub fn print(
|
||||
writer: anytype,
|
||||
level: u8,
|
||||
mod: *Module,
|
||||
) (@TypeOf(writer).Error || Allocator.Error)!void {
|
||||
var val = tv.val;
|
||||
var ty = tv.ty;
|
||||
/// If this `Sema` is provided, we will recurse through pointers where possible to provide friendly output.
|
||||
opt_sema: ?*Sema,
|
||||
) (@TypeOf(writer).Error || Module.CompileError)!void {
|
||||
const ip = &mod.intern_pool;
|
||||
while (true) switch (ip.indexToKey(val.toIntern())) {
|
||||
const val = tv.val;
|
||||
switch (ip.indexToKey(val.toIntern())) {
|
||||
.int_type,
|
||||
.ptr_type,
|
||||
.array_type,
|
||||
@ -94,324 +100,298 @@ pub fn print(
|
||||
.func_type,
|
||||
.error_set_type,
|
||||
.inferred_error_set_type,
|
||||
=> return Type.print(val.toType(), writer, mod),
|
||||
.undef => return writer.writeAll("undefined"),
|
||||
=> try Type.print(val.toType(), writer, mod),
|
||||
.undef => try writer.writeAll("undefined"),
|
||||
.simple_value => |simple_value| switch (simple_value) {
|
||||
.void => return writer.writeAll("{}"),
|
||||
.empty_struct => return printAggregate(ty, val, writer, level, mod),
|
||||
.generic_poison => return writer.writeAll("(generic poison)"),
|
||||
else => return writer.writeAll(@tagName(simple_value)),
|
||||
.void => try writer.writeAll("{}"),
|
||||
.empty_struct => try writer.writeAll(".{}"),
|
||||
.generic_poison => try writer.writeAll("(generic poison)"),
|
||||
else => try writer.writeAll(@tagName(simple_value)),
|
||||
},
|
||||
.variable => return writer.writeAll("(variable)"),
|
||||
.extern_func => |extern_func| return writer.print("(extern function '{}')", .{
|
||||
.variable => try writer.writeAll("(variable)"),
|
||||
.extern_func => |extern_func| try writer.print("(extern function '{}')", .{
|
||||
mod.declPtr(extern_func.decl).name.fmt(ip),
|
||||
}),
|
||||
.func => |func| return writer.print("(function '{}')", .{
|
||||
.func => |func| try writer.print("(function '{}')", .{
|
||||
mod.declPtr(func.owner_decl).name.fmt(ip),
|
||||
}),
|
||||
.int => |int| switch (int.storage) {
|
||||
inline .u64, .i64, .big_int => |x| return writer.print("{}", .{x}),
|
||||
.lazy_align => |lazy_ty| return writer.print("{d}", .{
|
||||
Type.fromInterned(lazy_ty).abiAlignment(mod),
|
||||
}),
|
||||
.lazy_size => |lazy_ty| return writer.print("{d}", .{
|
||||
Type.fromInterned(lazy_ty).abiSize(mod),
|
||||
}),
|
||||
inline .u64, .i64, .big_int => |x| try writer.print("{}", .{x}),
|
||||
.lazy_align => |ty| if (opt_sema) |sema| {
|
||||
const a = (try Type.fromInterned(ty).abiAlignmentAdvanced(mod, .{ .sema = sema })).scalar;
|
||||
try writer.print("{}", .{a.toByteUnits(0)});
|
||||
} else try writer.print("@alignOf({})", .{Type.fromInterned(ty).fmt(mod)}),
|
||||
.lazy_size => |ty| if (opt_sema) |sema| {
|
||||
const s = (try Type.fromInterned(ty).abiSizeAdvanced(mod, .{ .sema = sema })).scalar;
|
||||
try writer.print("{}", .{s});
|
||||
} else try writer.print("@sizeOf({})", .{Type.fromInterned(ty).fmt(mod)}),
|
||||
},
|
||||
.err => |err| return writer.print("error.{}", .{
|
||||
.err => |err| try writer.print("error.{}", .{
|
||||
err.name.fmt(ip),
|
||||
}),
|
||||
.error_union => |error_union| switch (error_union.val) {
|
||||
.err_name => |err_name| return writer.print("error.{}", .{
|
||||
.err_name => |err_name| try writer.print("error.{}", .{
|
||||
err_name.fmt(ip),
|
||||
}),
|
||||
.payload => |payload| {
|
||||
val = Value.fromInterned(payload);
|
||||
ty = ty.errorUnionPayload(mod);
|
||||
.payload => |payload| try print(.{
|
||||
.ty = tv.ty.errorUnionPayload(mod),
|
||||
.val = Value.fromInterned(payload),
|
||||
}, writer, level, mod, opt_sema),
|
||||
},
|
||||
},
|
||||
.enum_literal => |enum_literal| return writer.print(".{}", .{
|
||||
.enum_literal => |enum_literal| try writer.print(".{}", .{
|
||||
enum_literal.fmt(ip),
|
||||
}),
|
||||
.enum_tag => |enum_tag| {
|
||||
if (level == 0) {
|
||||
return writer.writeAll("(enum)");
|
||||
}
|
||||
const enum_type = ip.loadEnumType(ty.toIntern());
|
||||
const enum_type = ip.loadEnumType(val.typeOf(mod).toIntern());
|
||||
if (enum_type.tagValueIndex(ip, val.toIntern())) |tag_index| {
|
||||
try writer.print(".{i}", .{enum_type.names.get(ip)[tag_index].fmt(ip)});
|
||||
return;
|
||||
}
|
||||
if (level == 0) {
|
||||
try writer.writeAll("@enumFromInt(...)");
|
||||
}
|
||||
try writer.writeAll("@enumFromInt(");
|
||||
try print(.{
|
||||
.ty = Type.fromInterned(ip.typeOf(enum_tag.int)),
|
||||
.val = Value.fromInterned(enum_tag.int),
|
||||
}, writer, level - 1, mod);
|
||||
}, writer, level - 1, mod, opt_sema);
|
||||
try writer.writeAll(")");
|
||||
return;
|
||||
},
|
||||
.empty_enum_value => return writer.writeAll("(empty enum value)"),
|
||||
.empty_enum_value => try writer.writeAll("(empty enum value)"),
|
||||
.float => |float| switch (float.storage) {
|
||||
inline else => |x| return writer.print("{d}", .{@as(f64, @floatCast(x))}),
|
||||
inline else => |x| try writer.print("{d}", .{@as(f64, @floatCast(x))}),
|
||||
},
|
||||
.slice => |slice| {
|
||||
const ptr_ty = switch (ip.indexToKey(slice.ptr)) {
|
||||
.ptr => |ptr| ty: {
|
||||
if (ptr.addr == .int) return print(.{
|
||||
.ty = Type.fromInterned(ptr.ty),
|
||||
.val = Value.fromInterned(slice.ptr),
|
||||
}, writer, level - 1, mod);
|
||||
break :ty ip.indexToKey(ptr.ty).ptr_type;
|
||||
},
|
||||
.undef => |ptr_ty| ip.indexToKey(ptr_ty).ptr_type,
|
||||
else => unreachable,
|
||||
const print_contents = switch (ip.getBackingAddrTag(slice.ptr).?) {
|
||||
.field, .elem, .eu_payload, .opt_payload => unreachable,
|
||||
.anon_decl, .comptime_alloc, .comptime_field => true,
|
||||
.decl, .int => false,
|
||||
};
|
||||
if (level == 0) {
|
||||
return writer.writeAll(".{ ... }");
|
||||
if (print_contents) {
|
||||
// TODO: eventually we want to load the slice as an array with `opt_sema`, but that's
|
||||
// currently not possible without e.g. triggering compile errors.
|
||||
}
|
||||
const elem_ty = Type.fromInterned(ptr_ty.child);
|
||||
const len = Value.fromInterned(slice.len).toUnsignedInt(mod);
|
||||
if (elem_ty.eql(Type.u8, mod)) str: {
|
||||
const max_len = @min(len, max_string_len);
|
||||
var buf: [max_string_len]u8 = undefined;
|
||||
for (buf[0..max_len], 0..) |*c, i| {
|
||||
const maybe_elem = try val.maybeElemValue(mod, i);
|
||||
const elem = maybe_elem orelse return writer.writeAll(".{ (reinterpreted data) }");
|
||||
if (elem.isUndef(mod)) break :str;
|
||||
c.* = @as(u8, @intCast(elem.toUnsignedInt(mod)));
|
||||
}
|
||||
const truncated = if (len > max_string_len) " (truncated)" else "";
|
||||
return writer.print("\"{}{s}\"", .{ std.zig.fmtEscapes(buf[0..max_len]), truncated });
|
||||
}
|
||||
try writer.writeAll(".{ ");
|
||||
const max_len = @min(len, max_aggregate_items);
|
||||
for (0..max_len) |i| {
|
||||
if (i != 0) try writer.writeAll(", ");
|
||||
const maybe_elem = try val.maybeElemValue(mod, i);
|
||||
const elem = maybe_elem orelse return writer.writeAll("(reinterpreted data) }");
|
||||
try print(.{
|
||||
.ty = elem_ty,
|
||||
.val = elem,
|
||||
}, writer, level - 1, mod);
|
||||
}
|
||||
if (len > max_aggregate_items) {
|
||||
try writer.writeAll(", ...");
|
||||
}
|
||||
return writer.writeAll(" }");
|
||||
},
|
||||
.ptr => |ptr| {
|
||||
switch (ptr.addr) {
|
||||
.decl => |decl_index| {
|
||||
const decl = mod.declPtr(decl_index);
|
||||
if (level == 0) return writer.print("(decl '{}')", .{decl.name.fmt(ip)});
|
||||
return print(.{
|
||||
.ty = decl.typeOf(mod),
|
||||
.val = decl.val,
|
||||
}, writer, level - 1, mod);
|
||||
},
|
||||
.anon_decl => |anon_decl| {
|
||||
const decl_val = anon_decl.val;
|
||||
if (level == 0) return writer.print("(anon decl '{d}')", .{
|
||||
@intFromEnum(decl_val),
|
||||
});
|
||||
return print(.{
|
||||
.ty = Type.fromInterned(ip.typeOf(decl_val)),
|
||||
.val = Value.fromInterned(decl_val),
|
||||
}, writer, level - 1, mod);
|
||||
},
|
||||
.comptime_alloc => {
|
||||
// TODO: we need a Sema to print this!
|
||||
return writer.writeAll("(comptime alloc)");
|
||||
},
|
||||
.comptime_field => |field_val_ip| {
|
||||
return print(.{
|
||||
.ty = Type.fromInterned(ip.typeOf(field_val_ip)),
|
||||
.val = Value.fromInterned(field_val_ip),
|
||||
}, writer, level - 1, mod);
|
||||
},
|
||||
.int => |int_ip| {
|
||||
try writer.writeAll("@ptrFromInt(");
|
||||
try printPtr(slice.ptr, writer, false, false, 0, level, mod, opt_sema);
|
||||
try writer.writeAll("[0..");
|
||||
try print(.{
|
||||
.ty = Type.usize,
|
||||
.val = Value.fromInterned(int_ip),
|
||||
}, writer, level - 1, mod);
|
||||
try writer.writeByte(')');
|
||||
.val = Value.fromInterned(slice.len),
|
||||
}, writer, level - 1, mod, opt_sema);
|
||||
try writer.writeAll("]");
|
||||
},
|
||||
.eu_payload => |eu_ip| {
|
||||
try writer.writeAll("(payload of ");
|
||||
try print(.{
|
||||
.ty = Type.fromInterned(ip.typeOf(eu_ip)),
|
||||
.val = Value.fromInterned(eu_ip),
|
||||
}, writer, level - 1, mod);
|
||||
try writer.writeAll(")");
|
||||
},
|
||||
.opt_payload => |opt_ip| {
|
||||
try print(.{
|
||||
.ty = Type.fromInterned(ip.typeOf(opt_ip)),
|
||||
.val = Value.fromInterned(opt_ip),
|
||||
}, writer, level - 1, mod);
|
||||
try writer.writeAll(".?");
|
||||
},
|
||||
.elem => |elem| {
|
||||
if (level == 0) {
|
||||
try writer.writeAll("(...)");
|
||||
} else {
|
||||
try print(.{
|
||||
.ty = Type.fromInterned(ip.typeOf(elem.base)),
|
||||
.val = Value.fromInterned(elem.base),
|
||||
}, writer, level - 1, mod);
|
||||
.ptr => {
|
||||
const print_contents = switch (ip.getBackingAddrTag(val.toIntern()).?) {
|
||||
.field, .elem, .eu_payload, .opt_payload => unreachable,
|
||||
.anon_decl, .comptime_alloc, .comptime_field => true,
|
||||
.decl, .int => false,
|
||||
};
|
||||
if (print_contents) {
|
||||
// TODO: eventually we want to load the pointer with `opt_sema`, but that's
|
||||
// currently not possible without e.g. triggering compile errors.
|
||||
}
|
||||
try writer.print("[{}]", .{elem.index});
|
||||
},
|
||||
.field => |field| {
|
||||
const ptr_container_ty = Type.fromInterned(ip.typeOf(field.base));
|
||||
if (level == 0) {
|
||||
try writer.writeAll("(...)");
|
||||
} else {
|
||||
try print(.{
|
||||
.ty = ptr_container_ty,
|
||||
.val = Value.fromInterned(field.base),
|
||||
}, writer, level - 1, mod);
|
||||
}
|
||||
|
||||
const container_ty = ptr_container_ty.childType(mod);
|
||||
switch (container_ty.zigTypeTag(mod)) {
|
||||
.Struct => {
|
||||
if (container_ty.structFieldName(@intCast(field.index), mod).unwrap()) |field_name| {
|
||||
try writer.print(".{i}", .{field_name.fmt(ip)});
|
||||
} else {
|
||||
try writer.print("[{d}]", .{field.index});
|
||||
}
|
||||
},
|
||||
.Union => {
|
||||
const field_name = mod.typeToUnion(container_ty).?.loadTagType(ip).names.get(ip)[@intCast(field.index)];
|
||||
try writer.print(".{i}", .{field_name.fmt(ip)});
|
||||
},
|
||||
.Pointer => {
|
||||
std.debug.assert(container_ty.isSlice(mod));
|
||||
try writer.writeAll(switch (field.index) {
|
||||
Value.slice_ptr_index => ".ptr",
|
||||
Value.slice_len_index => ".len",
|
||||
else => unreachable,
|
||||
});
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
},
|
||||
}
|
||||
return;
|
||||
try printPtr(val.toIntern(), writer, false, false, 0, level, mod, opt_sema);
|
||||
},
|
||||
.opt => |opt| switch (opt.val) {
|
||||
.none => return writer.writeAll("null"),
|
||||
else => |payload| {
|
||||
val = Value.fromInterned(payload);
|
||||
ty = ty.optionalChild(mod);
|
||||
},
|
||||
},
|
||||
.aggregate => |aggregate| switch (aggregate.storage) {
|
||||
.bytes => |bytes| {
|
||||
// Strip the 0 sentinel off of strings before printing
|
||||
const zero_sent = blk: {
|
||||
const sent = ty.sentinel(mod) orelse break :blk false;
|
||||
break :blk sent.eql(Value.zero_u8, Type.u8, mod);
|
||||
};
|
||||
const str = if (zero_sent) bytes[0 .. bytes.len - 1] else bytes;
|
||||
return writer.print("\"{}\"", .{std.zig.fmtEscapes(str)});
|
||||
},
|
||||
.elems, .repeated_elem => return printAggregate(ty, val, writer, level, mod),
|
||||
.none => try writer.writeAll("null"),
|
||||
else => |payload| try print(.{
|
||||
.ty = tv.ty.childType(mod),
|
||||
.val = Value.fromInterned(payload),
|
||||
}, writer, level, mod, opt_sema),
|
||||
},
|
||||
.aggregate => |aggregate| try printAggregate(val, aggregate, writer, level, mod, opt_sema),
|
||||
.un => |un| {
|
||||
try writer.writeAll(".{ ");
|
||||
if (level > 0) {
|
||||
if (un.tag != .none) {
|
||||
try print(.{
|
||||
.ty = ty.unionTagTypeHypothetical(mod),
|
||||
.val = Value.fromInterned(un.tag),
|
||||
}, writer, level - 1, mod);
|
||||
try writer.writeAll(" = ");
|
||||
const field_ty = ty.unionFieldType(Value.fromInterned(un.tag), mod).?;
|
||||
try print(.{
|
||||
.ty = field_ty,
|
||||
.val = Value.fromInterned(un.val),
|
||||
}, writer, level - 1, mod);
|
||||
} else {
|
||||
try writer.writeAll("(unknown tag) = ");
|
||||
const backing_ty = try ty.unionBackingType(mod);
|
||||
if (level == 0) {
|
||||
try writer.writeAll(".{ ... }");
|
||||
return;
|
||||
}
|
||||
if (un.tag == .none) {
|
||||
const backing_ty = try tv.ty.unionBackingType(mod);
|
||||
try writer.print("@bitCast(@as({}, ", .{backing_ty.fmt(mod)});
|
||||
try print(.{
|
||||
.ty = backing_ty,
|
||||
.val = Value.fromInterned(un.val),
|
||||
}, writer, level - 1, mod);
|
||||
}, writer, level - 1, mod, opt_sema);
|
||||
try writer.writeAll("))");
|
||||
} else {
|
||||
try writer.writeAll(".{ ");
|
||||
try print(.{
|
||||
.ty = tv.ty.unionTagTypeHypothetical(mod),
|
||||
.val = Value.fromInterned(un.tag),
|
||||
}, writer, level - 1, mod, opt_sema);
|
||||
try writer.writeAll(" = ");
|
||||
const field_ty = tv.ty.unionFieldType(Value.fromInterned(un.tag), mod).?;
|
||||
try print(.{
|
||||
.ty = field_ty,
|
||||
.val = Value.fromInterned(un.val),
|
||||
}, writer, level - 1, mod, opt_sema);
|
||||
try writer.writeAll(" }");
|
||||
}
|
||||
} else try writer.writeAll("...");
|
||||
return writer.writeAll(" }");
|
||||
},
|
||||
.memoized_call => unreachable,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn printAggregate(
|
||||
ty: Type,
|
||||
val: Value,
|
||||
aggregate: InternPool.Key.Aggregate,
|
||||
writer: anytype,
|
||||
level: u8,
|
||||
mod: *Module,
|
||||
) (@TypeOf(writer).Error || Allocator.Error)!void {
|
||||
zcu: *Zcu,
|
||||
opt_sema: ?*Sema,
|
||||
) (@TypeOf(writer).Error || Module.CompileError)!void {
|
||||
if (level == 0) {
|
||||
return writer.writeAll(".{ ... }");
|
||||
}
|
||||
const ip = &mod.intern_pool;
|
||||
if (ty.zigTypeTag(mod) == .Struct) {
|
||||
try writer.writeAll(".{");
|
||||
const max_len = @min(ty.structFieldCount(mod), max_aggregate_items);
|
||||
|
||||
const ip = &zcu.intern_pool;
|
||||
const ty = Type.fromInterned(aggregate.ty);
|
||||
switch (ty.zigTypeTag(zcu)) {
|
||||
.Struct => if (!ty.isTuple(zcu)) {
|
||||
if (ty.structFieldCount(zcu) == 0) {
|
||||
return writer.writeAll(".{}");
|
||||
}
|
||||
try writer.writeAll(".{ ");
|
||||
const max_len = @min(ty.structFieldCount(zcu), max_aggregate_items);
|
||||
for (0..max_len) |i| {
|
||||
if (i != 0) try writer.writeAll(", ");
|
||||
|
||||
const field_name = ty.structFieldName(@intCast(i), mod);
|
||||
|
||||
if (field_name.unwrap()) |name| try writer.print(".{} = ", .{name.fmt(ip)});
|
||||
const field_name = ty.structFieldName(@intCast(i), zcu).unwrap().?;
|
||||
try writer.print(".{i} = ", .{field_name.fmt(ip)});
|
||||
try print(.{
|
||||
.ty = ty.structFieldType(i, mod),
|
||||
.val = try val.fieldValue(mod, i),
|
||||
}, writer, level - 1, mod);
|
||||
.ty = ty.structFieldType(i, zcu),
|
||||
.val = try val.fieldValue(zcu, i),
|
||||
}, writer, level - 1, zcu, opt_sema);
|
||||
}
|
||||
if (ty.structFieldCount(mod) > max_aggregate_items) {
|
||||
try writer.writeAll(", ...");
|
||||
}
|
||||
return writer.writeAll("}");
|
||||
} else {
|
||||
const elem_ty = ty.elemType2(mod);
|
||||
const len = ty.arrayLen(mod);
|
||||
|
||||
if (elem_ty.eql(Type.u8, mod)) str: {
|
||||
const max_len: usize = @min(len, max_string_len);
|
||||
var buf: [max_string_len]u8 = undefined;
|
||||
|
||||
var i: u32 = 0;
|
||||
while (i < max_len) : (i += 1) {
|
||||
const elem = try val.fieldValue(mod, i);
|
||||
if (elem.isUndef(mod)) break :str;
|
||||
buf[i] = std.math.cast(u8, elem.toUnsignedInt(mod)) orelse break :str;
|
||||
try writer.writeAll(" }");
|
||||
return;
|
||||
},
|
||||
.Array => if (aggregate.storage == .bytes) {
|
||||
return writer.print("\"{}\".*", .{std.zig.fmtEscapes(aggregate.storage.bytes)});
|
||||
} else if (ty.arrayLen(zcu) == 0) {
|
||||
return writer.writeAll(".{}");
|
||||
},
|
||||
.Vector => if (ty.arrayLen(zcu) == 0) {
|
||||
return writer.writeAll(".{}");
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
|
||||
const truncated = if (len > max_string_len) " (truncated)" else "";
|
||||
return writer.print("\"{}{s}\"", .{ std.zig.fmtEscapes(buf[0..max_len]), truncated });
|
||||
}
|
||||
const elem_ty = ty.childType(zcu);
|
||||
const len = ty.arrayLen(zcu);
|
||||
|
||||
try writer.writeAll(".{ ");
|
||||
|
||||
const max_len = @min(len, max_aggregate_items);
|
||||
var i: u32 = 0;
|
||||
while (i < max_len) : (i += 1) {
|
||||
for (0..max_len) |i| {
|
||||
if (i != 0) try writer.writeAll(", ");
|
||||
try print(.{
|
||||
.ty = elem_ty,
|
||||
.val = try val.fieldValue(mod, i),
|
||||
}, writer, level - 1, mod);
|
||||
.val = try val.fieldValue(zcu, i),
|
||||
}, writer, level - 1, zcu, opt_sema);
|
||||
}
|
||||
if (len > max_aggregate_items) {
|
||||
try writer.writeAll(", ...");
|
||||
}
|
||||
return writer.writeAll(" }");
|
||||
}
|
||||
|
||||
fn printPtr(
|
||||
ptr_val: InternPool.Index,
|
||||
writer: anytype,
|
||||
force_type: bool,
|
||||
force_addrof: bool,
|
||||
leading_parens: u32,
|
||||
level: u8,
|
||||
zcu: *Zcu,
|
||||
opt_sema: ?*Sema,
|
||||
) (@TypeOf(writer).Error || Module.CompileError)!void {
|
||||
const ip = &zcu.intern_pool;
|
||||
const ptr = switch (ip.indexToKey(ptr_val)) {
|
||||
.undef => |ptr_ty| {
|
||||
if (force_addrof) try writer.writeAll("&");
|
||||
try writer.writeByteNTimes('(', leading_parens);
|
||||
try writer.print("@as({}, undefined)", .{Type.fromInterned(ptr_ty).fmt(zcu)});
|
||||
return;
|
||||
},
|
||||
.ptr => |ptr| ptr,
|
||||
else => unreachable,
|
||||
};
|
||||
switch (ptr.addr) {
|
||||
.int => |int| {
|
||||
if (force_addrof) try writer.writeAll("&");
|
||||
try writer.writeByteNTimes('(', leading_parens);
|
||||
if (force_type) {
|
||||
try writer.print("@as({}, @ptrFromInt(", .{Type.fromInterned(ptr.ty).fmt(zcu)});
|
||||
try print(.{
|
||||
.ty = Type.usize,
|
||||
.val = Value.fromInterned(int),
|
||||
}, writer, level - 1, zcu, opt_sema);
|
||||
try writer.writeAll("))");
|
||||
} else {
|
||||
try writer.writeAll("@ptrFromInt(");
|
||||
try print(.{
|
||||
.ty = Type.usize,
|
||||
.val = Value.fromInterned(int),
|
||||
}, writer, level - 1, zcu, opt_sema);
|
||||
try writer.writeAll(")");
|
||||
}
|
||||
},
|
||||
.decl => |index| {
|
||||
try writer.writeAll("&");
|
||||
try zcu.declPtr(index).renderFullyQualifiedName(zcu, writer);
|
||||
},
|
||||
.comptime_alloc => try writer.writeAll("&(comptime alloc)"),
|
||||
.anon_decl => |anon| {
|
||||
const ty = Type.fromInterned(ip.typeOf(anon.val));
|
||||
try writer.print("&@as({}, ", .{ty.fmt(zcu)});
|
||||
try print(.{
|
||||
.ty = ty,
|
||||
.val = Value.fromInterned(anon.val),
|
||||
}, writer, level - 1, zcu, opt_sema);
|
||||
try writer.writeAll(")");
|
||||
},
|
||||
.comptime_field => |val| {
|
||||
const ty = Type.fromInterned(ip.typeOf(val));
|
||||
try writer.print("&@as({}, ", .{ty.fmt(zcu)});
|
||||
try print(.{
|
||||
.ty = ty,
|
||||
.val = Value.fromInterned(val),
|
||||
}, writer, level - 1, zcu, opt_sema);
|
||||
try writer.writeAll(")");
|
||||
},
|
||||
.eu_payload => |base| {
|
||||
try printPtr(base, writer, true, true, leading_parens, level, zcu, opt_sema);
|
||||
try writer.writeAll(".?");
|
||||
},
|
||||
.opt_payload => |base| {
|
||||
try writer.writeAll("(");
|
||||
try printPtr(base, writer, true, true, leading_parens + 1, level, zcu, opt_sema);
|
||||
try writer.writeAll(" catch unreachable");
|
||||
},
|
||||
.elem => |elem| {
|
||||
try printPtr(elem.base, writer, true, true, leading_parens, level, zcu, opt_sema);
|
||||
try writer.print("[{d}]", .{elem.index});
|
||||
},
|
||||
.field => |field| {
|
||||
try printPtr(field.base, writer, true, true, leading_parens, level, zcu, opt_sema);
|
||||
const base_ty = Type.fromInterned(ip.typeOf(field.base)).childType(zcu);
|
||||
switch (base_ty.zigTypeTag(zcu)) {
|
||||
.Struct => if (base_ty.isTuple(zcu)) {
|
||||
try writer.print("[{d}]", .{field.index});
|
||||
} else {
|
||||
const field_name = base_ty.structFieldName(@intCast(field.index), zcu).unwrap().?;
|
||||
try writer.print(".{i}", .{field_name.fmt(ip)});
|
||||
},
|
||||
.Union => {
|
||||
const tag_ty = base_ty.unionTagTypeHypothetical(zcu);
|
||||
const field_name = tag_ty.enumFieldName(@intCast(field.index), zcu);
|
||||
try writer.print(".{i}", .{field_name.fmt(ip)});
|
||||
},
|
||||
.Pointer => switch (field.index) {
|
||||
Value.slice_ptr_index => try writer.writeAll(".ptr"),
|
||||
Value.slice_len_index => try writer.writeAll(".len"),
|
||||
else => unreachable,
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
108
src/Value.zig
108
src/Value.zig
@ -305,16 +305,16 @@ pub fn toBool(val: Value) bool {
|
||||
};
|
||||
}
|
||||
|
||||
fn isDeclRef(val: Value, mod: *Module) bool {
|
||||
fn ptrHasIntAddr(val: Value, mod: *Module) bool {
|
||||
var check = val;
|
||||
while (true) switch (mod.intern_pool.indexToKey(check.toIntern())) {
|
||||
.ptr => |ptr| switch (ptr.addr) {
|
||||
.decl, .comptime_alloc, .comptime_field, .anon_decl => return true,
|
||||
.decl, .comptime_alloc, .comptime_field, .anon_decl => return false,
|
||||
.int => return true,
|
||||
.eu_payload, .opt_payload => |base| check = Value.fromInterned(base),
|
||||
.elem, .field => |base_index| check = Value.fromInterned(base_index.base),
|
||||
.int => return false,
|
||||
},
|
||||
else => return false,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
@ -439,7 +439,7 @@ pub fn writeToMemory(val: Value, ty: Type, mod: *Module, buffer: []u8) error{
|
||||
},
|
||||
.Pointer => {
|
||||
if (ty.isSlice(mod)) return error.IllDefinedMemoryLayout;
|
||||
if (val.isDeclRef(mod)) return error.ReinterpretDeclRef;
|
||||
if (!val.ptrHasIntAddr(mod)) return error.ReinterpretDeclRef;
|
||||
return val.writeToMemory(Type.usize, mod, buffer);
|
||||
},
|
||||
.Optional => {
|
||||
@ -566,7 +566,7 @@ pub fn writeToPackedMemory(
|
||||
},
|
||||
.Pointer => {
|
||||
assert(!ty.isSlice(mod)); // No well defined layout.
|
||||
if (val.isDeclRef(mod)) return error.ReinterpretDeclRef;
|
||||
if (!val.ptrHasIntAddr(mod)) return error.ReinterpretDeclRef;
|
||||
return val.writeToPackedMemory(Type.usize, mod, buffer, bit_offset);
|
||||
},
|
||||
.Optional => {
|
||||
@ -1261,62 +1261,23 @@ pub fn slicePtr(val: Value, mod: *Module) Value {
|
||||
return Value.fromInterned(mod.intern_pool.slicePtr(val.toIntern()));
|
||||
}
|
||||
|
||||
pub fn sliceLen(val: Value, mod: *Module) u64 {
|
||||
const ip = &mod.intern_pool;
|
||||
return switch (ip.indexToKey(val.toIntern())) {
|
||||
.ptr => |ptr| switch (ip.indexToKey(switch (ptr.addr) {
|
||||
.decl => |decl| mod.declPtr(decl).typeOf(mod).toIntern(),
|
||||
.comptime_alloc => @panic("TODO"),
|
||||
.anon_decl => |anon_decl| ip.typeOf(anon_decl.val),
|
||||
.comptime_field => |comptime_field| ip.typeOf(comptime_field),
|
||||
else => unreachable,
|
||||
})) {
|
||||
.array_type => |array_type| array_type.len,
|
||||
else => 1,
|
||||
/// Gets the `len` field of a slice value as a `u64`.
|
||||
/// Resolves the length using the provided `Sema` if necessary.
|
||||
pub fn sliceLen(val: Value, sema: *Sema) !u64 {
|
||||
return Value.fromInterned(sema.mod.intern_pool.sliceLen(val.toIntern())).toUnsignedIntAdvanced(sema);
|
||||
}
|
||||
|
||||
/// Asserts the value is an aggregate, and returns the element value at the given index.
|
||||
pub fn elemValue(val: Value, zcu: *Zcu, index: usize) Allocator.Error!Value {
|
||||
const ip = &zcu.intern_pool;
|
||||
switch (zcu.intern_pool.indexToKey(val.toIntern())) {
|
||||
.undef => |ty| {
|
||||
return Value.fromInterned(try zcu.intern(.{ .undef = Type.fromInterned(ty).childType(zcu).toIntern() }));
|
||||
},
|
||||
.slice => |slice| Value.fromInterned(slice.len).toUnsignedInt(mod),
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
/// Asserts the value is a single-item pointer to an array, or an array,
|
||||
/// or an unknown-length pointer, and returns the element value at the index.
|
||||
pub fn elemValue(val: Value, mod: *Module, index: usize) Allocator.Error!Value {
|
||||
return (try val.maybeElemValue(mod, index)).?;
|
||||
}
|
||||
|
||||
/// Like `elemValue`, but returns `null` instead of asserting on failure.
|
||||
pub fn maybeElemValue(val: Value, mod: *Module, index: usize) Allocator.Error!?Value {
|
||||
return val.maybeElemValueFull(null, mod, index);
|
||||
}
|
||||
|
||||
pub fn maybeElemValueFull(val: Value, sema: ?*Sema, mod: *Module, index: usize) Allocator.Error!?Value {
|
||||
return switch (mod.intern_pool.indexToKey(val.toIntern())) {
|
||||
.undef => |ty| Value.fromInterned((try mod.intern(.{
|
||||
.undef = Type.fromInterned(ty).elemType2(mod).toIntern(),
|
||||
}))),
|
||||
.slice => |slice| return Value.fromInterned(slice.ptr).maybeElemValueFull(sema, mod, index),
|
||||
.ptr => |ptr| switch (ptr.addr) {
|
||||
.decl => |decl| mod.declPtr(decl).val.maybeElemValueFull(sema, mod, index),
|
||||
.anon_decl => |anon_decl| Value.fromInterned(anon_decl.val).maybeElemValueFull(sema, mod, index),
|
||||
.comptime_alloc => |idx| if (sema) |s| Value.fromInterned(
|
||||
try s.getComptimeAlloc(idx).val.intern(mod, s.arena),
|
||||
).maybeElemValueFull(sema, mod, index) else null,
|
||||
.int, .eu_payload => null,
|
||||
.opt_payload => |base| Value.fromInterned(base).maybeElemValueFull(sema, mod, index),
|
||||
.comptime_field => |field_val| Value.fromInterned(field_val).maybeElemValueFull(sema, mod, index),
|
||||
.elem => |elem| Value.fromInterned(elem.base).maybeElemValueFull(sema, mod, index + @as(usize, @intCast(elem.index))),
|
||||
.field => |field| if (Value.fromInterned(field.base).pointerDecl(mod)) |decl_index| {
|
||||
const base_decl = mod.declPtr(decl_index);
|
||||
const field_val = try base_decl.val.fieldValue(mod, @as(usize, @intCast(field.index)));
|
||||
return field_val.maybeElemValueFull(sema, mod, index);
|
||||
} else null,
|
||||
},
|
||||
.opt => |opt| Value.fromInterned(opt.val).maybeElemValueFull(sema, mod, index),
|
||||
.aggregate => |aggregate| {
|
||||
const len = mod.intern_pool.aggregateTypeLen(aggregate.ty);
|
||||
const len = ip.aggregateTypeLen(aggregate.ty);
|
||||
if (index < len) return Value.fromInterned(switch (aggregate.storage) {
|
||||
.bytes => |bytes| try mod.intern(.{ .int = .{
|
||||
.bytes => |bytes| try zcu.intern(.{ .int = .{
|
||||
.ty = .u8_type,
|
||||
.storage = .{ .u64 = bytes[index] },
|
||||
} }),
|
||||
@ -1324,10 +1285,10 @@ pub fn maybeElemValueFull(val: Value, sema: ?*Sema, mod: *Module, index: usize)
|
||||
.repeated_elem => |elem| elem,
|
||||
});
|
||||
assert(index == len);
|
||||
return Value.fromInterned(mod.intern_pool.indexToKey(aggregate.ty).array_type.sentinel);
|
||||
return Type.fromInterned(aggregate.ty).sentinel(zcu).?;
|
||||
},
|
||||
else => null,
|
||||
};
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn isLazyAlign(val: Value, mod: *Module) bool {
|
||||
@ -1359,19 +1320,8 @@ pub fn sliceArray(
|
||||
) error{OutOfMemory}!Value {
|
||||
// TODO: write something like getCoercedInts to avoid needing to dupe
|
||||
const mod = sema.mod;
|
||||
return switch (mod.intern_pool.indexToKey(val.toIntern())) {
|
||||
.ptr => |ptr| switch (ptr.addr) {
|
||||
.decl => |decl| try mod.declPtr(decl).val.sliceArray(sema, start, end),
|
||||
.comptime_alloc => |idx| try Value.fromInterned(
|
||||
try sema.getComptimeAlloc(idx).val.intern(mod, sema.arena),
|
||||
).sliceArray(sema, start, end),
|
||||
.comptime_field => |comptime_field| Value.fromInterned(comptime_field)
|
||||
.sliceArray(sema, start, end),
|
||||
.elem => |elem| Value.fromInterned(elem.base)
|
||||
.sliceArray(sema, start + @as(usize, @intCast(elem.index)), end + @as(usize, @intCast(elem.index))),
|
||||
else => unreachable,
|
||||
},
|
||||
.aggregate => |aggregate| Value.fromInterned((try mod.intern(.{ .aggregate = .{
|
||||
const aggregate = mod.intern_pool.indexToKey(val.toIntern()).aggregate;
|
||||
return Value.fromInterned(try mod.intern(.{ .aggregate = .{
|
||||
.ty = switch (mod.intern_pool.indexToKey(mod.intern_pool.typeOf(val.toIntern()))) {
|
||||
.array_type => |array_type| try mod.arrayType(.{
|
||||
.len = @as(u32, @intCast(end - start)),
|
||||
@ -1389,9 +1339,7 @@ pub fn sliceArray(
|
||||
.elems => .{ .elems = try sema.arena.dupe(InternPool.Index, mod.intern_pool.indexToKey(val.toIntern()).aggregate.storage.elems[start..end]) },
|
||||
.repeated_elem => |elem| .{ .repeated_elem = elem },
|
||||
},
|
||||
} }))),
|
||||
else => unreachable,
|
||||
};
|
||||
} }));
|
||||
}
|
||||
|
||||
pub fn fieldValue(val: Value, mod: *Module, index: usize) !Value {
|
||||
@ -3586,6 +3534,10 @@ pub fn isGenericPoison(val: Value) bool {
|
||||
return val.toIntern() == .generic_poison;
|
||||
}
|
||||
|
||||
pub fn typeOf(val: Value, zcu: *const Zcu) Type {
|
||||
return Type.fromInterned(zcu.intern_pool.typeOf(val.toIntern()));
|
||||
}
|
||||
|
||||
/// For an integer (comptime or fixed-width) `val`, returns the comptime-known bounds of the value.
|
||||
/// If `val` is not undef, the bounds are both `val`.
|
||||
/// If `val` is undef and has a fixed-width type, the bounds are the bounds of the type.
|
||||
|
||||
@ -693,31 +693,6 @@ test "string concatenation" {
|
||||
try expect(b[len] == 0);
|
||||
}
|
||||
|
||||
fn manyptrConcat(comptime s: [*:0]const u8) [*:0]const u8 {
|
||||
return "very " ++ s;
|
||||
}
|
||||
|
||||
test "comptime manyptr concatenation" {
|
||||
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
|
||||
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
|
||||
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
|
||||
|
||||
const s = "epic";
|
||||
const actual = manyptrConcat(s);
|
||||
const expected = "very epic";
|
||||
|
||||
const len = mem.len(actual);
|
||||
const len_with_null = len + 1;
|
||||
{
|
||||
var i: u32 = 0;
|
||||
while (i < len_with_null) : (i += 1) {
|
||||
try expect(actual[i] == expected[i]);
|
||||
}
|
||||
}
|
||||
try expect(actual[len] == 0);
|
||||
try expect(expected[len] == 0);
|
||||
}
|
||||
|
||||
test "result location is optional inside error union" {
|
||||
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
|
||||
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user