#Temporary lifetime

1 messages · Page 1 of 1 (latest)

tropic lynx
#

If you write a function call like foo(&.{a, b, c}), how long is the reference to the tuple guaranteed to be valid? End of statement, end of scope, etc?

vale hamlet
#

temporary variables are allocated on the stack of the caller

tropic lynx
#

But how long is that stack space valid? If I do foo(&.{a}) + foo(&.{b}), can the second tuple clobber the first? What about foo(&.{a}); foo(&.{b});?

vale hamlet
#

the stack space is valid until the function calling foo returns

tropic lynx
#

Got it thanks

#

Wait that can't be right

#

while (true) foo(&.{a}); would require infinite stack space

vale hamlet
#

any stack variables in a loop are allowed to be reused, otherwise all loops would require infinite stack space

#

the stack space still lives until the end of the function scope

wet sigil
#

note: the function scope refers to the called function

cunning trench
#

the array lives until the end of scope, not until the function returns.

tropic lynx
#

So with while (true) { foo(&.{a}); foo(&.{b}); } foo(a) is guaranteed to remain valid during foo(b), and the optimizer is not allowed to reuse the memory?

#

Then at the closing brace, both arrays become invalid?

wet sigil
#

are you storing &.{a} inside the foo function?

tropic lynx
#

More or less yeah. But only if I find out it's legal lol

cunning trench
#

the optimizer will not reuse the memory in that case.

wet sigil
#

'cause I'm pretty sure &.{a} just becomes invalid after the foo(&.{a}) call ends

cunning trench
#

no it doesnt

#

just wrote code doing this yesterday 😛

vale hamlet
#

sounds like a shaky assumption

wet sigil
#

seems dubious

#

I'm gonna test this out

tropic lynx
#

It seemed dubious to me, but I'd like to know what guarantees the language spec makes so I can take advantage of them

vale hamlet
#

there likely are none

tropic lynx
#

and/or not overly pessimize my code

cunning trench
#

i very much doubt these semantics are gonna change

wet sigil
#

alright, so the optimiser doesn't appear to re-use the addresses here, from what I can tell

tropic lynx
#

But is it guaranteed to stay that way? 🙂

wet sigil
#

there are no guarantees that I know of

tropic lynx
#

These details are kind of important to pin down if your language doesn't have a borrow checker lol

wet sigil
#

just to note though:

const std = @import("std");

var a: u8 = 0;
var b: u8 = 0;
var c: u8 = 0;

pub fn main() !void {
    foo(&.{a});
    {
        foo(&.{b});
    }
    foo(&.{c});
}

noinline fn foo(slice: []const u8) void {
    std.debug.print("{x}\n", .{@ptrToInt(slice.ptr)});
}

this prints 3 different addresses, despite the second call being in a scope in which the stack used by the temporary should die

cunning trench
#

well that the optimizer is allowed to do something doesnt mean it will

vale hamlet
wet sigil
#

sure

#

but I can't imagine why not in this instance

cunning trench
#

¯_(ツ)_/¯

wet sigil
cunning trench
#

naah this is probably defined, no?

vale hamlet
#

nothing is defined when there isn't a spec

tropic lynx
#

I agree with the gut feel if I'm being maximally cautious, but it would helpful to know for sure

cunning trench
tropic lynx
#

Are you allowed to ask questions in #compiler-devel?

wet sigil
#

pointers to temporaries were made const specifically to avoid using them as secret allocations

tropic lynx
#

or are the plebs not allowed in

wet sigil
#

no plebs

#

only folks working on the compiler or the language

tropic lynx
#

😦

#

Taken to the extreme this means in foo(&.{a}).bar(&.{b}), .{a} will be invalid during bar

#

Right?

vale hamlet
#

yes

tropic lynx
#

That is extremely limiting

vale hamlet
#

thats the point

#

magic allocations should be avoided

cunning trench
#

a scope-local constant is extremely non-magical

tropic lynx
#

Yeah I don't see how that's magic. The word "magic" is overused in discussions like this. This isn't Ruby

vale hamlet
#

at the moment it's mostly "here's a pointer to something, it'll live for a little bit" unless you're actively paying attention to each and every scope it's not immediately obvious

wet sigil
#

declaring where your memory lives is literally one of zig's front-facing ideals

cunning trench
#

it isnt implicit. its local to the block its in, just like any expression. this is perfectly logical.

wet sigil
#

logical != good

cunning trench
#

its consistent with how zig does memory.

wet sigil
#

how?

cunning trench
#

scope ends with the block (thats also what defer triggers on) and thats also where stack variables are freed

wet sigil
#

stack variables

cunning trench
#

and constants

wet sigil
#

the implicit part here is that there's no variable acting as the memory location

#

the compiler would just create it for you

#

and the fact that this thread exists makes it pretty obvious it's not immediately clear whether or not that's what would be done

cunning trench
#

hmm yea

#

well i think its good if it stays this way because everything else would make stuff incredibly annoying 😛

wet sigil
#

I'll be chuckling like a smug evil villain when the optimiser wreaks havoc on you

cunning trench
wet sigil
#

I can imagine. I imagine it would force you to take a different approach lol

cunning trench
#

yea i should anyways

wooden urchin
#

I don't think it's inherently unintuitive it's just breaking the rules of the predecessors

#

if I were writing assembly and needed the address of a temporary I obviously have to put it on the stack briefly

#

so zig having the same behavior is like duh

#

C++'s rules are dumb as fuck to me but I get that RAII makes it complicated

vale hamlet
wet sigil
#

sure, I'd make no argument about anything being inherent. But by virtue of there being anything to discuss about it means there can't really be a consensus on how intuitive it is - meaning, it will make sense if you already know how it works

wooden urchin
#

zig maps very close to assembly, and yes in zig you are expected to have an understanding of the stack, heap, static vars which are all low-level concepts

wet sigil
#

I mean, from that perspective, it makes more sense why you'd need to allocate a variable explicitly

#

like you said, you have to put it on the stack

wooden urchin
#

yeah, there's no other obvious alternative

#

besides making it UB or forbidden

#

so why not just implement the obvious behavior

#

I feel like that's intuitive

#

on the other hand, people often create const variables which get compiled away and never actually exist on the stack. is that unintuitive? I don't really think so

wet sigil
#

that doesn't result in any observably different behaviour, unless you're working directly with the stack pointer or whatever else

wooden urchin
#

exactly

tropic lynx
#

yeah I don't think it necessarily matters if it's intuitive, it just needs to be defined by the language, so people can learn it once and know what is/isn't allowed. as of now the consensus on the rules is "nobody knows". and the pre-1.0 compiler does little if any stack packing, and so people are taking advantage of current observed behavior. but there's a looming threat that one day an optimization will silently break your code if you run afoul of some undecided future set of rules. that doesn't seem like a great situation.

#

here's an idea. in debug builds, the language should overwrite temporaries with 0xaa once they're no longer valid. that way mistakes are guaranteed to be noticed no matter what the optimizer decides to do with the stack

#

but maybe that's more of a #bikeshed than a #1019652020308824145