That can be fixed by “outlining” the function body. Just redefine what “inline function” means such that its use does not guarantee actual inlining of code. That’s an anachronism anyway. Given the complexity of modern CPUs, determining whether forcing the inlining of one particular function would result in better performance is basically impossible. There’s no good reason to preserve the semantic from C (and if we want to, we can make it a callconv option instead).
Redefining inline functions to mean that their local variables are in the stack space of the caller creates many opportunities for optimization and simplification. Unlike C, Zig has comptime arguments. It does happen frequently where a function returning variable-length result would know the maximum extent. Take this humble example:
var buffer: [32]u8 = undefined;
const name = try std.fmt.bufPrint(&buffer, "item #{d}", .{index});
Currently, the caller has to provide the buffer, even though it’s not in a good position to know how large it needs to be in order to avoid an error. bufPrint() does know based on the format string and the arg-tuple type. So we could something like this instead:
const name = std.fmt.inlinePrint("item #{d}", .{index});
Such situations are commonplace, where the length of the result is known at comptime to the callee but not the caller. In my own, the build invokes a user-supplied function that’s supposed to look like this:
pub fn getImports(b: *std.Build, args: anytype) []const std.Build.Module.Import {
const sqlite = b.dependency("sqlite", .{
.target = args.target,
.optimize = args.optimize,
}).module("sqlite");
return &.{
.{ .name = "sqlite", .module = sqlite },
};
}
The code works (for now) because I call the function with @call(.always_inline), but it’s relying on unsanctioned behavior. Why not make it official? Is it even logically possibly to implement function inlining such a way that the inlined function would see a different stack pointer?