From b34f994c0ba2d87fce2a3409d6bcfa7a5ebe78ff Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 21 Jan 2022 00:49:58 -0700 Subject: [PATCH] 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. --- lib/std/builtin.zig | 8 +- src/AstGen.zig | 10 +- src/Module.zig | 119 +++++- src/Sema.zig | 580 ++++++++++++++++++++++++------ src/codegen/llvm.zig | 4 + src/target.zig | 9 + src/type.zig | 229 +++--------- src/value.zig | 12 +- test/behavior.zig | 21 +- test/behavior/align.zig | 20 ++ test/behavior/align_stage1.zig | 17 - test/behavior/basic.zig | 4 +- test/behavior/basic_llvm.zig | 4 +- test/behavior/bugs/1500.zig | 2 +- test/behavior/bugs/3112.zig | 5 +- test/behavior/cast_llvm.zig | 6 +- test/behavior/comptime_memory.zig | 99 ++++- test/behavior/error.zig | 1 + test/behavior/fn.zig | 12 +- test/behavior/inttoptr.zig | 12 +- test/behavior/member_func.zig | 10 +- test/behavior/slice.zig | 13 + test/behavior/slice_stage2.zig | 12 - test/behavior/union.zig | 5 +- 24 files changed, 858 insertions(+), 356 deletions(-) delete mode 100644 test/behavior/slice_stage2.zig diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index ba5801e936..3675a90257 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -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; diff --git a/src/AstGen.zig b/src/AstGen.zig index 114cd5f505..d75ae82e0a 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -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)); diff --git a/src/Module.zig b/src/Module.zig index 3892b35aea..88469a794a 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -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 { diff --git a/src/Sema.zig b/src/Sema.zig index 6a2085cf3f..7da547da3d 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -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); + }, + }; +} diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 00733dd34b..763f72dc10 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -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); diff --git a/src/target.zig b/src/target.zig index 8a95e756bf..dc29129e42 100644 --- a/src/target.zig +++ b/src/target.zig @@ -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, + }; +} diff --git a/src/type.zig b/src/type.zig index 1db6ceeb41..8f9b9d9164 100644 --- a/src/type.zig +++ b/src/type.zig @@ -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, diff --git a/src/value.zig b/src/value.zig index c043bc9364..2c177f3e93 100644 --- a/src/value.zig +++ b/src/value.zig @@ -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); }, } } diff --git a/test/behavior.zig b/test/behavior.zig index cbd4bd3c69..03e1c635f3 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -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) { diff --git a/test/behavior/align.zig b/test/behavior/align.zig index 26a914576c..19b8c902af 100644 --- a/test/behavior/align.zig +++ b/test/behavior/align.zig @@ -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(); +} diff --git a/test/behavior/align_stage1.zig b/test/behavior/align_stage1.zig index 30d08abb38..71a8c87e82 100644 --- a/test/behavior/align_stage1.zig +++ b/test/behavior/align_stage1.zig @@ -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; diff --git a/test/behavior/basic.zig b/test/behavior/basic.zig index 9064339877..3a7d95457e 100644 --- a/test/behavior/basic.zig +++ b/test/behavior/basic.zig @@ -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" { diff --git a/test/behavior/basic_llvm.zig b/test/behavior/basic_llvm.zig index 32b35bef0a..29cad01567 100644 --- a/test/behavior/basic_llvm.zig +++ b/test/behavior/basic_llvm.zig @@ -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 { diff --git a/test/behavior/bugs/1500.zig b/test/behavior/bugs/1500.zig index 5683d53721..18fd40cef2 100644 --- a/test/behavior/bugs/1500.zig +++ b/test/behavior/bugs/1500.zig @@ -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; diff --git a/test/behavior/bugs/3112.zig b/test/behavior/bugs/3112.zig index 68e86c7fcb..ea2197eef1 100644 --- a/test/behavior/bugs/3112.zig +++ b/test/behavior/bugs/3112.zig @@ -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); diff --git a/test/behavior/cast_llvm.zig b/test/behavior/cast_llvm.zig index 1b27d10d59..625d54ce3c 100644 --- a/test/behavior/cast_llvm.zig +++ b/test/behavior/cast_llvm.zig @@ -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)); } diff --git a/test/behavior/comptime_memory.zig b/test/behavior/comptime_memory.zig index 5547b9fd89..24a774aeb6 100644 --- a/test/behavior/comptime_memory.zig +++ b/test/behavior/comptime_memory.zig @@ -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 = .{ diff --git a/test/behavior/error.zig b/test/behavior/error.zig index b7d4511fe9..3cb1bcf43b 100644 --- a/test/behavior/error.zig +++ b/test/behavior/error.zig @@ -1,3 +1,4 @@ +const builtin = @import("builtin"); const std = @import("std"); const expect = std.testing.expect; const expectError = std.testing.expectError; diff --git a/test/behavior/fn.zig b/test/behavior/fn.zig index 3a1f3e0b35..8cf9fbfe48 100644 --- a/test/behavior/fn.zig +++ b/test/behavior/fn.zig @@ -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; diff --git a/test/behavior/inttoptr.zig b/test/behavior/inttoptr.zig index ec26a09699..5c1acf51cd 100644 --- a/test/behavior/inttoptr.zig +++ b/test/behavior/inttoptr.zig @@ -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" { diff --git a/test/behavior/member_func.zig b/test/behavior/member_func.zig index 092a691901..3e4895e729 100644 --- a/test/behavior/member_func.zig +++ b/test/behavior/member_func.zig @@ -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); diff --git a/test/behavior/slice.zig b/test/behavior/slice.zig index 0332cff802..01ae10ee4e 100644 --- a/test/behavior/slice.zig +++ b/test/behavior/slice.zig @@ -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); +} diff --git a/test/behavior/slice_stage2.zig b/test/behavior/slice_stage2.zig deleted file mode 100644 index 360527e8ba..0000000000 --- a/test/behavior/slice_stage2.zig +++ /dev/null @@ -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); -} diff --git a/test/behavior/union.zig b/test/behavior/union.zig index 12190e418c..3136646df5 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -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{