From b34e538856c30e279b8665664b653c53a7e771ba Mon Sep 17 00:00:00 2001 From: Fabian Schmalzried Date: Tue, 26 Aug 2025 15:44:05 +0000 Subject: [PATCH 1/7] first shot at QUERY_FORMATTED --- src/playground_wasm/README.md | 33 ++++++++++++++----- src/playground_wasm/main.zig | 47 ++++++++++++++++++++++++++-- test/playground-integration/main.zig | 37 +++++++++++++++++++++- 3 files changed, 105 insertions(+), 12 deletions(-) diff --git a/src/playground_wasm/README.md b/src/playground_wasm/README.md index e82a1c1df8..ce714f4dce 100644 --- a/src/playground_wasm/README.md +++ b/src/playground_wasm/README.md @@ -185,7 +185,24 @@ All communication uses JSON messages. Each message must have a `type` field. } ``` -#### 7. GET_HOVER_INFO +#### 7. QUERY_FORMATTED +**State**: LOADED +**Purpose**: Get formatted Roc code + +**Request**: +```json +{"type": "QUERY_FORMATTED"} +``` + +**Response**: +```json +{ + "status": "SUCCESS", + "data": "module [foo]\n\nfoo = 42\n\nbar = \"baz\"\n" +} +``` + +#### 8. GET_HOVER_INFO **State**: LOADED or REPL_ACTIVE **Purpose**: Get hover information for an identifier at a specific source position @@ -219,7 +236,7 @@ All communication uses JSON messages. Each message must have a `type` field. **Note**: In REPL_ACTIVE state, this works with the last REPL evaluation's ModuleEnv. -#### 8. INIT_REPL +#### 9. INIT_REPL **State**: READY → REPL_ACTIVE **Purpose**: Initialize a REPL session for interactive evaluation @@ -240,7 +257,7 @@ All communication uses JSON messages. Each message must have a `type` field. } ``` -#### 9. REPL_STEP +#### 10. REPL_STEP **State**: REPL_ACTIVE **Purpose**: Submit a line of input to the REPL for evaluation @@ -308,10 +325,10 @@ All communication uses JSON messages. Each message must have a `type` field. - `error_details`: Additional error details, typically the specific error name or message **Compiler Availability**: -- `compiler_available: true`: Compiler queries (QUERY_CIR, QUERY_TYPES, GET_HOVER_INFO) are available +- `compiler_available: true`: Compiler queries (QUERY_CIR, QUERY_TYPES, QUERY_FORMATTED, GET_HOVER_INFO) are available - `compiler_available: false`: Compiler queries are not available (usually due to errors) -#### 10. CLEAR_REPL +#### 11. CLEAR_REPL **State**: REPL_ACTIVE **Purpose**: Clear all REPL definitions while keeping the REPL session active @@ -332,7 +349,7 @@ All communication uses JSON messages. Each message must have a `type` field. } ``` -#### 11. RESET +#### 12. RESET **State**: LOADED or REPL_ACTIVE → READY **Purpose**: Clean up compilation state and return to READY @@ -403,10 +420,10 @@ The playground now supports interactive REPL (Read-Eval-Print Loop) sessions tha When in `REPL_ACTIVE` state, the following compiler query messages work with the last REPL evaluation: - `QUERY_CIR`: Returns CIR for the last REPL evaluation -- `QUERY_TYPES`: Returns types for the last REPL evaluation +- `QUERY_TYPES`: Returns types for the last REPL evaluation - `GET_HOVER_INFO`: Returns hover info for the last REPL evaluation -These queries are only available when `compiler_available: true` in the REPL response. +- These queries are only available when `compiler_available: true` in the REPL response. ## Integration Notes diff --git a/src/playground_wasm/main.zig b/src/playground_wasm/main.zig index 66ea268391..fdd0e6838b 100644 --- a/src/playground_wasm/main.zig +++ b/src/playground_wasm/main.zig @@ -26,6 +26,7 @@ const compile = @import("compile"); const can = @import("can"); const check = @import("check"); const unbundle = @import("unbundle"); +const fmt = @import("fmt"); const WasmFilesystem = @import("WasmFilesystem.zig"); const Can = can.Can; @@ -58,6 +59,7 @@ const MessageType = enum { QUERY_AST, QUERY_CIR, QUERY_TYPES, + QUERY_FORMATTED, GET_HOVER_INFO, RESET, INIT_REPL, @@ -71,6 +73,7 @@ const MessageType = enum { if (std.mem.eql(u8, str, "QUERY_AST")) return .QUERY_AST; if (std.mem.eql(u8, str, "QUERY_CIR")) return .QUERY_CIR; if (std.mem.eql(u8, str, "QUERY_TYPES")) return .QUERY_TYPES; + if (std.mem.eql(u8, str, "QUERY_FORMATTED")) return .QUERY_FORMATTED; if (std.mem.eql(u8, str, "GET_HOVER_INFO")) return .GET_HOVER_INFO; if (std.mem.eql(u8, str, "RESET")) return .RESET; if (std.mem.eql(u8, str, "INIT_REPL")) return .INIT_REPL; @@ -653,6 +656,9 @@ fn handleLoadedState(message_type: MessageType, message_json: std.json.Value, re .QUERY_TYPES => { try writeTypesResponse(response_buffer, data); }, + .QUERY_FORMATTED => { + try writeFormattedResponse(response_buffer, data); + }, .GET_HOVER_INFO => { try writeHoverInfoResponse(response_buffer, data, message_json); }, @@ -769,9 +775,9 @@ fn handleReplState(message_type: MessageType, root: std.json.Value, response_buf // Write CIR response directly using the REPL's module env try writeReplCanCirResponse(response_buffer, module_env); }, - .QUERY_TYPES, .GET_HOVER_INFO => { - // These queries need type information which isn't readily available in REPL mode - try writeErrorResponse(response_buffer, .ERROR, "Type queries not available in REPL mode"); + .QUERY_TYPES, .QUERY_FORMATTED, .GET_HOVER_INFO => { + // These queries need parse/type information which isn't readily available in REPL mode + try writeErrorResponse(response_buffer, .ERROR, "Parse/type queries not available in REPL mode"); }, else => { try writeErrorResponse(response_buffer, .INVALID_STATE, "Invalid message type for REPL state"); @@ -1265,6 +1271,41 @@ fn writeParseAstResponse(response_buffer: []u8, data: CompilerStageData) Respons try resp_writer.finalize(); } +/// Write formatted response with formatted Roc code +fn writeFormattedResponse(response_buffer: []u8, data: CompilerStageData) ResponseWriteError!void { + var resp_writer = ResponseWriter{ .buffer = response_buffer }; + resp_writer.pos = @sizeOf(u32); + const w = resp_writer.writer(); + + try w.writeAll("{\"status\":\"SUCCESS\",\"data\":\""); + + if (data.parse_ast) |*parse_ast| { + // Create a local arena for formatting + var local_arena = std.heap.ArenaAllocator.init(allocator); + defer local_arena.deinit(); + const temp_alloc = local_arena.allocator(); + + var formatted = std.ArrayList(u8).init(temp_alloc); + defer formatted.deinit(); + + // Format the AST - in the playground we only deal with file-level parsing + fmt.formatAst(parse_ast.*, formatted.writer().any()) catch { + try writeJsonString(w, "Formatting failed"); + try w.writeAll("\"}"); + try resp_writer.finalize(); + return; + }; + + // Return the formatted code + try writeJsonString(w, formatted.items); + } else { + try writeJsonString(w, "Parse AST not available for formatting"); + } + + try w.writeAll("\"}"); + try resp_writer.finalize(); +} + /// Write canonicalized CIR response for REPL mode using ModuleEnv directly fn writeReplCanCirResponse(response_buffer: []u8, module_env: *ModuleEnv) ResponseWriteError!void { var resp_writer = ResponseWriter{ .buffer = response_buffer }; diff --git a/test/playground-integration/main.zig b/test/playground-integration/main.zig index 16fb4b67c8..6654fc7e17 100644 --- a/test/playground-integration/main.zig +++ b/test/playground-integration/main.zig @@ -766,6 +766,16 @@ fn runTestSteps(allocator: std.mem.Allocator, wasm_interface: *WasmInterface, te } else { logDebug(" Step {}: QUERY_TYPES successful. Status: {s}. No type data returned.\n", .{ i + 1, response.status }); } + } else if (std.mem.eql(u8, step.message.type, "QUERY_FORMATTED")) { + if (response.data) |data| { + if (data.len > 0) { + logDebug(" Step {}: QUERY_FORMATTED successful. Status: {s}. Formatted code retrieved ({} chars).\n", .{ i + 1, response.status, data.len }); + } else { + logDebug(" Step {}: QUERY_FORMATTED successful. Status: {s}. Empty formatted code.\n", .{ i + 1, response.status }); + } + } else { + logDebug(" Step {}: QUERY_FORMATTED successful. Status: {s}. No formatted data returned.\n", .{ i + 1, response.status }); + } } else { logDebug(" Step {}: {s} successful. Status: {s}, Message: {?s}\n", .{ i + 1, step.message.type, response.status, response.message }); } @@ -919,7 +929,7 @@ pub fn main() !void { defer test_cases.deinit(); // This will free the TestCase structs and their `steps` slices. // Functional Test - var happy_path_steps = try allocator.alloc(MessageStep, 7); + var happy_path_steps = try allocator.alloc(MessageStep, 8); // Check that INIT returns the compiler version (both test and WASM are built with same options) happy_path_steps[0] = .{ .message = .{ .type = "INIT" }, .expected_status = "SUCCESS", .expected_message_contains = build_options.compiler_version }; const happy_path_code = try TestData.happyPathRocCode(allocator); @@ -951,6 +961,11 @@ pub fn main() !void { .expected_data_contains = "inferred-types", }; happy_path_steps[6] = .{ + .message = .{ .type = "QUERY_FORMATTED" }, + .expected_status = "SUCCESS", + .expected_data_contains = "foo", + }; + happy_path_steps[7] = .{ .message = .{ .type = "GET_HOVER_INFO", .identifier = "foo", .line = 3, .ch = 1 }, .expected_status = "SUCCESS", .expected_hover_info_contains = "Str", @@ -971,6 +986,26 @@ pub fn main() !void { const empty_source_code = try allocator.dupe(u8, ""); try test_cases.append(try createSimpleTest(allocator, "Empty Source Code", empty_source_code, null, false)); // Disable diagnostic expectations + // Code Formatting Test + var formatted_test_steps = try allocator.alloc(MessageStep, 3); + formatted_test_steps[0] = .{ .message = .{ .type = "INIT" }, .expected_status = "SUCCESS" }; + const unformatted_code = try allocator.dupe(u8, "module [foo]\n\nfoo=42\nbar=\"hello world\"\n"); + formatted_test_steps[1] = .{ + .message = .{ .type = "LOAD_SOURCE", .source = unformatted_code }, + .expected_status = "SUCCESS", + .expected_message_contains = "LOADED", + .owned_source = unformatted_code, + }; + formatted_test_steps[2] = .{ + .message = .{ .type = "QUERY_FORMATTED" }, + .expected_status = "SUCCESS", + .expected_data_contains = "foo", + }; + try test_cases.append(.{ + .name = "QUERY_FORMATTED - Code Formatting", + .steps = formatted_test_steps, + }); + // Invalid Message Type Test var invalid_msg_type_steps = try allocator.alloc(MessageStep, 2); invalid_msg_type_steps[0] = .{ .message = .{ .type = "INIT" }, .expected_status = "SUCCESS" }; From 4336fb9f48477015cbfa43a47a5a4adab4f7ae5d Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Wed, 27 Aug 2025 09:35:37 +1000 Subject: [PATCH 2/7] WIP - fix RESET --- src/playground_wasm/main.zig | 118 +++++++++++++++++++---------------- 1 file changed, 65 insertions(+), 53 deletions(-) diff --git a/src/playground_wasm/main.zig b/src/playground_wasm/main.zig index fdd0e6838b..ff9d57c50e 100644 --- a/src/playground_wasm/main.zig +++ b/src/playground_wasm/main.zig @@ -411,30 +411,23 @@ fn wasmRocCrashed(crashed_args: *const builtins.host_abi.RocCrashed, _: *anyopaq /// Initialize the WASM module in START state export fn init() void { - fba = std.heap.FixedBufferAllocator.init(&wasm_heap_memory); - allocator = fba.allocator(); - + // Clean up any complex data structures first, before resetting allocator if (compiler_data) |*data| { data.deinit(); compiler_data = null; } - // Clean up REPL state + // Clean up REPL state before resetting allocator cleanupReplState(); - // Clean up any existing buffers - if (host_message_buffer) |buf| { - allocator.free(buf); - host_message_buffer = null; - } - if (host_response_buffer) |buf| { - allocator.free(buf); - host_response_buffer = null; - } - if (last_error) |err| { - allocator.free(err); - last_error = null; - } + // Reset allocator to clean slate - this invalidates all previous allocations + fba = std.heap.FixedBufferAllocator.init(&wasm_heap_memory); + allocator = fba.allocator(); + + // Simply null out buffer pointers - they're invalid after allocator reset + host_message_buffer = null; + host_response_buffer = null; + last_error = null; } /// Allocate a buffer for incoming messages from the host. @@ -603,32 +596,40 @@ fn handleReadyState(message_type: MessageType, root: std.json.Value, response_bu try writeReplInitResponse(response_buffer); }, .RESET => { - // A RESET message should clean up all compilation-related memory. + logDebug("RESET: Starting in READY state\n", .{}); + + // First, clean up any complex data structures properly if (compiler_data) |*old_data| { + logDebug("RESET: Cleaning up compiler_data\n", .{}); old_data.deinit(); compiler_data = null; + logDebug("RESET: Compiler data cleaned up\n", .{}); } - // Also free the host-managed buffers, as they are part of the old state. - if (host_message_buffer) |buf| { - allocator.free(buf); - host_message_buffer = null; - } - if (host_response_buffer) |buf| { - allocator.free(buf); - host_response_buffer = null; - } - - // Clean up REPL state + + // Clean up REPL state before resetting allocator + logDebug("RESET: Cleaning up REPL state\n", .{}); cleanupReplState(); + logDebug("RESET: REPL state cleaned up\n", .{}); - // Now, fully reset the allocator to prevent fragmentation. + // CRITICAL FIX: Reset the allocator FIRST, then just null out buffer pointers + // Don't try to free individual buffers after resetting the allocator! + logDebug("RESET: Resetting allocator to clean slate\n", .{}); fba = std.heap.FixedBufferAllocator.init(&wasm_heap_memory); allocator = fba.allocator(); + + // Simply null out the buffer pointers - they're now invalid anyway + host_message_buffer = null; + host_response_buffer = null; + + logDebug("RESET: Allocator reset complete, buffers nullified\n", .{}); current_state = .READY; + logDebug("RESET: State set to READY\n", .{}); const compiler_version = build_options.compiler_version; + logDebug("RESET: Writing success response\n", .{}); try writeSuccessResponse(response_buffer, compiler_version, null); + logDebug("RESET: Success response written\n", .{}); }, else => { try writeErrorResponse(response_buffer, .INVALID_STATE, "INVALID_STATE"); @@ -663,29 +664,35 @@ fn handleLoadedState(message_type: MessageType, message_json: std.json.Value, re try writeHoverInfoResponse(response_buffer, data, message_json); }, .RESET => { - // A RESET message should clean up all compilation-related memory. + logDebug("RESET: Starting in LOADED state\n", .{}); + + // First, clean up any complex data structures properly if (compiler_data) |*old_data| { + logDebug("RESET: Cleaning up compiler_data\n", .{}); old_data.deinit(); compiler_data = null; - } - // Also free the host-managed buffers, as they are part of the old state. - if (host_message_buffer) |buf| { - allocator.free(buf); - host_message_buffer = null; - } - if (host_response_buffer) |buf| { - allocator.free(buf); - host_response_buffer = null; + logDebug("RESET: Compiler data cleaned up\n", .{}); } - // Now, fully reset the allocator to prevent fragmentation. + // CRITICAL FIX: Reset the allocator FIRST, then just null out buffer pointers + // Don't try to free individual buffers after resetting the allocator! + logDebug("RESET: Resetting allocator to clean slate\n", .{}); fba = std.heap.FixedBufferAllocator.init(&wasm_heap_memory); allocator = fba.allocator(); + + // Simply null out the buffer pointers - they're now invalid anyway + host_message_buffer = null; + host_response_buffer = null; + + logDebug("RESET: Allocator reset complete, buffers nullified\n", .{}); current_state = .READY; + logDebug("RESET: State set to READY\n", .{}); const compiler_version = build_options.compiler_version; + logDebug("RESET: Writing success response\n", .{}); try writeSuccessResponse(response_buffer, compiler_version, null); + logDebug("RESET: Success response written\n", .{}); }, else => { try writeErrorResponse(response_buffer, .INVALID_STATE, "INVALID_STATE"); @@ -743,27 +750,32 @@ fn handleReplState(message_type: MessageType, root: std.json.Value, response_buf try writeReplClearResponse(response_buffer); }, .RESET => { - // Clean up REPL state + logDebug("RESET: Starting in REPL_ACTIVE state\n", .{}); + + // Clean up REPL state before resetting allocator + logDebug("RESET: Cleaning up REPL state\n", .{}); cleanupReplState(); + logDebug("RESET: REPL state cleaned up\n", .{}); - // Also free the host-managed buffers, as they are part of the old state. - if (host_message_buffer) |buf| { - allocator.free(buf); - host_message_buffer = null; - } - if (host_response_buffer) |buf| { - allocator.free(buf); - host_response_buffer = null; - } - - // Now, fully reset the allocator to prevent fragmentation. + // CRITICAL FIX: Reset the allocator FIRST, then just null out buffer pointers + // Don't try to free individual buffers after resetting the allocator! + logDebug("RESET: Resetting allocator to clean slate\n", .{}); fba = std.heap.FixedBufferAllocator.init(&wasm_heap_memory); allocator = fba.allocator(); + + // Simply null out the buffer pointers - they're now invalid anyway + host_message_buffer = null; + host_response_buffer = null; + + logDebug("RESET: Allocator reset complete, buffers nullified\n", .{}); current_state = .READY; + logDebug("RESET: State set to READY\n", .{}); const compiler_version = build_options.compiler_version; + logDebug("RESET: Writing success response\n", .{}); try writeSuccessResponse(response_buffer, compiler_version, null); + logDebug("RESET: Success response written\n", .{}); }, .QUERY_CIR => { // For REPL mode, we need to generate CIR from the REPL's last module env From de6cd674bbd2292874b6c8d188db4301f747aede Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Wed, 27 Aug 2025 09:53:08 +1000 Subject: [PATCH 3/7] try more conservative RESET management --- src/playground_wasm/main.zig | 126 ++++++++++++++++++++++------------- 1 file changed, 79 insertions(+), 47 deletions(-) diff --git a/src/playground_wasm/main.zig b/src/playground_wasm/main.zig index ff9d57c50e..b234a04523 100644 --- a/src/playground_wasm/main.zig +++ b/src/playground_wasm/main.zig @@ -411,20 +411,19 @@ fn wasmRocCrashed(crashed_args: *const builtins.host_abi.RocCrashed, _: *anyopaq /// Initialize the WASM module in START state export fn init() void { - // Clean up any complex data structures first, before resetting allocator + // For the very first initialization, we can reset the allocator + fba = std.heap.FixedBufferAllocator.init(&wasm_heap_memory); + allocator = fba.allocator(); + if (compiler_data) |*data| { data.deinit(); compiler_data = null; } - // Clean up REPL state before resetting allocator + // Clean up REPL state cleanupReplState(); - // Reset allocator to clean slate - this invalidates all previous allocations - fba = std.heap.FixedBufferAllocator.init(&wasm_heap_memory); - allocator = fba.allocator(); - - // Simply null out buffer pointers - they're invalid after allocator reset + // Initialize buffer pointers to null host_message_buffer = null; host_response_buffer = null; last_error = null; @@ -486,29 +485,39 @@ export fn processMessage(message_ptr: [*]const u8, message_len: usize, response_ const message_slice = message_ptr[0..message_len]; const response_slice = response_ptr[0..response_len]; + logDebug("processMessage: Starting, state={}, message_len={}\n", .{ @intFromEnum(current_state), message_len }); + // Check if buffer is large enough for the length prefix (u32) if (response_slice.len < @sizeOf(u32)) { + logDebug("processMessage: Response buffer too small for length prefix\n", .{}); return @intFromEnum(WasmError.response_buffer_too_small); } - const parsed = std.json.parseFromSlice(std.json.Value, allocator, message_slice, .{}) catch { + logDebug("processMessage: Parsing JSON message\n", .{}); + const parsed = std.json.parseFromSlice(std.json.Value, allocator, message_slice, .{}) catch |err| { + logDebug("processMessage: JSON parse failed: {}\n", .{err}); // Write error response. This will also write the length prefix. writeErrorResponse(response_slice, ResponseStatus.ERROR, "Invalid JSON message") catch return @intFromEnum(WasmError.response_buffer_too_small); return @intFromEnum(WasmError.success); }; defer parsed.deinit(); + logDebug("processMessage: JSON parsed successfully\n", .{}); const root = parsed.value; const message_type_str = root.object.get("type") orelse { + logDebug("processMessage: Missing message type\n", .{}); writeErrorResponse(response_slice, ResponseStatus.INVALID_MESSAGE, "Missing message type") catch return @intFromEnum(WasmError.response_buffer_too_small); return @intFromEnum(WasmError.success); }; const message_type = MessageType.fromString(message_type_str.string) orelse { + logDebug("processMessage: Unknown message type: {s}\n", .{message_type_str.string}); writeErrorResponse(response_slice, ResponseStatus.INVALID_MESSAGE, "Unknown message type") catch return @intFromEnum(WasmError.response_buffer_too_small); return @intFromEnum(WasmError.success); }; + logDebug("processMessage: Handling {s} in state {}\n", .{ message_type_str.string, @intFromEnum(current_state) }); + // Handle message based on current state const result = switch (current_state) { .START => handleStartState(message_type, root, response_slice), @@ -517,6 +526,8 @@ export fn processMessage(message_ptr: [*]const u8, message_len: usize, response_ .REPL_ACTIVE => handleReplState(message_type, root, response_slice), }; + logDebug("processMessage: Message handling complete, returning result\n", .{}); + return if (result) |_| @intFromEnum(WasmError.success) else |err| switch (err) { error.OutOfBufferSpace => @intFromEnum(WasmError.response_buffer_too_small), }; @@ -540,31 +551,41 @@ fn handleStartState(message_type: MessageType, _: std.json.Value, response_buffe fn handleReadyState(message_type: MessageType, root: std.json.Value, response_buffer: []u8) ResponseWriteError!void { switch (message_type) { .LOAD_SOURCE => { + logDebug("LOAD_SOURCE: Starting in READY state\n", .{}); const source_value = root.object.get("source") orelse { + logDebug("LOAD_SOURCE: Missing source in message\n", .{}); try writeErrorResponse(response_buffer, .INVALID_MESSAGE, "Missing source"); return; }; const source = source_value.string; + logDebug("LOAD_SOURCE: Got source, length={}\n", .{source.len}); // Clean up previous compilation if any if (compiler_data) |*data| { + logDebug("LOAD_SOURCE: Cleaning up previous compiler data\n", .{}); data.deinit(); compiler_data = null; + logDebug("LOAD_SOURCE: Previous compiler data cleaned up\n", .{}); } // Compile the source through all stages // Compile and return result + logDebug("LOAD_SOURCE: Starting compilation\n", .{}); const result = compileSource(source) catch |err| { + logDebug("LOAD_SOURCE: Compilation failed: {}\n", .{err}); try writeErrorResponse(response_buffer, .ERROR, @errorName(err)); return; }; + logDebug("LOAD_SOURCE: Compilation completed successfully\n", .{}); compiler_data = result; current_state = .LOADED; // Return success with diagnostics + logDebug("LOAD_SOURCE: Writing loaded response\n", .{}); try writeLoadedResponse(response_buffer, result); + logDebug("LOAD_SOURCE: Loaded response written\n", .{}); }, .INIT_REPL => { // Clean up any existing REPL state @@ -597,7 +618,7 @@ fn handleReadyState(message_type: MessageType, root: std.json.Value, response_bu }, .RESET => { logDebug("RESET: Starting in READY state\n", .{}); - + // First, clean up any complex data structures properly if (compiler_data) |*old_data| { logDebug("RESET: Cleaning up compiler_data\n", .{}); @@ -605,23 +626,23 @@ fn handleReadyState(message_type: MessageType, root: std.json.Value, response_bu compiler_data = null; logDebug("RESET: Compiler data cleaned up\n", .{}); } - - // Clean up REPL state before resetting allocator + + // Clean up REPL state logDebug("RESET: Cleaning up REPL state\n", .{}); cleanupReplState(); logDebug("RESET: REPL state cleaned up\n", .{}); - // CRITICAL FIX: Reset the allocator FIRST, then just null out buffer pointers - // Don't try to free individual buffers after resetting the allocator! - logDebug("RESET: Resetting allocator to clean slate\n", .{}); - fba = std.heap.FixedBufferAllocator.init(&wasm_heap_memory); - allocator = fba.allocator(); - - // Simply null out the buffer pointers - they're now invalid anyway - host_message_buffer = null; - host_response_buffer = null; - - logDebug("RESET: Allocator reset complete, buffers nullified\n", .{}); + // Clean up host-managed buffers normally + if (host_message_buffer) |buf| { + logDebug("RESET: Freeing host_message_buffer\n", .{}); + allocator.free(buf); + host_message_buffer = null; + } + if (host_response_buffer) |buf| { + logDebug("RESET: Freeing host_response_buffer\n", .{}); + allocator.free(buf); + host_response_buffer = null; + } current_state = .READY; logDebug("RESET: State set to READY\n", .{}); @@ -665,7 +686,7 @@ fn handleLoadedState(message_type: MessageType, message_json: std.json.Value, re }, .RESET => { logDebug("RESET: Starting in LOADED state\n", .{}); - + // First, clean up any complex data structures properly if (compiler_data) |*old_data| { logDebug("RESET: Cleaning up compiler_data\n", .{}); @@ -674,17 +695,17 @@ fn handleLoadedState(message_type: MessageType, message_json: std.json.Value, re logDebug("RESET: Compiler data cleaned up\n", .{}); } - // CRITICAL FIX: Reset the allocator FIRST, then just null out buffer pointers - // Don't try to free individual buffers after resetting the allocator! - logDebug("RESET: Resetting allocator to clean slate\n", .{}); - fba = std.heap.FixedBufferAllocator.init(&wasm_heap_memory); - allocator = fba.allocator(); - - // Simply null out the buffer pointers - they're now invalid anyway - host_message_buffer = null; - host_response_buffer = null; - - logDebug("RESET: Allocator reset complete, buffers nullified\n", .{}); + // Clean up host-managed buffers normally + if (host_message_buffer) |buf| { + logDebug("RESET: Freeing host_message_buffer\n", .{}); + allocator.free(buf); + host_message_buffer = null; + } + if (host_response_buffer) |buf| { + logDebug("RESET: Freeing host_response_buffer\n", .{}); + allocator.free(buf); + host_response_buffer = null; + } current_state = .READY; logDebug("RESET: State set to READY\n", .{}); @@ -751,23 +772,23 @@ fn handleReplState(message_type: MessageType, root: std.json.Value, response_buf }, .RESET => { logDebug("RESET: Starting in REPL_ACTIVE state\n", .{}); - - // Clean up REPL state before resetting allocator + + // Clean up REPL state logDebug("RESET: Cleaning up REPL state\n", .{}); cleanupReplState(); logDebug("RESET: REPL state cleaned up\n", .{}); - // CRITICAL FIX: Reset the allocator FIRST, then just null out buffer pointers - // Don't try to free individual buffers after resetting the allocator! - logDebug("RESET: Resetting allocator to clean slate\n", .{}); - fba = std.heap.FixedBufferAllocator.init(&wasm_heap_memory); - allocator = fba.allocator(); - - // Simply null out the buffer pointers - they're now invalid anyway - host_message_buffer = null; - host_response_buffer = null; - - logDebug("RESET: Allocator reset complete, buffers nullified\n", .{}); + // Clean up host-managed buffers normally + if (host_message_buffer) |buf| { + logDebug("RESET: Freeing host_message_buffer\n", .{}); + allocator.free(buf); + host_message_buffer = null; + } + if (host_response_buffer) |buf| { + logDebug("RESET: Freeing host_response_buffer\n", .{}); + allocator.free(buf); + host_response_buffer = null; + } current_state = .READY; logDebug("RESET: State set to READY\n", .{}); @@ -799,8 +820,11 @@ fn handleReplState(message_type: MessageType, root: std.json.Value, response_buf /// Compile source through all compiler stages. fn compileSource(source: []const u8) !CompilerStageData { + logDebug("compileSource: Starting, source.len={}\n", .{source.len}); + // Handle empty input gracefully to prevent crashes if (source.len == 0) { + logDebug("compileSource: Empty source, creating empty CompilerStageData\n", .{}); // Return empty compiler stage data for completely empty input var module_env = try allocator.create(ModuleEnv); module_env.* = try ModuleEnv.init(allocator, source); @@ -810,6 +834,7 @@ fn compileSource(source: []const u8) !CompilerStageData { const trimmed_source = std.mem.trim(u8, source, " \t\n\r"); if (trimmed_source.len == 0) { + logDebug("compileSource: Whitespace-only source, creating empty CompilerStageData\n", .{}); // Return empty compiler stage data for whitespace-only input var module_env = try allocator.create(ModuleEnv); module_env.* = try ModuleEnv.init(allocator, source); @@ -817,13 +842,20 @@ fn compileSource(source: []const u8) !CompilerStageData { return CompilerStageData.init(allocator, module_env); } + logDebug("compileSource: Setting up WASM filesystem\n", .{}); // Set up the source in WASM filesystem WasmFilesystem.setSource(allocator, source); + logDebug("compileSource: Creating ModuleEnv\n", .{}); // Initialize the ModuleEnv var module_env = try allocator.create(ModuleEnv); + logDebug("compileSource: ModuleEnv allocated\n", .{}); + module_env.* = try ModuleEnv.init(allocator, source); + logDebug("compileSource: ModuleEnv initialized\n", .{}); + try module_env.common.calcLineStarts(module_env.gpa); + logDebug("compileSource: Line starts calculated\n", .{}); var result = CompilerStageData.init(allocator, module_env); From 45973b138388115047f60247b8416556d04cb7ac Mon Sep 17 00:00:00 2001 From: Luke Boswell Date: Wed, 27 Aug 2025 10:48:02 +1000 Subject: [PATCH 4/7] cleanup --- src/playground_wasm/main.zig | 69 ++---------------------------------- 1 file changed, 3 insertions(+), 66 deletions(-) diff --git a/src/playground_wasm/main.zig b/src/playground_wasm/main.zig index b234a04523..4bb65b0095 100644 --- a/src/playground_wasm/main.zig +++ b/src/playground_wasm/main.zig @@ -485,39 +485,29 @@ export fn processMessage(message_ptr: [*]const u8, message_len: usize, response_ const message_slice = message_ptr[0..message_len]; const response_slice = response_ptr[0..response_len]; - logDebug("processMessage: Starting, state={}, message_len={}\n", .{ @intFromEnum(current_state), message_len }); - // Check if buffer is large enough for the length prefix (u32) if (response_slice.len < @sizeOf(u32)) { - logDebug("processMessage: Response buffer too small for length prefix\n", .{}); return @intFromEnum(WasmError.response_buffer_too_small); } - logDebug("processMessage: Parsing JSON message\n", .{}); - const parsed = std.json.parseFromSlice(std.json.Value, allocator, message_slice, .{}) catch |err| { - logDebug("processMessage: JSON parse failed: {}\n", .{err}); + const parsed = std.json.parseFromSlice(std.json.Value, allocator, message_slice, .{}) catch { // Write error response. This will also write the length prefix. writeErrorResponse(response_slice, ResponseStatus.ERROR, "Invalid JSON message") catch return @intFromEnum(WasmError.response_buffer_too_small); return @intFromEnum(WasmError.success); }; defer parsed.deinit(); - logDebug("processMessage: JSON parsed successfully\n", .{}); const root = parsed.value; const message_type_str = root.object.get("type") orelse { - logDebug("processMessage: Missing message type\n", .{}); writeErrorResponse(response_slice, ResponseStatus.INVALID_MESSAGE, "Missing message type") catch return @intFromEnum(WasmError.response_buffer_too_small); return @intFromEnum(WasmError.success); }; const message_type = MessageType.fromString(message_type_str.string) orelse { - logDebug("processMessage: Unknown message type: {s}\n", .{message_type_str.string}); writeErrorResponse(response_slice, ResponseStatus.INVALID_MESSAGE, "Unknown message type") catch return @intFromEnum(WasmError.response_buffer_too_small); return @intFromEnum(WasmError.success); }; - logDebug("processMessage: Handling {s} in state {}\n", .{ message_type_str.string, @intFromEnum(current_state) }); - // Handle message based on current state const result = switch (current_state) { .START => handleStartState(message_type, root, response_slice), @@ -526,8 +516,6 @@ export fn processMessage(message_ptr: [*]const u8, message_len: usize, response_ .REPL_ACTIVE => handleReplState(message_type, root, response_slice), }; - logDebug("processMessage: Message handling complete, returning result\n", .{}); - return if (result) |_| @intFromEnum(WasmError.success) else |err| switch (err) { error.OutOfBufferSpace => @intFromEnum(WasmError.response_buffer_too_small), }; @@ -551,41 +539,30 @@ fn handleStartState(message_type: MessageType, _: std.json.Value, response_buffe fn handleReadyState(message_type: MessageType, root: std.json.Value, response_buffer: []u8) ResponseWriteError!void { switch (message_type) { .LOAD_SOURCE => { - logDebug("LOAD_SOURCE: Starting in READY state\n", .{}); const source_value = root.object.get("source") orelse { - logDebug("LOAD_SOURCE: Missing source in message\n", .{}); try writeErrorResponse(response_buffer, .INVALID_MESSAGE, "Missing source"); return; }; const source = source_value.string; - logDebug("LOAD_SOURCE: Got source, length={}\n", .{source.len}); // Clean up previous compilation if any if (compiler_data) |*data| { - logDebug("LOAD_SOURCE: Cleaning up previous compiler data\n", .{}); data.deinit(); compiler_data = null; - logDebug("LOAD_SOURCE: Previous compiler data cleaned up\n", .{}); } // Compile the source through all stages - // Compile and return result - logDebug("LOAD_SOURCE: Starting compilation\n", .{}); const result = compileSource(source) catch |err| { - logDebug("LOAD_SOURCE: Compilation failed: {}\n", .{err}); try writeErrorResponse(response_buffer, .ERROR, @errorName(err)); return; }; - logDebug("LOAD_SOURCE: Compilation completed successfully\n", .{}); compiler_data = result; current_state = .LOADED; // Return success with diagnostics - logDebug("LOAD_SOURCE: Writing loaded response\n", .{}); try writeLoadedResponse(response_buffer, result); - logDebug("LOAD_SOURCE: Loaded response written\n", .{}); }, .INIT_REPL => { // Clean up any existing REPL state @@ -617,40 +594,29 @@ fn handleReadyState(message_type: MessageType, root: std.json.Value, response_bu try writeReplInitResponse(response_buffer); }, .RESET => { - logDebug("RESET: Starting in READY state\n", .{}); - - // First, clean up any complex data structures properly + // Clean up any complex data structures properly if (compiler_data) |*old_data| { - logDebug("RESET: Cleaning up compiler_data\n", .{}); old_data.deinit(); compiler_data = null; - logDebug("RESET: Compiler data cleaned up\n", .{}); } // Clean up REPL state - logDebug("RESET: Cleaning up REPL state\n", .{}); cleanupReplState(); - logDebug("RESET: REPL state cleaned up\n", .{}); // Clean up host-managed buffers normally if (host_message_buffer) |buf| { - logDebug("RESET: Freeing host_message_buffer\n", .{}); allocator.free(buf); host_message_buffer = null; } if (host_response_buffer) |buf| { - logDebug("RESET: Freeing host_response_buffer\n", .{}); allocator.free(buf); host_response_buffer = null; } current_state = .READY; - logDebug("RESET: State set to READY\n", .{}); const compiler_version = build_options.compiler_version; - logDebug("RESET: Writing success response\n", .{}); try writeSuccessResponse(response_buffer, compiler_version, null); - logDebug("RESET: Success response written\n", .{}); }, else => { try writeErrorResponse(response_buffer, .INVALID_STATE, "INVALID_STATE"); @@ -685,35 +651,26 @@ fn handleLoadedState(message_type: MessageType, message_json: std.json.Value, re try writeHoverInfoResponse(response_buffer, data, message_json); }, .RESET => { - logDebug("RESET: Starting in LOADED state\n", .{}); - - // First, clean up any complex data structures properly + // Clean up any complex data structures properly if (compiler_data) |*old_data| { - logDebug("RESET: Cleaning up compiler_data\n", .{}); old_data.deinit(); compiler_data = null; - logDebug("RESET: Compiler data cleaned up\n", .{}); } // Clean up host-managed buffers normally if (host_message_buffer) |buf| { - logDebug("RESET: Freeing host_message_buffer\n", .{}); allocator.free(buf); host_message_buffer = null; } if (host_response_buffer) |buf| { - logDebug("RESET: Freeing host_response_buffer\n", .{}); allocator.free(buf); host_response_buffer = null; } current_state = .READY; - logDebug("RESET: State set to READY\n", .{}); const compiler_version = build_options.compiler_version; - logDebug("RESET: Writing success response\n", .{}); try writeSuccessResponse(response_buffer, compiler_version, null); - logDebug("RESET: Success response written\n", .{}); }, else => { try writeErrorResponse(response_buffer, .INVALID_STATE, "INVALID_STATE"); @@ -771,32 +728,23 @@ fn handleReplState(message_type: MessageType, root: std.json.Value, response_buf try writeReplClearResponse(response_buffer); }, .RESET => { - logDebug("RESET: Starting in REPL_ACTIVE state\n", .{}); - // Clean up REPL state - logDebug("RESET: Cleaning up REPL state\n", .{}); cleanupReplState(); - logDebug("RESET: REPL state cleaned up\n", .{}); // Clean up host-managed buffers normally if (host_message_buffer) |buf| { - logDebug("RESET: Freeing host_message_buffer\n", .{}); allocator.free(buf); host_message_buffer = null; } if (host_response_buffer) |buf| { - logDebug("RESET: Freeing host_response_buffer\n", .{}); allocator.free(buf); host_response_buffer = null; } current_state = .READY; - logDebug("RESET: State set to READY\n", .{}); const compiler_version = build_options.compiler_version; - logDebug("RESET: Writing success response\n", .{}); try writeSuccessResponse(response_buffer, compiler_version, null); - logDebug("RESET: Success response written\n", .{}); }, .QUERY_CIR => { // For REPL mode, we need to generate CIR from the REPL's last module env @@ -820,11 +768,8 @@ fn handleReplState(message_type: MessageType, root: std.json.Value, response_buf /// Compile source through all compiler stages. fn compileSource(source: []const u8) !CompilerStageData { - logDebug("compileSource: Starting, source.len={}\n", .{source.len}); - // Handle empty input gracefully to prevent crashes if (source.len == 0) { - logDebug("compileSource: Empty source, creating empty CompilerStageData\n", .{}); // Return empty compiler stage data for completely empty input var module_env = try allocator.create(ModuleEnv); module_env.* = try ModuleEnv.init(allocator, source); @@ -834,7 +779,6 @@ fn compileSource(source: []const u8) !CompilerStageData { const trimmed_source = std.mem.trim(u8, source, " \t\n\r"); if (trimmed_source.len == 0) { - logDebug("compileSource: Whitespace-only source, creating empty CompilerStageData\n", .{}); // Return empty compiler stage data for whitespace-only input var module_env = try allocator.create(ModuleEnv); module_env.* = try ModuleEnv.init(allocator, source); @@ -842,20 +786,13 @@ fn compileSource(source: []const u8) !CompilerStageData { return CompilerStageData.init(allocator, module_env); } - logDebug("compileSource: Setting up WASM filesystem\n", .{}); // Set up the source in WASM filesystem WasmFilesystem.setSource(allocator, source); - logDebug("compileSource: Creating ModuleEnv\n", .{}); // Initialize the ModuleEnv var module_env = try allocator.create(ModuleEnv); - logDebug("compileSource: ModuleEnv allocated\n", .{}); - module_env.* = try ModuleEnv.init(allocator, source); - logDebug("compileSource: ModuleEnv initialized\n", .{}); - try module_env.common.calcLineStarts(module_env.gpa); - logDebug("compileSource: Line starts calculated\n", .{}); var result = CompilerStageData.init(allocator, module_env); From 32dfd9121167f740b2b4f9c9421b529fd0fcf051 Mon Sep 17 00:00:00 2001 From: Fabian Schmalzried Date: Thu, 4 Sep 2025 10:25:07 +0200 Subject: [PATCH 5/7] Add formatted code handling to CompilerStageData --- src/playground_wasm/main.zig | 47 ++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/playground_wasm/main.zig b/src/playground_wasm/main.zig index 4bb65b0095..3f84f73b53 100644 --- a/src/playground_wasm/main.zig +++ b/src/playground_wasm/main.zig @@ -126,6 +126,7 @@ const CompilerStageData = struct { // Pre-canonicalization HTML representations tokens_html: ?[]const u8 = null, ast_html: ?[]const u8 = null, + formatted_code: ?[]const u8 = null, // Diagnostic reports from each stage tokenize_reports: std.ArrayList(reporting.Report), @@ -152,6 +153,7 @@ const CompilerStageData = struct { // Free pre-generated HTML if (self.tokens_html) |html| allocator.free(html); if (self.ast_html) |html| allocator.free(html); + if (self.formatted_code) |code| allocator.free(code); // Deinit reports, which may reference data in the AST or ModuleEnv for (self.tokenize_reports.items) |*report| { @@ -817,7 +819,7 @@ fn compileSource(source: []const u8) !CompilerStageData { }; // Generate AST HTML - var ast_html_buffer = std.ArrayList(u8).init(allocator); + var ast_html_buffer = std.ArrayList(u8).init(temp_alloc); const ast_writer = ast_html_buffer.writer().any(); { const file = parse_ast.store.getFile(); @@ -829,9 +831,23 @@ fn compileSource(source: []const u8) !CompilerStageData { try tree.toHtml(ast_writer); } - // the AST HTML is stored in our heap and will be cleaned up when the module is RESET - // no need to free it here, we will re-use whenever the AST is queried - result.ast_html = ast_html_buffer.items; + + result.ast_html = allocator.dupe(u8, ast_html_buffer.items) catch |err| { + logDebug("compileSource: failed to dupe ast_html: {}\n", .{err}); + return err; + }; + + // Generate formatted code + var formatted_code_buffer = std.ArrayList(u8).init(temp_alloc); + fmt.formatAst(parse_ast, formatted_code_buffer.writer().any()) catch |err| { + logDebug("compileSource: formatAst failed: {}\n", .{err}); + return err; + }; + + result.formatted_code = allocator.dupe(u8, formatted_code_buffer.items) catch |err| { + logDebug("compileSource: failed to dupe formatted_code: {}\n", .{err}); + return err; + }; // Collect tokenize diagnostics with additional error handling for (parse_ast.tokenize_diagnostics.items) |diagnostic| { @@ -1260,27 +1276,10 @@ fn writeFormattedResponse(response_buffer: []u8, data: CompilerStageData) Respon try w.writeAll("{\"status\":\"SUCCESS\",\"data\":\""); - if (data.parse_ast) |*parse_ast| { - // Create a local arena for formatting - var local_arena = std.heap.ArenaAllocator.init(allocator); - defer local_arena.deinit(); - const temp_alloc = local_arena.allocator(); - - var formatted = std.ArrayList(u8).init(temp_alloc); - defer formatted.deinit(); - - // Format the AST - in the playground we only deal with file-level parsing - fmt.formatAst(parse_ast.*, formatted.writer().any()) catch { - try writeJsonString(w, "Formatting failed"); - try w.writeAll("\"}"); - try resp_writer.finalize(); - return; - }; - - // Return the formatted code - try writeJsonString(w, formatted.items); + if (data.formatted_code) |formatted| { + try writeJsonString(w, formatted); } else { - try writeJsonString(w, "Parse AST not available for formatting"); + try writeJsonString(w, "Formatted code not available"); } try w.writeAll("\"}"); From d6bf12f15981c6094b685ec50202c8d338207d9c Mon Sep 17 00:00:00 2001 From: Fabian Schmalzried Date: Thu, 4 Sep 2025 11:12:19 +0200 Subject: [PATCH 6/7] Refactor RESET handling to simplify cleanup and ensure all state variables are reset --- src/playground_wasm/main.zig | 69 ++++++++++++++---------------------- 1 file changed, 27 insertions(+), 42 deletions(-) diff --git a/src/playground_wasm/main.zig b/src/playground_wasm/main.zig index 3f84f73b53..d4995e7ea8 100644 --- a/src/playground_wasm/main.zig +++ b/src/playground_wasm/main.zig @@ -596,24 +596,16 @@ fn handleReadyState(message_type: MessageType, root: std.json.Value, response_bu try writeReplInitResponse(response_buffer); }, .RESET => { - // Clean up any complex data structures properly - if (compiler_data) |*old_data| { - old_data.deinit(); - compiler_data = null; - } + // Make sure everything is null + compiler_data = null; + repl_instance = null; + host_message_buffer = null; + host_response_buffer = null; + repl_instance = null; + repl_roc_ops = null; - // Clean up REPL state - cleanupReplState(); - - // Clean up host-managed buffers normally - if (host_message_buffer) |buf| { - allocator.free(buf); - host_message_buffer = null; - } - if (host_response_buffer) |buf| { - allocator.free(buf); - host_response_buffer = null; - } + // Reset allocator to clear all allocations + fba.reset(); current_state = .READY; @@ -653,21 +645,16 @@ fn handleLoadedState(message_type: MessageType, message_json: std.json.Value, re try writeHoverInfoResponse(response_buffer, data, message_json); }, .RESET => { - // Clean up any complex data structures properly - if (compiler_data) |*old_data| { - old_data.deinit(); - compiler_data = null; - } + // Make sure everything is null + compiler_data = null; + repl_instance = null; + host_message_buffer = null; + host_response_buffer = null; + repl_instance = null; + repl_roc_ops = null; - // Clean up host-managed buffers normally - if (host_message_buffer) |buf| { - allocator.free(buf); - host_message_buffer = null; - } - if (host_response_buffer) |buf| { - allocator.free(buf); - host_response_buffer = null; - } + // Reset allocator to clear all allocations + fba.reset(); current_state = .READY; @@ -730,18 +717,16 @@ fn handleReplState(message_type: MessageType, root: std.json.Value, response_buf try writeReplClearResponse(response_buffer); }, .RESET => { - // Clean up REPL state - cleanupReplState(); + // Make sure everything is null + compiler_data = null; + repl_instance = null; + host_message_buffer = null; + host_response_buffer = null; + repl_instance = null; + repl_roc_ops = null; - // Clean up host-managed buffers normally - if (host_message_buffer) |buf| { - allocator.free(buf); - host_message_buffer = null; - } - if (host_response_buffer) |buf| { - allocator.free(buf); - host_response_buffer = null; - } + // Reset allocator to clear all allocations + fba.reset(); current_state = .READY; From 459b01fe835461365cdfc5c2ee719705d1341c81 Mon Sep 17 00:00:00 2001 From: Fabian Schmalzried Date: Thu, 4 Sep 2025 11:20:53 +0200 Subject: [PATCH 7/7] Extract common RESET handling --- src/playground_wasm/main.zig | 47 +++++++++++++----------------------- 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/src/playground_wasm/main.zig b/src/playground_wasm/main.zig index d4995e7ea8..0025c55a96 100644 --- a/src/playground_wasm/main.zig +++ b/src/playground_wasm/main.zig @@ -245,6 +245,20 @@ var debug_log_buffer: [4096]u8 = undefined; var debug_log_pos: usize = 0; var debug_log_oom: bool = false; +/// Reset all global state and allocator +fn resetGlobalState() void { + // Make sure everything is null + compiler_data = null; + repl_instance = null; + host_message_buffer = null; + host_response_buffer = null; + repl_instance = null; + repl_roc_ops = null; + + // Reset allocator to clear all allocations + fba.reset(); +} + /// Writes a formatted string to the in-memory debug log. fn logDebug(comptime format: []const u8, args: anytype) void { if (debug_log_oom) { @@ -596,16 +610,7 @@ fn handleReadyState(message_type: MessageType, root: std.json.Value, response_bu try writeReplInitResponse(response_buffer); }, .RESET => { - // Make sure everything is null - compiler_data = null; - repl_instance = null; - host_message_buffer = null; - host_response_buffer = null; - repl_instance = null; - repl_roc_ops = null; - - // Reset allocator to clear all allocations - fba.reset(); + resetGlobalState(); current_state = .READY; @@ -645,16 +650,7 @@ fn handleLoadedState(message_type: MessageType, message_json: std.json.Value, re try writeHoverInfoResponse(response_buffer, data, message_json); }, .RESET => { - // Make sure everything is null - compiler_data = null; - repl_instance = null; - host_message_buffer = null; - host_response_buffer = null; - repl_instance = null; - repl_roc_ops = null; - - // Reset allocator to clear all allocations - fba.reset(); + resetGlobalState(); current_state = .READY; @@ -717,16 +713,7 @@ fn handleReplState(message_type: MessageType, root: std.json.Value, response_buf try writeReplClearResponse(response_buffer); }, .RESET => { - // Make sure everything is null - compiler_data = null; - repl_instance = null; - host_message_buffer = null; - host_response_buffer = null; - repl_instance = null; - repl_roc_ops = null; - - // Reset allocator to clear all allocations - fba.reset(); + resetGlobalState(); current_state = .READY;