diff --git a/arimo/compiler/backend/IRLower.arm b/arimo/compiler/backend/IRLower.arm index 03d49f5..b5a9bc9 100644 --- a/arimo/compiler/backend/IRLower.arm +++ b/arimo/compiler/backend/IRLower.arm @@ -66,15 +66,20 @@ public class IRLower { private allFieldClasses: List; private classFldStarts : List; private classFldCounts : List; + private allStaticFieldNames : List; + private classStaticFldStarts : List; + private classStaticFldCounts : List; private methodRetNames : List; private methodRetClasses: List; private teardownLabels : List; private curClass : String; private thisReg : String; private linux : Boolean; + private loweringError : Boolean; public constructor(linux: Boolean) { this.linux = linux; + this.loweringError = false; this.irFns = List(); this.strNames = List(); this.strConts = List(); @@ -98,6 +103,9 @@ public class IRLower { this.allFieldClasses = List(); this.classFldStarts = List(); this.classFldCounts = List(); + this.allStaticFieldNames = List(); + this.classStaticFldStarts = List(); + this.classStaticFldCounts = List(); this.methodRetNames = List(); this.methodRetClasses = List(); this.teardownLabels = List(); @@ -106,6 +114,8 @@ public class IRLower { this.irFns.append(IRFunction("__placeholder", IRType.VOID)); } + public hasLoweringError() : Boolean { return this.loweringError; } + // ===== Class layout registration ===== private registerClasses(modules: List) { @@ -128,6 +138,8 @@ public class IRLower { if (this.classIdxOf(cd.name) >= 0) { return; } Integer start = this.allFieldNames.length(); Integer count = 0; + Integer stStart = this.allStaticFieldNames.length(); + Integer stCount = 0; Integer fi = 0; while (fi < cd.fields.length()) { FieldDecl fd = cd.fields.get(fi) as FieldDecl; @@ -135,12 +147,17 @@ public class IRLower { this.allFieldNames.append(fd.name); this.allFieldClasses.append(this.tyToClass(fd.ty)); count = count + 1; + } else { + this.allStaticFieldNames.append(fd.name); + stCount = stCount + 1; } fi = fi + 1; } this.classNames.append(cd.name); this.classFldStarts.append(start); this.classFldCounts.append(count); + this.classStaticFldStarts.append(stStart); + this.classStaticFldCounts.append(stCount); this.teardownLabels.append(""); // Track parent class for recursive field release (inheritance chain) if (cd.extends_ != "" && cd.extends_.length() > 0) { @@ -543,6 +560,14 @@ public class IRLower { } private emitFieldPtr(objVal: IRValue, offset: Integer) : IRValue { + if (objVal.kind == IRValueKind.IMM_INT && objVal.immInt == 0) { + IO.println("arc: [FAIL] SafeRegAlloc null field base offset=${offset} class=${this.curClass}"); + this.loweringError = true; + String dummyR = this.newReg(); + IRValue dummyV = IRValue.reg(dummyR, IRType.PTR); + this.emit(IRInstr.mov(dummyV, IRValue.ofInt(1, IRType.I64))); + return dummyV; + } String pReg = this.newReg(); IRValue pDst = IRValue.reg(pReg, IRType.PTR); // Compute field pointer via MOV + ADD @@ -988,6 +1013,28 @@ public class IRLower { } if (expr.kind == ExprKind.FIELD) { + if (expr.object.kind == ExprKind.IDENT) { + Integer stClsIdx = this.classIdxOf(expr.object.strVal); + if (stClsIdx >= 0) { + Integer stFldIdx = this.fieldIdxOf(stClsIdx, expr.field); + if (stFldIdx >= 0) { + String stR = this.newReg(); + this.emit(IRInstr.mov(IRValue.reg(stR, IRType.I64), IRValue.ofInt(stFldIdx, IRType.I64))); + return IRValue.reg(stR, IRType.I64); + } + Integer stStart = this.classStaticFldStarts.get(stClsIdx) as Integer; + Integer stCount = this.classStaticFldCounts.get(stClsIdx) as Integer; + Integer sfi = 0; + while (sfi < stCount) { + if (this.allStaticFieldNames.get(stStart + sfi) as String == expr.field) { + String stR = this.newReg(); + this.emit(IRInstr.mov(IRValue.reg(stR, IRType.I64), IRValue.ofInt(sfi, IRType.I64))); + return IRValue.reg(stR, IRType.I64); + } + sfi = sfi + 1; + } + } + } IRValue objVal = this.lowerExpr(expr.object); String objCls = this.inferClass(expr.object); Integer clsIdx = this.classIdxOf(objCls); @@ -1607,6 +1654,11 @@ public class IRLower { if (!libcRes.isNone()) { return libcRes; } } Integer clsIdx = this.classIdxOf(class_); + // If class_ is not a registered class, parser generated CTOR for what + // is actually a static method call on the current class (e.g. foo() → T.foo()). + if (clsIdx < 0 && this.curClass != "") { + return this.lowerStaticCall(this.curClass, class_, args); + } Integer sz = 16; if (clsIdx >= 0) { sz = this.classSizeBytes(clsIdx); } IRValue objPtr = this.emitHeapAlloc(IRValue.ofInt(sz, IRType.I64)); @@ -1637,8 +1689,15 @@ public class IRLower { if (obj.strVal == "Env") { return this.lowerStaticCall("Env", method, args); } + if (obj.strVal == "File") { + return this.lowerStaticCall("File", method, args); + } + if (obj.strVal == "Process") { + return this.lowerStaticCall("Process", method, args); + } // Static method call on known class: ClassName.method() - if (this.classIdxOf(obj.strVal) >= 0) { + // Guarded by varLookup — only redirect if name is NOT a local variable. + if (this.varLookup(obj.strVal) == "" && this.classIdxOf(obj.strVal) >= 0) { return this.lowerStaticCall(obj.strVal, method, args); } } @@ -3000,6 +3059,7 @@ public class IRLower { this.generateToLower(); this.generateSystem(); this.generateBuildArgs(); + this.generateFileExists(); this.generateStartFn(); } @@ -3186,4 +3246,26 @@ public class IRLower { this.emit(IRInstr.label("sy_err")); this.emit(IRInstr.ret(IRValue.ofInt(-1, IRType.I64))); } + + private generateFileExists() { + this.beginFn("File__exists", IRType.I64); + this.addParamToLast("path", IRType.PTR); + this.resetFnContext(); this.emit(IRInstr.label("entry")); + IRValue pathV = IRValue.reg("path", IRType.PTR); + String modeR = this.newReg(); IRValue modeV = IRValue.reg(modeR, IRType.PTR); + this.emit(IRInstr.mov(modeV, IRValue.global(this.internStr("r")))); + List foArgs = List(); foArgs.append(pathV); foArgs.append(modeV); + String fR = this.newReg(); IRValue fV = IRValue.reg(fR, IRType.I64); + this.emit(IRInstr.call(fV, "__arimo_fopen", foArgs)); + String eqL = this.newLabel("fe_fail"); String endL = this.newLabel("fe_end"); + String retR = this.newReg(); IRValue retV = IRValue.reg(retR, IRType.I64); + this.emit(IRInstr.cmp(fV, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.branch(IROpcode.JE, eqL)); + this.emit(IRInstr.mov(retV, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.jmp(endL)); + this.emit(IRInstr.label(eqL)); + this.emit(IRInstr.mov(retV, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.label(endL)); + this.emit(IRInstr.ret(retV)); + } } diff --git a/arimo/compiler/backend/IRToX64.arm b/arimo/compiler/backend/IRToX64.arm index f7cc980..456a5b7 100644 --- a/arimo/compiler/backend/IRToX64.arm +++ b/arimo/compiler/backend/IRToX64.arm @@ -166,6 +166,7 @@ public class IRToX64 { this.safeRa.assertKnown(val.name); this.safeRa.assertWritten(val.name); Integer off = this.safeRa.slotOffsetByName(val.name); + if (off == 0) { this.safeRa.errorCode = 3; } this.enc.movRMem(scratchReg, X64Reg.RBP, off); return scratchReg; } @@ -182,6 +183,7 @@ public class IRToX64 { private safeStoreDst(name: String, srcReg: Integer) { this.safeRa.assertKnown(name); Integer off = this.safeRa.slotOffsetByName(name); + if (off == 0) { this.safeRa.errorCode = 3; } this.enc.movMemR(X64Reg.RBP, off, srcReg); this.safeRa.markValueWritten(name); } @@ -279,6 +281,11 @@ public class IRToX64 { this.curName = fn.name; this.savedCount = 0; // no callee-saved tracking in safe mode + // DEBUG: flag large frames (potential slot explosion) + if (frameSize > 1024) { + IO.println("SAFEDBG BIGFRAME fn=${fn.name} slots=${this.safeRa.totalSlots()} frame=${frameSize}"); + } + this.enc.defineLabel(fn.name); // _start raw prologue (Linux): capture argc/argv from initial stack diff --git a/arimo/compiler/backend/SafeRegAlloc.arm b/arimo/compiler/backend/SafeRegAlloc.arm index 2c52381..13a9996 100644 --- a/arimo/compiler/backend/SafeRegAlloc.arm +++ b/arimo/compiler/backend/SafeRegAlloc.arm @@ -51,6 +51,7 @@ public class SafeRegAlloc { private sci : Integer; private curFn : String; private dirty : Boolean; + public errorCode : Integer; // 0=ok, 1=unknown slot, 2=unwritten slot, 3=offset zero public constructor() { this.slots = List(); @@ -59,6 +60,7 @@ public class SafeRegAlloc { this.sci = 0; this.curFn = ""; this.dirty = false; + this.errorCode = 0; // Scratch pool: caller-saved regs for instruction-local use // RAX excluded — reserved for idiv/call-return/syscall-num this.scratch.append(X64Reg.RDI); @@ -137,9 +139,14 @@ public class SafeRegAlloc { } // slotOffsetByName: stack offset looked up by value name. + // Returns offset below frame (safe sentinel) for unknown names — never 0. public slotOffsetByName(name: String) : Integer { Integer idx = this.getSlot(name); - if (idx < 0) { return 0; } + if (idx < 0) { + this.errorCode = 1; + Integer sentinel = -(this.slotCnt + 100) * 8; + return sentinel; + } return this.slotOffset(idx); } @@ -239,29 +246,27 @@ public class SafeRegAlloc { public assertKnown(name: String) { Integer idx = this.getSlot(name); if (idx < 0) { - IO.println("arc: [FAIL] SafeRegAlloc: unknown value \"${name}\" in fn ${this.curFn} — no slot allocated"); - // In V1 self-host: mark error state for caller to check + this.errorCode = 1; } } // assertKnownIdx: index-based variant. public assertKnownIdx(idx: Integer) { if (idx < 0 || idx >= this.slots.length()) { - IO.println("arc: [FAIL] SafeRegAlloc: bad slot index ${idx} in fn ${this.curFn} — out of range (0..${this.slots.length() - 1})"); + this.errorCode = 1; } } // assertWritten: hard-fail if slot exists but never written to. - // Prevents reloading uninitialized stack slots (e.g., used before def). public assertWritten(name: String) { Integer idx = this.getSlot(name); if (idx < 0) { - IO.println("arc: [FAIL] SafeRegAlloc: assertWritten unknown value \"${name}\" in fn ${this.curFn}"); + this.errorCode = 2; return; } SafeValueSlot sv = this.slots.get(idx) as SafeValueSlot; if (!sv.written) { - IO.println("arc: [FAIL] SafeRegAlloc: reload of unwritten slot ${sv.slot} (value \"${name}\") in fn ${this.curFn}"); + this.errorCode = 2; } }