aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVeikka Tuominen <git@vexu.eu>2022-12-20 22:33:38 +0200
committerVeikka Tuominen <git@vexu.eu>2022-12-26 16:36:30 +0200
commit81443fcde84879782f38fde33510e56d2faaaa21 (patch)
tree2c78513811f46d2759ffbdcb6e5dac525eb3f182
parent0b859831ad8a0c9df11e8da11f13739ed2dcf0d5 (diff)
downloadzig-81443fcde84879782f38fde33510e56d2faaaa21.tar.gz
zig-81443fcde84879782f38fde33510e56d2faaaa21.zip
Sema: add error for recursive inline call
Closes #12973
-rw-r--r--src/Sema.zig46
-rw-r--r--src/codegen/llvm.zig4
-rw-r--r--test/behavior.zig1
-rw-r--r--test/behavior/bugs/13164.zig18
-rw-r--r--test/behavior/call.zig14
-rw-r--r--test/behavior/vector.zig16
-rw-r--r--test/cases/compile_errors/recursive_inline_fn.zig18
7 files changed, 76 insertions, 41 deletions
diff --git a/src/Sema.zig b/src/Sema.zig
index 413621dd5c..602cf0063b 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -335,6 +335,7 @@ pub const Block = struct {
/// It is shared among all the blocks in an inline or comptime called
/// function.
pub const Inlining = struct {
+ func: ?*Module.Fn,
comptime_result: Air.Inst.Ref,
merges: Merges,
};
@@ -6428,7 +6429,6 @@ fn analyzeCall(
}),
else => unreachable,
};
- if (!is_comptime_call and module_fn.state == .sema_failure) return error.AnalysisFail;
if (func_ty_info.is_var_args) {
return sema.fail(block, call_src, "{s} call of variadic function", .{
@as([]const u8, if (is_comptime_call) "comptime" else "inline"),
@@ -6448,6 +6448,7 @@ fn analyzeCall(
// This one is shared among sub-blocks within the same callee, but not
// shared among the entire inline/comptime call stack.
var inlining: Block.Inlining = .{
+ .func = null,
.comptime_result = undefined,
.merges = .{
.results = .{},
@@ -6534,6 +6535,7 @@ fn analyzeCall(
const fn_info = sema.code.getFnInfo(module_fn.zir_body_inst);
try sema.inst_map.ensureSpaceForInstructions(sema.gpa, fn_info.param_body);
+ var has_comptime_args = false;
var arg_i: usize = 0;
for (fn_info.param_body) |inst| {
sema.analyzeInlineCallArg(
@@ -6549,6 +6551,7 @@ fn analyzeCall(
memoized_call_key,
func_ty_info.param_types,
func,
+ &has_comptime_args,
) catch |err| switch (err) {
error.NeededSourceLocation => {
_ = sema.inst_map.remove(inst);
@@ -6566,6 +6569,7 @@ fn analyzeCall(
memoized_call_key,
func_ty_info.param_types,
func,
+ &has_comptime_args,
);
unreachable;
},
@@ -6573,6 +6577,19 @@ fn analyzeCall(
};
}
+ if (!has_comptime_args and module_fn.state == .sema_failure) return error.AnalysisFail;
+
+ const recursive_msg = "inline call is recursive";
+ var head = if (!has_comptime_args) block else null;
+ while (head) |some| {
+ const parent_inlining = some.inlining orelse break;
+ if (parent_inlining.func == module_fn) {
+ return sema.fail(block, call_src, recursive_msg, .{});
+ }
+ head = some.parent;
+ }
+ if (!has_comptime_args) inlining.func = module_fn;
+
// In case it is a generic function with an expression for the return type that depends
// on parameters, we must now do the same for the return type as we just did with
// each of the parameters, resolving the return type and providing it to the child
@@ -6657,6 +6674,7 @@ fn analyzeCall(
error.ComptimeReturn => break :result inlining.comptime_result,
error.AnalysisFail => {
const err_msg = sema.err orelse return err;
+ if (std.mem.eql(u8, err_msg.msg, recursive_msg)) return err;
try sema.errNote(block, call_src, err_msg, "called from here", .{});
err_msg.clearTrace(sema.gpa);
return err;
@@ -6814,9 +6832,14 @@ fn analyzeInlineCallArg(
memoized_call_key: Module.MemoizedCall.Key,
raw_param_types: []const Type,
func_inst: Air.Inst.Ref,
+ has_comptime_args: *bool,
) !void {
const zir_tags = sema.code.instructions.items(.tag);
switch (zir_tags[inst]) {
+ .param_comptime, .param_anytype_comptime => has_comptime_args.* = true,
+ else => {},
+ }
+ switch (zir_tags[inst]) {
.param, .param_comptime => {
// Evaluate the parameter type expression now that previous ones have
// been mapped, and coerce the corresponding argument to it.
@@ -6870,23 +6893,20 @@ fn analyzeInlineCallArg(
.ty = param_ty,
.val = arg_val,
};
- } else if (zir_tags[inst] == .param_comptime or try sema.typeRequiresComptime(param_ty)) {
- sema.inst_map.putAssumeCapacityNoClobber(inst, casted_arg);
- } else if (try sema.resolveMaybeUndefVal(casted_arg)) |val| {
- // We have a comptime value but we need a runtime value to preserve inlining semantics,
- const wrapped = try sema.addConstant(param_ty, try Value.Tag.runtime_value.create(sema.arena, val));
- sema.inst_map.putAssumeCapacityNoClobber(inst, wrapped);
} else {
sema.inst_map.putAssumeCapacityNoClobber(inst, casted_arg);
}
+ if (try sema.resolveMaybeUndefVal(casted_arg)) |_| {
+ has_comptime_args.* = true;
+ }
+
arg_i.* += 1;
},
.param_anytype, .param_anytype_comptime => {
// No coercion needed.
const uncasted_arg = uncasted_args[arg_i.*];
new_fn_info.param_types[arg_i.*] = sema.typeOf(uncasted_arg);
- const param_ty = sema.typeOf(uncasted_arg);
if (is_comptime_call) {
sema.inst_map.putAssumeCapacityNoClobber(inst, uncasted_arg);
@@ -6912,16 +6932,14 @@ fn analyzeInlineCallArg(
.ty = sema.typeOf(uncasted_arg),
.val = arg_val,
};
- } else if (zir_tags[inst] == .param_anytype_comptime or try sema.typeRequiresComptime(param_ty)) {
- sema.inst_map.putAssumeCapacityNoClobber(inst, uncasted_arg);
- } else if (try sema.resolveMaybeUndefVal(uncasted_arg)) |val| {
- // We have a comptime value but we need a runtime value to preserve inlining semantics,
- const wrapped = try sema.addConstant(param_ty, try Value.Tag.runtime_value.create(sema.arena, val));
- sema.inst_map.putAssumeCapacityNoClobber(inst, wrapped);
} else {
sema.inst_map.putAssumeCapacityNoClobber(inst, uncasted_arg);
}
+ if (try sema.resolveMaybeUndefVal(uncasted_arg)) |_| {
+ has_comptime_args.* = true;
+ }
+
arg_i.* += 1;
},
else => {},
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
index 9fc3055969..4c553f0305 100644
--- a/src/codegen/llvm.zig
+++ b/src/codegen/llvm.zig
@@ -3361,6 +3361,10 @@ pub const DeclGen = struct {
const llvm_type = try dg.lowerType(tv.ty);
return llvm_type.constNull();
},
+ .opt_payload => {
+ const payload = tv.val.castTag(.opt_payload).?.data;
+ return dg.lowerParentPtr(payload, tv.ty);
+ },
else => |tag| return dg.todo("implement const of pointer type '{}' ({})", .{
tv.ty.fmtDebug(), tag,
}),
diff --git a/test/behavior.zig b/test/behavior.zig
index 36403a8f0c..72f6bf8e6a 100644
--- a/test/behavior.zig
+++ b/test/behavior.zig
@@ -133,7 +133,6 @@ test {
_ = @import("behavior/bugs/13113.zig");
_ = @import("behavior/bugs/13128.zig");
_ = @import("behavior/bugs/13159.zig");
- _ = @import("behavior/bugs/13164.zig");
_ = @import("behavior/bugs/13171.zig");
_ = @import("behavior/bugs/13209.zig");
_ = @import("behavior/bugs/13285.zig");
diff --git a/test/behavior/bugs/13164.zig b/test/behavior/bugs/13164.zig
deleted file mode 100644
index 66f4e28fd8..0000000000
--- a/test/behavior/bugs/13164.zig
+++ /dev/null
@@ -1,18 +0,0 @@
-const std = @import("std");
-const builtin = @import("builtin");
-
-inline fn setLimits(min: ?u32, max: ?u32) !void {
- if (min != null and max != null) {
- try std.testing.expect(min.? <= max.?);
- }
-}
-
-test {
- if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
- if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
- if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
- if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
-
- var x: u32 = 42;
- try setLimits(x, null);
-}
diff --git a/test/behavior/call.zig b/test/behavior/call.zig
index 4addd93227..73ffe7c157 100644
--- a/test/behavior/call.zig
+++ b/test/behavior/call.zig
@@ -381,3 +381,17 @@ test "generic function with generic function parameter" {
};
try S.f(S.g, 123);
}
+
+test "recursive inline call with comptime known argument" {
+ const S = struct {
+ inline fn foo(x: i32) i32 {
+ if (x <= 0) {
+ return 0;
+ } else {
+ return x * 2 + foo(x - 1);
+ }
+ }
+ };
+
+ try expect(S.foo(4) == 20);
+}
diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig
index 926c2f8f3e..7a441a925d 100644
--- a/test/behavior/vector.zig
+++ b/test/behavior/vector.zig
@@ -1268,12 +1268,12 @@ test "store to vector in slice" {
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
- var v = [_]@Vector(3, f32){
- .{ 1, 1, 1 },
- .{ 0, 0, 0 },
- };
- var s: []@Vector(3, f32) = &v;
- var i: usize = 1;
- s[i] = s[0];
- try expectEqual(v[1], v[0]);
+ var v = [_]@Vector(3, f32){
+ .{ 1, 1, 1 },
+ .{ 0, 0, 0 },
+ };
+ var s: []@Vector(3, f32) = &v;
+ var i: usize = 1;
+ s[i] = s[0];
+ try expectEqual(v[1], v[0]);
}
diff --git a/test/cases/compile_errors/recursive_inline_fn.zig b/test/cases/compile_errors/recursive_inline_fn.zig
new file mode 100644
index 0000000000..2308bbdbc7
--- /dev/null
+++ b/test/cases/compile_errors/recursive_inline_fn.zig
@@ -0,0 +1,18 @@
+inline fn foo(x: i32) i32 {
+ if (x <= 0) {
+ return 0;
+ } else {
+ return x * 2 + foo(x - 1);
+ }
+}
+
+pub export fn entry() void {
+ var x: i32 = 4;
+ _ = foo(x) == 20;
+}
+
+// error
+// backend=stage2
+// target=native
+//
+// :5:27: error: inline call is recursive