diff --git a/src/Ast.zig b/src/Ast.zig index 9cbba680..d6f19f87 100644 --- a/src/Ast.zig +++ b/src/Ast.zig @@ -6,7 +6,7 @@ const v = @import("value.zig"); const Value = v.Value; const FFI = @import("FFI.zig"); const Parser = @import("Parser.zig"); -const GarbageCollector = @import("memory.zig").GarbageCollector; +const GC = @import("GC.zig"); // TODO: cleanup Error sets! const Error = @import("Codegen.zig").Error; @@ -484,7 +484,7 @@ pub const Slice = struct { return ctx.result orelse false; } - fn binaryValue(self: Self.Slice, node: Node.Index, gc: *GarbageCollector) !?Value { + fn binaryValue(self: Self.Slice, node: Node.Index, gc: *GC) !?Value { const components = self.nodes.items(.components)[node].Binary; const left = try self.toValue(components.left, gc); @@ -702,7 +702,7 @@ pub const Slice = struct { } } - pub fn toValue(self: Self.Slice, node: Node.Index, gc: *GarbageCollector) Error!Value { + pub fn toValue(self: Self.Slice, node: Node.Index, gc: *GC) Error!Value { const value = &self.nodes.items(.value)[node]; if (value.* == null and try self.isConstant(gc.allocator, node)) { diff --git a/src/Codegen.zig b/src/Codegen.zig index 90bebc33..4c0e70da 100644 --- a/src/Codegen.zig +++ b/src/Codegen.zig @@ -10,7 +10,7 @@ const RunFlavor = vm.RunFlavor; const Value = @import("value.zig").Value; const Parser = @import("Parser.zig"); const Token = @import("Token.zig"); -const GarbageCollector = @import("memory.zig").GarbageCollector; +const GC = @import("GC.zig"); const Reporter = @import("Reporter.zig"); const BuildOptions = @import("build_options"); const JIT = if (!is_wasm) @import("Jit.zig") else void; @@ -60,7 +60,7 @@ const Breaks = std.ArrayList(Break); current: ?*Frame = null, ast: Ast.Slice = undefined, -gc: *GarbageCollector, +gc: *GC, flavor: RunFlavor, /// Jump to patch at end of current expression with a optional unwrapping in the middle of it opt_jumps: std.ArrayList(std.ArrayList(usize)) = .{}, @@ -137,7 +137,7 @@ const generators = [_]?NodeGen{ }; pub fn init( - gc: *GarbageCollector, + gc: *GC, parser: *Parser, flavor: RunFlavor, jit: ?*JIT, diff --git a/src/FFI.zig b/src/FFI.zig index c3eb1cd1..01ae8ffa 100644 --- a/src/FFI.zig +++ b/src/FFI.zig @@ -3,11 +3,11 @@ const Ast = std.zig.Ast; const BuzzAst = @import("Ast.zig"); const o = @import("obj.zig"); -const m = @import("memory.zig"); const v = @import("value.zig"); const Parser = @import("Parser.zig"); const ZigType = @import("zigtypes.zig").Type; const Reporter = @import("Reporter.zig"); +const GC = @import("GC.zig"); const Self = @This(); @@ -188,12 +188,12 @@ pub const State = struct { structs: std.StringHashMap(*Zdef), }; -gc: *m.GarbageCollector, +gc: *GC, reporter: Reporter, state: ?State = null, type_expr_cache: std.StringHashMap(?*Zdef), -pub fn init(gc: *m.GarbageCollector) Self { +pub fn init(gc: *GC) Self { return .{ .gc = gc, .reporter = .{ diff --git a/src/GC.zig b/src/GC.zig new file mode 100644 index 00000000..8b63283d --- /dev/null +++ b/src/GC.zig @@ -0,0 +1,1081 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const v = @import("vm.zig"); +const Value = @import("value.zig").Value; +const o = @import("obj.zig"); +const dumpStack = @import("disassembler.zig").dumpStack; +const BuildOptions = @import("build_options"); +const Token = @import("Token.zig"); +const buzz_api = @import("buzz_api.zig"); +const Reporter = @import("Reporter.zig"); +const is_wasm = builtin.cpu.arch.isWasm(); +const io = @import("io.zig"); +const TypeRegistry = @import("TypeRegistry.zig"); + +// Sticky Mark Bits Generational GC basic idea: +// 1. First GC: do a normal mark, don't clear `marked` at the end +// 2. Young GC: +// - Already marked objects are 'old', don't trace them (because all referenced object from it are also old and marked)and don't clear them +// - Write barrier: anytime an object is modified, mark it as 'dirty' and add it to the gray_stack so its traced +// - Old but dirty object is traced, then marked clean again (because any reference to 'young' objects will be 'old' after this gc sweep) +// 3. Old GC: +// - Trace only already marked objects +// 4. Full GC: +// - Unmark all objects +// - Do a mark and sweep +// +// For now we only have either young or old objects but we could improve on it with more categories with each its threshold +const GC = @This(); + +const Mode = enum { + Young, + // Old, + Full, +}; + +allocator: std.mem.Allocator, +strings: std.StringHashMapUnmanaged(*o.ObjString), +type_registry: TypeRegistry, +bytes_allocated: usize = 0, +// next_gc == next_full_gc at first so the first cycle is a full gc +next_gc: usize = if (builtin.mode == .Debug) 1024 else 1024 * BuildOptions.initial_gc, +next_full_gc: usize = if (builtin.mode == .Debug) 1024 else 1024 * BuildOptions.initial_gc, +last_gc: ?Mode = null, +objects: std.DoublyLinkedList = .{}, +gray_stack: std.ArrayList(*o.Obj), +active_vms: std.AutoHashMapUnmanaged(*v.VM, void), +// o.Obj being collected, useful to avoid setting object instance dirty while running its collector method +obj_collected: ?*o.Obj = null, + +debugger: ?Debugger, +where: ?Token = null, + +// Types we generaly don't wan't to ever be collected +objfiber_members: []?*o.ObjNative, +objfiber_memberDefs: []?*o.ObjTypeDef, +objpattern_members: []?*o.ObjNative, +objpattern_memberDefs: []?*o.ObjTypeDef, +objstring_members: []?*o.ObjNative, +objstring_memberDefs: []?*o.ObjTypeDef, +objrange_memberDefs: []?*o.ObjTypeDef, +objrange_members: []?*o.ObjNative, + +full_collection_count: usize = 0, +light_collection_count: usize = 0, +max_allocated: usize = 0, + +gc_time: usize = 0, + +pub fn init(allocator: std.mem.Allocator) !GC { + const self = GC{ + .allocator = allocator, + .strings = std.StringHashMapUnmanaged(*o.ObjString){}, + .type_registry = undefined, + .gray_stack = std.ArrayList(*o.Obj){}, + .active_vms = std.AutoHashMapUnmanaged(*v.VM, void){}, + .debugger = if (BuildOptions.gc_debug_access) Debugger.init(allocator) else null, + + .objfiber_members = try allocator.alloc(?*o.ObjNative, o.ObjFiber.members.len), + .objfiber_memberDefs = try allocator.alloc(?*o.ObjTypeDef, o.ObjFiber.members.len), + .objpattern_members = try allocator.alloc(?*o.ObjNative, o.ObjPattern.members.len), + .objpattern_memberDefs = try allocator.alloc(?*o.ObjTypeDef, o.ObjPattern.members.len), + .objstring_members = try allocator.alloc(?*o.ObjNative, o.ObjString.members.len), + .objstring_memberDefs = try allocator.alloc(?*o.ObjTypeDef, o.ObjString.members.len), + .objrange_members = try allocator.alloc(?*o.ObjNative, o.ObjRange.members.len), + .objrange_memberDefs = try allocator.alloc(?*o.ObjTypeDef, o.ObjRange.members.len), + }; + + for (0..o.ObjFiber.members.len) |i| { + self.objfiber_members[i] = null; + self.objfiber_memberDefs[i] = null; + } + + for (0..o.ObjPattern.members.len) |i| { + self.objpattern_members[i] = null; + self.objpattern_memberDefs[i] = null; + } + + for (0..o.ObjString.members.len) |i| { + self.objstring_members[i] = null; + self.objstring_memberDefs[i] = null; + } + + for (0..o.ObjRange.members.len) |i| { + self.objrange_members[i] = null; + self.objrange_memberDefs[i] = null; + } + + return self; +} + +pub fn registerVM(self: *GC, vm: *v.VM) !void { + try self.active_vms.put( + self.allocator, + vm, + {}, + ); +} + +pub fn unregisterVM(self: *GC, vm: *v.VM) void { + std.debug.assert(self.active_vms.remove(vm)); +} + +pub fn deinit(self: *GC) void { + self.gray_stack.deinit(self.allocator); + self.strings.deinit(self.allocator); + self.active_vms.deinit(self.allocator); + if (BuildOptions.gc_debug_access) { + self.debugger.?.deinit(); + } + + self.allocator.free(self.objfiber_members); + self.allocator.free(self.objfiber_memberDefs); + self.allocator.free(self.objpattern_members); + self.allocator.free(self.objpattern_memberDefs); + self.allocator.free(self.objstring_members); + self.allocator.free(self.objstring_memberDefs); + self.allocator.free(self.objrange_members); + self.allocator.free(self.objrange_memberDefs); +} + +pub fn allocate(self: *GC, comptime T: type) !*T { + var timer = if (!is_wasm) std.time.Timer.start() catch unreachable else {}; + + self.bytes_allocated += @sizeOf(T); + + if (self.bytes_allocated > self.max_allocated) { + self.max_allocated = self.bytes_allocated; + } + + if (self.bytes_allocated > self.next_gc and BuildOptions.gc) { + try self.collectGarbage(); + } + + if (BuildOptions.memory_limit != null and self.bytes_allocated > BuildOptions.memory_limit.?) { + return error.ReachedMaximumMemoryUsage; + } + + const allocated = try self.allocator.create(T); + + if (BuildOptions.gc_debug) { + io.print( + "Allocated @{} {} for {} (now {}/{})\n", + .{ + @intFromPtr(allocated), + T, + std.fmt.fmtIntSizeDec(@sizeOf(T)), + std.fmt.fmtIntSizeDec(self.bytes_allocated), + std.fmt.fmtIntSizeDec(self.next_gc), + }, + ); + } + + if (!is_wasm) { + self.gc_time += timer.read(); + } + return allocated; +} + +pub fn allocateMany(self: *GC, comptime T: type, count: usize) ![]T { + var timer = if (!is_wasm) + std.time.Timer.start() catch unreachable + else {}; + + self.bytes_allocated += (@sizeOf(T) * count); + + if (self.bytes_allocated > self.max_allocated) { + self.max_allocated = self.bytes_allocated; + } + + if (self.bytes_allocated > self.next_gc and BuildOptions.gc) { + try self.collectGarbage(); + } + + if (!is_wasm) { + self.gc_time += timer.read(); + } + return try self.allocator.alloc(T, count); +} + +pub fn allocateObject(self: *GC, comptime T: type, data: T) !*T { + // var before: usize = self.bytes_allocated; + + const obj: *T = try self.allocate(T); + obj.* = data; + + const object: *o.Obj = switch (T) { + o.ObjString => o.ObjString.toObj(obj), + o.ObjTypeDef => o.ObjTypeDef.toObj(obj), + o.ObjUpValue => o.ObjUpValue.toObj(obj), + o.ObjClosure => o.ObjClosure.toObj(obj), + o.ObjFunction => o.ObjFunction.toObj(obj), + o.ObjObjectInstance => o.ObjObjectInstance.toObj(obj), + o.ObjObject => o.ObjObject.toObj(obj), + o.ObjList => o.ObjList.toObj(obj), + o.ObjMap => o.ObjMap.toObj(obj), + o.ObjEnum => o.ObjEnum.toObj(obj), + o.ObjEnumInstance => o.ObjEnumInstance.toObj(obj), + o.ObjBoundMethod => o.ObjBoundMethod.toObj(obj), + o.ObjNative => o.ObjNative.toObj(obj), + o.ObjUserData => o.ObjUserData.toObj(obj), + o.ObjPattern => o.ObjPattern.toObj(obj), + o.ObjFiber => o.ObjFiber.toObj(obj), + o.ObjForeignContainer => o.ObjForeignContainer.toObj(obj), + o.ObjRange => o.ObjRange.toObj(obj), + else => {}, + }; + + // if (BuildOptions.gc_debug) { + // io.print("allocated {*} {*}\n", .{ obj, object }); + // io.print("(from {}) {} allocated, total {}\n", .{ before, self.bytes_allocated - before, self.bytes_allocated }); + // } + + // Add new object at start of vm.objects linked list + try self.addObject(object); + + if (BuildOptions.gc_debug_access) { + self.debugger.?.allocated( + object, + self.where, + switch (T) { + o.ObjString => .String, + o.ObjTypeDef => .Type, + o.ObjUpValue => .UpValue, + o.ObjClosure => .Closure, + o.ObjFunction => .Function, + o.ObjObjectInstance => .ObjectInstance, + o.ObjObject => .Object, + o.ObjList => .List, + o.ObjMap => .Map, + o.ObjEnum => .Enum, + o.ObjEnumInstance => .EnumInstance, + o.ObjBoundMethod => .Bound, + o.ObjNative => .Native, + o.ObjUserData => .UserData, + o.ObjPattern => .Pattern, + o.ObjFiber => .Fiber, + o.ObjForeignContainer => .ForeignContainer, + o.ObjRange => .Range, + else => @panic("Unknown object type being allocated"), + }, + ); + } + + return obj; +} + +fn addObject(self: *GC, obj: *o.Obj) !void { + self.objects.prepend(&obj.node); +} + +pub fn allocateString(self: *GC, chars: []const u8) !*o.ObjString { + const string: *o.ObjString = try allocateObject( + self, + o.ObjString, + o.ObjString{ .string = chars }, + ); + + try self.strings.put( + self.allocator, + string.string, + string, + ); + + return string; +} + +pub fn copyString(self: *GC, chars: []const u8) !*o.ObjString { + if (self.strings.get(chars)) |interned| { + return interned; + } + + const copy: []u8 = try self.allocateMany(u8, chars.len); + std.mem.copyForwards(u8, copy, chars); + + if (BuildOptions.gc_debug) { + io.print("Allocated slice {*} `{s}`\n", .{ copy, copy }); + } + + return try allocateString(self, copy); +} + +fn free(self: *GC, comptime T: type, pointer: *T) void { + var timer = if (!is_wasm) std.time.Timer.start() catch unreachable else {}; + + if (BuildOptions.gc_debug) { + io.print("Going to free {*}\n", .{pointer}); + } + + self.bytes_allocated -= @sizeOf(T); + self.allocator.destroy(pointer); + + if (BuildOptions.gc_debug) { + io.print( + "(from {}), collected {}, {} allocated\n", + .{ + std.fmt.fmtIntSizeDec(self.bytes_allocated + @sizeOf(T)), + std.fmt.fmtIntSizeDec(@sizeOf(T)), + std.fmt.fmtIntSizeDec(self.bytes_allocated), + }, + ); + } + + if (!is_wasm) { + self.gc_time += timer.read(); + } +} + +fn freeMany(self: *GC, comptime T: type, pointer: []const T) void { + var timer = if (!is_wasm) std.time.Timer.start() catch unreachable else {}; + + if (BuildOptions.gc_debug) { + io.print("Going to free slice {*} `{s}`\n", .{ pointer, pointer }); + } + + const n: usize = (@sizeOf(T) * pointer.len); + self.bytes_allocated -= n; + self.allocator.free(pointer); + + if (BuildOptions.gc_debug) { + io.print( + "(from {}), collected {}, {} allocated\n", + .{ + std.fmt.fmtIntSizeDec(self.bytes_allocated + n), + std.fmt.fmtIntSizeDec(n), + std.fmt.fmtIntSizeDec(self.bytes_allocated), + }, + ); + } + + if (!is_wasm) { + self.gc_time += timer.read(); + } +} + +pub fn markObjDirty(self: *GC, obj: *o.Obj) !void { + if (!obj.dirty and self.obj_collected != obj) { + obj.dirty = true; + + // io.print( + // "Marked obj @{} {} dirty, gray_stack @{} or GC @{} will be {} items long\n", + // .{ + // @intFromPtr(obj), + // obj.obj_type, + // @intFromPtr(&self.gray_stack), + // @intFromPtr(self), + // self.gray_stack.items.len, + // }, + // ); + + // A dirty obj is: an old object with reference to potential young objects that will need to be marked + // Since old object are ignored when tracing references, this will force tracing for it + try self.gray_stack.append(self.allocator, obj); + } +} + +pub fn markObj(self: *GC, obj: *o.Obj) !void { + if (obj.marked or self.obj_collected == obj) { + if (BuildOptions.gc_debug) { + io.print( + "{*} {s} already marked or old\n", + .{ + obj, + try Value.fromObj(obj).toStringAlloc(self.allocator), + }, + ); + } + return; + } + + if (BuildOptions.gc_debug) { + io.print("marking {*}: ", .{obj}); + io.print( + "{s}\n", + .{ + try Value.fromObj(obj).toStringAlloc(self.allocator), + }, + ); + } + + obj.marked = true; + + // Move marked obj to tail so we sweeping can stop going through objects when finding the first marked object + self.objects.remove(&obj.node); + // Just to be safe, reset node before inserting it again + obj.node = .{ + .prev = null, + .next = null, + }; + self.objects.append(&obj.node); + + try self.gray_stack.append(self.allocator, obj); +} + +fn blackenObject(self: *GC, obj: *o.Obj) !void { + if (BuildOptions.gc_debug) { + io.print( + "blackening @{} {}\n", + .{ + @intFromPtr(obj), + obj.obj_type, + }, + ); + } + + obj.dirty = false; + + _ = try switch (obj.obj_type) { + .String => obj.access(o.ObjString, .String, self).?.mark(self), + .Type => obj.access(o.ObjTypeDef, .Type, self).?.mark(self), + .UpValue => obj.access(o.ObjUpValue, .UpValue, self).?.mark(self), + .Closure => obj.access(o.ObjClosure, .Closure, self).?.mark(self), + .Function => obj.access(o.ObjFunction, .Function, self).?.mark(self), + .ObjectInstance => obj.access(o.ObjObjectInstance, .ObjectInstance, self).?.mark(self), + .Object => obj.access(o.ObjObject, .Object, self).?.mark(self), + .List => obj.access(o.ObjList, .List, self).?.mark(self), + .Map => obj.access(o.ObjMap, .Map, self).?.mark(self), + .Enum => obj.access(o.ObjEnum, .Enum, self).?.mark(self), + .EnumInstance => obj.access(o.ObjEnumInstance, .EnumInstance, self).?.mark(self), + .Bound => obj.access(o.ObjBoundMethod, .Bound, self).?.mark(self), + .Native => obj.access(o.ObjNative, .Native, self).?.mark(self), + .UserData => obj.access(o.ObjUserData, .UserData, self).?.mark(self), + .Pattern => obj.access(o.ObjPattern, .Pattern, self).?.mark(self), + .Fiber => obj.access(o.ObjFiber, .Fiber, self).?.mark(self), + .ForeignContainer => obj.access(o.ObjForeignContainer, .ForeignContainer, self).?.mark(self), + .Range => obj.access(o.ObjRange, .Range, self).?.mark(self), + }; + + if (BuildOptions.gc_debug) { + io.print( + "done blackening @{} {}\n", + .{ + @intFromPtr(obj), + obj.obj_type, + }, + ); + } +} + +fn freeObj(self: *GC, obj: *o.Obj) (std.mem.Allocator.Error || std.fmt.BufPrintError)!void { + if (BuildOptions.gc_debug) { + io.print(">> freeing {} {}\n", .{ @intFromPtr(obj), obj.obj_type }); + } + + if (BuildOptions.gc_debug_access) { + self.debugger.?.collected(obj, self.where.?); + } + + self.obj_collected = obj; + defer self.obj_collected = null; + + switch (obj.obj_type) { + .String => { + const obj_string = o.ObjString.cast(obj).?; + + // Remove it from interned strings + _ = self.strings.remove(obj_string.string); + + freeMany(self, u8, obj_string.string); + free(self, o.ObjString, obj_string); + }, + .Pattern => { + var obj_pattern = o.ObjPattern.cast(obj).?; + if (!is_wasm) { + obj_pattern.pattern.free(); + } + + free(self, o.ObjPattern, obj_pattern); + }, + .Type => { + var obj_typedef = o.ObjTypeDef.cast(obj).?; + const hash = TypeRegistry.typeDefHash(obj_typedef.*); + + if (self.type_registry.registry.get(hash)) |registered_obj| { + if (registered_obj == obj_typedef) { + _ = self.type_registry.registry.remove(hash); + if (BuildOptions.gc_debug) { + io.print( + "Removed registered type @{} #{} `{s}`\n", + .{ + @intFromPtr(registered_obj), + hash, + obj_typedef.toStringAlloc(self.allocator, true) catch unreachable, + }, + ); + } + } else { + // io.print( + // "ObjTypeDef {*} `{s}` was allocated outside of type registry\n", + // .{ + // obj_typedef, + // try obj_typedef.toStringAlloc(self.allocator), + // }, + // ); + // unreachable; + // FIXME: this should not occur. Right now this because of the way we resolve placeholders by changing their content and replacing the type in the typ registry + // Previously registered same type is now outside of the type registry and is collected. + return; + } + } + + obj_typedef.deinit(); + + free(self, o.ObjTypeDef, obj_typedef); + }, + .UpValue => { + const obj_upvalue = o.ObjUpValue.cast(obj).?; + if (obj_upvalue.closed) |value| { + if (value.isObj()) { + try freeObj(self, value.obj()); + } + } + + free(self, o.ObjUpValue, obj_upvalue); + }, + .Closure => { + var obj_closure = o.ObjClosure.cast(obj).?; + obj_closure.deinit(self.allocator); + + free(self, o.ObjClosure, obj_closure); + }, + .Function => { + var obj_function = o.ObjFunction.cast(obj).?; + obj_function.deinit(); + + free(self, o.ObjFunction, obj_function); + }, + .ObjectInstance => { + var obj_objectinstance = o.ObjObjectInstance.cast(obj).?; + + // Calling eventual destructor method + if (obj_objectinstance.object) |object| { + if (object.type_def.resolved_type.?.Object.fields.get("collect")) |field| { + if (field.method and !field.static) { + if (BuildOptions.gc_debug_access) { + self.debugger.?.invoking_collector = true; + } + buzz_api.bz_invoke( + obj_objectinstance.vm, + obj_objectinstance.toValue(), + field.index, + null, + 0, + null, + ); + if (BuildOptions.gc_debug_access) { + self.debugger.?.invoking_collector = false; + } + + // Remove void result of the collect call + _ = obj_objectinstance.vm.pop(); + } + } + } + + obj_objectinstance.deinit(self.allocator); + + free(self, o.ObjObjectInstance, obj_objectinstance); + }, + .Object => { + var obj_object = o.ObjObject.cast(obj).?; + obj_object.deinit(self.allocator); + + free(self, o.ObjObject, obj_object); + }, + .List => { + var obj_list = o.ObjList.cast(obj).?; + obj_list.deinit(self.allocator); + + free(self, o.ObjList, obj_list); + }, + .Map => { + var obj_map = o.ObjMap.cast(obj).?; + obj_map.deinit(self.allocator); + + free(self, o.ObjMap, obj_map); + }, + .Enum => { + const obj_enum = o.ObjEnum.cast(obj).?; + self.allocator.free(obj_enum.cases); + + free(self, o.ObjEnum, obj_enum); + }, + .EnumInstance => free(self, o.ObjEnumInstance, o.ObjEnumInstance.cast(obj).?), + .Bound => free(self, o.ObjBoundMethod, o.ObjBoundMethod.cast(obj).?), + .Native => free(self, o.ObjNative, o.ObjNative.cast(obj).?), + .UserData => free(self, o.ObjUserData, o.ObjUserData.cast(obj).?), + .Fiber => { + var obj_fiber = o.ObjFiber.cast(obj).?; + obj_fiber.fiber.deinit(); + + self.allocator.destroy(obj_fiber.fiber); + + free(self, o.ObjFiber, obj_fiber); + }, + .ForeignContainer => { + const obj_foreignstruct = o.ObjForeignContainer.cast(obj).?; + + self.freeMany(u8, obj_foreignstruct.data); + + free(self, o.ObjForeignContainer, obj_foreignstruct); + }, + .Range => { + free(self, o.ObjRange, o.ObjRange.cast(obj).?); + }, + } +} + +pub fn markValue(self: *GC, value: Value) !void { + if (value.isObj()) { + try self.markObj(value.obj()); + } +} + +pub fn markFiber(self: *GC, fiber: *v.Fiber) !void { + var current_fiber: ?*v.Fiber = fiber; + while (current_fiber) |ufiber| { + try self.markObj(@constCast(ufiber.type_def.toObj())); + // Mark main fiber + if (BuildOptions.gc_debug) { + io.print("MARKING STACK OF FIBER @{}\n", .{@intFromPtr(ufiber)}); + } + var i: [*]Value = @ptrCast(fiber.stack); + while (@intFromPtr(i) < @intFromPtr(fiber.stack_top)) : (i += 1) { + try self.markValue(i[0]); + } + if (BuildOptions.gc_debug) { + io.print("DONE MARKING STACK OF FIBER @{}\n", .{@intFromPtr(ufiber)}); + } + + // Mark closure + if (BuildOptions.gc_debug) { + io.print("MARKING FRAMES OF FIBER @{}\n", .{@intFromPtr(ufiber)}); + } + for (fiber.frames.items) |frame| { + try self.markObj(frame.closure.toObj()); + if (frame.error_value) |error_value| { + try self.markValue(error_value); + } + if (frame.native_call_error_value) |error_value| { + try self.markValue(error_value); + } + } + if (BuildOptions.gc_debug) { + io.print("DONE MARKING FRAMES OF FIBER @{}\n", .{@intFromPtr(ufiber)}); + } + + // Mark opened upvalues + if (BuildOptions.gc_debug) { + io.print("MARKING UPVALUES OF FIBER @{}\n", .{@intFromPtr(ufiber)}); + } + if (fiber.open_upvalues) |open_upvalues| { + var upvalue: ?*o.ObjUpValue = open_upvalues; + while (upvalue) |unwrapped| : (upvalue = unwrapped.next) { + try self.markObj(unwrapped.toObj()); + } + } + if (BuildOptions.gc_debug) { + io.print("DONE MARKING UPVALUES OF FIBER @{}\n", .{@intFromPtr(ufiber)}); + } + + current_fiber = ufiber.parent_fiber; + } +} + +fn markMethods(self: *GC) !void { + if (BuildOptions.gc_debug) { + io.print("MARKING BASIC TYPES METHOD\n", .{}); + } + // Mark basic types methods + for (self.objfiber_members) |member| { + if (member) |umember| { + try self.markObj(umember.toObj()); + } + } + + for (self.objfiber_memberDefs) |def| { + if (def) |udef| { + try self.markObj(udef.toObj()); + } + } + + for (self.objrange_members) |member| { + if (member) |umember| { + try self.markObj(umember.toObj()); + } + } + + for (self.objrange_memberDefs) |def| { + if (def) |udef| { + try self.markObj(udef.toObj()); + } + } + + for (self.objstring_members) |member| { + if (member) |umember| { + try self.markObj(umember.toObj()); + } + } + + for (self.objstring_memberDefs) |def| { + if (def) |udef| { + try self.markObj(udef.toObj()); + } + } + + for (self.objpattern_members) |member| { + if (member) |umember| { + try self.markObj(umember.toObj()); + } + } + + for (self.objpattern_memberDefs) |def| { + if (def) |udef| { + try self.markObj(udef.toObj()); + } + } + + if (BuildOptions.gc_debug) { + io.print("DONE MARKING BASIC TYPES METHOD\n", .{}); + } +} + +fn markRoots(self: *GC, vm: *v.VM) !void { + // FIXME: We should not need this, but we don't know how to prevent collection before the VM actually starts making reference to them + try self.type_registry.mark(); + + try self.markMethods(); + + // Mark special strings we always need + if (self.strings.get("$")) |dollar| { + try self.markObj(dollar.toObj()); + } + + // Mark import registry + var it = vm.import_registry.iterator(); + while (it.next()) |kv| { + try self.markObj(kv.key_ptr.*.toObj()); + for (kv.value_ptr.*) |global| { + try self.markValue(global); + } + } + + // Mark current fiber and its parent fibers + try markFiber(self, vm.current_fiber); + + // Mark globals + if (BuildOptions.gc_debug) { + io.print("MARKING GLOBALS OF VM @{}\n", .{@intFromPtr(vm)}); + } + for (vm.globals.items) |global| { + try self.markValue(global); + } + if (BuildOptions.gc_debug) { + io.print("DONE MARKING GLOBALS OF VM @{}\n", .{@intFromPtr(vm)}); + } + + // Mark ast constant values (some are only referenced by the JIT so might be collected before) + // TODO: does this takes too long or are we saved by vertue of MultiArrayList? + for (vm.current_ast.nodes.items(.value)) |valueOpt| { + if (valueOpt) |value| { + try self.markValue(value); + } + } +} + +fn traceReference(self: *GC) !void { + if (BuildOptions.gc_debug) { + io.print("TRACING REFERENCE\n", .{}); + } + while (self.gray_stack.items.len > 0) { + try blackenObject(self, self.gray_stack.pop().?); + } + if (BuildOptions.gc_debug) { + io.print("DONE TRACING REFERENCE\n", .{}); + } +} + +fn sweep(self: *GC, mode: Mode) !void { + const swept: usize = self.bytes_allocated; + + var obj_count: usize = 0; + var obj_node = self.objects.first; + var count: usize = 0; + while (obj_node) |node| : (count += 1) { + const obj: *o.Obj = @fieldParentPtr("node", node); + const marked = obj.marked; + if (marked) { + if (BuildOptions.gc_debug and mode == .Full) { + io.print( + "UNMARKING @{}\n", + .{ + @intFromPtr( + @as(*o.Obj, @fieldParentPtr("node", node)), + ), + }, + ); + } + // If not a full gc, we reset marked, this object is now 'old' + obj.marked = if (mode == .Full) false else marked; + + // If a young collection we don't reset marked flags and since we move all marked object + // to the tail of the list, we can stop here, there's no more objects to collect + if (mode == .Young) { + break; + } + + obj_node = node.next; + } else { + const unreached: *o.Obj = obj; + obj_node = node.next; + + self.objects.remove(node); + + try freeObj(self, unreached); + obj_count += 1; + } + } + + if (BuildOptions.gc_debug or BuildOptions.gc_debug_light) { + if (swept < self.bytes_allocated) { + io.print("Warn: sweep gained memory, possibly due to an o.Object collector that takes up memory\n", .{}); + } + + io.print( + "\nSwept {} objects for {}, now {}\n", + .{ + obj_count, + std.fmt.fmtIntSizeDec(@max(swept, self.bytes_allocated) - self.bytes_allocated), + std.fmt.fmtIntSizeDec(self.bytes_allocated), + }, + ); + } +} + +pub fn collectGarbage(self: *GC) !void { + var timer = if (!is_wasm) std.time.Timer.start() catch unreachable else {}; + + // Don't collect until a VM is actually running + var vm_it = self.active_vms.iterator(); + const first_vm = vm_it.next(); + if (first_vm == null or first_vm.?.key_ptr.*.flavor == .Repl) { + return; + } + + // Avoid triggering another sweep while running collectors + if (self.obj_collected != null) { + return; + } + + const mode: Mode = if (self.bytes_allocated > self.next_full_gc and self.last_gc != null) .Full else .Young; + + if (BuildOptions.gc_debug or BuildOptions.gc_debug_light) { + io.print( + "-- gc starts mode {s}, {}, {} objects\n", + .{ + @tagName(mode), + std.fmt.fmtIntSizeDec(self.bytes_allocated), + self.objects.len(), + }, + ); + } + + var it = self.active_vms.iterator(); + while (it.next()) |kv| { + var vm = kv.key_ptr.*; + + if (BuildOptions.gc_debug) { + io.print( + "\tMarking VM @{}, on fiber @{} and closure @{} (function @{} {s})\n", + .{ + @intFromPtr(vm), + @intFromPtr(vm.current_fiber), + @intFromPtr(vm.currentFrame().?.closure), + @intFromPtr(vm.currentFrame().?.closure.function), + vm.currentFrame().?.closure.function.type_def.resolved_type.?.Function.name.string, + }, + ); + } + + try markRoots(self, vm); + } + + try traceReference(self); + + try sweep(self, mode); + + if (mode == .Full) { + self.full_collection_count += 1; + } else { + self.light_collection_count += 1; + } + + self.next_gc = self.bytes_allocated * BuildOptions.next_gc_ratio; + if (mode == .Full) { + self.next_full_gc = self.bytes_allocated * BuildOptions.next_full_gc_ratio; + } + self.last_gc = mode; + + if (BuildOptions.gc_debug or BuildOptions.gc_debug_light) { + io.print( + "-- gc end, {}, {} objects, next_gc {}, next_full_gc {}\n", + .{ + std.fmt.fmtIntSizeDec(self.bytes_allocated), + self.objects.len(), + std.fmt.fmtIntSizeDec(self.next_gc), + std.fmt.fmtIntSizeDec(self.next_full_gc), + }, + ); + } + // io.print("gc took {}ms\n", .{timer.read() / 1000000}); + + if (!is_wasm) { + self.gc_time += timer.read(); + } +} + +pub const Debugger = struct { + const Self = @This(); + + pub const Ptr = struct { + what: o.ObjType, + allocated_at: ?Token, + collected_at: ?Token = null, + }; + + allocator: std.mem.Allocator, + reporter: Reporter, + tracker: std.AutoHashMapUnmanaged(*o.Obj, Ptr), + invoking_collector: bool = false, + + pub fn init(allocator: std.mem.Allocator) Self { + return .{ + .allocator = allocator, + .tracker = std.AutoHashMapUnmanaged(*o.Obj, Ptr){}, + .reporter = Reporter{ + .allocator = allocator, + }, + }; + } + + pub fn deinit(self: *Self) void { + self.tracker.deinit(self.allocator); + } + + pub fn allocated(self: *Self, ptr: *o.Obj, at: ?Token, what: o.ObjType) void { + std.debug.assert(self.tracker.get(ptr) == null); + self.tracker.put( + self.allocator, + ptr, + Ptr{ + .what = what, + .allocated_at = at, + }, + ) catch @panic("Could not track object"); + } + + pub fn collected(self: *Self, ptr: *o.Obj, at: Token) void { + if (self.tracker.getPtr(ptr)) |tracked| { + if (tracked.collected_at) |collected_at| { + self.reporter.reportWithOrigin( + .gc, + at, + at, + collected_at, + collected_at, + "Trying to collected already collected {} {*}", + .{ tracked.what, ptr }, + "first collected here", + ); + + @panic("Double collect"); + } + + tracked.collected_at = at; + } else { + @panic("Collect of untracked object"); + } + } + + pub fn accessed(self: *Self, ptr: *o.Obj, at: ?Token) void { + if (self.invoking_collector) return; + + if (self.tracker.getPtr(ptr)) |tracked| { + if (tracked.collected_at) |collected_at| { + var items = std.array_list.Managed(Reporter.ReportItem).init(self.allocator); + defer items.deinit(); + + var message = std.array_list.Managed(u8).init(self.allocator); + defer message.deinit(); + + message.writer().print( + "Access to already collected {} {*}", + .{ + tracked.what, + ptr, + }, + ) catch unreachable; + + items.append( + .{ + .location = at.?, + .end_location = at.?, + .kind = .@"error", + .message = message.items, + }, + ) catch unreachable; + + if (tracked.allocated_at) |allocated_at| { + items.append( + .{ + .location = allocated_at, + .end_location = allocated_at, + .kind = .hint, + .message = "allocated here", + }, + ) catch unreachable; + } + + items.append( + .{ + .location = collected_at, + .end_location = collected_at, + .kind = .hint, + .message = "collected here", + }, + ) catch unreachable; + + var report = Reporter.Report{ + .message = message.items, + .error_type = .gc, + .items = items.items, + }; + + report.reportStderr(&self.reporter) catch unreachable; + + @panic("Access to already collected object"); + } + } else { + if (at) |accessed_at| { + self.reporter.reportErrorFmt( + .gc, + accessed_at, + accessed_at, + "Untracked obj {*}", + .{ + ptr, + }, + ); + } else { + io.print( + "Untracked obj {*}\n", + .{ + ptr, + }, + ); + } + + @panic("Access to untracked object"); + } + } +}; diff --git a/src/Parser.zig b/src/Parser.zig index 420c3373..ee42c6ca 100644 --- a/src/Parser.zig +++ b/src/Parser.zig @@ -10,7 +10,7 @@ const Value = v.Value; const Integer = v.Integer; const FFI = @import("FFI.zig"); const Ast = @import("Ast.zig"); -const GarbageCollector = @import("memory.zig").GarbageCollector; +const GC = @import("GC.zig"); const Scanner = @import("Scanner.zig"); const RunFlavor = @import("vm.zig").RunFlavor; const Reporter = @import("Reporter.zig"); @@ -142,7 +142,7 @@ else const Self = @This(); ast: Ast, -gc: *GarbageCollector, +gc: *GC, scanner: ?Scanner = null, current_token: ?Ast.TokenIndex = null, script_name: []const u8 = undefined, @@ -168,7 +168,7 @@ reporter: Reporter, opt_jumps: ?std.ArrayList(Precedence) = null, pub fn init( - gc: *GarbageCollector, + gc: *GC, imports: *std.StringHashMapUnmanaged(ScriptImport), imported: bool, flavor: RunFlavor, diff --git a/src/TypeRegistry.zig b/src/TypeRegistry.zig new file mode 100644 index 00000000..18accfcd --- /dev/null +++ b/src/TypeRegistry.zig @@ -0,0 +1,298 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const v = @import("vm.zig"); +const Value = @import("value.zig").Value; +const o = @import("obj.zig"); +const dumpStack = @import("disassembler.zig").dumpStack; +const BuildOptions = @import("build_options"); +const Token = @import("Token.zig"); +const buzz_api = @import("buzz_api.zig"); +const Reporter = @import("Reporter.zig"); +const is_wasm = builtin.cpu.arch.isWasm(); +const io = @import("io.zig"); +const GC = @import("GC.zig"); + +const TypeRegistry = @This(); + +pub const TypeDefHash = u64; + +gc: *GC, +registry: std.AutoHashMapUnmanaged(TypeDefHash, *o.ObjTypeDef) = .empty, + +// Common types we reuse all the time +void_type: *o.ObjTypeDef, +str_type: *o.ObjTypeDef, +int_type: *o.ObjTypeDef, +float_type: *o.ObjTypeDef, +bool_type: *o.ObjTypeDef, +any_type: *o.ObjTypeDef, +pat_type: *o.ObjTypeDef, +ud_type: *o.ObjTypeDef, +rg_type: *o.ObjTypeDef, + +pub fn init(gc: *GC) !TypeRegistry { + var self = TypeRegistry{ + .gc = gc, + .void_type = undefined, + .str_type = undefined, + .int_type = undefined, + .float_type = undefined, + .bool_type = undefined, + .any_type = undefined, + .pat_type = undefined, + .ud_type = undefined, + .rg_type = undefined, + }; + + self.void_type = try self.getTypeDef(.{ .def_type = .Void }); + self.str_type = try self.getTypeDef(.{ .def_type = .String }); + self.int_type = try self.getTypeDef(.{ .def_type = .Integer }); + self.float_type = try self.getTypeDef(.{ .def_type = .Double }); + self.bool_type = try self.getTypeDef(.{ .def_type = .Bool }); + self.any_type = try self.getTypeDef(.{ .def_type = .Any }); + self.pat_type = try self.getTypeDef(.{ .def_type = .Pattern }); + self.ud_type = try self.getTypeDef(.{ .def_type = .UserData }); + self.rg_type = try self.getTypeDef(.{ .def_type = .Range }); + + return self; +} + +pub fn deinit(self: *TypeRegistry) void { + self.registry.deinit(self.gc.allocator); +} + +pub fn dump(self: *TypeRegistry) void { + io.print("\n====== Type Registry ======\n", .{}); + var it = self.registry.iterator(); + while (it.next()) |entry| { + io.print( + "#{} = @{} `{s}`\n", + .{ + entry.key_ptr.*, + @intFromPtr(entry.value_ptr.*), + entry.value_ptr.*.toStringAlloc(self.gc.allocator) catch unreachable, + }, + ); + } + io.print("===========================\n\n", .{}); +} + +pub fn getTypeDef(self: *TypeRegistry, type_def: o.ObjTypeDef) !*o.ObjTypeDef { + const hash = typeDefHash(type_def); + + // We don't return a cached version of a placeholder since they all maintain a particular state (link) + if (type_def.def_type != .Placeholder) { + if (self.registry.get(hash)) |type_def_ptr| { + return type_def_ptr; + } + } + + const type_def_ptr = try self.gc.allocateObject(o.ObjTypeDef, type_def); + + if (BuildOptions.debug_placeholders or BuildOptions.debug_type_registry) { + io.print( + "`{s}` #{} @{}\n", + .{ + type_def_ptr.toStringAlloc(self.gc.allocator, true) catch unreachable, + hash, + @intFromPtr(type_def_ptr), + }, + ); + } + + // Since the key buffer is reused, we clone the key + try self.registry.put( + self.gc.allocator, + hash, + type_def_ptr, + ); + + return type_def_ptr; +} + +pub fn setTypeDef(self: *TypeRegistry, type_def: *o.ObjTypeDef) !void { + const hash = typeDefHash(type_def.*); + + std.debug.assert(type_def.def_type != .Placeholder); + + try self.registry.put( + self.gc.allocator, + hash, + type_def, + ); + + if (BuildOptions.debug_placeholders or BuildOptions.debug_type_registry) { + io.print( + "`{s}` type set to #{} @{}\n", + .{ + type_def.toStringAlloc(self.gc.allocator, true) catch unreachable, + hash, + @intFromPtr(type_def), + }, + ); + } +} + +pub inline fn getTypeDefByName(self: *TypeRegistry, name: []const u8) ?*o.ObjTypeDef { + return self.registry.get(name); +} + +pub fn mark(self: *TypeRegistry) !void { + var it = self.registry.iterator(); + while (it.next()) |kv| { + try self.gc.markObj(@constCast(kv.value_ptr.*).toObj()); + } +} + +fn hashHelper(hasher: *std.hash.Wyhash, type_def: *const o.ObjTypeDef) void { + std.hash.autoHash(hasher, type_def.def_type); + std.hash.autoHash(hasher, type_def.optional); + if (type_def.resolved_type) |resolved| { + switch (resolved) { + // We actually hash the ObjTypeDef and not its pointer since we don't put Placeholders in the registry + // BUT: when going deeper in those type we might encounter a pointer to a Placeholder ObjTypeDef, + // in that case we wan't to use the pointer (real this time) as hash value + .Placeholder => std.hash.autoHash(hasher, type_def), + + .Bool, + .Double, + .Integer, + .Pattern, + .String, + .Type, // Something that holds a type, not an actual type + .UserData, + .Void, + .Range, + => {}, + + .Any => std.hash.autoHash(hasher, resolved.Any), + .Enum => std.hash.autoHash( + hasher, + std.hash_map.hashString(resolved.Enum.qualified_name.string), + ), + .EnumInstance => { + std.hash.autoHash(hasher, resolved.EnumInstance.mutable); + hashHelper(hasher, resolved.EnumInstance.of); + }, + .Fiber => { + hashHelper(hasher, resolved.Fiber.return_type); + hashHelper(hasher, resolved.Fiber.yield_type); + }, + .ForeignContainer => std.hash.autoHash( + hasher, + std.hash_map.hashString(resolved.ForeignContainer.qualified_name.string), + ), + .Function => { + std.hash.autoHash( + hasher, + std.hash_map.hashString(resolved.Function.name.string), + ); + std.hash.autoHash( + hasher, + std.hash_map.hashString(resolved.Function.script_name.string), + ); + hashHelper(hasher, resolved.Function.return_type); + hashHelper(hasher, resolved.Function.yield_type); + if (resolved.Function.error_types) |types| { + for (types) |error_type| { + hashHelper(hasher, error_type); + } + } + + { + var it = resolved.Function.parameters.iterator(); + while (it.next()) |kv| { + std.hash.autoHash( + hasher, + std.hash_map.hashString(kv.key_ptr.*.string), + ); + hashHelper(hasher, kv.value_ptr.*); + } + } + + { + var it = resolved.Function.defaults.iterator(); + while (it.next()) |kv| { + std.hash.autoHash( + hasher, + std.hash_map.hashString(kv.key_ptr.*.string), + ); + std.hash.autoHash(hasher, kv.value_ptr.*); + } + } + + std.hash.autoHash(hasher, resolved.Function.function_type); + std.hash.autoHash(hasher, resolved.Function.lambda); + + for (resolved.Function.generic_types.keys()) |generic| { + std.hash.autoHash( + hasher, + std.hash_map.hashString(generic.string), + ); + } + + if (resolved.Function.resolved_generics) |types| { + for (types) |gen_type| { + hashHelper(hasher, gen_type); + } + } + }, + .Generic => std.hash.autoHash(hasher, resolved.Generic), + .List => { + hashHelper(hasher, resolved.List.item_type); + std.hash.autoHash(hasher, resolved.List.mutable); + }, + .Map => { + hashHelper(hasher, resolved.Map.key_type); + hashHelper(hasher, resolved.Map.value_type); + std.hash.autoHash(hasher, resolved.Map.mutable); + }, + .Object => { + if (resolved.Object.anonymous) { + // If anonymous, we must take the whole type into account + // But since it'type_def anonymous, we only need to worry about fields type knowing there'type_def no method, static, etc. + var it = resolved.Object.fields.iterator(); + while (it.next()) |kv| { + std.hash.autoHash( + hasher, + std.hash_map.hashString(kv.key_ptr.*), + ); + hashHelper(hasher, kv.value_ptr.type_def); + } + } else { + // Actual object: name + resolved generics is distinction enough + std.hash.autoHash( + hasher, + std.hash_map.hashString(resolved.Object.qualified_name.string), + ); + + if (resolved.Object.resolved_generics) |rg| { + for (rg) |gen| { + hashHelper(hasher, gen); + } + } + } + }, + .ObjectInstance => { + std.hash.autoHash(hasher, resolved.ObjectInstance.mutable); + hashHelper(hasher, resolved.ObjectInstance.of); + }, + .Protocol => std.hash.autoHash( + hasher, + std.hash_map.hashString(resolved.Protocol.qualified_name.string), + ), + .ProtocolInstance => { + std.hash.autoHash(hasher, resolved.ProtocolInstance.mutable); + hashHelper(hasher, resolved.ProtocolInstance.of); + }, + } + } +} + +pub fn typeDefHash(type_def: o.ObjTypeDef) TypeDefHash { + var hasher = std.hash.Wyhash.init(0); + + hashHelper(&hasher, &type_def); + + return hasher.final(); +} diff --git a/src/builtin/pattern.zig b/src/builtin/pattern.zig index 9c725cb0..e58c4de5 100644 --- a/src/builtin/pattern.zig +++ b/src/builtin/pattern.zig @@ -61,10 +61,6 @@ const fake_token: Token = .{ // Return match anonymous object type: obj{ start: int, end: int, capture: str } fn matchType(vm: *VM) !*o.ObjTypeDef { - if (vm.gc.type_registry.registry.get(".{ capture: str, start: int, end: int }")) |type_def| { - return type_def; - } - var object_def = o.ObjObject.ObjectDef.init( 0, try vm.gc.copyString("match"), diff --git a/src/buzz_api.zig b/src/buzz_api.zig index 7060cb0b..dfd1ba49 100644 --- a/src/buzz_api.zig +++ b/src/buzz_api.zig @@ -7,7 +7,8 @@ const TryCtx = vmachine.TryCtx; const ImportRegistry = vmachine.ImportRegistry; const o = @import("obj.zig"); const v = @import("value.zig"); -const memory = @import("memory.zig"); +const GC = @import("GC.zig"); +const TypeRegistry = @import("TypeRegistry.zig"); const Parser = @import("Parser.zig"); const CodeGen = @import("Codegen.zig"); const BuildOptions = @import("build_options"); @@ -586,10 +587,10 @@ export fn bz_getUserDataPtr(userdata: v.Value) callconv(.c) u64 { export fn bz_newVM() *VM { const vm = allocator.create(VM) catch @panic("Out of memory"); - var gc = allocator.create(memory.GarbageCollector) catch @panic("Out of memory"); + var gc = allocator.create(GC) catch @panic("Out of memory"); // FIXME: should share strings between gc - gc.* = memory.GarbageCollector.init(allocator) catch @panic("Out of memory"); - gc.type_registry = memory.TypeRegistry.init(gc) catch @panic("Out of memory"); + gc.* = GC.init(allocator) catch @panic("Out of memory"); + gc.type_registry = TypeRegistry.init(gc) catch @panic("Out of memory"); const import_registry = allocator.create(ImportRegistry) catch @panic("Out of memory"); import_registry.* = .{}; diff --git a/src/lib/buzz_io.zig b/src/lib/buzz_io.zig index 1e6a0dfd..5e1249ca 100644 --- a/src/lib/buzz_io.zig +++ b/src/lib/buzz_io.zig @@ -426,6 +426,8 @@ pub export fn FileGetPoller(ctx: *api.NativeCtx) callconv(.c) c_int { .{ .file = file.file }, ); + // poller.reader(.file).* = ; + ctx.vm.bz_push( ctx.vm.bz_newUserData(@intFromPtr(poller)), ); diff --git a/src/lsp.zig b/src/lsp.zig index 64daef50..cf3ec3a0 100644 --- a/src/lsp.zig +++ b/src/lsp.zig @@ -4,7 +4,8 @@ const is_wasm = builtin.cpu.arch.isWasm(); const BuildOptions = @import("build_options"); const lsp = @import("lsp"); const Ast = @import("Ast.zig"); -const mem = @import("memory.zig"); +const GC = @import("GC.zig"); +const TypeRegistry = @import("TypeRegistry.zig"); const Parser = @import("Parser.zig"); const Reporter = @import("Reporter.zig"); const CodeGen = @import("Codegen.zig"); @@ -46,8 +47,8 @@ const Document = struct { var arena = std.heap.ArenaAllocator.init(parent_allocator); const allocator = arena.allocator(); - var gc = try mem.GarbageCollector.init(allocator); - gc.type_registry = mem.TypeRegistry.init(&gc) catch return error.OutOfMemory; + var gc = try GC.init(allocator); + gc.type_registry = TypeRegistry.init(&gc) catch return error.OutOfMemory; var imports = std.StringHashMapUnmanaged(Parser.ScriptImport){}; var parser = Parser.init( diff --git a/src/main.zig b/src/main.zig index 093febbd..2ee4bfc0 100644 --- a/src/main.zig +++ b/src/main.zig @@ -10,11 +10,11 @@ const CodeGen = @import("Codegen.zig"); const _obj = @import("obj.zig"); const ObjString = _obj.ObjString; const ObjTypeDef = _obj.ObjTypeDef; -const TypeRegistry = @import("memory.zig").TypeRegistry; +const TypeRegistry = @import("TypeRegistry.zig"); const Ast = @import("Ast.zig"); const BuildOptions = @import("build_options"); const clap = @import("clap"); -const GarbageCollector = @import("memory.zig").GarbageCollector; +const GC = @import("GC.zig"); const JIT = if (!is_wasm) @import("Jit.zig") else void; const is_wasm = builtin.cpu.arch.isWasm(); const repl = if (!is_wasm) @import("repl.zig").repl else void; @@ -70,7 +70,7 @@ fn printBanner(out: anytype, full: bool) void { pub fn runFile(allocator: Allocator, file_name: []const u8, args: []const []const u8, flavor: RunFlavor) !void { var total_timer = if (!is_wasm) std.time.Timer.start() catch unreachable else {}; var import_registry = ImportRegistry{}; - var gc = try GarbageCollector.init(allocator); + var gc = try GC.init(allocator); gc.type_registry = try TypeRegistry.init(&gc); var imports = std.StringHashMapUnmanaged(Parser.ScriptImport){}; var vm = try VM.init(&gc, &import_registry, flavor); diff --git a/src/memory.zig b/src/memory.zig deleted file mode 100644 index 63128fdb..00000000 --- a/src/memory.zig +++ /dev/null @@ -1,1213 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const v = @import("vm.zig"); -const Value = @import("value.zig").Value; -const o = @import("obj.zig"); -const dumpStack = @import("disassembler.zig").dumpStack; -const BuildOptions = @import("build_options"); -const Token = @import("Token.zig"); -const buzz_api = @import("buzz_api.zig"); -const Reporter = @import("Reporter.zig"); -const is_wasm = builtin.cpu.arch.isWasm(); -const io = @import("io.zig"); - -pub const TypeRegistry = struct { - const Self = @This(); - - gc: *GarbageCollector, - registry: std.StringHashMap(*o.ObjTypeDef), - - // Common types we reuse all the time - void_type: *o.ObjTypeDef, - str_type: *o.ObjTypeDef, - int_type: *o.ObjTypeDef, - float_type: *o.ObjTypeDef, - bool_type: *o.ObjTypeDef, - any_type: *o.ObjTypeDef, - pat_type: *o.ObjTypeDef, - ud_type: *o.ObjTypeDef, - rg_type: *o.ObjTypeDef, - - // Buffer resused when we build a type key - type_def_key_buffer: std.ArrayList(u8) = .{}, - - pub fn init(gc: *GarbageCollector) !Self { - var self = Self{ - .gc = gc, - .registry = .init(gc.allocator), - .void_type = undefined, - .str_type = undefined, - .int_type = undefined, - .float_type = undefined, - .bool_type = undefined, - .any_type = undefined, - .pat_type = undefined, - .ud_type = undefined, - .rg_type = undefined, - }; - - self.void_type = try self.getTypeDef(.{ .def_type = .Void }); - self.str_type = try self.getTypeDef(.{ .def_type = .String }); - self.int_type = try self.getTypeDef(.{ .def_type = .Integer }); - self.float_type = try self.getTypeDef(.{ .def_type = .Double }); - self.bool_type = try self.getTypeDef(.{ .def_type = .Bool }); - self.any_type = try self.getTypeDef(.{ .def_type = .Any }); - self.pat_type = try self.getTypeDef(.{ .def_type = .Pattern }); - self.ud_type = try self.getTypeDef(.{ .def_type = .UserData }); - self.rg_type = try self.getTypeDef(.{ .def_type = .Range }); - - return self; - } - - pub fn deinit(self: *Self) void { - self.registry.deinit(); - self.type_def_key_buffer.deinit(); - } - - pub fn dump(self: *Self) void { - io.print("\n====== Type Registry ======\n", .{}); - var it = self.registry.iterator(); - while (it.next()) |entry| { - io.print( - "`{s}` = @{} `{s}`\n", - .{ - entry.key_ptr.*, - @intFromPtr(entry.value_ptr.*), - entry.value_ptr.*.toStringAlloc(self.gc.allocator) catch unreachable, - }, - ); - } - io.print("===========================\n\n", .{}); - } - - pub fn getTypeDef(self: *Self, type_def: o.ObjTypeDef) !*o.ObjTypeDef { - self.type_def_key_buffer.shrinkRetainingCapacity(0); - try type_def.toString(&self.type_def_key_buffer.writer(self.gc.allocator), true); - - // We don't return a cached version of a placeholder since they all maintain a particular state (link) - if (type_def.def_type != .Placeholder) { - if (self.registry.get(self.type_def_key_buffer.items)) |type_def_ptr| { - return type_def_ptr; - } - } - - const type_def_ptr = try self.gc.allocateObject(o.ObjTypeDef, type_def); - - if (BuildOptions.debug_placeholders or BuildOptions.debug_type_registry) { - io.print( - "`{s}` @{}\n", - .{ - self.type_def_key_buffer.items, - @intFromPtr(type_def_ptr), - }, - ); - } - - // Since the key buffer is reused, we clone the key - var copy = try self.type_def_key_buffer.clone(self.gc.allocator); - - const slot = try self.registry.getOrPut(try copy.toOwnedSlice(self.gc.allocator)); - - slot.value_ptr.* = type_def_ptr; - - if (slot.found_existing) { - copy.deinit(self.gc.allocator); - } - - return type_def_ptr; - } - - pub fn setTypeDef(self: *Self, type_def: *o.ObjTypeDef) !void { - const type_def_str = try type_def.toStringAlloc(self.gc.allocator, true); - - std.debug.assert(type_def.def_type != .Placeholder); - - _ = try self.registry.put(type_def_str, type_def); - - if (BuildOptions.debug_placeholders or BuildOptions.debug_type_registry) { - io.print( - "`{s}` type set to @{}\n", - .{ - type_def_str, - @intFromPtr(type_def), - }, - ); - } - } - - pub inline fn getTypeDefByName(self: *Self, name: []const u8) ?*o.ObjTypeDef { - return self.registry.get(name); - } - - pub fn mark(self: *Self) !void { - var it = self.registry.iterator(); - while (it.next()) |kv| { - try self.gc.markObj(@constCast(kv.value_ptr.*).toObj()); - } - } -}; - -// Sticky Mark Bits Generational GC basic idea: -// 1. First GC: do a normal mark, don't clear `marked` at the end -// 2. Young GC: -// - Already marked objects are 'old', don't trace them (because all referenced object from it are also old and marked)and don't clear them -// - Write barrier: anytime an object is modified, mark it as 'dirty' and add it to the gray_stack so its traced -// - Old but dirty object is traced, then marked clean again (because any reference to 'young' objects will be 'old' after this gc sweep) -// 3. Old GC: -// - Trace only already marked objects -// 4. Full GC: -// - Unmark all objects -// - Do a mark and sweep -// -// For now we only have either young or old objects but we could improve on it with more categories with each its threshold -pub const GarbageCollector = struct { - const Self = @This(); - - const Mode = enum { - Young, - // Old, - Full, - }; - - allocator: std.mem.Allocator, - strings: std.StringHashMapUnmanaged(*o.ObjString), - type_registry: TypeRegistry, - bytes_allocated: usize = 0, - // next_gc == next_full_gc at first so the first cycle is a full gc - next_gc: usize = if (builtin.mode == .Debug) 1024 else 1024 * BuildOptions.initial_gc, - next_full_gc: usize = if (builtin.mode == .Debug) 1024 else 1024 * BuildOptions.initial_gc, - last_gc: ?Mode = null, - objects: std.DoublyLinkedList = .{}, - gray_stack: std.ArrayList(*o.Obj), - active_vms: std.AutoHashMapUnmanaged(*v.VM, void), - // o.Obj being collected, useful to avoid setting object instance dirty while running its collector method - obj_collected: ?*o.Obj = null, - - debugger: ?GarbageCollectorDebugger, - where: ?Token = null, - - // Types we generaly don't wan't to ever be collected - objfiber_members: []?*o.ObjNative, - objfiber_memberDefs: []?*o.ObjTypeDef, - objpattern_members: []?*o.ObjNative, - objpattern_memberDefs: []?*o.ObjTypeDef, - objstring_members: []?*o.ObjNative, - objstring_memberDefs: []?*o.ObjTypeDef, - objrange_memberDefs: []?*o.ObjTypeDef, - objrange_members: []?*o.ObjNative, - - full_collection_count: usize = 0, - light_collection_count: usize = 0, - max_allocated: usize = 0, - - gc_time: usize = 0, - - pub fn init(allocator: std.mem.Allocator) !Self { - const self = Self{ - .allocator = allocator, - .strings = std.StringHashMapUnmanaged(*o.ObjString){}, - .type_registry = undefined, - .gray_stack = std.ArrayList(*o.Obj){}, - .active_vms = std.AutoHashMapUnmanaged(*v.VM, void){}, - .debugger = if (BuildOptions.gc_debug_access) GarbageCollectorDebugger.init(allocator) else null, - - .objfiber_members = try allocator.alloc(?*o.ObjNative, o.ObjFiber.members.len), - .objfiber_memberDefs = try allocator.alloc(?*o.ObjTypeDef, o.ObjFiber.members.len), - .objpattern_members = try allocator.alloc(?*o.ObjNative, o.ObjPattern.members.len), - .objpattern_memberDefs = try allocator.alloc(?*o.ObjTypeDef, o.ObjPattern.members.len), - .objstring_members = try allocator.alloc(?*o.ObjNative, o.ObjString.members.len), - .objstring_memberDefs = try allocator.alloc(?*o.ObjTypeDef, o.ObjString.members.len), - .objrange_members = try allocator.alloc(?*o.ObjNative, o.ObjRange.members.len), - .objrange_memberDefs = try allocator.alloc(?*o.ObjTypeDef, o.ObjRange.members.len), - }; - - for (0..o.ObjFiber.members.len) |i| { - self.objfiber_members[i] = null; - self.objfiber_memberDefs[i] = null; - } - - for (0..o.ObjPattern.members.len) |i| { - self.objpattern_members[i] = null; - self.objpattern_memberDefs[i] = null; - } - - for (0..o.ObjString.members.len) |i| { - self.objstring_members[i] = null; - self.objstring_memberDefs[i] = null; - } - - for (0..o.ObjRange.members.len) |i| { - self.objrange_members[i] = null; - self.objrange_memberDefs[i] = null; - } - - return self; - } - - pub fn registerVM(self: *Self, vm: *v.VM) !void { - try self.active_vms.put( - self.allocator, - vm, - {}, - ); - } - - pub fn unregisterVM(self: *Self, vm: *v.VM) void { - std.debug.assert(self.active_vms.remove(vm)); - } - - pub fn deinit(self: *Self) void { - self.gray_stack.deinit(self.allocator); - self.strings.deinit(self.allocator); - self.active_vms.deinit(self.allocator); - if (BuildOptions.gc_debug_access) { - self.debugger.?.deinit(); - } - - self.allocator.free(self.objfiber_members); - self.allocator.free(self.objfiber_memberDefs); - self.allocator.free(self.objpattern_members); - self.allocator.free(self.objpattern_memberDefs); - self.allocator.free(self.objstring_members); - self.allocator.free(self.objstring_memberDefs); - self.allocator.free(self.objrange_members); - self.allocator.free(self.objrange_memberDefs); - } - - pub fn allocate(self: *Self, comptime T: type) !*T { - var timer = if (!is_wasm) std.time.Timer.start() catch unreachable else {}; - - self.bytes_allocated += @sizeOf(T); - - if (self.bytes_allocated > self.max_allocated) { - self.max_allocated = self.bytes_allocated; - } - - if (self.bytes_allocated > self.next_gc and BuildOptions.gc) { - try self.collectGarbage(); - } - - if (BuildOptions.memory_limit != null and self.bytes_allocated > BuildOptions.memory_limit.?) { - return error.ReachedMaximumMemoryUsage; - } - - const allocated = try self.allocator.create(T); - - if (BuildOptions.gc_debug) { - io.print( - "Allocated @{} {} for {} (now {}/{})\n", - .{ - @intFromPtr(allocated), - T, - std.fmt.fmtIntSizeDec(@sizeOf(T)), - std.fmt.fmtIntSizeDec(self.bytes_allocated), - std.fmt.fmtIntSizeDec(self.next_gc), - }, - ); - } - - if (!is_wasm) { - self.gc_time += timer.read(); - } - return allocated; - } - - pub fn allocateMany(self: *Self, comptime T: type, count: usize) ![]T { - var timer = if (!is_wasm) - std.time.Timer.start() catch unreachable - else {}; - - self.bytes_allocated += (@sizeOf(T) * count); - - if (self.bytes_allocated > self.max_allocated) { - self.max_allocated = self.bytes_allocated; - } - - if (self.bytes_allocated > self.next_gc and BuildOptions.gc) { - try self.collectGarbage(); - } - - if (!is_wasm) { - self.gc_time += timer.read(); - } - return try self.allocator.alloc(T, count); - } - - pub fn allocateObject(self: *Self, comptime T: type, data: T) !*T { - // var before: usize = self.bytes_allocated; - - const obj: *T = try self.allocate(T); - obj.* = data; - - const object: *o.Obj = switch (T) { - o.ObjString => o.ObjString.toObj(obj), - o.ObjTypeDef => o.ObjTypeDef.toObj(obj), - o.ObjUpValue => o.ObjUpValue.toObj(obj), - o.ObjClosure => o.ObjClosure.toObj(obj), - o.ObjFunction => o.ObjFunction.toObj(obj), - o.ObjObjectInstance => o.ObjObjectInstance.toObj(obj), - o.ObjObject => o.ObjObject.toObj(obj), - o.ObjList => o.ObjList.toObj(obj), - o.ObjMap => o.ObjMap.toObj(obj), - o.ObjEnum => o.ObjEnum.toObj(obj), - o.ObjEnumInstance => o.ObjEnumInstance.toObj(obj), - o.ObjBoundMethod => o.ObjBoundMethod.toObj(obj), - o.ObjNative => o.ObjNative.toObj(obj), - o.ObjUserData => o.ObjUserData.toObj(obj), - o.ObjPattern => o.ObjPattern.toObj(obj), - o.ObjFiber => o.ObjFiber.toObj(obj), - o.ObjForeignContainer => o.ObjForeignContainer.toObj(obj), - o.ObjRange => o.ObjRange.toObj(obj), - else => {}, - }; - - // if (BuildOptions.gc_debug) { - // io.print("allocated {*} {*}\n", .{ obj, object }); - // io.print("(from {}) {} allocated, total {}\n", .{ before, self.bytes_allocated - before, self.bytes_allocated }); - // } - - // Add new object at start of vm.objects linked list - try self.addObject(object); - - if (BuildOptions.gc_debug_access) { - self.debugger.?.allocated( - object, - self.where, - switch (T) { - o.ObjString => .String, - o.ObjTypeDef => .Type, - o.ObjUpValue => .UpValue, - o.ObjClosure => .Closure, - o.ObjFunction => .Function, - o.ObjObjectInstance => .ObjectInstance, - o.ObjObject => .Object, - o.ObjList => .List, - o.ObjMap => .Map, - o.ObjEnum => .Enum, - o.ObjEnumInstance => .EnumInstance, - o.ObjBoundMethod => .Bound, - o.ObjNative => .Native, - o.ObjUserData => .UserData, - o.ObjPattern => .Pattern, - o.ObjFiber => .Fiber, - o.ObjForeignContainer => .ForeignContainer, - o.ObjRange => .Range, - else => @panic("Unknown object type being allocated"), - }, - ); - } - - return obj; - } - - fn addObject(self: *Self, obj: *o.Obj) !void { - self.objects.prepend(&obj.node); - } - - pub fn allocateString(self: *Self, chars: []const u8) !*o.ObjString { - const string: *o.ObjString = try allocateObject( - self, - o.ObjString, - o.ObjString{ .string = chars }, - ); - - try self.strings.put( - self.allocator, - string.string, - string, - ); - - return string; - } - - pub fn copyString(self: *Self, chars: []const u8) !*o.ObjString { - if (self.strings.get(chars)) |interned| { - return interned; - } - - const copy: []u8 = try self.allocateMany(u8, chars.len); - std.mem.copyForwards(u8, copy, chars); - - if (BuildOptions.gc_debug) { - io.print("Allocated slice {*} `{s}`\n", .{ copy, copy }); - } - - return try allocateString(self, copy); - } - - fn free(self: *Self, comptime T: type, pointer: *T) void { - var timer = if (!is_wasm) std.time.Timer.start() catch unreachable else {}; - - if (BuildOptions.gc_debug) { - io.print("Going to free {*}\n", .{pointer}); - } - - self.bytes_allocated -= @sizeOf(T); - self.allocator.destroy(pointer); - - if (BuildOptions.gc_debug) { - io.print( - "(from {}), collected {}, {} allocated\n", - .{ - std.fmt.fmtIntSizeDec(self.bytes_allocated + @sizeOf(T)), - std.fmt.fmtIntSizeDec(@sizeOf(T)), - std.fmt.fmtIntSizeDec(self.bytes_allocated), - }, - ); - } - - if (!is_wasm) { - self.gc_time += timer.read(); - } - } - - fn freeMany(self: *Self, comptime T: type, pointer: []const T) void { - var timer = if (!is_wasm) std.time.Timer.start() catch unreachable else {}; - - if (BuildOptions.gc_debug) { - io.print("Going to free slice {*} `{s}`\n", .{ pointer, pointer }); - } - - const n: usize = (@sizeOf(T) * pointer.len); - self.bytes_allocated -= n; - self.allocator.free(pointer); - - if (BuildOptions.gc_debug) { - io.print( - "(from {}), collected {}, {} allocated\n", - .{ - std.fmt.fmtIntSizeDec(self.bytes_allocated + n), - std.fmt.fmtIntSizeDec(n), - std.fmt.fmtIntSizeDec(self.bytes_allocated), - }, - ); - } - - if (!is_wasm) { - self.gc_time += timer.read(); - } - } - - pub fn markObjDirty(self: *Self, obj: *o.Obj) !void { - if (!obj.dirty and self.obj_collected != obj) { - obj.dirty = true; - - // io.print( - // "Marked obj @{} {} dirty, gray_stack @{} or GC @{} will be {} items long\n", - // .{ - // @intFromPtr(obj), - // obj.obj_type, - // @intFromPtr(&self.gray_stack), - // @intFromPtr(self), - // self.gray_stack.items.len, - // }, - // ); - - // A dirty obj is: an old object with reference to potential young objects that will need to be marked - // Since old object are ignored when tracing references, this will force tracing for it - try self.gray_stack.append(self.allocator, obj); - } - } - - pub fn markObj(self: *Self, obj: *o.Obj) !void { - if (obj.marked or self.obj_collected == obj) { - if (BuildOptions.gc_debug) { - io.print( - "{*} {s} already marked or old\n", - .{ - obj, - try Value.fromObj(obj).toStringAlloc(self.allocator), - }, - ); - } - return; - } - - if (BuildOptions.gc_debug) { - io.print("marking {*}: ", .{obj}); - io.print( - "{s}\n", - .{ - try Value.fromObj(obj).toStringAlloc(self.allocator), - }, - ); - } - - obj.marked = true; - - // Move marked obj to tail so we sweeping can stop going through objects when finding the first marked object - self.objects.remove(&obj.node); - // Just to be safe, reset node before inserting it again - obj.node = .{ - .prev = null, - .next = null, - }; - self.objects.append(&obj.node); - - try self.gray_stack.append(self.allocator, obj); - } - - fn blackenObject(self: *Self, obj: *o.Obj) !void { - if (BuildOptions.gc_debug) { - io.print( - "blackening @{} {}\n", - .{ - @intFromPtr(obj), - obj.obj_type, - }, - ); - } - - obj.dirty = false; - - _ = try switch (obj.obj_type) { - .String => obj.access(o.ObjString, .String, self).?.mark(self), - .Type => obj.access(o.ObjTypeDef, .Type, self).?.mark(self), - .UpValue => obj.access(o.ObjUpValue, .UpValue, self).?.mark(self), - .Closure => obj.access(o.ObjClosure, .Closure, self).?.mark(self), - .Function => obj.access(o.ObjFunction, .Function, self).?.mark(self), - .ObjectInstance => obj.access(o.ObjObjectInstance, .ObjectInstance, self).?.mark(self), - .Object => obj.access(o.ObjObject, .Object, self).?.mark(self), - .List => obj.access(o.ObjList, .List, self).?.mark(self), - .Map => obj.access(o.ObjMap, .Map, self).?.mark(self), - .Enum => obj.access(o.ObjEnum, .Enum, self).?.mark(self), - .EnumInstance => obj.access(o.ObjEnumInstance, .EnumInstance, self).?.mark(self), - .Bound => obj.access(o.ObjBoundMethod, .Bound, self).?.mark(self), - .Native => obj.access(o.ObjNative, .Native, self).?.mark(self), - .UserData => obj.access(o.ObjUserData, .UserData, self).?.mark(self), - .Pattern => obj.access(o.ObjPattern, .Pattern, self).?.mark(self), - .Fiber => obj.access(o.ObjFiber, .Fiber, self).?.mark(self), - .ForeignContainer => obj.access(o.ObjForeignContainer, .ForeignContainer, self).?.mark(self), - .Range => obj.access(o.ObjRange, .Range, self).?.mark(self), - }; - - if (BuildOptions.gc_debug) { - io.print( - "done blackening @{} {}\n", - .{ - @intFromPtr(obj), - obj.obj_type, - }, - ); - } - } - - fn freeObj(self: *Self, obj: *o.Obj) (std.mem.Allocator.Error || std.fmt.BufPrintError)!void { - if (BuildOptions.gc_debug) { - io.print(">> freeing {} {}\n", .{ @intFromPtr(obj), obj.obj_type }); - } - - if (BuildOptions.gc_debug_access) { - self.debugger.?.collected(obj, self.where.?); - } - - self.obj_collected = obj; - defer self.obj_collected = null; - - switch (obj.obj_type) { - .String => { - const obj_string = o.ObjString.cast(obj).?; - - // Remove it from interned strings - _ = self.strings.remove(obj_string.string); - - freeMany(self, u8, obj_string.string); - free(self, o.ObjString, obj_string); - }, - .Pattern => { - var obj_pattern = o.ObjPattern.cast(obj).?; - if (!is_wasm) { - obj_pattern.pattern.free(); - } - - free(self, o.ObjPattern, obj_pattern); - }, - .Type => { - var obj_typedef = o.ObjTypeDef.cast(obj).?; - - const str = try obj_typedef.toStringAlloc(self.allocator, true); - defer self.allocator.free(str); - - if (self.type_registry.registry.get(str)) |registered_obj| { - if (registered_obj == obj_typedef) { - _ = self.type_registry.registry.remove(str); - if (BuildOptions.gc_debug) { - io.print("Removed registered type @{} `{s}`\n", .{ @intFromPtr(registered_obj), str }); - } - } else { - // io.print( - // "ObjTypeDef {*} `{s}` was allocated outside of type registry\n", - // .{ - // obj_typedef, - // try obj_typedef.toStringAlloc(self.allocator), - // }, - // ); - // unreachable; - // FIXME: this should not occur. Right now this because of the way we resolve placeholders by changing their content and replacing the type in the typ registry - // Previously registered same type is now outside of the type registry and is collected. - return; - } - } - - obj_typedef.deinit(); - - free(self, o.ObjTypeDef, obj_typedef); - }, - .UpValue => { - const obj_upvalue = o.ObjUpValue.cast(obj).?; - if (obj_upvalue.closed) |value| { - if (value.isObj()) { - try freeObj(self, value.obj()); - } - } - - free(self, o.ObjUpValue, obj_upvalue); - }, - .Closure => { - var obj_closure = o.ObjClosure.cast(obj).?; - obj_closure.deinit(self.allocator); - - free(self, o.ObjClosure, obj_closure); - }, - .Function => { - var obj_function = o.ObjFunction.cast(obj).?; - obj_function.deinit(); - - free(self, o.ObjFunction, obj_function); - }, - .ObjectInstance => { - var obj_objectinstance = o.ObjObjectInstance.cast(obj).?; - - // Calling eventual destructor method - if (obj_objectinstance.object) |object| { - if (object.type_def.resolved_type.?.Object.fields.get("collect")) |field| { - if (field.method and !field.static) { - if (BuildOptions.gc_debug_access) { - self.debugger.?.invoking_collector = true; - } - buzz_api.bz_invoke( - obj_objectinstance.vm, - obj_objectinstance.toValue(), - field.index, - null, - 0, - null, - ); - if (BuildOptions.gc_debug_access) { - self.debugger.?.invoking_collector = false; - } - - // Remove void result of the collect call - _ = obj_objectinstance.vm.pop(); - } - } - } - - obj_objectinstance.deinit(self.allocator); - - free(self, o.ObjObjectInstance, obj_objectinstance); - }, - .Object => { - var obj_object = o.ObjObject.cast(obj).?; - obj_object.deinit(self.allocator); - - free(self, o.ObjObject, obj_object); - }, - .List => { - var obj_list = o.ObjList.cast(obj).?; - obj_list.deinit(self.allocator); - - free(self, o.ObjList, obj_list); - }, - .Map => { - var obj_map = o.ObjMap.cast(obj).?; - obj_map.deinit(self.allocator); - - free(self, o.ObjMap, obj_map); - }, - .Enum => { - const obj_enum = o.ObjEnum.cast(obj).?; - self.allocator.free(obj_enum.cases); - - free(self, o.ObjEnum, obj_enum); - }, - .EnumInstance => free(self, o.ObjEnumInstance, o.ObjEnumInstance.cast(obj).?), - .Bound => free(self, o.ObjBoundMethod, o.ObjBoundMethod.cast(obj).?), - .Native => free(self, o.ObjNative, o.ObjNative.cast(obj).?), - .UserData => free(self, o.ObjUserData, o.ObjUserData.cast(obj).?), - .Fiber => { - var obj_fiber = o.ObjFiber.cast(obj).?; - obj_fiber.fiber.deinit(); - - self.allocator.destroy(obj_fiber.fiber); - - free(self, o.ObjFiber, obj_fiber); - }, - .ForeignContainer => { - const obj_foreignstruct = o.ObjForeignContainer.cast(obj).?; - - self.freeMany(u8, obj_foreignstruct.data); - - free(self, o.ObjForeignContainer, obj_foreignstruct); - }, - .Range => { - free(self, o.ObjRange, o.ObjRange.cast(obj).?); - }, - } - } - - pub fn markValue(self: *Self, value: Value) !void { - if (value.isObj()) { - try self.markObj(value.obj()); - } - } - - pub fn markFiber(self: *Self, fiber: *v.Fiber) !void { - var current_fiber: ?*v.Fiber = fiber; - while (current_fiber) |ufiber| { - try self.markObj(@constCast(ufiber.type_def.toObj())); - // Mark main fiber - if (BuildOptions.gc_debug) { - io.print("MARKING STACK OF FIBER @{}\n", .{@intFromPtr(ufiber)}); - } - var i: [*]Value = @ptrCast(fiber.stack); - while (@intFromPtr(i) < @intFromPtr(fiber.stack_top)) : (i += 1) { - try self.markValue(i[0]); - } - if (BuildOptions.gc_debug) { - io.print("DONE MARKING STACK OF FIBER @{}\n", .{@intFromPtr(ufiber)}); - } - - // Mark closure - if (BuildOptions.gc_debug) { - io.print("MARKING FRAMES OF FIBER @{}\n", .{@intFromPtr(ufiber)}); - } - for (fiber.frames.items) |frame| { - try self.markObj(frame.closure.toObj()); - if (frame.error_value) |error_value| { - try self.markValue(error_value); - } - if (frame.native_call_error_value) |error_value| { - try self.markValue(error_value); - } - } - if (BuildOptions.gc_debug) { - io.print("DONE MARKING FRAMES OF FIBER @{}\n", .{@intFromPtr(ufiber)}); - } - - // Mark opened upvalues - if (BuildOptions.gc_debug) { - io.print("MARKING UPVALUES OF FIBER @{}\n", .{@intFromPtr(ufiber)}); - } - if (fiber.open_upvalues) |open_upvalues| { - var upvalue: ?*o.ObjUpValue = open_upvalues; - while (upvalue) |unwrapped| : (upvalue = unwrapped.next) { - try self.markObj(unwrapped.toObj()); - } - } - if (BuildOptions.gc_debug) { - io.print("DONE MARKING UPVALUES OF FIBER @{}\n", .{@intFromPtr(ufiber)}); - } - - current_fiber = ufiber.parent_fiber; - } - } - - fn markMethods(self: *Self) !void { - if (BuildOptions.gc_debug) { - io.print("MARKING BASIC TYPES METHOD\n", .{}); - } - // Mark basic types methods - for (self.objfiber_members) |member| { - if (member) |umember| { - try self.markObj(umember.toObj()); - } - } - - for (self.objfiber_memberDefs) |def| { - if (def) |udef| { - try self.markObj(udef.toObj()); - } - } - - for (self.objrange_members) |member| { - if (member) |umember| { - try self.markObj(umember.toObj()); - } - } - - for (self.objrange_memberDefs) |def| { - if (def) |udef| { - try self.markObj(udef.toObj()); - } - } - - for (self.objstring_members) |member| { - if (member) |umember| { - try self.markObj(umember.toObj()); - } - } - - for (self.objstring_memberDefs) |def| { - if (def) |udef| { - try self.markObj(udef.toObj()); - } - } - - for (self.objpattern_members) |member| { - if (member) |umember| { - try self.markObj(umember.toObj()); - } - } - - for (self.objpattern_memberDefs) |def| { - if (def) |udef| { - try self.markObj(udef.toObj()); - } - } - - if (BuildOptions.gc_debug) { - io.print("DONE MARKING BASIC TYPES METHOD\n", .{}); - } - } - - fn markRoots(self: *Self, vm: *v.VM) !void { - // FIXME: We should not need this, but we don't know how to prevent collection before the VM actually starts making reference to them - try self.type_registry.mark(); - - try self.markMethods(); - - // Mark special strings we always need - if (self.strings.get("$")) |dollar| { - try self.markObj(dollar.toObj()); - } - - // Mark import registry - var it = vm.import_registry.iterator(); - while (it.next()) |kv| { - try self.markObj(kv.key_ptr.*.toObj()); - for (kv.value_ptr.*) |global| { - try self.markValue(global); - } - } - - // Mark current fiber and its parent fibers - try markFiber(self, vm.current_fiber); - - // Mark globals - if (BuildOptions.gc_debug) { - io.print("MARKING GLOBALS OF VM @{}\n", .{@intFromPtr(vm)}); - } - for (vm.globals.items) |global| { - try self.markValue(global); - } - if (BuildOptions.gc_debug) { - io.print("DONE MARKING GLOBALS OF VM @{}\n", .{@intFromPtr(vm)}); - } - - // Mark ast constant values (some are only referenced by the JIT so might be collected before) - // TODO: does this takes too long or are we saved by vertue of MultiArrayList? - for (vm.current_ast.nodes.items(.value)) |valueOpt| { - if (valueOpt) |value| { - try self.markValue(value); - } - } - } - - fn traceReference(self: *Self) !void { - if (BuildOptions.gc_debug) { - io.print("TRACING REFERENCE\n", .{}); - } - while (self.gray_stack.items.len > 0) { - try blackenObject(self, self.gray_stack.pop().?); - } - if (BuildOptions.gc_debug) { - io.print("DONE TRACING REFERENCE\n", .{}); - } - } - - fn sweep(self: *Self, mode: Mode) !void { - const swept: usize = self.bytes_allocated; - - var obj_count: usize = 0; - var obj_node = self.objects.first; - var count: usize = 0; - while (obj_node) |node| : (count += 1) { - const obj: *o.Obj = @fieldParentPtr("node", node); - const marked = obj.marked; - if (marked) { - if (BuildOptions.gc_debug and mode == .Full) { - io.print( - "UNMARKING @{}\n", - .{ - @intFromPtr( - @as(*o.Obj, @fieldParentPtr("node", node)), - ), - }, - ); - } - // If not a full gc, we reset marked, this object is now 'old' - obj.marked = if (mode == .Full) false else marked; - - // If a young collection we don't reset marked flags and since we move all marked object - // to the tail of the list, we can stop here, there's no more objects to collect - if (mode == .Young) { - break; - } - - obj_node = node.next; - } else { - const unreached: *o.Obj = obj; - obj_node = node.next; - - self.objects.remove(node); - - try freeObj(self, unreached); - obj_count += 1; - } - } - - if (BuildOptions.gc_debug or BuildOptions.gc_debug_light) { - if (swept < self.bytes_allocated) { - io.print("Warn: sweep gained memory, possibly due to an o.Object collector that takes up memory\n", .{}); - } - - io.print( - "\nSwept {} objects for {}, now {}\n", - .{ - obj_count, - std.fmt.fmtIntSizeDec(@max(swept, self.bytes_allocated) - self.bytes_allocated), - std.fmt.fmtIntSizeDec(self.bytes_allocated), - }, - ); - } - } - - pub fn collectGarbage(self: *Self) !void { - var timer = if (!is_wasm) std.time.Timer.start() catch unreachable else {}; - - // Don't collect until a VM is actually running - var vm_it = self.active_vms.iterator(); - const first_vm = vm_it.next(); - if (first_vm == null or first_vm.?.key_ptr.*.flavor == .Repl) { - return; - } - - // Avoid triggering another sweep while running collectors - if (self.obj_collected != null) { - return; - } - - const mode: Mode = if (self.bytes_allocated > self.next_full_gc and self.last_gc != null) .Full else .Young; - - if (BuildOptions.gc_debug or BuildOptions.gc_debug_light) { - io.print( - "-- gc starts mode {s}, {}, {} objects\n", - .{ - @tagName(mode), - std.fmt.fmtIntSizeDec(self.bytes_allocated), - self.objects.len(), - }, - ); - } - - var it = self.active_vms.iterator(); - while (it.next()) |kv| { - var vm = kv.key_ptr.*; - - if (BuildOptions.gc_debug) { - io.print( - "\tMarking VM @{}, on fiber @{} and closure @{} (function @{} {s})\n", - .{ - @intFromPtr(vm), - @intFromPtr(vm.current_fiber), - @intFromPtr(vm.currentFrame().?.closure), - @intFromPtr(vm.currentFrame().?.closure.function), - vm.currentFrame().?.closure.function.type_def.resolved_type.?.Function.name.string, - }, - ); - } - - try markRoots(self, vm); - } - - try traceReference(self); - - try sweep(self, mode); - - if (mode == .Full) { - self.full_collection_count += 1; - } else { - self.light_collection_count += 1; - } - - self.next_gc = self.bytes_allocated * BuildOptions.next_gc_ratio; - if (mode == .Full) { - self.next_full_gc = self.bytes_allocated * BuildOptions.next_full_gc_ratio; - } - self.last_gc = mode; - - if (BuildOptions.gc_debug or BuildOptions.gc_debug_light) { - io.print( - "-- gc end, {}, {} objects, next_gc {}, next_full_gc {}\n", - .{ - std.fmt.fmtIntSizeDec(self.bytes_allocated), - self.objects.len(), - std.fmt.fmtIntSizeDec(self.next_gc), - std.fmt.fmtIntSizeDec(self.next_full_gc), - }, - ); - } - // io.print("gc took {}ms\n", .{timer.read() / 1000000}); - - if (!is_wasm) { - self.gc_time += timer.read(); - } - } -}; - -pub const GarbageCollectorDebugger = struct { - const Self = @This(); - - pub const Ptr = struct { - what: o.ObjType, - allocated_at: ?Token, - collected_at: ?Token = null, - }; - - allocator: std.mem.Allocator, - reporter: Reporter, - tracker: std.AutoHashMapUnmanaged(*o.Obj, Ptr), - invoking_collector: bool = false, - - pub fn init(allocator: std.mem.Allocator) Self { - return .{ - .allocator = allocator, - .tracker = std.AutoHashMapUnmanaged(*o.Obj, Ptr){}, - .reporter = Reporter{ - .allocator = allocator, - }, - }; - } - - pub fn deinit(self: *Self) void { - self.tracker.deinit(self.allocator); - } - - pub fn allocated(self: *Self, ptr: *o.Obj, at: ?Token, what: o.ObjType) void { - std.debug.assert(self.tracker.get(ptr) == null); - self.tracker.put( - self.allocator, - ptr, - Ptr{ - .what = what, - .allocated_at = at, - }, - ) catch @panic("Could not track object"); - } - - pub fn collected(self: *Self, ptr: *o.Obj, at: Token) void { - if (self.tracker.getPtr(ptr)) |tracked| { - if (tracked.collected_at) |collected_at| { - self.reporter.reportWithOrigin( - .gc, - at, - at, - collected_at, - collected_at, - "Trying to collected already collected {} {*}", - .{ tracked.what, ptr }, - "first collected here", - ); - - @panic("Double collect"); - } - - tracked.collected_at = at; - } else { - @panic("Collect of untracked object"); - } - } - - pub fn accessed(self: *Self, ptr: *o.Obj, at: ?Token) void { - if (self.invoking_collector) return; - - if (self.tracker.getPtr(ptr)) |tracked| { - if (tracked.collected_at) |collected_at| { - var items = std.array_list.Managed(Reporter.ReportItem).init(self.allocator); - defer items.deinit(); - - var message = std.array_list.Managed(u8).init(self.allocator); - defer message.deinit(); - - message.writer().print( - "Access to already collected {} {*}", - .{ - tracked.what, - ptr, - }, - ) catch unreachable; - - items.append( - .{ - .location = at.?, - .end_location = at.?, - .kind = .@"error", - .message = message.items, - }, - ) catch unreachable; - - if (tracked.allocated_at) |allocated_at| { - items.append( - .{ - .location = allocated_at, - .end_location = allocated_at, - .kind = .hint, - .message = "allocated here", - }, - ) catch unreachable; - } - - items.append( - .{ - .location = collected_at, - .end_location = collected_at, - .kind = .hint, - .message = "collected here", - }, - ) catch unreachable; - - var report = Reporter.Report{ - .message = message.items, - .error_type = .gc, - .items = items.items, - }; - - report.reportStderr(&self.reporter) catch unreachable; - - @panic("Access to already collected object"); - } - } else { - if (at) |accessed_at| { - self.reporter.reportErrorFmt( - .gc, - accessed_at, - accessed_at, - "Untracked obj {*}", - .{ - ptr, - }, - ); - } else { - io.print( - "Untracked obj {*}\n", - .{ - ptr, - }, - ); - } - - @panic("Access to untracked object"); - } - } -}; diff --git a/src/obj.zig b/src/obj.zig index 1d1d92bb..c4973fce 100644 --- a/src/obj.zig +++ b/src/obj.zig @@ -9,9 +9,8 @@ const _vm = @import("vm.zig"); const VM = _vm.VM; const Fiber = _vm.Fiber; const Parser = @import("Parser.zig"); -const _memory = @import("memory.zig"); -const GarbageCollector = _memory.GarbageCollector; -const TypeRegistry = _memory.TypeRegistry; +const GC = @import("GC.zig"); +const TypeRegistry = @import("TypeRegistry.zig"); const v = @import("value.zig"); const Integer = v.Integer; const Value = v.Value; @@ -71,7 +70,7 @@ pub const Obj = struct { return @alignCast(@fieldParentPtr("obj", obj)); } - pub fn access(obj: *Obj, comptime T: type, obj_type: ObjType, gc: *GarbageCollector) ?*T { + pub fn access(obj: *Obj, comptime T: type, obj_type: ObjType, gc: *GC) ?*T { if (BuildOptions.gc_debug_access) { gc.debugger.?.accessed(obj, gc.where); } @@ -331,7 +330,7 @@ pub const Obj = struct { } } - pub fn typeOf(self: *Self, gc: *GarbageCollector) error{ OutOfMemory, NoSpaceLeft, ReachedMaximumMemoryUsage }!*ObjTypeDef { + pub fn typeOf(self: *Self, gc: *GC) error{ OutOfMemory, NoSpaceLeft, ReachedMaximumMemoryUsage }!*ObjTypeDef { return switch (self.obj_type) { .Range => gc.type_registry.rg_type, .String => gc.type_registry.str_type, @@ -720,7 +719,7 @@ pub const ObjFiber = struct { fiber: *Fiber, - pub fn mark(self: *Self, gc: *GarbageCollector) !void { + pub fn mark(self: *Self, gc: *GC) !void { try gc.markFiber(self.fiber); } @@ -813,7 +812,7 @@ pub const ObjFiber = struct { return_type: *ObjTypeDef, yield_type: *ObjTypeDef, - pub fn mark(self: *SelfFiberDef, gc: *GarbageCollector) !void { + pub fn mark(self: *SelfFiberDef, gc: *GC) !void { try gc.markObj(@constCast(self.return_type.toObj())); try gc.markObj(@constCast(self.yield_type.toObj())); } @@ -834,7 +833,7 @@ pub const ObjPattern = struct { source: []const u8, pattern: Pattern, - pub fn mark(_: *Self, _: *GarbageCollector) !void {} + pub fn mark(_: *Self, _: *GC) !void {} pub inline fn toObj(self: *Self) *Obj { return &self.obj; @@ -945,7 +944,7 @@ pub const ObjUserData = struct { userdata: u64, - pub fn mark(_: *Self, _: *GarbageCollector) void {} + pub fn mark(_: *Self, _: *GC) void {} pub inline fn toObj(self: *Self) *Obj { return &self.obj; @@ -969,7 +968,7 @@ pub const ObjString = struct { /// The actual string string: []const u8, - pub fn mark(_: *Self, _: *GarbageCollector) !void {} + pub fn mark(_: *Self, _: *GC) !void {} pub inline fn toObj(self: *Self) *Obj { return &self.obj; @@ -1146,7 +1145,7 @@ pub const ObjUpValue = struct { }; } - pub fn mark(self: *Self, gc: *GarbageCollector) !void { + pub fn mark(self: *Self, gc: *GC) !void { try gc.markValue(self.location.*); // Useless if (self.closed) |uclosed| { try gc.markValue(uclosed); @@ -1187,7 +1186,7 @@ pub const ObjClosure = struct { }; } - pub fn mark(self: *Self, gc: *GarbageCollector) !void { + pub fn mark(self: *Self, gc: *GC) !void { try gc.markObj(self.function.toObj()); for (self.upvalues) |upvalue| { try gc.markObj(upvalue.toObj()); @@ -1242,7 +1241,7 @@ pub const ObjNative = struct { // type_def: *ObjTypeDef, native: *anyopaque, - pub fn mark(_: *Self, _: *GarbageCollector) void {} + pub fn mark(_: *Self, _: *GC) void {} pub inline fn toObj(self: *Self) *Obj { return &self.obj; @@ -1319,7 +1318,7 @@ pub const ObjFunction = struct { self.chunk.deinit(); } - pub fn mark(self: *Self, gc: *GarbageCollector) !void { + pub fn mark(self: *Self, gc: *GC) !void { try gc.markObj(@constCast(self.type_def.toObj())); if (BuildOptions.gc_debug) { io.print( @@ -1383,7 +1382,7 @@ pub const ObjFunction = struct { return FunctionDefSelf.next_id; } - pub fn mark(self: *FunctionDefSelf, gc: *GarbageCollector) !void { + pub fn mark(self: *FunctionDefSelf, gc: *GC) !void { try gc.markObj(self.name.toObj()); try gc.markObj(self.script_name.toObj()); try gc.markObj(@constCast(self.return_type.toObj())); @@ -1431,13 +1430,13 @@ pub const ObjObjectInstance = struct { /// VM in which the instance was created, we need this so the instance destructor can be called in the appropriate vm vm: *VM, - pub fn setField(self: *Self, gc: *GarbageCollector, key: usize, value: Value) !void { + pub fn setField(self: *Self, gc: *GC, key: usize, value: Value) !void { self.fields[key] = value; try gc.markObjDirty(&self.obj); } /// Should not be called by runtime when possible - pub fn setFieldByName(self: *Self, gc: *GarbageCollector, key: *ObjString, value: Value) !void { + pub fn setFieldByName(self: *Self, gc: *GC, key: *ObjString, value: Value) !void { const object_def = self.type_def.resolved_type.?.ObjectInstance.resolved_type.?.Object; const index = std.mem.indexOf( *ObjString, @@ -1456,7 +1455,7 @@ pub const ObjObjectInstance = struct { vm: *VM, object: ?*ObjObject, type_def: *ObjTypeDef, - gc: *GarbageCollector, + gc: *GC, ) !Self { return .{ .vm = vm, @@ -1471,7 +1470,7 @@ pub const ObjObjectInstance = struct { }; } - pub fn mark(self: *Self, gc: *GarbageCollector) !void { + pub fn mark(self: *Self, gc: *GC) !void { if (self.object) |object| { try gc.markObj(object.toObj()); } @@ -1570,7 +1569,7 @@ pub const ObjForeignContainer = struct { // Filled by codegen fields: std.StringArrayHashMap(Field), - pub fn mark(def: *ContainerDef, gc: *GarbageCollector) !void { + pub fn mark(def: *ContainerDef, gc: *GC) !void { try gc.markObj(def.name.toObj()); try gc.markObj(def.qualified_name.toObj()); var it = def.buzz_type.iterator(); @@ -1580,7 +1579,7 @@ pub const ObjForeignContainer = struct { } }; - pub fn mark(self: *Self, gc: *GarbageCollector) !void { + pub fn mark(self: *Self, gc: *GC) !void { try gc.markObj(@constCast(self.type_def.toObj())); } @@ -1648,22 +1647,22 @@ pub const ObjObject = struct { return property_count; } - pub fn setField(self: *Self, gc: *GarbageCollector, key: usize, value: Value) !void { + pub fn setField(self: *Self, gc: *GC, key: usize, value: Value) !void { self.fields[key] = value; try gc.markObjDirty(&self.obj); } - pub fn setDefault(self: *Self, gc: *GarbageCollector, key: usize, value: Value) !void { + pub fn setDefault(self: *Self, gc: *GC, key: usize, value: Value) !void { self.defaults[key] = value; try gc.markObjDirty(&self.obj); } - pub fn setPropertyDefaultValue(self: *Self, gc: *GarbageCollector, key: usize, value: Value) !void { + pub fn setPropertyDefaultValue(self: *Self, gc: *GC, key: usize, value: Value) !void { self.defaults[key] = value; try gc.markObjDirty(&self.obj); } - pub fn mark(self: *Self, gc: *GarbageCollector) !void { + pub fn mark(self: *Self, gc: *GC) !void { try gc.markObj(@constCast(self.type_def.toObj())); for (self.fields) |field| { @@ -1723,7 +1722,7 @@ pub const ObjObject = struct { self.methods_locations.deinit(); } - pub fn mark(self: *ProtocolDefSelf, gc: *GarbageCollector) !void { + pub fn mark(self: *ProtocolDefSelf, gc: *GC) !void { try gc.markObj(self.name.toObj()); try gc.markObj(self.qualified_name.toObj()); @@ -1890,7 +1889,7 @@ pub const ObjObject = struct { return null; } - pub fn mark(self: *ObjectDef, gc: *GarbageCollector) !void { + pub fn mark(self: *ObjectDef, gc: *GC) !void { try gc.markObj(self.name.toObj()); try gc.markObj(self.qualified_name.toObj()); @@ -1953,7 +1952,7 @@ pub const ObjList = struct { return self; } - pub fn mark(self: *Self, gc: *GarbageCollector) !void { + pub fn mark(self: *Self, gc: *GC) !void { for (self.items.items) |value| { try gc.markValue(value); } @@ -2052,7 +2051,7 @@ pub const ObjList = struct { return native.toValue(); } - pub fn rawAppend(self: *Self, gc: *GarbageCollector, value: Value) !void { + pub fn rawAppend(self: *Self, gc: *GC, value: Value) !void { try self.items.append( gc.allocator, value, @@ -2060,7 +2059,7 @@ pub const ObjList = struct { try gc.markObjDirty(&self.obj); } - pub fn rawInsert(self: *Self, gc: *GarbageCollector, index: usize, value: Value) !void { + pub fn rawInsert(self: *Self, gc: *GC, index: usize, value: Value) !void { try self.items.insert( gc.allocator, index, @@ -2069,7 +2068,7 @@ pub const ObjList = struct { try gc.markObjDirty(&self.obj); } - pub fn set(self: *Self, gc: *GarbageCollector, index: usize, value: Value) !void { + pub fn set(self: *Self, gc: *GC, index: usize, value: Value) !void { self.items.items[index] = value; try gc.markObjDirty(&self.obj); } @@ -2119,7 +2118,7 @@ pub const ObjList = struct { self.methods.deinit(); } - pub fn mark(self: *SelfListDef, gc: *GarbageCollector) !void { + pub fn mark(self: *SelfListDef, gc: *GC) !void { try gc.markObj(@constCast(self.item_type.toObj())); var it = self.methods.iterator(); while (it.next()) |method| { @@ -3079,7 +3078,7 @@ pub const ObjRange = struct { low: Integer, high: Integer, - pub fn mark(_: *Self, _: *GarbageCollector) void {} + pub fn mark(_: *Self, _: *GC) void {} pub inline fn toObj(self: *Self) *Obj { return &self.obj; @@ -3209,7 +3208,7 @@ pub const ObjMap = struct { return self; } - pub fn set(self: *Self, gc: *GarbageCollector, key: Value, value: Value) !void { + pub fn set(self: *Self, gc: *GC, key: Value, value: Value) !void { try self.map.put( gc.allocator, key, @@ -3277,7 +3276,7 @@ pub const ObjMap = struct { return native.toValue(); } - pub fn mark(self: *Self, gc: *GarbageCollector) !void { + pub fn mark(self: *Self, gc: *GC) !void { var it = self.map.iterator(); while (it.next()) |kv| { try gc.markValue(kv.key_ptr.*); @@ -3353,7 +3352,7 @@ pub const ObjMap = struct { self.methods.deinit(); } - pub fn mark(self: *SelfMapDef, gc: *GarbageCollector) !void { + pub fn mark(self: *SelfMapDef, gc: *GC) !void { try gc.markObj(@constCast(self.key_type.toObj())); try gc.markObj(@constCast(self.value_type.toObj())); var it = self.methods.iterator(); @@ -4138,7 +4137,7 @@ pub const ObjEnum = struct { }; } - pub fn mark(self: *Self, gc: *GarbageCollector) !void { + pub fn mark(self: *Self, gc: *GC) !void { try gc.markObj(@constCast(self.type_def.toObj())); for (self.cases) |case| { try gc.markValue(case); @@ -4189,7 +4188,7 @@ pub const ObjEnum = struct { // Circular reference but needed so that we can generate enum case at compile time value: ?*ObjEnum = null, - pub fn mark(self: *EnumDefSelf, gc: *GarbageCollector) !void { + pub fn mark(self: *EnumDefSelf, gc: *GC) !void { try gc.markObj(self.name.toObj()); try gc.markObj(self.qualified_name.toObj()); try gc.markObj(@constCast(self.enum_type.toObj())); @@ -4205,7 +4204,7 @@ pub const ObjEnumInstance = struct { enum_ref: *ObjEnum, case: u24, - pub fn mark(self: *Self, gc: *GarbageCollector) !void { + pub fn mark(self: *Self, gc: *GC) !void { try gc.markObj(self.enum_ref.toObj()); } @@ -4236,7 +4235,7 @@ pub const ObjBoundMethod = struct { closure: ?*ObjClosure = null, native: ?*ObjNative = null, - pub fn mark(self: *Self, gc: *GarbageCollector) !void { + pub fn mark(self: *Self, gc: *GC) !void { try gc.markValue(self.receiver); if (self.closure) |closure| { try gc.markObj(closure.toObj()); @@ -4350,7 +4349,7 @@ pub const ObjTypeDef = struct { }; } - pub fn mark(self: *Self, gc: *GarbageCollector) !void { + pub fn mark(self: *Self, gc: *GC) !void { if (self.resolved_type) |*resolved| { if (resolved.* == .ObjectInstance) { try gc.markObj(@constCast(resolved.ObjectInstance.of.toObj())); @@ -4513,7 +4512,10 @@ pub const ObjTypeDef = struct { resolved.generic_types.deinit(type_registry.gc.allocator); resolved.generic_types = try old_object_def.generic_types.clone(type_registry.gc.allocator); - resolved.resolved_generics = generics; + + if (resolved.generic_types.count() == generics.len) { + resolved.resolved_generics = generics; + } { var fields = std.StringArrayHashMapUnmanaged(ObjObject.ObjectDef.Field){}; @@ -5356,9 +5358,10 @@ pub const ObjTypeDef = struct { .UserData, .Type, .Range, - .Any, => unreachable, + .Any => expected.Any == actual.Any, + .ForeignContainer => std.mem.eql(u8, expected.ForeignContainer.name.string, actual.ForeignContainer.name.string), .Generic => expected.Generic.origin == actual.Generic.origin and expected.Generic.index == actual.Generic.index, diff --git a/src/repl.zig b/src/repl.zig index 3f77fcfe..8f179d0c 100644 --- a/src/repl.zig +++ b/src/repl.zig @@ -4,7 +4,6 @@ const is_wasm = builtin.cpu.arch.isWasm(); const BuildOptions = @import("build_options"); const v = @import("vm.zig"); -const memory = @import("memory.zig"); const obj = @import("obj.zig"); const Parser = @import("Parser.zig"); const JIT = @import("Jit.zig"); @@ -14,6 +13,8 @@ const disassembler = @import("disassembler.zig"); const CodeGen = @import("Codegen.zig"); const Scanner = @import("Scanner.zig"); const io = @import("io.zig"); +const GC = @import("GC.zig"); +const TypeRegistry = @import("TypeRegistry.zig"); pub const PROMPT = ">>> "; pub const MULTILINE_PROMPT = "... "; @@ -73,8 +74,8 @@ pub fn repl(allocator: std.mem.Allocator) !void { false; var import_registry = v.ImportRegistry{}; - var gc = try memory.GarbageCollector.init(allocator); - gc.type_registry = try memory.TypeRegistry.init(&gc); + var gc = try GC.init(allocator); + gc.type_registry = try TypeRegistry.init(&gc); var imports = std.StringHashMapUnmanaged(Parser.ScriptImport){}; var vm = try v.VM.init(&gc, &import_registry, .Repl); vm.jit = if (BuildOptions.jit and BuildOptions.cycle_limit == null) @@ -142,7 +143,7 @@ pub fn repl(allocator: std.mem.Allocator) !void { var previous_global_top = vm.globals_count; var previous_parser_globals = try parser.globals.clone(allocator); var previous_globals = try vm.globals.clone(allocator); - var previous_type_registry = try gc.type_registry.registry.clone(); + var previous_type_registry = try gc.type_registry.registry.clone(allocator); var previous_input: ?[]u8 = null; var reader_buffer = [_]u8{0}; @@ -242,7 +243,7 @@ pub fn repl(allocator: std.mem.Allocator) !void { // previous_globals.deinit(); previous_globals = try vm.globals.clone(allocator); // previous_type_registry.deinit(); - previous_type_registry = try gc.type_registry.registry.clone(); + previous_type_registry = try gc.type_registry.registry.clone(allocator); // Dump top of stack if (previous_global_top != vm.globals_count or expr != null) { @@ -306,7 +307,7 @@ fn runSource( vm: *v.VM, codegen: *CodeGen, parser: *Parser, - gc: *memory.GarbageCollector, + gc: *GC, ) !?Value { var total_timer = std.time.Timer.start() catch unreachable; var timer = try std.time.Timer.start(); diff --git a/src/tests/fmt.zig b/src/tests/fmt.zig index d04f9f72..3fe17a5b 100644 --- a/src/tests/fmt.zig +++ b/src/tests/fmt.zig @@ -2,7 +2,8 @@ const std = @import("std"); const assert = std.debug.assert; const Ast = @import("../Ast.zig"); const Parser = @import("../Parser.zig"); -const mem = @import("../memory.zig"); +const GC = @import("../GC.zig"); +const TypeRegistry = @import("../TypeRegistry.zig"); const Renderer = @import("../renderer.zig").Renderer; const WriteableArrayList = @import("../writeable_array_list.zig").WriteableArrayList; @@ -15,8 +16,8 @@ fn testFmt( defer arena.deinit(); const allocator = arena.allocator(); - var gc = try mem.GarbageCollector.init(allocator); - gc.type_registry = try mem.TypeRegistry.init(&gc); + var gc = try GC.init(allocator); + gc.type_registry = try TypeRegistry.init(&gc); var imports = std.StringHashMapUnmanaged(Parser.ScriptImport){}; var parser = Parser.init( &gc, diff --git a/src/value.zig b/src/value.zig index 1ab3c48e..1b031262 100644 --- a/src/value.zig +++ b/src/value.zig @@ -3,7 +3,7 @@ const Allocator = std.mem.Allocator; const StringHashMap = std.StringHashMap; const o = @import("obj.zig"); const VM = @import("vm.zig").VM; -const GarbageCollector = @import("memory.zig").GarbageCollector; +const GC = @import("GC.zig"); pub const Double = f64; pub const Integer = i48; @@ -131,7 +131,7 @@ pub const Value = packed struct { return if (self.isObj()) self.obj() else null; } - pub fn typeOf(self: Value, gc: *GarbageCollector) !*o.ObjTypeDef { + pub fn typeOf(self: Value, gc: *GC) !*o.ObjTypeDef { if (self.isObj()) { return try self.obj().typeOf(gc); } diff --git a/src/vm.zig b/src/vm.zig index f85ae0cd..2aa0176f 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -7,7 +7,7 @@ const Ast = @import("Ast.zig"); const disassembler = @import("disassembler.zig"); const obj = @import("obj.zig"); const BuildOptions = @import("build_options"); -const memory = @import("memory.zig"); +const GC = @import("GC.zig"); const is_wasm = builtin.cpu.arch.isWasm(); const JIT = if (!is_wasm) @import("Jit.zig") else void; const Token = @import("Token.zig"); @@ -424,7 +424,7 @@ pub const VM = struct { Custom, // TODO: remove when user can use this set directly in buzz code } || std.mem.Allocator.Error || std.fmt.BufPrintError; - gc: *memory.GarbageCollector, + gc: *GC, current_fiber: *Fiber, current_ast: Ast.Slice, globals: std.ArrayList(Value) = .{}, @@ -437,7 +437,7 @@ pub const VM = struct { reporter: Reporter, ffi: FFI, - pub fn init(gc: *memory.GarbageCollector, import_registry: *ImportRegistry, flavor: RunFlavor) !Self { + pub fn init(gc: *GC, import_registry: *ImportRegistry, flavor: RunFlavor) !Self { return .{ .gc = gc, .import_registry = import_registry, diff --git a/src/wasm_repl.zig b/src/wasm_repl.zig index 41dd9b25..80a31852 100644 --- a/src/wasm_repl.zig +++ b/src/wasm_repl.zig @@ -5,9 +5,8 @@ const VM = _vm.VM; const ImportRegistry = _vm.ImportRegistry; const wasm = @import("wasm.zig"); const BuildOptions = @import("build_options"); -const _mem = @import("memory.zig"); -const GarbageCollector = _mem.GarbageCollector; -const TypeRegistry = _mem.TypeRegistry; +const GC = @import("GC.zig"); +const TypeRegistry = @import("TypeRegistry.zig"); const _obj = @import("obj.zig"); const ObjTypeDef = _obj.ObjTypeDef; const Parser = @import("Parser.zig"); @@ -35,8 +34,8 @@ pub export fn initRepl() *ReplCtx { const import_registry = allocator.create(ImportRegistry) catch unreachable; import_registry.* = .{}; - const gc = allocator.create(GarbageCollector) catch unreachable; - gc.* = GarbageCollector.init(allocator) catch unreachable; + const gc = allocator.create(GC) catch unreachable; + gc.* = GC.init(allocator) catch unreachable; gc.type_registry = TypeRegistry.init(gc) catch unreachable; const imports = allocator.create(std.StringHashMapUnmanaged(Parser.ScriptImport)) catch unreachable; @@ -103,7 +102,7 @@ pub export fn runLine(ctx: *ReplCtx) void { var previous_global_top = ctx.vm.globals_count; var previous_parser_globals = ctx.parser.globals.clone(ctx.parser.gc.allocator) catch unreachable; var previous_globals = ctx.vm.globals.clone(ctx.parser.gc.allocator) catch unreachable; - var previous_type_registry = ctx.vm.gc.type_registry.registry.clone() catch unreachable; + var previous_type_registry = ctx.vm.gc.type_registry.registry.clone(ctx.parser.gc.allocator) catch unreachable; const source = stdin.readAll(ctx.vm.gc.allocator) catch unreachable; errdefer ctx.vm.gc.allocator.free(source); @@ -134,7 +133,7 @@ pub export fn runLine(ctx: *ReplCtx) void { // previous_globals.deinit(); previous_globals = ctx.vm.globals.clone(ctx.parser.gc.allocator) catch unreachable; // previous_type_registry.deinit(); - previous_type_registry = ctx.vm.gc.type_registry.registry.clone() catch unreachable; + previous_type_registry = ctx.vm.gc.type_registry.registry.clone(ctx.parser.gc.allocator) catch unreachable; // Dump top of stack if (previous_global_top != ctx.vm.globals_count or expr != null) {