stage2: type system treats fn ptr and body separately

This commit updates stage2 to enforce the property that the syntax
`fn()void` is a function *body* not a *pointer*. To get a pointer, the
syntax `*const fn()void` is required.

ZIR puts function alignment into the func instruction rather than the
decl because this way it makes it into function types. LLVM backend
respects function alignments.

Struct and Union have methods `fieldSrcLoc` to help look up source
locations of their fields. These trigger full loading, tokenization, and
parsing of source files, so should only be called once it is confirmed
that an error message needs to be printed.

There are some nice new error hints for explaining why a type is
required to be comptime, particularly for structs that contain function
body types.

`Type.requiresComptime` is now moved into Sema because it can fail and
might need to trigger field type resolution. Comptime pointer loading
takes into account types that do not have a well-defined memory layout
and does not try to compute a byte offset for them.

`fn()void` syntax no longer secretly makes a pointer. You get a function
body type, which requires comptime. However a pointer to a function body
can be runtime known (obviously).

Compile errors that report "expected pointer, found ..." are factored
out into convenience functions `checkPtrOperand` and `checkPtrType` and
have a note about function pointers.

Implemented `Value.hash` for functions, enum literals, and undefined values.

stage1 is not updated to this (yet?), so some workarounds and disabled
tests are needed to keep everything working. Should we update stage1 to
these new type semantics? Yes probably because I don't want to add too
much conditional compilation logic in the std lib for the different
backends.
This commit is contained in:
Andrew Kelley 2022-01-21 00:49:58 -07:00
parent 0866fa9d1d
commit b34f994c0b
24 changed files with 858 additions and 356 deletions

View File

