[RFC] Stack arenas using alloca

Hi Folks,

I wanted to share the seed of an idea I have been ruminating on for a while, and that is being able to return alloca memory from a function. Note: this is an edit to a message sent to the GCC mailing list.

I think it’s trivially possible to return alloca memory from a function by hacking the epilogue to unlink the frame pointer but not pop the stack. Stack traces should work fine and it would look like the parent function called an alloca covering the fixed child frame and the alloca or VLA that it returned.

I tend to use alloca a lot in my programming style as I like to avoid the heap. I have a style where I run a loop with a size pass, then call alloca, then run the loop again without suppressing writes. I would like to be able to return, initially, one alloca to a parent function, but more than one should also be possible.

I haven’t thought a lot about the mechanics of how one might expose this to a user to signal to the compiler to suppress popping the stack. I imagined one could return a pointer or structure with _Stack qualifiers to signal that stack memory from the child frame is being returned. There is a separate conversation about _Stack, _Data, _Heap, and _Thread (TLS) memory realm qualifiers on pointers for type reflection and checkers that perhaps translate underneath to address spaces.

This is a somewhat brief note as there is a deeper conversation about compacting stack arenas that would be possible if one had a runtime and compile time reflection API for frame metadata, where the frame is expressed as an anonymous C structure perhaps accessible via a function on a reference to a function. If you had frame metadata, you could emit one or many memmove calls. trivially one just needs the fixed frame length to recover the fixed frame stack memory, and, with full frame reflection, it may be possible to defer to a CRT routine in case two or more stack pointers are returned, assuming the compiler tracks sizes of alloca calls, although there are some issues with safe stack mentioned at the end of this post. Ideally, the reflection API would also be constexpr-able meaning the runtime compaction could be inlined. This wouldn’t work for types with embedded pointers to stack unless one has a full single-thread compacting GC that uses type metadata, but I tend to use indices anyway. But unlike heap compaction, it doesn’t have to worry about races.

The beauty of stack arenas, and compacting stack arenas, as a subtype, are many. Firstly the memory has no thread aliasing issues unlike heap memory, meaning analysis is much simpler and there are no pause issues like there are for GC. Secondly, it would be quite a neat way to constexpr variable-length arrays or a _List type because, unlike malloc, it’s much easier to reason about. This would also be useful in C++ to inline returning (initializer) lists as memory SSA is cleaner when not calling malloc because technically malloc memory is neither alias-free nor race-free, and memory SSA is taking some liberties.

I also have a relatively complete C reflection API that could be used as a starting point for C reflection. It currently works as an LLVM plugin. It would also be great to have access to the frame of a function as an anonymous structure. The API is only runtime at present and it could benefit from a for comprehension as looping on variable length lists is a little clumsy as I need to fetch the size first then pass in a pointer. If I could return alloca memory in a structure that would largely solve this problem with a more elegant interface by returning a structure with a length and a pointer to alloca memory.

There is a secondary question here, and that is, is there a way to reflect on the frame of a function in a plugin, perhaps as an anonymous structure? I guess this is complicated because one would need DWARF-style program-points as the frame could vary at different program-points/line-numbers due to frame memory reuse, although, I guess it should be trivially possible to get the size of the extent of the fixed and variable portions of the frame, assuming the use of VLAs. I’m thinking one could make some builtins like:

  • _buitin_fn_stack_fixed_begin
  • _buitin_fn_stack_fixed_end
  • _buitin_fn_stack_var_begin
  • _buitin_fn_stack_var_end

They could be used in a macro, or stack inline function, to emit one or more memmove calls. It would also need to be safe-stack aware. It would also be nice if the CRT could expose a way to get the beginning and end pointers of the stack for the current thread and safe-stack if compiled using safe stacks. In the case of safe-stack, the variable portion would be in a different arena and the runtime would need to be aware of this and suppress memmove to compact over the fixed portion of the frame. Although compaction is not strictly necessary for this to work, it’s a nice to have.

