@@ -90,13 +90,68 @@ pub const Frame = struct {
9090 }
9191};
9292
93+ /// The VM operand stack has a much hotter append path than a general-purpose
94+ /// ArrayList: almost every bytecode instruction pushes at least one Value. The
95+ /// standard unmanaged append calls ensureTotalCapacity even when capacity is
96+ /// already available; profiles attributed roughly a third of arithmetic VM
97+ /// samples to that call/check boundary. Keep ArrayList growth semantics on the
98+ /// rare full-capacity path, but make the common append one inlined comparison
99+ /// and one store.
100+ const OperandStack = struct {
101+ items : []Value = &.{},
102+ capacity : usize = 0 ,
103+
104+ pub const empty : OperandStack = .{};
105+
106+ pub inline fn append (self : * OperandStack , allocator : std.mem.Allocator , item : Value ) std.mem.Allocator.Error ! void {
107+ if (self .items .len == self .capacity ) try self .grow (allocator );
108+ self .items .len += 1 ;
109+ self .items [self .items .len - 1 ] = item ;
110+ }
111+
112+ noinline fn grow (self : * OperandStack , allocator : std.mem.Allocator ) std.mem.Allocator.Error ! void {
113+ var list : std .ArrayListUnmanaged (Value ) = .{
114+ .items = self .items ,
115+ .capacity = self .capacity ,
116+ };
117+ try list .ensureTotalCapacity (allocator , self .items .len + 1 );
118+ self .items = list .items ;
119+ self .capacity = list .capacity ;
120+ }
121+
122+ pub inline fn pop (self : * OperandStack ) ? Value {
123+ if (self .items .len == 0 ) return null ;
124+ const item = self .items [self .items .len - 1 ];
125+ self .items .len -= 1 ;
126+ return item ;
127+ }
128+
129+ pub inline fn shrinkRetainingCapacity (self : * OperandStack , new_len : usize ) void {
130+ std .debug .assert (new_len <= self .items .len );
131+ self .items .len = new_len ;
132+ }
133+
134+ pub inline fn clearRetainingCapacity (self : * OperandStack ) void {
135+ self .items .len = 0 ;
136+ }
137+
138+ pub fn deinit (self : * OperandStack , allocator : std.mem.Allocator ) void {
139+ var list : std .ArrayListUnmanaged (Value ) = .{
140+ .items = self .items ,
141+ .capacity = self .capacity ,
142+ };
143+ list .deinit (allocator );
144+ self .* = .empty ;
145+ }
146+ };
147+
93148/// A resumable execution state: the operand stack, completion accumulator, and
94149/// instruction pointer. For a normal call this lives on the host stack and is
95150/// thrown away when `run` returns; for a generator it lives in the `Generator`
96151/// and persists across `yield`/resume, which is what makes suspension faithful
97152/// (the whole operand stack is saved, so a `yield` can sit mid-expression).
98153pub const Exec = struct {
99- stack : std . ArrayListUnmanaged ( Value ) = .empty ,
154+ stack : OperandStack = .empty ,
100155 acc : Value = Value .undef (),
101156 ip : usize = 0 ,
102157 /// The activation frame this Exec is running (null for env-mode bodies:
@@ -135,7 +190,7 @@ fn looksLikeCompletionKind(v: Value) bool {
135190 return n >= 0 and n <= @as (f64 , @floatFromInt (@intFromEnum (Completion .continue_ )));
136191}
137192
138- fn completionKindBelowTop (stack : * const std . ArrayListUnmanaged ( Value ) ) ? Completion {
193+ fn completionKindBelowTop (stack : * const OperandStack ) ? Completion {
139194 if (stack .items .len == 0 ) return null ;
140195 const v = stack .items [stack .items .len - 1 ];
141196 if (! looksLikeCompletionKind (v )) return null ;
0 commit comments