diff --git a/source/coral/arena.zig b/source/coral/arena.zig index a9719bf..a6509ad 100755 --- a/source/coral/arena.zig +++ b/source/coral/arena.zig @@ -49,7 +49,7 @@ pub const Stacking = struct { } fn allocate_page(self: *Stacking, page_size: usize) io.AllocationError!*Page { - var buffer = try io.allocate_many(u8, page_size, self.base_allocator); + var buffer = try io.allocate_many(self.base_allocator, page_size, u8); errdefer io.deallocate(self.base_allocator, buffer); diff --git a/source/coral/io.zig b/source/coral/io.zig index 90cbb23..400c564 100755 --- a/source/coral/io.zig +++ b/source/coral/io.zig @@ -7,6 +7,7 @@ pub const AllocationError = error { }; pub const AllocationOptions = struct { + return_address: usize, allocation: ?[]u8 = null, size: usize, }; @@ -133,57 +134,17 @@ pub const FixedBuffer = struct { } }; -pub const GrowingBuffer = struct { - allocator: Allocator, - appender: Appender, - - const AppendOptions = struct { - allocator: Allocator, - bytes: []const u8, - }; - - const Appender = Generator(AllocationError!void, AppendOptions); - - pub fn as_writer(self: *GrowingBuffer) Writer { - return Writer.bind(GrowingBuffer, self, struct { - fn write(growing_buffer: *GrowingBuffer, bytes: []const u8) ?usize { - growing_buffer.write(bytes) catch return null; - - return bytes.len; - } - }.write); - } - - pub fn bind(comptime State: type, allocator: Allocator, state: *State, comptime appender: fn (capture: *State, allocator: Allocator, bytes: []const u8) AllocationError!void) GrowingBuffer { - return .{ - .appender = Appender.bind(State, state, struct { - fn append(self: *State, options: AppendOptions) AllocationError!void { - return appender(self, options.allocator, options.bytes); - } - }.append), - - .allocator = allocator, - }; - } - - pub fn write(self: GrowingBuffer, bytes: []const u8) AllocationError!void { - return self.appender.invoke(.{ - .allocator = self.allocator, - .bytes = bytes, - }); - } -}; - pub const Writer = Generator(?usize, []const u8); -pub fn allocate_many(comptime Type: type, amount: usize, allocator: Allocator) AllocationError![]Type { +pub fn allocate_many(allocator: Allocator, amount: usize, comptime Type: type) AllocationError![]Type { if (@sizeOf(Type) == 0) { @compileError("Cannot allocate memory for 0-byte type " ++ @typeName(Type)); } - return @ptrCast([*]Type, @alignCast(@alignOf(Type), allocator.invoke(.{.size = @sizeOf(Type) * amount}) orelse { - return error.OutOfMemory; - }))[0 .. amount]; + return @ptrCast([*]Type, @alignCast(@alignOf(Type), allocator.invoke(.{ + .size = @sizeOf(Type) * amount, + .return_address = @returnAddress(), + }) orelse return error.OutOfMemory))[0 .. amount]; } pub fn allocate_one(allocator: Allocator, value: anytype) AllocationError!*@TypeOf(value) { @@ -193,9 +154,10 @@ pub fn allocate_one(allocator: Allocator, value: anytype) AllocationError!*@Type @compileError("Cannot allocate memory for 0-byte type " ++ @typeName(Type)); } - const allocation = @ptrCast(*Type, @alignCast(@alignOf(Type), allocator.invoke(.{.size = @sizeOf(Type)}) orelse { - return error.OutOfMemory; - })); + const allocation = @ptrCast(*Type, @alignCast(@alignOf(Type), allocator.invoke(.{ + .size = @sizeOf(Type), + .return_address = @returnAddress(), + }) orelse return error.OutOfMemory)); allocation.* = value; @@ -232,17 +194,16 @@ pub fn copy(target: []u8, source: []const u8) void { } pub fn deallocate(allocator: Allocator, allocation: anytype) void { - const Allocation = @TypeOf(allocation); - - switch (@typeInfo(Allocation)) { - .Pointer => |allocation_pointer| { + switch (@typeInfo(@TypeOf(allocation))) { + .Pointer => |pointer| { _ = allocator.invoke(.{ - .allocation = switch (allocation_pointer.size) { - .One => @ptrCast([*]u8, allocation)[0 .. @sizeOf(Allocation)], - .Slice => @ptrCast([*]u8, allocation.ptr)[0 .. (@sizeOf(Allocation) * allocation.len)], + .allocation = switch (pointer.size) { + .One => @ptrCast([*]u8, allocation)[0 .. @sizeOf(pointer.child)], + .Slice => @ptrCast([*]u8, allocation.ptr)[0 .. (@sizeOf(pointer.child) * allocation.len)], .Many, .C => @compileError("length of allocation must be known to deallocate"), }, + .return_address = @returnAddress(), .size = 0, }); }, diff --git a/source/coral/list.zig b/source/coral/list.zig index 9a70511..d501be2 100755 --- a/source/coral/list.zig +++ b/source/coral/list.zig @@ -17,19 +17,6 @@ pub fn Stack(comptime Value: type) type { /// const Self = @This(); - /// - /// Returns a [io.GrowableBuffer] bound to `self` and `allocator`. - /// - /// The returned buffer may be used to write to the stack without needing to explicitly pass an allocator - /// context, as well decay further into a generic [io.Writer] type. - /// - /// *Note* if `capacity` is a non-zero value, `allocator` must reference the same allocation strategy as the one - /// originally used to allocate the current internal buffer. - /// - pub fn as_buffer(self: *Self, allocator: io.Allocator) io.GrowingBuffer { - return io.GrowingBuffer.bind(Self, allocator, self, push_all); - } - /// /// Clears all elements from `self` while preserving the current internal buffer. /// @@ -52,7 +39,7 @@ pub fn Stack(comptime Value: type) type { return; } - io.deallocate(allocator, self.values); + io.deallocate(allocator, self.values.ptr[0 .. self.capacity]); self.values = &.{}; self.capacity = 0; @@ -87,7 +74,7 @@ pub fn Stack(comptime Value: type) type { /// pub fn grow(self: *Self, allocator: io.Allocator, growth_amount: usize) io.AllocationError!void { const grown_capacity = self.capacity + growth_amount; - const values = (try io.allocate_many(Value, grown_capacity, allocator))[0 .. self.values.len]; + const values = (try io.allocate_many(allocator, grown_capacity, Value))[0 .. self.values.len]; errdefer io.deallocate(allocator, values); @@ -96,7 +83,7 @@ pub fn Stack(comptime Value: type) type { values[index] = self.values[index]; } - io.deallocate(allocator, self.values); + io.deallocate(allocator, self.values.ptr[0 .. self.capacity]); } self.values = values; @@ -132,7 +119,7 @@ pub fn Stack(comptime Value: type) type { pub fn push_all(self: *Self, allocator: io.Allocator, values: []const Value) io.AllocationError!void { const new_length = self.values.len + values.len; - if (new_length >= self.capacity) { + if (new_length > self.capacity) { try self.grow(allocator, values.len + values.len); } @@ -194,3 +181,49 @@ pub fn Stack(comptime Value: type) type { } }; } + +/// +/// Bridge context between a list type implement as part of the list module and an allocator, allowing the list resource +/// referenced by the [Writable] instance to be written to directly or virtually via the [io.Writer] interface. +/// +/// *Note* if the given list contains an existing allocation, the provided [io.Allocator] instance must reference the +/// same allocation strategy as the one originally used to allocate the list type memory. +/// +pub const Writable = struct { + allocator: io.Allocator, + + list: union (enum) { + stack: *ByteStack, + }, + + /// + /// Stack of bytes. + /// + const ByteStack = Stack(u8); + + /// + /// Returns a [io.Writer] instance that binds a reference of `self` to the [write] operation. + /// + pub fn as_writer(self: *Writable) io.Writer { + return io.Writer.bind(Writable, self, struct { + fn write(writable: *Writable, bytes: []const u8) ?usize { + writable.write(bytes) catch return null; + + return bytes.len; + } + }.write); + } + + /// + /// Attempts to call the appropriate multi-element writing function for the current list referenced by `self`, + /// passing `bytes` along. + /// + /// The function returns [io.AllocationError] if `allocator` could not commit the memory by the list implementation + /// referenced by `self`. See the specific implementation details of the respective list type for more information. + /// + pub fn write(self: *Writable, bytes: []const u8) io.AllocationError!void { + return switch (self.list) { + .stack => |stack| stack.push_all(self.allocator, bytes), + }; + } +}; diff --git a/source/coral/slab.zig b/source/coral/slab.zig index c80f969..4dd6d30 100644 --- a/source/coral/slab.zig +++ b/source/coral/slab.zig @@ -52,17 +52,6 @@ pub fn Map(comptime index_int: std.builtin.Type.Int, comptime Value: type) type entry.value = value; } - /// - /// Fetches the value referenced by `index` in `self`, returning it. - /// - pub fn fetch(self: *Self, index: Index) Value { - const entry = &self.table[index]; - - debug.assert(entry.* == .value); - - return entry.value; - } - /// /// Deinitializes `self` and sets it to an invalid state, freeing all memory allocated by `allocator`. /// @@ -81,6 +70,17 @@ pub fn Map(comptime index_int: std.builtin.Type.Int, comptime Value: type) type self.free_index = 0; } + /// + /// Fetches the value referenced by `index` in `self`, returning it. + /// + pub fn fetch(self: *Self, index: Index) Value { + const entry = &self.table[index]; + + debug.assert(entry.* == .value); + + return entry.value; + } + /// /// Attempts to grow the internal buffer of `self` by `growth_amount` using `allocator`. /// @@ -95,7 +95,7 @@ pub fn Map(comptime index_int: std.builtin.Type.Int, comptime Value: type) type /// pub fn grow(self: *Self, allocator: io.Allocator, growth_amount: usize) io.AllocationError!void { const grown_capacity = self.table.len + growth_amount; - const entries = try io.allocate_many(Entry, grown_capacity, allocator); + const entries = try io.allocate_many(allocator, grown_capacity, Entry); errdefer io.deallocate(allocator, entries); @@ -147,12 +147,20 @@ pub fn Map(comptime index_int: std.builtin.Type.Int, comptime Value: type) type debug.assert(entry.* == .free_index); + self.count += 1; self.free_index = entry.free_index; entry.* = .{.value = value}; return entry_index; } + /// + /// Returns `true` if `self` contains no values, otherwise `false`. + /// + pub fn is_empty(self: Self) bool { + return self.count == 0; + } + /// /// Removes the value referenced by `index` from `self`. /// @@ -161,6 +169,7 @@ pub fn Map(comptime index_int: std.builtin.Type.Int, comptime Value: type) type debug.assert(entry.* == .value); + self.count -= 1; entry.* = .{.free_index = self.free_index}; self.free_index = index; } diff --git a/source/coral/table.zig b/source/coral/table.zig index 82fe1aa..d80a5a2 100755 --- a/source/coral/table.zig +++ b/source/coral/table.zig @@ -65,6 +65,30 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke } }; + /// + /// Iterable wrapper for [Hashed] instances to make unordered traversal of key-value entries relatively trivial. + /// + pub const Iterable = struct { + hashed_map: *Self, + iterations: usize = 0, + + /// + /// Attempts to move past the current iteration of `self` and onto the next key-value entry, returning it or + /// `null` if there are no more elements in the referenced map. + /// + pub fn next(self: *Iterable) ?Entry { + while (self.iterations < self.hashed_map.table.len) { + defer self.iterations += 1; + + if (self.hashed_map.table[self.iterations]) |entry| { + return entry; + } + } + + return null; + } + }; + /// /// Table type. /// @@ -228,7 +252,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime keyer: Keyer(Ke pub fn rehash(self: *Self, allocator: io.Allocator, requested_range: usize) io.AllocationError!void { const old_table = self.table; - self.table = try io.allocate_many(?Entry, math.max(requested_range, self.count), allocator); + self.table = try io.allocate_many(allocator, math.max(requested_range, self.count), ?Entry); errdefer { io.deallocate(allocator, self.table); diff --git a/source/ona/heap.zig b/source/ona/heap.zig index 25923f0..b6a28f9 100644 --- a/source/ona/heap.zig +++ b/source/ona/heap.zig @@ -1,54 +1,254 @@ +const builtin = @import("builtin"); + const coral = @import("coral"); const ext = @import("./ext.zig"); +const std = @import("std"); + +/// +/// Recorded allocation info state. +/// +const AllocationInfo = struct { + trace: AllocationTrace, + next_info: ?*AllocationInfo, + size: usize, +}; + +/// +/// Recorded stack trace of allocation call site. +/// +/// *Note* this structure is reduced to zero bytes in released builds optimized for speed or size. +/// +const AllocationTrace = std.debug.ConfigurableTrace(2, 4, switch (builtin.mode) { + .Debug, .ReleaseSafe => true, + .ReleaseFast, .ReleaseSmall => false, +}); + +/// +/// Heap allocation context. +/// const Context = struct { - live_allocations: usize, + allocation_info_head: ?*AllocationInfo = null, - const Self = @This(); + /// + /// Attempts to allocate a buffer of `size` length from `self`, with `return_address` as the location of the + /// allocation request origin. + /// + /// A reference to the allocated buffer is returned via a slice if the allocation was successful, otherwise `null` + /// is returned. + /// + /// *Note* the returned buffer must be deallocated with [deallocate] before program exit or it will cause a memory + /// leak. + /// + /// *Note* allocation checks are disabled in release builds optimized for speed or size. + /// + fn allocate(self: *Context, size: usize, return_address: usize) ?[]u8 { + switch (builtin.mode) { + .Debug, .ReleaseSafe => { + const allocation_info_size = @sizeOf(AllocationInfo); + const total_allocation_size = allocation_info_size + size; + const allocation = ext.SDL_malloc(total_allocation_size) orelse return null; + const allocation_info = @ptrCast(*AllocationInfo, @alignCast(@alignOf(AllocationInfo), allocation)); - const empty_allocation = [0]u8{}; + allocation_info.* = .{ + .size = size, + .next_info = self.allocation_info_head, + .trace = .{}, + }; - fn reallocate(self: *Self, options: coral.io.AllocationOptions) ?[]u8 { - if (options.size == 0) { - if (options.allocation) |allocation| { - if (allocation.ptr != &empty_allocation) { - ext.SDL_free(allocation.ptr); + allocation_info.trace.addAddr(return_address, ""); + + self.allocation_info_head = allocation_info; + + return @ptrCast([*]u8, allocation)[allocation_info_size .. total_allocation_size]; + }, + + .ReleaseFast, .ReleaseSmall => { + return @ptrCast([*]u8, ext.SDL_malloc(size) orelse return null)[0 .. size]; + }, + } + } + + /// + /// Returns the assumed pointer to the [AllocationInfo] address of `allocation`. + /// + fn allocation_info_of(allocation: [*]u8) *AllocationInfo { + return @intToPtr(*AllocationInfo, @ptrToInt(allocation) - @sizeOf(AllocationInfo)); + } + + /// + /// Deallocates a the allocation buffer referenced by `allocation`. + /// + /// *Note* the pointer and length of `allocation` must match valid values known to `allocator` otherwise safety- + /// checked behavior will occur. + /// + /// *Note* allocation checks are disabled in release builds optimized for speed or size. + /// + fn deallocate(self: *Context, allocation: []u8) void { + switch (builtin.mode) { + .Debug, .ReleaseSafe => { + const target_allocation_info = allocation_info_of(allocation.ptr); + + if (target_allocation_info.size != allocation.len) { + @panic("incorrect allocation length for deallocating"); } - self.live_allocations -= 1; + if (self.allocation_info_head) |allocation_info_head| { + if (target_allocation_info == allocation_info_head) { + self.allocation_info_head = allocation_info_head.next_info; - return null; - } + ext.SDL_free(target_allocation_info); - self.live_allocations += 1; + return; + } - return &empty_allocation; + var previous_allocation_info = allocation_info_head; + var current_allocation_info = allocation_info_head.next_info; + + while (current_allocation_info) |allocation_info| { + if (allocation_info == target_allocation_info) { + previous_allocation_info.next_info = allocation_info.next_info; + + ext.SDL_free(target_allocation_info); + + return; + } + + previous_allocation_info = allocation_info; + current_allocation_info = allocation_info.next_info; + } + } + + @panic("incorrect allocation address for deallocating"); + }, + + .ReleaseFast, .ReleaseSmall => { + ext.SDL_free(allocation.ptr); + }, } + } - if (options.allocation) |allocation| { - if (ext.SDL_realloc(allocation.ptr, options.size)) |reallocation| { - self.live_allocations += 1; + /// + /// Attempts to reallocate the buffer referenced by `allocation` to be `size` length from `self`. + /// + /// A reference to the reallocated buffer is returned via a slice if the reallocation was successful, otherwise + /// `null` is returned. + /// + /// *Note* the returned buffer must be deallocated with [deallocate] before program exit or it will cause a memory + /// leak. + /// + /// *Note* the pointer and length of `allocation` must match valid values known to `allocator` otherwise safety- + /// checked behavior will occur. + /// + /// *Note* the allocation referenced by `allocation` should be considered invalid once the function returns, + /// discarding it in favor of the return value. + /// + /// *Note* allocation checks are disabled in release builds optimized for speed or size. + /// + fn reallocate(self: *Context, allocation: []u8, size: usize) ?[]u8 { + switch (builtin.mode) { + .Debug, .ReleaseSafe => { + const target_allocation_info = allocation_info_of(allocation.ptr); - return @ptrCast([*]u8, reallocation)[0 .. options.size]; - } + if (target_allocation_info.size != allocation.len) { + @panic("incorrect allocation length for reallocating"); + } + + const allocation_info_size = @sizeOf(AllocationInfo); + + if (self.allocation_info_head) |allocation_info_head| { + if (target_allocation_info == allocation_info_head) { + self.allocation_info_head = allocation_info_head.next_info; + + const allocation_address = ext.SDL_realloc(target_allocation_info, size) orelse return null; + + target_allocation_info.size = size; + + return @ptrCast([*]u8, allocation_address)[ + allocation_info_size .. (allocation_info_size + size)]; + } + + var previous_allocation_info = allocation_info_head; + var current_allocation_info = allocation_info_head.next_info; + + while (current_allocation_info) |allocation_info| { + if (allocation_info == target_allocation_info) { + previous_allocation_info.next_info = allocation_info.next_info; + + const allocation_address = ext.SDL_realloc(target_allocation_info, size) orelse return null; + + target_allocation_info.size = size; + + return @ptrCast([*]u8, allocation_address)[ + allocation_info_size .. (allocation_info_size + size)]; + } + + previous_allocation_info = allocation_info; + current_allocation_info = allocation_info.next_info; + } + } + + @panic("incorrect allocation address for reallocating"); + }, + + .ReleaseFast, .ReleaseSmall => { + return @ptrCast([*]u8, ext.SDL_realloc(allocation.ptr, size) orelse return null)[0 .. size]; + }, } - - if (ext.SDL_malloc(options.size)) |allocation| { - self.live_allocations += 1; - - return @ptrCast([*]u8, allocation)[0 .. options.size]; - } - - return null; } }; -var context = Context{ - .live_allocations = 0, -}; +/// +/// Heap context. +/// +var context = Context{}; /// /// Heap allocator. /// -pub const allocator = coral.io.Allocator.bind(Context, &context, Context.reallocate); +pub const allocator = coral.io.Allocator.bind(Context, &context, struct { + fn reallocate(self: *Context, options: coral.io.AllocationOptions) ?[]u8 { + if (options.size == 0) { + if (options.allocation) |allocation| { + self.deallocate(allocation); + + return null; + } + + return self.allocate(0, options.return_address); + } + + if (options.allocation) |allocation| { + return self.reallocate(allocation, options.size); + } + + return self.allocate(options.size, options.return_address); + } +}.reallocate); + +/// +/// Checks for any allocations belonging to the process heap allocated through the [allocator] interface that are still +/// alive and reports the stack traces of any detected allocations to stderr along with the allocation address and +/// length. +/// +/// *Note* this function becomes a no-op in release builds optimized for speed or size. +/// +pub fn trace_leaks() void { + switch (builtin.mode) { + .Debug, .ReleaseSafe => { + var current_allocation_info = context.allocation_info_head; + + while (current_allocation_info) |allocation_info| : (current_allocation_info = allocation_info.next_info) { + std.debug.print("{d} byte leak at 0x{x} detected:\n", .{ + allocation_info.size, + @ptrToInt(allocation_info) + @sizeOf(AllocationInfo), + }); + + allocation_info.trace.dump(); + } + }, + + .ReleaseFast, .ReleaseSmall => {}, + } +} diff --git a/source/ona/kym/Chunk.zig b/source/ona/kym/Chunk.zig index 30add13..a2c10f1 100644 --- a/source/ona/kym/Chunk.zig +++ b/source/ona/kym/Chunk.zig @@ -53,7 +53,7 @@ fn clear_error_details(self: *Self) void { pub fn compile(self: *Self, data: []const u8) types.RuntimeError!void { var ast = try Ast.init(self.env.allocator); - errdefer ast.deinit(); + defer ast.deinit(); { var tokenizer = tokens.Tokenizer{.source = data}; @@ -62,9 +62,12 @@ pub fn compile(self: *Self, data: []const u8) types.RuntimeError!void { if (init_error == error.BadSyntax) { self.clear_error_details(); - var message_buffer = self.message_data.as_buffer(self.env.allocator); + var writable_data = coral.list.Writable{ + .allocator = self.env.allocator, + .list = .{.stack = &self.message_data}, + }; - coral.utf8.print_formatted(message_buffer.as_writer(), "@({line}): {name}", .{ + coral.utf8.print_formatted(writable_data.as_writer(), "@({line}): {name}", .{ .line = tokenizer.lines_stepped, .name = ast.error_message, }) catch return error.OutOfMemory; diff --git a/source/ona/kym/Environment.zig b/source/ona/kym/Environment.zig index b5fcc8c..e35742b 100644 --- a/source/ona/kym/Environment.zig +++ b/source/ona/kym/Environment.zig @@ -69,21 +69,6 @@ const Object = struct { self.ref_count += 1; } - - pub fn release(self: *Object, env: *Self) bool { - coral.debug.assert(self.ref_count != 0); - - self.ref_count -= 1; - - if (self.ref_count == 0) { - coral.io.deallocate(env.allocator, self.state.userdata); - self.state.fields.deinit(env.allocator); - - return false; - } - - return true; - } }; pub const ObjectInfo = struct { @@ -185,22 +170,26 @@ pub fn check(self: *Self, condition: bool, failure_message: []const u8) !void { } pub fn deinit(self: *Self) void { + self.object_release(self.global_object); + + { + var interned_iterable = InternTable.Iterable{.hashed_map = &self.interned}; + + while (interned_iterable.next()) |entry| { + self.object_release(entry.value); + } + } + + self.interned.deinit(self.allocator); self.values.deinit(self.allocator); self.calls.deinit(self.allocator); + coral.debug.assert(self.heap.is_empty()); + self.heap.deinit(self.allocator); } pub fn discard(self: *Self, val: types.Val) void { switch (val) { - .object => |object| { - var data = self.heap.fetch(object); - - if (data.release(self)) { - self.heap.remove(object); - } else { - self.heap.assign(object, data); - } - }, - + .object => |object| self.object_release(object), else => {}, } } @@ -214,17 +203,21 @@ pub fn execute_data(self: *Self, source: DataSource) types.RuntimeError!types.Va } }; - var chunk = try Chunk.init(self, source.name); + var compiled_chunk = init_compiled_chunk: { + var chunk = try Chunk.init(self, source.name); - errdefer chunk.deinit(); + errdefer chunk.deinit(); - chunk.compile(source.data) catch |compile_error| { - self.reporter.invoke(chunk.error_details()); + chunk.compile(source.data) catch |compile_error| { + self.reporter.invoke(chunk.error_details()); - return compile_error; + return compile_error; + }; + + break: init_compiled_chunk chunk; }; - const script = try self.new_object(coral.io.bytes_of(&chunk), .{ + const script = try self.new_object(coral.io.bytes_of(&compiled_chunk), .{ .identity = typeid, .deinitializer = Behaviors.deinitialize, }); @@ -239,25 +232,29 @@ pub fn execute_file(self: *Self, fs: file.System, file_path: file.Path) ExecuteF defer readable_file.close(); - var file_source = coral.list.Stack(u8){}; + var file_data = coral.list.Stack(u8){}; const file_size = (try fs.query_info(file_path)).size; - try file_source.grow(self.allocator, file_size); + try file_data.grow(self.allocator, file_size); - defer file_source.deinit(self.allocator); + defer file_data.deinit(self.allocator); { - var file_buffer = file_source.as_buffer(self.allocator); + var writable_data = coral.list.Writable{ + .allocator = self.allocator, + .list = .{.stack = &file_data}, + }; + var stream_buffer = [_]u8{0} ** 4096; - if ((try coral.io.stream(file_buffer.as_writer(), readable_file.as_reader(), &stream_buffer)) != file_size) { + if ((try coral.io.stream(writable_data.as_writer(), readable_file.as_reader(), &stream_buffer)) != file_size) { return error.ReadFailure; } } return try self.execute_data(.{ .name = try file_path.to_string(), - .data = file_source.values, + .data = file_data.values, }); } @@ -346,12 +343,11 @@ pub fn native_cast(self: *Self, castable: types.Ref, id: *const anyopaque, compt try self.check(castable == .object, "invalid type conversion: object"); const object = self.heap.fetch(castable.object); - const alignment = @alignOf(Type); - const is_expected_type = (object.state.info.identity == id) and (object.state.userdata.len == alignment); + const is_expected_type = (object.state.info.identity == id) and (object.state.userdata.len == @sizeOf(Type)); try self.check(is_expected_type, "invalid object cast: native type"); - return @ptrCast(*Type, @alignCast(alignment, object.state.userdata)); + return @ptrCast(*Type, @alignCast(@alignOf(Type), object.state.userdata)); } pub fn new_array(self: *Self) coral.io.AllocationError!types.Val { @@ -361,7 +357,7 @@ pub fn new_array(self: *Self) coral.io.AllocationError!types.Val { } pub fn new_object(self: *Self, userdata: []const u8, info: ObjectInfo) coral.io.AllocationError!types.Val { - const allocation = try coral.io.allocate_many(u8, userdata.len, self.allocator); + const allocation = try coral.io.allocate_many(self.allocator, userdata.len, u8); errdefer coral.io.deallocate(self.allocator, allocation); @@ -400,6 +396,28 @@ pub fn new_string(self: *Self, data: []const u8) coral.io.AllocationError!types. }); } +pub fn object_release(self: *Self, object: types.Object) void { + var data = self.heap.fetch(object); + + coral.debug.assert(data.ref_count != 0); + + data.ref_count -= 1; + + if (data.ref_count == 0) { + data.state.info.deinitializer(.{ + .env = self, + .obj = .{.object = object}, + }); + + // TODO: Free individual key-value pairs of fields + data.state.fields.deinit(self.allocator); + coral.io.deallocate(self.allocator, data.state.userdata); + self.heap.remove(object); + } else { + self.heap.assign(object, data); + } +} + pub fn set_global(self: *Self, global_name: []const u8, value: types.Ref) coral.io.AllocationError!void { try self.globals.assign(self.allocator, global_name, value); } diff --git a/source/ona/ona.zig b/source/ona/ona.zig index 13e36c2..33e5fb2 100755 --- a/source/ona/ona.zig +++ b/source/ona/ona.zig @@ -56,6 +56,8 @@ const AppManifest = struct { }; pub fn run_app(base_file_system: file.System) void { + defer heap.trace_leaks(); + const Logger = struct { const Self = @This();