简体   繁体   English

优化c ++编译器如何重用函数的堆栈槽?

[英]how does an optimizing c++ compiler reuse stack slots of a function?

How does an optimizing c++ compiler determine when a stack slot of a function(part of stack frame of a function) is no longer needed by that function, so it can reuse its memory? 优化c ++编译器如何确定该函数何时不再需要函数的堆栈槽(函数的堆栈帧的一部分),以便它可以重用其内存? .
By stack slot I mean a part of stack frame of a function, not necessarily a whole stack frame of a function and an example to clarify the matter is, suppose we have a function that has six integer variables defined in its scope, when it's time to use sixth variable in the function, fifth variable's become useless so compiler can use same memory block for fifth and sixth variables. 通过堆栈槽我是指函数的堆栈帧的一部分,不一定是函数的整个堆栈帧,并且澄清问题的一个例子是,假设我们有一个函数,在其作用域中定义了六个整数变量,当它的时间在函数中使用第六个变量,第五个变量变得无用,因此编译器可以对第五个和第六个变量使用相同的内存块。
any information on this subject is appreciated. 有关此主题的任何信息表示赞赏。

EDIT: I interpreted the question to mean, "how does the compiler reuse a particular memory word in the stack?" 编辑:我将这个问题解释为“编译器如何重用堆栈中的特定内存字?” Most of the following answers that question, and a note a the end answers the question, "how does the compiler reuse all the stack space needed by a function?". 下面的大多数答案都提出了问题,并且结尾的一个注释回答了问题,“编译器如何重用函数所需的所有堆栈空间?”。

Most compilers don't assign stack slots first. 大多数编译器不首先分配堆栈槽。 Rather, what they do, for each function body, is treat each update to a variable, and all accesses to that variable that can see that particular assignment, as a so-called variable lifetime . 相反,对于每个函数体,它们所做的是将变量的每个更新处理,以及对可以看到特定赋值的该变量的所有访问,作为所谓的变量生存期 A variable which is assigned multiple times will thus cause the compiler to create multiple lifetimes. 因此,多次分配的变量将使编译器创建多个生命周期。