There is another topic here accessing-stack-frame-after-returning-from-the-function that is related, but I have created a new topic because I am proposing to call these stack arenas and I give more details.

Regards,
Michael

Stack Arenas

Draft of a slightly more formal proposal for stack arenas using alloca.

Motivation

I have been working on a C reflection API that is orientated towards runtime use in entity managers for structure serialization and deserialization using LEB128 and ASN.1, etc. It currently works as an LLVM plugin. It lacks function stack frame metadata but unlike DWARF, it is platform-independent. The background is that I have a programming style where I use alloca frequently with Vulkan-style C APIs, where I tend to run a loop with a size pass, call alloca to make temporary space, and then rerun the loop without writes being suppressed. To make this easier and more graceful, I would like to be able to return, initially, one alloca to a parent function, but more than one should be possible as I maintain that stack memory is better used for temporary space of a certain size as opposed to the heap memory.

The reflection code could also benefit from a for comprehension as looping on variable length lists in C is a little clumsy as I need to fetch the size first then pass in a pointer. If I could return alloca memory in a structure that would largely solve this problem because then the API doesn’t need to worry about freeing client memory which is the reason for the two-call size pass approach.

Proposal

It is possible to return alloca memory from an extern function by modifying the epilogue to unlink the frame pointer but not pop the stack pointer. Under this change, stack traces should work fine and it would look like the parent function called an alloca covering the fixed size of the child frame along with the alloca or VLA that it returned. It could also be that alloca implementation becomes slightly more sophisticated and pushes a pointer list head at the end of the fixed frame, linking to the next alloca, so they can be traced.

How does a user signal the compiler to suppress popping the stack to return alloca memory?

I haven’t yet thought about how one might expose alloca returns in C. I imagined one could return a pointer or structure with _Stack qualifiers to signal that stack memory from the child frame is being returned. There is a separate conversation about _Stack, _Data, _Heap, and _Thread (Thread-Local-Storage) memory realm qualifiers on pointers for type reflection and checkers that translate these underneath to address spaces. The problem with with _Stack, maybe with a Region suffix, is that it only indicates to a checker that there is a pointer is to stack memory. That doesn’t signal that child frame memory is being returned, so that the epilogue can be modified.

Perhaps _AllocaReturn on functions might be best?

Compacting Stack Arenas

There is a deeper conversation about compacting stack arenas that would be possible if we had runtime and compile time type reflection with function stack frame metadata. If we had frame metadata, we could emit one or many memmove calls to compact the frame when returning, using the algebraic extents of the dynamic part of the function stack frame.

Trivially one needs the fixed frame length to recover the fixed frame stack memory, and, with full stack frame reflection and type reflection, it may be possible to defer to a reflection-aware CRT routine in case two or more stack pointers are returned, for it to compact the child frame and adjust the stack pointer, assuming the compiler tracks sizes of alloca calls, perhaps via a linked list of alloca regions at the end of the fixed frame. Ideally, the reflection API would be available at runtime but also constexpr-able meaning the runtime compaction could be inlined at compile time. This wouldn’t work for types with embedded pointers to stack unless one has a full single-thread compacting GC that uses this type metadata. That is not impossible, and unlike heap memory compaction, it doesn’t need to worry about data races.

Although stack arena compaction is not strictly necessary for alloca returns from functions to work, it’s a nice to have in a more comprehensive system.

Benefits of Stack Arenas

The benefits of stack arenas, and compacting stack arenas, as a subtype, are many. Firstly the memory has no thread aliasing issues unlike heap memory, meaning analysis is much simpler and there are no locking or pause issues like there are for traditional GC on the heap. Secondly, it would be quite a neat way to constexpr variable-length arrays or a _List type, as unlike malloc, it’s much easier to reason about. This would also be useful in C++ to inline returning (initializer) lists as memory SSA is cleaner when not calling malloc because technically malloc memory is neither alias-free nor race-free, and memory SSA is taking some liberties there. It should also be faster as it needs no locking.

