From b9f521b40217835c72bcb91debf3904d5b9df6e7 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 10 Mar 2022 12:02:31 +0200 Subject: [PATCH 1/4] Sema: add coercion from [:x]T to [*:x]T --- src/Sema.zig | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 11f250e44c..517b04bc4b 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -16033,7 +16033,6 @@ fn coerce( }, .Pointer => p: { const inst_info = inst_ty.ptrInfo().data; - if (inst_info.size == .Slice) break :p; switch (try sema.coerceInMemoryAllowed( block, dest_info.pointee_type, @@ -16046,6 +16045,14 @@ fn coerce( .ok => {}, .no_match => break :p, } + if (inst_info.size == .Slice) { + if (dest_info.sentinel == null or inst_info.sentinel == null or + !dest_info.sentinel.?.eql(inst_info.sentinel.?, dest_info.pointee_type)) + break :p; + + const slice_ptr = try sema.analyzeSlicePtr(block, inst_src, inst, inst_ty); + return sema.coerceCompatiblePtrs(block, dest_ty, slice_ptr, inst_src); + } return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src); }, else => {}, @@ -16089,7 +16096,30 @@ fn coerce( return sema.coerceTupleToSlicePtrs(block, dest_ty, dest_ty_src, inst, inst_src); } }, - .Many => {}, + .Many => p: { + const inst_info = inst_ty.ptrInfo().data; + if (inst_info.size != .Slice) break :p; + + switch (try sema.coerceInMemoryAllowed( + block, + dest_info.pointee_type, + inst_info.pointee_type, + dest_info.mutable, + target, + dest_ty_src, + inst_src, + )) { + .ok => {}, + .no_match => break :p, + } + + if (dest_info.sentinel == null or inst_info.sentinel == null or + !dest_info.sentinel.?.eql(inst_info.sentinel.?, dest_info.pointee_type)) + break :p; + + const slice_ptr = try sema.analyzeSlicePtr(block, inst_src, inst, inst_ty); + return sema.coerceCompatiblePtrs(block, dest_ty, slice_ptr, inst_src); + }, } // This will give an extra hint on top of what the bottom of this func would provide. From e0fb0770d1012e1a5afba947ba96dc53fe2ca45c Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 10 Mar 2022 13:04:55 +0200 Subject: [PATCH 2/4] Sema: if generic function evaluates to another generic function call it inline ```zig fn foo(a: anytype, b: @TypeOf(a)) void { _ = b; } test { // foo evaluates to `fn (type) void` and must be called inline foo(u32, u32); } ``` --- src/Sema.zig | 564 ++++++++++++++++++------------------ test/behavior.zig | 2 +- test/behavior/bugs/3779.zig | 7 +- 3 files changed, 293 insertions(+), 280 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 517b04bc4b..2b0ec08171 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -4474,8 +4474,26 @@ fn analyzeCall( const is_comptime_call = block.is_comptime or modifier == .compile_time or try sema.typeRequiresComptime(block, func_src, func_ty_info.return_type); - const is_inline_call = is_comptime_call or modifier == .always_inline or + var is_inline_call = is_comptime_call or modifier == .always_inline or func_ty_info.cc == .Inline; + + if (!is_inline_call and func_ty_info.is_generic) { + if (sema.instantiateGenericCall( + block, + func, + func_src, + call_src, + func_ty_info, + ensure_result_used, + uncasted_args, + )) |some| { + return some; + } else |err| switch (err) { + error.GenericPoison => is_inline_call = true, + else => |e| return e, + } + } + const result: Air.Inst.Ref = if (is_inline_call) res: { const func_val = try sema.resolveConstValue(block, func_src, func); const module_fn = switch (func_val.tag()) { @@ -4728,278 +4746,8 @@ fn analyzeCall( try wip_captures.finalize(); break :res res2; - } else if (func_ty_info.is_generic) res: { - const func_val = try sema.resolveConstValue(block, func_src, func); - const module_fn = switch (func_val.tag()) { - .function => func_val.castTag(.function).?.data, - .decl_ref => func_val.castTag(.decl_ref).?.data.val.castTag(.function).?.data, - else => unreachable, - }; - // Check the Module's generic function map with an adapted context, so that we - // can match against `uncasted_args` rather than doing the work below to create a - // generic Scope only to junk it if it matches an existing instantiation. - const namespace = module_fn.owner_decl.src_namespace; - const fn_zir = namespace.file_scope.zir; - const fn_info = fn_zir.getFnInfo(module_fn.zir_body_inst); - const zir_tags = fn_zir.instructions.items(.tag); - - // This hash must match `Module.MonomorphedFuncsContext.hash`. - // For parameters explicitly marked comptime and simple parameter type expressions, - // we know whether a parameter is elided from a monomorphed function, and can - // use it in the hash here. However, for parameter type expressions that are not - // explicitly marked comptime and rely on previous parameter comptime values, we - // don't find out until after generating a monomorphed function whether the parameter - // type ended up being a "must-be-comptime-known" type. - var hasher = std.hash.Wyhash.init(0); - std.hash.autoHash(&hasher, @ptrToInt(module_fn)); - - const comptime_tvs = try sema.arena.alloc(TypedValue, func_ty_info.param_types.len); - - for (func_ty_info.param_types) |param_ty, i| { - const is_comptime = func_ty_info.paramIsComptime(i); - if (is_comptime) { - const arg_src = call_src; // TODO better source location - const casted_arg = try sema.coerce(block, param_ty, uncasted_args[i], arg_src); - if (try sema.resolveMaybeUndefVal(block, arg_src, casted_arg)) |arg_val| { - if (param_ty.tag() != .generic_poison) { - arg_val.hash(param_ty, &hasher); - } - comptime_tvs[i] = .{ - // This will be different than `param_ty` in the case of `generic_poison`. - .ty = sema.typeOf(casted_arg), - .val = arg_val, - }; - } else { - return sema.failWithNeededComptime(block, arg_src); - } - } else { - comptime_tvs[i] = .{ - .ty = sema.typeOf(uncasted_args[i]), - .val = Value.initTag(.generic_poison), - }; - } - } - - const precomputed_hash = hasher.final(); - - const adapter: GenericCallAdapter = .{ - .generic_fn = module_fn, - .precomputed_hash = precomputed_hash, - .func_ty_info = func_ty_info, - .comptime_tvs = comptime_tvs, - }; - const gop = try mod.monomorphed_funcs.getOrPutAdapted(gpa, {}, adapter); - if (gop.found_existing) { - const callee_func = gop.key_ptr.*; - break :res try sema.finishGenericCall( - block, - call_src, - callee_func, - func_src, - uncasted_args, - fn_info, - zir_tags, - ); - } - const new_module_func = try gpa.create(Module.Fn); - gop.key_ptr.* = new_module_func; - { - errdefer gpa.destroy(new_module_func); - const remove_adapter: GenericRemoveAdapter = .{ - .precomputed_hash = precomputed_hash, - }; - errdefer assert(mod.monomorphed_funcs.removeAdapted(new_module_func, remove_adapter)); - - try namespace.anon_decls.ensureUnusedCapacity(gpa, 1); - - // Create a Decl for the new function. - const src_decl = namespace.getDecl(); - // TODO better names for generic function instantiations - const name_index = mod.getNextAnonNameIndex(); - const decl_name = try std.fmt.allocPrintZ(gpa, "{s}__anon_{d}", .{ - module_fn.owner_decl.name, name_index, - }); - const new_decl = try mod.allocateNewDecl(decl_name, namespace, module_fn.owner_decl.src_node, src_decl.src_scope); - new_decl.src_line = module_fn.owner_decl.src_line; - new_decl.is_pub = module_fn.owner_decl.is_pub; - new_decl.is_exported = module_fn.owner_decl.is_exported; - new_decl.has_align = module_fn.owner_decl.has_align; - new_decl.has_linksection_or_addrspace = module_fn.owner_decl.has_linksection_or_addrspace; - new_decl.@"addrspace" = module_fn.owner_decl.@"addrspace"; - new_decl.zir_decl_index = module_fn.owner_decl.zir_decl_index; - new_decl.alive = true; // This Decl is called at runtime. - new_decl.has_tv = true; - new_decl.owns_tv = true; - new_decl.analysis = .in_progress; - new_decl.generation = mod.generation; - - namespace.anon_decls.putAssumeCapacityNoClobber(new_decl, {}); - - // The generic function Decl is guaranteed to be the first dependency - // of each of its instantiations. - assert(new_decl.dependencies.keys().len == 0); - try mod.declareDeclDependency(new_decl, module_fn.owner_decl); - - var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); - errdefer new_decl_arena.deinit(); - const new_decl_arena_allocator = new_decl_arena.allocator(); - - // Re-run the block that creates the function, with the comptime parameters - // pre-populated inside `inst_map`. This causes `param_comptime` and - // `param_anytype_comptime` ZIR instructions to be ignored, resulting in a - // new, monomorphized function, with the comptime parameters elided. - var child_sema: Sema = .{ - .mod = mod, - .gpa = gpa, - .arena = sema.arena, - .perm_arena = new_decl_arena_allocator, - .code = fn_zir, - .owner_decl = new_decl, - .func = null, - .fn_ret_ty = Type.void, - .owner_func = null, - .comptime_args = try new_decl_arena_allocator.alloc(TypedValue, uncasted_args.len), - .comptime_args_fn_inst = module_fn.zir_body_inst, - .preallocated_new_func = new_module_func, - }; - defer child_sema.deinit(); - - var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, new_decl.src_scope); - defer wip_captures.deinit(); - - var child_block: Block = .{ - .parent = null, - .sema = &child_sema, - .src_decl = new_decl, - .namespace = namespace, - .wip_capture_scope = wip_captures.scope, - .instructions = .{}, - .inlining = null, - .is_comptime = true, - }; - defer { - child_block.instructions.deinit(gpa); - child_block.params.deinit(gpa); - } - - try child_sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, uncasted_args.len)); - var arg_i: usize = 0; - for (fn_info.param_body) |inst| { - var is_comptime = false; - var is_anytype = false; - switch (zir_tags[inst]) { - .param => { - is_comptime = func_ty_info.paramIsComptime(arg_i); - }, - .param_comptime => { - is_comptime = true; - }, - .param_anytype => { - is_anytype = true; - is_comptime = func_ty_info.paramIsComptime(arg_i); - }, - .param_anytype_comptime => { - is_anytype = true; - is_comptime = true; - }, - else => continue, - } - const arg_src = call_src; // TODO: better source location - const arg = uncasted_args[arg_i]; - if (is_comptime) { - if (try sema.resolveMaybeUndefVal(block, arg_src, arg)) |arg_val| { - const child_arg = try child_sema.addConstant(sema.typeOf(arg), arg_val); - child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); - } else { - return sema.failWithNeededComptime(block, arg_src); - } - } else if (is_anytype) { - const arg_ty = sema.typeOf(arg); - 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); - } else { - // We insert into the map an instruction which is runtime-known - // but has the type of the argument. - const child_arg = try child_block.addArg(arg_ty); - child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); - } - } - arg_i += 1; - } - const new_func_inst = child_sema.resolveBody(&child_block, fn_info.param_body, fn_info.param_body_inst) catch |err| { - // TODO look up the compile error that happened here and attach a note to it - // pointing here, at the generic instantiation callsite. - if (sema.owner_func) |owner_func| { - owner_func.state = .dependency_failure; - } else { - sema.owner_decl.analysis = .dependency_failure; - } - return err; - }; - const new_func_val = child_sema.resolveConstValue(&child_block, .unneeded, new_func_inst) catch unreachable; - const new_func = new_func_val.castTag(.function).?.data; - assert(new_func == new_module_func); - - arg_i = 0; - for (fn_info.param_body) |inst| { - switch (zir_tags[inst]) { - .param_comptime, .param_anytype_comptime, .param, .param_anytype => {}, - else => continue, - } - const arg = child_sema.inst_map.get(inst).?; - const copied_arg_ty = try child_sema.typeOf(arg).copy(new_decl_arena_allocator); - if (child_sema.resolveMaybeUndefValAllowVariables( - &child_block, - .unneeded, - arg, - ) catch unreachable) |arg_val| { - child_sema.comptime_args[arg_i] = .{ - .ty = copied_arg_ty, - .val = try arg_val.copy(new_decl_arena_allocator), - }; - } else { - child_sema.comptime_args[arg_i] = .{ - .ty = copied_arg_ty, - .val = Value.initTag(.generic_poison), - }; - } - - arg_i += 1; - } - - try wip_captures.finalize(); - - // Populate the Decl ty/val with the function and its type. - new_decl.ty = try child_sema.typeOf(new_func_inst).copy(new_decl_arena_allocator); - new_decl.val = try Value.Tag.function.create(new_decl_arena_allocator, new_func); - new_decl.analysis = .complete; - - log.debug("generic function '{s}' instantiated with type {}", .{ - new_decl.name, new_decl.ty, - }); - assert(!new_decl.ty.fnInfo().is_generic); - - // Queue up a `codegen_func` work item for the new Fn. The `comptime_args` field - // will be populated, ensuring it will have `analyzeBody` called with the ZIR - // parameters mapped appropriately. - try mod.comp.bin_file.allocateDeclIndexes(new_decl); - try mod.comp.work_queue.writeItem(.{ .codegen_func = new_func }); - - try new_decl.finalizeNewArena(&new_decl_arena); - } - - break :res try sema.finishGenericCall( - block, - call_src, - new_module_func, - func_src, - uncasted_args, - fn_info, - zir_tags, - ); } else res: { + assert(!func_ty_info.is_generic); try sema.requireRuntimeBlock(block, call_src); const args = try sema.arena.alloc(Air.Inst.Ref, uncasted_args.len); @@ -5037,16 +4785,274 @@ fn analyzeCall( return result; } -fn finishGenericCall( +fn instantiateGenericCall( sema: *Sema, block: *Block, - call_src: LazySrcLoc, - callee: *Module.Fn, + func: Air.Inst.Ref, func_src: LazySrcLoc, + call_src: LazySrcLoc, + func_ty_info: Type.Payload.Function.Data, + ensure_result_used: bool, uncasted_args: []const Air.Inst.Ref, - fn_info: Zir.FnInfo, - zir_tags: []const Zir.Inst.Tag, ) CompileError!Air.Inst.Ref { + const mod = sema.mod; + const gpa = sema.gpa; + + const func_val = try sema.resolveConstValue(block, func_src, func); + const module_fn = switch (func_val.tag()) { + .function => func_val.castTag(.function).?.data, + .decl_ref => func_val.castTag(.decl_ref).?.data.val.castTag(.function).?.data, + else => unreachable, + }; + // Check the Module's generic function map with an adapted context, so that we + // can match against `uncasted_args` rather than doing the work below to create a + // generic Scope only to junk it if it matches an existing instantiation. + const namespace = module_fn.owner_decl.src_namespace; + const fn_zir = namespace.file_scope.zir; + const fn_info = fn_zir.getFnInfo(module_fn.zir_body_inst); + const zir_tags = fn_zir.instructions.items(.tag); + + // This hash must match `Module.MonomorphedFuncsContext.hash`. + // For parameters explicitly marked comptime and simple parameter type expressions, + // we know whether a parameter is elided from a monomorphed function, and can + // use it in the hash here. However, for parameter type expressions that are not + // explicitly marked comptime and rely on previous parameter comptime values, we + // don't find out until after generating a monomorphed function whether the parameter + // type ended up being a "must-be-comptime-known" type. + var hasher = std.hash.Wyhash.init(0); + std.hash.autoHash(&hasher, @ptrToInt(module_fn)); + + const comptime_tvs = try sema.arena.alloc(TypedValue, func_ty_info.param_types.len); + + for (func_ty_info.param_types) |param_ty, i| { + const is_comptime = func_ty_info.paramIsComptime(i); + if (is_comptime) { + const arg_src = call_src; // TODO better source location + const casted_arg = try sema.coerce(block, param_ty, uncasted_args[i], arg_src); + if (try sema.resolveMaybeUndefVal(block, arg_src, casted_arg)) |arg_val| { + if (param_ty.tag() != .generic_poison) { + arg_val.hash(param_ty, &hasher); + } + comptime_tvs[i] = .{ + // This will be different than `param_ty` in the case of `generic_poison`. + .ty = sema.typeOf(casted_arg), + .val = arg_val, + }; + } else { + return sema.failWithNeededComptime(block, arg_src); + } + } else { + comptime_tvs[i] = .{ + .ty = sema.typeOf(uncasted_args[i]), + .val = Value.initTag(.generic_poison), + }; + } + } + + const precomputed_hash = hasher.final(); + + const adapter: GenericCallAdapter = .{ + .generic_fn = module_fn, + .precomputed_hash = precomputed_hash, + .func_ty_info = func_ty_info, + .comptime_tvs = comptime_tvs, + }; + const gop = try mod.monomorphed_funcs.getOrPutAdapted(gpa, {}, adapter); + if (!gop.found_existing) { + const new_module_func = try gpa.create(Module.Fn); + gop.key_ptr.* = new_module_func; + errdefer gpa.destroy(new_module_func); + const remove_adapter: GenericRemoveAdapter = .{ + .precomputed_hash = precomputed_hash, + }; + errdefer assert(mod.monomorphed_funcs.removeAdapted(new_module_func, remove_adapter)); + + try namespace.anon_decls.ensureUnusedCapacity(gpa, 1); + + // Create a Decl for the new function. + const src_decl = namespace.getDecl(); + // TODO better names for generic function instantiations + const name_index = mod.getNextAnonNameIndex(); + const decl_name = try std.fmt.allocPrintZ(gpa, "{s}__anon_{d}", .{ + module_fn.owner_decl.name, name_index, + }); + const new_decl = try mod.allocateNewDecl(decl_name, namespace, module_fn.owner_decl.src_node, src_decl.src_scope); + errdefer new_decl.destroy(mod); + new_decl.src_line = module_fn.owner_decl.src_line; + new_decl.is_pub = module_fn.owner_decl.is_pub; + new_decl.is_exported = module_fn.owner_decl.is_exported; + new_decl.has_align = module_fn.owner_decl.has_align; + new_decl.has_linksection_or_addrspace = module_fn.owner_decl.has_linksection_or_addrspace; + new_decl.@"addrspace" = module_fn.owner_decl.@"addrspace"; + new_decl.zir_decl_index = module_fn.owner_decl.zir_decl_index; + new_decl.alive = true; // This Decl is called at runtime. + new_decl.analysis = .in_progress; + new_decl.generation = mod.generation; + + namespace.anon_decls.putAssumeCapacityNoClobber(new_decl, {}); + errdefer assert(namespace.anon_decls.orderedRemove(new_decl)); + + // The generic function Decl is guaranteed to be the first dependency + // of each of its instantiations. + assert(new_decl.dependencies.keys().len == 0); + try mod.declareDeclDependency(new_decl, module_fn.owner_decl); + errdefer assert(module_fn.owner_decl.dependants.orderedRemove(new_decl)); + + var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); + errdefer new_decl_arena.deinit(); + const new_decl_arena_allocator = new_decl_arena.allocator(); + + // Re-run the block that creates the function, with the comptime parameters + // pre-populated inside `inst_map`. This causes `param_comptime` and + // `param_anytype_comptime` ZIR instructions to be ignored, resulting in a + // new, monomorphized function, with the comptime parameters elided. + var child_sema: Sema = .{ + .mod = mod, + .gpa = gpa, + .arena = sema.arena, + .perm_arena = new_decl_arena_allocator, + .code = fn_zir, + .owner_decl = new_decl, + .func = null, + .fn_ret_ty = Type.void, + .owner_func = null, + .comptime_args = try new_decl_arena_allocator.alloc(TypedValue, uncasted_args.len), + .comptime_args_fn_inst = module_fn.zir_body_inst, + .preallocated_new_func = new_module_func, + }; + defer child_sema.deinit(); + + var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, new_decl.src_scope); + defer wip_captures.deinit(); + + var child_block: Block = .{ + .parent = null, + .sema = &child_sema, + .src_decl = new_decl, + .namespace = namespace, + .wip_capture_scope = wip_captures.scope, + .instructions = .{}, + .inlining = null, + .is_comptime = true, + }; + defer { + child_block.instructions.deinit(gpa); + child_block.params.deinit(gpa); + } + + try child_sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, uncasted_args.len)); + var arg_i: usize = 0; + for (fn_info.param_body) |inst| { + var is_comptime = false; + var is_anytype = false; + switch (zir_tags[inst]) { + .param => { + is_comptime = func_ty_info.paramIsComptime(arg_i); + }, + .param_comptime => { + is_comptime = true; + }, + .param_anytype => { + is_anytype = true; + is_comptime = func_ty_info.paramIsComptime(arg_i); + }, + .param_anytype_comptime => { + is_anytype = true; + is_comptime = true; + }, + else => continue, + } + const arg_src = call_src; // TODO: better source location + const arg = uncasted_args[arg_i]; + if (is_comptime) { + if (try sema.resolveMaybeUndefVal(block, arg_src, arg)) |arg_val| { + const child_arg = try child_sema.addConstant(sema.typeOf(arg), arg_val); + child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); + } else { + return sema.failWithNeededComptime(block, arg_src); + } + } else if (is_anytype) { + const arg_ty = sema.typeOf(arg); + 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); + } else { + // We insert into the map an instruction which is runtime-known + // but has the type of the argument. + const child_arg = try child_block.addArg(arg_ty); + child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); + } + } + arg_i += 1; + } + const new_func_inst = child_sema.resolveBody(&child_block, fn_info.param_body, fn_info.param_body_inst) catch |err| { + // TODO look up the compile error that happened here and attach a note to it + // pointing here, at the generic instantiation callsite. + if (sema.owner_func) |owner_func| { + owner_func.state = .dependency_failure; + } else { + sema.owner_decl.analysis = .dependency_failure; + } + return err; + }; + const new_func_val = child_sema.resolveConstValue(&child_block, .unneeded, new_func_inst) catch unreachable; + const new_func = new_func_val.castTag(.function).?.data; + assert(new_func == new_module_func); + + arg_i = 0; + for (fn_info.param_body) |inst| { + switch (zir_tags[inst]) { + .param_comptime, .param_anytype_comptime, .param, .param_anytype => {}, + else => continue, + } + const arg = child_sema.inst_map.get(inst).?; + const copied_arg_ty = try child_sema.typeOf(arg).copy(new_decl_arena_allocator); + if (child_sema.resolveMaybeUndefValAllowVariables( + &child_block, + .unneeded, + arg, + ) catch unreachable) |arg_val| { + child_sema.comptime_args[arg_i] = .{ + .ty = copied_arg_ty, + .val = try arg_val.copy(new_decl_arena_allocator), + }; + } else { + child_sema.comptime_args[arg_i] = .{ + .ty = copied_arg_ty, + .val = Value.initTag(.generic_poison), + }; + } + + arg_i += 1; + } + + try wip_captures.finalize(); + + // Populate the Decl ty/val with the function and its type. + new_decl.ty = try child_sema.typeOf(new_func_inst).copy(new_decl_arena_allocator); + // If the call evaluated to a generic type return errror and call inline. + if (new_decl.ty.fnInfo().is_generic) return error.GenericPoison; + + new_decl.val = try Value.Tag.function.create(new_decl_arena_allocator, new_func); + new_decl.has_tv = true; + new_decl.owns_tv = true; + new_decl.analysis = .complete; + + log.debug("generic function '{s}' instantiated with type {}", .{ + new_decl.name, new_decl.ty, + }); + + // Queue up a `codegen_func` work item for the new Fn. The `comptime_args` field + // will be populated, ensuring it will have `analyzeBody` called with the ZIR + // parameters mapped appropriately. + try mod.comp.bin_file.allocateDeclIndexes(new_decl); + try mod.comp.work_queue.writeItem(.{ .codegen_func = new_func }); + + try new_decl.finalizeNewArena(&new_decl_arena); + } + + const callee = gop.key_ptr.*; const callee_inst = try sema.analyzeDeclVal(block, func_src, callee.owner_decl); // Make a runtime call to the new function, making sure to omit the comptime args. @@ -5106,6 +5112,10 @@ fn finishGenericCall( } }, }); sema.appendRefsAssumeCapacity(runtime_args); + + if (ensure_result_used) { + try sema.ensureResultUsed(block, func_inst, call_src); + } return func_inst; } diff --git a/test/behavior.zig b/test/behavior.zig index 44187b3673..32bb6382fa 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -147,6 +147,7 @@ test { _ = @import("behavior/saturating_arithmetic.zig"); _ = @import("behavior/widening.zig"); _ = @import("behavior/bugs/2114.zig"); + _ = @import("behavior/bugs/3779.zig"); _ = @import("behavior/union_with_members.zig"); if (builtin.zig_backend == .stage1) { @@ -160,7 +161,6 @@ test { _ = @import("behavior/bugs/920.zig"); _ = @import("behavior/bugs/1120.zig"); _ = @import("behavior/bugs/1851.zig"); - _ = @import("behavior/bugs/3779.zig"); _ = @import("behavior/bugs/6456.zig"); _ = @import("behavior/bugs/6781.zig"); _ = @import("behavior/bugs/7027.zig"); diff --git a/test/behavior/bugs/3779.zig b/test/behavior/bugs/3779.zig index 7f0be4dcf8..d33b155c2a 100644 --- a/test/behavior/bugs/3779.zig +++ b/test/behavior/bugs/3779.zig @@ -1,11 +1,13 @@ const std = @import("std"); +const builtin = @import("builtin"); const TestEnum = enum { TestEnumValue }; const tag_name = @tagName(TestEnum.TestEnumValue); const ptr_tag_name: [*:0]const u8 = tag_name; test "@tagName() returns a string literal" { - try std.testing.expectEqual([:0]const u8, @TypeOf(tag_name)); + if (builtin.zig_backend == .stage1) return error.SkipZigTest; // stage1 gets the type wrong + try std.testing.expectEqual(*const [13:0]u8, @TypeOf(tag_name)); try std.testing.expectEqualStrings("TestEnumValue", tag_name); try std.testing.expectEqualStrings("TestEnumValue", ptr_tag_name[0..tag_name.len]); } @@ -15,7 +17,8 @@ const error_name = @errorName(TestError.TestErrorCode); const ptr_error_name: [*:0]const u8 = error_name; test "@errorName() returns a string literal" { - try std.testing.expectEqual([:0]const u8, @TypeOf(error_name)); + if (builtin.zig_backend == .stage1) return error.SkipZigTest; // stage1 gets the type wrong + try std.testing.expectEqual(*const [13:0]u8, @TypeOf(error_name)); try std.testing.expectEqualStrings("TestErrorCode", error_name); try std.testing.expectEqualStrings("TestErrorCode", ptr_error_name[0..error_name.len]); } From 673fafc23142b0bdc467efcb8554cd4e70bba17b Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 10 Mar 2022 19:52:03 +0200 Subject: [PATCH 3/4] stage2 llvm: implement lowerParentPtr for int_{u,i}64 --- src/codegen/llvm.zig | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index aacb3af47d..44c7e75629 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1472,12 +1472,12 @@ pub const DeclGen = struct { return llvm_int.constIntToPtr(try dg.llvmType(tv.ty)); }, .field_ptr, .opt_payload_ptr, .eu_payload_ptr => { - const parent = try dg.lowerParentPtr(tv.val); + const parent = try dg.lowerParentPtr(tv.val, tv.ty); return parent.llvm_ptr.constBitCast(try dg.llvmType(tv.ty)); }, .elem_ptr => { const elem_ptr = tv.val.castTag(.elem_ptr).?.data; - const parent = try dg.lowerParentPtr(elem_ptr.array_ptr); + const parent = try dg.lowerParentPtr(elem_ptr.array_ptr, tv.ty); const llvm_usize = try dg.llvmType(Type.usize); if (parent.llvm_ptr.typeOf().getElementType().getTypeKind() == .Array) { const indices: [2]*const llvm.Value = .{ @@ -2683,7 +2683,7 @@ pub const DeclGen = struct { }; } - fn lowerParentPtr(dg: *DeclGen, ptr_val: Value) Error!ParentPtr { + fn lowerParentPtr(dg: *DeclGen, ptr_val: Value, base_ty: Type) Error!ParentPtr { switch (ptr_val.tag()) { .decl_ref_mut => { const decl = ptr_val.castTag(.decl_ref_mut).?.data.decl; @@ -2697,9 +2697,27 @@ pub const DeclGen = struct { const decl = ptr_val.castTag(.variable).?.data.owner_decl; return dg.lowerParentPtrDecl(ptr_val, decl); }, + .int_i64 => { + const int = ptr_val.castTag(.int_i64).?.data; + const llvm_usize = try dg.llvmType(Type.usize); + const llvm_int = llvm_usize.constInt(@bitCast(u64, int), .False); + return ParentPtr{ + .llvm_ptr = llvm_int.constIntToPtr(try dg.llvmType(base_ty)), + .ty = base_ty, + }; + }, + .int_u64 => { + const int = ptr_val.castTag(.int_u64).?.data; + const llvm_usize = try dg.llvmType(Type.usize); + const llvm_int = llvm_usize.constInt(int, .False); + return ParentPtr{ + .llvm_ptr = llvm_int.constIntToPtr(try dg.llvmType(base_ty)), + .ty = base_ty, + }; + }, .field_ptr => { const field_ptr = ptr_val.castTag(.field_ptr).?.data; - const parent = try dg.lowerParentPtr(field_ptr.container_ptr); + const parent = try dg.lowerParentPtr(field_ptr.container_ptr, base_ty); const field_index = @intCast(u32, field_ptr.field_index); const llvm_u32 = dg.context.intType(32); const target = dg.module.getTarget(); @@ -2753,7 +2771,7 @@ pub const DeclGen = struct { }, .elem_ptr => { const elem_ptr = ptr_val.castTag(.elem_ptr).?.data; - const parent = try dg.lowerParentPtr(elem_ptr.array_ptr); + const parent = try dg.lowerParentPtr(elem_ptr.array_ptr, base_ty); const llvm_usize = try dg.llvmType(Type.usize); const indices: [2]*const llvm.Value = .{ llvm_usize.constInt(0, .False), @@ -2766,7 +2784,7 @@ pub const DeclGen = struct { }, .opt_payload_ptr => { const opt_payload_ptr = ptr_val.castTag(.opt_payload_ptr).?.data; - const parent = try dg.lowerParentPtr(opt_payload_ptr); + const parent = try dg.lowerParentPtr(opt_payload_ptr, base_ty); var buf: Type.Payload.ElemType = undefined; const payload_ty = parent.ty.optionalChild(&buf); if (!payload_ty.hasRuntimeBits() or parent.ty.isPtrLikeOptional()) { @@ -2790,7 +2808,7 @@ pub const DeclGen = struct { }, .eu_payload_ptr => { const eu_payload_ptr = ptr_val.castTag(.eu_payload_ptr).?.data; - const parent = try dg.lowerParentPtr(eu_payload_ptr); + const parent = try dg.lowerParentPtr(eu_payload_ptr, base_ty); const payload_ty = parent.ty.errorUnionPayload(); if (!payload_ty.hasRuntimeBits()) { // In this case, we represent pointer to error union the same as pointer From f9e4344bb51c70aa9959680f3dfc9d7712858395 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 10 Mar 2022 20:00:07 +0200 Subject: [PATCH 4/4] Sema: implement zirStructInit is_ref=true union --- src/Sema.zig | 19 +++++++++++++------ test/behavior.zig | 6 +++--- test/behavior/export.zig | 3 --- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 2b0ec08171..195a0ef274 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -3995,7 +3995,7 @@ pub fn analyzeExport( try mod.ensureDeclAnalyzed(exported_decl); // TODO run the same checks as we do for C ABI struct fields switch (exported_decl.ty.zigTypeTag()) { - .Fn, .Int, .Struct, .Array, .Float => {}, + .Fn, .Int, .Enum, .Struct, .Union, .Array, .Float => {}, else => return sema.fail(block, src, "unable to export type '{}'", .{exported_decl.ty}), } @@ -11674,18 +11674,25 @@ fn zirStructInit( const field_name = sema.code.nullTerminatedString(field_type_extra.name_start); const field_index = try sema.unionFieldIndex(block, resolved_ty, field_name, field_src); - if (is_ref) { - return sema.fail(block, src, "TODO: Sema.zirStructInit is_ref=true union", .{}); - } - const init_inst = sema.resolveInst(item.data.init); if (try sema.resolveMaybeUndefVal(block, field_src, init_inst)) |val| { const tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); - return sema.addConstant( + return sema.addConstantMaybeRef( + block, + src, resolved_ty, try Value.Tag.@"union".create(sema.arena, .{ .tag = tag_val, .val = val }), + is_ref, ); } + + if (is_ref) { + const alloc = try block.addTy(.alloc, resolved_ty); + const field_ptr = try sema.unionFieldPtr(block, field_src, alloc, field_name, field_src, resolved_ty); + try sema.storePtr(block, src, field_ptr, init_inst); + return alloc; + } + return sema.fail(block, src, "TODO: Sema.zirStructInit for runtime-known union values", .{}); } unreachable; diff --git a/test/behavior.zig b/test/behavior.zig index 32bb6382fa..871bd1c697 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -57,6 +57,7 @@ test { _ = @import("behavior/bugs/5487.zig"); _ = @import("behavior/bugs/6850.zig"); _ = @import("behavior/bugs/7003.zig"); + _ = @import("behavior/bugs/7047.zig"); _ = @import("behavior/bugs/7250.zig"); _ = @import("behavior/bugs/11100.zig"); _ = @import("behavior/bugs/10970.zig"); @@ -142,12 +143,14 @@ test { if (builtin.zig_backend != .stage2_c) { // Tests that pass for stage1 and the llvm backend. _ = @import("behavior/atomics.zig"); + _ = @import("behavior/export.zig"); _ = @import("behavior/maximum_minimum.zig"); _ = @import("behavior/popcount.zig"); _ = @import("behavior/saturating_arithmetic.zig"); _ = @import("behavior/widening.zig"); _ = @import("behavior/bugs/2114.zig"); _ = @import("behavior/bugs/3779.zig"); + _ = @import("behavior/bugs/10147.zig"); _ = @import("behavior/union_with_members.zig"); if (builtin.zig_backend == .stage1) { @@ -164,10 +167,7 @@ test { _ = @import("behavior/bugs/6456.zig"); _ = @import("behavior/bugs/6781.zig"); _ = @import("behavior/bugs/7027.zig"); - _ = @import("behavior/bugs/7047.zig"); - _ = @import("behavior/bugs/10147.zig"); _ = @import("behavior/const_slice_child.zig"); - _ = @import("behavior/export.zig"); _ = @import("behavior/select.zig"); _ = @import("behavior/shuffle.zig"); _ = @import("behavior/struct_contains_slice_of_itself.zig"); diff --git a/test/behavior/export.zig b/test/behavior/export.zig index 9521c7cadb..5f8ccf02d4 100644 --- a/test/behavior/export.zig +++ b/test/behavior/export.zig @@ -38,9 +38,6 @@ export fn testPackedStuff(a: *const PackedStruct, b: *const PackedUnion) void { test "exporting enum type and value" { const S = struct { const E = enum(c_int) { one, two }; - comptime { - @export(E, .{ .name = "E" }); - } const e: E = .two; comptime { @export(e, .{ .name = "e" });