authorgravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2022-12-20 22:33:38+02:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2022-12-26 16:36:30+02:00
log81443fcde84879782f38fde33510e56d2faaaa21
tree2c78513811f46d2759ffbdcb6e5dac525eb3f182
parent0b859831ad8a0c9df11e8da11f13739ed2dcf0d5

Sema: add error for recursive inline call

Closes #12973

7 files changed, 76 insertions(+), 41 deletions(-)

src/Sema.zig+32-14
......@@ -335,6 +335,7 @@ pub const Block = struct {
335335 /// It is shared among all the blocks in an inline or comptime called
336336 /// function.
337337 pub const Inlining = struct {
338 func: ?*Module.Fn,
338339 comptime_result: Air.Inst.Ref,
339340 merges: Merges,
340341 };
......@@ -6428,7 +6429,6 @@ fn analyzeCall(
64286429 }),
64296430 else => unreachable,
64306431 };
6431 if (!is_comptime_call and module_fn.state == .sema_failure) return error.AnalysisFail;
64326432 if (func_ty_info.is_var_args) {
64336433 return sema.fail(block, call_src, "{s} call of variadic function", .{
64346434 @as([]const u8, if (is_comptime_call) "comptime" else "inline"),
......@@ -6448,6 +6448,7 @@ fn analyzeCall(
64486448 // This one is shared among sub-blocks within the same callee, but not
64496449 // shared among the entire inline/comptime call stack.
64506450 var inlining: Block.Inlining = .{
6451 .func = null,
64516452 .comptime_result = undefined,
64526453 .merges = .{
64536454 .results = .{},
......@@ -6534,6 +6535,7 @@ fn analyzeCall(
65346535 const fn_info = sema.code.getFnInfo(module_fn.zir_body_inst);
65356536 try sema.inst_map.ensureSpaceForInstructions(sema.gpa, fn_info.param_body);
65366537
6538 var has_comptime_args = false;
65376539 var arg_i: usize = 0;
65386540 for (fn_info.param_body) |inst| {
65396541 sema.analyzeInlineCallArg(
......@@ -6549,6 +6551,7 @@ fn analyzeCall(
65496551 memoized_call_key,
65506552 func_ty_info.param_types,
65516553 func,
6554 &has_comptime_args,
65526555 ) catch |err| switch (err) {
65536556 error.NeededSourceLocation => {
65546557 _ = sema.inst_map.remove(inst);
......@@ -6566,6 +6569,7 @@ fn analyzeCall(
65666569 memoized_call_key,
65676570 func_ty_info.param_types,
65686571 func,
6572 &has_comptime_args,
65696573 );
65706574 unreachable;
65716575 },
......@@ -6573,6 +6577,19 @@ fn analyzeCall(
65736577 };
65746578 }
65756579
6580 if (!has_comptime_args and module_fn.state == .sema_failure) return error.AnalysisFail;
6581
6582 const recursive_msg = "inline call is recursive";
6583 var head = if (!has_comptime_args) block else null;
6584 while (head) |some| {
6585 const parent_inlining = some.inlining orelse break;
6586 if (parent_inlining.func == module_fn) {
6587 return sema.fail(block, call_src, recursive_msg, .{});
6588 }
6589 head = some.parent;
6590 }
6591 if (!has_comptime_args) inlining.func = module_fn;
6592
65766593 // In case it is a generic function with an expression for the return type that depends
65776594 // on parameters, we must now do the same for the return type as we just did with
65786595 // each of the parameters, resolving the return type and providing it to the child
......@@ -6657,6 +6674,7 @@ fn analyzeCall(
66576674 error.ComptimeReturn => break :result inlining.comptime_result,
66586675 error.AnalysisFail => {
66596676 const err_msg = sema.err orelse return err;
6677 if (std.mem.eql(u8, err_msg.msg, recursive_msg)) return err;
66606678 try sema.errNote(block, call_src, err_msg, "called from here", .{});
66616679 err_msg.clearTrace(sema.gpa);
66626680 return err;
......@@ -6814,8 +6832,13 @@ fn analyzeInlineCallArg(
68146832 memoized_call_key: Module.MemoizedCall.Key,
68156833 raw_param_types: []const Type,
68166834 func_inst: Air.Inst.Ref,
6835 has_comptime_args: *bool,
68176836) !void {
68186837 const zir_tags = sema.code.instructions.items(.tag);
6838 switch (zir_tags[inst]) {
6839 .param_comptime, .param_anytype_comptime => has_comptime_args.* = true,
6840 else => {},
6841 }
68196842 switch (zir_tags[inst]) {
68206843 .param, .param_comptime => {
68216844 // Evaluate the parameter type expression now that previous ones have
......@@ -6870,23 +6893,20 @@ fn analyzeInlineCallArg(
68706893 .ty = param_ty,
68716894 .val = arg_val,
68726895 };
6873 } else if (zir_tags[inst] == .param_comptime or try sema.typeRequiresComptime(param_ty)) {
6874 sema.inst_map.putAssumeCapacityNoClobber(inst, casted_arg);
6875 } else if (try sema.resolveMaybeUndefVal(casted_arg)) |val| {
6876 // We have a comptime value but we need a runtime value to preserve inlining semantics,
6877 const wrapped = try sema.addConstant(param_ty, try Value.Tag.runtime_value.create(sema.arena, val));
6878 sema.inst_map.putAssumeCapacityNoClobber(inst, wrapped);
68796896 } else {
68806897 sema.inst_map.putAssumeCapacityNoClobber(inst, casted_arg);
68816898 }
68826899
6900 if (try sema.resolveMaybeUndefVal(casted_arg)) |_| {
6901 has_comptime_args.* = true;
6902 }
6903
68836904 arg_i.* += 1;
68846905 },
68856906 .param_anytype, .param_anytype_comptime => {
68866907 // No coercion needed.
68876908 const uncasted_arg = uncasted_args[arg_i.*];
68886909 new_fn_info.param_types[arg_i.*] = sema.typeOf(uncasted_arg);
6889 const param_ty = sema.typeOf(uncasted_arg);
68906910
68916911 if (is_comptime_call) {
68926912 sema.inst_map.putAssumeCapacityNoClobber(inst, uncasted_arg);
......@@ -6912,16 +6932,14 @@ fn analyzeInlineCallArg(
69126932 .ty = sema.typeOf(uncasted_arg),
69136933 .val = arg_val,
69146934 };
6915 } else if (zir_tags[inst] == .param_anytype_comptime or try sema.typeRequiresComptime(param_ty)) {
6916 sema.inst_map.putAssumeCapacityNoClobber(inst, uncasted_arg);
6917 } else if (try sema.resolveMaybeUndefVal(uncasted_arg)) |val| {
6918 // We have a comptime value but we need a runtime value to preserve inlining semantics,
6919 const wrapped = try sema.addConstant(param_ty, try Value.Tag.runtime_value.create(sema.arena, val));
6920 sema.inst_map.putAssumeCapacityNoClobber(inst, wrapped);
69216935 } else {
69226936 sema.inst_map.putAssumeCapacityNoClobber(inst, uncasted_arg);
69236937 }
69246938
6939 if (try sema.resolveMaybeUndefVal(uncasted_arg)) |_| {
6940 has_comptime_args.* = true;
6941 }
6942
69256943 arg_i.* += 1;
69266944 },
69276945 else => {},
src/codegen/llvm.zig+4
......@@ -3361,6 +3361,10 @@ pub const DeclGen = struct {
33613361 const llvm_type = try dg.lowerType(tv.ty);
33623362 return llvm_type.constNull();
33633363 },
3364 .opt_payload => {
3365 const payload = tv.val.castTag(.opt_payload).?.data;
3366 return dg.lowerParentPtr(payload, tv.ty);
3367 },
33643368 else => |tag| return dg.todo("implement const of pointer type '{}' ({})", .{
33653369 tv.ty.fmtDebug(), tag,
33663370 }),
test/behavior.zig-1
......@@ -133,7 +133,6 @@ test {
133133 _ = @import("behavior/bugs/13113.zig");
134134 _ = @import("behavior/bugs/13128.zig");
135135 _ = @import("behavior/bugs/13159.zig");
136 _ = @import("behavior/bugs/13164.zig");
137136 _ = @import("behavior/bugs/13171.zig");
138137 _ = @import("behavior/bugs/13209.zig");
139138 _ = @import("behavior/bugs/13285.zig");
test/behavior/bugs/13164.zig deleted-18
......@@ -1,18 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3
4inline fn setLimits(min: ?u32, max: ?u32) !void {
5 if (min != null and max != null) {
6 try std.testing.expect(min.? <= max.?);
7 }
8}
9
10test {
11 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
12 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
13 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
14 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
15
16 var x: u32 = 42;
17 try setLimits(x, null);
18}
test/behavior/call.zig+14
......@@ -381,3 +381,17 @@ test "generic function with generic function parameter" {
381381 };
382382 try S.f(S.g, 123);
383383}
384
385test "recursive inline call with comptime known argument" {
386 const S = struct {
387 inline fn foo(x: i32) i32 {
388 if (x <= 0) {
389 return 0;
390 } else {
391 return x * 2 + foo(x - 1);
392 }
393 }
394 };
395
396 try expect(S.foo(4) == 20);
397}
test/behavior/vector.zig+8-8
......@@ -1268,12 +1268,12 @@ test "store to vector in slice" {
12681268 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
12691269 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
12701270
1271 var v = [_]@Vector(3, f32){
1272 .{ 1, 1, 1 },
1273 .{ 0, 0, 0 },
1274 };
1275 var s: []@Vector(3, f32) = &v;
1276 var i: usize = 1;
1277 s[i] = s[0];
1278 try expectEqual(v[1], v[0]);
1271 var v = [_]@Vector(3, f32){
1272 .{ 1, 1, 1 },
1273 .{ 0, 0, 0 },
1274 };
1275 var s: []@Vector(3, f32) = &v;
1276 var i: usize = 1;
1277 s[i] = s[0];
1278 try expectEqual(v[1], v[0]);
12791279}
test/cases/compile_errors/recursive_inline_fn.zig created+18
......@@ -0,0 +1,18 @@
1inline fn foo(x: i32) i32 {
2 if (x <= 0) {
3 return 0;
4 } else {
5 return x * 2 + foo(x - 1);
6 }
7}
8
9pub export fn entry() void {
10 var x: i32 = 4;
11 _ = foo(x) == 20;
12}
13
14// error
15// backend=stage2
16// target=native
17//
18// :5:27: error: inline call is recursive