Compile-time Stack Frame Structure Reflection

I would also like to access the stack frame metadata for functions inside plugins. I am not sure if I can do this. The C reflection API that motivated this proposal is only runtime at present. This would be useful for runtime compaction.

Is there a way to reflect on the frame of a function in a plugin, perhaps as an anonymous structure? I guess it is complicated because one would need DWARF-style program-points as the frame could vary at different program-points/line-numbers due to frame memory reuse

Runtime Stack Frame Extent Reflection

It should be trivially possible to get extents of the fixed and variable portions of the stack frame inside inline code or macros, assuming the use of VLAs. I’m thinking one could make some builtins like:

  • _buitin_function_stack_fixed_begin
  • _buitin_function_stack_fixed_end
  • _buitin_function_stack_variable_begin
  • _buitin_function_stack_variable_end

They could be used in a macro, static inline, or constexpr function, to emit one or more memmove calls when returning alloca memory.

Compatibility with Safe Stack

Stack arenas would need to be safe-stack compatible. In this case, it would also be nice if the CRT could expose a way to get the beginning and end pointers of the stack for the current thread and safe-stack if compiled using safe stacks. In the case of safe-stack, the variable portion would be in a different arena and the runtime would need to be aware of this and suppress memmove to compact over the fixed portion of the frame. This can trivially be tested with a pointer comparison to see that the variable extent is within the fixed extent.

  • _buitin_thread_stack_fixed_begin
  • _buitin_thread_stack_fixed_end
  • _buitin_thread_stack_variable_begin
  • _buitin_thread_stack_variable_end

Some thoughts:

  • It would require a new calling convention: the caller must have a frame pointer and know that for some callee, rsp is not callee-saved.
  • It would be easy to inadvertantly cause unbounded stack growth, which is already a problem in practice with alloca/VLAs.

Moving memory regions in C/C++-like languages is not generally possible: there can be self-references in data structures (e.g., SmallVector) and pointers can escape, e.g. to non-stack memory or to integers (e.g., uintptr_t). It is also valid for pointers to stack memory to be shared across threads (as long as the memory region exists). Unlike managed languages like Go, it is also not generally possible to say where pointers inside the stack frame are, e.g. due to unions, pointer tagging schemes (PointerUnion, TinyPtrVector), etc.

I maintain that stack memory is better used for temporary space of a certain size as opposed to the heap memory.

Have you considered using a bump pointer allocator on a large chunk of heap memory that you pass through your functions? This might be a better, simpler, and cleaner solution to the problem of avoiding many small heap allocations.

3 Likes

good observation. I didn’t consider the internal LLVM/Clang class hierarchy to communicate calling convention. afaik I need the same calling convention as __builtin_alloca. I’m aware it’s just add $-n, %rsp in most cases, and without frame pointers, one just omits the frame pointer unlink in the epilogue.

but alas you are right. the compiler needs to know the stack pointer is changing. especially in the case without frame pointers (but on x86 at least the compiler seems to ignore -fomit-frame-pointer). it’s frame pointer less basis for the stack pop will have just changed. with frame pointers, it’s in %rbp of %fp or whatever it is called.

btw if I try to implement alloca as a statement expression, with asm barriers on stack pointer, it fails, but I might be doing something wrong. this sort of stuff has worked in older compilers in the past because I remember reading alloca in some old compilers, but yeah this stuff is kind of fraught. just curious what bug there is in this because I am messing about.

#define ARCH_REG_SP "%rsp"

#define alloca(n) ({ \
    register void* sp __asm__(ARCH_REG_SP); \
    __asm__ volatile ("" : "=r" (sp) : : "memory"); \
    register void* p = sp; \
    sp = (void*)(((uintptr_t)p - (n) - 15) & ~15ull); \
    __asm__ volatile ("" : : "r" (sp) : "memory"); \
    (void*)(((uintptr_t)p + 15) & ~15ull); \
});

