diff --git a/docs/docs.md b/docs/docs.md index 68d3a4908..ed699e35c 100644 --- a/docs/docs.md +++ b/docs/docs.md @@ -194,9 +194,9 @@ func foo(): ``` The `.` prefix is used to reference the current module's namespace. -Unlike local variables, static variables do not currently infer the type from the right hand side so a specific type must be specified or it will default to the `any` type: +A type specifier can be provided after the variable name, otherwise, it's inferred from the initializer: ```cy -var .my_map Map = Map{} +var .my_map any = Map{} ``` Since static variables are initialized outside of a fiber's execution flow, they can not reference any local variables: diff --git a/src/bc_gen.zig b/src/bc_gen.zig index b81e163bd..dd05b4551 100644 --- a/src/bc_gen.zig +++ b/src/bc_gen.zig @@ -361,6 +361,7 @@ fn genStmt(c: *Chunk, idx: u32) anyerror!void { .forRangeStmt => try forRangeStmt(c, idx, node), .funcBlock => try funcBlock(c, idx, node), .ifStmt => try genIfStmt(c, idx, node), + .init_var_sym => try initVarSym(c, idx, node), .loopStmt => try loopStmt(c, idx, node), .mainBlock => try mainBlock(c, idx, node), .block => try genBlock(c, idx, node), @@ -374,7 +375,7 @@ fn genStmt(c: *Chunk, idx: u32) anyerror!void { .setLocal => try irSetLocal(c, idx, node), .set_field => try setField(c, idx, node), .set_deref => try genSetDeref(c, idx, node), - .setVarSym => try setVarSym(c, idx, node), + .set_var_sym => try setVarSym(c, idx, node), .switchStmt => try genSwitchStmt(c, idx, node), .tryStmt => try genTryStmt(c, idx, node), .verbose => try verbose(c, idx, node), @@ -1944,7 +1945,7 @@ fn genVarSym(c: *Chunk, idx: usize, cstr: Cstr, node: *ast.Node) !GenValue { const varId = c.compiler.genSymMap.get(data.sym).?.varSym.id; - const inst = try bc.selectForNoErrNoDepInst(c, cstr, ret_t, true, node); + const inst = try bc.selectForNoErrNoDepInst(c, cstr, ret_t, false, node); if (inst.requiresPreRelease) { try pushRelease(c, inst.dst, node); } @@ -1953,12 +1954,15 @@ fn genVarSym(c: *Chunk, idx: usize, cstr: Cstr, node: *ast.Node) !GenValue { const pc = c.buf.len(); try c.buf.pushOp3(.staticVar, 0, 0, inst.dst); c.buf.setOpArgU16(pc + 1, @intCast(varId)); - if (inst.own_dst) { try initSlot(c, inst.dst, true, node); } - return finishNoErrNoDepInst(c, inst, true); + const boxed = !c.sema.isUnboxedType(ret_t); + if (boxed) { + try c.pushCode(.retain, &.{ inst.dst }, node); + } + return finishNoErrNoDepInst(c, inst, boxed); } fn genFuncPtr(c: *Chunk, idx: usize, cstr: Cstr, node: *ast.Node) !GenValue { @@ -2091,13 +2095,25 @@ fn reserveFuncSlots(c: *Chunk, maxIrLocals: u8, numParamCopies: u8, params: []al nextReg += numParamCopies; } -fn setVarSym(c: *Chunk, idx: usize, node: *ast.Node) !void { +fn initVarSym(c: *Chunk, idx: usize, node: *ast.Node) !void { _ = node; - const data = c.ir.getStmtData(idx, .setVarSym).generic; - const varSym = c.ir.getExprData(data.left, .varSym); + const data = c.ir.getStmtData(idx, .init_var_sym); - const id = c.compiler.genSymMap.get(varSym.sym).?.varSym.id; - _ = try genExpr(c, data.right, Cstr.toVarSym(id)); + const ir_save = c.ir; + defer c.ir = ir_save; + + c.ir = data.src_ir; + const id = c.compiler.genSymMap.get(data.sym).?.varSym.id; + _ = try genExpr(c, data.expr, Cstr.toVarSym(id, false)); +} + +fn setVarSym(c: *Chunk, idx: usize, node: *ast.Node) !void { + _ = node; + const data = c.ir.getStmtData(idx, .set_var_sym); + const id = c.compiler.genSymMap.get(data.sym).?.varSym.id; + const expr_t = c.ir.getExprType(data.expr).id; + const boxed = !c.sema.isUnboxedType(expr_t); + _ = try genExpr(c, data.expr, Cstr.toVarSym(id, boxed)); } fn declareLocalInit(c: *Chunk, idx: u32, node: *ast.Node) !SlotId { @@ -2673,13 +2689,15 @@ fn genToExactDesc(c: *Chunk, src: GenValue, dst: Cstr, node: *ast.Node, extraIdx }, .varSym => { const src_s = getSlot(c, src.reg); - if (src_s.boxed and src_s.type == .local) { + const from_boxed_local = src_s.boxed and src_s.type == .local; + const retain_to_dst = !src_s.boxed_retains and dst.data.varSym.retain; + if (from_boxed_local or retain_to_dst) { try c.pushCode(.retain, &.{ src.reg }, node); } const pc = c.buf.len(); - try c.pushCodeExt(.setStaticVar, &.{ 0, 0, src.reg }, node, extraIdx); - c.buf.setOpArgU16(pc + 1, @intCast(dst.data.varSym)); + try c.pushCodeExt(.setStaticVar, &.{ 0, 0, src.reg, @intFromBool(src_s.boxed) }, node, extraIdx); + c.buf.setOpArgU16(pc + 1, @intCast(dst.data.varSym.id)); // Ownership was moved from temp. try consumeTempValue(c, src, node); @@ -4207,7 +4225,10 @@ pub const Cstr = struct { releaseDst: bool, }, // Runtime id. - varSym: u32, + varSym: struct { + id: u32, + retain: bool, + }, liftedLocal: struct { reg: SlotId, /// This shouldn't change after initialization. @@ -4251,9 +4272,12 @@ pub const Cstr = struct { }}}; } - pub fn toVarSym(id: u32) Cstr { + pub fn toVarSym(id: u32, retain: bool) Cstr { return .{ .type = .varSym, .data = .{ - .varSym = id + .varSym = .{ + .id = id, + .retain = retain, + }, }}; } diff --git a/src/bytecode.zig b/src/bytecode.zig index 830afda88..365de8770 100644 --- a/src/bytecode.zig +++ b/src/bytecode.zig @@ -828,7 +828,8 @@ pub fn dumpInst(vm: *cy.VM, pcOffset: u32, code: OpCode, pc: [*]const Inst, opts .setStaticVar => { const symId = @as(*const align(1) u16, @ptrCast(pc + 1)).*; const src = pc[3].val; - len += try fmt.printCount(w, "vars[{}] = %{}", &.{v(symId), v(src)}); + const release = pc[4].val == 1; + len += try fmt.printCount(w, "vars[{}] = %{}, release={}", &.{v(symId), v(src), v(release)}); }, .catch_op => { const endOffset = @as(*const align(1) u16, @ptrCast(pc + 1)).*; @@ -1019,7 +1020,6 @@ pub fn getInstLenAt(pc: [*]const Inst) u8 { .constOp, .constRetain, .staticVar, - .setStaticVar, .setField, .jumpCond, .compare, @@ -1034,6 +1034,7 @@ pub fn getInstLenAt(pc: [*]const Inst) u8 { const numExprs = pc[2].val; return 4 + numExprs + 1; }, + .setStaticVar, .func_union, .typeCheck, .field, diff --git a/src/cgen.zig b/src/cgen.zig index 6faaf1fbf..dc1093baa 100644 --- a/src/cgen.zig +++ b/src/cgen.zig @@ -513,7 +513,7 @@ pub fn gen(self: *cy.Compiler) !cy.compiler.AotCompileResult { for (self.chunks.items, 0..) |chunk, i| { chunks[i] = .{ .alloc = self.alloc, - .ir = chunk.ir, + .ir = chunk.own_ir, .encoder = chunk.encoder, .out = .{}, .outw = undefined, diff --git a/src/chunk.zig b/src/chunk.zig index d7316ec38..2d6b7da02 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -50,7 +50,7 @@ pub const Chunk = struct { curNode: ?*ast.Node, /// - /// Sema pass + /// Sema pass. /// semaProcs: std.ArrayListUnmanaged(sema.Proc), semaBlocks: std.ArrayListUnmanaged(sema.Block), @@ -66,7 +66,9 @@ pub const Chunk = struct { valueStack: std.ArrayListUnmanaged(cy.Value), - ir: cy.ir.Buffer, + /// IR buffer can be swapped in, this will go away when migrating to `Worker`. + ir: *cy.ir.Buffer, + own_ir: cy.ir.Buffer, /// Maps a IR local to a VM local. genIrLocalMapStack: std.ArrayListUnmanaged(u8), @@ -80,23 +82,12 @@ pub const Chunk = struct { /// The slots in the front are reserved for the call convention and params. slot_stack: std.ArrayListUnmanaged(bc.Slot), - /// Record other chunks that this chunk's static initializer depends on. - symInitChunkDeps: std.AutoHashMapUnmanaged(*cy.Chunk, void), - /// Local vars. varStack: std.ArrayListUnmanaged(sema.LocalVar), varShadowStack: std.ArrayListUnmanaged(cy.sema.VarShadow), preLoopVarSaveStack: std.ArrayListUnmanaged(cy.sema.PreLoopVarSave), assignedVarStack: std.ArrayListUnmanaged(sema.LocalVarId), - /// Which sema sym var is currently being analyzed for an assignment initializer. - curInitingSym: ?*cy.Sym, - curInitingSymDeps: std.AutoHashMapUnmanaged(*cy.Sym, void), - - /// Currently used to store lists of static var dependencies. - symInitDeps: std.ArrayListUnmanaged(SymInitDep), - symInitInfos: std.AutoHashMapUnmanaged(*cy.Sym, SymInitInfo), - /// Main sema block id. mainSemaProcId: sema.ProcId, @@ -173,8 +164,6 @@ pub const Chunk = struct { curHostVarIdx: u32, hasStaticInit: bool, - initializerVisiting: bool, - initializerVisited: bool, encoder: cy.ast.Encoder, @@ -223,7 +212,8 @@ pub const Chunk = struct { .preLoopVarSaveStack = .{}, .typeStack = .{}, .valueStack = .{}, - .ir = cy.ir.Buffer.init(), + .ir = undefined, + .own_ir = cy.ir.Buffer.init(), .slot_stack = .{}, .genIrLocalMapStack = .{}, .dataStack = .{}, @@ -237,11 +227,6 @@ pub const Chunk = struct { .jitBuf = undefined, .x64Enc = undefined, .curNode = null, - .symInitDeps = .{}, - .symInitInfos = .{}, - .curInitingSym = null, - .curInitingSymDeps = .{}, - .symInitChunkDeps = .{}, .tempBufU8 = .{}, .srcOwned = true, .mainSemaProcId = cy.NullId, @@ -258,8 +243,6 @@ pub const Chunk = struct { .typeDeps = .{}, .typeDepsMap = .{}, .hasStaticInit = false, - .initializerVisited = false, - .initializerVisiting = false, .encoder = undefined, .nextUnnamedId = 1, .indent = 0, @@ -271,6 +254,7 @@ pub const Chunk = struct { .host_types = .{}, .host_funcs = .{}, }; + self.ir = &self.own_ir; try self.parser.init(c.alloc); if (cy.hasJIT) { @@ -312,13 +296,9 @@ pub const Chunk = struct { self.unwind_stack.deinit(self.alloc); self.capVarDescs.deinit(self.alloc); - self.symInitDeps.deinit(self.alloc); - self.symInitInfos.deinit(self.alloc); - self.curInitingSymDeps.deinit(self.alloc); - self.typeStack.deinit(self.alloc); self.valueStack.deinit(self.alloc); - self.ir.deinit(self.alloc); + self.own_ir.deinit(self.alloc); self.dataStack.deinit(self.alloc); self.dataU8Stack.deinit(self.alloc); self.listDataStack.deinit(self.alloc); @@ -416,7 +396,7 @@ pub const Chunk = struct { } pub inline fn isInStaticInitializer(self: *Chunk) bool { - return self.curInitingSym != null; + return self.compiler.svar_init_stack.items.len > 0; } pub inline fn semaBlockDepth(self: *Chunk) u32 { @@ -691,16 +671,19 @@ pub const Chunk = struct { /// An instruction that can fail (can throw or panic). pub fn pushFCode(c: *Chunk, code: cy.OpCode, args: []const u8, node: *ast.Node) !void { + log.tracev("pushFCode: {s} {}", .{@tagName(code), c.buf.ops.items.len}); try c.pushFailableDebugSym(node); try c.buf.pushOpSliceExt(code, args, null); } pub fn pushCode(c: *Chunk, code: cy.OpCode, args: []const u8, node: *ast.Node) !void { + log.tracev("pushCode: {s} {}", .{@tagName(code), c.buf.ops.items.len}); try c.pushOptionalDebugSym(node); try c.buf.pushOpSliceExt(code, args, null); } pub fn pushCodeExt(c: *Chunk, code: cy.OpCode, args: []const u8, node: *ast.Node, desc: ?u32) !void { + log.tracev("pushCode: {s} {}", .{@tagName(code), c.buf.ops.items.len}); try c.pushOptionalDebugSym(node); try c.buf.pushOpSliceExt(code, args, desc); } @@ -742,20 +725,6 @@ const ReservedTempLocal = struct { const LocalId = u8; -pub const SymInitInfo = struct { - depStart: u32, - depEnd: u32, - irStart: u32, - irEnd: u32, - visiting: bool = false, - visited: bool = false, -}; - -const SymInitDep = struct { - sym: *cy.Sym, - refNodeId: *ast.Node, -}; - pub const LLVM_Func = struct { typeRef: llvm.TypeRef, funcRef: llvm.ValueRef, diff --git a/src/compiler.zig b/src/compiler.zig index ebc7165ec..19df88288 100644 --- a/src/compiler.zig +++ b/src/compiler.zig @@ -99,6 +99,9 @@ pub const Compiler = struct { chunk_start: u32, type_start: u32, + /// TODO: Move the following into the `Worker`. + svar_init_stack: std.ArrayListUnmanaged(StaticVarInit), + pub fn init(self: *Compiler, vm: *cy.VM) !void { self.* = .{ .alloc = vm.alloc, @@ -125,6 +128,7 @@ pub const Compiler = struct { .chunk_start = 0, .type_start = 0, .context_vars = .{}, + .svar_init_stack = .{}, }; try self.reinitPerRun(); } @@ -174,6 +178,9 @@ pub const Compiler = struct { chunk.deinit(); self.alloc.destroy(chunk); } + for (self.svar_init_stack.items) |*svar_init| { + svar_init.deps.deinit(self.alloc); + } if (reset) { self.chunks.clearRetainingCapacity(); self.chunk_map.clearRetainingCapacity(); @@ -181,6 +188,7 @@ pub const Compiler = struct { self.gen_vtables.clearRetainingCapacity(); self.import_tasks.clearRetainingCapacity(); self.context_vars.clearRetainingCapacity(); + self.svar_init_stack.clearRetainingCapacity(); } else { self.chunks.deinit(self.alloc); self.chunk_map.deinit(self.alloc); @@ -188,6 +196,7 @@ pub const Compiler = struct { self.gen_vtables.deinit(self.alloc); self.import_tasks.deinit(self.alloc); self.context_vars.deinit(self.alloc); + self.svar_init_stack.deinit(self.alloc); } // Chunks depends on modules. @@ -395,8 +404,6 @@ pub const Compiler = struct { log.tracev("Perform init sema.", .{}); for (self.newChunks()) |chunk| { // First stmt is root at index 0. - chunk.initializerVisited = false; - chunk.initializerVisiting = false; if (chunk.hasStaticInit) { try performChunkInitSema(self, chunk); } @@ -616,27 +623,12 @@ fn performChunkInitSema(self: *Compiler, c: *cy.Chunk) !void { _ = try sema.pushFuncProc(c, func); + // Emit in correct dependency order. for (c.syms.items) |sym| { switch (sym.type) { .userVar => { const user_var = sym.cast(.userVar); - try sema.staticDecl(c, sym, user_var.decl.?); - }, - else => {}, - } - } - - // Pop unordered stmts list. - _ = c.ir.popStmtBlock(); - // Create a new stmt list. - try c.ir.pushStmtBlock(c.alloc); - - // Reorder local declarations in DFS order by patching next stmt IR. - for (c.syms.items) |sym| { - switch (sym.type) { - .userVar => { - const info = c.symInitInfos.getPtr(sym).?; - try appendSymInitIrDFS(c, sym, info, null); + try sema.semaUserVarInitDeep(c, user_var); }, else => {}, } @@ -646,32 +638,6 @@ fn performChunkInitSema(self: *Compiler, c: *cy.Chunk) !void { func.emitted = true; } -fn appendSymInitIrDFS(c: *cy.Chunk, sym: *cy.Sym, info: *cy.chunk.SymInitInfo, refNode: ?*ast.Node) !void { - if (info.visited) { - return; - } - if (info.visiting) { - return c.reportErrorFmt("Referencing `{}` creates a circular dependency in the module.", &.{v(sym.name())}, refNode); - } - info.visiting = true; - - const deps = c.symInitDeps.items[info.depStart..info.depEnd]; - if (deps.len > 0) { - for (deps) |dep| { - if (c.symInitInfos.getPtr(dep.sym)) |depInfo| { - try appendSymInitIrDFS(c, dep.sym, depInfo, dep.refNodeId); - } - } - } - - // Append stmt in the correct order. - c.ir.appendToParent(info.irStart); - // Reset this stmt's next or it can end up creating a cycle list. - c.ir.setStmtNext(info.irStart, cy.NullId); - - info.visited = true; -} - fn completeImportTask(self: *Compiler, task: ImportTask, res: *cy.Chunk) !void { switch (task.type) { .nop => {}, @@ -1063,7 +1029,8 @@ fn resolveSyms(self: *Compiler) !void { try sema.resolveContextVar(chunk, @ptrCast(sym)); }, .userVar => { - try sema.resolveUserVar(chunk, @ptrCast(sym)); + // Delay resolving user vars since they are typically only + // referenced when resolving runtime code. }, .hostVar => { try sema.resolveHostVar(chunk, @ptrCast(sym)); @@ -1274,3 +1241,7 @@ const ContextVar = struct { test "vm compiler internals." { } + +const StaticVarInit = struct { + deps: std.ArrayListUnmanaged(*cy.sym.UserVar) = .{}, +}; diff --git a/src/ir.zig b/src/ir.zig index 2f6c36b7e..d5760920a 100644 --- a/src/ir.zig +++ b/src/ir.zig @@ -58,9 +58,12 @@ pub const StmtCode = enum(u8) { set_field_dyn, set_field, setIndex, - setVarSym, + set_var_sym, set_deref, + /// Like `set_var_sym` but allows referencing IR from a different chunk. + init_var_sym, + pushDebugLabel, dumpBytecode, verbose, @@ -483,6 +486,17 @@ pub const Context = struct { sym: *cy.sym.ContextVar, }; +pub const InitVarSym = struct { + src_ir: *cy.ir.Buffer, + sym: *cy.Sym, + expr: Loc, +}; + +pub const SetVarSym = struct { + sym: *cy.Sym, + expr: Loc, +}; + pub const VarSym = struct { sym: *cy.Sym, }; @@ -639,8 +653,9 @@ pub fn StmtData(comptime code: StmtCode) type { .set_field_dyn, .setCaptured, .set_field, - .setVarSym, .set => Set, + .set_var_sym => SetVarSym, + .init_var_sym => InitVarSym, .set_deref => SetDeref, .opSet => OpSet, .pushDebugLabel => PushDebugLabel, diff --git a/src/module.zig b/src/module.zig index 5d0bff74c..68b3be381 100644 --- a/src/module.zig +++ b/src/module.zig @@ -256,7 +256,7 @@ pub const ChunkExt = struct { return sym; } - pub fn resolveUserVar(c: *cy.Chunk, sym: *cy.sym.UserVar, type_id: cy.TypeId) void { + pub fn resolveUserVarType(c: *cy.Chunk, sym: *cy.sym.UserVar, type_id: cy.TypeId) void { _ = c; sym.type = type_id; } diff --git a/src/sema.zig b/src/sema.zig index 9303a9381..548c57c43 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -973,19 +973,25 @@ fn assignStmt(c: *cy.Chunk, node: *ast.Node, left_n: *ast.Node, right: *ast.Node right_res = try c.semaExprOrOpAssignBinExpr(rightExpr, opts.rhsOpAssignBinExpr); } - const irStart = try c.ir.pushEmptyStmt(c.alloc, .set, node); - c.ir.setStmtData(irStart, .set, .{ .generic = .{ - .left_t = leftT, - .right_t = right_res.type, - .left = leftRes.irIdx, - .right = right_res.irIdx, - }}); switch (leftRes.resType) { - .varSym => c.ir.setStmtCode(irStart, .setVarSym), + .varSym => { + const left_ir = c.ir.getExprData(leftRes.irIdx, .varSym); + return try c.ir.pushStmt(c.alloc, .set_var_sym, node, .{ + .sym = left_ir.sym, + .expr = right_res.irIdx, + }); + }, .func => { return c.reportErrorFmt("Can not reassign to a namespace function.", &.{}, node); }, .local => { + const irStart = try c.ir.pushEmptyStmt(c.alloc, .set, node); + c.ir.setStmtData(irStart, .set, .{ .generic = .{ + .left_t = leftT, + .right_t = right_res.type, + .left = leftRes.irIdx, + .right = right_res.irIdx, + }}); c.ir.setStmtCode(irStart, .setLocal); c.varStack.items[leftRes.data.local].vtype = right_res.type; const info = c.varStack.items[leftRes.data.local].inner.local; @@ -993,14 +999,24 @@ fn assignStmt(c: *cy.Chunk, node: *ast.Node, left_n: *ast.Node, right: *ast.Node .id = info.id, .right = right_res.irIdx, }}); + return irStart; + }, + .capturedLocal => { + const irStart = try c.ir.pushEmptyStmt(c.alloc, .set, node); + c.ir.setStmtData(irStart, .set, .{ .generic = .{ + .left_t = leftT, + .right_t = right_res.type, + .left = leftRes.irIdx, + .right = right_res.irIdx, + }}); + c.ir.setStmtCode(irStart, .setCaptured); + return irStart; }, - .capturedLocal => c.ir.setStmtCode(irStart, .setCaptured), else => { log.tracev("leftRes {s} {}", .{@tagName(leftRes.resType), leftRes.type}); return c.reportErrorFmt("Assignment to the left `{}` is unsupported.", &.{v(left_n.type())}, node); } } - return irStart; }, else => { return c.reportErrorFmt("Assignment to the left `{}` is unsupported.", &.{v(left_n.type())}, node); @@ -1484,7 +1500,12 @@ pub fn declareUseImport(c: *cy.Chunk, node: *ast.ImportStmt) !void { c.use_global = true; if (c.compiler.global_sym == null) { const global = try c.reserveUserVar(@ptrCast(c.compiler.main_chunk.sym), "$global", null); - c.resolveUserVar(global, bt.Map); + c.resolveUserVarType(global, bt.Map); + + // Mark as resolved and emitted. + global.ir = 0; + global.emitted = true; + c.compiler.global_sym = global; const func = try c.reserveHostFunc(@ptrCast(c.compiler.main_chunk.sym), "$getGlobal", null, false, false); @@ -2663,20 +2684,69 @@ pub fn resolveContextVar(c: *cy.Chunk, sym: *cy.sym.ContextVar) !void { c.resolveContextVar(sym, exp_var.type, exp_var.idx); } -pub fn resolveUserVar(c: *cy.Chunk, sym: *cy.sym.UserVar) !void { +pub fn ensureUserVarResolved(c: *cy.Chunk, sym: *cy.sym.UserVar) !void { if (sym.isResolved()) { return; } + try resolveUserVar(c, sym); +} +/// Resolves the variable's type and initializer. +/// If type spec is not provided, the initializer is resolved to obtain the type. +pub fn resolveUserVar(c: *cy.Chunk, sym: *cy.sym.UserVar) !void { try pushResolveContext(c); defer popResolveContext(c); - if (sym.decl.?.typed) { - const typeId = try resolveTypeSpecNode(c, sym.decl.?.typeSpec); - c.resolveUserVar(sym, typeId); + const node = sym.decl.?; + + try c.compiler.svar_init_stack.append(c.alloc, .{}); + sym.resolving_init = true; + + var type_id: cy.TypeId = undefined; + var res: ExprResult = undefined; + if (node.typed) { + if (node.typeSpec) |type_spec| { + type_id = try resolveTypeSpecNode(c, type_spec); + res = try c.semaExprCstr(node.right.?, type_id); + } else { + // Infer type from initializer. + res = try c.semaExpr(node.right.?, .{}); + type_id = res.type.toDeclType(); + } } else { - c.resolveUserVar(sym, bt.Dyn); + type_id = bt.Dyn; + res = try c.semaExprCstr(node.right.?, type_id); + } + c.resolveUserVarType(sym, type_id); + sym.ir = res.irIdx; + sym.resolving_init = false; + + var svar_init = c.compiler.svar_init_stack.pop(); + sym.deps = try svar_init.deps.toOwnedSlice(c.alloc); +} + +pub fn semaUserVarInitDeep(c: *cy.Chunk, sym: *cy.sym.UserVar) !void { + if (sym.emitted) { + return; + } + + // Ensure initializer IR. + const src_chunk = sym.head.parent.?.getMod().?.chunk; + try ensureUserVarResolved(src_chunk, sym); + + // Emit deps first. + for (sym.deps) |dep| { + try semaUserVarInitDeep(c, dep); } + + const node = sym.decl.?; + + _ = try c.ir.pushStmt(c.alloc, .init_var_sym, @ptrCast(node), .{ + .src_ir = &src_chunk.own_ir, + .sym = @ptrCast(sym), + .expr = sym.ir, + }); + sym.emitted = true; } pub fn resolveHostVar(c: *cy.Chunk, sym: *cy.sym.HostVar) !void { @@ -2911,34 +2981,6 @@ fn localDecl(c: *cy.Chunk, node: *ast.VarDecl) !void { try c.assignedVarStack.append(c.alloc, varId); } -pub fn staticDecl(c: *cy.Chunk, sym: *Sym, node: *ast.StaticVarDecl) !void { - try sema.pushResolveContext(c); - defer sema.popResolveContext(c); - - c.curInitingSym = sym; - c.curInitingSymDeps.clearRetainingCapacity(); - defer c.curInitingSym = null; - const depStart = c.symInitDeps.items.len; - - const irIdx = try c.ir.pushEmptyStmt(c.alloc, .setVarSym, @ptrCast(node)); - const recLoc = try c.ir.pushExpr(.varSym, c.alloc, bt.Void, @ptrCast(node), .{ .sym = sym }); - const symType = CompactType.init((try c.getSymValueType(sym)).?); - const right = try semaExprCType(c, node.right.?, symType); - c.ir.setStmtData(irIdx, .setVarSym, .{ .generic = .{ - .left_t = symType, - .right_t = right.type, - .left = recLoc, - .right = right.irIdx, - }}); - - try c.symInitInfos.put(c.alloc, sym, .{ - .depStart = @intCast(depStart), - .depEnd = @intCast(c.symInitDeps.items.len), - .irStart = @intCast(irIdx), - .irEnd = @intCast(c.ir.buf.items.len), - }); -} - const SemaExprOptions = struct { target_t: TypeId = cy.NullId, req_target_t: bool = false, @@ -2947,15 +2989,6 @@ const SemaExprOptions = struct { prefer_addressable: bool = false, }; -fn semaExprCType(c: *cy.Chunk, node: *ast.Node, ctype: CompactType) !ExprResult { - return try c.semaExpr(node, .{ - .target_t = ctype.id, - .req_target_t = true, - .fit_target = true, - .fit_target_unbox_dyn = !ctype.dynamic, - }); -} - const ExprResultType = enum(u8) { value, sym, @@ -3170,11 +3203,17 @@ pub fn symbol(c: *cy.Chunk, sym: *Sym, expr: Expr, prefer_ct_sym: bool) !ExprRes const loc = try c.ir.pushExpr(.context, c.alloc, typeId, node, .{ .sym = @ptrCast(sym) }); return ExprResult.init(loc, ctype); }, - .userVar, + .userVar => { + const user_var = sym.cast(.userVar); + const src_chunk = user_var.head.parent.?.getMod().?.chunk; + try ensureUserVarResolved(src_chunk, user_var); + const loc = try c.ir.pushExpr(.varSym, c.alloc, user_var.type, node, .{ .sym = sym }); + return ExprResult.initCustom(loc, .varSym, CompactType.init(user_var.type), .{ .varSym = sym }); + }, .hostVar => { - const typeId = (try c.getSymValueType(sym)) orelse return error.Unexpected; - const ctype = CompactType.init(typeId); - const loc = try c.ir.pushExpr(.varSym, c.alloc, typeId, node, .{ .sym = sym }); + const host_var = sym.cast(.hostVar); + const ctype = CompactType.init(host_var.type); + const loc = try c.ir.pushExpr(.varSym, c.alloc, host_var.type, node, .{ .sym = sym }); return ExprResult.initCustom(loc, .varSym, ctype, .{ .varSym = sym }); }, .func => { @@ -3748,23 +3787,26 @@ pub fn semaMainBlock(compiler: *cy.Compiler, mainc: *cy.Chunk) !u32 { if (!compiler.cont) { if (compiler.global_sym) |global| { - const set = try mainc.ir.pushEmptyStmt(compiler.alloc, .setVarSym, mainc.ast.null_node); - const sym = try mainc.ir.pushExpr(.varSym, compiler.alloc, bt.Map, mainc.ast.null_node, .{ .sym = @as(*cy.Sym, @ptrCast(global)) }); const map = try mainc.semaMap(mainc.ast.null_node); - mainc.ir.setStmtData(set, .setVarSym, .{ .generic = .{ - .left_t = CompactType.initStatic(bt.Map), - .right_t = CompactType.initStatic(bt.Map), - .left = sym, - .right = map.irIdx, - }}); + _ = try mainc.ir.pushStmt(compiler.alloc, .set_var_sym, mainc.ast.null_node, .{ + .sym = @as(*cy.Sym, @ptrCast(global)), + .expr = map.irIdx, + }); } } - // Emit IR for initializers. DFS order. Stop at circular dependency. - // TODO: Allow circular dependency between chunks but not symbols. + // Emit IR to invoke each chunks `$init` which has already has the correct dependency ordering. + // TODO: Should operate per worker not chunk. for (compiler.newChunks()) |c| { - if (c.hasStaticInit and !c.initializerVisited) { - try visitChunkInit(compiler, c); + if (c.hasStaticInit) { + const func = c.sym.getMod().getSym("$init").?.cast(.func).first; + const exprLoc = try compiler.main_chunk.ir.pushExpr(.call_sym, c.alloc, bt.Void, c.ast.null_node, .{ + .func = func, .numArgs = 0, .args = 0, + }); + _ = try compiler.main_chunk.ir.pushStmt(c.alloc, .exprStmt, c.ast.null_node, .{ + .expr = exprLoc, + .isBlockResult = false, + }); } } @@ -3784,35 +3826,6 @@ pub fn semaMainBlock(compiler: *cy.Compiler, mainc: *cy.Chunk) !u32 { return loc; } -fn visitChunkInit(self: *cy.Compiler, c: *cy.Chunk) !void { - c.initializerVisiting = true; - var iter = c.symInitChunkDeps.keyIterator(); - - while (iter.next()) |key| { - const depChunk = key.*; - if (depChunk.initializerVisited) { - continue; - } - if (depChunk.initializerVisiting) { - return c.reportErrorFmt("Referencing `{}` created a circular module dependency.", &.{v(c.srcUri)}, null); - } - try visitChunkInit(self, depChunk); - } - - // Once all deps are visited, emit IR. - const func = c.sym.getMod().getSym("$init").?.cast(.func).first; - const exprLoc = try self.main_chunk.ir.pushExpr(.call_sym, c.alloc, bt.Void, c.ast.null_node, .{ - .func = func, .numArgs = 0, .args = 0, - }); - _ = try self.main_chunk.ir.pushStmt(c.alloc, .exprStmt, c.ast.null_node, .{ - .expr = exprLoc, - .isBlockResult = false, - }); - - c.initializerVisiting = false; - c.initializerVisited = true; -} - pub fn pushProc(self: *cy.Chunk, func: ?*cy.Func) !ProcId { var isStaticFuncBlock = false; var node: *ast.Node = undefined; @@ -4074,11 +4087,6 @@ fn referenceSym(c: *cy.Chunk, sym: *Sym, node: *ast.Node) !void { return; } - if (c.curInitingSym == sym) { - const name = c.ast.nodeString(node); - return c.reportErrorFmt("Reference to `{}` creates a circular dependency.", &.{v(name)}, node); - } - // Determine the chunk the symbol belongs to. // Skip host symbols. var chunk: *cy.Chunk = undefined; @@ -4087,17 +4095,15 @@ fn referenceSym(c: *cy.Chunk, sym: *Sym, node: *ast.Node) !void { } chunk = sym.parent.?.getMod().?.chunk; - if (chunk == c) { - // Internal sym dep. - if (c.curInitingSymDeps.contains(sym)) { - return; + if (c.compiler.svar_init_stack.items.len > 0) { + const user_var = sym.cast(.userVar); + if (user_var.resolving_init) { + return c.reportErrorFmt("Referencing `{}` creates a circular dependency.", &.{v(sym.name())}, node); + } + const cur_svar = &c.compiler.svar_init_stack.items[c.compiler.svar_init_stack.items.len-1]; + if (std.mem.indexOfScalar(*cy.sym.UserVar, cur_svar.deps.items, user_var) == null) { + try cur_svar.deps.append(c.alloc, user_var); } - try c.curInitingSymDeps.put(c.alloc, sym, {}); - try c.symInitDeps.append(c.alloc, .{ .sym = sym, .refNodeId = node }); - } else { - // Module dep. - // Record this symbol's chunk as a dependency. - try c.symInitChunkDeps.put(c.alloc, chunk, {}); } } diff --git a/src/std/test.zig b/src/std/test.zig index 58ecfc2e3..e01025bd2 100644 --- a/src/std/test.zig +++ b/src/std/test.zig @@ -198,7 +198,11 @@ fn eq2(c: cy.Context, type_id: cy.TypeId, act: rt.Any, exp: rt.Any) bool { if (std.meta.eql(actv, expv)) { return true; } else { - rt.errFmt(c, "actual: {} != {}\n", &.{v(actv.type), v(expv.type)}); + const act_name = c.sema.allocTypeName(actv.type) catch @panic(""); + defer c.alloc.free(act_name); + const exp_name = c.sema.allocTypeName(expv.type) catch @panic(""); + defer c.alloc.free(exp_name); + rt.errFmt(c, "actual: {} != {}\n", &.{v(act_name), v(exp_name)}); return false; } }, diff --git a/src/sym.zig b/src/sym.zig index 745a59fdd..53c0585f1 100644 --- a/src/sym.zig +++ b/src/sym.zig @@ -162,7 +162,9 @@ pub const Sym = extern struct { alloc.destroy(self.cast(.context_var)); }, .userVar => { - alloc.destroy(self.cast(.userVar)); + const user_var = self.cast(.userVar); + user_var.deinit(alloc); + alloc.destroy(user_var); }, .hostVar => { alloc.destroy(self.cast(.hostVar)); @@ -481,13 +483,29 @@ pub const ContextVar = extern struct { } }; -pub const UserVar = extern struct { +pub const UserVar = struct { head: Sym, decl: ?*ast.StaticVarDecl, type: cy.TypeId, + /// Holds the initializer expression. + ir: u32, + + /// Whether init statement was emitted. + emitted: bool, + + /// Owned. + deps: []*UserVar, + + /// Used to detect circular reference while resolving `ir`. + resolving_init: bool, + pub fn isResolved(self: *UserVar) bool { - return self.type != cy.NullId; + return self.ir != cy.NullId; + } + + pub fn deinit(self: *UserVar, alloc: std.mem.Allocator) void { + alloc.free(self.deps); } }; @@ -1397,6 +1415,10 @@ pub const ChunkExt = struct { .head = Sym.init(.userVar, parent, name), .decl = decl, .type = cy.NullId, + .ir = cy.NullId, + .emitted = false, + .deps = &.{}, + .resolving_init = false, }); try c.syms.append(c.alloc, @ptrCast(sym)); return sym; diff --git a/src/tools/cbindgen.cy b/src/tools/cbindgen.cy index 5db07862a..62719a58f 100755 --- a/src/tools/cbindgen.cy +++ b/src/tools/cbindgen.cy @@ -45,7 +45,7 @@ if args['rest'].len() <= 2: print 'Missing path to header file.' os.exit(1) -dyn headerPath = args['rest'][2] +var headerPath = args['rest'][2] print headerPath var headerSrc = os.readFile(headerPath) @@ -124,7 +124,7 @@ os.writeFile(args['o'], out) var .skipMap = Map{} var .macros = {_} -var .cvisitor = pointer(void, 0) +dyn .cvisitor = pointer(void, 0) -- Build output string. var .out = '' var .skipChildren = false @@ -349,7 +349,7 @@ func rootVisitor(cursor dyn, parent dyn, state dyn) dyn: print "TODO: var $(name)" else: - print "visitor invoked $(cursor.kind) $(name)" + print "visitor invoked kind=$(cursor.kind) name=$(name)" throw error.Unsupported return clang.CXChildVisit_Continue diff --git a/src/vm.c b/src/vm.c index 34d41243b..3ee03648c 100644 --- a/src/vm.c +++ b/src/vm.c @@ -2122,17 +2122,19 @@ ResultCode execBytecode(VM* vm) { CASE(StaticVar): { u16 symId = READ_U16(1); Value sym = ((StaticVar*)vm->c.varSyms.buf)[symId].value; - retain(vm, sym); stack[pc[3]] = sym; pc += 4; NEXT(); } CASE(SetStaticVar): { u16 symId = READ_U16(1); - Value prev = ((StaticVar*)vm->c.varSyms.buf)[symId].value; + bool release_ = pc[4]; + if (release_) { + Value prev = ((StaticVar*)vm->c.varSyms.buf)[symId].value; + release(vm, prev); + } ((StaticVar*)vm->c.varSyms.buf)[symId].value = stack[pc[3]]; - release(vm, prev); - pc += 4; + pc += 5; NEXT(); } CASE(Context): { diff --git a/src/vm.zig b/src/vm.zig index 874aae2a8..ac42e6ae1 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -373,8 +373,27 @@ pub const VM = struct { logger.tracev("release varSyms", .{}); for (self.c.getVarSyms().items(), 0..) |vsym, i| { - logger.tracevIf(build_options.log_mem, "release varSym: {s}", .{self.varSymExtras.buf[i].name()}); - release(self, vsym.value); + var do_release = false; + const sym = self.varSymExtras.buf[i]; + switch (sym.type) { + .userVar => { + const user_var = sym.cast(.userVar); + if (!self.sema.isUnboxedType(user_var.type)) { + do_release = true; + } + }, + .hostVar => { + const host_var = sym.cast(.hostVar); + if (!self.sema.isUnboxedType(host_var.type)) { + do_release = true; + } + }, + else => std.debug.panic("Unexpected: {}", .{sym.type}), + } + if (do_release) { + logger.tracevIf(build_options.log_mem, "release varSym: {s}", .{self.varSymExtras.buf[i].name()}); + release(self, vsym.value); + } } self.c.getVarSyms().clearRetainingCapacity(); diff --git a/test/trace_test.zig b/test/trace_test.zig index 0c5f546fc..d31ca67cf 100644 --- a/test/trace_test.zig +++ b/test/trace_test.zig @@ -130,8 +130,8 @@ test "ARC for static variable declarations." { _ = try res.getValue(); c.deinit(run.vm); const trace = run.getTrace(); - try t.eq(trace.numRetainAttempts, 7); - try t.eq(trace.numRetains, 6); + try t.eq(trace.numRetainAttempts, 6); + try t.eq(trace.numRetains, 5); }}.func); } diff --git a/test/vars/static_init.cy b/test/vars/static_init.cy index 7e9459173..a9374f4d9 100644 --- a/test/vars/static_init.cy +++ b/test/vars/static_init.cy @@ -8,6 +8,9 @@ var .a = 123 -- Reading from a static variable. t.eq(a, 123) +-- Type is inferred from initializer. +t.eq(typeof(a), int) + -- Invoke as function. var .a1 = func() int: return 123 diff --git a/test/vars/static_init_circular_ref_error.cy b/test/vars/static_init_circular_ref_error.cy index 90fa9a2d6..98524b9e9 100644 --- a/test/vars/static_init_circular_ref_error.cy +++ b/test/vars/static_init_circular_ref_error.cy @@ -2,7 +2,7 @@ var .a = b var .b = a --cytest: error ---CompileError: Referencing `a` creates a circular dependency in the module. +--CompileError: Referencing `a` creates a circular dependency. -- --main:2:10: --var .b = a diff --git a/test/vars/static_init_read_self_error.cy b/test/vars/static_init_read_self_error.cy index 4ed222c26..334e0c45f 100644 --- a/test/vars/static_init_read_self_error.cy +++ b/test/vars/static_init_read_self_error.cy @@ -1,7 +1,7 @@ var .c = c --cytest: error ---CompileError: Reference to `c` creates a circular dependency. +--CompileError: Referencing `c` creates a circular dependency. -- --main:1:10: --var .c = c