implementing closures (ch25)

This commit is contained in:
Patrick MARIE 2024-08-29 10:54:03 +02:00
parent 7ed6cf6dcc
commit 53d5cca124
12 changed files with 376 additions and 33 deletions

View File

@ -0,0 +1,13 @@
// this program should print "outer"; without proper closure support, it shows "global".
var x = "global";
fun outer() {
var x = "outer";
fun inner() {
print x;
}
inner();
}
outer();

View File

@ -0,0 +1,22 @@
fun makeClosure() {
var local = "local";
fun closure() {
print local;
}
return closure;
}
var closure = makeClosure();
closure();
fun makeClosure2(value) {
fun closure() {
print value;
}
return closure;
}
var doughnut = makeClosure("doughnut");
var bagel = makeClosure("bagel");
doughnut();
bagel();

View File

@ -0,0 +1,11 @@
fun outer() {
var a = 1;
var b = 2;
fun middle() {
var c = 3;
var d = 4;
fun inner() {
print a + c + b + d;
}
}
}

View File

@ -0,0 +1,8 @@
fun outer() {
var x = "outside";
fun inner() {
print x;
}
inner();
}
outer();

View File

@ -0,0 +1,10 @@
fun outer() {
var x = "outside";
fun inner() {
print x;
}
return inner;
}
var closure = outer();
closure();

View File

@ -0,0 +1,22 @@
var globalSet;
var globalGet;
fun main() {
var a = "initial";
fun set() {
a = "updated";
}
fun get() {
print a;
}
globalSet = set;
globalGet = get;
}
main();
globalSet();
globalGet();

View File