I did consider a heap bump allocator but I decided I want one on the stack because then I can just return from a parent frame and voilà, memory freed. of course, C++ needs to run destructors on structures. but I want this to work for C and I want to use stack memory to return VLAs.

I can trivially create a stack overflow by calling a function recursively. This is no different. I am the user in this case and I want to use stack memory not heap, so will fork the local compiler if need be. my binaries will still work. thread stacks on my system are like 8MiB.

anyway, the string and list utilities are going to be interesting. I don’t know if other people are going to like this, but I am a big fan of VLAs and alloca.

btw this works for me in a tiny kernel running on bare metal compiled with GCC. I am working on a bare metal experiment where the OS thread stacks will be thread private so that stack escapes can’t be dereferenced in other threads. it needs a page table root per thread so that adds a couple of PTE pages per thread. that’s 8KiB which is pretty small when the thread stacks default to 8MiB.

#define ARCH_REG_SP "%rsp"

static inline void* get_sp()
{
    register void* sp asm(ARCH_REG_SP);
    asm volatile ("" : "=r" (sp) : : "memory");
    return sp;
}

static inline int pivot_stack(int argc, char **argv, void *ns,
    int (*main)(int argc, char **argv))
{
    register int r;
    register void* sp asm(ARCH_REG_SP);
    asm volatile ("" : "=r" (sp) : : "memory");
    void *o = sp;
    sp = ns;
    asm volatile ("" : : "r" (sp) : "memory");
    r = main(argc, argv);
    sp = o;
    asm volatile ("" : : "r" (sp) : "memory");
    return r;
}

thinking about two approaches for returning alloca memory from functions:

  • a). attributed function epilogue leaves with expanded stack pointer, and pushes return address at end.
  • b). attributed function epilogue leaves with dynamic stack delta in a register and caller issues an alloca unintialized again.

I’m currently liking a). more, I am thinking we could make an AllocaCallInst that saves the stack pointer, issues CallInst, measures stacl delta, restores stack pointer, then issues an Alloca in the parent frame to expand the dynamic frame area in the parent, along with metadata on a parent local holding the detla, and this could get optimized out, but the function returns with the dynamic area in the stack pointer. this seems like a cleaner boundary at function return because the stack pointer covers the memory region at function return to clearly indicate the covered memory bounds. it’s just now it is the fixed frame plus the dynamic area.

temporal safety

with temporal safety issues and careful use of carried bounds on VLAs, I think the idea of bumping stack to 128MiB per thread or more on processes that are stack aware, is better than using heap memory, which we dedicate to memory queues because the compacting stack arena is going to be lock-free because we know there is only one thread on that memory, so no inter-core cache coherency traffic. LLVM uses like > 5GiB memory for linking per processor. IMHO most of this was probably single-threaded code in the past and should have had its space on the stack because malloc can use quite a lot of CPU. returning the dynamic area seems very useful to me.

security analysis

Boeing is not going to be calling string functions in the aileron code and have to worry about stack overflows from unbounded strings. so I don’t think it’s a worry, and in fact, carrying bounds on VLAs and extending them into parent frames is IMHO a good idea for security in general. the parent will even know the bounds because it has access to the local with the bound and the array. we just need a way to return a VLA type not as a pointer but as a VLA type with algebraic attachment to the boinds. it needs a deduced return type. seems useful. this is a deeper topic because the first compiler to allow users to return alloca memory, and later VLAs, will spawn a lot of security analysis.

frame pointer like discipline for alloca

