@@ -8,7 +8,7 @@ const Program = @import("program.zig").Program;
88
99pub const VmError = error {
1010 DivisionByZero ,
11- TypeError , // wront types for arithmetic/negate/table
11+ TypeError , // wrong types for arithmetic/negate/table
1212 UndefinedVariable ,
1313 ArityMismatch ,
1414 NotCallable , // Not a function/method that we can call
@@ -27,8 +27,15 @@ pub const Vm = struct {
2727 // Program containing the Chunk + VM tables
2828 program : * const Program ,
2929
30+ // Use Arrays rather than ArrayLists for performance.
31+ // The size is fixed anyway, so we don't need to worry about resizing.
32+ // ArrayList data is contiguous but lives on the heap behind a pointer,
33+ // meaning that each access requires a pointer indirection.
34+ // With an Array, the value is just an arithmetic offset off the struct base pointer.
35+
3036 // Value stack
31- stack : std .ArrayList (Value ),
37+ stack : [MAX_STACK ]Value ,
38+ stack_top : usize ,
3239
3340 // Call frame stack
3441 frames : [MAX_FRAMES ]CallFrame ,
@@ -45,7 +52,8 @@ pub const Vm = struct {
4552 pub fn init (program : * const Program , ctx : RuntimeContext , allocator : std.mem.Allocator ) Vm {
4653 return .{
4754 .program = program ,
48- .stack = .{},
55+ .stack = undefined ,
56+ .stack_top = 0 ,
4957 .frames = undefined ,
5058 .frame_count = 0 ,
5159 .globals = .{},
@@ -55,15 +63,13 @@ pub const Vm = struct {
5563 }
5664
5765 pub fn deinit (self : * Vm ) void {
58- self .stack .deinit (self .allocator );
5966 self .globals .deinit (self .allocator );
6067 }
6168
6269 pub fn run (self : * Vm ) ! Value {
6370 // Setup initial frame for top-level
6471 self .frames [0 ] = .{ .chunk = & self .program .chunk , .ip = 0 , .base_slot = 0 };
6572 self .frame_count = 1 ;
66- try self .stack .ensureTotalCapacity (self .allocator , MAX_STACK );
6773 return self .execute ();
6874 }
6975
@@ -152,18 +158,18 @@ pub const Vm = struct {
152158 const name = frame .chunk .constants .items [idx ].string ;
153159
154160 if (self .globals .getPtr (name )) | ptr | {
155- ptr .* = self .stack . getLast () ;
161+ ptr .* = self .stack [ self . stack_top - 1 ] ;
156162 } else {
157163 return error .UndefinedVariable ;
158164 }
159165 },
160166 .get_local = > {
161167 const idx = frame .base_slot + readU16 (frame .chunk , & frame .ip );
162- self .push (self .stack . items [idx ]);
168+ self .push (self .stack [idx ]);
163169 },
164170 .set_local = > {
165171 const idx = frame .base_slot + readU16 (frame .chunk , & frame .ip );
166- self .stack . items [idx ] = self .stack . getLast () ;
172+ self .stack [idx ] = self .stack [ self . stack_top - 1 ] ;
167173 },
168174 .jump = > frame .ip = readU16 (frame .chunk , & frame .ip ),
169175 .jump_if_false = > {
@@ -179,13 +185,13 @@ pub const Vm = struct {
179185 .loop = > frame .ip = readU16 (frame .chunk , & frame .ip ),
180186 .@"return" = > {
181187 // Pop the return value (or unit if stack is at base)
182- const ret_value = if (self .stack . items . len > frame .base_slot )
188+ const ret_value = if (self .stack_top > frame .base_slot )
183189 self .pop ()
184190 else
185191 Value .unit ;
186192
187193 // Remove all locals of the current stack frame
188- self .stack . shrinkRetainingCapacity ( frame .base_slot ) ;
194+ self .stack_top = frame .base_slot ;
189195 self .frame_count -= 1 ;
190196
191197 // Top-level frame, return the value
@@ -203,8 +209,8 @@ pub const Vm = struct {
203209 const arity = readU8 (frame .chunk , & frame .ip );
204210
205211 // Peak the callee to check if we can cast it to *FnObject
206- const base_slot = self .stack . items . len - 1 - arity ;
207- const callee = self .stack . items [base_slot ];
212+ const base_slot = self .stack_top - 1 - arity ;
213+ const callee = self .stack [base_slot ];
208214
209215 switch (callee ) {
210216 .function = > {
@@ -225,11 +231,11 @@ pub const Vm = struct {
225231 },
226232 .native = > {
227233 // Slice of the fn args
228- const args = self .stack . items [base_slot + 1 .. base_slot + 1 + arity ];
234+ const args = self .stack [base_slot + 1 .. base_slot + 1 + arity ];
229235
230236 // Call builtin function, remove the args and push the return value
231237 const ret_value = callee .native .func (args , self .ctx );
232- self .stack . shrinkRetainingCapacity ( base_slot ) ;
238+ self .stack_top = base_slot ;
233239 self .push (ret_value );
234240 },
235241 else = > return error .NotCallable ,
@@ -245,14 +251,15 @@ pub const Vm = struct {
245251
246252 // Pushes the value onto the stack
247253 fn push (self : * Vm , value : Value ) void {
248- self .stack .appendAssumeCapacity (value );
254+ self .stack [self .stack_top ] = value ;
255+ self .stack_top += 1 ;
249256 }
250257
251258 // Pops the value from the stack
252259 fn pop (self : * Vm ) Value {
253- std .debug .assert (self .stack . items . len > 0 );
254- // Unwrap the nullable or crash. Life is hard.
255- return self .stack . pop () .? ;
260+ std .debug .assert (self .stack_top > 0 );
261+ self . stack_top -= 1 ;
262+ return self .stack [ self . stack_top ] ;
256263 }
257264
258265 // Read a 8-bit value from the code
0 commit comments