@ -80,6 +80,7 @@ pub const Chunk = struct {
} }
pub fn dissassemble_instruction(self: Chunk, offset: usize) usize { pub fn dissassemble_instruction(self: Chunk, offset: usize) usize {
var current_offset = offset;
debug.print("{d:0>4} ", .{offset}); debug.print("{d:0>4} ", .{offset});
if (offset > 0 and self.lines[offset] == self.lines[offset - 1]) { if (offset > 0 and self.lines[offset] == self.lines[offset - 1]) {
@ -116,6 +117,32 @@ pub const Chunk = struct {
@intFromEnum(OpCode.OP_JUMP_IF_FALSE) => return utils.jump_instruction("OP_JUMP_IF_FALSE", 1, self, offset), @intFromEnum(OpCode.OP_JUMP_IF_FALSE) => return utils.jump_instruction("OP_JUMP_IF_FALSE", 1, self, offset),
@intFromEnum(OpCode.OP_LOOP) => return utils.jump_instruction("OP_LOOP", -1, self, offset), @intFromEnum(OpCode.OP_LOOP) => return utils.jump_instruction("OP_LOOP", -1, self, offset),
@intFromEnum(OpCode.OP_CALL) => return utils.byte_instruction("OP_CALL", self, offset), @intFromEnum(OpCode.OP_CALL) => return utils.byte_instruction("OP_CALL", self, offset),
@intFromEnum(OpCode.OP_CLOSURE) => {
current_offset += 1;
const constant = self.code[current_offset];
current_offset += 1;
debug.print("{s:<16} {d:0>4} ", .{ "OP_CLOSURE", constant });
self.constants.values[constant].print();
debug.print("\n", .{});
const function = self.constants.values[constant].as_obj().as_function();
for (0..function.upvalue_count) |j| {
_ = j;
const is_local_str = switch (self.code[current_offset]) {
1 => "local",
else => "upvalue",
};
current_offset += 1;
const index = self.code[current_offset];
current_offset += 1;
debug.print("{d:0>4} | {s:<19} {s} {d}\n", .{ current_offset - 2, "", is_local_str, index });
}
return current_offset;
},
@intFromEnum(OpCode.OP_GET_UPVALUE) => return utils.byte_instruction("OP_GET_UPVALUE", self, offset),
@intFromEnum(OpCode.OP_SET_UPVALUE) => return utils.byte_instruction("OP_SET_UPVALUE", self, offset),
@intFromEnum(OpCode.OP_CLOSE_UPVALUE) => return utils.simple_instruction("OP_CLOSE_UPVALUE", offset),
else => { else => {
debug.print("unknown opcode {d}\n", .{instruction}); debug.print("unknown opcode {d}\n", .{instruction});
return offset + 1; return offset + 1;

View File

@ -340,24 +340,27 @@ const Parser = struct {
fn named_variable(self: *Parser, token: Token, can_assign: bool) ParsingError!void { fn named_variable(self: *Parser, token: Token, can_assign: bool) ParsingError!void {
var get_op: OpCode = OpCode.OP_GET_LOCAL; var get_op: OpCode = OpCode.OP_GET_LOCAL;
var set_op: OpCode = OpCode.OP_SET_LOCAL; var set_op: OpCode = OpCode.OP_SET_LOCAL;
var has_local = true;
var constant = self.resolve_local(token) catch blk: { var arg = self.resolve_local(self.compiler, token);
has_local = false; const upvalue_arg = self.resolve_upvalue(self.compiler, token);
break :blk 0; if (arg != -1) {
}; get_op = OpCode.OP_GET_LOCAL;
set_op = OpCode.OP_SET_LOCAL;
if (!has_local) { } else if (upvalue_arg != -1) {
constant = try self.identifier_constant(token); get_op = OpCode.OP_GET_UPVALUE;
set_op = OpCode.OP_SET_UPVALUE;
arg = upvalue_arg;
} else {
arg = try self.identifier_constant(token);
get_op = OpCode.OP_GET_GLOBAL; get_op = OpCode.OP_GET_GLOBAL;
set_op = OpCode.OP_SET_GLOBAL; set_op = OpCode.OP_SET_GLOBAL;
} }
if (can_assign and self.match(TokenType.EQUAL)) { if (can_assign and self.match(TokenType.EQUAL)) {
try self.expression(); try self.expression();
try self.emit_bytes(@intFromEnum(set_op), constant); try self.emit_bytes(@intFromEnum(set_op), @intCast(arg));
} else { } else {
try self.emit_bytes(@intFromEnum(get_op), constant); try self.emit_bytes(@intFromEnum(get_op), @intCast(arg));
} }
} }
@ -538,7 +541,11 @@ const Parser = struct {
self.compiler.scope_depth -= 1; self.compiler.scope_depth -= 1;
while (self.compiler.local_count > 0 and self.compiler.locals[self.compiler.local_count - 1].depth.? > self.compiler.scope_depth) { while (self.compiler.local_count > 0 and self.compiler.locals[self.compiler.local_count - 1].depth.? > self.compiler.scope_depth) {
if (self.compiler.locals[self.compiler.local_count - 1].is_captured) {
try self.emit_byte(@intFromEnum(OpCode.OP_CLOSE_UPVALUE));
} else {
try self.emit_byte(@intFromEnum(OpCode.OP_POP)); try self.emit_byte(@intFromEnum(OpCode.OP_POP));
}
self.compiler.local_count -= 1; self.compiler.local_count -= 1;
} }
} }
@ -554,17 +561,18 @@ const Parser = struct {
local.name = token; local.name = token;
local.depth = null; local.depth = null;
local.is_captured = false;
} }
fn resolve_local(self: *Parser, name: Token) !u8 { fn resolve_local(self: *Parser, compiler: *Compiler, name: Token) isize {
if (self.compiler.local_count == 0) { if (compiler.local_count == 0) {
return ParsingError.NotFound; return -1;
} }
var idx: u8 = @intCast(self.compiler.local_count - 1); var idx: u8 = @intCast(compiler.local_count - 1);
while (idx >= 0) { while (idx >= 0) {
const local = &self.compiler.locals[idx]; const local = &compiler.locals[idx];
if (identifiers_equals(local.name, name)) { if (identifiers_equals(local.name, name)) {
if (local.depth == null) { if (local.depth == null) {
@ -579,7 +587,47 @@ const Parser = struct {
idx -= 1; idx -= 1;
} }
return ParsingError.NotFound; return -1;
}
fn resolve_upvalue(self: *Parser, compiler: *Compiler, name: Token) isize {
if (compiler.enclosing == null) {
return -1;
}
const local = self.resolve_local(compiler.enclosing.?, name);
if (local != -1) {
compiler.enclosing.?.locals[@intCast(local)].is_captured = true;
return @intCast(self.add_upvalue(compiler, @intCast(local), true));
}
const upvalue = self.resolve_upvalue(compiler.enclosing.?, name);
if (upvalue != -1) {
return @intCast(self.add_upvalue(compiler, @intCast(upvalue), false));
}
return -1;
}
fn add_upvalue(self: *Parser, compiler: *Compiler, index: u8, is_local: bool) usize {
const upvalue_count = compiler.function.upvalue_count;
for (0..upvalue_count) |i| {
const upvalue: *Upvalue = &compiler.upvalues[i];
if (upvalue.index == index and upvalue.is_local == is_local) {
return i;
}
}
if (upvalue_count == constants.UINT8_COUNT) {
self.error_msg("Too many closure variables in function.");
return 0;
}
compiler.upvalues[upvalue_count].is_local = is_local;
compiler.upvalues[upvalue_count].index = index;
compiler.function.upvalue_count += 1;
return compiler.function.upvalue_count - 1;
} }
fn if_statement(self: *Parser) !void { fn if_statement(self: *Parser) !void {
@ -759,7 +807,16 @@ const Parser = struct {
const obj_function = try self.end_parser(); const obj_function = try self.end_parser();
const constant = try self.make_constant(Value.obj_val(&obj_function.obj)); const constant = try self.make_constant(Value.obj_val(&obj_function.obj));
try self.emit_bytes(@intFromEnum(OpCode.OP_CONSTANT), constant); try self.emit_bytes(@intFromEnum(OpCode.OP_CLOSURE), constant);
for (0..obj_function.upvalue_count) |i| {
if (compiler.upvalues[i].is_local) {
try self.emit_byte(1);
} else {
try self.emit_byte(0);
}
try self.emit_byte(@intCast(compiler.upvalues[i].index));
}
} }
fn call(self: *Parser, can_assign: bool) ParsingError!void { fn call(self: *Parser, can_assign: bool) ParsingError!void {
@ -819,6 +876,7 @@ const Compiler = struct {
locals: [constants.UINT8_COUNT]Local, locals: [constants.UINT8_COUNT]Local,
local_count: usize, local_count: usize,
upvalues: [constants.UINT8_COUNT]Upvalue,
scope_depth: usize, scope_depth: usize,
fn new(allocator: std.mem.Allocator, enclosing: ?*Compiler, function_type: FunctionType) Compiler { fn new(allocator: std.mem.Allocator, enclosing: ?*Compiler, function_type: FunctionType) Compiler {
@ -827,6 +885,7 @@ const Compiler = struct {
var compiler = Compiler{ var compiler = Compiler{
.locals = undefined, .locals = undefined,
.local_count = 0, .local_count = 0,
.upvalues = undefined,
.scope_depth = 0, .scope_depth = 0,
.function = obj_function, .function = obj_function,
.function_type = function_type, .function_type = function_type,
@ -840,6 +899,7 @@ const Compiler = struct {
.length = 0, .length = 0,
.line = 0, .line = 0,
}; };
compiler.locals[0].is_captured = false;
compiler.local_count += 1; compiler.local_count += 1;
@ -855,6 +915,12 @@ const Compiler = struct {
const Local = struct { const Local = struct {
name: Token, name: Token,
depth: ?usize, depth: ?usize,
is_captured: bool,
};
const Upvalue = struct {
index: usize,
is_local: bool,
}; };
pub fn compile(vm: *VM, contents: []const u8) !?*Obj.Function { pub fn compile(vm: *VM, contents: []const u8) !?*Obj.Function {

View File

@ -10,7 +10,7 @@ pub const UINT8_COUNT = UINT8_MAX + 1;
pub const FRAMES_MAX = 64; pub const FRAMES_MAX = 64;
pub const STACK_MAX = (FRAMES_MAX * UINT8_MAX); pub const STACK_MAX = (FRAMES_MAX * UINT8_MAX);
pub const DEBUG_PRINT_CODE = false; pub const DEBUG_PRINT_CODE = true;
pub const DEBUG_TRACE_EXECUTION = false; pub const DEBUG_TRACE_EXECUTION = true;
pub const DEBUG_PRINT_INTERNAL_STRINGS = false; pub const DEBUG_PRINT_INTERNAL_STRINGS = false;
pub const DEBUG_PRINT_GLOBALS = false; pub const DEBUG_PRINT_GLOBALS = false;

View File

@ -12,6 +12,8 @@ pub const ObjType = enum {
String, String,
Function, Function,
Native, Native,
Closure,
Upvalue,
}; };
pub const NativeFn = *const fn (vm: *VM, arg_count: usize, args: []Value) Value; pub const NativeFn = *const fn (vm: *VM, arg_count: usize, args: []Value) Value;
@ -49,6 +51,7 @@ pub const Obj = struct {
pub const Function = struct { pub const Function = struct {
obj: Obj, obj: Obj,
arity: usize, arity: usize,
upvalue_count: usize,
chunk: *Chunk, chunk: *Chunk,
name: ?*Obj.String, name: ?*Obj.String,
@ -61,6 +64,7 @@ pub const Obj = struct {
const function_obj = allocator.create(Function) catch unreachable; const function_obj = allocator.create(Function) catch unreachable;
function_obj.obj = obj; function_obj.obj = obj;
function_obj.arity = 0; function_obj.arity = 0;
function_obj.upvalue_count = 0;
function_obj.chunk = Chunk.new(allocator); function_obj.chunk = Chunk.new(allocator);
function_obj.name = null; function_obj.name = null;
@ -95,6 +99,64 @@ pub const Obj = struct {
} }
}; };
pub const Closure = struct {
obj: Obj,
function: *Obj.Function,
upvalues: []?*Obj.Upvalue,
upvalue_count: usize,
pub fn new(allocator: std.mem.Allocator, function: *Obj.Function) *Closure {
const obj = Obj{
.kind = ObjType.Closure,
.allocator = allocator,
};
const closure_obj = allocator.create(Closure) catch unreachable;
closure_obj.obj = obj;
closure_obj.function = function;
closure_obj.upvalue_count = function.upvalue_count;
closure_obj.upvalues = allocator.alloc(?*Obj.Upvalue, function.upvalue_count) catch unreachable;
for (0..function.upvalue_count) |i| {
closure_obj.upvalues[i] = null;
}
return closure_obj;
}
pub fn destroy(self: *Closure) void {
self.obj.allocator.free(self.upvalues);
self.obj.allocator.destroy(self);
}
};
pub const Upvalue = struct {
obj: Obj,
location: *Value,
next: ?*Obj.Upvalue,
closed: Value,
pub fn new(allocator: std.mem.Allocator, slot: *Value) *Upvalue {
const obj = Obj{
.kind = ObjType.Upvalue,
.allocator = allocator,
};
const upvalue_obj = allocator.create(Upvalue) catch unreachable;
upvalue_obj.obj = obj;
upvalue_obj.location = slot;
upvalue_obj.next = null;
upvalue_obj.closed = Value.nil_val();
return upvalue_obj;
}
pub fn destroy(self: *Upvalue) void {
self.obj.allocator.destroy(self);
}
};
pub fn is_type(self: *Obj, kind: ObjType) bool { pub fn is_type(self: *Obj, kind: ObjType) bool {
return self.kind == kind; return self.kind == kind;
} }
@ -111,6 +173,14 @@ pub const Obj = struct {
return self.is_type(ObjType.Native); return self.is_type(ObjType.Native);
} }
pub fn is_closure(self: *Obj) bool {
return self.is_type(ObjType.Closure);
}
pub fn is_upvalue(self: *Obj) bool {
return self.is_type(ObjType.Upvalue);
}
pub fn print(self: *Obj) void { pub fn print(self: *Obj) void {
switch (self.kind) { switch (self.kind) {
ObjType.String => { ObjType.String => {
@ -126,9 +196,15 @@ pub const Obj = struct {
} }
}, },
ObjType.Native => { ObjType.Native => {
// const obj = self.as_native();
debug.print("<native fn>", .{}); debug.print("<native fn>", .{});
}, },
ObjType.Closure => {
const obj = self.as_closure();
obj.function.obj.print();
},
ObjType.Upvalue => {
debug.print("upvalue", .{});
},
} }
} }
@ -146,6 +222,14 @@ pub const Obj = struct {
const obj: *Native = @fieldParentPtr("obj", self); const obj: *Native = @fieldParentPtr("obj", self);
obj.destroy(); obj.destroy();
}, },
ObjType.Closure => {
const obj: *Closure = @fieldParentPtr("obj", self);
obj.destroy();
},
ObjType.Upvalue => {
const obj: *Upvalue = @fieldParentPtr("obj", self);
obj.destroy();
},
} }
} }
@ -163,4 +247,9 @@ pub const Obj = struct {
std.debug.assert(self.kind == ObjType.Native); std.debug.assert(self.kind == ObjType.Native);
return @fieldParentPtr("obj", self); return @fieldParentPtr("obj", self);
} }
pub fn as_closure(self: *Obj) *Closure {
std.debug.assert(self.kind == ObjType.Closure);
return @fieldParentPtr("obj", self);
}
}; };

View File

@ -4,11 +4,13 @@ pub const OpCode = enum(u8) {
OP_TRUE, OP_TRUE,
OP_FALSE, OP_FALSE,
OP_POP, OP_POP,
OP_GET_GLOBAL,
OP_DEFINE_GLOBAL, OP_DEFINE_GLOBAL,
OP_GET_GLOBAL,
OP_SET_GLOBAL, OP_SET_GLOBAL,
OP_GET_LOCAL, OP_GET_LOCAL,
OP_SET_LOCAL, OP_SET_LOCAL,
OP_GET_UPVALUE,
OP_SET_UPVALUE,
OP_EQUAL, OP_EQUAL,
OP_GREATER, OP_GREATER,
OP_LESS, OP_LESS,
@ -23,5 +25,7 @@ pub const OpCode = enum(u8) {
OP_JUMP_IF_FALSE, OP_JUMP_IF_FALSE,
OP_LOOP, OP_LOOP,
OP_CALL, OP_CALL,
OP_CLOSURE,
OP_CLOSE_UPVALUE,
OP_RETURN, OP_RETURN,
}; };

View File

@ -26,7 +26,7 @@ pub const InterpretResult = enum {
}; };
pub const CallFrame = struct { pub const CallFrame = struct {
function: *Obj.Function, closure: *Obj.Closure,
ip: usize, ip: usize,
// pointer to stack index provided to this frame // pointer to stack index provided to this frame
slots_idx: usize, slots_idx: usize,
@ -43,6 +43,7 @@ pub const VM = struct {
globals: Table, globals: Table,
frames: [constants.FRAMES_MAX]CallFrame, frames: [constants.FRAMES_MAX]CallFrame,
frame_count: usize, frame_count: usize,
open_upvalues: ?*Obj.Upvalue,
pub fn new(allocator: Allocator) VM { pub fn new(allocator: Allocator) VM {
return VM{ return VM{
@ -54,6 +55,7 @@ pub const VM = struct {
.globals = Table.new(allocator), .globals = Table.new(allocator),
.frames = undefined, .frames = undefined,
.frame_count = 0, .frame_count = 0,
.open_upvalues = null,
}; };
} }
@ -75,7 +77,7 @@ pub const VM = struct {
} }
inline fn current_chunk(self: *VM) *Chunk { inline fn current_chunk(self: *VM) *Chunk {
return self.frames[self.frame_count - 1].function.chunk; return self.frames[self.frame_count - 1].closure.function.chunk;
} }
inline fn current_frame(self: *VM) *CallFrame { inline fn current_frame(self: *VM) *CallFrame {
@ -90,7 +92,10 @@ pub const VM = struct {
defer function.?.destroy(); defer function.?.destroy();
_ = try self.push(Value.obj_val(&function.?.obj)); _ = try self.push(Value.obj_val(&function.?.obj));
_ = self.call(function.?, 0); const closure: *Obj.Closure = Obj.Closure.new(self.allocator, function.?);
_ = self.pop();
_ = try self.push(Value.obj_val(&closure.obj));
_ = self.call(closure, 0);
return try self.run(); return try self.run();
} }
@ -149,6 +154,7 @@ pub const VM = struct {
}, },
@intFromEnum(OpCode.OP_RETURN) => { @intFromEnum(OpCode.OP_RETURN) => {
const result = self.pop(); const result = self.pop();
self.close_upvalues(&self.stack[self.current_frame().slots_idx]);
self.frame_count -= 1; self.frame_count -= 1;
if (self.frame_count == 0) { if (self.frame_count == 0) {
_ = self.pop(); _ = self.pop();
@ -221,6 +227,33 @@ pub const VM = struct {
return InterpretResult.RUNTIME_ERROR; return InterpretResult.RUNTIME_ERROR;
} }
}, },
@intFromEnum(OpCode.OP_CLOSURE) => {
const function = self.read_constant().as_obj().as_function();
const closure = Obj.Closure.new(self.allocator, function);
_ = try self.push(Value.obj_val(&closure.obj));
for (0..closure.upvalue_count) |i| {
const is_local = self.read_byte();
const index = self.read_byte();
if (is_local == 1) {
const value_idx = self.current_frame().slots_idx + index;
closure.upvalues[i] = self.capture_upvalue(&self.stack[value_idx]);
} else {
closure.upvalues[i] = self.current_frame().closure.upvalues[index];
}
}
},
@intFromEnum(OpCode.OP_GET_UPVALUE) => {
const slot = self.read_byte();
try self.push(self.current_frame().closure.upvalues[slot].?.location.*);
},
@intFromEnum(OpCode.OP_SET_UPVALUE) => {
const slot = self.read_byte();
self.current_frame().closure.upvalues[slot].?.location = @constCast(&self.peek(0));
},
@intFromEnum(OpCode.OP_CLOSE_UPVALUE) => {
self.close_upvalues(&self.stack[self.stack_top - 1]);
_ = self.pop();
},
else => { else => {
debug.print("Invalid instruction: {d}\n", .{instruction}); debug.print("Invalid instruction: {d}\n", .{instruction});
return InterpretResult.RUNTIME_ERROR; return InterpretResult.RUNTIME_ERROR;
@ -314,15 +347,15 @@ pub const VM = struct {
while (true) { while (true) {
const frame = self.frames[frame_idx]; const frame = self.frames[frame_idx];
const function = frame.function; const closure = frame.closure;
const instruction = frame.ip; const instruction = frame.ip;
debug.print("[line {d}] in ", .{function.chunk.lines[instruction]}); debug.print("[line {d}] in ", .{closure.function.chunk.lines[instruction]});
if (function.name == null) { if (closure.function.name == null) {
debug.print("script\n", .{}); debug.print("script\n", .{});
} else { } else {
debug.print("{s}()\n", .{function.name.?.chars}); debug.print("{s}()\n", .{closure.function.name.?.chars});
} }
if (frame_idx == 0) { if (frame_idx == 0) {
@ -385,7 +418,7 @@ pub const VM = struct {
if (callee.is_obj()) { if (callee.is_obj()) {
switch (callee.as_obj().kind) { switch (callee.as_obj().kind) {
ObjType.Function => { ObjType.Function => {
return self.call(callee.as_obj().as_function(), arg_count); return self.call(callee.as_obj().as_closure(), arg_count);
}, },
ObjType.Native => { ObjType.Native => {
const native_obj: *Obj.Native = callee.as_obj().as_native(); const native_obj: *Obj.Native = callee.as_obj().as_native();
@ -398,6 +431,9 @@ pub const VM = struct {
_ = try self.push(value); _ = try self.push(value);
return true; return true;
}, },
ObjType.Closure => {
return self.call(callee.as_obj().as_closure(), arg_count);
},
else => {}, else => {},
} }
} }
@ -405,8 +441,8 @@ pub const VM = struct {
return false; return false;
} }
pub fn call(self: *VM, function: *Obj.Function, arg_count: usize) bool { pub fn call(self: *VM, closure: *Obj.Closure, arg_count: usize) bool {
if (arg_count != function.arity) { if (arg_count != closure.function.arity) {
self.runtime_error("Invalid argument count."); self.runtime_error("Invalid argument count.");
// runtimeError("Expected %d arguments but got %d.", function->arity, argCount); // runtimeError("Expected %d arguments but got %d.", function->arity, argCount);
return false; return false;
@ -420,7 +456,7 @@ pub const VM = struct {
const frame = &self.frames[self.frame_count]; const frame = &self.frames[self.frame_count];
self.frame_count += 1; self.frame_count += 1;
frame.function = function; frame.closure = closure;
frame.ip = 0; frame.ip = 0;
frame.slots_idx = self.stack_top - arg_count - 1; frame.slots_idx = self.stack_top - arg_count - 1;
@ -436,4 +472,39 @@ pub const VM = struct {
_ = self.pop(); _ = self.pop();
_ = self.pop(); _ = self.pop();
} }
fn capture_upvalue(self: *VM, local: *Value) *Obj.Upvalue {
var prev_upvalue: ?*Obj.Upvalue = null;
var upvalue: ?*Obj.Upvalue = self.open_upvalues;
while (upvalue != null and @intFromPtr(upvalue.?.location) > @intFromPtr(local)) {
prev_upvalue = upvalue;
upvalue = upvalue.?.next;
}
if (upvalue != null and upvalue.?.location == local) {
return upvalue.?;
}
const created_upvalue = Obj.Upvalue.new(self.allocator, local);
created_upvalue.next = upvalue;
if (prev_upvalue == null) {
self.open_upvalues = created_upvalue;
} else {
prev_upvalue.?.next = created_upvalue;
}
return created_upvalue;
}
fn close_upvalues(self: *VM, last: *Value) void {
while (self.open_upvalues != null and @intFromPtr(self.open_upvalues.?.location) >= @intFromPtr(last)) {
const upvalue = self.open_upvalues.?;
upvalue.closed = upvalue.location.*;
upvalue.location = &upvalue.closed;
self.open_upvalues = upvalue.next;
}
}
}; };