as mentioned, it also might be nice to give alloca a frame pointer-like discipline. so it becomes a nested frame. i.e. like a linked list, then it could be walked more easily by debuggers. which means one extra store, and add, or push instruction for an alloca. that could add a couple of cycles and some store memory bandwidth. there is no dependency on the extra pointer write so no other instructions would have to wait. so latency should be very low and possibly completely hidden. as it’s only the epilogue of chained blocks that need that metadata. that would be a new way for the epilogue in parent frames to unwind the dynamic area. reflective.

Besides describing these with DWARF, my first question is how something like libunwind() would deal with this? We have our own unwinding code on OpenVMS and I’m having trouble imagining how we’d deal with this. I think you need some CFI markers in the generated code to describe it?

short story

making the call look like a dynamic alloca is the way to go because dynamic allocas already have unwind metadata, but I am early enough in my investigation not to know exactly how to achieve that, just that I believe it is possible. there are multiple ABIs including PE/win32 and SysV. and there is SafeStack where dynamic allocas aren’t on the stack but are bump allocated in a heap region parallel to the fixed frame stack. so it is a significant undertaking.

currently reading X86FrameLowering::emitEpilogue for SEH and CFI metadata for dynamic allocas. we should be able to emit something similar.

long story

the idea to have a frame pointer-like discipline for dynamic alloca was just a thought. as there is an implicit closure on the stack pointer. I was thinking of them as virtual lambdas with a block that is a barrier on the stack pointer. I’m not 100% sure how the barrier on the stack pointer is currently communicated inside LLVM (maybe it is deduced in llvm/lib/Analysis/StackLifetime.cpp) because it seems the calling convention can’t indicate the stack pointer is modified as it appears to be ignored due to it being a “fixed register”. perhaps it is possible to do this at zero cost with unwind metadata without adjusting the frame pointer . I guess it is a choice in translation. I was curious if debuggers could show a list of dynamic allocas for a call frame as opposed to one lump of dynamic memory delta against the fixed frame. if we could return dynamic alloca memory this would become more useful. it mostly emerged out of thinking about a register VM versus a stack VM, and a potential hybrid that uses nested blocks for barriers on stack or frame pointer relative accesses (noting this also has to work with -fomit-frame-pointers).

the benefit of a frame pointer like discipline is that one could reflect on the size of allocas at a relatively low cost. just it’s very different and I don’t think it suits being done in a mainstream compiler. at the moment it seems that the epilogue just knows how to release all dynamic allocas in one go (at least on X86) via a frame pointer relative load of the fixed frame stack pointer using a frame pointer relative load. of course I also need to study -fomit-frame-pointers translations. It’s just I was thinking about walking allocas in a debugger, but like I said. perhaps this can be done at zero cost using unwind metadata. it needs more research…

alloca return and jump functions

a heads up about __attribute__(alloca_return)) and a relationship with Jump Functions. I emailed the GCC mailing list about the alloca_return idea with the observation that returning dynamic alloca, debug metadata aside, means RET no longer works after the epilogue, unless, as a potentially ROP-unsafe hack, one pushes the return address onto the stack after the dynamic alloca area, noting that it is probably better off as an indirect branch. I mentioned that observation on the GCC mailing list. I am also noting that epilogue and return have separate lowering in LLVM, but they would need signaling for alloca_return to use an indirect branch and there would now be an interdependency between the two nodes. yesterday I spotted a discussion on the GCC list about Jump Functions, which have a jump instead of a return after the epilogue, and they are a related issue.

epilogue and return

so for alloca_return it possibly makes sense to modify the epilogue and return lowering to work together, noting that on X86, %rbp has been popped at the end of the epilogue so we would need an address in a register to get to the return address post-epilogue because we can no longer use frame relative references to the fixed frame. besides this, we just don’t restore %rsp as well as the other changes in callers to observe the call like it was an alloca and expect %rsp to grow. e.g.

epilogue:

// substitute with return address displacement
mov -0x10(%rbp), %rsi
pop %rbp

return:

// jmp *-0x10(%rbp) cannot work here
jmp *%rsi

