diff --git a/src/bc_gen.zig b/src/bc_gen.zig index b0c351b50..645c1675e 100644 --- a/src/bc_gen.zig +++ b/src/bc_gen.zig @@ -257,7 +257,10 @@ pub fn prepareFunc(c: *cy.Compiler, opt_group: ?rt.FuncGroupId, func: *cy.Func) if (opt_group) |group| { _ = try addGroupFunc(c, group, func, rtFunc); } else { - _ = try completeFunc(c, func, rtFunc); + const id = try reserveFunc(c, func); + if (c.vm.funcSyms.buf[id].type == .null) { + completeFunc(c, id, func, rtFunc); + } } }, .userFunc => { @@ -291,10 +294,12 @@ fn reserveFunc(c: *cy.Compiler, func: *cy.Func) !u32 { return res.value_ptr.func.id; } -fn completeFunc(c: *cy.Compiler, func: *cy.Func, sym: rt.FuncSymbol) !u32 { - const id = try reserveFunc(c, func); +fn completeFunc(c: *cy.Compiler, id: u32, func: *cy.Func, sym: rt.FuncSymbol) void { + log.tracev("complete func gen: {s} {}", .{func.name(), id}); + if (c.vm.funcSyms.buf[id].type != .null) { + std.debug.panic("Func already completed: {s} {}", .{func.name(), id}); + } c.vm.funcSyms.buf[id] = sym; - return id; } fn genChunk(c: *Chunk) !void { @@ -553,6 +558,11 @@ pub fn funcBlock(c: *Chunk, idx: usize, node: *ast.Node) !void { const data = c.ir.getStmtData(idx, .funcBlock); const func = data.func; + // Skip if marked as skip (usually for one-off compile-time functions) + if (data.skip) { + return; + } + // Skip if func has already been generated by compile-time. const id = try reserveFunc(c.compiler, func); if (c.vm.funcSyms.buf[id].type != .null) { @@ -570,7 +580,7 @@ pub fn funcBlock(c: *Chunk, idx: usize, node: *ast.Node) !void { const stackSize = c.getMaxUsedRegisters(); const rt_func = rt.FuncSymbol.initFunc(funcPc, @intCast(stackSize), func.numParams, func.funcSigId, func.reqCallTypeCheck, func.isMethod()); - c.vm.funcSyms.buf[id] = rt_func; + completeFunc(c.compiler, id, func, rt_func); try popFuncBlockCommon(c, func); } @@ -3320,7 +3330,8 @@ fn genLambda(c: *Chunk, idx: usize, cstr: Cstr, node: *ast.Node) !GenValue { const stackSize = c.getMaxUsedRegisters(); const rt_func = rt.FuncSymbol.initFunc(funcPc, @intCast(stackSize), func.numParams, func.funcSigId, func.reqCallTypeCheck, func.isMethod()); - const rt_id = try completeFunc(c.compiler, func, rt_func); + const rt_id = try reserveFunc(c.compiler, func); + completeFunc(c.compiler, rt_id, func, rt_func); try popFuncBlockCommon(c, func); c.patchJumpToCurPc(skipJump); diff --git a/src/cte.zig b/src/cte.zig index 8e1fb036e..5887d8183 100644 --- a/src/cte.zig +++ b/src/cte.zig @@ -149,7 +149,7 @@ pub fn expandCtFuncTemplateOnCallArgs(c: *cy.Chunk, template: *cy.sym.Template, try pushNodeValuesCstr(c, args, template, valueStart, node); const arg_vals = c.valueStack.items[valueStart..]; - return expandCtFuncTemplate(c, template, arg_vals); + return expandValueTemplate(c, template, arg_vals); } pub fn expandFuncTemplateOnCallArgs(c: *cy.Chunk, template: *cy.Func, args: []const *ast.Node, node: *ast.Node) !*cy.Func { @@ -252,22 +252,33 @@ fn compileFuncDeep(c: *cy.Chunk, func: *cy.Func, queued: *std.AutoHashMapUnmanag src_chunk.buf = &c.compiler.buf; // TODO: defer restore bc state. try bcgen.prepareFunc(src_chunk.compiler, null, func); - try bcgen.funcBlock(src_chunk, loc, loc_n); - - // Analyze IR for func deps. - var visitor = try src_chunk.ir.visitStmt(c.alloc, @intCast(loc)); - defer visitor.deinit(); - while (try visitor.next()) |entry| { - if (entry.is_stmt) { - continue; - } - const code = src_chunk.ir.getExprCode(entry.loc); - switch (code) { - .call_sym => { - const data = src_chunk.ir.getExprData(entry.loc, .call_sym); - try compileFuncDeep(c, data.func, queued); - }, - else => {}, + switch (func.type) { + .hostFunc => { + // Nop. Already setup from `prepareFunc`. + }, + .userFunc => { + try bcgen.funcBlock(src_chunk, loc, loc_n); + + // Analyze IR for func deps. + var visitor = try src_chunk.ir.visitStmt(c.alloc, @intCast(loc)); + defer visitor.deinit(); + while (try visitor.next()) |entry| { + if (entry.is_stmt) { + continue; + } + const code = src_chunk.ir.getExprCode(entry.loc); + switch (code) { + .call_sym => { + const data = src_chunk.ir.getExprData(entry.loc, .call_sym); + try compileFuncDeep(c, data.func, queued); + }, + else => {}, + } + } + func.data = .{ .userFunc = .{ .loc = @intCast(loc) }}; + }, + else => { + return error.Unexpected; } } func.emitted_deps = true; @@ -292,7 +303,7 @@ pub fn callFunc(c: *cy.Chunk, func: *cy.Func, args: []const cy.Value) !CtValue { }; } -pub fn expandCtFuncTemplate(c: *cy.Chunk, template: *cy.sym.Template, args: []const cy.Value) !CtValue { +pub fn expandValueTemplate(c: *cy.Chunk, template: *cy.sym.Template, args: []const cy.Value) !CtValue { // Ensure variant type. const res = try template.variant_cache.getOrPutContext(c.alloc, args, .{ .sema = c.sema }); if (!res.found_existing) { @@ -328,7 +339,14 @@ pub fn expandCtFuncTemplate(c: *cy.Chunk, template: *cy.sym.Template, args: []co // Generate ct func. Can assume not a `@host` func. const func = try c.createFunc(.userFunc, @ptrCast(template), @ptrCast(template.decl.child_decl), false); - defer c.vm.alloc.destroy(func); + defer { + // Remove references to the func and invalidate the sema func block. + _ = c.compiler.genSymMap.remove(@ptrCast(func)); + const src_chunk = func.chunk(); + const data = src_chunk.ir.getStmtDataPtr(func.data.userFunc.loc, .funcBlock); + data.skip = true; + c.vm.alloc.destroy(func); + } const func_sig = c.sema.getFuncSig(sig); func.funcSigId = sig; func.retType = func_sig.getRetType(); diff --git a/src/ir.zig b/src/ir.zig index 827083423..2f6c36b7e 100644 --- a/src/ir.zig +++ b/src/ir.zig @@ -351,6 +351,10 @@ pub const FuncBlock = struct { // For methods only. parentType: cy.TypeId, + + /// Mark a func block to skip generation. + /// Useful for removing temporary compile-time functions. + skip: bool = false, }; pub const PushBlock = struct { @@ -795,7 +799,7 @@ pub const Buffer = struct { } pub fn pushEmptyExpr(self: *Buffer, comptime code: ExprCode, alloc: std.mem.Allocator, expr_t: ExprType, node_id: *ast.Node) !u32 { - log.tracev("irPushExpr: {}", .{code}); + log.tracev("irPushExpr: {} at {}", .{code, self.buf.items.len}); const start = self.buf.items.len; try self.buf.resize(alloc, self.buf.items.len + 1 + 8 + 4 + @sizeOf(ExprData(code))); self.buf.items[start] = @intFromEnum(code); @@ -908,7 +912,7 @@ pub const Buffer = struct { } pub fn pushEmptyStmt2(self: *Buffer, alloc: std.mem.Allocator, comptime code: StmtCode, node: *ast.Node, comptime appendToParent_: bool) !u32 { - log.tracev("irPushStmt: {}", .{code}); + log.tracev("irPushStmt: {} at {}", .{code, self.buf.items.len}); const start: u32 = @intCast(self.buf.items.len); try self.buf.resize(alloc, self.buf.items.len + 1 + 8 + 4 + @sizeOf(StmtData(code))); self.buf.items[start] = @intFromEnum(code); diff --git a/src/sym.zig b/src/sym.zig index 661537b54..3549275df 100644 --- a/src/sym.zig +++ b/src/sym.zig @@ -1063,6 +1063,11 @@ pub const Func = struct { vtable_idx: u32, }, template: *FuncTemplate, + userFunc: struct { + /// Currently used to invalidate the IR func block when removing temporary functions. + /// See `cte.expandValueTemplate`. + loc: u32, + }, }, variant: ?*Variant, reqCallTypeCheck: bool,