@ -730,10 +730,16 @@ pub const CompilerBackend = enum(u64) {
/// therefore must be kept in sync with the compiler implementation.
pub const TestFn = struct {
name: []const u8,
func: fn () anyerror!void,
func: testFnProto,
async_frame_size: ?usize,
};
/// stage1 is *wrong*. It is not yet updated to support the new function type semantics.
const testFnProto = switch (builtin.zig_backend) {
.stage1 => fn () anyerror!void, // wrong!
else => *const fn () anyerror!void,
};
/// This function type is used by the Zig language code generation and
/// therefore must be kept in sync with the compiler implementation.
pub const PanicFn = fn ([]const u8, ?*StackTrace) noreturn;

View File

@ -3240,7 +3240,8 @@ fn fnDecl(
const doc_comment_index = try astgen.docCommentAsString(fn_proto.firstToken());
const has_section_or_addrspace = fn_proto.ast.section_expr != 0 or fn_proto.ast.addrspace_expr != 0;
wip_members.nextDecl(is_pub, is_export, fn_proto.ast.align_expr != 0, has_section_or_addrspace);
// Alignment is passed in the func instruction in this case.
wip_members.nextDecl(is_pub, is_export, false, has_section_or_addrspace);
var params_scope = &fn_gz.base;
const is_var_args = is_var_args: {
@ -3380,7 +3381,7 @@ fn fnDecl(
.param_block = block_inst,
.body_gz = null,
.cc = cc,
.align_inst = .none, // passed in the per-decl data
.align_inst = align_inst,
.lib_name = lib_name,
.is_var_args = is_var_args,
.is_inferred_error = false,
@ -3423,7 +3424,7 @@ fn fnDecl(
.ret_br = ret_br,
.body_gz = &fn_gz,
.cc = cc,
.align_inst = .none, // passed in the per-decl data
.align_inst = align_inst,
.lib_name = lib_name,
.is_var_args = is_var_args,
.is_inferred_error = is_inferred_error,
@ -3449,9 +3450,6 @@ fn fnDecl(
wip_members.appendToDecl(fn_name_str_index);
wip_members.appendToDecl(block_inst);
wip_members.appendToDecl(doc_comment_index);
if (align_inst != .none) {
wip_members.appendToDecl(@enumToInt(align_inst));
}
if (has_section_or_addrspace) {
wip_members.appendToDecl(@enumToInt(section_inst));
wip_members.appendToDecl(@enumToInt(addrspace_inst));

View File

@ -898,6 +898,45 @@ pub const Struct = struct {
};
}
pub fn fieldSrcLoc(s: Struct, gpa: Allocator, query: FieldSrcQuery) SrcLoc {
@setCold(true);
const tree = s.owner_decl.getFileScope().getTree(gpa) catch |err| {
// In this case we emit a warning + a less precise source location.
log.warn("unable to load {s}: {s}", .{
s.owner_decl.getFileScope().sub_file_path, @errorName(err),
});
return s.srcLoc();
};
const node = s.owner_decl.relativeToNodeIndex(s.node_offset);
const node_tags = tree.nodes.items(.tag);
const file = s.owner_decl.getFileScope();
switch (node_tags[node]) {
.container_decl,
.container_decl_trailing,
=> return queryFieldSrc(tree.*, query, file, tree.containerDecl(node)),
.container_decl_two, .container_decl_two_trailing => {
var buffer: [2]Ast.Node.Index = undefined;
return queryFieldSrc(tree.*, query, file, tree.containerDeclTwo(&buffer, node));
},
.container_decl_arg,
.container_decl_arg_trailing,
=> return queryFieldSrc(tree.*, query, file, tree.containerDeclArg(node)),
.tagged_union,
.tagged_union_trailing,
=> return queryFieldSrc(tree.*, query, file, tree.taggedUnion(node)),
.tagged_union_two, .tagged_union_two_trailing => {
var buffer: [2]Ast.Node.Index = undefined;
return queryFieldSrc(tree.*, query, file, tree.taggedUnionTwo(&buffer, node));
},
.tagged_union_enum_tag,
.tagged_union_enum_tag_trailing,
=> return queryFieldSrc(tree.*, query, file, tree.taggedUnionEnumTag(node)),
else => unreachable,
}
}
pub fn haveFieldTypes(s: Struct) bool {
return switch (s.status) {
.none,
@ -1063,6 +1102,33 @@ pub const Union = struct {
};
}
pub fn fieldSrcLoc(u: Union, gpa: Allocator, query: FieldSrcQuery) SrcLoc {
@setCold(true);
const tree = u.owner_decl.getFileScope().getTree(gpa) catch |err| {
// In this case we emit a warning + a less precise source location.
log.warn("unable to load {s}: {s}", .{
u.owner_decl.getFileScope().sub_file_path, @errorName(err),
});
return u.srcLoc();
};
const node = u.owner_decl.relativeToNodeIndex(u.node_offset);
const node_tags = tree.nodes.items(.tag);
const file = u.owner_decl.getFileScope();
switch (node_tags[node]) {
.container_decl,
.container_decl_trailing,
=> return queryFieldSrc(tree.*, query, file, tree.containerDecl(node)),
.container_decl_two, .container_decl_two_trailing => {
var buffer: [2]Ast.Node.Index = undefined;
return queryFieldSrc(tree.*, query, file, tree.containerDeclTwo(&buffer, node));
},
.container_decl_arg,
.container_decl_arg_trailing,
=> return queryFieldSrc(tree.*, query, file, tree.containerDeclArg(node)),
else => unreachable,
}
}
pub fn haveFieldTypes(u: Union) bool {
return switch (u.status) {
.none,
@ -4662,8 +4728,8 @@ pub fn createAnonymousDeclFromDeclNamed(
new_decl.src_line = src_decl.src_line;
new_decl.ty = typed_value.ty;
new_decl.val = typed_value.val;
new_decl.align_val = Value.initTag(.null_value);
new_decl.linksection_val = Value.initTag(.null_value);
new_decl.align_val = Value.@"null";
new_decl.linksection_val = Value.@"null";
new_decl.has_tv = true;
new_decl.analysis = .complete;
new_decl.generation = mod.generation;
@ -4905,6 +4971,55 @@ pub const PeerTypeCandidateSrc = union(enum) {
}
};
const FieldSrcQuery = struct {
index: usize,
range: enum { name, type, value, alignment },
};
fn queryFieldSrc(
tree: Ast,
query: FieldSrcQuery,
file_scope: *File,
container_decl: Ast.full.ContainerDecl,
) SrcLoc {
const node_tags = tree.nodes.items(.tag);
var field_index: usize = 0;
for (container_decl.ast.members) |member_node| {
const field = switch (node_tags[member_node]) {
.container_field_init => tree.containerFieldInit(member_node),
.container_field_align => tree.containerFieldAlign(member_node),
.container_field => tree.containerField(member_node),
else => continue,
};
if (field_index == query.index) {
return switch (query.range) {
.name => .{
.file_scope = file_scope,
.parent_decl_node = 0,
.lazy = .{ .token_abs = field.ast.name_token },
},
.type => .{
.file_scope = file_scope,
.parent_decl_node = 0,
.lazy = .{ .node_abs = field.ast.type_expr },
},
.value => .{
.file_scope = file_scope,
.parent_decl_node = 0,
.lazy = .{ .node_abs = field.ast.value_expr },
},
.alignment => .{
.file_scope = file_scope,
.parent_decl_node = 0,
.lazy = .{ .node_abs = field.ast.align_expr },
},
};
}
field_index += 1;
}
unreachable;
}
/// Called from `performAllTheWork`, after all AstGen workers have finished,
/// and before the main semantic analysis loop begins.
pub fn processOutdatedAndDeletedDecls(mod: *Module) !void {

View File

@ -4147,7 +4147,7 @@ fn analyzeCall(
const gpa = sema.gpa;
const is_comptime_call = block.is_comptime or modifier == .compile_time or
func_ty_info.return_type.requiresComptime();
try sema.typeRequiresComptime(block, func_src, func_ty_info.return_type);
const is_inline_call = is_comptime_call or modifier == .always_inline or
func_ty_info.cc == .Inline;
const result: Air.Inst.Ref = if (is_inline_call) res: {
@ -4576,7 +4576,7 @@ fn analyzeCall(
}
} else if (is_anytype) {
const arg_ty = sema.typeOf(arg);
if (arg_ty.requiresComptime()) {
if (try sema.typeRequiresComptime(block, arg_src, arg_ty)) {
const arg_val = try sema.resolveConstValue(block, arg_src, arg);
const child_arg = try child_sema.addConstant(arg_ty, arg_val);
child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg);
@ -5430,7 +5430,6 @@ fn funcCommon(
src_locs: Zir.Inst.Func.SrcLocs,
opt_lib_name: ?[]const u8,
) CompileError!Air.Inst.Ref {
const src: LazySrcLoc = .{ .node_offset = src_node_offset };
const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = src_node_offset };
// The return type body might be a type expression that depends on generic parameters.
@ -5481,11 +5480,22 @@ fn funcCommon(
errdefer if (maybe_inferred_error_set_node) |node| sema.gpa.destroy(node);
// Note: no need to errdefer since this will still be in its default state at the end of the function.
const target = mod.getTarget();
const fn_ty: Type = fn_ty: {
const alignment: u32 = if (align_val.tag() == .null_value) 0 else a: {
const alignment = @intCast(u32, align_val.toUnsignedInt());
if (alignment == target_util.defaultFunctionAlignment(target)) {
break :a 0;
} else {
break :a alignment;
}
};
// Hot path for some common function types.
// TODO can we eliminate some of these Type tag values? seems unnecessarily complicated.
if (!is_generic and block.params.items.len == 0 and !var_args and
align_val.tag() == .null_value and !inferred_error_set)
alignment == 0 and !inferred_error_set)
{
if (bare_return_type.zigTypeTag() == .NoReturn and cc == .Unspecified) {
break :fn_ty Type.initTag(.fn_noreturn_no_args);
@ -5507,16 +5517,15 @@ fn funcCommon(
const param_types = try sema.arena.alloc(Type, block.params.items.len);
const comptime_params = try sema.arena.alloc(bool, block.params.items.len);
for (block.params.items) |param, i| {
const param_src: LazySrcLoc = .{ .node_offset = src_node_offset }; // TODO better src
param_types[i] = param.ty;
comptime_params[i] = param.is_comptime or param.ty.requiresComptime();
comptime_params[i] = param.is_comptime or
try sema.typeRequiresComptime(block, param_src, param.ty);
is_generic = is_generic or comptime_params[i] or param.ty.tag() == .generic_poison;
}
if (align_val.tag() != .null_value) {
return sema.fail(block, src, "TODO implement support for function prototypes to have alignment specified", .{});
}
is_generic = is_generic or bare_return_type.requiresComptime();
is_generic = is_generic or
try sema.typeRequiresComptime(block, ret_ty_src, bare_return_type);
const return_type = if (!inferred_error_set or bare_return_type.tag() == .generic_poison)
bare_return_type
@ -5537,6 +5546,7 @@ fn funcCommon(
.comptime_params = comptime_params.ptr,
.return_type = return_type,
.cc = cc,
.alignment = alignment,
.is_var_args = var_args,
.is_generic = is_generic,
});
@ -5550,7 +5560,6 @@ fn funcCommon(
lib_name, @errorName(err),
});
};
const target = mod.getTarget();
if (target_util.is_libc_lib_name(target, lib_name)) {
if (!mod.comp.bin_file.options.link_libc) {
return sema.fail(
@ -5591,12 +5600,7 @@ fn funcCommon(
}
if (body_inst == 0) {
const fn_ptr_ty = try Type.ptr(sema.arena, .{
.pointee_type = fn_ty,
.@"addrspace" = .generic,
.mutable = false,
});
return sema.addType(fn_ptr_ty);
return sema.addType(fn_ty);
}
const is_inline = fn_ty.fnCallingConvention() == .Inline;
@ -5632,7 +5636,7 @@ fn zirParam(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
is_comptime: bool,
comptime_syntax: bool,
) CompileError!void {
const inst_data = sema.code.instructions.items(.data)[inst].pl_tok;
const src = inst_data.src();
@ -5669,7 +5673,7 @@ fn zirParam(
// insert an anytype parameter.
try block.params.append(sema.gpa, .{
.ty = Type.initTag(.generic_poison),
.is_comptime = is_comptime,
.is_comptime = comptime_syntax,
});
try sema.inst_map.putNoClobber(sema.gpa, inst, .generic_poison);
return;
@ -5677,8 +5681,10 @@ fn zirParam(
else => |e| return e,
}
};
const is_comptime = comptime_syntax or
try sema.typeRequiresComptime(block, src, param_ty);
if (sema.inst_map.get(inst)) |arg| {
if (is_comptime or param_ty.requiresComptime()) {
if (is_comptime) {
// We have a comptime value for this parameter so it should be elided from the
// function type of the function instruction in this block.
const coerced_arg = try sema.coerce(block, param_ty, arg, src);
@ -5692,7 +5698,7 @@ fn zirParam(
try block.params.append(sema.gpa, .{
.ty = param_ty,
.is_comptime = is_comptime or param_ty.requiresComptime(),
.is_comptime = is_comptime,
});
const result = try sema.addConstant(param_ty, Value.initTag(.generic_poison));
try sema.inst_map.putNoClobber(sema.gpa, inst, result);
@ -5702,9 +5708,10 @@ fn zirParamAnytype(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
is_comptime: bool,
comptime_syntax: bool,
) CompileError!void {
const inst_data = sema.code.instructions.items(.data)[inst].str_tok;
const src = inst_data.src();
const param_name = inst_data.get(sema.code);
// TODO check if param_name shadows a Decl. This only needs to be done if
@ -5713,7 +5720,7 @@ fn zirParamAnytype(
if (sema.inst_map.get(inst)) |air_ref| {
const param_ty = sema.typeOf(air_ref);
if (is_comptime or param_ty.requiresComptime()) {
if (comptime_syntax or try sema.typeRequiresComptime(block, src, param_ty)) {
// We have a comptime value for this parameter so it should be elided from the
// function type of the function instruction in this block.
return;
@ -5730,7 +5737,7 @@ fn zirParamAnytype(
try block.params.append(sema.gpa, .{
.ty = Type.initTag(.generic_poison),
.is_comptime = is_comptime,
.is_comptime = comptime_syntax,
});
try sema.inst_map.put(sema.gpa, inst, .generic_poison);
}
@ -11118,8 +11125,7 @@ fn zirIntToPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
const type_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
const type_res = try sema.resolveType(block, src, extra.lhs);
if (type_res.zigTypeTag() != .Pointer)
return sema.fail(block, type_src, "expected pointer, found '{}'", .{type_res});
try sema.checkPtrType(block, type_src, type_res);
const ptr_align = type_res.ptrAlignment(sema.mod.getTarget());
if (try sema.resolveDefinedValue(block, operand_src, operand_coerced)) |val| {
@ -11176,16 +11182,8 @@ fn zirPtrCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
const dest_ty = try sema.resolveType(block, dest_ty_src, extra.lhs);
const operand = sema.resolveInst(extra.rhs);
const operand_ty = sema.typeOf(operand);
if (operand_ty.zigTypeTag() != .Pointer) {
return sema.fail(block, operand_src, "expected pointer, found {s} type '{}'", .{
@tagName(operand_ty.zigTypeTag()), operand_ty,
});
}
if (dest_ty.zigTypeTag() != .Pointer) {
return sema.fail(block, dest_ty_src, "expected pointer, found {s} type '{}'", .{
@tagName(dest_ty.zigTypeTag()), dest_ty,
});
}
try sema.checkPtrType(block, dest_ty_src, dest_ty);
try sema.checkPtrOperand(block, operand_src, operand_ty);
return sema.coerceCompatiblePtrs(block, dest_ty, operand, operand_src);
}
@ -11264,7 +11262,7 @@ fn zirAlignCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
// TODO in addition to pointers, this instruction is supposed to work for
// pointer-like optionals and slices.
try sema.checkPtrType(block, ptr_src, ptr_ty);
try sema.checkPtrOperand(block, ptr_src, ptr_ty);
// TODO compile error if the result pointer is comptime known and would have an
// alignment that disagrees with the Decl's alignment.
@ -11462,6 +11460,34 @@ fn checkIntType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileEr
}
}
fn checkPtrOperand(
sema: *Sema,
block: *Block,
ty_src: LazySrcLoc,
ty: Type,
) CompileError!void {
switch (ty.zigTypeTag()) {
.Pointer => {},
.Fn => {
const msg = msg: {
const msg = try sema.errMsg(
block,
ty_src,
"expected pointer, found {}",
.{ty},
);
errdefer msg.destroy(sema.gpa);
try sema.errNote(block, ty_src, msg, "use '&' to obtain a function pointer", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(msg);
},
else => return sema.fail(block, ty_src, "expected pointer, found '{}'", .{ty}),
}
}
fn checkPtrType(
sema: *Sema,
block: *Block,
@ -11470,6 +11496,22 @@ fn checkPtrType(
) CompileError!void {
switch (ty.zigTypeTag()) {
.Pointer => {},
.Fn => {
const msg = msg: {
const msg = try sema.errMsg(
block,
ty_src,
"expected pointer type, found '{}'",
.{ty},
);
errdefer msg.destroy(sema.gpa);
try sema.errNote(block, ty_src, msg, "use '*const ' to make a function pointer type", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(msg);
},
else => return sema.fail(block, ty_src, "expected pointer type, found '{}'", .{ty}),
}
}
@ -12139,20 +12181,14 @@ fn zirMemcpy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
const dest_ptr = sema.resolveInst(extra.dest);
const dest_ptr_ty = sema.typeOf(dest_ptr);
if (dest_ptr_ty.zigTypeTag() != .Pointer) {
return sema.fail(block, dest_src, "expected pointer, found '{}'", .{dest_ptr_ty});
}
try sema.checkPtrOperand(block, dest_src, dest_ptr_ty);
if (dest_ptr_ty.isConstPtr()) {
return sema.fail(block, dest_src, "cannot store through const pointer '{}'", .{dest_ptr_ty});
}
const uncasted_src_ptr = sema.resolveInst(extra.source);
const uncasted_src_ptr_ty = sema.typeOf(uncasted_src_ptr);
if (uncasted_src_ptr_ty.zigTypeTag() != .Pointer) {
return sema.fail(block, src_src, "expected pointer, found '{}'", .{
uncasted_src_ptr_ty,
});
}
try sema.checkPtrOperand(block, src_src, uncasted_src_ptr_ty);
const src_ptr_info = uncasted_src_ptr_ty.ptrInfo().data;
const wanted_src_ptr_ty = try Type.ptr(sema.arena, .{
.pointee_type = dest_ptr_ty.elemType2(),
@ -12203,9 +12239,7 @@ fn zirMemset(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
const len_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node };
const dest_ptr = sema.resolveInst(extra.dest);
const dest_ptr_ty = sema.typeOf(dest_ptr);
if (dest_ptr_ty.zigTypeTag() != .Pointer) {
return sema.fail(block, dest_src, "expected pointer, found '{}'", .{dest_ptr_ty});
}
try sema.checkPtrOperand(block, dest_src, dest_ptr_ty);
if (dest_ptr_ty.isConstPtr()) {
return sema.fail(block, dest_src, "cannot store through const pointer '{}'", .{dest_ptr_ty});
}
@ -12487,7 +12521,7 @@ fn zirPrefetch(
const opts_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = extra.node };
const options_ty = try sema.getBuiltinType(block, opts_src, "PrefetchOptions");
const ptr = sema.resolveInst(extra.lhs);
try sema.checkPtrType(block, ptr_src, sema.typeOf(ptr));
try sema.checkPtrOperand(block, ptr_src, sema.typeOf(ptr));
const options = try sema.coerce(block, options_ty, sema.resolveInst(extra.rhs), opts_src);
const rw = try sema.fieldVal(block, opts_src, options, "rw", opts_src);
@ -12568,12 +12602,15 @@ fn validateVarType(
.Type,
.Undefined,
.Null,
.Fn,
=> break,
.Pointer => {
const elem_ty = ty.childType();
if (elem_ty.zigTypeTag() == .Opaque) return;
ty = elem_ty;
switch (elem_ty.zigTypeTag()) {
.Opaque, .Fn => return,
else => ty = elem_ty,
}
},
.Opaque => if (is_extern) return else break,
@ -12586,9 +12623,9 @@ fn validateVarType(
.ErrorUnion => ty = ty.errorUnionPayload(),
.Fn, .Struct, .Union => {
.Struct, .Union => {
const resolved_ty = try sema.resolveTypeFields(block, src, ty);
if (resolved_ty.requiresComptime()) {
if (try sema.typeRequiresComptime(block, src, resolved_ty)) {
break;
} else {
return;
@ -12596,7 +12633,99 @@ fn validateVarType(
},
} else unreachable; // TODO should not need else unreachable
return sema.fail(block, src, "variable of type '{}' must be const or comptime", .{var_ty});
const msg = msg: {
const msg = try sema.errMsg(block, src, "variable of type '{}' must be const or comptime", .{var_ty});
errdefer msg.destroy(sema.gpa);
try sema.explainWhyTypeIsComptime(block, src, msg, src.toSrcLoc(block.src_decl), var_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(msg);
}
fn explainWhyTypeIsComptime(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
msg: *Module.ErrorMsg,
src_loc: Module.SrcLoc,
ty: Type,
) CompileError!void {
const mod = sema.mod;
switch (ty.zigTypeTag()) {
.Bool,
.Int,
.Float,
.ErrorSet,
.Enum,
.Frame,
.AnyFrame,
.Void,
=> return,
.Fn => {
try mod.errNoteNonLazy(src_loc, msg, "use '*const {}' for a function pointer type", .{
ty,
});
},
.Type => {
try mod.errNoteNonLazy(src_loc, msg, "types are not available at runtime", .{});
},
.BoundFn,
.ComptimeFloat,
.ComptimeInt,
.EnumLiteral,
.NoReturn,
.Undefined,
.Null,
.Opaque,
.Optional,
=> return,
.Pointer, .Array, .Vector => {
try sema.explainWhyTypeIsComptime(block, src, msg, src_loc, ty.elemType());
},
.ErrorUnion => {
try sema.explainWhyTypeIsComptime(block, src, msg, src_loc, ty.errorUnionPayload());
},
.Struct => {
if (ty.castTag(.@"struct")) |payload| {
const struct_obj = payload.data;
for (struct_obj.fields.values()) |field, i| {
const field_src_loc = struct_obj.fieldSrcLoc(sema.gpa, .{
.index = i,
.range = .type,
});
if (try sema.typeRequiresComptime(block, src, field.ty)) {
try mod.errNoteNonLazy(field_src_loc, msg, "struct requires comptime because of this field", .{});
try sema.explainWhyTypeIsComptime(block, src, msg, field_src_loc, field.ty);
}
}
}
// TODO tuples
},
.Union => {
if (ty.cast(Type.Payload.Union)) |payload| {
const union_obj = payload.data;
for (union_obj.fields.values()) |field, i| {
const field_src_loc = union_obj.fieldSrcLoc(sema.gpa, .{
.index = i,
.range = .type,
});
if (try sema.typeRequiresComptime(block, src, field.ty)) {
try mod.errNoteNonLazy(field_src_loc, msg, "union requires comptime because of this field", .{});
try sema.explainWhyTypeIsComptime(block, src, msg, field_src_loc, field.ty);
}
}
}
},
}
}
pub const PanicId = enum {
@ -13883,6 +14012,10 @@ fn coerce(
{
return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src);
}
// This will give an extra hint on top of what the bottom of this func would provide.
try sema.checkPtrOperand(block, dest_ty_src, inst_ty);
unreachable;
},
.Int, .ComptimeInt => switch (inst_ty.zigTypeTag()) {
.Float, .ComptimeFloat => float: {
@ -14683,7 +14816,8 @@ const ComptimePtrLoadKit = struct {
/// The Type of the parent Value.
ty: Type,
/// The starting byte offset of `val` from `root_val`.
byte_offset: usize,
/// If the type does not have a well-defined memory layout, this is null.
byte_offset: ?usize,
/// Whether the `root_val` could be mutated by further
/// semantic analysis and a copy must be performed.
is_mutable: bool,
@ -14738,12 +14872,24 @@ fn beginComptimePtrLoad(
});
}
const elem_ty = parent.ty.childType();
const elem_size = elem_ty.abiSize(target);
const byte_offset: ?usize = bo: {
if (try sema.typeRequiresComptime(block, src, elem_ty)) {
break :bo null;
} else {
if (parent.byte_offset) |off| {
try sema.resolveTypeLayout(block, src, elem_ty);
const elem_size = elem_ty.abiSize(target);
break :bo try sema.usizeCast(block, src, off + elem_size * elem_ptr.index);
} else {
break :bo null;
}
}
};
return ComptimePtrLoadKit{
.root_val = parent.root_val,
.val = try parent.val.elemValue(sema.arena, elem_ptr.index),
.ty = elem_ty,
.byte_offset = try sema.usizeCast(block, src, parent.byte_offset + elem_size * elem_ptr.index),
.byte_offset = byte_offset,
.is_mutable = parent.is_mutable,
};
},
@ -14768,13 +14914,24 @@ fn beginComptimePtrLoad(
const field_ptr = ptr_val.castTag(.field_ptr).?.data;
const parent = try beginComptimePtrLoad(sema, block, src, field_ptr.container_ptr);
const field_index = @intCast(u32, field_ptr.field_index);
try sema.resolveTypeLayout(block, src, parent.ty);
const field_offset = parent.ty.structFieldOffset(field_index, target);
const byte_offset: ?usize = bo: {
if (try sema.typeRequiresComptime(block, src, parent.ty)) {
break :bo null;
} else {
if (parent.byte_offset) |off| {
try sema.resolveTypeLayout(block, src, parent.ty);
const field_offset = parent.ty.structFieldOffset(field_index, target);
break :bo try sema.usizeCast(block, src, off + field_offset);
} else {
break :bo null;
}
}
};
return ComptimePtrLoadKit{
.root_val = parent.root_val,
.val = try parent.val.fieldValue(sema.arena, field_index),
.ty = parent.ty.structFieldType(field_index),
.byte_offset = try sema.usizeCast(block, src, parent.byte_offset + field_offset),
.byte_offset = byte_offset,
.is_mutable = parent.is_mutable,
};
},
@ -14785,7 +14942,7 @@ fn beginComptimePtrLoad(
.root_val = parent.root_val,
.val = parent.val.castTag(.eu_payload).?.data,
.ty = parent.ty.errorUnionPayload(),
.byte_offset = undefined,
.byte_offset = null,
.is_mutable = parent.is_mutable,
};
},
@ -14796,7 +14953,7 @@ fn beginComptimePtrLoad(
.root_val = parent.root_val,
.val = parent.val.castTag(.opt_payload).?.data,
.ty = try parent.ty.optionalChildAlloc(sema.arena),
.byte_offset = undefined,
.byte_offset = null,
.is_mutable = parent.is_mutable,
};
},
@ -16090,28 +16247,12 @@ fn resolveTypeFields(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) Comp
switch (ty.tag()) {
.@"struct" => {
const struct_obj = ty.castTag(.@"struct").?.data;
switch (struct_obj.status) {
.none => {},
.field_types_wip => {
return sema.fail(block, src, "struct {} depends on itself", .{ty});
},
.have_field_types,
.have_layout,
.layout_wip,
.fully_resolved_wip,
.fully_resolved,
=> return ty,
}
struct_obj.status = .field_types_wip;
try semaStructFields(sema.mod, struct_obj);
if (struct_obj.fields.count() == 0) {
struct_obj.status = .have_layout;
} else {
struct_obj.status = .have_field_types;
}
try sema.resolveTypeFieldsStruct(block, src, ty, struct_obj);
return ty;
},
.@"union", .union_tagged => {
const union_obj = ty.cast(Type.Payload.Union).?.data;
try sema.resolveTypeFieldsUnion(block, src, ty, union_obj);
return ty;
},
.type_info => return sema.resolveBuiltinTypeFields(block, src, "TypeInfo"),
@ -16126,31 +16267,65 @@ fn resolveTypeFields(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) Comp
.call_options => return sema.resolveBuiltinTypeFields(block, src, "CallOptions"),
.prefetch_options => return sema.resolveBuiltinTypeFields(block, src, "PrefetchOptions"),
.@"union", .union_tagged => {
const union_obj = ty.cast(Type.Payload.Union).?.data;
switch (union_obj.status) {
.none => {},
.field_types_wip => {
return sema.fail(block, src, "union {} depends on itself", .{ty});
},
.have_field_types,
.have_layout,
.layout_wip,
.fully_resolved_wip,
.fully_resolved,
=> return ty,
}
union_obj.status = .field_types_wip;
try semaUnionFields(sema.mod, union_obj);
union_obj.status = .have_field_types;
return ty;
},
else => return ty,
}
}
fn resolveTypeFieldsStruct(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
ty: Type,
struct_obj: *Module.Struct,
) CompileError!void {
switch (struct_obj.status) {
.none => {},
.field_types_wip => {
return sema.fail(block, src, "struct {} depends on itself", .{ty});
},
.have_field_types,
.have_layout,
.layout_wip,
.fully_resolved_wip,
.fully_resolved,
=> return,
}
struct_obj.status = .field_types_wip;
try semaStructFields(sema.mod, struct_obj);
if (struct_obj.fields.count() == 0) {
struct_obj.status = .have_layout;
} else {
struct_obj.status = .have_field_types;
}
}
fn resolveTypeFieldsUnion(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
ty: Type,
union_obj: *Module.Union,
) CompileError!void {
switch (union_obj.status) {
.none => {},
.field_types_wip => {
return sema.fail(block, src, "union {} depends on itself", .{ty});
},
.have_field_types,
.have_layout,
.layout_wip,
.fully_resolved_wip,
.fully_resolved,
=> return,
}
union_obj.status = .field_types_wip;
try semaUnionFields(sema.mod, union_obj);
union_obj.status = .have_field_types;
}
fn resolveBuiltinTypeFields(
sema: *Sema,
block: *Block,
@ -17295,3 +17470,198 @@ fn typePtrOrOptionalPtrTy(
else => return null,
}
}
/// Anything that reports hasCodeGenBits() false returns false here as well.
/// `generic_poison` will return false.
/// This function returns false negatives when structs and unions are having their
/// field types resolved.
fn typeRequiresComptime(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError!bool {
return switch (ty.tag()) {
.u1,
.u8,
.i8,
.u16,
.i16,
.u32,
.i32,
.u64,
.i64,
.u128,
.i128,
.usize,
.isize,
.c_short,
.c_ushort,
.c_int,
.c_uint,
.c_long,
.c_ulong,
.c_longlong,
.c_ulonglong,
.c_longdouble,
.f16,
.f32,
.f64,
.f128,
.anyopaque,
.bool,
.void,
.anyerror,
.noreturn,
.@"anyframe",
.@"null",
.@"undefined",
.atomic_order,
.atomic_rmw_op,
.calling_convention,
.address_space,
.float_mode,
.reduce_op,
.call_options,
.prefetch_options,
.export_options,
.extern_options,
.manyptr_u8,
.manyptr_const_u8,
.manyptr_const_u8_sentinel_0,
.const_slice_u8,
.const_slice_u8_sentinel_0,
.anyerror_void_error_union,
.empty_struct_literal,
.empty_struct,
.error_set,
.error_set_single,
.error_set_inferred,
.error_set_merged,
.@"opaque",
.generic_poison,
.array_u8,
.array_u8_sentinel_0,
.int_signed,
.int_unsigned,
.enum_simple,
=> false,
.single_const_pointer_to_comptime_int,
.type,
.comptime_int,
.comptime_float,
.enum_literal,
.type_info,
// These are function bodies, not function pointers.
.fn_noreturn_no_args,
.fn_void_no_args,
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
.function,
=> true,
.var_args_param => unreachable,
.inferred_alloc_mut => unreachable,
.inferred_alloc_const => unreachable,
.bound_fn => unreachable,
.array,
.array_sentinel,
.vector,
=> return sema.typeRequiresComptime(block, src, ty.childType()),
.pointer,
.single_const_pointer,
.single_mut_pointer,
.many_const_pointer,
.many_mut_pointer,
.c_const_pointer,
.c_mut_pointer,
.const_slice,
.mut_slice,
=> {
const child_ty = ty.childType();
if (child_ty.zigTypeTag() == .Fn) {
return false;
} else {
return sema.typeRequiresComptime(block, src, child_ty);
}
},
.optional,
.optional_single_mut_pointer,
.optional_single_const_pointer,
=> {
var buf: Type.Payload.ElemType = undefined;
return sema.typeRequiresComptime(block, src, ty.optionalChild(&buf));
},
.tuple => {
const tuple = ty.castTag(.tuple).?.data;
for (tuple.types) |field_ty| {
if (try sema.typeRequiresComptime(block, src, field_ty)) {
return true;
}
}
return false;
},
.@"struct" => {
const struct_obj = ty.castTag(.@"struct").?.data;
switch (struct_obj.requires_comptime) {
.no, .wip => return false,
.yes => return true,
.unknown => {
if (struct_obj.status == .field_types_wip)
return false;
try sema.resolveTypeFieldsStruct(block, src, ty, struct_obj);
struct_obj.requires_comptime = .wip;
for (struct_obj.fields.values()) |field| {
if (try sema.typeRequiresComptime(block, src, field.ty)) {
struct_obj.requires_comptime = .yes;
return true;
}
}
struct_obj.requires_comptime = .no;
return false;
},
}
},
.@"union", .union_tagged => {
const union_obj = ty.cast(Type.Payload.Union).?.data;
switch (union_obj.requires_comptime) {
.no, .wip => return false,
.yes => return true,
.unknown => {
if (union_obj.status == .field_types_wip)
return false;
try sema.resolveTypeFieldsUnion(block, src, ty, union_obj);
union_obj.requires_comptime = .wip;
for (union_obj.fields.values()) |field| {
if (try sema.typeRequiresComptime(block, src, field.ty)) {
union_obj.requires_comptime = .yes;
return true;
}
}
union_obj.requires_comptime = .no;
return false;
},
}
},
.error_union => return sema.typeRequiresComptime(block, src, ty.errorUnionPayload()),
.anyframe_T => {
const child_ty = ty.castTag(.anyframe_T).?.data;
return sema.typeRequiresComptime(block, src, child_ty);
},
.enum_numbered => {
const tag_ty = ty.castTag(.enum_numbered).?.data.tag_ty;
return sema.typeRequiresComptime(block, src, tag_ty);
},
.enum_full, .enum_nonexhaustive => {
const tag_ty = ty.cast(Type.Payload.EnumFull).?.data.tag_ty;
return sema.typeRequiresComptime(block, src, tag_ty);
},
};
}

View File

@ -725,6 +725,10 @@ pub const DeclGen = struct {
llvm_fn.setFunctionCallConv(toLlvmCallConv(fn_info.cc, target));
}
if (fn_info.alignment != 0) {
llvm_fn.setAlignment(fn_info.alignment);
}
// Function attributes that are independent of analysis results of the function body.
dg.addCommonFnAttributes(llvm_fn);

View File

@ -637,3 +637,12 @@ pub fn llvmMachineAbi(target: std.Target) ?[:0]const u8 {
else => return null,
}
}
pub fn defaultFunctionAlignment(target: std.Target) u32 {
return switch (target.cpu.arch) {
.arm, .armeb => 4,
.aarch64, .aarch64_32, .aarch64_be => 4,
.riscv64 => 2,
else => 1,
};
}

View File

@ -5,6 +5,7 @@ const Allocator = std.mem.Allocator;
const Target = std.Target;
const Module = @import("Module.zig");
const log = std.log.scoped(.Type);
const target_util = @import("target.zig");
const file_struct = @This();
@ -577,21 +578,36 @@ pub const Type = extern union {
}
},
.Fn => {
if (!a.fnReturnType().eql(b.fnReturnType()))
const a_info = a.fnInfo();
const b_info = b.fnInfo();
if (!eql(a_info.return_type, b_info.return_type))
return false;
if (a.fnCallingConvention() != b.fnCallingConvention())
if (a_info.cc != b_info.cc)
return false;
const a_param_len = a.fnParamLen();
const b_param_len = b.fnParamLen();
if (a_param_len != b_param_len)
if (a_info.param_types.len != b_info.param_types.len)
return false;
var i: usize = 0;
while (i < a_param_len) : (i += 1) {
if (!a.fnParamType(i).eql(b.fnParamType(i)))
for (a_info.param_types) |a_param_ty, i| {
const b_param_ty = b_info.param_types[i];
if (!eql(a_param_ty, b_param_ty))
return false;
if (a_info.comptime_params[i] != b_info.comptime_params[i])
return false;
}
if (a.fnIsVarArgs() != b.fnIsVarArgs())
if (a_info.alignment != b_info.alignment)
return false;
if (a_info.is_var_args != b_info.is_var_args)
return false;
if (a_info.is_generic != b_info.is_generic)
return false;
return true;
},
.Optional => {
@ -686,6 +702,7 @@ pub const Type = extern union {
return false;
},
.Float => return a.tag() == b.tag(),
.BoundFn,
.Frame,
=> std.debug.panic("TODO implement Type equality comparison of {} and {}", .{ a, b }),
@ -937,6 +954,7 @@ pub const Type = extern union {
.return_type = try payload.return_type.copy(allocator),
.param_types = param_types,
.cc = payload.cc,
.alignment = payload.alignment,
.is_var_args = payload.is_var_args,
.is_generic = payload.is_generic,
.comptime_params = comptime_params.ptr,
@ -1114,9 +1132,15 @@ pub const Type = extern union {
}
try writer.writeAll("...");
}
try writer.writeAll(") callconv(.");
try writer.writeAll(@tagName(payload.cc));
try writer.writeAll(") ");
if (payload.cc != .Unspecified) {
try writer.writeAll("callconv(.");
try writer.writeAll(@tagName(payload.cc));
try writer.writeAll(") ");
}
if (payload.alignment != 0) {
try writer.print("align({d}) ", .{payload.alignment});
}
ty = payload.return_type;
continue;
},
@ -1423,170 +1447,6 @@ pub const Type = extern union {
}
}
/// Anything that reports hasCodeGenBits() false returns false here as well.
/// `generic_poison` will return false.
pub fn requiresComptime(ty: Type) bool {
return switch (ty.tag()) {
.u1,
.u8,
.i8,
.u16,
.i16,
.u32,
.i32,
.u64,
.i64,
.u128,
.i128,
.usize,
.isize,
.c_short,
.c_ushort,
.c_int,
.c_uint,
.c_long,
.c_ulong,
.c_longlong,
.c_ulonglong,
.c_longdouble,
.f16,
.f32,
.f64,
.f128,
.anyopaque,
.bool,
.void,
.anyerror,
.noreturn,
.@"anyframe",
.@"null",
.@"undefined",
.atomic_order,
.atomic_rmw_op,
.calling_convention,
.address_space,
.float_mode,
.reduce_op,
.call_options,
.prefetch_options,
.export_options,
.extern_options,
.manyptr_u8,
.manyptr_const_u8,
.manyptr_const_u8_sentinel_0,
.fn_noreturn_no_args,
.fn_void_no_args,
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
.const_slice_u8,
.const_slice_u8_sentinel_0,
.anyerror_void_error_union,
.empty_struct_literal,
.function,
.empty_struct,
.error_set,
.error_set_single,
.error_set_inferred,
.error_set_merged,
.@"opaque",
.generic_poison,
.array_u8,
.array_u8_sentinel_0,
.int_signed,
.int_unsigned,
.enum_simple,
=> false,
.single_const_pointer_to_comptime_int,
.type,
.comptime_int,
.comptime_float,
.enum_literal,
.type_info,
=> true,
.var_args_param => unreachable,
.inferred_alloc_mut => unreachable,
.inferred_alloc_const => unreachable,
.bound_fn => unreachable,
.array,
.array_sentinel,
.vector,
.pointer,
.single_const_pointer,
.single_mut_pointer,
.many_const_pointer,
.many_mut_pointer,
.c_const_pointer,
.c_mut_pointer,
.const_slice,
.mut_slice,
=> return requiresComptime(childType(ty)),
.optional,
.optional_single_mut_pointer,
.optional_single_const_pointer,
=> {
var buf: Payload.ElemType = undefined;
return requiresComptime(optionalChild(ty, &buf));
},
.tuple => {
const tuple = ty.castTag(.tuple).?.data;
for (tuple.types) |field_ty| {
if (requiresComptime(field_ty)) {
return true;
}
}
return false;
},
.@"struct" => {
const struct_obj = ty.castTag(.@"struct").?.data;
switch (struct_obj.requires_comptime) {
.no, .wip => return false,
.yes => return true,
.unknown => {
struct_obj.requires_comptime = .wip;
for (struct_obj.fields.values()) |field| {
if (requiresComptime(field.ty)) {
struct_obj.requires_comptime = .yes;
return true;
}
}
struct_obj.requires_comptime = .no;
return false;
},
}
},
.@"union", .union_tagged => {
const union_obj = ty.cast(Payload.Union).?.data;
switch (union_obj.requires_comptime) {
.no, .wip => return false,
.yes => return true,
.unknown => {
union_obj.requires_comptime = .wip;
for (union_obj.fields.values()) |field| {
if (requiresComptime(field.ty)) {
union_obj.requires_comptime = .yes;
return true;
}
}
union_obj.requires_comptime = .no;
return false;
},
}
},
.error_union => return requiresComptime(errorUnionPayload(ty)),
.anyframe_T => return ty.castTag(.anyframe_T).?.data.requiresComptime(),
.enum_numbered => return ty.castTag(.enum_numbered).?.data.tag_ty.requiresComptime(),
.enum_full, .enum_nonexhaustive => return ty.cast(Payload.EnumFull).?.data.tag_ty.requiresComptime(),
};
}
pub fn toValue(self: Type, allocator: Allocator) Allocator.Error!Value {
switch (self.tag()) {
.u1 => return Value.initTag(.u1_type),
@ -1918,12 +1778,13 @@ pub const Type = extern union {
.fn_void_no_args, // represents machine code; not a pointer
.fn_naked_noreturn_no_args, // represents machine code; not a pointer
.fn_ccc_void_no_args, // represents machine code; not a pointer
.function, // represents machine code; not a pointer
=> return switch (target.cpu.arch) {
.arm, .armeb => 4,
.aarch64, .aarch64_32, .aarch64_be => 4,
.riscv64 => 2,
else => 1,
=> return target_util.defaultFunctionAlignment(target),
// represents machine code; not a pointer
.function => {
const alignment = self.castTag(.function).?.data.alignment;
if (alignment != 0) return alignment;
return target_util.defaultFunctionAlignment(target);
},
.i16, .u16 => return 2,
@ -3424,6 +3285,7 @@ pub const Type = extern union {
.comptime_params = undefined,
.return_type = initTag(.noreturn),
.cc = .Unspecified,
.alignment = 0,
.is_var_args = false,
.is_generic = false,
},
@ -3432,6 +3294,7 @@ pub const Type = extern union {
.comptime_params = undefined,
.return_type = initTag(.void),
.cc = .Unspecified,
.alignment = 0,
.is_var_args = false,
.is_generic = false,
},
@ -3440,6 +3303,7 @@ pub const Type = extern union {
.comptime_params = undefined,
.return_type = initTag(.noreturn),
.cc = .Naked,
.alignment = 0,
.is_var_args = false,
.is_generic = false,
},
@ -3448,6 +3312,7 @@ pub const Type = extern union {
.comptime_params = undefined,
.return_type = initTag(.void),
.cc = .C,
.alignment = 0,
.is_var_args = false,
.is_generic = false,
},
@ -4572,6 +4437,8 @@ pub const Type = extern union {
param_types: []Type,
comptime_params: [*]bool,
return_type: Type,
/// If zero use default target function code alignment.
alignment: u32,
cc: std.builtin.CallingConvention,
is_var_args: bool,
is_generic: bool,

View File

@ -1520,6 +1520,11 @@ pub const Value = extern union {
}
return true;
},
.function => {
const a_payload = a.castTag(.function).?.data;
const b_payload = b.castTag(.function).?.data;
return a_payload == b_payload;
},
else => {},
}
} else if (a_tag == .null_value or b_tag == .null_value) {
@ -1573,6 +1578,7 @@ pub const Value = extern union {
pub fn hash(val: Value, ty: Type, hasher: *std.hash.Wyhash) void {
const zig_ty_tag = ty.zigTypeTag();
std.hash.autoHash(hasher, zig_ty_tag);
if (val.isUndef()) return;
switch (zig_ty_tag) {
.BoundFn => unreachable, // TODO remove this from the language
@ -1694,7 +1700,8 @@ pub const Value = extern union {
union_obj.val.hash(active_field_ty, hasher);
},
.Fn => {
@panic("TODO implement hashing function values");
const func = val.castTag(.function).?.data;
return std.hash.autoHash(hasher, func.owner_decl);
},
.Frame => {
@panic("TODO implement hashing frame values");
@ -1703,7 +1710,8 @@ pub const Value = extern union {
@panic("TODO implement hashing anyframe values");
},
.EnumLiteral => {
@panic("TODO implement hashing enum literal values");
const bytes = val.castTag(.enum_literal).?.data;
hasher.update(bytes);
},
}
}

View File

@ -2,22 +2,23 @@ const builtin = @import("builtin");
test {
// Tests that pass for stage1, llvm backend, C backend, wasm backend, arm backend and x86_64 backend.
_ = @import("behavior/align.zig");
_ = @import("behavior/array.zig");
_ = @import("behavior/bool.zig");
_ = @import("behavior/bugs/655.zig");
_ = @import("behavior/bugs/679.zig");
_ = @import("behavior/bugs/1111.zig");
_ = @import("behavior/bugs/2346.zig");
_ = @import("behavior/slice_sentinel_comptime.zig");
_ = @import("behavior/bugs/679.zig");
_ = @import("behavior/bugs/6850.zig");
_ = @import("behavior/cast.zig");
_ = @import("behavior/comptime_memory.zig");
_ = @import("behavior/fn_in_struct_in_comptime.zig");
_ = @import("behavior/hasdecl.zig");
_ = @import("behavior/hasfield.zig");
_ = @import("behavior/prefetch.zig");
_ = @import("behavior/pub_enum.zig");
_ = @import("behavior/slice_sentinel_comptime.zig");
_ = @import("behavior/type.zig");
_ = @import("behavior/bugs/655.zig");
_ = @import("behavior/bool.zig");
_ = @import("behavior/align.zig");
_ = @import("behavior/array.zig");
_ = @import("behavior/cast.zig");
if (builtin.zig_backend != .stage2_arm and builtin.zig_backend != .stage2_x86_64) {
// Tests that pass for stage1, llvm backend, C backend, wasm backend.
@ -113,11 +114,7 @@ test {
_ = @import("behavior/switch.zig");
_ = @import("behavior/widening.zig");
if (builtin.zig_backend != .stage1) {
// When all comptime_memory.zig tests pass, #9646 can be closed.
// _ = @import("behavior/comptime_memory.zig");
_ = @import("behavior/slice_stage2.zig");
} else {
if (builtin.zig_backend == .stage1) {
// Tests that only pass for the stage1 backend.
_ = @import("behavior/align_stage1.zig");
if (builtin.os.tag != .wasi) {

View File

@ -181,3 +181,23 @@ test "page aligned array on stack" {
try expect(number1 == 42);
try expect(number2 == 43);
}
fn derp() align(@sizeOf(usize) * 2) i32 {
return 1234;
}
fn noop1() align(1) void {}
fn noop4() align(4) void {}
test "function alignment" {
// function alignment is a compile error on wasm32/wasm64
if (native_arch == .wasm32 or native_arch == .wasm64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
try expect(derp() == 1234);
try expect(@TypeOf(noop1) == fn () align(1) void);
try expect(@TypeOf(noop4) == fn () align(4) void);
noop1();
noop4();
}

View File

@ -3,23 +3,6 @@ const expect = std.testing.expect;
const builtin = @import("builtin");
const native_arch = builtin.target.cpu.arch;
fn derp() align(@sizeOf(usize) * 2) i32 {
return 1234;
}
fn noop1() align(1) void {}
fn noop4() align(4) void {}
test "function alignment" {
// function alignment is a compile error on wasm32/wasm64
if (native_arch == .wasm32 or native_arch == .wasm64) return error.SkipZigTest;
try expect(derp() == 1234);
try expect(@TypeOf(noop1) == fn () align(1) void);
try expect(@TypeOf(noop4) == fn () align(4) void);
noop1();
noop4();
}
test "implicitly decreasing fn alignment" {
// function alignment is a compile error on wasm32/wasm64
if (native_arch == .wasm32 or native_arch == .wasm64) return error.SkipZigTest;

View File

@ -259,6 +259,8 @@ fn fB() []const u8 {
}
test "call function pointer in struct" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
try expect(mem.eql(u8, f3(true), "a"));
try expect(mem.eql(u8, f3(false), "b"));
}
@ -276,7 +278,7 @@ fn f3(x: bool) []const u8 {
}
const FnPtrWrapper = struct {
fn_ptr: fn () []const u8,
fn_ptr: *const fn () []const u8,
};
test "const ptr from var variable" {

View File

@ -205,9 +205,11 @@ test "multiline string literal is null terminated" {
}
test "self reference through fn ptr field" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
const S = struct {
const A = struct {
f: fn (A) u8,
f: *const fn (A) u8,
};
fn foo(a: A) u8 {

View File

@ -2,7 +2,7 @@ const A = struct {
b: B,
};
const B = fn (A) void;
const B = *const fn (A) void;
test "allow these dependencies" {
var a: A = undefined;

View File

@ -1,9 +1,10 @@
const builtin = @import("builtin");
const std = @import("std");
const expect = std.testing.expect;
const State = struct {
const Self = @This();
enter: fn (previous: ?Self) void,
enter: *const fn (previous: ?Self) void,
};
fn prev(p: ?State) void {
@ -11,6 +12,8 @@ fn prev(p: ?State) void {
}
test "zig test crash" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
var global: State = undefined;
global.enter = prev;
global.enter(null);

View File

@ -47,12 +47,14 @@ fn incrementVoidPtrArray(array: ?*anyopaque, len: usize) void {
}
test "compile time int to ptr of function" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .aarch64) return error.SkipZigTest; // TODO
try foobar(FUNCTION_CONSTANT);
}
pub const FUNCTION_CONSTANT = @intToPtr(PFN_void, maxInt(usize));
pub const PFN_void = fn (*anyopaque) callconv(.C) void;
pub const PFN_void = *const fn (*anyopaque) callconv(.C) void;
fn foobar(func: PFN_void) !void {
try std.testing.expect(@ptrToInt(func) == maxInt(usize));
@ -154,7 +156,7 @@ test "implicit cast *[0]T to E![]const u8" {
var global_array: [4]u8 = undefined;
test "cast from array reference to fn" {
const f = @ptrCast(fn () callconv(.C) void, &global_array);
const f = @ptrCast(*const fn () callconv(.C) void, &global_array);
try expect(@ptrToInt(f) == @ptrToInt(&global_array));
}

View File

@ -1,8 +1,15 @@
const endian = @import("builtin").cpu.arch.endian();
const builtin = @import("builtin");
const endian = builtin.cpu.arch.endian();
const testing = @import("std").testing;
const ptr_size = @sizeOf(usize);
test "type pun signed and unsigned as single pointer" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
var x: u32 = 0;
const y = @ptrCast(*i32, &x);
@ -12,6 +19,12 @@ test "type pun signed and unsigned as single pointer" {
}
test "type pun signed and unsigned as many pointer" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
var x: u32 = 0;
const y = @ptrCast([*]i32, &x);
@ -21,6 +34,12 @@ test "type pun signed and unsigned as many pointer" {
}
test "type pun signed and unsigned as array pointer" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
var x: u32 = 0;
const y = @ptrCast(*[1]i32, &x);
@ -30,6 +49,12 @@ test "type pun signed and unsigned as array pointer" {
}
test "type pun signed and unsigned as offset many pointer" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
var x: u32 = 0;
var y = @ptrCast([*]i32, &x);
@ -40,6 +65,12 @@ test "type pun signed and unsigned as offset many pointer" {
}
test "type pun signed and unsigned as array pointer" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
var x: u32 = 0;
const y = @ptrCast([*]i32, &x) - 10;
@ -50,6 +81,12 @@ test "type pun signed and unsigned as array pointer" {
}
test "type pun value and struct" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
const StructOfU32 = extern struct { x: u32 };
var inst: StructOfU32 = .{ .x = 0 };
@ -64,6 +101,12 @@ fn bigToNativeEndian(comptime T: type, v: T) T {
return if (endian == .Big) v else @byteSwap(T, v);
}
test "type pun endianness" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
const StructOfBytes = extern struct { x: [4]u8 };
var inst: StructOfBytes = .{ .x = [4]u8{ 0, 0, 0, 0 } };
@ -155,6 +198,12 @@ fn doTypePunBitsTest(as_bits: *Bits) !void {
}
test "type pun bits" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
var v: u32 = undefined;
try doTypePunBitsTest(@ptrCast(*Bits, &v));
@ -167,6 +216,12 @@ const imports = struct {
// Make sure lazy values work on their own, before getting into more complex tests
test "basic pointer preservation" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
const lazy_address = @ptrToInt(&imports.global_u32);
try testing.expectEqual(@ptrToInt(&imports.global_u32), lazy_address);
@ -175,6 +230,12 @@ test "basic pointer preservation" {
}
test "byte copy preserves linker value" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
const ct_value = comptime blk: {
const lazy = &imports.global_u32;
var result: *u32 = undefined;
@ -193,6 +254,12 @@ test "byte copy preserves linker value" {
}
test "unordered byte copy preserves linker value" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
const ct_value = comptime blk: {
const lazy = &imports.global_u32;
var result: *u32 = undefined;
@ -212,6 +279,12 @@ test "unordered byte copy preserves linker value" {
}
test "shuffle chunks of linker value" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
const lazy_address = @ptrToInt(&imports.global_u32);
const shuffled1_rt = shuffle(lazy_address, Bits, ShuffledBits);
const unshuffled1_rt = shuffle(shuffled1_rt, ShuffledBits, Bits);
@ -225,6 +298,12 @@ test "shuffle chunks of linker value" {
}
test "dance on linker values" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
var arr: [2]usize = undefined;
arr[0] = @ptrToInt(&imports.global_u32);
@ -251,6 +330,12 @@ test "dance on linker values" {
}
test "offset array ptr by element size" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
const VirtualStruct = struct { x: u32 };
var arr: [4]VirtualStruct = .{
@ -273,6 +358,12 @@ test "offset array ptr by element size" {
}
test "offset instance by field size" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
const VirtualStruct = struct { x: u32, y: u32, z: u32, w: u32 };
var inst = VirtualStruct{ .x = 0, .y = 1, .z = 2, .w = 3 };
@ -293,6 +384,12 @@ test "offset instance by field size" {
}
test "offset field ptr by enclosing array element size" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend != .stage1) {
// TODO https://github.com/ziglang/zig/issues/9646
return error.SkipZigTest;
}
comptime {
const VirtualStruct = struct { x: u32 };
var arr: [4]VirtualStruct = .{

View File

@ -1,3 +1,4 @@
const builtin = @import("builtin");
const std = @import("std");
const expect = std.testing.expect;
const expectError = std.testing.expectError;

View File

@ -57,7 +57,7 @@ test "assign inline fn to const variable" {
inline fn inlineFn() void {}
fn outer(y: u32) fn (u32) u32 {
fn outer(y: u32) *const fn (u32) u32 {
const Y = @TypeOf(y);
const st = struct {
fn get(z: u32) u32 {
@ -68,6 +68,8 @@ fn outer(y: u32) fn (u32) u32 {
}
test "return inner function which references comptime variable of outer function" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
var func = outer(10);
try expect(func(3) == 7);
}
@ -92,6 +94,8 @@ test "discard the result of a function that returns a struct" {
}
test "inline function call that calls optional function pointer, return pointer at callsite interacts correctly with callsite return type" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
const S = struct {
field: u32,
@ -113,7 +117,7 @@ test "inline function call that calls optional function pointer, return pointer
return bar2.?();
}
var bar2: ?fn () u32 = null;
var bar2: ?*const fn () u32 = null;
fn actualFn() u32 {
return 1234;
@ -135,8 +139,10 @@ fn fnWithUnreachable() noreturn {
}
test "extern struct with stdcallcc fn pointer" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
const S = extern struct {
ptr: fn () callconv(if (builtin.target.cpu.arch == .i386) .Stdcall else .C) i32,
ptr: *const fn () callconv(if (builtin.target.cpu.arch == .i386) .Stdcall else .C) i32,
fn foo() callconv(if (builtin.target.cpu.arch == .i386) .Stdcall else .C) i32 {
return 1234;

View File

@ -1,14 +1,16 @@
const builtin = @import("builtin");
test "casting random address to function pointer" {
test "casting integer address to function pointer" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .aarch64) return error.SkipZigTest; // TODO
randomAddressToFunction();
comptime randomAddressToFunction();
addressToFunction();
comptime addressToFunction();
}
fn randomAddressToFunction() void {
fn addressToFunction() void {
var addr: usize = 0xdeadbeef;
_ = @intToPtr(fn () void, addr);
_ = @intToPtr(*const fn () void, addr);
}
test "mutate through ptr initialized with constant intToPtr value" {

View File

@ -1,8 +1,10 @@
const expect = @import("std").testing.expect;
const builtin = @import("builtin");
const std = @import("std");
const expect = std.testing.expect;
const HasFuncs = struct {
state: u32,
func_field: fn (u32) u32,
func_field: *const fn (u32) u32,
fn inc(self: *HasFuncs) void {
self.state += 1;
@ -25,6 +27,8 @@ const HasFuncs = struct {
};
test "standard field calls" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
try expect(HasFuncs.one(0) == 1);
try expect(HasFuncs.two(0) == 2);
@ -64,6 +68,8 @@ test "standard field calls" {
}
test "@field field calls" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
try expect(@field(HasFuncs, "one")(0) == 1);
try expect(@field(HasFuncs, "two")(0) == 2);

View File

@ -1,3 +1,4 @@
const builtin = @import("builtin");
const std = @import("std");
const expect = std.testing.expect;
const expectEqualSlices = std.testing.expectEqualSlices;
@ -166,3 +167,15 @@ test "slicing zero length array" {
try expect(mem.eql(u8, s1, ""));
try expect(mem.eql(u32, s2, &[_]u32{}));
}
const x = @intToPtr([*]i32, 0x1000)[0..0x500];
const y = x[0x100..];
test "compile time slice of pointer to hard coded address" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
try expect(@ptrToInt(x) == 0x1000);
try expect(x.len == 0x500);
try expect(@ptrToInt(y) == 0x1400);
try expect(y.len == 0x400);
}

View File

@ -1,12 +0,0 @@
const std = @import("std");
const expect = std.testing.expect;
const x = @intToPtr([*]i32, 0x1000)[0..0x500];
const y = x[0x100..];
test "compile time slice of pointer to hard coded address" {
try expect(@ptrToInt(x) == 0x1000);
try expect(x.len == 0x500);
try expect(@ptrToInt(y) == 0x1400);
try expect(y.len == 0x400);
}

View File

@ -1,3 +1,4 @@
const builtin = @import("builtin");
const std = @import("std");
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
@ -166,8 +167,10 @@ test "union with specified enum tag" {
}
test "packed union generates correctly aligned LLVM type" {
if (builtin.zig_backend == .stage1) return error.SkipZigTest;
const U = packed union {
f1: fn () error{TestUnexpectedResult}!void,
f1: *const fn () error{TestUnexpectedResult}!void,
f2: u32,
};
var foo = [_]U{