or something similar on X86. it somewhat addresses a potential ROP issue, noting that no code runs between the epilogue and return so it is precautionary. that said, an indirect branch seems simpler than pushing a duplicate return address at the end of the epilogue, which seems to me more like a workaround, but it is what I have at present in my hacky POC patches that I’m not yet ready to share.

stack overflow

about stack overflows. I can already stack overflow just in the much less elegant and more convoluted way of calling a function twice, once to get the size, like the Vulkan and other APIs, with no write if pointer is NULL size passes, then pass in some alloca memory, which I can also do recursively, which was the thinking that prompted the idea in the first place. although one must do this explicitly and there are a lot of potential surprises with alloca_return for unsuspecting callers that suggests a C language derivative to experiment in might be worthwhile because I don’t see it as a mainstream feature without a lot of study on the consequences of escaping stack memory to parent functions.

I’d suggest that hardware stacks may not be a perfect vehicle for this, as return addresses, saved register values, and other hardware contextual information would be pushed there and become dead-weights, after caller retained callee payload.

Why not a separate stack that purely user-data-oriented for this purpose?

in the safe-stack case, we would use a separate stack, consistent with how the compiler works now. with safe-stack, dynamic alloca is moved to a separate stack.

alloca memory is a class of stack-disciplined automatic memory, unlike heap memory which is manually managed and requires locking. the proposal is particular about using stack memory because it has different memory aliasing rules to heap memory. objects allocated on the stack should never be globally visible, and it is UB to pass stack memory to another thread. VLAs are already placed on the stack and are automatically freed there. the proposal just defers this to the parent frame as if a call to alloca in a child function was made in the parent function.

the small string and list functions we intend to use this for will have most of their fixed frame in registers so we will only lose the frame pointer and return address memory. like a couple of pointers.

I’m not sure what you’re getting at here. At least as stated, this is not true. The address of an object on the stack may be exposed and used by other threads – there is no UB in doing so. Of course you cannot access an object after it’s been freed, but that’s no different than a heap object, either.

it is undefined behavior to escape pointers to stack memory to other threads.

we only have implementations to guide us here. secure implementations such as the PaX and grsecurity patches to Linux and possibly mainline Linux soon are moving to a page table root pointer per thread for kernel thread stacks so that thread stacks are not visible to other threads.

99.99% of programs don’t do this and it is considered an error by thread sanity checkers. customarily this is just a “don’t do” like “user-after-free” and isn’t documented in a “C memory model” like the C malloc and free functions from the C Standard Library on POSIX/UNIX Virtual Memory with threads memory model, as opposed to some embedded memory model, because we don’t have one.

pthreads came so late in the game that they didn’t modify virtual memory. it is such a low level detail that it is needed for green threads and fibers but is not needed for user programs and is customarily seen as an error… to escape pointers to thread stack memory to other threads.

I am not sure what you are getting at here. You can access a freed object after it has been freed.

Can you share wording from either the C or C++ standard that supports this perspective? I’m not aware of any wording that would make such escaping undefined behavior.

Such an object would be outside its lifetime. Per C++ [basic.life]p1:

… The lifetime of an object o of type T ends when:

  • (1.3)
    if T is a non-class type, the object is destroyed, or
  • (1.4)
    if T is a class type, the destructor call starts, or
  • (1.5)
    the storage which the object occupies is released, or is reused by an object that is not nested within o ([intro.object]).

…

Per the C standard, it is “implementation-defined”.

An object whose identifier is declared with no linkage and without the storage-class specifier static has automatic storage duration, as do some compound literals. The result of attempting to indirectly access an object with automatic storage duration from a thread other than the one with which the object is associated is implementation-defined.

However, it is required to work by POSIX:

3.190 Live Thread

A single flow of control within a process. Each thread has its own thread ID, scheduling priority and policy, errno value, floating point environment, thread-specific key/value bindings, and the required system resources to support a flow of control. Anything whose address can be determined by a thread, including but not limited to static variables, storage obtained via malloc(), directly addressable storage obtained through implementation-defined functions, and automatic variables, are accessible to all live threads in the same process.