(There are complications with this idea that occur when multiple assignments can reach an access through different control paths; this is solved by using a clever enhancement to this idea called static single assignment , which I'm not going to discuss here). (当多个分配可以通过不同的控制路径访问时,会出现这种想法的复杂性;这可以通过对这个称为静态单一赋值的思想进行巧妙的增强来解决,我在此不再讨论)。

At any point in the code, there are a set of variable lifetimes that are valid; 在代码中的任何一点,都有一组有效的变量生命周期; as you choose differnt code points, you have different valid variable lifetimes. 当您选择不同的代码点时,您将拥有不同的有效变量生命周期。 The compiler's actual problem is to assign different registers or stack slots of each of the lifetimes. 编译器的实际问题是为每个生命周期分配不同的寄存器或堆栈槽。 One can think of this as a graph-coloring problem: each lifetime is a node, and if two lifetimes can overlap at a point in the code, there is an "interference" arc from that node to the other node representing the other lifetime. 人们可以将此视为图形着色问题:每个生命周期都是一个节点,如果两个生命周期可以在代码中的某个点重叠,则从该节点到代表另一个生命周期的另一个节点存在“干扰”弧。 You can color the graph (or equivalently use numbers instead of colors), such that no two nodes connected by an interference arc have the same color (number); 您可以为图形着色(或等效地使用数字而不是颜色),这样干扰弧连接的两个节点就不会有相同的颜色(数字); you may have to use arbitarily large numbers to do this, but for most functions the numbers don't have to be very large. 您可能必须使用任意大数来执行此操作,但对于大多数函数,数字不必非常大。 If you do this, the colors (numbers) will tell you a safe stack slot to use for the assigned value of the particular variable lifetime. 如果这样做,颜色(数字)将告诉您一个安全的堆栈槽用于特定变量生命周期的指定值。 (This idea is normally used in roughly two phases: once to allocate registers, and once to allocate stack slots for those lifetimes that don't fit into the registers). (这个想法通常大致分两个阶段使用:一次分配寄存器,一次为那些不适合寄存器的生命周期分配堆栈槽)。

By determining the largest number used as a color on the graph, the compiler knows how many slots are needed in the worst case, and can reserve that much storage at function entry time. 通过确定在图表上用作颜色的最大数字,编译器知道在最坏情况下需要多少个插槽,并且可以在功能进入时保留那么多存储器。

There's lots of complications: different values take different amounts of space, etc., but the basic idea is here. 有很多并发症:不同的值占用不同的空间等,但基本的想法就在这里。 Not all compilers use the graph coloring technique, but almost all of them figure out how to assign stack slots and registers in a way to avoid the implied interference. 并非所有编译器都使用图形着色技术,但几乎所有编译器都会弄清楚如何以避免隐含干扰的方式分配堆栈插槽和寄存器。 And thus they know stack slot numbers and the size of the stack frame. 因此,他们知道堆栈槽号和堆栈帧的大小。

EDIT... while typing, it appears that the question has been interpreted as "when does the stack frame for a function vanish"? 编辑...在键入时,似乎问题被解释为“函数的堆栈帧什么时候消失”? The answer is, at function exit. 答案是,在功能退出时。 The compiler already knows how big it is. 编译器已经知道它有多大。 It has no need to push or pop onto the stack during function execution; 它不需要在函数执行期间推送或弹出堆栈; it knows where to put everything based on the stack slot numbering determined by the graph coloring. 它根据图形着色确定的堆栈槽编号知道放置所有内容的位置。

The easy part is: When a function exits, all local variables of that function are released. 简单的部分是:当一个函数退出时,该函数的所有局部变量都被释放。 Thus, function exit indicates that the whole stack frame can be freed. 因此,函数exit表示可以释放整个堆栈帧。 That's a no-brainer, though, and you wouldn't have mentioned "optimizing compiler" if that's what you were after. 不过,这是一个不费吹灰之力,如果那就是你所追求的,你就不会提到“优化编译器”。

In theory, a compiler can do flow analysis on a function, find out which chunks of memory are used at what time, and perhaps even re-order stack allocation based on the order in which variables become available. 理论上,编译器可以对函数进行流分析,找出在什么时间使用哪些内存块,甚至可以根据变量可用的顺序重新排序堆栈分配。 Then, if new automatic variables are introduced somewhere in the middle of the function or other scope nested within the function (rather than at its beginning), those recently freed slots could be re-used. 然后,如果在函数中间某处引入新的自动变量或嵌套在函数内的其他范围(而不是在其开头),则可以重新使用那些最近释放的时隙。

In practice, this sounds like a lot of spinning gears, and I suspect that stack is simply allocated whenever variables come in scope and popped off en block by decrementing the stack pointer when the scope finishes. 在实践中,这听起来像很多旋转齿轮,我怀疑只要变量进入范围就会分配堆栈,并在范围结束时通过递减堆栈指针来弹出块。 But I admit I'm no expert on this topic. 但我承认我不是这个主题的专家。 Someone with more authoritative knowledge may come along and correct me. 具有更多权威知识的人可能会出现并纠正我。

If I understand the question correctly, this is about call chaining, ie invoking function from function without allocating new stack frame. 如果我正确理解了这个问题,那就是调用链,即从函数调用函数而不分配新的堆栈帧。

This is possible when the call can be transformed into tail call - the last op before return. 当调用可以转换为尾调用时,这是可能的 - 返回前的最后一个操作。 This way all local variables (stack) are already out of scope, so the space can be reused. 这样,所有局部变量(堆栈)都已超出范围,因此可以重用该空间。 Compiler then generates a jump instead of call instruction. 编译器然后生成jump而不是call指令。 The original return return address is still at the proper place on the stack. 原始返回地址仍在堆栈上的适当位置。

I probably glossed over lots of details here, but that's the idea. 我可能在这里掩饰了很多细节,但那就是这个想法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM