diff --git a/docs/hugo/content/docs/toc/syntax.md b/docs/hugo/content/docs/toc/syntax.md index 8cc622b16..3ae5ce449 100644 --- a/docs/hugo/content/docs/toc/syntax.md +++ b/docs/hugo/content/docs/toc/syntax.md @@ -61,22 +61,18 @@ var a = 123 a = 234 ``` -A new variable can be declared in function blocks with the same name as a variable from a parent block. +Blocks create a new variable scope. Variables declared in the current scope will take precedence over any parent variables with the same name. ```cy -var a = 123 -foo = func(): - -- A new local `a` inside function `foo`. +func foo(): + -- `a` declared inside `foo`. var a = 234 -foo() -print a -- '123' -``` -However, variables declared in sub-blocks such as `if` and `for` can not shadow variables in the same main/function block. -```cy -var a = 123 -if true: - -- CompileError, `a` is already declared in the main block. - var a = 234 + if true: + -- A new `a` declared inside `if`. + var a = 345 + print a -- Prints "345" + + print a -- Prints "234" ``` When a parent local is referenced in a [lambda function]({{}}), the variable is automatically captured. Note that [static functions]({{}}) can not capture parent locals. diff --git a/src/arc.zig b/src/arc.zig index 75a5e7756..9fe622dec 100644 --- a/src/arc.zig +++ b/src/arc.zig @@ -108,12 +108,9 @@ pub fn runTempReleaseOps(vm: *cy.VM, fp: [*]const cy.Value, tempIdx: u32) void { } } -pub fn runBlockEndReleaseOps(vm: *cy.VM, stack: []const cy.Value, framePtr: usize, startPc: usize) void { - if (vm.ops[startPc].opcode() == .releaseN) { - const numLocals = vm.ops[startPc+1].val; - for (vm.ops[startPc+2..startPc+2+numLocals]) |local| { - release(vm, stack[framePtr + local.val]); - } +pub fn releaseLocals(vm: *cy.VM, stack: []const cy.Value, framePtr: usize, locals: cy.IndexSlice(u8)) void { + for (stack[framePtr+locals.start..framePtr+locals.end]) |val| { + release(vm, val); } } @@ -146,11 +143,11 @@ pub inline fn retain(self: *cy.VM, val: cy.Value) linksection(cy.HotSection) voi } if (val.isPointer()) { const obj = val.asHeapObject(); - obj.head.rc += 1; if (cy.Trace) { checkRetainDanglingPointer(self, obj); log.tracev("retain {} {}", .{obj.getUserTag(), obj.head.rc}); } + obj.head.rc += 1; if (cy.TrackGlobalRC) { self.refCounts += 1; } @@ -166,11 +163,11 @@ pub inline fn retainInc(self: *cy.VM, val: cy.Value, inc: u32) linksection(cy.Ho } if (val.isPointer()) { const obj = val.asHeapObject(); - obj.head.rc += inc; if (cy.Trace) { checkRetainDanglingPointer(self, obj); log.tracev("retain {} {}", .{obj.getUserTag(), obj.head.rc}); } + obj.head.rc += inc; if (cy.TrackGlobalRC) { self.refCounts += inc; } @@ -293,10 +290,12 @@ fn performSweep(vm: *cy.VM) !GCResult { vm.numFreed += @intCast(cycObjs.items.len); } - return GCResult{ + const res = GCResult{ .numCycFreed = @intCast(cycObjs.items.len), .numObjFreed = if (cy.Trace) vm.numFreed else 0, }; + log.tracev("gc result: num cyc {}, num obj {}", .{res.numCycFreed, res.numObjFreed}); + return res; } fn markMainStackRoots(vm: *cy.VM) !void { @@ -311,8 +310,8 @@ fn markMainStackRoots(vm: *cy.VM) !void { const symIdx = cy.debug.indexOfDebugSym(vm, pcOff) orelse return error.NoDebugSym; const sym = cy.debug.getDebugSymByIndex(vm, symIdx); const tempIdx = cy.debug.getDebugTempIndex(vm, symIdx); - const endLocalsPc = cy.debug.debugSymToEndLocalsPc(vm, sym); - log.debug("mark frame: {} {} {} {}", .{pcOff, vm.ops[pcOff].opcode(), tempIdx, endLocalsPc}); + const locals = sym.getLocals(); + log.tracev("mark frame: pc={} {} fp={} {}, locals={}..{}", .{pcOff, vm.ops[pcOff].opcode(), fpOff, tempIdx, locals.start, locals.end}); if (tempIdx != cy.NullId) { const fp = vm.stack.ptr + fpOff; @@ -328,14 +327,10 @@ fn markMainStackRoots(vm: *cy.VM) !void { } } - if (endLocalsPc != cy.NullId) { - if (vm.ops[endLocalsPc].opcode() == .releaseN) { - const numLocals = vm.ops[endLocalsPc+1].val; - for (vm.ops[endLocalsPc+2..endLocalsPc+2+numLocals]) |local| { - const v = vm.stack[fpOff + local.val]; - if (v.isCycPointer()) { - markValue(vm, v); - } + if (locals.len() > 0) { + for (vm.stack[fpOff+locals.start..fpOff+locals.end]) |val| { + if (val.isCycPointer()) { + markValue(vm, val); } } } diff --git a/src/builtins/bindings.zig b/src/builtins/bindings.zig index dc2cf3f8a..fa8979e7e 100644 --- a/src/builtins/bindings.zig +++ b/src/builtins/bindings.zig @@ -2055,7 +2055,7 @@ pub const ModuleBuilder = struct { ) !void { const funcSigId = try sema.ensureFuncSig(self.compiler, params, ret); const funcSig = self.compiler.sema.getFuncSig(funcSigId); - if (funcSig.isParamsTyped) { + if (funcSig.reqCallTypeCheck) { try self.vm.addMethod(typeId, mgId, rt.MethodInit.initHostTyped(funcSigId, ptr, @intCast(params.len))); } else { try self.vm.addMethod(typeId, mgId, rt.MethodInit.initHostUntyped(funcSigId, ptr, @intCast(params.len))); diff --git a/src/bytecode.zig b/src/bytecode.zig index 3d96ba7fa..a12063f1e 100644 --- a/src/bytecode.zig +++ b/src/bytecode.zig @@ -139,12 +139,14 @@ pub const ByteCodeBuffer = struct { }); } - pub fn pushFailableDebugSym(self: *ByteCodeBuffer, pc: usize, file: u32, loc: u32, frameLoc: u32, unwindTempIdx: u32) !void { + pub fn pushFailableDebugSym(self: *ByteCodeBuffer, pc: usize, file: u32, loc: u32, frameLoc: u32, unwindTempIdx: u32, localStart: u8, localEnd: u8) !void { try self.debugTable.append(self.alloc, .{ .pc = @intCast(pc), .loc = loc, .file = @intCast(file), .frameLoc = frameLoc, + .localStart = localStart, + .localEnd = localEnd, }); try self.debugTempIndexTable.append(self.alloc, unwindTempIdx); } @@ -388,12 +390,6 @@ pub fn dumpInst(pcOffset: u32, code: OpCode, pc: [*]const Inst, len: usize, extr const dst = pc[3].val; fmt.printStderr("{} {} left={}, right={}, dst={}", &.{v(pcOffset), v(code), v(left), v(right), v(dst)}); }, - .field => { - const recv = pc[1].val; - const dst = pc[2].val; - const symId = @as(*const align(1) u16, @ptrCast(pc + 3)).*; - fmt.printStderr("{} {} recv={}, dst={}, sym={}", &.{v(pcOffset), v(code), v(recv), v(dst), v(symId)}); - }, .fieldRetain => { const recv = pc[1].val; const dst = pc[2].val; @@ -492,11 +488,6 @@ pub fn dumpInst(pcOffset: u32, code: OpCode, pc: [*]const Inst, len: usize, extr const right = pc[3].val; fmt.printStderr("{} {} list={}, index={}, right={}", &.{v(pcOffset), v(code), v(list), v(index), v(right)}); }, - .init => { - const start = pc[1].val; - const numLocals = pc[2].val; - fmt.printStderr("{} {} start={}, numLocals={}", &.{v(pcOffset), v(code), v(start), v(numLocals) }); - }, .coinit => { const startArgs = pc[1].val; const numArgs = pc[2].val; @@ -609,7 +600,15 @@ pub const DebugSym = extern struct { frameLoc: u32, /// CompileChunkId. - file: u32, + file: u16, + + /// Which locals are alive before this instruction. + localStart: u8, + localEnd: u8, + + pub fn getLocals(sym: cy.DebugSym) cy.IndexSlice(u8) { + return cy.IndexSlice(u8).init(sym.localStart, @intCast(sym.localEnd)); + } }; const DebugMarkerType = enum(u8) { @@ -687,7 +686,6 @@ pub fn getInstLenAt(pc: [*]const Inst) u8 { const numVars = pc[1].val; return 2 + numVars; }, - .init, .popTry, .copy, .copyRetainSrc, @@ -763,8 +761,6 @@ pub fn getInstLenAt(pc: [*]const Inst) u8 { }, .fieldRetain, .fieldRetainIC, - .field, - .fieldIC, .forRangeInit => { return 8; }, @@ -878,8 +874,6 @@ pub const OpCode = enum(u8) { /// [calleeLocal] [numArgs] [numRet=0/1] call = vmc.CodeCall, - field = vmc.CodeField, - fieldIC = vmc.CodeFieldIC, fieldRetain = vmc.CodeFieldRetain, fieldRetainIC = vmc.CodeFieldRetainIC, lambda = vmc.CodeLambda, @@ -905,10 +899,6 @@ pub const OpCode = enum(u8) { stringTemplate = vmc.CodeStringTemplate, negFloat = vmc.CodeNegFloat, - /// Initialize locals starting from `startLocal` to the `none` value. - /// init [startLocal] [numLocals] - init = vmc.CodeInit, - objectTypeCheck = vmc.CodeObjectTypeCheck, objectSmall = vmc.CodeObjectSmall, object = vmc.CodeObject, @@ -999,7 +989,7 @@ pub const OpCode = enum(u8) { }; test "bytecode internals." { - try t.eq(std.enums.values(OpCode).len, 109); + try t.eq(std.enums.values(OpCode).len, 106); try t.eq(@sizeOf(Inst), 1); try t.eq(@sizeOf(Const), 8); try t.eq(@alignOf(Const), 8); diff --git a/src/chunk.zig b/src/chunk.zig index 70b16aa29..24c2822b0 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -60,7 +60,9 @@ pub const Chunk = struct { /// Additional info for initializer symbols. semaInitializerSyms: std.AutoArrayHashMapUnmanaged(sema.CompactSymbolId, sema.InitializerSym), - declaredVarStack: std.ArrayListUnmanaged(sema.LocalVarId), + varShadowStack: std.ArrayListUnmanaged(cy.sema.VarShadow), + varDeclStack: std.ArrayListUnmanaged(cy.sema.NameVar), + preLoopVarSaveStack: std.ArrayListUnmanaged(cy.sema.PreLoopVarSave), assignedVarStack: std.ArrayListUnmanaged(sema.LocalVarId), curSemaBlockId: sema.BlockId, curSemaSubBlockId: sema.SubBlockId, @@ -174,7 +176,9 @@ pub const Chunk = struct { .blockJumpStack = .{}, .subBlockJumpStack = .{}, .assignedVarStack = .{}, - .declaredVarStack = .{}, + .varShadowStack = .{}, + .varDeclStack = .{}, + .preLoopVarSaveStack = .{}, .regStack = .{}, .operandStack = .{}, .unwindTempIndexStack = .{}, @@ -249,7 +253,9 @@ pub const Chunk = struct { self.blockJumpStack.deinit(self.alloc); self.subBlockJumpStack.deinit(self.alloc); self.assignedVarStack.deinit(self.alloc); - self.declaredVarStack.deinit(self.alloc); + self.varShadowStack.deinit(self.alloc); + self.varDeclStack.deinit(self.alloc); + self.preLoopVarSaveStack.deinit(self.alloc); self.regStack.deinit(self.alloc); self.operandStack.deinit(self.alloc); self.unwindTempIndexStack.deinit(self.alloc); @@ -294,33 +300,6 @@ pub const Chunk = struct { return self.semaBlockDepth() == 1; } - pub fn pushSemaBlock(self: *Chunk, id: sema.BlockId) !void { - // Codegen block should be pushed first so nextSemaSubBlock can use it. - try self.pushBlock(); - - const sblock = self.semaBlocks.items[id]; - if (self.blocks.items.len == 1) { - const tempStart: u8 = @intCast(sblock.locals.items.len); - self.rega.resetState(tempStart); - } else { - const tempStart: u8 = @intCast(sblock.params.items.len + sblock.locals.items.len + 5); - self.rega.resetState(tempStart); - } - - try self.semaBlockStack.append(self.alloc, id); - self.curSemaBlockId = id; - self.nextSemaSubBlockId = self.semaBlocks.items[id].firstSubBlockId; - self.nextSemaSubBlock(); - } - - pub fn popSemaBlock(self: *Chunk) void { - self.semaBlockStack.items.len -= 1; - self.curSemaBlockId = self.semaBlockStack.items[self.semaBlockStack.items.len-1]; - self.prevSemaSubBlock(); - - self.popBlock(); - } - pub fn reserveIfTempLocal(self: *Chunk, local: LocalId) !void { if (self.isTempLocal(local)) { try self.setReservedTempLocal(local); @@ -328,14 +307,14 @@ pub const Chunk = struct { } pub inline fn isTempLocal(self: *const Chunk, local: LocalId) bool { - return local >= self.curBlock.numLocals; + return local >= self.rega.tempStart; } pub inline fn isParamOrLocalVar(self: *const Chunk, reg: u8) bool { if (self.blocks.items.len > 1) { - return reg != 0 and reg < self.curBlock.numLocals; + return reg != 0 and reg < self.rega.tempStart; } else { - return reg < self.curBlock.numLocals; + return reg < self.rega.tempStart; } } @@ -413,59 +392,6 @@ pub const Chunk = struct { try self.regStack.append(self.alloc, reg); } - pub fn reserveLocal(self: *Chunk, block: *GenBlock) !u8 { - const idx = block.numLocals; - block.numLocals += 1; - if (idx <= std.math.maxInt(u8)) { - return @intCast(idx); - } else { - return self.reportError("Exceeded max local count: {}", &.{v(@as(u8, std.math.maxInt(u8)))}); - } - } - - /// Reserve params and captured vars. - /// Function stack layout: - /// [startLocal/retLocal] [retInfo] [retAddress] [prevFramePtr] [params...] [callee] [var locals...] [temp locals...] - /// `callee` is reserved so that function values can call static functions with the same call convention. - /// For this reason, `callee` isn't freed in the function body and a separate release inst is required for lambda calls. - /// A closure can also occupy the callee and is used to do captured var lookup. - pub fn reserveFuncParams(self: *Chunk, numParams: u32) !void { - // First local is reserved for a single return value. - _ = try self.reserveLocal(self.curBlock); - - // Second local is reserved for the return info. - _ = try self.reserveLocal(self.curBlock); - - // Third local is reserved for the return address. - _ = try self.reserveLocal(self.curBlock); - - // Fourth local is reserved for the previous frame pointer. - _ = try self.reserveLocal(self.curBlock); - - const sblock = sema.curBlock(self); - - // Reserve func params. - for (sblock.params.items[0..numParams]) |varId| { - _ = try self.reserveLocalVar(varId); - - // Params are already defined. - self.vars.items[varId].isDefinedOnce = true; - } - - // An extra callee slot is reserved so that function values - // can call static functions with the same call convention. - _ = try self.reserveLocal(self.curBlock); - - if (sblock.params.items.len > numParams) { - for (sblock.params.items[numParams..]) |varId| { - _ = try self.reserveLocalVar(varId); - - // Params are already defined. - self.vars.items[varId].isDefinedOnce = true; - } - } - } - pub fn setNodeFuncDecl(self: *Chunk, nodeId: cy.NodeId, declId: sema.FuncDeclId) void { self.nodes[nodeId].head.func.semaDeclId = declId; } @@ -521,40 +447,6 @@ pub const Chunk = struct { return null; } - pub fn genBlockEnding(self: *Chunk) !void { - self.curBlock.endLocalsPc = @intCast(self.buf.ops.items.len); - try self.endLocals(); - if (self.curBlock.requiresEndingRet1) { - try self.buf.pushOp(.ret1); - } else { - try self.buf.pushOp(.ret0); - } - } - - pub fn endLocals(self: *Chunk) !void { - const sblock = sema.curBlock(self); - - const start = self.operandStack.items.len; - defer self.operandStack.items.len = start; - - for (sblock.locals.items) |varId| { - const svar = self.vars.items[varId]; - if (svar.lifetimeRcCandidate and svar.isDefinedOnce) { - try self.operandStack.append(self.alloc, svar.local); - } - } - - const locals = self.operandStack.items[start..]; - if (locals.len > 0) { - const nodeId = sema.getBlockNodeId(self, sblock); - try self.pushOptionalDebugSym(nodeId); - - // For now always use `releaseN` to distinguish between temp release ops. - try self.buf.pushOp1(.releaseN, @intCast(locals.len)); - try self.buf.pushOperands(locals); - } - } - pub fn pushJumpBackNotNone(self: *Chunk, toPc: usize, condLocal: LocalId) !void { const pc = self.buf.ops.items.len; try self.buf.pushOp3(.jumpNotNone, 0, 0, condLocal); @@ -666,9 +558,7 @@ pub const Chunk = struct { pub fn patchBlockJumps(self: *Chunk, jumpStackStart: usize) void { for (self.blockJumpStack.items[jumpStackStart..]) |jump| { switch (jump.jumpT) { - .jumpToEndLocals => { - self.buf.setOpArgU16(jump.pc + jump.pcOffset, @intCast(self.curBlock.endLocalsPc - jump.pc)); - } + else => {}, } } } @@ -720,56 +610,29 @@ pub const Chunk = struct { } } - pub fn reserveLocalVar(self: *Chunk, varId: sema.LocalVarId) !LocalId { - const local = try self.reserveLocal(self.curBlock); - self.vars.items[varId].local = local; - return local; - } - - pub fn nextSemaSubBlock(self: *Chunk) void { - self.curSemaSubBlockId = self.nextSemaSubBlockId; - self.nextSemaSubBlockId += 1; - - const ssblock = sema.curSubBlock(self); - for (ssblock.iterVarBeginTypes.items) |varAndType| { - const svar = &self.vars.items[varAndType.id]; - // log.debug("{s} iter var", .{self.getVarName(varAndType.id)}); - svar.vtype = varAndType.vtype; - svar.isDefinedOnce = true; - } - } - - pub fn prevSemaSubBlock(self: *Chunk) void { - const ssblock = sema.curSubBlock(self); - self.curSemaSubBlockId = ssblock.prevSubBlockId; - - // Update narrow types. - for (ssblock.endMergeTypes.items) |it| { - self.vars.items[it.id].vtype = it.vtype; - } - } - pub fn unescapeString(self: *Chunk, literal: []const u8) ![]const u8 { try self.tempBufU8.resize(self.alloc, literal.len); return cy.sema.unescapeString(self.tempBufU8.items, literal); } pub fn dumpLocals(self: *const Chunk, sblock: *sema.Block) !void { - if (builtin.mode == .Debug and !cy.silentInternal) { - fmt.printStderr("Locals:\n", &.{}); - for (sblock.params.items) |varId| { - const svar = self.vars.items[varId]; - fmt.printStderr("{} (param), local: {}, curType: {}, rc: {}, lrc: {}, boxed: {}, capIdx: {}\n", &.{ - v(svar.name), v(svar.local), v(svar.vtype), - v(types.isRcCandidateType(self.compiler, svar.vtype)), v(svar.lifetimeRcCandidate), v(svar.isBoxed), v(svar.capturedIdx), - }); - } - for (sblock.locals.items) |varId| { - const svar = self.vars.items[varId]; - fmt.printStderr("{}, local: {}, curType: {}, rc: {}, lrc: {}, boxed: {}, capIdx: {}\n", &.{ - v(svar.name), v(svar.local), v(svar.vtype), - v(types.isRcCandidateType(self.compiler, svar.vtype)), v(svar.lifetimeRcCandidate), v(svar.isBoxed), v(svar.capturedIdx), - }); + if (cy.Trace) { + if (!cy.silentInternal) { + fmt.printStderr("Locals:\n", &.{}); + for (sblock.params.items) |varId| { + const svar = self.vars.items[varId]; + fmt.printStderr("{} (param), local: {}, curType: {}, rc: {}, lrc: {}, boxed: {}, capIdx: {}\n", &.{ + v(svar.name), v(svar.local), v(svar.vtype), + v(types.isRcCandidateType(self.compiler, svar.vtype)), v(svar.lifetimeRcCandidate), v(svar.isBoxed), v(svar.capturedIdx), + }); + } + for (sblock.locals.items) |varId| { + const svar = self.vars.items[varId]; + fmt.printStderr("{}, local: {}, curType: {}, rc: {}, lrc: {}, boxed: {}, capIdx: {}\n", &.{ + v(svar.name), v(svar.local), v(svar.vtype), + v(types.isRcCandidateType(self.compiler, svar.vtype)), v(svar.lifetimeRcCandidate), v(svar.isBoxed), v(svar.capturedIdx), + }); + } } } } @@ -787,6 +650,12 @@ pub const Chunk = struct { return error.CompileError; } + pub fn getNodeIdTokenString(self: *const Chunk, nodeId: cy.NodeId) []const u8 { + const node = self.nodes[nodeId]; + const token = self.tokens[node.start_token]; + return self.src[token.pos()..token.data.end_pos]; + } + pub fn getNodeTokenString(self: *const Chunk, node: cy.Node) []const u8 { const token = self.tokens[node.start_token]; return self.src[token.pos()..token.data.end_pos]; @@ -795,7 +664,10 @@ pub const Chunk = struct { /// An optional debug sym is only included in Debug builds. pub fn pushOptionalDebugSym(self: *Chunk, nodeId: cy.NodeId) !void { if (builtin.mode == .Debug or self.compiler.vm.config.genAllDebugSyms) { - try self.buf.pushFailableDebugSym(self.buf.ops.items.len, self.id, nodeId, self.curBlock.frameLoc, cy.NullId); + try self.buf.pushFailableDebugSym( + self.buf.ops.items.len, self.id, nodeId, self.curBlock.frameLoc, + cy.NullId, 0, 0, + ); } } @@ -885,7 +757,10 @@ pub const Chunk = struct { pub fn pushFailableDebugSym(self: *Chunk, nodeId: cy.NodeId) !void { const unwindTempIdx = try self.getLastUnwindTempIndex(); - try self.buf.pushFailableDebugSym(self.buf.ops.items.len, self.id, nodeId, self.curBlock.frameLoc, unwindTempIdx); + try self.buf.pushFailableDebugSym( + self.buf.ops.items.len, self.id, nodeId, self.curBlock.frameLoc, + unwindTempIdx, self.curBlock.startLocalReg, self.curBlock.nextLocalReg, + ); } fn pushFailableDebugSymAt(self: *Chunk, pc: usize, nodeId: cy.NodeId, unwindTempIdx: u32) !void { @@ -907,11 +782,7 @@ test "chunk internals." { } const GenBlock = struct { - /// This includes the return info, function params, captured params, and local vars. - /// Does not include temp locals. - numLocals: u32, frameLoc: cy.NodeId = cy.NullId, - endLocalsPc: u32, /// Whether codegen should create an ending that returns 1 arg. /// Otherwise `ret0` is generated. @@ -926,18 +797,24 @@ const GenBlock = struct { regaNextTemp: u8, regaMaxTemp: u8, + /// Starts after the prelude registers. + startLocalReg: u8, + + /// Increased as var decls are encountered and recedes by sub-block's `numLocals`. + nextLocalReg: u8, + /// LLVM funcRef: if (cy.hasJIT) llvm.ValueRef else void = undefined, fn init() GenBlock { return .{ - .numLocals = 0, - .endLocalsPc = 0, .requiresEndingRet1 = false, .closureLocal = cy.NullU8, .regaTempStart = undefined, .regaNextTemp = undefined, .regaMaxTemp = undefined, + .startLocalReg = 0, + .nextLocalReg = 0, }; } diff --git a/src/codegen.zig b/src/codegen.zig index 3c6fbed41..50dc5639a 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -65,6 +65,7 @@ pub const GenValue = struct { fn identifier(c: *Chunk, nodeId: cy.NodeId, cstr: RegisterCstr) !GenValue { const node = c.nodes[nodeId]; + const semaType = c.nodeTypes[nodeId]; if (c.genGetVar(node.head.ident.semaVarId)) |svar| { // csymId would be active instead. cy.dassert(svar.type != .staticAlias); @@ -76,36 +77,22 @@ fn identifier(c: *Chunk, nodeId: cy.NodeId, cstr: RegisterCstr) !GenValue { const selfLocal: u8 = @intCast(4 + sblock.params.items.len - 1); const name = c.getNodeTokenString(node); const fieldId = try c.compiler.vm.ensureFieldSym(name); - if (cstr.mustRetain) { - try pushFieldRetain(c, selfLocal, dst, @intCast(fieldId)); - return c.initGenValue(dst, bt.Any, true); - } else { - const pc = c.compiler.buf.len(); - try c.compiler.buf.pushOpSlice(.field, &.{ selfLocal, dst, 0, 0, 0, 0, 0 }); - c.compiler.buf.setOpArgU16(pc + 3, @intCast(fieldId)); - return c.initGenValue(dst, bt.Any, false); - } + try pushFieldRetain(c, selfLocal, dst, @intCast(fieldId), nodeId); + return c.initGenValue(dst, bt.Any, true); } else if (svar.type == .parentObjectMemberAlias) { dst = try c.rega.selectFromNonLocalVar(cstr, cstr.mustRetain); try c.buf.pushOp3(.captured, c.curBlock.closureLocal, svar.capturedIdx, dst); try c.buf.pushOp2(.boxValue, dst, dst); const name = c.getNodeTokenString(node); const fieldId = try c.compiler.vm.ensureFieldSym(name); - if (cstr.mustRetain) { - try pushFieldRetain(c, dst, dst, @intCast(fieldId)); - return c.initGenValue(dst, bt.Any, true); - } else { - const pc = c.buf.len(); - try c.buf.pushOpSlice(.field, &.{ dst, dst, 0, 0, 0, 0, 0 }); - c.buf.setOpArgU16(pc + 3, @intCast(fieldId)); - return c.initGenValue(dst, bt.Any, false); - } + try pushFieldRetain(c, dst, dst, @intCast(fieldId), nodeId); + return c.initGenValue(dst, bt.Any, true); } else if (!svar.isParentLocalAlias()) { dst = try c.rega.selectFromLocalVar(cstr, svar.local); } else { dst = try c.rega.selectFromNonLocalVar(cstr, cstr.mustRetain); } - if (cstr.mustRetain and types.isRcCandidateType(c.compiler, svar.vtype)) { + if (cstr.mustRetain and types.isRcCandidateType(c.compiler, semaType)) { // Ensure retain +1. if (svar.isBoxed) { if (svar.isParentLocalAlias()) { @@ -121,7 +108,7 @@ fn identifier(c: *Chunk, nodeId: cy.NodeId, cstr: RegisterCstr) !GenValue { try c.buf.pushOp2(.copyRetainSrc, svar.local, dst); } } - return c.initGenValue(dst, svar.vtype, true); + return c.initGenValue(dst, semaType, true); } else { if (svar.isBoxed) { if (svar.isParentLocalAlias()) { @@ -137,7 +124,7 @@ fn identifier(c: *Chunk, nodeId: cy.NodeId, cstr: RegisterCstr) !GenValue { try c.buf.pushOp2(.copy, svar.local, dst); } } - return c.initGenValue(dst, svar.vtype, false); + return c.initGenValue(dst, semaType, false); } } else { return symbolTo(c, node.head.ident.sema_csymId, cstr); @@ -716,19 +703,18 @@ fn lambdaMulti(self: *Chunk, nodeId: cy.NodeId, req: RegisterCstr) !GenValue { const func = self.semaFuncDecls.items[node.head.func.semaDeclId]; - try self.pushSemaBlock(func.semaBlockId); + try pushSemaBlock(self, func.semaBlockId); self.curBlock.frameLoc = nodeId; const jumpStackStart = self.blockJumpStack.items.len; const opStart: u32 = @intCast(self.buf.ops.items.len); // Generate function body. - try self.reserveFuncParams(func.numParams); - try initVarLocals(self); + try reserveFuncRegs(self, func.numParams); try genStatements(self, node.head.func.bodyHead, false); - try self.genBlockEnding(); + try genBlockEnd(self); self.patchJumpToCurPc(jumpPc); const sblock = sema.curBlock(self); @@ -739,7 +725,7 @@ fn lambdaMulti(self: *Chunk, nodeId: cy.NodeId, req: RegisterCstr) !GenValue { self.blockJumpStack.items.len = jumpStackStart; const stackSize = self.getMaxUsedRegisters(); - self.popSemaBlock(); + popSemaBlock(self); const dst = try self.rega.selectFromNonLocalVar(req, true); @@ -781,15 +767,14 @@ fn lambdaExpr(self: *Chunk, nodeId: cy.NodeId, req: RegisterCstr) !GenValue { const jumpPc = try self.pushEmptyJump(); const func = self.semaFuncDecls.items[node.head.func.semaDeclId]; - try self.pushSemaBlock(func.semaBlockId); + try pushSemaBlock(self, func.semaBlockId); const opStart: u32 = @intCast(self.buf.ops.items.len); // Generate function body. - try self.reserveFuncParams(func.numParams); - try initVarLocals(self); + try reserveFuncRegs(self, func.numParams); _ = try expression(self, node.head.func.bodyHead, RegisterCstr.exactMustRetain(0)); - try self.endLocals(); + try genBlockReleaseLocals(self); try self.buf.pushOp(.ret1); self.patchJumpToCurPc(jumpPc); @@ -797,7 +782,7 @@ fn lambdaExpr(self: *Chunk, nodeId: cy.NodeId, req: RegisterCstr) !GenValue { const numCaptured: u8 = @intCast(sblock.captures.items.len); const closureLocal = self.curBlock.closureLocal; const stackSize = self.getMaxUsedRegisters(); - self.popSemaBlock(); + popSemaBlock(self); const dst = try self.rega.selectFromNonLocalVar(req, true); @@ -1121,25 +1106,12 @@ fn genField(self: *cy.Chunk, nodeId: cy.NodeId, req: RegisterCstr) !GenValue { const newtype = (try sema.getAccessExprResult(self, leftv.vtype, name)).exprT; - try self.pushFailableDebugSym(nodeId); - const leftIsTempRetained = leftv.retained and leftv.isTempLocal; - if (req.mustRetain or leftIsTempRetained) { - try pushFieldRetain(self, leftv.local, dst, @intCast(fieldId)); - - // ARC cleanup. - try releaseIfRetainedTemp(self, leftv); - - return self.initGenValue(dst, newtype, true); - } else { - const pc = self.buf.len(); - try self.buf.pushOpSlice(.field, &.{ leftv.local, dst, 0, 0, 0, 0, 0 }); - self.buf.setOpArgU16(pc + 3, @intCast(fieldId)); + try pushFieldRetain(self, leftv.local, dst, @intCast(fieldId), nodeId); - // ARC cleanup. - try releaseIfRetainedTemp(self, leftv); + // ARC cleanup. + try releaseIfRetainedTemp(self, leftv); - return self.initGenValue(dst, newtype, false); - } + return self.initGenValue(dst, newtype, true); } /// Only generates the top declaration statements. @@ -1185,9 +1157,7 @@ pub fn genStatements(self: *Chunk, head: cy.NodeId, comptime attachEnd: bool) an if (attachEnd) { const local = try exprStmt(self, cur_id, true); if (shouldGenMainScopeReleaseOps(self.compiler)) { - self.curBlock.endLocalsPc = @intCast(self.buf.ops.items.len); - self.nodes[0].head.root.genEndLocalsPc = self.curBlock.endLocalsPc; - try self.endLocals(); + try genBlockReleaseLocals(self); } try self.buf.pushOp1(.end, local); } else { @@ -1197,9 +1167,7 @@ pub fn genStatements(self: *Chunk, head: cy.NodeId, comptime attachEnd: bool) an if (attachEnd) { try statement(self, cur_id); if (shouldGenMainScopeReleaseOps(self.compiler)) { - self.curBlock.endLocalsPc = @intCast(self.buf.ops.items.len); - self.nodes[0].head.root.genEndLocalsPc = self.curBlock.endLocalsPc; - try self.endLocals(); + try genBlockReleaseLocals(self); } try self.buf.pushOp1(.end, 255); } else { @@ -1235,7 +1203,11 @@ fn statement(c: *Chunk, nodeId: cy.NodeId) !void { if (node.head.localDecl.right != cy.NullId) { // Can assume left is .ident from sema. const varSpec = c.nodes[node.head.localDecl.varSpec]; - try assignExprToLocalVar(c, varSpec.head.varSpec.name, node.head.localDecl.right); + const varId = c.nodes[varSpec.head.varSpec.name].head.ident.semaVarId; + + _ = try reserveLocalReg(c, varId, false); + try assignExprToLocalVar(c, varSpec.head.varSpec.name, node.head.localDecl.right, true); + c.curBlock.nextLocalReg += 1; } }, .assign_stmt => { @@ -1307,15 +1279,16 @@ fn statement(c: *Chunk, nodeId: cy.NodeId) !void { var errorVarLocal: u8 = cy.NullU8; if (node.head.tryStmt.errorVar != cy.NullId) { const errorVarN = c.nodes[node.head.tryStmt.errorVar]; - errorVarLocal = c.genGetVar(errorVarN.head.ident.semaVarId).?.local; + errorVarLocal = try reserveLocalReg(c, errorVarN.head.ident.semaVarId, true); } const pushTryPc = c.buf.ops.items.len; try c.buf.pushOp3(.pushTry, errorVarLocal, 0, 0); // try body. - c.nextSemaSubBlock(); + nextSemaSubBlock(c); try genStatements(c, node.head.tryStmt.tryFirstStmt, false); - c.prevSemaSubBlock(); + try genSubBlockReleaseLocals(c); + prevSemaSubBlock(c); // pop try block. const popTryPc = c.buf.ops.items.len; @@ -1323,9 +1296,10 @@ fn statement(c: *Chunk, nodeId: cy.NodeId) !void { c.buf.setOpArgU16(pushTryPc + 2, @intCast(c.buf.ops.items.len - pushTryPc)); // catch body. - c.nextSemaSubBlock(); + nextSemaSubBlock(c); try genStatements(c, node.head.tryStmt.catchFirstStmt, false); - c.prevSemaSubBlock(); + try genSubBlockReleaseLocals(c); + prevSemaSubBlock(c); c.buf.setOpArgU16(popTryPc + 1, @intCast(c.buf.ops.items.len - popTryPc)); }, @@ -1336,7 +1310,7 @@ fn statement(c: *Chunk, nodeId: cy.NodeId) !void { try whileOptStmt(c, nodeId); }, .whileInfStmt => { - c.nextSemaSubBlock(); + nextSemaSubBlock(c); const pcSave: u32 = @intCast(c.buf.ops.items.len); const jumpStackSave: u32 = @intCast(c.subBlockJumpStack.items.len); @@ -1353,13 +1327,14 @@ fn statement(c: *Chunk, nodeId: cy.NodeId) !void { // } try genStatements(c, node.head.child_head, false); + try genSubBlockReleaseLocals(c); try c.pushJumpBackTo(pcSave); c.patchForBlockJumps(jumpStackSave, c.buf.ops.items.len, pcSave); - c.prevSemaSubBlock(); + prevSemaSubBlock(c); }, .whileCondStmt => { - c.nextSemaSubBlock(); + nextSemaSubBlock(c); const topPc: u32 = @intCast(c.buf.ops.items.len); const jumpStackSave: u32 = @intCast(c.subBlockJumpStack.items.len); @@ -1378,12 +1353,13 @@ fn statement(c: *Chunk, nodeId: cy.NodeId) !void { c.rega.setNextTemp(tempStart); try genStatements(c, node.head.whileCondStmt.bodyHead, false); + try genSubBlockReleaseLocals(c); try c.pushJumpBackTo(topPc); c.patchJumpNotCondToCurPc(jumpPc); c.patchForBlockJumps(jumpStackSave, c.buf.ops.items.len, topPc); - c.prevSemaSubBlock(); + prevSemaSubBlock(c); }, .for_range_stmt => { try forRangeStmt(c, nodeId); @@ -1393,18 +1369,19 @@ fn statement(c: *Chunk, nodeId: cy.NodeId) !void { }, .return_stmt => { if (c.blocks.items.len == 1) { - try c.endLocals(); + try genBlockReleaseLocals(c); try c.buf.pushOp1(.end, 255); } else { - try c.endLocals(); + try genBlockReleaseLocals(c); try c.buf.pushOp(.ret0); } }, .return_expr_stmt => { + // TODO: If the returned expr is a local, consume the local after copying to reg 0. if (c.blocks.items.len == 1) { // Main block. const val = try expression(c, node.head.child_head, RegisterCstr.simpleMustRetain); - try c.endLocals(); + try genBlockReleaseLocals(c); try c.buf.pushOp1(.end, @intCast(val.local)); } else { // if (c.curBlock.funcSymId != cy.NullId) { @@ -1414,7 +1391,7 @@ fn statement(c: *Chunk, nodeId: cy.NodeId) !void { // } else { _ = try expression(c, node.head.child_head, RegisterCstr.exactMustRetain(0)); // } - try c.endLocals(); + try genBlockReleaseLocals(c); try c.buf.pushOp(.ret1); } }, @@ -1429,7 +1406,7 @@ fn statement(c: *Chunk, nodeId: cy.NodeId) !void { fn whileOptStmt(c: *cy.Chunk, nodeId: cy.NodeId) !void { const node = c.nodes[nodeId]; - c.nextSemaSubBlock(); + nextSemaSubBlock(c); const topPc: u32 = @intCast(c.buf.ops.items.len); const jumpStackSave: u32 = @intCast(c.subBlockJumpStack.items.len); @@ -1438,10 +1415,9 @@ fn whileOptStmt(c: *cy.Chunk, nodeId: cy.NodeId) !void { var optLocal: LocalId = undefined; if (node.head.whileOptStmt.some != cy.NullId) { const some = c.nodes[node.head.whileOptStmt.some]; - optLocal = c.genGetVar(some.head.ident.semaVarId).?.local; - // Since this variable is used in the loop, it is considered defined before codegen. - c.vars.items[some.head.ident.semaVarId].isDefinedOnce = true; - try assignExprToLocalVar(c, node.head.whileOptStmt.some, node.head.whileOptStmt.opt); + optLocal = try reserveLocalReg(c, some.head.ident.semaVarId, false); + try assignExprToLocalVar(c, node.head.whileOptStmt.some, node.head.whileOptStmt.opt, true); + c.curBlock.nextLocalReg += 1; } else { const optv = try expression(c, node.head.whileOptStmt.opt, RegisterCstr.simple); optLocal = optv.local; @@ -1452,12 +1428,13 @@ fn whileOptStmt(c: *cy.Chunk, nodeId: cy.NodeId) !void { c.patchJumpNotNoneToCurPc(skipSkipJump); try genStatements(c, node.head.whileOptStmt.bodyHead, false); + try genSubBlockReleaseLocals(c); try c.pushJumpBackTo(topPc); c.patchJumpToCurPc(skipBodyJump); c.patchForBlockJumps(jumpStackSave, c.buf.ops.items.len, topPc); - c.prevSemaSubBlock(); + prevSemaSubBlock(c); } fn opAssignStmt(c: *cy.Chunk, nodeId: cy.NodeId) !void { @@ -1556,7 +1533,7 @@ fn assignStmt(c: *cy.Chunk, nodeId: cy.NodeId) !void { const left = c.nodes[node.head.left_right.left]; if (left.node_t == .ident) { if (left.head.ident.semaVarId != cy.NullId) { - try assignExprToLocalVar(c, node.head.left_right.left, node.head.left_right.right); + try assignExprToLocalVar(c, node.head.left_right.left, node.head.left_right.right, false); } else { const csymId = left.head.ident.sema_csymId; if (!csymId.isFuncSymId) { @@ -1672,9 +1649,10 @@ fn ifStmt(c: *cy.Chunk, nodeId: cy.NodeId) !void { c.rega.setNextTemp(tempStart); - c.nextSemaSubBlock(); + nextSemaSubBlock(c); try genStatements(c, node.head.left_right.right, false); - c.prevSemaSubBlock(); + try genSubBlockReleaseLocals(c); + prevSemaSubBlock(c); var elseClauseId = node.head.left_right.extra; if (elseClauseId != cy.NullId) { @@ -1690,9 +1668,10 @@ fn ifStmt(c: *cy.Chunk, nodeId: cy.NodeId) !void { const elseClause = c.nodes[elseClauseId]; if (elseClause.head.else_clause.cond == cy.NullId) { - c.nextSemaSubBlock(); + nextSemaSubBlock(c); try genStatements(c, elseClause.head.else_clause.body_head, false); - c.prevSemaSubBlock(); + try genSubBlockReleaseLocals(c); + prevSemaSubBlock(c); endsWithElse = true; break; } else { @@ -1707,9 +1686,10 @@ fn ifStmt(c: *cy.Chunk, nodeId: cy.NodeId) !void { c.rega.setNextTemp(tempStart2); - c.nextSemaSubBlock(); + nextSemaSubBlock(c); try genStatements(c, elseClause.head.else_clause.body_head, false); - c.prevSemaSubBlock(); + try genSubBlockReleaseLocals(c); + prevSemaSubBlock(c); elseClauseId = elseClause.head.else_clause.else_clause; } } @@ -1756,8 +1736,8 @@ fn comptimeStmt(c: *Chunk, nodeId: cy.NodeId) !void { } fn forIterStmt(c: *Chunk, nodeId: cy.NodeId) !void { - c.nextSemaSubBlock(); - defer c.prevSemaSubBlock(); + nextSemaSubBlock(c); + defer prevSemaSubBlock(c); const node = c.nodes[nodeId]; @@ -1771,16 +1751,15 @@ fn forIterStmt(c: *Chunk, nodeId: cy.NodeId) !void { if (hasEach) { const eachClause = c.nodes[node.head.for_iter_stmt.eachClause]; if (eachClause.node_t == .ident) { - valReg = c.genGetVar(eachClause.head.ident.semaVarId).?.local; - - // At this point the temp var is loosely defined. - c.vars.items[eachClause.head.ident.semaVarId].isDefinedOnce = true; + valReg = try reserveLocalReg(c, eachClause.head.ident.semaVarId, false); } else if (eachClause.node_t == .seqDestructure) { var curId = eachClause.head.seqDestructure.head; + var nextLocalReg = c.curBlock.nextLocalReg; while (curId != cy.NullId) { const ident = c.nodes[curId]; - seqRegs[seqLen] = c.genGetVar(ident.head.ident.semaVarId).?.local; - c.vars.items[ident.head.ident.semaVarId].isDefinedOnce = true; + try reserveLocalRegAt(c, ident.head.ident.semaVarId, nextLocalReg); + seqRegs[seqLen] = nextLocalReg; + nextLocalReg += 1; seqLen += 1; if (seqLen == 10) { return c.reportErrorAt("Too many destructure identifiers: 10", &.{}, node.head.for_iter_stmt.eachClause); @@ -1791,7 +1770,6 @@ fn forIterStmt(c: *Chunk, nodeId: cy.NodeId) !void { } else { cy.unexpected(); } - } // Reserve temp local for iterator. @@ -1822,11 +1800,12 @@ fn forIterStmt(c: *Chunk, nodeId: cy.NodeId) !void { try pushRelease(c, iterLocal + 5, node.head.for_iter_stmt.iterable); if (hasEach) { skipFromTop = try c.pushEmptyJumpNone(iterLocal + 1); - try pushReleases(c, seqRegs[0..seqLen], node.head.for_iter_stmt.eachClause); try c.pushFailableDebugSym(nodeId); try c.buf.pushOp2(.seqDestructure, iterLocal + 1, @intCast(seqLen)); try c.buf.pushOperands(seqRegs[0..seqLen]); + c.curBlock.nextLocalReg += @intCast(seqLen); + try pushRelease(c, iterLocal + 1, node.head.for_iter_stmt.eachClause); } else { skipFromTop = try c.pushEmptyJumpNone(iterLocal + 1); @@ -1838,7 +1817,8 @@ fn forIterStmt(c: *Chunk, nodeId: cy.NodeId) !void { try pushRelease(c, iterLocal + 5, node.head.for_iter_stmt.iterable); if (hasEach) { try c.pushOptionalDebugSym(nodeId); - try c.buf.pushOp2(.copyReleaseDst, iterLocal + 1, valReg); + try c.buf.pushOp2(.copy, iterLocal + 1, valReg); + c.curBlock.nextLocalReg += 1; } skipFromTop = try c.pushEmptyJumpNone(iterLocal + 1); } @@ -1847,6 +1827,7 @@ fn forIterStmt(c: *Chunk, nodeId: cy.NodeId) !void { const jumpStackSave: u32 = @intCast(c.subBlockJumpStack.items.len); defer c.subBlockJumpStack.items.len = jumpStackSave; try genStatements(c, node.head.for_iter_stmt.body_head, false); + try genSubBlockReleaseLocals(c); const contPc = c.buf.ops.items.len; @@ -1860,7 +1841,6 @@ fn forIterStmt(c: *Chunk, nodeId: cy.NodeId) !void { if (hasEach) { // Must check for `none` before destructuring. skipFromInner = try c.pushEmptyJumpNone(iterLocal + 1); - try pushReleases(c, seqRegs[0..seqLen], node.head.for_iter_stmt.eachClause); try c.pushFailableDebugSym(nodeId); try c.buf.pushOp2(.seqDestructure, iterLocal + 1, @intCast(seqLen)); @@ -1874,7 +1854,7 @@ fn forIterStmt(c: *Chunk, nodeId: cy.NodeId) !void { try pushRelease(c, iterLocal + 5, node.head.for_iter_stmt.iterable); if (hasEach) { try c.pushOptionalDebugSym(nodeId); - try c.buf.pushOp2(.copyReleaseDst, iterLocal + 1, valReg); + try c.buf.pushOp2(.copy, iterLocal + 1, valReg); } } @@ -1895,15 +1875,15 @@ fn forIterStmt(c: *Chunk, nodeId: cy.NodeId) !void { } fn forRangeStmt(c: *Chunk, nodeId: cy.NodeId) !void { - c.nextSemaSubBlock(); - defer c.prevSemaSubBlock(); + nextSemaSubBlock(c); + defer prevSemaSubBlock(c); const node = c.nodes[nodeId]; var local: u8 = cy.NullU8; if (node.head.for_range_stmt.eachClause != cy.NullId) { const ident = c.nodes[node.head.for_range_stmt.eachClause]; - local = c.genGetVar(ident.head.ident.semaVarId).?.local; + local = try reserveLocalReg(c, ident.head.ident.semaVarId, true); // inc = as_clause.head.as_range_clause.inc; // if (as_clause.head.as_range_clause.step != NullId) { @@ -1948,6 +1928,7 @@ fn forRangeStmt(c: *Chunk, nodeId: cy.NodeId) !void { const jumpStackSave: u32 = @intCast(c.subBlockJumpStack.items.len); defer c.subBlockJumpStack.items.len = jumpStackSave; try genStatements(c, node.head.for_range_stmt.body_head, false); + try genSubBlockReleaseLocals(c); // Perform counter update and perform check against end range. const jumpBackOffset: u16 = @intCast(c.buf.ops.items.len - bodyPc); @@ -2015,8 +1996,8 @@ fn matchBlock(self: *Chunk, nodeId: cy.NodeId, cstr: ?RegisterCstr) !GenValue { var condOffset: u32 = 0; while (curCase != cy.NullId) { const case = self.nodes[curCase]; - self.nextSemaSubBlock(); - defer self.prevSemaSubBlock(); + nextSemaSubBlock(self); + defer prevSemaSubBlock(self); const blockPc = self.buf.ops.items.len; if (isStmt) { @@ -2032,6 +2013,7 @@ fn matchBlock(self: *Chunk, nodeId: cy.NodeId, cstr: ?RegisterCstr) !GenValue { try genStatements(self, case.head.caseBlock.firstChild, false); } } + try genSubBlockReleaseLocals(self); if (isStmt) { if (case.next != cy.NullId) { @@ -2603,6 +2585,7 @@ fn pushReleaseOpt2(self: *Chunk, pushRegA: bool, rega: u8, pushRegB: bool, regb: fn pushReleases(self: *Chunk, regs: []const u8, debugNodeId: cy.NodeId) !void { if (regs.len > 1) { + try self.pushOptionalDebugSym(debugNodeId); try self.buf.pushOp1(.releaseN, @intCast(regs.len)); try self.buf.pushOperands(regs); } else if (regs.len == 1) { @@ -2692,7 +2675,7 @@ fn funcDecl(self: *Chunk, parentSymId: sema.SymbolId, nodeId: cy.NodeId) !void { const jumpPc = try self.pushEmptyJump(); - try self.pushSemaBlock(func.semaBlockId); + try pushSemaBlock(self, func.semaBlockId); if (self.compiler.config.genDebugFuncMarkers) { try self.compiler.buf.pushDebugFuncStart(node.head.func.semaDeclId, self.id); } @@ -2704,12 +2687,10 @@ fn funcDecl(self: *Chunk, parentSymId: sema.SymbolId, nodeId: cy.NodeId) !void { const jumpStackStart = self.blockJumpStack.items.len; const opStart: u32 = @intCast(self.buf.ops.items.len); - try self.reserveFuncParams(func.numParams); - try initVarLocals(self); + try reserveFuncRegs(self, func.numParams); try genStatements(self, node.head.func.bodyHead, false); // TODO: Check last statement to skip adding ret. - try self.genBlockEnding(); - func.genEndLocalsPc = self.curBlock.endLocalsPc; + try genBlockEnd(self); // Reserve another local for the call return info. const sblock = sema.curBlock(self); @@ -2722,7 +2703,7 @@ fn funcDecl(self: *Chunk, parentSymId: sema.SymbolId, nodeId: cy.NodeId) !void { self.blockJumpStack.items.len = jumpStackStart; const stackSize = self.getMaxUsedRegisters(); - self.popSemaBlock(); + popSemaBlock(self); if (self.compiler.config.genDebugFuncMarkers) { try self.compiler.buf.pushDebugFuncEnd(node.head.func.semaDeclId, self.id); @@ -3030,9 +3011,7 @@ fn expression(c: *Chunk, nodeId: cy.NodeId, cstr: RegisterCstr) anyerror!GenValu return c.initGenValue(dst, bt.Any, true); }, .coyield => { - const pc = c.buf.ops.items.len; - try c.buf.pushOp2(.coyield, 0, 0); - try c.blockJumpStack.append(c.alloc, .{ .jumpT = .jumpToEndLocals, .pc = @intCast(pc), .pcOffset = 1 }); + try c.buf.pushOp2(.coyield, c.curBlock.startLocalReg, c.curBlock.nextLocalReg); // TODO: return coyield expression. const dst = try c.rega.selectFromNonLocalVar(cstr, false); @@ -3294,10 +3273,7 @@ fn binOpAssignToField( const recv = try expression(self, left.head.accessExpr.left, RegisterCstr.simple); const leftLocal = try self.rega.consumeNextTemp(); - const pc = self.buf.len(); - try self.pushFailableDebugSym(leftId); - try self.buf.pushOpSlice(.field, &.{ recv.local, leftLocal, 0, 0, 0, 0, 0 }); - self.buf.setOpArgU16(pc + 3, @intCast(fieldId)); + try pushFieldRetain(self, recv.local, leftLocal, @intCast(fieldId), leftId); const resv = try binExpr2(self, .{ .leftId = leftId, @@ -3325,20 +3301,19 @@ fn genMethodDecl(self: *Chunk, typeId: rt.TypeId, node: cy.Node, func: sema.Func const jumpPc = try self.pushEmptyJump(); - try self.pushSemaBlock(func.semaBlockId); + try pushSemaBlock(self, func.semaBlockId); if (self.compiler.config.genDebugFuncMarkers) { try self.compiler.buf.pushDebugFuncStart(node.head.func.semaDeclId, self.id); } const opStart: u32 = @intCast(self.buf.ops.items.len); - try self.reserveFuncParams(func.numParams); - try initVarLocals(self); + try reserveFuncRegs(self, func.numParams); try genStatements(self, node.head.func.bodyHead, false); // TODO: Check last statement to skip adding ret. - try self.genBlockEnding(); + try genBlockEnd(self); const stackSize = self.getMaxUsedRegisters(); - self.popSemaBlock(); + popSemaBlock(self); if (self.compiler.config.genDebugFuncMarkers) { try self.compiler.buf.pushDebugFuncEnd(node.head.func.semaDeclId, self.id); } @@ -3346,7 +3321,7 @@ fn genMethodDecl(self: *Chunk, typeId: rt.TypeId, node: cy.Node, func: sema.Func self.patchJumpToCurPc(jumpPc); const funcSig = self.compiler.sema.getFuncSig(func.funcSigId); - if (funcSig.isTyped) { + if (funcSig.reqCallTypeCheck) { const m = rt.MethodInit.initTyped(func.funcSigId, opStart, stackSize, func.numParams); try self.compiler.vm.addMethod(typeId, mgId, m); } else { @@ -3355,13 +3330,13 @@ fn genMethodDecl(self: *Chunk, typeId: rt.TypeId, node: cy.Node, func: sema.Func } } -fn assignExprToLocalVar(c: *Chunk, leftId: cy.NodeId, exprId: cy.NodeId) !void { +fn assignExprToLocalVar(c: *Chunk, leftId: cy.NodeId, exprId: cy.NodeId, comptime init: bool) !void { const varId = c.nodes[leftId].head.ident.semaVarId; + const varT = c.nodeTypes[leftId]; const expr = c.nodes[exprId]; const svar = c.genGetVarPtr(varId).?; if (svar.isBoxed) { - cy.dassert(svar.isDefinedOnce); try assignExprToBoxedVar(c, svar, exprId); return; } @@ -3370,10 +3345,9 @@ fn assignExprToLocalVar(c: *Chunk, leftId: cy.NodeId, exprId: cy.NodeId) !void { const csymId = expr.head.ident.sema_csymId; if (csymId.isPresent()) { // Copying a symbol. - if (!svar.isDefinedOnce or !types.isRcCandidateType(c.compiler, svar.vtype)) { + if (init or !types.isRcCandidateType(c.compiler, svar.vtype)) { const exprv = try expression(c, exprId, RegisterCstr.exactMustRetain(svar.local)); svar.vtype = exprv.vtype; - svar.isDefinedOnce = true; } else { const exprv = try expression(c, exprId, RegisterCstr.tempMustRetain); svar.vtype = exprv.vtype; @@ -3384,7 +3358,7 @@ fn assignExprToLocalVar(c: *Chunk, leftId: cy.NodeId, exprId: cy.NodeId) !void { } const exprv = try expression(c, exprId, RegisterCstr.simple); - if (svar.isDefinedOnce) { + if (!init) { if (types.isRcCandidateType(c.compiler, svar.vtype)) { // log.debug("releaseSet {} {}", .{varId, svar.vtype.typeT}); if (types.isRcCandidateType(c.compiler, exprv.vtype)) { @@ -3408,7 +3382,6 @@ fn assignExprToLocalVar(c: *Chunk, leftId: cy.NodeId, exprId: cy.NodeId) !void { } else { try c.buf.pushOp2(.copy, exprv.local, svar.local); } - svar.isDefinedOnce = true; svar.vtype = exprv.vtype; } return; @@ -3438,10 +3411,9 @@ fn assignExprToLocalVar(c: *Chunk, leftId: cy.NodeId, exprId: cy.NodeId) !void { } // Retain rval. - if (!svar.isDefinedOnce or !types.isRcCandidateType(c.compiler, svar.vtype)) { + if (init or !types.isRcCandidateType(c.compiler, varT)) { const exprv = try expression(c, exprId, RegisterCstr.exactMustRetain(svar.local)); svar.vtype = exprv.vtype; - svar.isDefinedOnce = true; } else { const exprv = try expression(c, exprId, RegisterCstr.simpleMustRetain); cy.dassert(exprv.local != svar.local); @@ -3455,7 +3427,6 @@ fn assignExprToBoxedVar(self: *Chunk, svar: *sema.LocalVar, exprId: cy.NodeId) ! // Retain rval. const exprv = try expression(self, exprId, RegisterCstr.tempMustRetain); svar.vtype = exprv.vtype; - svar.isDefinedOnce = true; if (!types.isRcCandidateType(self.compiler, svar.vtype)) { if (svar.isParentLocalAlias()) { const temp = try self.rega.consumeNextTemp(); @@ -3477,54 +3448,32 @@ fn assignExprToBoxedVar(self: *Chunk, svar: *sema.LocalVar, exprId: cy.NodeId) ! } } -/// Reserve and initialize all var locals to `none`. -/// There are a few reasons why this is needed: -/// 1. Since explicit initializers can fail (errors are thrown by default) -/// the shared endLocals inst can still rely on the var locals having defined values. -/// 2. By always generating this fixed sized `init` inst, it allows single-pass -/// compilation and doesn't require copying a temp inst buffer after a dynamically sized prelude. -/// 3. It allows variables first assigned in a loop construct to rely on a defined value for -/// a release op before use. This also allows the lifetime of all var locals to the block end. -pub fn initVarLocals(self: *Chunk) !void { - const sblock = sema.curBlock(self); - - // Use the callee local for closure. - if (sblock.captures.items.len > 0) { - self.curBlock.closureLocal = @intCast(4 + sblock.params.items.len); +fn reserveLocalRegAt(c: *Chunk, varId: sema.LocalVarId, reg: u8) !void { + const svar = &c.vars.items[varId]; + svar.local = reg; - // Captured vars are already defined. - for (sblock.captures.items) |varId| { - self.vars.items[varId].isDefinedOnce = true; - } - } + try c.varDeclStack.append(c.alloc, .{ + // Name was only needed for sema. + .namePtr = undefined, + .nameLen = undefined, + .varId = varId, + }); - for (sblock.params.items) |varId| { - const svar = &self.vars.items[varId]; - if (!svar.isParentLocalAlias()) { - svar.isBoxed = false; - } + // Reset boxed. + if (!svar.isParentLocalAlias()) { + svar.isBoxed = false; } + log.tracev("reserve {s}: {}", .{sema.getVarName(c, varId), svar.local}); +} - // Main block var locals start at 0 otherwise after the call return info and params. - // All locals must be defined at anytime since there is only one cleanup procedure (endLocals). - const startLocal: u8 = if (self.semaBlockDepth() == 1) 0 else @intCast(4 + sblock.params.items.len + 1); - try self.buf.pushOp2(.init, startLocal, @intCast(sblock.locals.items.len)); - - // Reserve the locals. - for (sblock.locals.items) |varId| { - _ = try self.reserveLocalVar(varId); - - // Reset boxed. - const svar = &self.vars.items[varId]; - if (!svar.isParentLocalAlias()) { - svar.isBoxed = false; - } - // Copy param to local. - if (svar.type == .paramCopy) { - try self.buf.pushOp2(.copyRetainSrc, 4 + svar.inner.paramCopy, svar.local); +fn reserveLocalReg(c: *Chunk, varId: sema.LocalVarId, advanceNext: bool) !RegisterId { + try reserveLocalRegAt(c, varId, c.curBlock.nextLocalReg); + defer { + if (advanceNext) { + c.curBlock.nextLocalReg += 1; } - // log.debug("reserve {} {s}", .{local, self.getVarName(varId)}); } + return c.curBlock.nextLocalReg; } fn unexpectedFmt(format: []const u8, vals: []const fmt.FmtValue) noreturn { @@ -3534,7 +3483,8 @@ fn unexpectedFmt(format: []const u8, vals: []const fmt.FmtValue) noreturn { cy.fatal(); } -fn pushFieldRetain(c: *cy.Chunk, recv: u8, dst: u8, fieldId: u16) !void { +fn pushFieldRetain(c: *cy.Chunk, recv: u8, dst: u8, fieldId: u16, debugNodeId: cy.NodeId) !void { + try c.pushFailableDebugSym(debugNodeId); const start = c.buf.ops.items.len; try c.buf.pushOpSlice(.fieldRetain, &.{ recv, dst, 0, 0, 0, 0, 0 }); c.buf.setOpArgU16(start + 3, fieldId); @@ -3574,4 +3524,184 @@ fn genCallTypeCheck(c: *cy.Chunk, startLocal: u8, numArgs: u32, funcSigId: sema. const start = c.buf.ops.items.len; try c.buf.pushOpSlice(.callTypeCheck, &[_]u8{ startLocal, @intCast(numArgs), 0, 0, }); c.buf.setOpArgU16(start + 3, @intCast(funcSigId)); +} + +fn genSubBlockReleaseLocals(c: *Chunk) !void { + const sblock = sema.curSubBlock(c); + + const start = c.operandStack.items.len; + defer c.operandStack.items.len = start; + + const varDecls = c.varDeclStack.items[sblock.varDeclStart..]; + for (varDecls) |decl| { + const svar = c.vars.items[decl.varId]; + if (svar.lifetimeRcCandidate) { + try c.operandStack.append(c.alloc, svar.local); + } + } + + const locals = c.operandStack.items[start..]; + if (locals.len > 0) { + try pushReleases(c, locals, sblock.nodeId); + } +} + +/// Only the locals that are alive at this moment are considered for released. +fn genBlockReleaseLocals(c: *Chunk) !void { + const block = sema.curBlock(c); + + const start = c.operandStack.items.len; + defer c.operandStack.items.len = start; + + const varDecls = c.varDeclStack.items[block.varDeclStart..]; + for (varDecls) |decl| { + const svar = c.vars.items[decl.varId]; + if (svar.lifetimeRcCandidate) { + try c.operandStack.append(c.alloc, svar.local); + } + } + + const locals = c.operandStack.items[start..]; + if (locals.len > 0) { + const nodeId = sema.getBlockNodeId(c, block); + try pushReleases(c, locals, nodeId); + } +} + +fn genBlockEnd(c: *Chunk) !void { + try genBlockReleaseLocals(c); + if (c.curBlock.requiresEndingRet1) { + try c.buf.pushOp(.ret1); + } else { + try c.buf.pushOp(.ret0); + } +} + +pub fn reserveMainRegs(self: *Chunk) !void { + var nextReg: u8 = 0; + + self.curBlock.startLocalReg = nextReg; + self.curBlock.nextLocalReg = nextReg; + + // Reset temp register state. + const sblock = sema.curBlock(self); + const tempRegStart = nextReg + sblock.maxLocals; + self.rega.resetState(tempRegStart); +} + +/// Reserve params and captured vars. +/// Function stack layout: +/// [startLocal/retLocal] [retInfo] [retAddress] [prevFramePtr] [params...] [callee] [var locals...] [temp locals...] +/// `callee` is reserved so that function values can call static functions with the same call convention. +/// For this reason, `callee` isn't freed in the function body and a separate release inst is required for lambda calls. +/// A closure can also occupy the callee and is used to do captured var lookup. +pub fn reserveFuncRegs(self: *Chunk, numParams: u32) !void { + // First local is reserved for a single return value. + // Second local is reserved for the return info. + // Third local is reserved for the return address. + // Fourth local is reserved for the previous frame pointer. + var nextReg: u8 = 4; + + const sblock = sema.curBlock(self); + + // Reserve func params. + var numParamCopies: u8 = 0; + const params = sblock.params.items[0..numParams]; + for (params) |varId| { + const svar = &self.vars.items[varId]; + if (!svar.isParentLocalAlias()) { + svar.isBoxed = false; + } + if (svar.inner.param.copied) { + // Forward reserve the param copy. + svar.local = @intCast(4 + params.len + 1 + numParamCopies); + try self.varDeclStack.append(self.alloc, .{ + .namePtr = undefined, + .nameLen = undefined, + .varId = varId, + }); + + // Copy param to local. + try self.buf.pushOp2(.copyRetainSrc, nextReg, svar.local); + + numParamCopies += 1; + } else { + svar.local = nextReg; + } + nextReg += 1; + } + + // An extra callee slot is reserved so that function values + // can call static functions with the same call convention. + // It's also used to store the closure object. + if (sblock.captures.items.len > 0) { + self.curBlock.closureLocal = nextReg; + } + nextReg += 1; + + if (sblock.params.items.len > numParams) { + { + @panic("This path is actually used?"); + } + for (sblock.params.items[numParams..]) |varId| { + self.vars.items[varId].local = nextReg; + nextReg += 1; + } + } + + self.curBlock.startLocalReg = nextReg; + nextReg += numParamCopies; + self.curBlock.nextLocalReg = nextReg; + + // Reset temp register state. + const tempRegStart = nextReg + sblock.maxLocals; + self.rega.resetState(tempRegStart); +} + +fn nextSemaSubBlock(self: *Chunk) void { + self.curSemaSubBlockId = self.nextSemaSubBlockId; + self.nextSemaSubBlockId += 1; + + // Codegen varDeclStart can be different than sema due to param copies. + const sblock = sema.curSubBlock(self); + sblock.varDeclStart = @intCast(self.varDeclStack.items.len); +} + +/// `genSubBlockReleaseLocals` is not called here since +/// loop sub blocks need to generate a jump statement after the release ops. +fn prevSemaSubBlock(c: *Chunk) void { + const sblock = sema.curSubBlock(c); + + // Unwind var decls. + c.varDeclStack.items.len = sblock.varDeclStart; + + c.curSemaSubBlockId = sblock.prevSubBlockId; + + // Recede nextLocalReg. + c.curBlock.nextLocalReg -= sblock.numLocals; +} + +pub fn pushSemaBlock(self: *Chunk, id: sema.BlockId) !void { + // Codegen block should be pushed first so nextSemaSubBlock can use it. + try self.pushBlock(); + + try self.semaBlockStack.append(self.alloc, id); + self.curSemaBlockId = id; + + // Codegen varDeclStart can be different than sema due to param copies. + sema.curBlock(self).varDeclStart = @intCast(self.varDeclStack.items.len); + + self.nextSemaSubBlockId = self.semaBlocks.items[id].firstSubBlockId; + nextSemaSubBlock(self); +} + +fn popSemaBlock(self: *Chunk) void { + prevSemaSubBlock(self); + + self.varDeclStack.items.len = sema.curBlock(self).varDeclStart; + + self.semaBlockStack.items.len -= 1; + self.curSemaBlockId = self.semaBlockStack.items[self.semaBlockStack.items.len-1]; + + self.popBlock(); } \ No newline at end of file diff --git a/src/cyber.zig b/src/cyber.zig index 52a6a845f..3b08c24b0 100644 --- a/src/cyber.zig +++ b/src/cyber.zig @@ -162,6 +162,7 @@ pub const hasCLI = build_options.cli; const build_options = @import("build_options"); pub const Trace = build_options.trace; +pub const TraceNewObject = Trace and false; pub const TrackGlobalRC = build_options.trackGlobalRC; pub const Malloc = build_options.malloc; diff --git a/src/debug.zig b/src/debug.zig index 4973afd04..67d1ab98a 100644 --- a/src/debug.zig +++ b/src/debug.zig @@ -56,6 +56,7 @@ pub fn countNewLines(str: []const u8, outLastIdx: *u32) u32 { return count; } +/// TODO: Memoize this function. pub fn getDebugSymByPc(vm: *const cy.VM, pc: usize) ?cy.DebugSym { return getDebugSymFromTable(vm.debugTable, pc); } @@ -430,39 +431,6 @@ pub fn buildStackTrace(self: *cy.VM) !void { self.stackTrace.frames = try frames.toOwnedSlice(self.alloc); } -/// Given pc position, return the end locals pc in the same frame. -/// TODO: Memoize this function. -pub fn pcToEndLocalsPc(vm: *const cy.VM, pc: usize) u32 { - const idx = indexOfDebugSym(vm, pc) orelse { - cy.panicFmt("Missing debug symbol: {}", .{vm.ops[pc].opcode()}); - }; - const sym = vm.debugTable[idx]; - if (sym.frameLoc != cy.NullId) { - const chunk = vm.compiler.chunks.items[sym.file]; - const node = chunk.nodes[sym.frameLoc]; - return chunk.semaFuncDecls.items[node.head.func.semaDeclId].genEndLocalsPc; - } else { - // Located in the main block. - const chunk = vm.compiler.chunks.items[0]; - const node = chunk.nodes[0]; - // Can be NullId if `shouldGenMainScopeReleaseOps` is false. - return node.head.root.genEndLocalsPc; - } -} - -pub fn debugSymToEndLocalsPc(vm: *const cy.VM, sym: cy.DebugSym) u32 { - if (sym.frameLoc != cy.NullId) { - const chunk = vm.compiler.chunks.items[sym.file]; - return chunk.getNodeFuncDecl(sym.frameLoc).genEndLocalsPc; - } else { - // Located in the main block. - const chunk = vm.compiler.chunks.items[0]; - const node = chunk.nodes[0]; - // Can be NullId if `shouldGenMainScopeReleaseOps` is false. - return node.head.root.genEndLocalsPc; - } -} - pub const ObjectTrace = struct { /// Points to inst that allocated the object. allocPc: u32, diff --git a/src/fiber.zig b/src/fiber.zig index 92ecbe55e..54a907e6d 100644 --- a/src/fiber.zig +++ b/src/fiber.zig @@ -138,10 +138,13 @@ pub fn releaseFiberStack(vm: *cy.VM, fiber: *cy.Fiber) !void { if (pc != cy.NullId) { // Check if fiber was previously on a yield op. if (vm.ops[pc].opcode() == .coyield) { - const jump = @as(*const align(1) u16, @ptrCast(&vm.ops[pc+1])).*; - log.debug("release on frame {} {} {}", .{framePtr, pc, pc + jump}); - // The yield statement already contains the end locals pc. - cy.arc.runBlockEndReleaseOps(vm, stack, framePtr, pc + jump); + // The yield statement contains the alive locals. + const localsStart = vm.ops[pc+1].val; + const localsEnd = vm.ops[pc+2].val; + log.debug("release on frame {} {} localsEnd: {}", .{framePtr, pc, localsEnd}); + for (stack[framePtr+localsStart..framePtr+localsEnd]) |val| { + cy.arc.release(vm, val); + } // Prev frame. pc = @intCast(getInstOffset(vm.ops.ptr, stack[framePtr + 2].retPcPtr) - stack[framePtr + 1].retInfoCallInstOffset()); @@ -153,14 +156,14 @@ pub fn releaseFiberStack(vm: *cy.VM, fiber: *cy.Fiber) !void { const sym = cy.debug.getDebugSymByIndex(vm, symIdx); const tempIdx = cy.debug.getDebugTempIndex(vm, symIdx); - const endLocalsPc = cy.debug.debugSymToEndLocalsPc(vm, sym); - log.debug("release on frame {} {} {}", .{framePtr, pc, endLocalsPc}); + const locals = sym.getLocals(); + log.debug("release on frame {} {}, locals: {}-{}", .{framePtr, pc, locals.start, locals.end}); if (tempIdx != cy.NullId) { cy.arc.runTempReleaseOps(vm, stack.ptr + framePtr, tempIdx); } - if (endLocalsPc != cy.NullId) { - cy.arc.runBlockEndReleaseOps(vm, stack, framePtr, endLocalsPc); + if (locals.len() > 0) { + cy.arc.releaseLocals(vm, vm.stack, framePtr, locals); } // Prev frame. @@ -198,16 +201,16 @@ pub fn unwindReleaseStack(vm: *cy.VM, stack: []const Value, startFramePtr: [*]co const symIdx = cy.debug.indexOfDebugSym(vm, pcOffset) orelse return error.NoDebugSym; const sym = cy.debug.getDebugSymByIndex(vm, symIdx); const tempIdx = cy.debug.getDebugTempIndex(vm, symIdx); - const endLocalsPc = cy.debug.debugSymToEndLocalsPc(vm, sym); - log.debug("release frame: {} {} {} {}", .{pcOffset, vm.ops[pcOffset].opcode(), tempIdx, endLocalsPc}); + const locals = sym.getLocals(); + log.debug("release frame: {} {} {}, locals: {}-{}", .{pcOffset, vm.ops[pcOffset].opcode(), tempIdx, locals.start, locals.end}); // Release temporaries in the current frame. if (tempIdx != cy.NullId) { cy.arc.runTempReleaseOps(vm, vm.stack.ptr + fpOffset, tempIdx); } - if (endLocalsPc != cy.NullId) { - cy.arc.runBlockEndReleaseOps(vm, stack, fpOffset, endLocalsPc); + if (locals.len() > 0) { + cy.arc.releaseLocals(vm, stack, fpOffset, locals); } if (fpOffset == 0) { // Done, at main block. @@ -233,14 +236,14 @@ pub fn unwindThrowUntilFramePtr(vm: *cy.VM, startFp: [*]const Value, pc: [*]cons const symIdx = cy.debug.indexOfDebugSym(vm, pcOffset) orelse return error.NoDebugSym; const sym = cy.debug.getDebugSymByIndex(vm, symIdx); const tempIdx = cy.debug.getDebugTempIndex(vm, symIdx); - const endLocalsPc = cy.debug.debugSymToEndLocalsPc(vm, sym); - log.debug("release frame: {} {}, tempIdx: {}, endLocals: {}", .{pcOffset, vm.ops[pcOffset].opcode(), tempIdx, endLocalsPc}); + const locals = sym.getLocals(); + log.debug("release frame: {} {}, tempIdx: {}, locals: {}-{}", .{pcOffset, vm.ops[pcOffset].opcode(), tempIdx, locals.start, locals.end}); // Release temporaries in the current frame. cy.arc.runTempReleaseOps(vm, vm.stack.ptr + fpOffset, tempIdx); - if (endLocalsPc != cy.NullId) { - cy.arc.runBlockEndReleaseOps(vm, vm.stack, fpOffset, endLocalsPc); + if (locals.len() > 0) { + cy.arc.releaseLocals(vm, vm.stack, fpOffset, locals); } // Record frame. @@ -358,13 +361,7 @@ pub fn stackGrowTotalCapacity(self: *cy.VM, newCap: usize) !void { break; } } - if (self.alloc.resize(self.stack, betterCap)) { - self.stack.len = betterCap; - self.stackEndPtr = self.stack.ptr + betterCap; - } else { - self.stack = try self.alloc.realloc(self.stack, betterCap); - self.stackEndPtr = self.stack.ptr + betterCap; - } + try stackGrowTotalCapacityPrecise(self, betterCap); } pub fn stackGrowTotalCapacityPrecise(self: *cy.VM, newCap: usize) !void { @@ -374,9 +371,21 @@ pub fn stackGrowTotalCapacityPrecise(self: *cy.VM, newCap: usize) !void { } else { self.stack = try self.alloc.realloc(self.stack, newCap); self.stackEndPtr = self.stack.ptr + newCap; + + if (builtin.is_test or cy.Trace) { + // Fill the stack with null heap objects to surface undefined access better. + @memset(self.stack, Value.initCycPtr(&DummyHeapObject)); + } } } +var DummyHeapObject = cy.HeapObject{ + .head = .{ + .typeId = cy.NullId, + .rc = 0, + }, +}; + pub inline fn toVmPc(self: *const cy.VM, offset: usize) [*]cy.Inst { return self.ops.ptr + offset; } diff --git a/src/fmt.zig b/src/fmt.zig index 8efa07217..e8d4311c9 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -87,7 +87,7 @@ fn toFmtValueType(comptime T: type) FmtValueType { } } -pub fn v(val: anytype) FmtValue { +pub inline fn v(val: anytype) FmtValue { const fmt_t = comptime toFmtValueType(@TypeOf(val)); switch (fmt_t) { .bool => { diff --git a/src/heap.zig b/src/heap.zig index 10bcddf1e..0f92b6403 100644 --- a/src/heap.zig +++ b/src/heap.zig @@ -578,6 +578,9 @@ pub fn allocPoolObject(self: *cy.VM) linksection(cy.HotSection) !*HeapObject { self.heapFreeTail = self.heapFreeHead; } } + if (cy.TraceNewObject) { + log.tracev("Alloc pool object: {*}", .{ptr}); + } if (cy.TrackGlobalRC) { self.refCounts += 1; } diff --git a/src/lib.zig b/src/lib.zig index e4c77be14..6bf7dec16 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -282,7 +282,7 @@ export fn csNewVmObject(uvm: *cy.UserVM, typeId: rt.TypeId, fieldsPtr: [*]const const entry = &vm.types.buf[typeId]; std.debug.assert(!entry.isHostObject); - std.debug.assert(numFields == entry.data.numFields); + std.debug.assert(numFields == entry.data.object.numFields); const fields = fieldsPtr[0..numFields]; for (fields) |field| { cy.arc.retain(vm, field); diff --git a/src/parser.zig b/src/parser.zig index 022e8a8fc..11ea92635 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -59,7 +59,6 @@ const keywords = std.ComptimeStringMap(TokenType, .{ }); const BlockState = struct { - placeholder: u32 = 0, vars: std.StringHashMapUnmanaged(void), fn deinit(self: *BlockState, alloc: std.mem.Allocator) void { @@ -3587,8 +3586,6 @@ pub const Node = struct { }, root: struct { headStmt: NodeId, - genEndLocalsPc: u32 = NullId, - genTempStartReg: u8 = 255, }, child_head: NodeId, comptimeExpr: struct { diff --git a/src/sema.zig b/src/sema.zig index 5e63f8a25..754dce454 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -38,10 +38,6 @@ const LocalVarType = enum(u8) { param, - /// If a param is written to or turned into a Box, this local would be a copy of the param - /// and `inner.paramCopy` would be the param idx. - paramCopy, - /// Whether this var references a static variable. staticAlias, @@ -56,17 +52,24 @@ const LocalVarType = enum(u8) { /// Represents a variable or alias in a block. /// Local variables are given reserved registers on the stack frame. /// Captured variables have box values at runtime. +/// TODO: This should be SemaVar since it includes all vars not just locals. pub const LocalVar = struct { type: LocalVarType, - /// The current type of the var as the ast is traversed. - /// This is updated when there is a variable assignment or a child block returns. + /// If the variable is dynamic, this tracks the most recent type as the ast is traversed. + /// This is updated when there is a variable assignment or a child block returns. + /// If the variable has a static type, this should never change after initialization. vtype: TypeId, + /// Whether the variable is dynamic or statically typed. + dynamic: bool, + + /// Last sub-block that mutated the dynamic var. + dynamicLastMutSubBlockId: SubBlockId, + /// If non-null, points to the captured var idx in the closure. capturedIdx: u8 = cy.NullU8, - /// Currently a captured var always needs to be boxed. /// In the future, the concept of a const variable could change this. isBoxed: bool = false, @@ -80,30 +83,45 @@ pub const LocalVar = struct { /// Locals are relative to the stack frame's start position. local: RegisterId = undefined, - /// Since the same sema var is used later by codegen, - /// use a flag to indicate whether the var has been loosely defined in the block. (eg. assigned to lvalue) - /// Note that assigning inside a branch counts as defined. - /// Entering an iter block will auto mark those as defined since the var could have been assigned by a previous iteration. - isDefinedOnce: bool = false, - - /// Whether the var is currently declared (according to the language rules) at the current position in the current block. - /// When a sub block ends, this is set back to false. - /// If this variable is an alias, this is always false. - isDeclared: bool = false, - inner: extern union { staticAlias: extern struct { csymId: CompactSymbolId, }, - paramIdx: u8, - paramCopy: u8, + param: extern struct { + idx: u8, + + /// If a param is written to or turned into a Box, `copied` becomes true. + copied: bool, + }, } = undefined, - name: if (builtin.mode == .Debug) []const u8 else void, + name: if (cy.Trace) []const u8 else void, pub inline fn isParentLocalAlias(self: LocalVar) bool { return self.capturedIdx != cy.NullU8; } + + pub inline fn isCapturable(self: LocalVar) bool { + return self.type == .local or self.type == .param; + } +}; + +const VarSubBlock = extern struct { + varId: LocalVarId, + subBlockId: SubBlockId, +}; + +pub const VarShadow = extern struct { + namePtr: [*]const u8, + nameLen: u32, + varId: LocalVarId, + subBlockId: SubBlockId, +}; + +pub const NameVar = extern struct { + namePtr: [*]const u8, + nameLen: u32, + varId: LocalVarId, }; pub const CapVarDesc = extern union { @@ -111,7 +129,13 @@ pub const CapVarDesc = extern union { user: LocalVarId, }; -const VarAndType = struct { +pub const PreLoopVarSave = packed struct { + vtype: TypeId, + varId: u31, + lifetimeRcCandidate: bool, +}; + +pub const VarAndType = struct { id: LocalVarId, vtype: TypeId, }; @@ -119,15 +143,6 @@ const VarAndType = struct { pub const SubBlockId = u32; pub const SubBlock = struct { - /// Save var start types for entering a codegen iter block. - /// This can only be determined after the sema pass. - /// This is used to initialize the var type when entering the codegen iter block so - /// that the first `genSetVar` produces the correct `set` op. - iterVarBeginTypes: std.ArrayListUnmanaged(VarAndType), - - /// Record any merged narrow types at the end of the subblock for codegen. - endMergeTypes: std.ArrayListUnmanaged(VarAndType), - /// Track which vars were assigned to in the current sub block. /// If the var was first assigned in a parent sub block, the type is saved in the map to /// be merged later with the ending var type. @@ -139,39 +154,53 @@ pub const SubBlock = struct { /// back to the parent scope. assignedVarStart: u32, - /// Start of declared vars in this subblock in `declaredVarStack`. - declaredVarStart: u32, + /// Start of declared vars in this sub-block in `varDeclStack`. + varDeclStart: u32, + + /// Start of shadowed vars from the previous sub-block in `varShadowStack`. + varShadowStart: u32, + + preLoopVarSaveStart: u32, /// Previous sema sub block. /// When this sub block ends, the previous sub block id is set as the current. prevSubBlockId: SubBlockId, - // Whether execution can reach the end. - // If a return statement was generated, this would be set to false. + /// Node that began the sub-block. + nodeId: cy.NodeId, + + /// Whether execution can reach the end. + /// If a return statement was generated, this would be set to false. endReachable: bool = true, - pub fn init(prevSubBlockId: SubBlockId, assignedVarStart: usize, declaredVarStart: usize) SubBlock { + /// Tracks how many locals are owned by this sub-block. + /// When the sub-block is popped, this is subtracted from the block's `curNumLocals`. + numLocals: u8, + + pub fn init(nodeId: cy.NodeId, prevSubBlockId: SubBlockId, assignedVarStart: usize, varDeclStart: usize, varShadowStart: usize) SubBlock { return .{ + .nodeId = nodeId, .assignedVarStart = @intCast(assignedVarStart), - .declaredVarStart = @intCast(declaredVarStart), - .iterVarBeginTypes = .{}, - .endMergeTypes = .{}, + .varDeclStart = @intCast(varDeclStart), + .varShadowStart = @intCast(varShadowStart), + .preLoopVarSaveStart = 0, .prevVarTypes = .{}, .prevSubBlockId = prevSubBlockId, + .numLocals = 0, }; } pub fn deinit(self: *SubBlock, alloc: std.mem.Allocator) void { - self.iterVarBeginTypes.deinit(alloc); - self.endMergeTypes.deinit(alloc); + _ = self; + _ = alloc; } }; pub const BlockId = u32; pub const Block = struct { - /// Local vars defined in this block. Does not include function params. - locals: std.ArrayListUnmanaged(LocalVarId), + /// Keep a record of all locals for debugging. + locals: if (cy.Trace) std.ArrayListUnmanaged(LocalVarId) else void, /// Param vars for function blocks. /// Codegen will reserve these first for the calling convention layout. @@ -180,9 +209,11 @@ pub const Block = struct { /// Captured vars. captures: std.ArrayListUnmanaged(LocalVarId), - /// Name to var. + /// Maps a name to a var. + /// This is updated as sub-blocks declare their own locals and restored + /// when sub-blocks end. /// This can be deinited after ending the sema block. - nameToVar: std.StringHashMapUnmanaged(LocalVarId), + nameToVar: std.StringHashMapUnmanaged(VarSubBlock), /// First sub block id is recorded so the rest can be obtained by advancing /// the id in the same order it was traversed in the sema pass. @@ -203,10 +234,21 @@ pub const Block = struct { /// Whether temporaries (nameToVar) was deinit. deinitedTemps: bool, - pub fn init(funcDeclId: cy.NodeId, firstSubBlockId: SubBlockId, isStaticFuncBlock: bool) Block { + /// Track max locals so that codegen knows where stack registers begin. + maxLocals: u8, + + /// Everytime a new local is encountered in a sub-block, this is incremented by one. + /// At the end of the sub-block `maxLocals` is updated and this unwinds by subtracting its numLocals. + curNumLocals: u8, + + /// All locals currently alive in this block. + /// Every local above the current sub-block is included. + varDeclStart: u32, + + pub fn init(funcDeclId: cy.NodeId, firstSubBlockId: SubBlockId, isStaticFuncBlock: bool, varDeclStart: u32) Block { return .{ + .locals = if (cy.Trace) .{} else {}, .nameToVar = .{}, - .locals = .{}, .params = .{}, .subBlockDepth = 0, .funcDeclId = funcDeclId, @@ -214,11 +256,16 @@ pub const Block = struct { .isStaticFuncBlock = isStaticFuncBlock, .deinitedTemps = false, .captures = .{}, + .maxLocals = 0, + .curNumLocals = 0, + .varDeclStart = varDeclStart, }; } pub fn deinit(self: *Block, alloc: std.mem.Allocator) void { - self.locals.deinit(alloc); + if (cy.Trace) { + self.locals.deinit(alloc); + } self.params.deinit(alloc); // Deinit for CompileError during sema. @@ -477,6 +524,7 @@ pub fn semaStmt(c: *cy.Chunk, nodeId: cy.NodeId) !void { _ = try assignVar(c, node.head.left_right.left, rtype); } else { const rtype = try semaExpr(c, node.head.left_right.right); + _ = try assignVar(c, node.head.left_right.left, rtype); } } else if (left.node_t == .indexExpr) { @@ -521,10 +569,10 @@ pub fn semaStmt(c: *cy.Chunk, nodeId: cy.NodeId) !void { const name = c.getNodeTokenString(nameN); const nameId = try ensureNameSym(c.compiler, name); - const symId = try getOrResolveTypeSymFromSpecNode(c, node.head.typeAliasDecl.typeSpecHead); - const rSym = c.compiler.sema.getSymbol(symId); + const typeId = try getOrResolveTypeFromSpecNode(c, node.head.typeAliasDecl.typeSpecHead); + const rSym = c.compiler.sema.getSymbol(typeId); try setLocalSym(c, nameId, .{ - .symId = symId, + .symId = typeId, .funcSymId = cy.NullId, .parentSymId = rSym.key.resolvedSymKey.parentSymId, }); @@ -547,44 +595,46 @@ pub fn semaStmt(c: *cy.Chunk, nodeId: cy.NodeId) !void { return; }, .whileCondStmt => { - try pushIterSubBlock(c); + try pushLoopSubBlock(c, nodeId); _ = try semaExpr(c, node.head.whileCondStmt.cond); try semaStmts(c, node.head.whileCondStmt.bodyHead); - try endIterSubBlock(c); + try endLoopSubBlock(c); }, .whileOptStmt => { - try pushIterSubBlock(c); + try pushLoopSubBlock(c, nodeId); const optt = try semaExpr(c, node.head.whileOptStmt.opt); if (node.head.whileOptStmt.some != cy.NullId) { - _ = try getOrDeclareLocal(c, node.head.whileOptStmt.some, bt.Any); + const vtype = if (optt == bt.Dynamic) bt.Dynamic else bt.Any; + _ = try declareLocal(c, node.head.whileOptStmt.some, vtype, bt.Any); _ = try assignVar(c, node.head.whileOptStmt.some, optt); } try semaStmts(c, node.head.whileOptStmt.bodyHead); - try endIterSubBlock(c); + try endLoopSubBlock(c); }, .whileInfStmt => { - try pushIterSubBlock(c); + try pushLoopSubBlock(c, nodeId); try semaStmts(c, node.head.child_head); - try endIterSubBlock(c); + try endLoopSubBlock(c); }, .for_iter_stmt => { - try pushIterSubBlock(c); + try pushLoopSubBlock(c, nodeId); - _ = try semaExpr(c, node.head.for_iter_stmt.iterable); + const iterT = try semaExpr(c, node.head.for_iter_stmt.iterable); if (node.head.for_iter_stmt.eachClause != cy.NullId) { const eachClause = c.nodes[node.head.for_iter_stmt.eachClause]; + const vtype = if (iterT == bt.Dynamic) bt.Dynamic else bt.Any; if (eachClause.node_t == .ident) { - _ = try getOrDeclareLocal(c, node.head.for_iter_stmt.eachClause, bt.Any); + _ = try declareLocal(c, node.head.for_iter_stmt.eachClause, vtype, bt.Any); } else if (eachClause.node_t == .seqDestructure) { var curId = eachClause.head.seqDestructure.head; while (curId != cy.NullId) { - _ = try getOrDeclareLocal(c, curId, bt.Any); + _ = try declareLocal(c, curId, vtype, bt.Any); const cur = c.nodes[curId]; curId = cur.next; } @@ -594,15 +644,15 @@ pub fn semaStmt(c: *cy.Chunk, nodeId: cy.NodeId) !void { } try semaStmts(c, node.head.for_iter_stmt.body_head); - try endIterSubBlock(c); + try endLoopSubBlock(c); }, .for_range_stmt => { - try pushIterSubBlock(c); + try pushLoopSubBlock(c, nodeId); if (node.head.for_range_stmt.eachClause != cy.NullId) { const eachClause = c.nodes[node.head.for_range_stmt.eachClause]; if (eachClause.node_t == .ident) { - _ = try getOrDeclareLocal(c, node.head.for_range_stmt.eachClause, bt.Integer); + _ = try declareLocal(c, node.head.for_range_stmt.eachClause, bt.Integer, bt.Integer); } else { return c.reportErrorAt("Unsupported each clause: {}", &.{v(eachClause.node_t)}, node.head.for_range_stmt.eachClause); } @@ -613,7 +663,7 @@ pub fn semaStmt(c: *cy.Chunk, nodeId: cy.NodeId) !void { _ = try semaExpr(c, range_clause.head.left_right.right); try semaStmts(c, node.head.for_range_stmt.body_head); - try endIterSubBlock(c); + try endLoopSubBlock(c); }, .matchBlock => { _ = try matchBlock(c, nodeId, false); @@ -621,7 +671,7 @@ pub fn semaStmt(c: *cy.Chunk, nodeId: cy.NodeId) !void { .if_stmt => { _ = try semaExpr(c, node.head.left_right.left); - try pushSubBlock(c); + try pushSubBlock(c, nodeId); try semaStmts(c, node.head.left_right.right); try endSubBlock(c); @@ -629,14 +679,14 @@ pub fn semaStmt(c: *cy.Chunk, nodeId: cy.NodeId) !void { while (elseClauseId != cy.NullId) { const elseClause = c.nodes[elseClauseId]; if (elseClause.head.else_clause.cond == cy.NullId) { - try pushSubBlock(c); + try pushSubBlock(c, elseClauseId); try semaStmts(c, elseClause.head.else_clause.body_head); try endSubBlock(c); break; } else { _ = try semaExpr(c, elseClause.head.else_clause.cond); - try pushSubBlock(c); + try pushSubBlock(c, elseClauseId); try semaStmts(c, elseClause.head.else_clause.body_head); try endSubBlock(c); elseClauseId = elseClause.head.else_clause.else_clause; @@ -644,13 +694,13 @@ pub fn semaStmt(c: *cy.Chunk, nodeId: cy.NodeId) !void { } }, .tryStmt => { - try pushSubBlock(c); + try pushSubBlock(c, nodeId); try semaStmts(c, node.head.tryStmt.tryFirstStmt); try endSubBlock(c); - try pushSubBlock(c); + try pushSubBlock(c, nodeId); if (node.head.tryStmt.errorVar != cy.NullId) { - _ = try getOrDeclareLocal(c, node.head.tryStmt.errorVar, bt.Error); + _ = try declareLocal(c, node.head.tryStmt.errorVar, bt.Error, bt.Error); } try semaStmts(c, node.head.tryStmt.catchFirstStmt); try endSubBlock(c); @@ -774,7 +824,7 @@ fn matchBlock(c: *cy.Chunk, nodeId: cy.NodeId, canBreak: bool) !TypeId { curCase = node.head.matchBlock.firstCase; while (curCase != cy.NullId) { const case = c.nodes[curCase]; - try pushSubBlock(c); + try pushSubBlock(c, curCase); try semaStmts(c, case.head.caseBlock.firstChild); try endSubBlock(c); curCase = case.next; @@ -947,13 +997,7 @@ pub fn declareObjectMembers(c: *cy.Chunk, nodeId: cy.NodeId) !void { const fieldName = c.getNodeTokenString(field); const fieldNameId = try ensureNameSym(c.compiler, fieldName); const fieldSymId = try c.compiler.vm.ensureFieldSym(fieldName); - - var fieldType: SymbolId = undefined; - if (field.head.objectField.typeSpecHead != cy.NullId) { - fieldType = try getOrResolveTypeSymFromSpecNode(c, field.head.objectField.typeSpecHead); - } else { - fieldType = bt.Dynamic; - } + const fieldType = try getOrResolveTypeFromSpecNode(c, field.head.objectField.typeSpecHead); if (!cy.module.symNameExists(mod, fieldNameId)) { try cy.module.setField(mod, c.alloc, fieldNameId, i, fieldType); @@ -1185,7 +1229,7 @@ fn declareHostMethod(c: *cy.Chunk, modId: cy.ModuleId, nodeId: cy.NodeId) !void // Insert method entries into VM. const funcSig = c.compiler.sema.getFuncSig(func.funcSigId); if (sym.symT == .hostFunc) { - if (funcSig.isParamsTyped) { + if (funcSig.reqCallTypeCheck) { const m = rt.MethodInit.initHostTyped(func.funcSigId, @ptrCast(sym.inner.hostFunc.func), func.numParams); try c.compiler.vm.addMethod(typeId, mgId, m); } else { @@ -1239,7 +1283,7 @@ pub fn declareHostFunc(c: *cy.Chunk, modId: cy.ModuleId, nodeId: cy.NodeId) !voi _ = try resolveHostFunc(c, key, func.funcSigId, res.ptr); } else if (res.type == .quicken) { const funcSig = c.compiler.sema.getFuncSig(func.funcSigId); - if (funcSig.isParamsTyped) { + if (funcSig.reqCallTypeCheck) { return c.reportErrorAt("Failed to load: {}, Only untyped quicken func is supported.", &.{v(name)}, nodeId); } cy.module.declareHostQuickenFunc(c.compiler, modId, name, func.funcSigId, declId, @ptrCast(res.ptr)) catch |err| { @@ -1322,14 +1366,9 @@ pub fn declareVar(c: *cy.Chunk, nodeId: cy.NodeId) !void { try c.compiler.sema.modules.items[c.modId].setUserVar(c.compiler, name, nodeId); // var type. - var typeSymId: SymbolId = undefined; - if (varSpec.head.varSpec.typeSpecHead != cy.NullId) { - typeSymId = try getOrResolveTypeSymFromSpecNode(c, varSpec.head.varSpec.typeSpecHead); - } else { - typeSymId = bt.Any; - } + const typeId = try getOrResolveTypeFromSpecNode(c, varSpec.head.varSpec.typeSpecHead); - const symId = try resolveLocalVarSym(c, c.semaRootSymId, nameId, typeSymId, nodeId, true); + const symId = try resolveLocalVarSym(c, c.semaRootSymId, nameId, typeId, nodeId, true); c.nodes[nodeId].head.staticDecl.sema_symId = symId; } } @@ -1353,12 +1392,7 @@ fn declareHostVar(c: *cy.Chunk, nodeId: cy.NodeId) !void { var out: cy.Value = cy.Value.None; if (varLoader(@ptrCast(c.compiler.vm), info, &out)) { // var type. - var typeId: TypeId = undefined; - if (varSpec.head.varSpec.typeSpecHead != cy.NullId) { - typeId = try getOrResolveTypeSymFromSpecNode(c, varSpec.head.varSpec.typeSpecHead); - } else { - typeId = bt.Any; - } + const typeId = try getOrResolveTypeFromSpecNode(c, varSpec.head.varSpec.typeSpecHead); c.nodes[node.head.staticDecl.varSpec].next = typeId; const outTypeId = c.compiler.vm.types.buf[out.getTypeId()].semaTypeId; @@ -1377,42 +1411,66 @@ fn declareHostVar(c: *cy.Chunk, nodeId: cy.NodeId) !void { } } -fn localDecl(self: *cy.Chunk, nodeId: cy.NodeId) !void { - const node = self.nodes[nodeId]; - const rtype = try semaExpr(self, node.head.localDecl.right); - const varSpec = self.nodes[node.head.localDecl.varSpec]; - const nameNid = varSpec.head.varSpec.name; - const nameN = self.nodes[nameNid]; - const name = self.getNodeTokenString(nameN); +/// Assumes initType is a compatible with declType. +fn declareLocal(self: *cy.Chunk, identId: cy.NodeId, declType: TypeId, initType:TypeId) !LocalVarId { + const ident = self.nodes[identId]; + const name = self.getNodeTokenString(ident); - const sblock = curBlock(self); - if (sblock.nameToVar.get(name)) |varId| { - const svar = &self.vars.items[varId]; - if (svar.isParentLocalAlias()) { - return self.reportErrorAt("`{}` already references a parent local variable.", &.{v(name)}, nodeId); - } else if (svar.type == .staticAlias) { - return self.reportErrorAt("`{}` already references a static variable.", &.{v(name)}, nodeId); - } else { - if (!svar.isDeclared) { - svar.isDeclared = true; - try self.assignedVarStack.append(self.alloc, varId); - try self.declaredVarStack.append(self.alloc, varId); - self.nodes[nameNid].head.ident.semaVarId = varId; + const block = curBlock(self); + if (block.nameToVar.get(name)) |varInfo| { + if (varInfo.subBlockId == self.curSemaSubBlockId) { + const svar = &self.vars.items[varInfo.varId]; + if (svar.isParentLocalAlias()) { + return self.reportErrorAt("`{}` already references a parent local variable.", &.{v(name)}, identId); + } else if (svar.type == .staticAlias) { + return self.reportErrorAt("`{}` already references a static variable.", &.{v(name)}, identId); } else { - if (self.semaIsMainBlock()) { - return self.reportErrorAt("Variable `{}` is already declared in the main block.", &.{v(name)}, nodeId); - } else { - return self.reportErrorAt("Variable `{}` is already declared in the current function block.", &.{v(name)}, nodeId); - } + return self.reportErrorAt("Variable `{}` is already declared in the block.", &.{v(name)}, identId); } + } else { + // Create shadow entry for restoring the prev var. + try self.varShadowStack.append(self.alloc, .{ + .namePtr = name.ptr, + .nameLen = @intCast(name.len), + .varId = varInfo.varId, + .subBlockId = varInfo.subBlockId, + }); } - } else { - const id = try pushLocalBodyVar(self, name, rtype); - self.vars.items[id].isDeclared = true; - try self.assignedVarStack.append(self.alloc, id); - try self.declaredVarStack.append(self.alloc, id); - self.nodes[nameNid].head.ident.semaVarId = id; } + const id = try pushLocalVar(self, .local, name, declType); + var svar = &self.vars.items[id]; + if (svar.dynamic) { + svar.vtype = initType; + svar.lifetimeRcCandidate = types.isRcCandidateType(self.compiler, initType); + } + + if (cy.Trace) { + try block.locals.append(self.alloc, id); + } + try self.varDeclStack.append(self.alloc, .{ + .namePtr = name.ptr, + .nameLen = @intCast(name.len), + .varId = id, + }); + const sblock = curSubBlock(self); + sblock.numLocals += 1; + self.nodes[identId].head.ident.semaVarId = id; + + block.curNumLocals += 1; + return id; +} + +fn localDecl(self: *cy.Chunk, nodeId: cy.NodeId) !void { + const node = self.nodes[nodeId]; + const varSpec = self.nodes[node.head.localDecl.varSpec]; + + const typeId = try getOrResolveTypeFromSpecNode(self, varSpec.head.varSpec.typeSpecHead); + + // Infer rhs type. + const rtype = try semaExprCstr(self, node.head.localDecl.right, typeId, true); + + const varId = try declareLocal(self, varSpec.head.varSpec.name, typeId, rtype); + try self.assignedVarStack.append(self.alloc, varId); } fn staticDecl(c: *cy.Chunk, nodeId: cy.NodeId) !void { @@ -1766,9 +1824,9 @@ fn semaExprInner(c: *cy.Chunk, nodeId: cy.NodeId, preferType: TypeId) anyerror!T }, .castExpr => { _ = try semaExpr(c, node.head.castExpr.expr); - const rTypeSymId = try getOrResolveTypeSymFromSpecNode(c, node.head.castExpr.typeSpecHead); - c.nodes[nodeId].head.castExpr.semaTypeSymId = rTypeSymId; - return rTypeSymId; + const typeId = try getOrResolveTypeFromSpecNode(c, node.head.castExpr.typeSpecHead); + c.nodes[nodeId].head.castExpr.semaTypeSymId = typeId; + return typeId; }, .binExpr => { const left = node.head.binExpr.left; @@ -2011,7 +2069,7 @@ fn callExpr(c: *cy.Chunk, nodeId: cy.NodeId) !TypeId { } else if (callee.node_t == .ident) { const name = c.getNodeTokenString(callee); - // Perform custom lookup for static vars. + // Perform custom lookup for static vars using symName + args. const res = try getOrLookupVar(c, name, false); switch (res) { .local => |id| { @@ -2024,6 +2082,8 @@ fn callExpr(c: *cy.Chunk, nodeId: cy.NodeId) !TypeId { _ = try semaExpr(c, arg_id); arg_id = arg.next; } + + c.nodeTypes[node.head.callExpr.callee] = bt.Any; return bt.Any; }, .static, // There was a previous usage of a static var alias. @@ -2045,6 +2105,7 @@ fn callExpr(c: *cy.Chunk, nodeId: cy.NodeId) !TypeId { if (try getOrResolveSymForFuncCall(c, c.semaRootSymId, nameId, callArgs.argTypes, reqRet, callArgs.hasDynamicArg)) |callRes| { try referenceSym(c, callRes.csymId, true); c.nodes[node.head.callExpr.callee].head.ident.sema_csymId = callRes.csymId; + c.nodeTypes[node.head.callExpr.callee] = callRes.retType; return callRes.retType; } return c.reportErrorAt("Undeclared func `{}`.", &.{v(name)}, node.head.callExpr.callee); @@ -2062,6 +2123,7 @@ fn callExpr(c: *cy.Chunk, nodeId: cy.NodeId) !TypeId { if (try getOrResolveSymForFuncCall(c, c.semaRootSymId, nameId, callArgs.argTypes, reqRet, callArgs.hasDynamicArg)) |callRes| { try referenceSym(c, callRes.csymId, true); c.nodes[node.head.callExpr.callee].head.ident.sema_csymId = callRes.csymId; + c.nodeTypes[node.head.callExpr.callee] = callRes.retType; return callRes.retType; } return c.reportErrorAt("Undeclared func `{}`.", &.{v(name)}, node.head.callExpr.callee); @@ -2120,9 +2182,12 @@ fn identifier(c: *cy.Chunk, nodeId: cy.NodeId) !TypeId { } } +/// If no type spec, default to `dynamic` type. /// Recursively walks a type spec head node and returns the final resolved type sym. -/// Assumes head is non null. -fn getOrResolveTypeSymFromSpecNode(chunk: *cy.Chunk, head: cy.NodeId) !SymbolId { +fn getOrResolveTypeFromSpecNode(chunk: *cy.Chunk, head: cy.NodeId) !types.TypeId { + if (head == cy.NullId) { + return bt.Dynamic; + } var nodeId = head; var parentSymId = chunk.semaRootSymId; // log.debug("getOrResolveTypeSymFromSpecNode from {} ", .{parentSymId}); @@ -2172,24 +2237,15 @@ fn appendFuncDecl(chunk: *cy.Chunk, nodeId: cy.NodeId, isStatic: bool) !FuncDecl var numParams: u8 = 0; while (curParamId != cy.NullId) { const param = chunk.nodes[curParamId]; - if (param.head.funcParam.typeSpecHead == cy.NullId) { - try chunk.compiler.tempSyms.append(chunk.alloc, bt.Any); - } else { - const rTypeSymId = try getOrResolveTypeSymFromSpecNode(chunk, param.head.funcParam.typeSpecHead); - try chunk.compiler.tempSyms.append(chunk.alloc, rTypeSymId); - } + const typeId = try getOrResolveTypeFromSpecNode(chunk, param.head.funcParam.typeSpecHead); + try chunk.compiler.tempSyms.append(chunk.alloc, typeId); numParams += 1; curParamId = param.next; } decl.numParams = numParams; // Get return type. - var retType: TypeId = undefined; - if (header.head.funcHeader.ret != cy.NullId) { - retType = try getOrResolveTypeSymFromSpecNode(chunk, header.head.funcHeader.ret); - } else { - retType = bt.Dynamic; - } + const retType = try getOrResolveTypeFromSpecNode(chunk, header.head.funcHeader.ret); // Resolve func signature. decl.funcSigId = try ensureFuncSig(chunk.compiler, chunk.compiler.tempSyms.items, retType); @@ -2204,20 +2260,77 @@ pub fn pushBlock(self: *cy.Chunk, funcDeclId: u32) !BlockId { self.curSemaBlockId = @intCast(self.semaBlocks.items.len); const nextSubBlockId: u32 = @intCast(self.semaSubBlocks.items.len); var isStaticFuncBlock = false; + var subBlockNodeId: cy.NodeId = undefined; if (funcDeclId != cy.NullId) { - isStaticFuncBlock = self.semaFuncDecls.items[funcDeclId].isStatic; + const decl = self.semaFuncDecls.items[funcDeclId]; + isStaticFuncBlock = decl.isStatic; + subBlockNodeId = decl.nodeId; + } else { + subBlockNodeId = self.parserAstRootId; } - try self.semaBlocks.append(self.alloc, Block.init(funcDeclId, nextSubBlockId, isStaticFuncBlock)); + const new = Block.init(funcDeclId, nextSubBlockId, isStaticFuncBlock, @intCast(self.varDeclStack.items.len)); + try self.semaBlocks.append(self.alloc, new); try self.semaBlockStack.append(self.alloc, self.curSemaBlockId); - try pushSubBlock(self); + try pushSubBlock(self, subBlockNodeId); return self.curSemaBlockId; } -fn pushSubBlock(self: *cy.Chunk) !void { +fn pushLoopSubBlock(c: *cy.Chunk, nodeId: cy.NodeId) !void { + const block = curBlock(c); + const sblock = curSubBlock(c); + + // Scan for dynamic vars and prepare them for entering loop block. + const start = c.preLoopVarSaveStack.items.len; + const varDecls = c.varDeclStack.items[block.varDeclStart..]; + for (varDecls) |decl| { + var svar = &c.vars.items[decl.varId]; + if (svar.dynamic) { + if (svar.vtype != bt.Any) { + // Dynamic vars enter the loop as `any` since the rest of the loop + // hasn't been seen. + try c.preLoopVarSaveStack.append(c.alloc, .{ + .vtype = svar.vtype, + .varId = @intCast(decl.varId), + .lifetimeRcCandidate = svar.lifetimeRcCandidate, + }); + svar.vtype = bt.Any; + svar.lifetimeRcCandidate = true; + } + } + } + + sblock.preLoopVarSaveStart = @intCast(start); + + try pushSubBlock(c, nodeId); +} + +fn endLoopSubBlock(c: *cy.Chunk) !void { + try endSubBlock(c); + + const sblock = curSubBlock(c); + const varSaves = c.preLoopVarSaveStack.items[sblock.preLoopVarSaveStart..]; + for (varSaves) |save| { + var svar = &c.vars.items[save.varId]; + if (svar.dynamicLastMutSubBlockId <= c.curSemaSubBlockId) { + // Unused inside loop block. Restore type. + svar.vtype = save.vtype; + svar.lifetimeRcCandidate = save.lifetimeRcCandidate; + } + } + c.preLoopVarSaveStack.items.len = sblock.preLoopVarSaveStart; +} + +fn pushSubBlock(self: *cy.Chunk, nodeId: cy.NodeId) !void { curBlock(self).subBlockDepth += 1; const prev = self.curSemaSubBlockId; self.curSemaSubBlockId = @intCast(self.semaSubBlocks.items.len); - try self.semaSubBlocks.append(self.alloc, SubBlock.init(prev, self.assignedVarStack.items.len, self.declaredVarStack.items.len)); + const new = SubBlock.init( + nodeId, prev, + self.assignedVarStack.items.len, + self.varDeclStack.items.len, + self.varShadowStack.items.len, + ); + try self.semaSubBlocks.append(self.alloc, new); } fn pushMethodParamVars(c: *cy.Chunk, func: *const FuncDecl) !void { @@ -2228,8 +2341,12 @@ fn pushMethodParamVars(c: *cy.Chunk, func: *const FuncDecl) !void { // Add self receiver param. var id = try pushLocalVar(c, .param, "self", bt.Any); - c.vars.items[id].inner.paramIdx = 0; - c.vars.items[id].isDeclared = true; + c.vars.items[id].inner = .{ + .param = .{ + .idx = 0, + .copied = false, + }, + }; try sblock.params.append(c.alloc, id); if (func.numParams > 1) { @@ -2248,8 +2365,12 @@ fn pushMethodParamVars(c: *cy.Chunk, func: *const FuncDecl) !void { c.curNodeId = curNode; id = try pushLocalVar(c, .param, name, rParamSymId); - c.vars.items[id].inner.paramIdx = @intCast(idx + 1); - c.vars.items[id].isDeclared = true; + c.vars.items[id].inner = .{ + .param = .{ + .idx = @intCast(idx + 1), + .copied = false, + }, + }; try sblock.params.append(c.alloc, id); curNode = param.next; @@ -2270,8 +2391,12 @@ fn appendFuncParamVars(chunk: *cy.Chunk, func: *const FuncDecl) !void { try types.assertTypeSym(chunk, rParamSymId); const id = try pushLocalVar(chunk, .param, name, rParamSymId); - chunk.vars.items[id].inner.paramIdx = @intCast(idx); - chunk.vars.items[id].isDeclared = true; + chunk.vars.items[id].inner = .{ + .param = .{ + .idx = @intCast(idx), + .copied = false, + } + }; try sblock.params.append(chunk.alloc, id); curNode = param.next; @@ -2279,22 +2404,23 @@ fn appendFuncParamVars(chunk: *cy.Chunk, func: *const FuncDecl) !void { } } -fn pushLocalVar(c: *cy.Chunk, _type: LocalVarType, name: []const u8, vtype: TypeId) !LocalVarId { +fn pushLocalVar(c: *cy.Chunk, _type: LocalVarType, name: []const u8, declType: TypeId) !LocalVarId { const sblock = curBlock(c); const id: u32 = @intCast(c.vars.items.len); - const res = try sblock.nameToVar.getOrPut(c.alloc, name); - if (res.found_existing) { - return c.reportError("Var `{}` already exists", &.{v(name)}); - } else { - res.value_ptr.* = id; - try c.vars.append(c.alloc, .{ - .type = _type, - .name = if (builtin.mode == .Debug) name else {}, - .vtype = toLocalType(vtype), - .lifetimeRcCandidate = types.isRcCandidateType(c.compiler, vtype), - }); - return id; - } + _ = try sblock.nameToVar.put(c.alloc, name, .{ + .varId = id, + .subBlockId = c.curSemaSubBlockId, + }); + const dynamic = declType == bt.Dynamic; + try c.vars.append(c.alloc, .{ + .type = _type, + .dynamic = dynamic, + .dynamicLastMutSubBlockId = 0, + .name = if (cy.Trace) name else {}, + .vtype = declType, + .lifetimeRcCandidate = if (dynamic) false else types.isRcCandidateType(c.compiler, declType), + }); + return id; } fn getVarPtr(self: *cy.Chunk, name: []const u8) ?*LocalVar { @@ -2322,7 +2448,6 @@ fn pushCapturedObjectMemberAlias(self: *cy.Chunk, name: []const u8, parentVarId: const capturedIdx: u8 = @intCast(block.captures.items.len); self.vars.items[id].capturedIdx = capturedIdx; self.vars.items[id].isBoxed = true; - self.vars.items[id].isDefinedOnce = true; try self.capVarDescs.put(self.alloc, id, .{ .user = parentVarId, @@ -2338,7 +2463,6 @@ fn pushCapturedVar(self: *cy.Chunk, name: []const u8, parentVarId: LocalVarId, v const capturedIdx: u8 = @intCast(block.captures.items.len); self.vars.items[id].capturedIdx = capturedIdx; self.vars.items[id].isBoxed = true; - self.vars.items[id].isDefinedOnce = true; try self.capVarDescs.put(self.alloc, id, .{ .user = parentVarId, @@ -2348,32 +2472,6 @@ fn pushCapturedVar(self: *cy.Chunk, name: []const u8, parentVarId: LocalVarId, v return id; } -fn pushLocalBodyVar(self: *cy.Chunk, name: []const u8, vtype: TypeId) !LocalVarId { - const id = try pushLocalVar(self, .local, name, vtype); - try curBlock(self).locals.append(self.alloc, id); - return id; -} - -fn getOrDeclareLocal(self: *cy.Chunk, ident: cy.NodeId, vtype: TypeId) !LocalVarId { - const node = self.nodes[ident]; - const name = self.getNodeTokenString(node); - if (curBlock(self).nameToVar.get(name)) |varId| { - self.nodes[ident].head.ident.semaVarId = varId; - if (!self.vars.items[varId].isDeclared) { - self.vars.items[varId].isDeclared = true; - try self.declaredVarStack.append(self.alloc, varId); - } - return varId; - } else { - // Declare var. - const id = try pushLocalBodyVar(self, name, vtype); - self.vars.items[id].isDeclared = true; - self.nodes[ident].head.ident.semaVarId = id; - try self.declaredVarStack.append(self.alloc, id); - return id; - } -} - fn referenceSym(c: *cy.Chunk, symId: CompactSymbolId, trackDep: bool) !void { if (trackDep) { if (c.isInStaticInitializer()) { @@ -2411,15 +2509,15 @@ const VarLookupResult = union(enum) { /// symbol with overloaded signatures. fn getOrLookupVar(self: *cy.Chunk, name: []const u8, staticLookup: bool) !VarLookupResult { const sblock = curBlock(self); - if (sblock.nameToVar.get(name)) |varId| { - const svar = self.vars.items[varId]; + if (sblock.nameToVar.get(name)) |varInfo| { + const svar = self.vars.items[varInfo.varId]; if (svar.type == .staticAlias) { return VarLookupResult{ .static = @bitCast(svar.inner.staticAlias.csymId), }; } else if (svar.type == .objectMemberAlias) { return VarLookupResult{ - .local = varId, + .local = varInfo.varId, }; } else if (svar.isParentLocalAlias()) { // Can not reference local var in a static var decl unless it's in a nested block. @@ -2430,22 +2528,16 @@ fn getOrLookupVar(self: *cy.Chunk, name: []const u8, staticLookup: bool) !VarLoo return error.CanNotUseLocal; } return VarLookupResult{ - .local = varId, + .local = varInfo.varId, }; } else { if (self.isInStaticInitializer() and self.semaBlockDepth() == 1) { self.compiler.errorPayload = self.curNodeId; return error.CanNotUseLocal; } - if (!svar.isDeclared) { - return VarLookupResult{ - .not_found = {}, - }; - } else { - return VarLookupResult{ - .local = varId, - }; - } + return VarLookupResult{ + .local = varInfo.varId, + }; } } @@ -2483,10 +2575,11 @@ fn getOrLookupVar(self: *cy.Chunk, name: []const u8, staticLookup: bool) !VarLoo if (res.isObjectMember) { const parentBlockId = self.semaBlockStack.items[res.blockDepth - 1]; const parentBlock = &self.semaBlocks.items[parentBlockId]; - const selfId = parentBlock.nameToVar.get("self").?; + const selfId = parentBlock.nameToVar.get("self").?.varId; var resVarId = res.varId; - if (self.vars.items[selfId].type == .param) { - resVarId = try replaceParamWithCopy(self, parentBlock, "self", selfId); + if (self.vars.items[selfId].type == .param and !self.vars.items[selfId].inner.param.copied) { + var svar = &self.vars.items[selfId]; + svar.inner.param.copied = true; } const id = try pushCapturedObjectMemberAlias(self, name, resVarId, parentVar.vtype); @@ -2496,10 +2589,9 @@ fn getOrLookupVar(self: *cy.Chunk, name: []const u8, staticLookup: bool) !VarLoo }; } else { var resVarId = res.varId; - if (self.vars.items[res.varId].type == .param) { - const parentBlockId = self.semaBlockStack.items[res.blockDepth - 1]; - const parentBlock = &self.semaBlocks.items[parentBlockId]; - resVarId = try replaceParamWithCopy(self, parentBlock, name, res.varId); + if (self.vars.items[res.varId].type == .param and !self.vars.items[res.varId].inner.param.copied) { + var svar = &self.vars.items[res.varId]; + svar.inner.param.copied = true; } const id = try pushCapturedVar(self, name, resVarId, parentVar.vtype); @@ -2541,16 +2633,14 @@ fn lookupParentLocal(c: *cy.Chunk, name: []const u8) !?LookupParentLocalResult { if (c.semaBlockDepth() > 1) { const prevId = c.semaBlockStack.items[c.semaBlockDepth() - 1]; const prev = c.semaBlocks.items[prevId]; - if (prev.nameToVar.get(name)) |varId| { - const svar = c.vars.items[varId]; - if (svar.isDeclared) { - if (svar.type != .staticAlias) { - return .{ - .varId = varId, - .blockDepth = c.semaBlockDepth(), - .isObjectMember = false, - }; - } + if (prev.nameToVar.get(name)) |varInfo| { + const svar = c.vars.items[varInfo.varId]; + if (svar.isCapturable()) { + return .{ + .varId = varInfo.varId, + .blockDepth = c.semaBlockDepth(), + .isObjectMember = false, + }; } } @@ -2562,7 +2652,7 @@ fn lookupParentLocal(c: *cy.Chunk, name: []const u8) !?LookupParentLocalResult { if (cy.module.getSym(mod, nameId)) |sym| { if (sym.symT == .field) { return .{ - .varId = prev.nameToVar.get("self").?, + .varId = prev.nameToVar.get("self").?.varId, .blockDepth = c.semaBlockDepth(), .isObjectMember = true, }; @@ -2680,23 +2770,18 @@ pub fn ensureFuncSig(c: *cy.VMcompiler, params: []const TypeId, ret: TypeId) !Fu } else { const id: u32 = @intCast(c.sema.resolvedFuncSigs.items.len); const new = try c.alloc.dupe(SymbolId, params); - var isParamsTyped = false; + var reqCallTypeCheck = false; for (params) |symId| { - if (symId != bt.Any) { - isParamsTyped = true; + if (symId != bt.Dynamic and symId != bt.Any) { + reqCallTypeCheck = true; break; } } - var isTyped = isParamsTyped; - if (ret != bt.Any) { - isTyped = true; - } try c.sema.resolvedFuncSigs.append(c.alloc, .{ .paramPtr = new.ptr, .paramLen = @intCast(new.len), .retSymId = ret, - .isTyped = isTyped, - .isParamsTyped = isParamsTyped, + .reqCallTypeCheck = reqCallTypeCheck, }); res.value_ptr.* = id; res.key_ptr.* = .{ @@ -3485,8 +3570,8 @@ fn resolveTypeSymFromModule(chunk: *cy.Chunk, modId: cy.ModuleId, nameId: NameSy .typeAlias => { const srcChunk = &chunk.compiler.chunks.items[mod.chunkId]; const node = srcChunk.nodes[modSym.inner.typeAlias.declId]; - const symId = try getOrResolveTypeSymFromSpecNode(srcChunk, node.head.typeAliasDecl.typeSpecHead); - return symId; + const typeId = try getOrResolveTypeFromSpecNode(srcChunk, node.head.typeAliasDecl.typeSpecHead); + return typeId; }, else => {}, } @@ -3597,8 +3682,8 @@ fn resolveSymFromModule(chunk: *cy.Chunk, modId: cy.ModuleId, nameId: NameSymId, .typeAlias => { const srcChunk = &chunk.compiler.chunks.items[mod.chunkId]; const node = srcChunk.nodes[modSym.inner.typeAlias.declId]; - const symId = try getOrResolveTypeSymFromSpecNode(srcChunk, node.head.typeAliasDecl.typeSpecHead); - return CompactSymbolId.initSymId(symId); + const typeId = try getOrResolveTypeFromSpecNode(srcChunk, node.head.typeAliasDecl.typeSpecHead); + return CompactSymbolId.initSymId(typeId); }, .symToManyFuncs => { // More than one func for sym. @@ -3679,10 +3764,12 @@ pub fn curBlock(self: *cy.Chunk) *Block { pub fn endBlock(self: *cy.Chunk) !void { try endSubBlock(self); - const sblock = curBlock(self); - sblock.deinitTemps(self.alloc); + const block = curBlock(self); + block.deinitTemps(self.alloc); self.semaBlockStack.items.len -= 1; self.curSemaBlockId = self.semaBlockStack.items[self.semaBlockStack.items.len-1]; + + self.varDeclStack.items.len = block.varDeclStart; } pub fn getAccessExprResult(c: *cy.Chunk, ltype: TypeId, rightName: []const u8) !AccessExprResult { @@ -3813,39 +3900,19 @@ const VarResult = struct { fromParentBlock: bool, }; -/// To a local type before assigning to a local variable. -fn toLocalType(vtype: TypeId) TypeId { - return vtype; -} - -fn replaceParamWithCopy(self: *cy.Chunk, sblock: *Block, name: []const u8, id: LocalVarId) !LocalVarId { - _ = sblock.nameToVar.remove(name); - const svar = self.vars.items[id]; - const newId: u32 = @intCast(self.vars.items.len); - try self.vars.append(self.alloc, .{ - .type = .paramCopy, - .name = if (builtin.mode == .Debug) name else {}, - .vtype = toLocalType(svar.vtype), - .lifetimeRcCandidate = types.isRcCandidateType(self.compiler, svar.vtype), - .isDefinedOnce = true, - .inner = .{ - .paramCopy = svar.inner.paramIdx, - }, - }); - try sblock.locals.append(self.alloc, newId); - try sblock.nameToVar.put(self.alloc, name, newId); - return newId; -} - fn assignVar(self: *cy.Chunk, ident: cy.NodeId, vtype: TypeId) !void { - // log.debug("set var {s}", .{name}); const node = self.nodes[ident]; const name = self.getNodeTokenString(node); + // log.tracev("set var {s}", .{name}); const res = try getOrLookupVar(self, name, true); switch (res) { .local => |id| { var svar = &self.vars.items[id]; + + // Save type for codegen. + self.nodeTypes[ident] = svar.vtype; + if (svar.isParentLocalAlias()) { if (!svar.isBoxed) { // Becomes boxed so codegen knows ahead of time. @@ -3853,10 +3920,8 @@ fn assignVar(self: *cy.Chunk, ident: cy.NodeId, vtype: TypeId) !void { } } - if (svar.type == .param) { - const sblock = curBlock(self); - const newId = try replaceParamWithCopy(self, sblock, name, id); - svar = &self.vars.items[newId]; + if (svar.type == .param and !svar.inner.param.copied) { + svar.inner.param.copied = true; } const ssblock = curSubBlock(self); @@ -3865,11 +3930,15 @@ fn assignVar(self: *cy.Chunk, ident: cy.NodeId, vtype: TypeId) !void { try ssblock.prevVarTypes.put(self.alloc, id, svar.vtype); } - // Update current type after checking for branched assignment. - if (!types.isSameType(svar.vtype, vtype)) { - svar.vtype = toLocalType(vtype); - if (!svar.lifetimeRcCandidate and types.isRcCandidateType(self.compiler, vtype)) { - svar.lifetimeRcCandidate = true; + if (svar.dynamic) { + svar.dynamicLastMutSubBlockId = self.curSemaSubBlockId; + + // Update current type after checking for branched assignment. + if (svar.vtype != vtype) { + svar.vtype = vtype; + if (!svar.lifetimeRcCandidate and types.isRcCandidateType(self.compiler, vtype)) { + svar.lifetimeRcCandidate = true; + } } } @@ -3893,6 +3962,14 @@ fn endSubBlock(self: *cy.Chunk) !void { const sblock = curBlock(self); const ssblock = curSubBlock(self); + // Update max locals. + if (sblock.curNumLocals > sblock.maxLocals) { + sblock.maxLocals = sblock.curNumLocals; + } + + // Unwind. + sblock.curNumLocals -= ssblock.numLocals; + const curAssignedVars = self.assignedVarStack.items[ssblock.assignedVarStart..]; self.assignedVarStack.items.len = ssblock.assignedVarStart; @@ -3912,12 +3989,6 @@ fn endSubBlock(self: *cy.Chunk) !void { if (!pssblock.prevVarTypes.contains(varId)) { try self.assignedVarStack.append(self.alloc, varId); } - - // Record merged type for codegen. - try ssblock.endMergeTypes.append(self.alloc, .{ - .id = varId, - .vtype = svar.vtype, - }); } } else { // New variable assignment, propagate to parent block. @@ -3927,45 +3998,32 @@ fn endSubBlock(self: *cy.Chunk) !void { } ssblock.prevVarTypes.deinit(self.alloc); - // Mark vars as undeclared. - const curDeclaredVars = self.declaredVarStack.items[ssblock.declaredVarStart..]; - self.declaredVarStack.items.len = ssblock.declaredVarStart; - for (curDeclaredVars) |varId| { - const svar = &self.vars.items[varId]; - svar.isDeclared = false; + // Restore `nameToVar` to previous sub-block state. + if (sblock.subBlockDepth > 1) { + // Remove dead vars. + const varDecls = self.varDeclStack.items[ssblock.varDeclStart..]; + for (varDecls) |decl| { + const name = decl.namePtr[0..decl.nameLen]; + _ = sblock.nameToVar.remove(name); + } + self.varDeclStack.items.len = ssblock.varDeclStart; + + // Restore shadowed vars. + const varShadows = self.varShadowStack.items[ssblock.varShadowStart..]; + for (varShadows) |shadow| { + const name = shadow.namePtr[0..shadow.nameLen]; + try sblock.nameToVar.putNoClobber(self.alloc, name, .{ + .varId = shadow.varId, + .subBlockId = shadow.subBlockId, + }); + } + self.varShadowStack.items.len = ssblock.varShadowStart; } self.curSemaSubBlockId = ssblock.prevSubBlockId; sblock.subBlockDepth -= 1; } -fn pushIterSubBlock(self: *cy.Chunk) !void { - try pushSubBlock(self); -} - -fn endIterSubBlock(self: *cy.Chunk) !void { - const ssblock = curSubBlock(self); - for (self.assignedVarStack.items[ssblock.assignedVarStart..]) |varId| { - const svar = self.vars.items[varId]; - if (ssblock.prevVarTypes.get(varId)) |prevt| { - if (svar.vtype != prevt) { - // Type differs from prev scope type. Record change for iter block codegen. - try ssblock.iterVarBeginTypes.append(self.alloc, .{ - .id = varId, - .vtype = bt.Any, - }); - } - } else { - // First assigned in iter block. Record change for iter block codegen. - try ssblock.iterVarBeginTypes.append(self.alloc, .{ - .id = varId, - .vtype = svar.vtype, - }); - } - } - try endSubBlock(self); -} - pub fn declareUsingModule(chunk: *cy.Chunk, modId: cy.ModuleId) !void { try chunk.usingModules.append(chunk.alloc, modId); } @@ -4203,7 +4261,7 @@ fn resolveHostQuickenFunc( const rtSymId = try c.compiler.vm.ensureFuncSym(parentSymId, nameId, funcSigId); const funcSig = c.compiler.sema.getFuncSig(funcSigId); - const rtSym = rt.FuncSymbol.initHostQuickenFunc(func, funcSig.isTyped, funcSig.numParams(), funcSigId); + const rtSym = rt.FuncSymbol.initHostQuickenFunc(func, funcSig.reqCallTypeCheck, funcSig.numParams(), funcSigId); c.compiler.vm.setFuncSym(rtSymId, rtSym); return try resolveFunc(c, key, funcSigId, cy.NullId); } @@ -4216,7 +4274,7 @@ fn resolveHostFunc( const rtSymId = try c.compiler.vm.ensureFuncSym(parentSymId, nameId, funcSigId); const funcSig = c.compiler.sema.getFuncSig(funcSigId); - const rtSym = rt.FuncSymbol.initHostFunc(func, funcSig.isTyped, funcSig.numParams(), funcSigId); + const rtSym = rt.FuncSymbol.initHostFunc(func, funcSig.reqCallTypeCheck, funcSig.numParams(), funcSigId); c.compiler.vm.setFuncSym(rtSymId, rtSym); return try resolveFunc(c, key, funcSigId, cy.NullId); } @@ -4395,10 +4453,13 @@ pub const FuncSig = struct { paramLen: u16, /// If a param or the return type is not the any type. - isTyped: bool, + // isTyped: bool, /// If a param is not the any type. - isParamsTyped: bool, + // isParamsTyped: bool, + + /// Requires type checking if any param is not `dynamic` or `any`. + reqCallTypeCheck: bool, pub inline fn params(self: FuncSig) []const types.TypeId { return self.paramPtr[0..self.paramLen]; @@ -4467,9 +4528,6 @@ pub const FuncDecl = struct { }, }, - /// pc to start of end locals procedure. - genEndLocalsPc: u32 = cy.NullId, - /// Number of params in the function signature. numParams: u8, @@ -4759,14 +4817,14 @@ pub fn appendResolvedRootModule(c: *cy.VMcompiler, absSpec: []const u8) !cy.Modu } test "sema internals." { - if (builtin.mode == .Debug) { + if (cy.Trace) { if (cy.is32Bit) { - try t.eq(@sizeOf(LocalVar), 24); + try t.eq(@sizeOf(LocalVar), 28); } else { - try t.eq(@sizeOf(LocalVar), 32); + try t.eq(@sizeOf(LocalVar), 40); } } else { - try t.eq(@sizeOf(LocalVar), 16); + try t.eq(@sizeOf(LocalVar), 20); } try t.eq(@sizeOf(FuncSym), 24); diff --git a/src/vm.c b/src/vm.c index b2ed26d6f..d4856c224 100644 --- a/src/vm.c +++ b/src/vm.c @@ -672,8 +672,6 @@ ResultCode execBytecode(VM* vm) { JENTRY(Ret1), JENTRY(Ret0), JENTRY(Call), - JENTRY(Field), - JENTRY(FieldIC), JENTRY(FieldRetain), JENTRY(FieldRetainIC), JENTRY(Lambda), @@ -694,7 +692,6 @@ ResultCode execBytecode(VM* vm) { JENTRY(CompareNot), JENTRY(StringTemplate), JENTRY(NegFloat), - JENTRY(Init), JENTRY(ObjectTypeCheck), JENTRY(ObjectSmall), JENTRY(Object), @@ -1308,58 +1305,30 @@ ResultCode execBytecode(VM* vm) { } RETURN(res.code); } - CASE(Field): { -#define FIELD_BODY(v) \ - uint8_t left = pc[1]; \ - uint8_t dst = pc[2]; \ - uint16_t symId = READ_U16(3); \ - Value recv = stack[left]; \ - if (VALUE_IS_POINTER(recv)) { \ - HeapObject* obj = VALUE_AS_HEAPOBJECT(recv); \ - uint8_t offset = getFieldOffset(vm, obj, symId); \ - if (offset != NULL_U8) { \ - stack[dst] = objectGetField((Object*)obj, offset); \ - pc[0] = FIELD_BODY_IC_##v; \ - WRITE_U16(5, OBJ_TYPEID(obj)); \ - pc[7] = offset; \ - } else { \ - stack[dst] = zGetFieldFallback(vm, obj, ((FieldSymbolMap*)vm->fieldSyms.buf)[symId].nameId); \ - } \ - FIELD_BODY_END_##v \ - pc += 8; \ - NEXT(); \ - } else { \ - panicFieldMissing(vm); \ - RETURN(RES_CODE_PANIC); \ - } -#define FIELD_BODY_IC_0 CodeFieldIC -#define FIELD_BODY_END_0 - FIELD_BODY(0); - } - CASE(FieldIC): { - Value recv = stack[pc[1]]; + CASE(FieldRetain): { + uint8_t left = pc[1]; uint8_t dst = pc[2]; + uint16_t symId = READ_U16(3); + Value recv = stack[left]; if (VALUE_IS_POINTER(recv)) { HeapObject* obj = VALUE_AS_HEAPOBJECT(recv); - if (OBJ_TYPEID(obj) == READ_U16(5)) { - stack[dst] = objectGetField((Object*)obj, pc[7]); - pc += 8; - NEXT(); + uint8_t offset = getFieldOffset(vm, obj, symId); + if (offset != NULL_U8) { + stack[dst] = objectGetField((Object*)obj, offset); + pc[0] = CodeFieldRetainIC; + WRITE_U16(5, OBJ_TYPEID(obj)); + pc[7] = offset; } else { - // Deoptimize. - pc[0] = CodeField; - NEXT(); + stack[dst] = zGetFieldFallback(vm, obj, ((FieldSymbolMap*)vm->fieldSyms.buf)[symId].nameId); } + retain(vm, stack[dst]); + pc += 8; + NEXT(); } else { panicFieldMissing(vm); RETURN(RES_CODE_PANIC); } } - CASE(FieldRetain): { -#define FIELD_BODY_IC_1 CodeFieldRetainIC -#define FIELD_BODY_END_1 retain(vm, stack[dst]); - FIELD_BODY(1); - } CASE(FieldRetainIC): { Value recv = stack[pc[1]]; uint8_t dst = pc[2]; @@ -1370,17 +1339,15 @@ ResultCode execBytecode(VM* vm) { retain(vm, stack[dst]); pc += 8; NEXT(); + } else { + // Deoptimize. + pc[0] = CodeFieldRetain; + NEXT(); } } else { - // return vm.getFieldMissingSymbolError(); - RETURN(RES_CODE_UNKNOWN); + panicFieldMissing(vm); + RETURN(RES_CODE_PANIC); } - // Deoptimize. - pc[0] = CodeFieldRetain; - // framePtr[dst] = try @call(.never_inline, gvm.getField, .{ recv, pc[3].arg }); - // retain(vm, framePtr[dst]); - // pc += 7; - NEXT(); } CASE(Lambda): { uint32_t funcPc = ((uint32_t)getInstOffset(vm, pc)) - pc[1]; @@ -1489,16 +1456,6 @@ ResultCode execBytecode(VM* vm) { CASE(NegFloat): { FLOAT_UNOP(stack[pc[2]] = VALUE_FLOAT(-VALUE_AS_FLOAT(val))) } - CASE(Init): { - uint8_t start = pc[1]; - uint8_t numLocals = pc[2]; - uint8_t i; - for (i = start; i < start + numLocals; i += 1) { - stack[i] = VALUE_NONE; - } - pc += 3; - NEXT(); - } CASE(ObjectTypeCheck): { u8 startLocal = pc[1]; u8 n = pc[2]; diff --git a/src/vm.h b/src/vm.h index f84c33e0d..35e6ccbe7 100644 --- a/src/vm.h +++ b/src/vm.h @@ -190,8 +190,6 @@ typedef enum { CodeRet1, CodeRet0, CodeCall, - CodeField, - CodeFieldIC, CodeFieldRetain, CodeFieldRetainIC, CodeLambda, @@ -216,7 +214,6 @@ typedef enum { CodeCompareNot, CodeStringTemplate, CodeNegFloat, - CodeInit, CodeObjectTypeCheck, CodeObjectSmall, CodeObject, diff --git a/src/vm.zig b/src/vm.zig index 66bf57f58..ad4a9ff84 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -584,14 +584,7 @@ pub const VM = struct { } tt = cy.debug.timer(); - defer { - tt.endPrint("eval"); - if (cy.Trace) { - if (cy.verbose) { - self.dumpInfo() catch fatal(); - } - } - } + defer tt.endPrint("eval"); return self.evalByteCode(res.buf); } diff --git a/src/vm_compiler.zig b/src/vm_compiler.zig index 4366dc8ae..708b5d79f 100644 --- a/src/vm_compiler.zig +++ b/src/vm_compiler.zig @@ -289,15 +289,6 @@ pub const VMcompiler = struct { while (id < self.chunks.items.len) : (id += 1) { try performChunkSema(self, id); } - - // Set up for genVarDecls. - // All main blocks should be initialized since genVarDecls can alternate chunks. - for (self.chunks.items) |*chunk| { - try chunk.pushSemaBlock(chunk.mainSemaBlockId); - chunk.buf = &self.buf; - // Temp locals can start at 0 for initializers codegen. - chunk.curBlock.numLocals = 0; - } if (!config.skipCodegen) { try performCodegen(self); @@ -342,7 +333,7 @@ fn performChunkSema(self: *VMcompiler, id: cy.ChunkId) !void { const chunk = &self.chunks.items[id]; // Dummy first element to avoid len > 0 check during pop. - try chunk.semaSubBlocks.append(self.alloc, sema.SubBlock.init(0, 0, 0)); + try chunk.semaSubBlocks.append(self.alloc, sema.SubBlock.init(0, 0, 0, 0, 0)); try chunk.semaBlockStack.append(self.alloc, 0); const root = chunk.nodes[chunk.parserAstRootId]; @@ -358,10 +349,11 @@ fn performChunkSema(self: *VMcompiler, id: cy.ChunkId) !void { fn performChunkCodegen(self: *VMcompiler, id: cy.ChunkId) !void { const chunk = &self.chunks.items[id]; + chunk.varDeclStack.clearRetainingCapacity(); if (id == 0) { // Main script performs gen for decls and the main block. - try gen.initVarLocals(chunk); + try gen.reserveMainRegs(chunk); const jumpStackStart = chunk.blockJumpStack.items.len; const root = chunk.nodes[0]; @@ -543,6 +535,15 @@ fn performCodegen(self: *VMcompiler) !void { } fn genBytecode(self: *VMcompiler) !void { + // Set up for genVarDecls. + // All main blocks should be initialized since genVarDecls can alternate chunks. + for (self.chunks.items) |*chunk| { + try cy.codegen.pushSemaBlock(chunk, chunk.mainSemaBlockId); + chunk.buf = &self.buf; + // Temp locals can start at 0 for initializers codegen. + chunk.rega.resetState(0); + } + // Once all symbols have been resolved, the static initializers are generated in DFS order. for (self.chunks.items) |*chunk| { log.tracev("gen static initializer for chunk: {}", .{chunk.id}); diff --git a/test/behavior_test.zig b/test/behavior_test.zig index 2320814d4..1b4835312 100644 --- a/test/behavior_test.zig +++ b/test/behavior_test.zig @@ -709,7 +709,7 @@ test "Debug labels." { const vm = run.vm.internal(); try t.eq(vm.compiler.buf.debugMarkers.items.len, 1); const actLabel = vm.compiler.buf.debugMarkers.items[0]; - try t.eq(actLabel.pc, 6); + try t.eq(actLabel.pc, 3); try t.eqStr(actLabel.getLabelName(), "MyLabel"); }}.func); } @@ -2311,11 +2311,11 @@ test "Local variable declaration." { \\var a = 2 , struct { fn func(run: *VMrunner, res: EvalResult) !void { try run.expectErrorReport(res, error.CompileError, - \\CompileError: Variable `a` is already declared in the main block. + \\CompileError: Variable `a` is already declared in the block. \\ - \\main:2:1: + \\main:2:5: \\var a = 2 - \\^ + \\ ^ \\ ); }}.func); @@ -2332,9 +2332,9 @@ test "Local variable declaration." { try run.expectErrorReport(res, error.CompileError, \\CompileError: `a` already references a parent local variable. \\ - \\main:6:3: + \\main:6:7: \\ var a = 3 - \\ ^ + \\ ^ \\ ); }}.func); @@ -2350,28 +2350,22 @@ test "Local variable declaration." { try run.expectErrorReport(res, error.CompileError, \\CompileError: `a` already references a static variable. \\ - \\main:5:3: + \\main:5:7: \\ var a = 3 - \\ ^ + \\ ^ \\ ); }}.func); - // Can't declare in a nested sub block. - try eval(.{ .silent = true }, + // New local inside sub-block. + try evalPass(.{}, + \\import test \\var a = 1 \\if true: \\ var a = 2 - , struct { fn func(run: *VMrunner, res: EvalResult) !void { - try run.expectErrorReport(res, error.CompileError, - \\CompileError: Variable `a` is already declared in the main block. - \\ - \\main:3:3: - \\ var a = 2 - \\ ^ - \\ - ); - }}.func); + \\ test.eq(a, 2) + \\test.eq(a, 1) + ); // Can't reference var from non parent if block. try eval(.{ .silent = true }, diff --git a/test/for_iter_test.cy b/test/for_iter_test.cy index 50c0f686b..7fab9d270 100644 --- a/test/for_iter_test.cy +++ b/test/for_iter_test.cy @@ -16,12 +16,14 @@ for sList each it: sum += it t.eq(sum, 6) --- Loop iterator var overwrites the user var. +-- Loop item var shadows parent var. var elem = 123 +sum = 0 list = [1, 2, 3] for list each elem: - pass -t.eq(elem, none) + sum += elem +t.eq(sum, 6) +t.eq(elem, 123) -- Break. list = [1, 2, 3] diff --git a/test/for_range_test.cy b/test/for_range_test.cy index 54de751c2..e1f005fa6 100644 --- a/test/for_range_test.cy +++ b/test/for_range_test.cy @@ -34,12 +34,13 @@ for 0..10 each i: count += inner t.eq(count, 100) --- Index vars overwrites user var. +-- Index vars shadow parent vars. var i = 123 var sum = 0 for 0..10 each i: sum += i -t.eq(i, 9) +t.eq(sum, 45) +t.eq(i, 123) -- Reverse direction. sum = 0 diff --git a/test/trace_test.zig b/test/trace_test.zig index a4455174c..a756b3c99 100644 --- a/test/trace_test.zig +++ b/test/trace_test.zig @@ -53,8 +53,8 @@ test "ARC." { \\t.eq(s.value[0], 123) ); trace = run.getTrace(); - try t.eq(trace.numRetains, 3); - try t.eq(trace.numReleases, 3); + try t.eq(trace.numRetains, 4); + try t.eq(trace.numReleases, 4); // Object is released when returned rvalue field access. val = try run.eval( @@ -93,7 +93,6 @@ test "ARC." { trace = run.getTrace(); try t.eq(trace.numRetains, 2); try t.eq(trace.numReleases, 2); - } test "ARC for static variable declarations." {