(This is not like “use after free”, which is unconditionally an error.)

3 Likes

Forgot to mention, it’s also required by C++.

Every thread in a program can potentially access every object and function in a program. (^38: An object with automatic or thread storage duration ([basic.stc]) is associated with one specific thread, and can be accessed by a different thread only indirectly through a pointer or reference ([basic.compound]))

3 Likes

Access to stack variables by other threads is also required by Rust.
The standard library has thread::scope which demonstrates safe use of stack variables by another thread in its usage example (copied here):

fn main() {
    use std::thread;
    let mut a = vec![1, 2, 3];
    let mut x = 0;
    thread::scope(|s| {
        s.spawn(|| {
            println!("hello from the first scoped thread");
            // We can borrow `a` here.
            dbg!(&a);
        });
        s.spawn(|| {
            // We can even mutably borrow `x` here,
            // because no other threads are using it.
            x += a[0] + a[2];
        });
        println!("hello from the main thread");
    });
    
    // After the scope, we can modify and access our variables again:
    a.push(4);
    assert_eq!(x, a.len());
}

implementation-defined and undefined are very similar and they overlap with implementation-observed semantics. it is not defined by the C standard. I note that some implementations are beginning to cause faults when local variables from one thread are referenced from another and I am working on a threading implementation that has that behavior. it is implementation defined. I think it is largely an artifact of mmap originally being process-specific as it preceded pthreads, because as a software developer, I would like to disallow pointers to local variable escaping to other threads.

you might differ on this point, but that won’t change my position. the fact that Rust allows it is only a byproduct of the VM memory model allowing it, not the language per see. it is customarily seen as an error to scape pointers to local variable of automatic storage duration to other threads.

also, POSIX is quite independent from C because Microsoft doesn’t implement POSIX. curious if you can find the precise text that requires it as opposed to allowing it as an artefact of earlier design choices because it could also be disallowed without affecting many programs while improving safety.

In my implementation I am defining local variables of automatic storage duration to be restricted to the thread that owns them and will require a message in a buffer, noting that the C language specification does not refer to “stack” or “heap” just automatic storage duration, I would say that customarily escaping pointers to local variables is seen as a potential safety violation. I understand that you can synchronize on a spawning stack to keep the variables in scope for the lifetime of the spawned threads, but they have to worry about being unwound by an exception while it is not possible to safely pause the other threads. I would prefer an implementation to use a buffer.

semantically it would be silly to inherit from what is customarily an unsafe case, to make pointers to local variables alias-like pointers to global memory whereby they can be changed at any time by “spooky action at a distance”. this would forbid a nuanced approach to for example a reference counting implementation where atomic-locked operations on local variables do not need to be used. that said, we don’t have memory region qualifiers in C/C++ yet so we can’t do this at present.

granting a lifetime on a scope of local variables to other threads is possible but not all implementations will allow users to do that. unless a region of the stack has escaped to another thread, then memory used by local variables should not need to worry about synchronization that is required for global memory.

that is the primary reason why I want to use stack memory in this proposal. and I am aware there are corner cases where prevalent implementations allow pointers to local variables to escape to other threads. I do not think it is wise.

if you assume the broadest definition of a degraded “allowed” implementation-defined cases, then you need to alias stack memory like global memory, and it is my opinion that that would be unwise.

by a similar token, I think memory-SSA is safer on local variables or alloca because when it SSAs heap memory from malloc, it is granting safety that does not exist, and which does largely exist for memory for local variables. at least “customarily”. you could use that loophole to say that threads can scribble over the stack of other threads at any moment which would be silly as it would be treating it like global memory.

if we actually had a memory model, it should allow the optimizer to treat local variables as not visible to other threads unless it has information they are. not to infer that as a default like global memory.