diff --git a/Main.arm b/Main.arm index e2bac4f..6606661 100644 --- a/Main.arm +++ b/Main.arm @@ -215,8 +215,8 @@ public class Main { IO.println("arc: run: cd ".concat(name).concat(" && arc build")); } - private static compile(inputPath: String, stdlibPath: String, checkOnly: Boolean, outName: String, isLinux: Boolean) : Void { - if (!File.exists(inputPath)) { + private static compile(inputPath: String, stdlibPath: String, checkOnly: Boolean, outName: String, isLinux: Boolean, safeRegAlloc: Boolean) : Void { + if (File.exists(inputPath) == false) { IO.println("arc: file not found: ${inputPath}"); return; } @@ -335,6 +335,10 @@ public class Main { IO.println("arc: generating native code..."); NativeBackend nb = NativeBackend(isLinux); + if (safeRegAlloc) { + nb.setSafeRegAlloc(true); + IO.println("arc: using SafeRegAlloc (stack-canonical, correctness-first)"); + } nb.compile(modules, exeFile); if (isLinux) { Process.exec("chmod +x ".concat(exeFile)); } IO.println("arc: done -> ${exeFile}"); @@ -353,14 +357,16 @@ public class Main { String stdlibPath = Main.findStdlib(exePath); String cmd = argv.get(1); - Boolean checkOnly = false; - Boolean doRun = false; - Boolean isLinux = Env.platform().compareTo("linux") == 0; + Boolean checkOnly = false; + Boolean doRun = false; + Boolean isLinux = Env.platform().compareTo("linux") == 0; + Boolean safeRegAlloc = false; Integer fi = 2; while (fi < argc) { String flag = argv.get(fi); - if (flag.compareTo("--check") == 0) { checkOnly = true; } + if (flag.compareTo("--check") == 0) { checkOnly = true; } + if (flag.compareTo("--safe-regalloc") == 0) { safeRegAlloc = true; } if (flag.compareTo("--stdlib-path") == 0) { fi++; if (fi < argc) { stdlibPath = argv.get(fi); } @@ -391,7 +397,7 @@ public class Main { if (cmd.compareTo("run") == 0) { doRun = true; } if (cmd.compareTo("check") == 0) { checkOnly = true; } - if (!File.exists("arc.toml")) { + if (File.exists("arc.toml") == false) { IO.println("arc: arc.toml not found - run from project root"); return; } @@ -402,7 +408,7 @@ public class Main { if (projName.length() == 0) { projName = "app"; } IO.println("arc v1.0 - building '${projName}'"); - Main.compile(entry, stdlibPath, checkOnly, projName, isLinux); + Main.compile(entry, stdlibPath, checkOnly, projName, isLinux, safeRegAlloc); if (doRun && !checkOnly) { String runCmd = ".\\".concat(projName).concat(".exe"); @@ -415,7 +421,7 @@ public class Main { if (cmd.endsWith(".arm")) { IO.println("arc v1.0 - compiling '${cmd}'"); - Main.compile(cmd, stdlibPath, checkOnly, "", isLinux); + Main.compile(cmd, stdlibPath, checkOnly, "", isLinux, safeRegAlloc); return; } diff --git a/arimo/compiler/backend/IRLower.arm b/arimo/compiler/backend/IRLower.arm index 36f0024..03d49f5 100644 --- a/arimo/compiler/backend/IRLower.arm +++ b/arimo/compiler/backend/IRLower.arm @@ -1,2831 +1,3189 @@ -/* -Arimo Lang Compiler - A modern programming language and compiler -Copyright (C) 2026 Egecan Akıncıoğlu - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published -by the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package arimo.compiler.backend; - -import arimo.compiler.ast.ArimoModule; -import arimo.compiler.ast.item.Item; -import arimo.compiler.ast.item.ItemKind; -import arimo.compiler.ast.item.ClassDecl; -import arimo.compiler.ast.decl.MethodDecl; -import arimo.compiler.ast.decl.FieldDecl; -import arimo.compiler.ast.decl.ConstructorDecl; -import arimo.compiler.ast.decl.Param; -import arimo.compiler.ast.stmt.Stmt; -import arimo.compiler.ast.stmt.StmtKind; -import arimo.compiler.ast.stmt.ElseIfBranch; -import arimo.compiler.ast.expr.Expr; -import arimo.compiler.ast.expr.ExprKind; -import arimo.compiler.ast.expr.BinaryOp; -import arimo.compiler.ast.expr.StrPart; -import arimo.compiler.ast.types.AstType; -import arimo.compiler.ast.types.TypeKind; -import arimo.compiler.backend.IRFunction; -import arimo.compiler.backend.IRInstr; -import arimo.compiler.backend.IRValue; -import arimo.compiler.backend.IRValueKind; -import arimo.compiler.backend.IROpcode; -import arimo.compiler.backend.IRType; - -public class IRLower { - public irFns : List; - public strNames : List; - public strConts : List; - private strCnt : Integer; - private labelCnt : Integer; - private regCnt : Integer; - private varNames : List; - private varRegs : List; - private varClassNames : List; - private breakLbl : String; - private contLbl : String; - private breakDepth : Integer; - private contDepth : Integer; - private scopeStack : List>; - private mainFn : String; - private classNames : List; - private classParents : List; - private allFieldNames : List; - private allFieldClasses: List; - private classFldStarts : List; - private classFldCounts : List; - private methodRetNames : List; - private methodRetClasses: List; - private teardownLabels : List; - private curClass : String; - private thisReg : String; - private linux : Boolean; - - public constructor(linux: Boolean) { - this.linux = linux; - this.irFns = List(); - this.strNames = List(); - this.strConts = List(); - this.strCnt = 0; - this.labelCnt = 0; - this.regCnt = 0; - this.varNames = List(); - this.varRegs = List(); - this.varClassNames = List(); - this.breakLbl = ""; - this.contLbl = ""; - this.breakDepth = 0; - this.contDepth = 0; - this.scopeStack = List(); - this.mainFn = ""; - this.classNames = List(); - this.classParents = List(); - this.allFieldNames = List(); - this.allFieldClasses = List(); - this.classFldStarts = List(); - this.classFldCounts = List(); - this.methodRetNames = List(); - this.methodRetClasses = List(); - this.teardownLabels = List(); - this.curClass = ""; - this.thisReg = ""; - this.irFns.append(IRFunction("__placeholder", IRType.VOID)); - } - - // ===== Class layout registration ===== - - private registerClasses(modules: List) { - Integer mi = 0; - while (mi < modules.length()) { - ArimoModule m = modules.get(mi) as ArimoModule; - Integer ii = 0; - while (ii < m.items.length()) { - Item it = m.items.get(ii) as Item; - if (it.kind == ItemKind.CLASS) { - this.registerClass(it.classDecl); - } - ii = ii + 1; - } - mi = mi + 1; - } - } - - private registerClass(cd: ClassDecl) { - if (this.classIdxOf(cd.name) >= 0) { return; } - Integer start = this.allFieldNames.length(); - Integer count = 0; - Integer fi = 0; - while (fi < cd.fields.length()) { - FieldDecl fd = cd.fields.get(fi) as FieldDecl; - if (!fd.isStatic) { - this.allFieldNames.append(fd.name); - this.allFieldClasses.append(this.tyToClass(fd.ty)); - count = count + 1; - } - fi = fi + 1; - } - this.classNames.append(cd.name); - this.classFldStarts.append(start); - this.classFldCounts.append(count); - this.teardownLabels.append(""); - // Track parent class for recursive field release (inheritance chain) - if (cd.extends_ != "" && cd.extends_.length() > 0) { - this.classParents.append(this.classIdxOf(cd.extends_)); - } else { - this.classParents.append(-1); - } - - // Track method return types for inferClass(METHOD) - Integer mi = 0; - while (mi < cd.methods.length()) { - MethodDecl md = cd.methods.get(mi) as MethodDecl; - String retCls = this.tyToClass(md.returnTy); - this.methodRetNames.append("${cd.name}__${md.name}"); - this.methodRetClasses.append(retCls); - mi = mi + 1; - } - } - - private classIdxOf(name: String) : Integer { - Integer i = 0; - while (i < this.classNames.length()) { - String n = this.classNames.get(i) as String; - if (n == name) { return i; } - i = i + 1; - } - return -1; - } - - private fieldIdxOf(clsIdx: Integer, fieldName: String) : Integer { - Integer start = this.classFldStarts.get(clsIdx) as Integer; - Integer count = this.classFldCounts.get(clsIdx) as Integer; - Integer i = 0; - while (i < count) { - String fn = this.allFieldNames.get(start + i) as String; - if (fn == fieldName) { return i + 1; } - i = i + 1; - } - return -1; - } - - private classSizeBytes(clsIdx: Integer) : Integer { - Integer count = this.classFldCounts.get(clsIdx) as Integer; - return (1 + count) * 8 + 8; - } - - private refCountOff(clsIdx: Integer) : Integer { - return this.classSizeBytes(clsIdx) - 8; - } - - // ===== Variable class tracking ===== - - private varClassOf(name: String) : String { - Integer i = 0; - while (i < this.varNames.length()) { - String n = this.varNames.get(i) as String; - if (n == name) { return this.varClassNames.get(i) as String; } - i = i + 1; - } - return ""; - } - - private varSetClass(name: String, cls: String) { - Integer i = 0; - while (i < this.varNames.length()) { - String n = this.varNames.get(i) as String; - if (n == name) { this.varClassNames.set(i, cls); return; } - i = i + 1; - } - } - - // ===== Type inference ===== - - private tyToClass(ty: AstType) : String { - if (ty == null) { return ""; } - if (ty.kind == TypeKind.NAMED) { return ty.name; } - if (ty.kind == TypeKind.STR) { return "String"; } - if (ty.kind == TypeKind.LIST) { return "List"; } - if (ty.kind == TypeKind.NULLABLE) { return this.tyToClass(ty.inner); } - if (ty.kind == TypeKind.INTEGER) { return "Integer"; } - if (ty.kind == TypeKind.BOOLEAN) { return "Boolean"; } - if (ty.kind == TypeKind.FLOAT) { return "Float"; } - if (ty.kind == TypeKind.CHAR) { return "Char"; } - if (ty.kind == TypeKind.I64) { return "I64"; } - if (ty.kind == TypeKind.I32) { return "I32"; } - if (ty.kind == TypeKind.I16) { return "I16"; } - if (ty.kind == TypeKind.I8) { return "I8"; } - if (ty.kind == TypeKind.U64) { return "U64"; } - if (ty.kind == TypeKind.U32) { return "U32"; } - if (ty.kind == TypeKind.U16) { return "U16"; } - if (ty.kind == TypeKind.U8) { return "U8"; } - return ""; - } - - private inferClass(expr: Expr) : String { - if (expr.kind == ExprKind.THIS) { return this.curClass; } - if (expr.kind == ExprKind.IDENT) { return this.varClassOf(expr.strVal); } - if (expr.kind == ExprKind.CTOR) { return expr.class_; } - if (expr.kind == ExprKind.CAST) { - if (expr.castTy.kind == TypeKind.NAMED) { return expr.castTy.name; } - if (expr.castTy.kind == TypeKind.STR) { return "String"; } - if (expr.castTy.kind == TypeKind.LIST) { return "List"; } - if (expr.castTy.kind == TypeKind.NULLABLE) { return this.tyToClass(expr.castTy.inner); } - } - if (expr.kind == ExprKind.FIELD) { - String objCls = this.inferClass(expr.object); - Integer clsIdx = this.classIdxOf(objCls); - if (clsIdx >= 0) { - Integer fldIdx = this.fieldIdxOf(clsIdx, expr.field); - if (fldIdx >= 0) { - Integer start = this.classFldStarts.get(clsIdx) as Integer; - return this.allFieldClasses.get(start + fldIdx - 1) as String; - } - } - return ""; - } - if (expr.kind == ExprKind.METHOD) { - String objCls = this.inferClass(expr.object); - if (objCls != "") { - String key = "${objCls}__${expr.method}"; - Integer i = 0; - while (i < this.methodRetNames.length()) { - if (this.methodRetNames.get(i) as String == key) { - return this.methodRetClasses.get(i) as String; - } - i = i + 1; - } - // Built-in method (String/List): assume method returns receiver type (common for chaining) - return objCls; - } - return ""; - } - return ""; - } - - private inferStaticCallReturn(className: String, methodName: String) : String { - String key = "${className}__${methodName}"; - Integer i = 0; - while (i < this.methodRetNames.length()) { - if (this.methodRetNames.get(i) as String == key) { - return this.methodRetClasses.get(i) as String; - } - i = i + 1; - } - // Known built-in returning String - if (className == "Env" && methodName == "platform") { return "String"; } - return ""; - } - - private isIntegerExpr(expr: Expr) : Boolean { - if (expr.kind == ExprKind.INT_LIT) { return true; } - if (expr.kind == ExprKind.BOOL_LIT) { return true; } - if (expr.kind == ExprKind.BINOP) { - Integer bop = expr.op; - if (bop == BinaryOp.ADD || bop == BinaryOp.SUB || - bop == BinaryOp.MUL || bop == BinaryOp.DIV || - bop == BinaryOp.MOD || bop == BinaryOp.EQ || - bop == BinaryOp.NE || bop == BinaryOp.LT || - bop == BinaryOp.LE || bop == BinaryOp.GT || - bop == BinaryOp.GE) { return true; } - } - if (expr.kind == ExprKind.METHOD) { - String m = expr.method; - if (m == "length" || m == "size" || m == "indexOf" || - m == "charCodeAt" || m == "compareTo") { return true; } - } - return false; - } - - private isStringExpr(expr: Expr) : Boolean { - if (expr.kind == ExprKind.STR_LIT) { return true; } - if (expr.kind == ExprKind.STR_INTERP) { return true; } - if (expr.kind == ExprKind.IDENT) { - String cls = this.varClassOf(expr.strVal); - return cls == "String"; - } - if (expr.kind == ExprKind.FIELD) { - String objCls = this.inferClass(expr.object); - Integer clsIdx = this.classIdxOf(objCls); - if (clsIdx >= 0) { - Integer fldIdx = this.fieldIdxOf(clsIdx, expr.field); - if (fldIdx >= 0) { - Integer start = this.classFldStarts.get(clsIdx) as Integer; - String fldCls = this.allFieldClasses.get(start + fldIdx - 1) as String; - return fldCls == "String"; - } - } - return false; - } - if (expr.kind == ExprKind.METHOD) { - // Check method return type via class metadata first - if (this.inferClass(expr) == "String") { return true; } - // Check static method call on known class (e.g., Functions.greet()) - if (expr.object.kind == ExprKind.IDENT) { - String key = "${expr.object.strVal}__${expr.method}"; - Integer ki = 0; - while (ki < this.methodRetNames.length()) { - if (this.methodRetNames.get(ki) as String == key) { - if (this.methodRetClasses.get(ki) as String == "String") { return true; } - } - ki = ki + 1; - } - } - // Fallback: hardcoded built-in string-returning methods - String m = expr.method; - if (m == "concat" || m == "substring" || m == "toString" || - m == "toUpperCase" || m == "toLowerCase" || m == "trim" || - m == "charAt") { return true; } - return false; - } - if (expr.kind == ExprKind.STATIC_CALL) { - return this.inferStaticCallReturn(expr.class_, expr.method) == "String"; - } - if (expr.kind == ExprKind.CTOR) { return expr.class_ == "String"; } - if (expr.kind == ExprKind.CAST) { - if (expr.castTy.kind == TypeKind.STR) { return true; } - if (expr.castTy.kind == TypeKind.NAMED) { return expr.castTy.name == "String"; } - return false; - } - return false; - } - - // ===== Core IR helpers ===== - - private beginFn(name: String, retTy: Integer) { - this.irFns.append(IRFunction(name, retTy)); - } - - private lastFn() : IRFunction { - Integer last = this.irFns.length() - 1; - return this.irFns.get(last) as IRFunction; - } - - private addParamToLast(name: String, ty: Integer) { - IRFunction f = this.lastFn(); - f.addParam(name, ty); - } - - private newReg() : String { - Integer n = this.regCnt; - this.regCnt = this.regCnt + 1; - return "t${n}"; - } - - private newLabel(prefix: String) : String { - Integer n = this.labelCnt; - this.labelCnt = this.labelCnt + 1; - return "${prefix}${n}"; - } - - private internStr(content: String) : String { - Integer i = 0; - while (i < this.strConts.length()) { - String c = this.strConts.get(i) as String; - if (c == content) { return this.strNames.get(i) as String; } - i = i + 1; - } - String name = "__str${this.strCnt}"; - this.strCnt = this.strCnt + 1; - this.strNames.append(name); - this.strConts.append(content); - return name; - } - - private emit(instr: IRInstr) { - IRFunction f = this.lastFn(); - f.instrs.append(instr); - } - - private varLookup(name: String) : String { - Integer i = 0; - while (i < this.varNames.length()) { - String n = this.varNames.get(i) as String; - if (n == name) { return this.varRegs.get(i) as String; } - i = i + 1; - } - return ""; - } - - private varDefine(name: String, reg: String) { - Integer i = 0; - while (i < this.varNames.length()) { - String n = this.varNames.get(i) as String; - if (n == name) { this.varRegs.set(i, reg); return; } - i = i + 1; - } - this.varNames.append(name); - this.varRegs.append(reg); - this.varClassNames.append(""); - this.trackScopedVar(name); - } - - private resetFnContext() { - this.varNames = List(); - this.varRegs = List(); - this.varClassNames = List(); - this.scopeStack = List(); - this.regCnt = 0; - this.curClass = ""; - this.thisReg = ""; - } - - // ===== Scope management ===== - - private pushScope() { - this.scopeStack.append(List()); - } - - private trackScopedVar(name: String) { - Integer n = this.scopeStack.length(); - if (n > 0) { - List top = this.scopeStack.get(n - 1) as List; - top.append(name); - } - } - - private popScope() { - Integer n = this.scopeStack.length(); - if (n == 0) { return; } - List vars = this.scopeStack.get(n - 1) as List; - Integer i = vars.length(); - while (i > 0) { - i = i - 1; - String vn = vars.get(i) as String; - // TODO: replace magic-string "__this" with ownership metadata model - if (vn == "__this") { continue; } - String vc = this.varClassOf(vn); - Integer vci = this.classIdxOf(vc); - if (vci >= 0) { - String vr = this.varLookup(vn); - if (vr != "") { - this.emitRelease(IRValue.reg(vr, IRType.I64), vci); - } - } - } - this.scopeStack.removeAt(n - 1); - } - - private emitUnwindToDepth(targetDepth: Integer) { - Integer n = this.scopeStack.length(); - while (n > targetDepth) { - this.popScope(); - n = this.scopeStack.length(); - } - } - - // ===== Heap + field helpers ===== - - private emitHeapAlloc(sizeVal: IRValue) : IRValue { - if (this.linux) { - // mmap(NULL, size, PROT_READ|PROT_WRITE=3, MAP_PRIVATE|MAP_ANONYMOUS=34, -1, 0) - String allocReg = this.newReg(); - IRValue allocDst = IRValue.reg(allocReg, IRType.PTR); - List mmapArgs = List(); - mmapArgs.append(IRValue.ofInt(0, IRType.I64)); - mmapArgs.append(sizeVal); - mmapArgs.append(IRValue.ofInt(3, IRType.I64)); - mmapArgs.append(IRValue.ofInt(34, IRType.I64)); - mmapArgs.append(IRValue.ofInt(-1, IRType.I64)); - mmapArgs.append(IRValue.ofInt(0, IRType.I64)); - this.emit(IRInstr.syscall(allocDst, 9, mmapArgs)); - return allocDst; - } - String phReg = this.newReg(); - IRValue phDst = IRValue.reg(phReg, IRType.I64); - List phArgs = List(); - this.emit(IRInstr.call(phDst, "__ext__GetProcessHeap", phArgs)); - String allocReg = this.newReg(); - IRValue allocDst = IRValue.reg(allocReg, IRType.PTR); - List haArgs = List(); - haArgs.append(phDst); - haArgs.append(IRValue.ofInt(0, IRType.I64)); - haArgs.append(sizeVal); - this.emit(IRInstr.call(allocDst, "__ext__HeapAlloc", haArgs)); - return allocDst; - } - - private emitHeapFree(objPtr: IRValue, clsIdx: Integer) { - Integer sz = this.classSizeBytes(clsIdx); - if (this.linux) { - List munmapArgs = List(); - munmapArgs.append(objPtr); - munmapArgs.append(IRValue.ofInt(sz, IRType.I64)); - String freeReg = this.newReg(); - IRValue freeDst = IRValue.reg(freeReg, IRType.VOID); - this.emit(IRInstr.syscall(freeDst, 11, munmapArgs)); - } else { - String phReg = this.newReg(); - IRValue phDst = IRValue.reg(phReg, IRType.I64); - List phArgs = List(); - this.emit(IRInstr.call(phDst, "__ext__GetProcessHeap", phArgs)); - List hfArgs = List(); - hfArgs.append(phDst); - hfArgs.append(IRValue.ofInt(0, IRType.I64)); - hfArgs.append(objPtr); - String freeReg = this.newReg(); - IRValue freeDst = IRValue.reg(freeReg, IRType.VOID); - this.emit(IRInstr.call(freeDst, "__ext__HeapFree", hfArgs)); - } - } - - private emitFieldPtr(objVal: IRValue, offset: Integer) : IRValue { - String pReg = this.newReg(); - IRValue pDst = IRValue.reg(pReg, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, pDst, objVal, IRValue.ofInt(offset, IRType.I64))); - return pDst; - } - - private emitFieldLoad(objVal: IRValue, offset: Integer) : IRValue { - IRValue ptr = this.emitFieldPtr(objVal, offset); - String vReg = this.newReg(); - IRValue vDst = IRValue.reg(vReg, IRType.I64); - this.emit(IRInstr.load(vDst, ptr, IRType.I64)); - return vDst; - } - - private emitFieldStore(objVal: IRValue, offset: Integer, val: IRValue) { - IRValue ptr = this.emitFieldPtr(objVal, offset); - this.emit(IRInstr.store(val, ptr, IRType.I64)); - } - - // ===== ARC retain helper ===== - - private emitRetain(objVal: IRValue, clsIdx: Integer) { - // Null guard: retain(null) is no-op (prevents SIGSEGV when field param is null) - String nullSkipLbl = this.newLabel("ret_null_skip"); - this.emit(IRInstr.cmp(objVal, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.branch(IROpcode.JE, nullSkipLbl)); - - // Inline retain: inc [obj + refCountOff] - // Zero CALL — pure IR instructions to avoid S1 linear-scan multi-call bug - IRValue curRef = this.emitFieldLoad(objVal, this.refCountOff(clsIdx)); - String nr = this.newReg(); - IRValue newRef = IRValue.reg(nr, IRType.I64); - this.emit(IRInstr.binop(IROpcode.ADD, newRef, curRef, IRValue.ofInt(1, IRType.I64))); - this.emitFieldStore(objVal, this.refCountOff(clsIdx), newRef); - - this.emit(IRInstr.label(nullSkipLbl)); - } - - // ===== Cached teardown routine generation ===== - - private needsTeardown(clsIdx: Integer) : Boolean { - Integer currIdx = clsIdx; - while (currIdx >= 0) { - Integer start = this.classFldStarts.get(currIdx) as Integer; - Integer count = this.classFldCounts.get(currIdx) as Integer; - Integer fi = 0; - while (fi < count) { - String fldCls = this.allFieldClasses.get(start + fi) as String; - if (this.classIdxOf(fldCls) >= 0) { return true; } - fi = fi + 1; - } - currIdx = this.classParents.get(currIdx) as Integer; - } - return false; - } - - private emitTeardownBody(clsIdx: Integer) { - // obj parameter = first param (RDI on Linux, RCX on Windows) - IRValue objParam = IRValue.reg("obj", IRType.PTR); - - Integer currIdx = clsIdx; - while (currIdx >= 0) { - Integer start = this.classFldStarts.get(currIdx) as Integer; - Integer count = this.classFldCounts.get(currIdx) as Integer; - Integer fi = count; - while (fi > 0) { - fi = fi - 1; - String fldCls = this.allFieldClasses.get(start + fi) as String; - Integer fldClsIdx = this.classIdxOf(fldCls); - if (fldClsIdx >= 0) { - // Load field value - IRValue fldVal = this.emitFieldLoad(objParam, (fi + 1) * 8); - - // Null guard for field - String fldSkipLbl = this.newLabel("td_fld_skip"); - this.emit(IRInstr.cmp(fldVal, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.branch(IROpcode.JE, fldSkipLbl)); - - // Decrement field refcount - IRValue fldRC = this.emitFieldLoad(fldVal, this.refCountOff(fldClsIdx)); - String nr = this.newReg(); - IRValue newRC = IRValue.reg(nr, IRType.I64); - this.emit(IRInstr.binop(IROpcode.SUB, newRC, fldRC, IRValue.ofInt(1, IRType.I64))); - this.emitFieldStore(fldVal, this.refCountOff(fldClsIdx), newRC); - - // Conditional free: only when field refcount reaches 0 - this.emit(IRInstr.cmp(newRC, IRValue.ofInt(0, IRType.I64))); - String freeSkipLbl = this.newLabel("td_free_skip"); - this.emit(IRInstr.branch(IROpcode.JNE, freeSkipLbl)); - - // CALL child teardown (may be same class → recursive, safe via rc guard) - String childTd = this.teardownLabels.get(fldClsIdx) as String; - if (childTd != "") { - List args = List(); - args.append(fldVal); - String dr = this.newReg(); - this.emit(IRInstr.call(IRValue.reg(dr, IRType.I64), childTd, args)); - } - - // Heap free child - this.emitHeapFree(fldVal, fldClsIdx); - this.emit(IRInstr.label(freeSkipLbl)); - this.emit(IRInstr.label(fldSkipLbl)); - } - } - currIdx = this.classParents.get(currIdx) as Integer; - } - - this.emit(IRInstr.retVoid()); - } - - private generateTeardownRoutines() { - Integer i = 0; - while (i < this.classNames.length()) { - if (this.needsTeardown(i)) { - String label = "__arimo_td_${i}"; - this.teardownLabels.set(i, label); - this.beginFn(label, IRType.VOID); - this.addParamToLast("obj", IRType.PTR); - this.emitTeardownBody(i); - } - i = i + 1; - } - } - - // ===== ARC release helper ===== - - private emitRelease(objVal: IRValue, clsIdx: Integer) { - // Null guard: release(null) is no-op (prevents SIGSEGV on x=null → scope exit) - String nullSkipLbl = this.newLabel("rel_null_skip"); - this.emit(IRInstr.cmp(objVal, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.branch(IROpcode.JE, nullSkipLbl)); - - // Inline release: dec [obj + refCountOff] - IRValue curRef = this.emitFieldLoad(objVal, this.refCountOff(clsIdx)); - String nr = this.newReg(); - IRValue newRef = IRValue.reg(nr, IRType.I64); - this.emit(IRInstr.binop(IROpcode.SUB, newRef, curRef, IRValue.ofInt(1, IRType.I64))); - this.emitFieldStore(objVal, this.refCountOff(clsIdx), newRef); - - // Conditional free: only when refcount reaches 0 - this.emit(IRInstr.cmp(newRef, IRValue.ofInt(0, IRType.I64))); - String freeSkipLbl = this.newLabel("rel_free_skip"); - this.emit(IRInstr.branch(IROpcode.JNE, freeSkipLbl)); - - // Cached teardown routine: field teardown generated once per class, - // called via runtime CALL (replaces exponential inline expansion). - String tdLabel = this.teardownLabels.get(clsIdx) as String; - if (tdLabel != "") { - List tdArgs = List(); - tdArgs.append(objVal); - String tdReg = this.newReg(); - this.emit(IRInstr.call(IRValue.reg(tdReg, IRType.I64), tdLabel, tdArgs)); - } - - this.emitHeapFree(objVal, clsIdx); - this.emit(IRInstr.label(freeSkipLbl)); - - this.emit(IRInstr.label(nullSkipLbl)); - } - - // ===== Condition lowering ===== - - private lowerCond(expr: Expr) : Integer { - if (expr.kind == ExprKind.BINOP) { - Integer bop = expr.op; - if (bop == BinaryOp.EQ || bop == BinaryOp.NE || - bop == BinaryOp.LT || bop == BinaryOp.LE || - bop == BinaryOp.GT || bop == BinaryOp.GE) { - IRValue lv = this.lowerExpr(expr.left); - IRValue rv = this.lowerExpr(expr.right); - this.emit(IRInstr.cmp(lv, rv)); - if (bop == BinaryOp.EQ) { return IROpcode.JNE; } - if (bop == BinaryOp.NE) { return IROpcode.JE; } - if (bop == BinaryOp.LT) { return IROpcode.JGE; } - if (bop == BinaryOp.LE) { return IROpcode.JG; } - if (bop == BinaryOp.GT) { return IROpcode.JLE; } - return IROpcode.JL; - } - } - IRValue cv = this.lowerExpr(expr); - this.emit(IRInstr.cmp(cv, IRValue.ofInt(0, IRType.I64))); - return IROpcode.JE; - } - - // ===== Statement lowering ===== - - private needsReturnRetain(expr: Expr) : Boolean { - if (expr.kind == ExprKind.IDENT) { return true; } - if (expr.kind == ExprKind.FIELD) { return true; } - // TODO: extend to INDEX/DEREF when indexed/deref field access is lowered - return false; - } - - private lowerStmt(stmt: Stmt) { - if (stmt.kind == StmtKind.RETURN) { - if (stmt.expr.kind != ExprKind.NULL_LIT) { - IRValue rv = this.lowerExpr(stmt.expr); - // ARC: retain ref-type return value before scope release - if (this.needsReturnRetain(stmt.expr)) { - String retCls = this.inferClass(stmt.expr); - Integer retClsIdx = this.classIdxOf(retCls); - if (retClsIdx >= 0) { this.emitRetain(rv, retClsIdx); } - } - // Full scope unwind - this.emitUnwindToDepth(0); - this.emit(IRInstr.ret(rv)); - } else { - this.emitUnwindToDepth(0); - this.emit(IRInstr.retVoid()); - } - return; - } - - if (stmt.kind == StmtKind.EXPR) { - this.lowerExpr(stmt.expr); - return; - } - - if (stmt.kind == StmtKind.VAR_DECL) { - String reg = this.newReg(); - IRValue dst = IRValue.reg(reg, IRType.I64); - String varCls = this.tyToClass(stmt.ty); - if (stmt.initExpr.kind != ExprKind.NULL_LIT) { - IRValue src = this.lowerExpr(stmt.initExpr); - // ARC: retain ref-type initializers (let c = a) - if (stmt.initExpr.kind == ExprKind.IDENT) { - String initCls = this.inferClass(stmt.initExpr); - Integer initClsIdx = this.classIdxOf(initCls); - if (initClsIdx >= 0) { this.emitRetain(src, initClsIdx); } - } - this.emit(IRInstr.mov(dst, src)); - if (varCls == "") { varCls = this.inferClass(stmt.initExpr); } - } else { - this.emit(IRInstr.mov(dst, IRValue.ofInt(0, IRType.I64))); - } - this.varDefine(stmt.name, reg); - if (varCls != "") { this.varSetClass(stmt.name, varCls); } - return; - } - - if (stmt.kind == StmtKind.IF) { - String endL = this.newLabel("if_end"); - String nextL = this.newLabel("if_else"); - Integer jmpOp = this.lowerCond(stmt.cond); - this.emit(IRInstr.branch(jmpOp, nextL)); - this.lowerBody(stmt.thenBody); - this.emit(IRInstr.jmp(endL)); - this.emit(IRInstr.label(nextL)); - Integer ei = 0; - while (ei < stmt.elseIfs.length()) { - ElseIfBranch eib = stmt.elseIfs.get(ei) as ElseIfBranch; - String afterL = this.newLabel("if_else"); - Integer jopEI = this.lowerCond(eib.cond); - this.emit(IRInstr.branch(jopEI, afterL)); - this.lowerBody(eib.body); - this.emit(IRInstr.jmp(endL)); - this.emit(IRInstr.label(afterL)); - ei = ei + 1; - } - if (stmt.elseBody != null && stmt.elseBody.length() > 0) { - this.lowerBody(stmt.elseBody); - } - this.emit(IRInstr.label(endL)); - return; - } - - if (stmt.kind == StmtKind.WHILE) { - String loopL = this.newLabel("while_loop"); - String breakL = this.newLabel("while_brk"); - String savedBreak = this.breakLbl; - String savedCont = this.contLbl; - Integer savedBreakDepth = this.breakDepth; - Integer savedContDepth = this.contDepth; - this.breakLbl = breakL; - this.contLbl = loopL; - this.breakDepth = this.scopeStack.length(); - this.contDepth = this.scopeStack.length(); - this.emit(IRInstr.label(loopL)); - Integer jmpOp = this.lowerCond(stmt.cond); - this.emit(IRInstr.branch(jmpOp, breakL)); - this.lowerBody(stmt.body); - this.emit(IRInstr.jmp(loopL)); - this.emit(IRInstr.label(breakL)); - this.breakLbl = savedBreak; - this.contLbl = savedCont; - this.breakDepth = savedBreakDepth; - this.contDepth = savedContDepth; - return; - } - - if (stmt.kind == StmtKind.FOR) { - if (stmt.forInit != null) { this.lowerStmt(stmt.forInit); } - String loopL = this.newLabel("for_loop"); - String breakL = this.newLabel("for_brk"); - String contL = this.newLabel("for_upd"); - String savedBreak = this.breakLbl; - String savedCont = this.contLbl; - Integer savedBreakDepth = this.breakDepth; - Integer savedContDepth = this.contDepth; - this.breakLbl = breakL; - this.contLbl = contL; - this.breakDepth = this.scopeStack.length(); - this.contDepth = this.scopeStack.length(); - this.emit(IRInstr.label(loopL)); - if (stmt.forCond != null) { - Integer jop = this.lowerCond(stmt.forCond); - this.emit(IRInstr.branch(jop, breakL)); - } - this.lowerBody(stmt.forBody); - this.emit(IRInstr.label(contL)); - if (stmt.forStep != null) { this.lowerExpr(stmt.forStep); } - this.emit(IRInstr.jmp(loopL)); - this.emit(IRInstr.label(breakL)); - this.breakLbl = savedBreak; - this.contLbl = savedCont; - this.breakDepth = savedBreakDepth; - this.contDepth = savedContDepth; - return; - } - - if (stmt.kind == StmtKind.FOR_EACH) { - IRValue iterVal = this.lowerExpr(stmt.iterable); - String iReg = this.newReg(); - IRValue iDst = IRValue.reg(iReg, IRType.I64); - this.emit(IRInstr.mov(iDst, IRValue.ofInt(0, IRType.I64))); - this.varDefine("__fe_i_${iReg}", iReg); - String loopL = this.newLabel("fe_loop"); - String breakL = this.newLabel("fe_brk"); - String savedBreak = this.breakLbl; - String savedCont = this.contLbl; - Integer savedBreakDepth = this.breakDepth; - Integer savedContDepth = this.contDepth; - this.breakLbl = breakL; - this.contLbl = loopL; - this.breakDepth = this.scopeStack.length(); - this.contDepth = this.scopeStack.length(); - this.emit(IRInstr.label(loopL)); - String lenReg = this.newReg(); - IRValue lenDst = IRValue.reg(lenReg, IRType.I64); - List lenArgs = List(); - lenArgs.append(iterVal); - this.emit(IRInstr.call(lenDst, "__arimo_list_length", lenArgs)); - this.emit(IRInstr.cmp(iDst, lenDst)); - this.emit(IRInstr.branch(IROpcode.JGE, breakL)); - String itemReg = this.newReg(); - IRValue itemDst = IRValue.reg(itemReg, IRType.I64); - List getArgs = List(); - getArgs.append(iterVal); - getArgs.append(iDst); - this.emit(IRInstr.call(itemDst, "__arimo_list_get", getArgs)); - this.varDefine(stmt.iterName, itemReg); - String iterCls = this.tyToClass(stmt.iterTy); - if (iterCls != "") { this.varSetClass(stmt.iterName, iterCls); } - this.lowerBody(stmt.body); - this.emit(IRInstr.binop(IROpcode.ADD, iDst, iDst, IRValue.ofInt(1, IRType.I64))); - this.emit(IRInstr.jmp(loopL)); - this.emit(IRInstr.label(breakL)); - this.breakLbl = savedBreak; - this.contLbl = savedCont; - this.breakDepth = savedBreakDepth; - this.contDepth = savedContDepth; - return; - } - - if (stmt.kind == StmtKind.BREAK) { - this.emitUnwindToDepth(this.breakDepth); - this.emit(IRInstr.jmp(this.breakLbl)); - return; - } - if (stmt.kind == StmtKind.CONTINUE) { - this.emitUnwindToDepth(this.contDepth); - this.emit(IRInstr.jmp(this.contLbl)); - return; - } - if (stmt.kind == StmtKind.BLOCK) { this.pushScope(); this.lowerBody(stmt.body); this.popScope(); return; } - if (stmt.kind == StmtKind.THROW) { this.lowerExpr(stmt.expr); return; } - if (stmt.kind == StmtKind.TRY) { this.lowerBody(stmt.tryBody); return; } - } - - private lowerBody(stmts: List) { - Integer i = 0; - while (i < stmts.length()) { - Stmt s = stmts.get(i) as Stmt; - this.lowerStmt(s); - i = i + 1; - } - } - - // ===== Expression lowering ===== - - private lowerExpr(expr: Expr) : IRValue { - if (expr.kind == ExprKind.INT_LIT) { return IRValue.ofInt(expr.intVal, IRType.I64); } - if (expr.kind == ExprKind.CHAR_LIT) { return IRValue.ofInt(expr.intVal, IRType.I64); } - - if (expr.kind == ExprKind.BOOL_LIT) { - if (expr.boolVal) { return IRValue.ofInt(1, IRType.I64); } - return IRValue.ofInt(0, IRType.I64); - } - - if (expr.kind == ExprKind.NULL_LIT) { return IRValue.ofInt(0, IRType.I64); } - - if (expr.kind == ExprKind.STR_LIT) { - String name = this.internStr(expr.strVal); - return IRValue.global(name); - } - - if (expr.kind == ExprKind.IDENT) { - String reg = this.varLookup(expr.strVal); - if (reg != "") { return IRValue.reg(reg, IRType.I64); } - return IRValue.ofInt(0, IRType.I64); - } - - if (expr.kind == ExprKind.THIS) { - if (this.thisReg != "") { return IRValue.reg(this.thisReg, IRType.PTR); } - return IRValue.ofInt(0, IRType.I64); - } - - if (expr.kind == ExprKind.FIELD) { - IRValue objVal = this.lowerExpr(expr.object); - String objCls = this.inferClass(expr.object); - Integer clsIdx = this.classIdxOf(objCls); - if (clsIdx >= 0) { - Integer fldIdx = this.fieldIdxOf(clsIdx, expr.field); - if (fldIdx >= 0) { return this.emitFieldLoad(objVal, fldIdx * 8); } - } - return this.emitFieldLoad(objVal, 0); - } - - if (expr.kind == ExprKind.NULL_SAFE) { - if (expr.args == null || expr.args.length() == 0) { - IRValue objVal = this.lowerExpr(expr.object); - String objCls = this.inferClass(expr.object); - Integer clsIdx = this.classIdxOf(objCls); - if (clsIdx >= 0) { - Integer fldIdx = this.fieldIdxOf(clsIdx, expr.field); - if (fldIdx >= 0) { return this.emitFieldLoad(objVal, fldIdx * 8); } - } - return this.emitFieldLoad(objVal, 0); - } - return this.lowerMethodCall(expr.object, expr.field, expr.args); - } - - if (expr.kind == ExprKind.CTOR) { - return this.lowerCtor(expr.class_, expr.args); - } - - if (expr.kind == ExprKind.STATIC_CALL) { - return this.lowerStaticCall(expr.class_, expr.method, expr.args); - } - - if (expr.kind == ExprKind.METHOD) { - return this.lowerMethodCall(expr.object, expr.method, expr.args); - } - - if (expr.kind == ExprKind.BINOP) { - return this.lowerBinop(expr); - } - - if (expr.kind == ExprKind.UNARY) { - IRValue operand = this.lowerExpr(expr.operand); - String dr = this.newReg(); - IRValue dst = IRValue.reg(dr, IRType.I64); - if (expr.op == 1) { this.emit(IRInstr.unop(IROpcode.NEG, dst, operand)); } - else if (expr.op == 2) { this.emit(IRInstr.unop(IROpcode.NOT, dst, operand)); } - else if (expr.op == 4 || expr.op == 5 || expr.op == 6 || expr.op == 7) { - if (expr.operand.kind == ExprKind.IDENT) { - String varName = expr.operand.strVal; - String oldReg = this.varLookup(varName); - if (oldReg != "") { - IRValue inPlace = IRValue.reg(oldReg, IRType.I64); - if (expr.op == 6 || expr.op == 7) { - // POST: copy old value to dst, then update oldReg in-place - this.emit(IRInstr.mov(dst, inPlace)); - if (expr.op == 6) { this.emit(IRInstr.binop(IROpcode.ADD, inPlace, inPlace, IRValue.ofInt(1, IRType.I64))); } - else { this.emit(IRInstr.binop(IROpcode.SUB, inPlace, inPlace, IRValue.ofInt(1, IRType.I64))); } - } else { - // PRE: update oldReg in-place, return it (now holds new value) - if (expr.op == 4) { this.emit(IRInstr.binop(IROpcode.ADD, inPlace, inPlace, IRValue.ofInt(1, IRType.I64))); } - else { this.emit(IRInstr.binop(IROpcode.SUB, inPlace, inPlace, IRValue.ofInt(1, IRType.I64))); } - return inPlace; - } - } else { - this.emit(IRInstr.mov(dst, operand)); - } - } else { - this.emit(IRInstr.mov(dst, operand)); - } - } - else { this.emit(IRInstr.mov(dst, operand)); } - return dst; - } - - if (expr.kind == ExprKind.CAST) { - IRValue src = this.lowerExpr(expr.operand); - String dr = this.newReg(); - IRValue dst = IRValue.reg(dr, IRType.I64); - this.emit(IRInstr.mov(dst, src)); - return dst; - } - - if (expr.kind == ExprKind.TERNARY) { - String falseL = this.newLabel("tern_f"); - String endL = this.newLabel("tern_e"); - String resReg = this.newReg(); - IRValue resDst = IRValue.reg(resReg, IRType.I64); - Integer jop = this.lowerCond(expr.cond); - this.emit(IRInstr.branch(jop, falseL)); - IRValue tv = this.lowerExpr(expr.then_); - this.emit(IRInstr.mov(resDst, tv)); - this.emit(IRInstr.jmp(endL)); - this.emit(IRInstr.label(falseL)); - IRValue fv = this.lowerExpr(expr.else_); - this.emit(IRInstr.mov(resDst, fv)); - this.emit(IRInstr.label(endL)); - return resDst; - } - - if (expr.kind == ExprKind.NULL_COALESCE) { - String notNullL = this.newLabel("nn_ok"); - String endL = this.newLabel("nn_end"); - String resReg = this.newReg(); - IRValue resDst = IRValue.reg(resReg, IRType.I64); - IRValue lv = this.lowerExpr(expr.left); - this.emit(IRInstr.mov(resDst, lv)); - this.emit(IRInstr.cmp(lv, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.branch(IROpcode.JNE, notNullL)); - IRValue rv = this.lowerExpr(expr.right); - this.emit(IRInstr.mov(resDst, rv)); - this.emit(IRInstr.jmp(endL)); - this.emit(IRInstr.label(notNullL)); - this.emit(IRInstr.label(endL)); - return resDst; - } - - if (expr.kind == ExprKind.STR_INTERP) { - String emptyName = this.internStr(""); - IRValue result = IRValue.global(emptyName); - Integer pi = 0; - while (pi < expr.parts.length()) { - StrPart part = expr.parts.get(pi) as StrPart; - IRValue partVal = IRValue.ofInt(0, IRType.I64); - if (part.isLit) { - String sn = this.internStr(part.lit); - partVal = IRValue.global(sn); - } else { - IRValue rawVal = this.lowerExpr(part.expr); - if (this.isIntegerExpr(part.expr)) { - String toStrReg = this.newReg(); - IRValue toStrDst = IRValue.reg(toStrReg, IRType.PTR); - List tsArgs = List(); - tsArgs.append(rawVal); - this.emit(IRInstr.call(toStrDst, "__arimo_i64_to_str", tsArgs)); - partVal = toStrDst; - } else { - partVal = rawVal; - } - } - String catReg = this.newReg(); - IRValue catDst = IRValue.reg(catReg, IRType.PTR); - List catArgs = List(); - catArgs.append(result); - catArgs.append(partVal); - this.emit(IRInstr.call(catDst, "__arimo_strcat", catArgs)); - result = catDst; - pi = pi + 1; - } - return result; - } - - return IRValue.ofInt(0, IRType.I64); - } - - // ===== Binary op lowering ===== - - private lowerBinop(expr: Expr) : IRValue { - Integer bop = expr.op; - - if (bop == BinaryOp.ASSIGN) { - if (expr.left.kind == ExprKind.IDENT) { - String vReg = this.varLookup(expr.left.strVal); - if (vReg != "") { - IRValue rv = this.lowerExpr(expr.right); - // ARC: retain new value if ref-type variable - if (expr.right.kind == ExprKind.IDENT) { - String rhsCls = this.inferClass(expr.right); - Integer rhsClsIdx = this.classIdxOf(rhsCls); - if (rhsClsIdx >= 0) { this.emitRetain(rv, rhsClsIdx); } - } - // ARC: release old value before overwrite - String oldCls = this.varClassOf(expr.left.strVal); - Integer oldClsIdx = this.classIdxOf(oldCls); - if (oldClsIdx >= 0) { - this.emitRelease(IRValue.reg(vReg, IRType.I64), oldClsIdx); - } - IRValue dst = IRValue.reg(vReg, IRType.I64); - this.emit(IRInstr.mov(dst, rv)); - return dst; - } - } - if (expr.left.kind == ExprKind.FIELD) { - String objCls = this.inferClass(expr.left.object); - Integer clsIdx = this.classIdxOf(objCls); - IRValue objVal = this.lowerExpr(expr.left.object); - Integer fldOff = 0; - Integer fldIdx = -1; - if (clsIdx >= 0) { - fldIdx = this.fieldIdxOf(clsIdx, expr.left.field); - if (fldIdx >= 0) { fldOff = fldIdx * 8; } - } - IRValue rv2 = this.lowerExpr(expr.right); - // ARC: retain new value if ref-type variable (mirrors IDENT case) - if (expr.right.kind == ExprKind.IDENT) { - String rhsCls = this.inferClass(expr.right); - Integer rhsClsIdx = this.classIdxOf(rhsCls); - if (rhsClsIdx >= 0) { this.emitRetain(rv2, rhsClsIdx); } - } - // ARC: release old field value before overwrite - if (clsIdx >= 0 && fldIdx >= 0) { - Integer start = this.classFldStarts.get(clsIdx) as Integer; - String oldFldCls = this.allFieldClasses.get(start + fldIdx - 1) as String; - Integer oldFldClsIdx = this.classIdxOf(oldFldCls); - if (oldFldClsIdx >= 0) { - IRValue oldVal = this.emitFieldLoad(objVal, fldOff); - this.emitRelease(oldVal, oldFldClsIdx); - } - } - this.emitFieldStore(objVal, fldOff, rv2); - return IRValue.ofInt(0, IRType.I64); - } - IRValue rv3 = this.lowerExpr(expr.right); - return rv3; - } - - if (bop == BinaryOp.AND || bop == BinaryOp.OR) { - IRValue lv = this.lowerExpr(expr.left); - IRValue rv = this.lowerExpr(expr.right); - String dr = this.newReg(); - IRValue dst = IRValue.reg(dr, IRType.I64); - if (bop == BinaryOp.AND) { this.emit(IRInstr.binop(IROpcode.AND, dst, lv, rv)); } - else { this.emit(IRInstr.binop(IROpcode.OR, dst, lv, rv)); } - return dst; - } - - if (bop == BinaryOp.EQ || bop == BinaryOp.NE || - bop == BinaryOp.LT || bop == BinaryOp.LE || - bop == BinaryOp.GT || bop == BinaryOp.GE) { - IRValue lv = this.lowerExpr(expr.left); - IRValue rv = this.lowerExpr(expr.right); - String trueL = this.newLabel("cmp_t"); - String endL = this.newLabel("cmp_e"); - String dr = this.newReg(); - IRValue dst = IRValue.reg(dr, IRType.I64); - this.emit(IRInstr.cmp(lv, rv)); - Integer trueJmp = IROpcode.JE; - if (bop == BinaryOp.EQ) { trueJmp = IROpcode.JE; } - if (bop == BinaryOp.NE) { trueJmp = IROpcode.JNE; } - if (bop == BinaryOp.LT) { trueJmp = IROpcode.JL; } - if (bop == BinaryOp.LE) { trueJmp = IROpcode.JLE; } - if (bop == BinaryOp.GT) { trueJmp = IROpcode.JG; } - if (bop == BinaryOp.GE) { trueJmp = IROpcode.JGE; } - this.emit(IRInstr.branch(trueJmp, trueL)); - this.emit(IRInstr.mov(dst, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.jmp(endL)); - this.emit(IRInstr.label(trueL)); - this.emit(IRInstr.mov(dst, IRValue.ofInt(1, IRType.I64))); - this.emit(IRInstr.label(endL)); - return dst; - } - - Integer irop = IROpcode.ADD; - if (bop == BinaryOp.ADD) { irop = IROpcode.ADD; } - if (bop == BinaryOp.SUB) { irop = IROpcode.SUB; } - if (bop == BinaryOp.MUL) { irop = IROpcode.MUL; } - if (bop == BinaryOp.DIV) { irop = IROpcode.DIV; } - if (bop == BinaryOp.MOD) { irop = IROpcode.MOD; } - if (bop == BinaryOp.BITAND) { irop = IROpcode.AND; } - if (bop == BinaryOp.BITOR) { irop = IROpcode.OR; } - if (bop == BinaryOp.XOR) { irop = IROpcode.XOR; } - if (bop == BinaryOp.SHL) { irop = IROpcode.SHL; } - if (bop == BinaryOp.SHR) { irop = IROpcode.SHR; } - - IRValue lv2 = this.lowerExpr(expr.left); - IRValue rv2 = this.lowerExpr(expr.right); - String dr2 = this.newReg(); - IRValue dst2 = IRValue.reg(dr2, IRType.I64); - this.emit(IRInstr.binop(irop, dst2, lv2, rv2)); - return dst2; - } - - // ===== Static call lowering ===== - - private lowerStaticCall(class_: String, method: String, args: List) : IRValue { - if (class_ == "IO" && method == "println") { - Expr argExpr = args.get(0) as Expr; - Boolean isStr = this.isStringExpr(argExpr); - if (argExpr.kind == ExprKind.METHOD) { - if (argExpr.object.kind == ExprKind.IDENT && argExpr.object.strVal == "Env") { isStr = true; } - } - if (argExpr.kind == ExprKind.STATIC_CALL) { - if (argExpr.class_ == "Env") { isStr = true; } - } - if (isStr) { - IRValue strArg = this.lowerExpr(argExpr); - List callArgs = List(); - callArgs.append(strArg); - String dr = this.newReg(); - IRValue dst = IRValue.reg(dr, IRType.VOID); - this.emit(IRInstr.call(dst, "__arimo_println", callArgs)); - } else { - IRValue rawVal = this.lowerExpr(argExpr); - List intArgs = List(); - intArgs.append(rawVal); - String dr = this.newReg(); - IRValue dst = IRValue.reg(dr, IRType.VOID); - this.emit(IRInstr.call(dst, "__arimo_println_int", intArgs)); - } - return IRValue.none(); - } - if (class_ == "IO" && method == "print") { - Expr argExpr2 = args.get(0) as Expr; - Boolean isStr2 = this.isStringExpr(argExpr2); - if (argExpr2.kind == ExprKind.METHOD) { - if (argExpr2.object.kind == ExprKind.IDENT && argExpr2.object.strVal == "Env") { isStr2 = true; } - } - if (isStr2) { - IRValue strArg2 = this.lowerExpr(argExpr2); - List callArgs2 = List(); - callArgs2.append(strArg2); - String dr2 = this.newReg(); - IRValue dst2 = IRValue.reg(dr2, IRType.VOID); - this.emit(IRInstr.call(dst2, "__arimo_print", callArgs2)); - } else { - IRValue rawVal2 = this.lowerExpr(argExpr2); - List intArgs2 = List(); - intArgs2.append(rawVal2); - String dr2 = this.newReg(); - IRValue dst2 = IRValue.reg(dr2, IRType.VOID); - this.emit(IRInstr.call(dst2, "__arimo_print_int", intArgs2)); - } - return IRValue.none(); - } - - // Env built-ins - if (class_ == "Env" && method == "platform") { - String platform = "linux"; - if (!this.linux) { platform = "windows"; } - return IRValue.global(this.internStr(platform)); - } - if (class_ == "Env" && method == "exit") { - Integer code = 0; - if (args.length() > 0) { - Expr e = args.get(0) as Expr; - if (e.kind == ExprKind.INT_LIT) { code = e.intVal; } - } - if (this.linux) { - List scArgs = List(); - scArgs.append(IRValue.ofInt(code, IRType.I64)); - this.emit(IRInstr.syscall(IRValue.none(), 60, scArgs)); - } else { - List ea = List(); - ea.append(IRValue.ofInt(code, IRType.I64)); - String er = this.newReg(); - this.emit(IRInstr.call(IRValue.reg(er, IRType.VOID), "__ext__ExitProcess", ea)); - } - return IRValue.none(); - } - if (class_ == "Env" && method == "args") { - // Stub: return empty List. Full argc/argv startup ABI → V1.1. - List la = List(); - String lr = this.newReg(); - this.emit(IRInstr.call(IRValue.reg(lr, IRType.PTR), "__arimo_list_new", la)); - return IRValue.reg(lr, IRType.PTR); - } - if (class_ == "Env" && method == "exePath") { - // Stub: return empty string. Full exePath → V1.1. - return IRValue.global(this.internStr("")); - } - // Generic static call fallback - String fnName = "${class_}__${method}"; - List callArgsFn = List(); - Integer ai = 0; - while (ai < args.length()) { - Expr arg = args.get(ai) as Expr; - callArgsFn.append(this.lowerExpr(arg)); - ai = ai + 1; - } - String dr3 = this.newReg(); - IRValue dst3 = IRValue.reg(dr3, IRType.I64); - this.emit(IRInstr.call(dst3, fnName, callArgsFn)); - return dst3; - } - - // ===== Constructor lowering ===== - - private lowerLinuxLibcCall(name: String, args: List) : IRValue { - // fopen(path, mode) → open(path, flags, 0o666=438) syscall - if (name == "fopen") { - IRValue pathVal = this.lowerExpr(args.get(0) as Expr); - IRValue modeVal = this.lowerExpr(args.get(1) as Expr); - String mbyteR = this.newReg(); - IRValue mbyte = IRValue.reg(mbyteR, IRType.I64); - this.emit(IRInstr.load(mbyte, modeVal, IRType.I8)); - String flagsR = this.newReg(); - IRValue flags = IRValue.reg(flagsR, IRType.I64); - String notRLbl = this.newLabel("fo_nr"); - String notALbl = this.newLabel("fo_na"); - String openLbl = this.newLabel("fo_op"); - this.emit(IRInstr.cmp(mbyte, IRValue.ofInt(114, IRType.I64))); - this.emit(IRInstr.branch(IROpcode.JNE, notRLbl)); - this.emit(IRInstr.mov(flags, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.jmp(openLbl)); - this.emit(IRInstr.label(notRLbl)); - this.emit(IRInstr.cmp(mbyte, IRValue.ofInt(97, IRType.I64))); - this.emit(IRInstr.branch(IROpcode.JNE, notALbl)); - this.emit(IRInstr.mov(flags, IRValue.ofInt(1089, IRType.I64))); - this.emit(IRInstr.jmp(openLbl)); - this.emit(IRInstr.label(notALbl)); - this.emit(IRInstr.mov(flags, IRValue.ofInt(577, IRType.I64))); - this.emit(IRInstr.label(openLbl)); - String fdR = this.newReg(); - IRValue fd = IRValue.reg(fdR, IRType.I64); - List openArgs = List(); - openArgs.append(pathVal); - openArgs.append(flags); - openArgs.append(IRValue.ofInt(438, IRType.I64)); - this.emit(IRInstr.syscall(fd, 2, openArgs)); - String retR = this.newReg(); - IRValue ret = IRValue.reg(retR, IRType.I64); - String errLbl = this.newLabel("fo_er"); - String okLbl = this.newLabel("fo_ok"); - this.emit(IRInstr.cmp(fd, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.branch(IROpcode.JL, errLbl)); - this.emit(IRInstr.mov(ret, fd)); - this.emit(IRInstr.jmp(okLbl)); - this.emit(IRInstr.label(errLbl)); - this.emit(IRInstr.mov(ret, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.label(okLbl)); - return ret; - } - // fread(buf, size, n, fd) → read(fd, buf, size*n) syscall 0 - if (name == "fread") { - IRValue bufVal = this.lowerExpr(args.get(0) as Expr); - IRValue sizeVal = this.lowerExpr(args.get(1) as Expr); - IRValue nVal = this.lowerExpr(args.get(2) as Expr); - IRValue fdVal = this.lowerExpr(args.get(3) as Expr); - String totR = this.newReg(); - IRValue total = IRValue.reg(totR, IRType.I64); - this.emit(IRInstr.binop(IROpcode.MUL, total, sizeVal, nVal)); - String resR = this.newReg(); - IRValue result = IRValue.reg(resR, IRType.I64); - List rdArgs = List(); - rdArgs.append(fdVal); - rdArgs.append(bufVal); - rdArgs.append(total); - this.emit(IRInstr.syscall(result, 0, rdArgs)); - return result; - } - // fwrite(buf, size, n, fd) → write(fd, buf, size*n) syscall 1 - if (name == "fwrite") { - IRValue bufVal = this.lowerExpr(args.get(0) as Expr); - IRValue sizeVal = this.lowerExpr(args.get(1) as Expr); - IRValue nVal = this.lowerExpr(args.get(2) as Expr); - IRValue fdVal = this.lowerExpr(args.get(3) as Expr); - String totR = this.newReg(); - IRValue total = IRValue.reg(totR, IRType.I64); - this.emit(IRInstr.binop(IROpcode.MUL, total, sizeVal, nVal)); - String resR = this.newReg(); - IRValue result = IRValue.reg(resR, IRType.I64); - List wrArgs = List(); - wrArgs.append(fdVal); - wrArgs.append(bufVal); - wrArgs.append(total); - this.emit(IRInstr.syscall(result, 1, wrArgs)); - return result; - } - // fclose(fd) → close(fd) syscall 3 - if (name == "fclose") { - IRValue fdVal = this.lowerExpr(args.get(0) as Expr); - String clR = this.newReg(); - IRValue clDst = IRValue.reg(clR, IRType.I64); - List clArgs = List(); - clArgs.append(fdVal); - this.emit(IRInstr.syscall(clDst, 3, clArgs)); - String zR = this.newReg(); - IRValue zero = IRValue.reg(zR, IRType.I64); - this.emit(IRInstr.mov(zero, IRValue.ofInt(0, IRType.I64))); - return zero; - } - // fseek(fd, offset, whence) → lseek(fd, offset, whence) syscall 8 - if (name == "fseek") { - IRValue fdVal = this.lowerExpr(args.get(0) as Expr); - IRValue offsetVal = this.lowerExpr(args.get(1) as Expr); - IRValue whenceVal = this.lowerExpr(args.get(2) as Expr); - String skR = this.newReg(); - IRValue skDst = IRValue.reg(skR, IRType.I64); - List skArgs = List(); - skArgs.append(fdVal); - skArgs.append(offsetVal); - skArgs.append(whenceVal); - this.emit(IRInstr.syscall(skDst, 8, skArgs)); - String zR = this.newReg(); - IRValue zero = IRValue.reg(zR, IRType.I64); - this.emit(IRInstr.mov(zero, IRValue.ofInt(0, IRType.I64))); - return zero; - } - // ftell(fd) → lseek(fd, 0, SEEK_CUR=1) syscall 8 - if (name == "ftell") { - IRValue fdVal = this.lowerExpr(args.get(0) as Expr); - String posR = this.newReg(); - IRValue pos = IRValue.reg(posR, IRType.I64); - List ftArgs = List(); - ftArgs.append(fdVal); - ftArgs.append(IRValue.ofInt(0, IRType.I64)); - ftArgs.append(IRValue.ofInt(1, IRType.I64)); - this.emit(IRInstr.syscall(pos, 8, ftArgs)); - return pos; - } - // remove(path) → unlink(path) syscall 87 - if (name == "remove") { - IRValue pathVal = this.lowerExpr(args.get(0) as Expr); - String rmR = this.newReg(); - IRValue rmDst = IRValue.reg(rmR, IRType.I64); - List rmArgs = List(); - rmArgs.append(pathVal); - this.emit(IRInstr.syscall(rmDst, 87, rmArgs)); - return rmDst; - } - // calloc(n, size) → mmap(NULL, n*size, PROT_RW=3, MAP_PRIVATE|ANON=34, -1, 0) syscall 9 - if (name == "calloc") { - IRValue nVal = this.lowerExpr(args.get(0) as Expr); - IRValue sizeVal = this.lowerExpr(args.get(1) as Expr); - String totR = this.newReg(); - IRValue total = IRValue.reg(totR, IRType.I64); - this.emit(IRInstr.binop(IROpcode.MUL, total, nVal, sizeVal)); - String ptrR = this.newReg(); - IRValue ptr = IRValue.reg(ptrR, IRType.PTR); - List mmArgs = List(); - mmArgs.append(IRValue.ofInt(0, IRType.I64)); - mmArgs.append(total); - mmArgs.append(IRValue.ofInt(3, IRType.I64)); - mmArgs.append(IRValue.ofInt(34, IRType.I64)); - mmArgs.append(IRValue.ofInt(-1, IRType.I64)); - mmArgs.append(IRValue.ofInt(0, IRType.I64)); - this.emit(IRInstr.syscall(ptr, 9, mmArgs)); - return ptr; - } - // fputc(c, fd) → stack-alloc 1 byte, store c, write(fd, buf, 1) syscall 1 - if (name == "fputc") { - IRValue cVal = this.lowerExpr(args.get(0) as Expr); - IRValue fdVal = this.lowerExpr(args.get(1) as Expr); - String bufR = this.newReg(); - IRValue buf = IRValue.reg(bufR, IRType.PTR); - this.emit(IRInstr.alloc(buf, 1)); - this.emit(IRInstr.store(cVal, buf, IRType.I8)); - String wR = this.newReg(); - IRValue wDst = IRValue.reg(wR, IRType.I64); - List wArgs = List(); - wArgs.append(fdVal); - wArgs.append(buf); - wArgs.append(IRValue.ofInt(1, IRType.I64)); - this.emit(IRInstr.syscall(wDst, 1, wArgs)); - return cVal; - } - return IRValue.none(); - } - - private lowerCtor(class_: String, args: List) : IRValue { - if (class_ == "List") { - String lr = this.newReg(); - IRValue lDst = IRValue.reg(lr, IRType.PTR); - List lArgs = List(); - this.emit(IRInstr.call(lDst, "__arimo_list_new", lArgs)); - return lDst; - } - if (this.linux) { - IRValue libcRes = this.lowerLinuxLibcCall(class_, args); - if (!libcRes.isNone()) { return libcRes; } - } - Integer clsIdx = this.classIdxOf(class_); - Integer sz = 16; - if (clsIdx >= 0) { sz = this.classSizeBytes(clsIdx); } - IRValue objPtr = this.emitHeapAlloc(IRValue.ofInt(sz, IRType.I64)); - if (clsIdx >= 0) { - this.emitFieldStore(objPtr, this.refCountOff(clsIdx), IRValue.ofInt(1, IRType.I64)); - } - List initArgs = List(); - initArgs.append(objPtr); - Integer ai = 0; - while (ai < args.length()) { - Expr arg = args.get(ai) as Expr; - initArgs.append(this.lowerExpr(arg)); - ai = ai + 1; - } - String initRes = this.newReg(); - IRValue initDst = IRValue.reg(initRes, IRType.VOID); - this.emit(IRInstr.call(initDst, "${class_}__init", initArgs)); - return objPtr; - } - - // ===== Method call lowering ===== - - private lowerMethodCall(obj: Expr, method: String, args: List) : IRValue { - if (obj.kind == ExprKind.IDENT) { - if (obj.strVal == "IO") { - return this.lowerStaticCall("IO", method, args); - } - if (obj.strVal == "Env") { - return this.lowerStaticCall("Env", method, args); - } - // Static method call on known class: ClassName.method() - if (this.classIdxOf(obj.strVal) >= 0) { - return this.lowerStaticCall(obj.strVal, method, args); - } - } - - IRValue objVal = this.lowerExpr(obj); - String objCls = this.inferClass(obj); - - // List methods - if (objCls == "List") { - return this.lowerListMethod(objVal, method, args); - } - - // toString() handler — must go before String/List dispatch to avoid - // lowerStringMethod/lowerListMethod fallback generating __str_toString - // which is never defined. - if (method == "toString") { - if (objCls == "String" || this.isStringExpr(obj)) { return IRValue.reg(objVal.name, objVal.ty); } - if (objCls == "" || objCls == "Integer") { - String tsReg = this.newReg(); - IRValue tsDst = IRValue.reg(tsReg, IRType.PTR); - List tsArgs = List(); - tsArgs.append(objVal); - this.emit(IRInstr.call(tsDst, "__arimo_i64_to_str", tsArgs)); - return tsDst; - } - // Unknown type: fall through - } - - // String methods - if (objCls == "String") { - return this.lowerStringMethod(objVal, method, args); - } - - // length() on unknown type: prefer list if we can't tell - if (method == "length") { - if (objCls == "") { - String lr = this.newReg(); - IRValue lDst = IRValue.reg(lr, IRType.I64); - List lArgs = List(); - lArgs.append(objVal); - this.emit(IRInstr.call(lDst, "__arimo_strlen", lArgs)); - return lDst; - } - } - // String method heuristic: call on unknown obj with string-like method names - if (method == "compareTo" || method == "concat" || method == "startsWith" || - method == "endsWith" || method == "substring" || method == "indexOf" || - method == "charCodeAt"|| method == "charAt" || method == "isEmpty" || - method == "equals" || method == "toUpperCase"|| method == "toLowerCase") { - return this.lowerStringMethod(objVal, method, args); - } - - // User class method dispatch - if (objCls != "") { - List callArgsCls = List(); - callArgsCls.append(objVal); - Integer ai = 0; - while (ai < args.length()) { - Expr arg = args.get(ai) as Expr; - callArgsCls.append(this.lowerExpr(arg)); - ai = ai + 1; - } - String dr = this.newReg(); - IRValue dst = IRValue.reg(dr, IRType.I64); - this.emit(IRInstr.call(dst, "${objCls}__${method}", callArgsCls)); - return dst; - } - - // Fallback - List fallbackArgs = List(); - fallbackArgs.append(objVal); - Integer ai2 = 0; - while (ai2 < args.length()) { - Expr arg2 = args.get(ai2) as Expr; - fallbackArgs.append(this.lowerExpr(arg2)); - ai2 = ai2 + 1; - } - String dr4 = this.newReg(); - IRValue dst4 = IRValue.reg(dr4, IRType.I64); - this.emit(IRInstr.call(dst4, "__method_${method}", fallbackArgs)); - return dst4; - } - - private lowerListMethod(objVal: IRValue, method: String, args: List) : IRValue { - if (method == "length" || method == "size") { - String dr = this.newReg(); - IRValue dst = IRValue.reg(dr, IRType.I64); - List a = List(); - a.append(objVal); - this.emit(IRInstr.call(dst, "__arimo_list_length", a)); - return dst; - } - if (method == "append" || method == "add") { - IRValue val = this.lowerExpr(args.get(0) as Expr); - List a = List(); - a.append(objVal); - a.append(val); - String dr = this.newReg(); - IRValue dst = IRValue.reg(dr, IRType.VOID); - this.emit(IRInstr.call(dst, "__arimo_list_append", a)); - return IRValue.none(); - } - if (method == "get") { - IRValue idx = this.lowerExpr(args.get(0) as Expr); - List a = List(); - a.append(objVal); - a.append(idx); - String dr = this.newReg(); - IRValue dst = IRValue.reg(dr, IRType.I64); - this.emit(IRInstr.call(dst, "__arimo_list_get", a)); - return dst; - } - if (method == "set") { - IRValue idx = this.lowerExpr(args.get(0) as Expr); - IRValue val = this.lowerExpr(args.get(1) as Expr); - List a = List(); - a.append(objVal); - a.append(idx); - a.append(val); - String dr = this.newReg(); - IRValue dst = IRValue.reg(dr, IRType.VOID); - this.emit(IRInstr.call(dst, "__arimo_list_set", a)); - return IRValue.none(); - } - if (method == "removeAt") { - IRValue idx = this.lowerExpr(args.get(0) as Expr); - List a = List(); - a.append(objVal); - a.append(idx); - String dr = this.newReg(); - IRValue dst = IRValue.reg(dr, IRType.VOID); - this.emit(IRInstr.call(dst, "__arimo_list_remove_at", a)); - return IRValue.none(); - } - // Fallback for other list methods - String dr5 = this.newReg(); - IRValue dst5 = IRValue.reg(dr5, IRType.I64); - List a5 = List(); - a5.append(objVal); - Integer xi = 0; - while (xi < args.length()) { - a5.append(this.lowerExpr(args.get(xi) as Expr)); - xi = xi + 1; - } - this.emit(IRInstr.call(dst5, "__list_${method}", a5)); - return dst5; - } - - private lowerStringMethod(objVal: IRValue, method: String, args: List) : IRValue { - if (method == "length") { - String dr = this.newReg(); - IRValue dst = IRValue.reg(dr, IRType.I64); - List a = List(); - a.append(objVal); - this.emit(IRInstr.call(dst, "__arimo_strlen", a)); - return dst; - } - if (method == "compareTo" || method == "equals") { - IRValue other = this.lowerExpr(args.get(0) as Expr); - List a = List(); - a.append(objVal); - a.append(other); - String dr = this.newReg(); - IRValue dst = IRValue.reg(dr, IRType.I64); - this.emit(IRInstr.call(dst, "__arimo_strcmp", a)); - return dst; - } - if (method == "concat") { - IRValue other = this.lowerExpr(args.get(0) as Expr); - List a = List(); - a.append(objVal); - a.append(other); - String dr = this.newReg(); - IRValue dst = IRValue.reg(dr, IRType.PTR); - this.emit(IRInstr.call(dst, "__arimo_strcat", a)); - return dst; - } - if (method == "startsWith") { - IRValue prefix = this.lowerExpr(args.get(0) as Expr); - List a = List(); - a.append(objVal); - a.append(prefix); - String dr = this.newReg(); - IRValue dst = IRValue.reg(dr, IRType.I64); - this.emit(IRInstr.call(dst, "__arimo_startswith", a)); - return dst; - } - if (method == "endsWith") { - IRValue suffix = this.lowerExpr(args.get(0) as Expr); - List a = List(); - a.append(objVal); - a.append(suffix); - String dr = this.newReg(); - IRValue dst = IRValue.reg(dr, IRType.I64); - this.emit(IRInstr.call(dst, "__arimo_endswith", a)); - return dst; - } - if (method == "substring") { - IRValue from = this.lowerExpr(args.get(0) as Expr); - IRValue to = this.lowerExpr(args.get(1) as Expr); - List a = List(); - a.append(objVal); - a.append(from); - a.append(to); - String dr = this.newReg(); - IRValue dst = IRValue.reg(dr, IRType.PTR); - this.emit(IRInstr.call(dst, "__arimo_substr", a)); - return dst; - } - if (method == "indexOf") { - IRValue sub = this.lowerExpr(args.get(0) as Expr); - IRValue from = IRValue.ofInt(0, IRType.I64); - if (args.length() > 1) { from = this.lowerExpr(args.get(1) as Expr); } - List a = List(); - a.append(objVal); - a.append(sub); - a.append(from); - String dr = this.newReg(); - IRValue dst = IRValue.reg(dr, IRType.I64); - this.emit(IRInstr.call(dst, "__arimo_indexof_from", a)); - return dst; - } - if (method == "charCodeAt") { - IRValue idx = this.lowerExpr(args.get(0) as Expr); - List a = List(); - a.append(objVal); - a.append(idx); - String dr = this.newReg(); - IRValue dst = IRValue.reg(dr, IRType.I64); - this.emit(IRInstr.call(dst, "__arimo_charcodeat", a)); - return dst; - } - if (method == "charAt") { - IRValue idx = this.lowerExpr(args.get(0) as Expr); - List a = List(); - a.append(objVal); - a.append(idx); - String dr = this.newReg(); - IRValue dst = IRValue.reg(dr, IRType.PTR); - this.emit(IRInstr.call(dst, "__arimo_charat", a)); - return dst; - } - if (method == "isEmpty") { - String dr = this.newReg(); - IRValue dst = IRValue.reg(dr, IRType.I64); - List a = List(); - a.append(objVal); - this.emit(IRInstr.call(dst, "__arimo_strlen", a)); - String eqL = this.newLabel("isemp_eq"); - String endL = this.newLabel("isemp_end"); - String res = this.newReg(); - IRValue resDst = IRValue.reg(res, IRType.I64); - this.emit(IRInstr.cmp(dst, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.branch(IROpcode.JE, eqL)); - this.emit(IRInstr.mov(resDst, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.jmp(endL)); - this.emit(IRInstr.label(eqL)); - this.emit(IRInstr.mov(resDst, IRValue.ofInt(1, IRType.I64))); - this.emit(IRInstr.label(endL)); - return resDst; - } - // Fallback - List fa = List(); - fa.append(objVal); - Integer xi = 0; - while (xi < args.length()) { - fa.append(this.lowerExpr(args.get(xi) as Expr)); - xi = xi + 1; - } - String fdr = this.newReg(); - IRValue fdst = IRValue.reg(fdr, IRType.I64); - this.emit(IRInstr.call(fdst, "__str_${method}", fa)); - return fdst; - } - - // ===== Method / constructor body lowering ===== - - private lowerMethod(className: String, md: MethodDecl) { - String fnName = "${className}__${md.name}"; - this.beginFn(fnName, IRType.I64); - this.resetFnContext(); - this.curClass = className; - - if (!md.isStatic) { - this.addParamToLast("__this", IRType.PTR); - this.thisReg = "__this"; - } - Integer pi = 0; - while (pi < md.params.length()) { - Param p = md.params.get(pi) as Param; - this.addParamToLast(p.name, IRType.I64); - pi = pi + 1; - } - - this.emit(IRInstr.label("entry")); - - if (!md.isStatic) { - this.varDefine("__this", "__this"); - this.varSetClass("__this", className); - } - Integer pi2 = 0; - while (pi2 < md.params.length()) { - Param p2 = md.params.get(pi2) as Param; - this.varDefine(p2.name, p2.name); - String pCls = this.tyToClass(p2.ty); - if (pCls != "") { this.varSetClass(p2.name, pCls); } - pi2 = pi2 + 1; - } - - this.lowerBody(md.body); - IRFunction curFn = this.lastFn(); - Boolean needsRet = true; - if (curFn.instrs.length() > 0) { - IRInstr lastInstr = curFn.instrs.get(curFn.instrs.length() - 1) as IRInstr; - if (lastInstr.op == IROpcode.RET) { needsRet = false; } - } - if (needsRet) { - this.emit(IRInstr.ret(IRValue.ofInt(0, IRType.I64))); - } - - if (md.isStatic && md.name == "main" && this.mainFn == "") { - this.mainFn = fnName; - } - } - - private lowerConstructor(className: String, ctor: ConstructorDecl) { - String fnName = "${className}__init"; - this.beginFn(fnName, IRType.VOID); - this.resetFnContext(); - this.curClass = className; - - this.addParamToLast("__this", IRType.PTR); - this.thisReg = "__this"; - Integer pi = 0; - while (pi < ctor.params.length()) { - Param p = ctor.params.get(pi) as Param; - this.addParamToLast(p.name, IRType.I64); - pi = pi + 1; - } - - this.emit(IRInstr.label("entry")); - this.varDefine("__this", "__this"); - this.varSetClass("__this", className); - Integer pi2 = 0; - while (pi2 < ctor.params.length()) { - Param p2 = ctor.params.get(pi2) as Param; - this.varDefine(p2.name, p2.name); - String pCls = this.tyToClass(p2.ty); - if (pCls != "") { this.varSetClass(p2.name, pCls); } - pi2 = pi2 + 1; - } - - this.lowerBody(ctor.body); - this.emit(IRInstr.retVoid()); - } - - private lowerClass(cd: ClassDecl) { - if (cd.ctor != null) { - ConstructorDecl ctor = cd.ctor as ConstructorDecl; - if (ctor.body != null) { - this.lowerConstructor(cd.name, ctor); - } - } - Integer i = 0; - while (i < cd.methods.length()) { - MethodDecl md = cd.methods.get(i) as MethodDecl; - if (md.body != null) { - this.lowerMethod(cd.name, md); - } - i = i + 1; - } - } - - private lowerModule(m: ArimoModule) { - Integer i = 0; - while (i < m.items.length()) { - Item it = m.items.get(i) as Item; - if (it.kind == ItemKind.CLASS) { - this.lowerClass(it.classDecl); - } - i = i + 1; - } - } - - // ===== Print-int helpers (avoid two-call register pressure in caller) ===== - - private generatePrintlnInt(name: String, addNewline: Boolean) { - this.beginFn(name, IRType.VOID); - this.addParamToLast("n", IRType.I64); - this.resetFnContext(); - this.emit(IRInstr.label("entry")); - - IRValue nv = IRValue.reg("n", IRType.I64); - - // Convert to string - String tsReg = this.newReg(); - IRValue tsDst = IRValue.reg(tsReg, IRType.PTR); - List tsArgs = List(); - tsArgs.append(nv); - this.emit(IRInstr.call(tsDst, "__arimo_i64_to_str", tsArgs)); - - // Print - String target = "__arimo_println"; - if (!addNewline) { target = "__arimo_print"; } - List callArgs = List(); - callArgs.append(tsDst); - String dr = this.newReg(); - IRValue dst = IRValue.reg(dr, IRType.VOID); - this.emit(IRInstr.call(dst, target, callArgs)); - this.emit(IRInstr.retVoid()); - } - - // ===== Print helpers ===== - - private generatePrintHelper(name: String, addNewline: Boolean) { - this.beginFn(name, IRType.VOID); - this.addParamToLast("str", IRType.PTR); - this.resetFnContext(); - this.emit(IRInstr.label("entry")); - - IRValue strSaved = IRValue.reg("str_saved", IRType.PTR); - this.emit(IRInstr.mov(strSaved, IRValue.reg("str", IRType.PTR))); - - String nlName = this.internStr("\n"); - IRValue strPtr = IRValue.reg("str_saved", IRType.PTR); - - String lenReg = "plen"; - IRValue lenDst = IRValue.reg(lenReg, IRType.I64); - List lenArgs = List(); - lenArgs.append(strPtr); - this.emit(IRInstr.call(lenDst, "__arimo_strlen", lenArgs)); - - if (this.linux) { - // write(1, str, len) - List wrArgs = List(); - wrArgs.append(IRValue.ofInt(1, IRType.I64)); - wrArgs.append(strPtr); - wrArgs.append(IRValue.reg("plen", IRType.I64)); - IRValue wrDst = IRValue.reg("pwr", IRType.VOID); - this.emit(IRInstr.syscall(wrDst, 1, wrArgs)); - if (addNewline) { - List wrArgs2 = List(); - wrArgs2.append(IRValue.ofInt(1, IRType.I64)); - wrArgs2.append(IRValue.global(nlName)); - wrArgs2.append(IRValue.ofInt(1, IRType.I64)); - IRValue wrDst2 = IRValue.reg("pwr2", IRType.VOID); - this.emit(IRInstr.syscall(wrDst2, 1, wrArgs2)); - } - } else { - IRValue hDst = IRValue.reg("ph_h", IRType.I64); - List hsArgs = List(); - hsArgs.append(IRValue.ofInt(-11, IRType.I64)); - this.emit(IRInstr.call(hDst, "__ext__GetStdHandle", hsArgs)); - - IRValue bwPtr = IRValue.reg("bwPtr", IRType.PTR); - this.emit(IRInstr.alloc(bwPtr, 4)); - - List wfArgs = List(); - wfArgs.append(IRValue.reg("ph_h", IRType.I64)); - wfArgs.append(strPtr); - wfArgs.append(IRValue.reg("plen", IRType.I64)); - wfArgs.append(IRValue.reg("bwPtr", IRType.PTR)); - wfArgs.append(IRValue.ofInt(0, IRType.I64)); - IRValue wfDst = IRValue.reg("pwf", IRType.VOID); - this.emit(IRInstr.call(wfDst, "__ext__WriteFile", wfArgs)); - - if (addNewline) { - List wfArgs2 = List(); - wfArgs2.append(IRValue.reg("ph_h", IRType.I64)); - wfArgs2.append(IRValue.global(nlName)); - wfArgs2.append(IRValue.ofInt(1, IRType.I64)); - wfArgs2.append(IRValue.reg("bwPtr", IRType.PTR)); - wfArgs2.append(IRValue.ofInt(0, IRType.I64)); - IRValue wfDst2 = IRValue.reg("pwf2", IRType.VOID); - this.emit(IRInstr.call(wfDst2, "__ext__WriteFile", wfArgs2)); - } - } - this.emit(IRInstr.retVoid()); - } - - // ===== Strlen helper ===== - - private generateStrlenHelper() { - this.beginFn("__arimo_strlen", IRType.I64); - this.addParamToLast("s", IRType.PTR); - this.resetFnContext(); - this.emit(IRInstr.label("entry")); - IRValue sBase = IRValue.reg("sBase", IRType.PTR); - this.emit(IRInstr.mov(sBase, IRValue.reg("s", IRType.PTR))); - IRValue cnt = IRValue.reg("cnt", IRType.I64); - this.emit(IRInstr.mov(cnt, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.label("strlen_loop")); - IRValue idx = IRValue.reg("cidx", IRType.I64); - this.emit(IRInstr.binop(IROpcode.ADD, idx, IRValue.reg("sBase", IRType.PTR), IRValue.reg("cnt", IRType.I64))); - IRValue ch = IRValue.reg("ch", IRType.I64); - this.emit(IRInstr.load(ch, IRValue.reg("cidx", IRType.I64), IRType.I8)); - this.emit(IRInstr.cmp(IRValue.reg("ch", IRType.I64), IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.branch(IROpcode.JE, "strlen_done")); - IRValue cnt2 = IRValue.reg("cnt", IRType.I64); - this.emit(IRInstr.binop(IROpcode.ADD, cnt2, IRValue.reg("cnt", IRType.I64), IRValue.ofInt(1, IRType.I64))); - IRValue sKeep = IRValue.reg("sKeep", IRType.PTR); - this.emit(IRInstr.mov(sKeep, IRValue.reg("sBase", IRType.PTR))); - this.emit(IRInstr.jmp("strlen_loop")); - this.emit(IRInstr.label("strlen_done")); - this.emit(IRInstr.ret(IRValue.reg("cnt", IRType.I64))); - } - - // ===== List runtime ===== - - private generateListNew() { - this.beginFn("__arimo_list_new", IRType.PTR); - this.resetFnContext(); - this.emit(IRInstr.label("entry")); - IRValue listPtr = this.emitHeapAlloc(IRValue.ofInt(24, IRType.I64)); - IRValue dataPtr = this.emitHeapAlloc(IRValue.ofInt(64, IRType.I64)); - this.emitFieldStore(listPtr, 0, dataPtr); - this.emitFieldStore(listPtr, 8, IRValue.ofInt(0, IRType.I64)); - this.emitFieldStore(listPtr, 16, IRValue.ofInt(8, IRType.I64)); - this.emit(IRInstr.ret(listPtr)); - } - - private generateListLength() { - this.beginFn("__arimo_list_length", IRType.I64); - this.addParamToLast("list", IRType.PTR); - this.resetFnContext(); - this.emit(IRInstr.label("entry")); - IRValue listPar = IRValue.reg("list", IRType.PTR); - IRValue lenVal = this.emitFieldLoad(listPar, 8); - this.emit(IRInstr.ret(lenVal)); - } - - private generateListGet() { - this.beginFn("__arimo_list_get", IRType.I64); - this.addParamToLast("list", IRType.PTR); - this.addParamToLast("idx", IRType.I64); - this.resetFnContext(); - this.emit(IRInstr.label("entry")); - IRValue listPar = IRValue.reg("list", IRType.PTR); - IRValue idxPar = IRValue.reg("idx", IRType.I64); - IRValue data = this.emitFieldLoad(listPar, 0); - String offReg = this.newReg(); - IRValue off = IRValue.reg(offReg, IRType.I64); - this.emit(IRInstr.binop(IROpcode.MUL, off, idxPar, IRValue.ofInt(8, IRType.I64))); - String pReg = this.newReg(); - IRValue slot = IRValue.reg(pReg, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, slot, data, off)); - String elemReg = this.newReg(); - IRValue elem = IRValue.reg(elemReg, IRType.I64); - this.emit(IRInstr.load(elem, slot, IRType.I64)); - this.emit(IRInstr.ret(elem)); - } - - private generateListSet() { - this.beginFn("__arimo_list_set", IRType.VOID); - this.addParamToLast("list", IRType.PTR); - this.addParamToLast("idx", IRType.I64); - this.addParamToLast("val", IRType.I64); - this.resetFnContext(); - this.emit(IRInstr.label("entry")); - IRValue listPar = IRValue.reg("list", IRType.PTR); - IRValue idxPar = IRValue.reg("idx", IRType.I64); - IRValue valPar = IRValue.reg("val", IRType.I64); - IRValue data = this.emitFieldLoad(listPar, 0); - String offReg = this.newReg(); - IRValue off = IRValue.reg(offReg, IRType.I64); - this.emit(IRInstr.binop(IROpcode.MUL, off, idxPar, IRValue.ofInt(8, IRType.I64))); - String pReg = this.newReg(); - IRValue slot = IRValue.reg(pReg, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, slot, data, off)); - this.emit(IRInstr.store(valPar, slot, IRType.I64)); - this.emit(IRInstr.retVoid()); - } - - private generateListAppend() { - this.beginFn("__arimo_list_append", IRType.VOID); - this.addParamToLast("list", IRType.PTR); - this.addParamToLast("val", IRType.I64); - this.resetFnContext(); - this.emit(IRInstr.label("entry")); - IRValue listPar = IRValue.reg("list", IRType.PTR); - IRValue valPar = IRValue.reg("val", IRType.I64); - - IRValue lenVal = this.emitFieldLoad(listPar, 8); - IRValue capVal = this.emitFieldLoad(listPar, 16); - - String noResL = this.newLabel("no_resize"); - this.emit(IRInstr.cmp(lenVal, capVal)); - this.emit(IRInstr.branch(IROpcode.JL, noResL)); - - // resize: new_cap = cap*2, alloc new data, copy, update list - String ncReg = this.newReg(); - IRValue newCap = IRValue.reg(ncReg, IRType.I64); - this.emit(IRInstr.binop(IROpcode.ADD, newCap, capVal, capVal)); - - String nszReg = this.newReg(); - IRValue newSz = IRValue.reg(nszReg, IRType.I64); - this.emit(IRInstr.binop(IROpcode.MUL, newSz, newCap, IRValue.ofInt(8, IRType.I64))); - - IRValue newData = this.emitHeapAlloc(newSz); - - IRValue oldData = this.emitFieldLoad(listPar, 0); - - String ciReg = this.newReg(); - IRValue copyI = IRValue.reg(ciReg, IRType.I64); - this.emit(IRInstr.mov(copyI, IRValue.ofInt(0, IRType.I64))); - - String cpLoopL = this.newLabel("cp_loop"); - String cpDoneL = this.newLabel("cp_done"); - this.emit(IRInstr.label(cpLoopL)); - this.emit(IRInstr.cmp(copyI, lenVal)); - this.emit(IRInstr.branch(IROpcode.JGE, cpDoneL)); - - String coReg = this.newReg(); - IRValue copyOff = IRValue.reg(coReg, IRType.I64); - this.emit(IRInstr.binop(IROpcode.MUL, copyOff, copyI, IRValue.ofInt(8, IRType.I64))); - - String spReg = this.newReg(); - IRValue srcPtr = IRValue.reg(spReg, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, srcPtr, oldData, copyOff)); - - String elReg = this.newReg(); - IRValue elem = IRValue.reg(elReg, IRType.I64); - this.emit(IRInstr.load(elem, srcPtr, IRType.I64)); - - String dpReg = this.newReg(); - IRValue dstPtr = IRValue.reg(dpReg, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, dstPtr, newData, copyOff)); - this.emit(IRInstr.store(elem, dstPtr, IRType.I64)); - - this.emit(IRInstr.binop(IROpcode.ADD, copyI, copyI, IRValue.ofInt(1, IRType.I64))); - this.emit(IRInstr.jmp(cpLoopL)); - this.emit(IRInstr.label(cpDoneL)); - - IRValue ndPtr = this.emitFieldPtr(listPar, 0); - this.emit(IRInstr.store(newData, ndPtr, IRType.I64)); - IRValue ncPtr = this.emitFieldPtr(listPar, 16); - this.emit(IRInstr.store(newCap, ncPtr, IRType.I64)); - - this.emit(IRInstr.label(noResL)); - - IRValue data2 = this.emitFieldLoad(listPar, 0); - String loReg = this.newReg(); - IRValue lenOff = IRValue.reg(loReg, IRType.I64); - this.emit(IRInstr.binop(IROpcode.MUL, lenOff, lenVal, IRValue.ofInt(8, IRType.I64))); - String slReg = this.newReg(); - IRValue slot = IRValue.reg(slReg, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, slot, data2, lenOff)); - this.emit(IRInstr.store(valPar, slot, IRType.I64)); - - String nlReg = this.newReg(); - IRValue newLen = IRValue.reg(nlReg, IRType.I64); - this.emit(IRInstr.binop(IROpcode.ADD, newLen, lenVal, IRValue.ofInt(1, IRType.I64))); - IRValue lfPtr = this.emitFieldPtr(listPar, 8); - this.emit(IRInstr.store(newLen, lfPtr, IRType.I64)); - this.emit(IRInstr.retVoid()); - } - - private generateListRemoveAt() { - this.beginFn("__arimo_list_remove_at", IRType.VOID); - this.addParamToLast("list", IRType.PTR); - this.addParamToLast("idx", IRType.I64); - this.resetFnContext(); - this.emit(IRInstr.label("entry")); - IRValue listPar = IRValue.reg("list", IRType.PTR); - IRValue idxPar = IRValue.reg("idx", IRType.I64); - - IRValue lenVal = this.emitFieldLoad(listPar, 8); - IRValue data = this.emitFieldLoad(listPar, 0); - - String iReg = this.newReg(); - IRValue shiftI = IRValue.reg(iReg, IRType.I64); - this.emit(IRInstr.mov(shiftI, idxPar)); - - String shLoopL = this.newLabel("sh_loop"); - String shDoneL = this.newLabel("sh_done"); - - String limReg = this.newReg(); - IRValue lim = IRValue.reg(limReg, IRType.I64); - this.emit(IRInstr.binop(IROpcode.SUB, lim, lenVal, IRValue.ofInt(1, IRType.I64))); - - this.emit(IRInstr.label(shLoopL)); - this.emit(IRInstr.cmp(shiftI, lim)); - this.emit(IRInstr.branch(IROpcode.JGE, shDoneL)); - - String no1Reg = this.newReg(); - IRValue nextOff = IRValue.reg(no1Reg, IRType.I64); - String ni1Reg = this.newReg(); - IRValue nextIdx = IRValue.reg(ni1Reg, IRType.I64); - this.emit(IRInstr.binop(IROpcode.ADD, nextIdx, shiftI, IRValue.ofInt(1, IRType.I64))); - this.emit(IRInstr.binop(IROpcode.MUL, nextOff, nextIdx, IRValue.ofInt(8, IRType.I64))); - - String np1Reg = this.newReg(); - IRValue nextPtr = IRValue.reg(np1Reg, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, nextPtr, data, nextOff)); - - String ev1Reg = this.newReg(); - IRValue elemVal = IRValue.reg(ev1Reg, IRType.I64); - this.emit(IRInstr.load(elemVal, nextPtr, IRType.I64)); - - String co1Reg = this.newReg(); - IRValue currOff = IRValue.reg(co1Reg, IRType.I64); - this.emit(IRInstr.binop(IROpcode.MUL, currOff, shiftI, IRValue.ofInt(8, IRType.I64))); - String cp1Reg = this.newReg(); - IRValue currPtr = IRValue.reg(cp1Reg, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, currPtr, data, currOff)); - this.emit(IRInstr.store(elemVal, currPtr, IRType.I64)); - - this.emit(IRInstr.binop(IROpcode.ADD, shiftI, shiftI, IRValue.ofInt(1, IRType.I64))); - this.emit(IRInstr.jmp(shLoopL)); - this.emit(IRInstr.label(shDoneL)); - - String nl2Reg = this.newReg(); - IRValue newLen = IRValue.reg(nl2Reg, IRType.I64); - this.emit(IRInstr.binop(IROpcode.SUB, newLen, lenVal, IRValue.ofInt(1, IRType.I64))); - IRValue lfPtr = this.emitFieldPtr(listPar, 8); - this.emit(IRInstr.store(newLen, lfPtr, IRType.I64)); - this.emit(IRInstr.retVoid()); - } - - // ===== String builtins ===== - - private generateStrCmp() { - this.beginFn("__arimo_strcmp", IRType.I64); - this.addParamToLast("a", IRType.PTR); - this.addParamToLast("b", IRType.PTR); - this.resetFnContext(); - this.emit(IRInstr.label("entry")); - IRValue aP = IRValue.reg("a", IRType.PTR); - IRValue bP = IRValue.reg("b", IRType.PTR); - IRValue i = IRValue.reg("sci", IRType.I64); - this.emit(IRInstr.mov(i, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.label("scmp_loop")); - - String pca = this.newReg(); - IRValue pa = IRValue.reg(pca, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, pa, aP, i)); - String cca = this.newReg(); - IRValue ca = IRValue.reg(cca, IRType.I64); - this.emit(IRInstr.load(ca, pa, IRType.I8)); - - String pcb = this.newReg(); - IRValue pb = IRValue.reg(pcb, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, pb, bP, i)); - String ccb = this.newReg(); - IRValue cb = IRValue.reg(ccb, IRType.I64); - this.emit(IRInstr.load(cb, pb, IRType.I8)); - - this.emit(IRInstr.cmp(ca, cb)); - this.emit(IRInstr.branch(IROpcode.JNE, "scmp_ne")); - - this.emit(IRInstr.cmp(ca, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.branch(IROpcode.JE, "scmp_eq")); - - this.emit(IRInstr.binop(IROpcode.ADD, i, i, IRValue.ofInt(1, IRType.I64))); - this.emit(IRInstr.jmp("scmp_loop")); - - this.emit(IRInstr.label("scmp_ne")); - String dr1 = this.newReg(); - IRValue diff = IRValue.reg(dr1, IRType.I64); - this.emit(IRInstr.binop(IROpcode.SUB, diff, ca, cb)); - this.emit(IRInstr.ret(diff)); - - this.emit(IRInstr.label("scmp_eq")); - this.emit(IRInstr.ret(IRValue.ofInt(0, IRType.I64))); - } - - private generateStrCat() { - this.beginFn("__arimo_strcat", IRType.PTR); - this.addParamToLast("a", IRType.PTR); - this.addParamToLast("b", IRType.PTR); - this.resetFnContext(); - this.emit(IRInstr.label("entry")); - IRValue aP = IRValue.reg("a", IRType.PTR); - IRValue bP = IRValue.reg("b", IRType.PTR); - - // alen = strlen(a) - String alenReg = this.newReg(); - IRValue alen = IRValue.reg(alenReg, IRType.I64); - List sla = List(); - sla.append(aP); - this.emit(IRInstr.call(alen, "__arimo_strlen", sla)); - - // blen = strlen(b) - String blenReg = this.newReg(); - IRValue blen = IRValue.reg(blenReg, IRType.I64); - List slb = List(); - slb.append(bP); - this.emit(IRInstr.call(blen, "__arimo_strlen", slb)); - - // total = alen + blen + 1 - String tlReg = this.newReg(); - IRValue total = IRValue.reg(tlReg, IRType.I64); - this.emit(IRInstr.binop(IROpcode.ADD, total, alen, blen)); - String tl2Reg = this.newReg(); - IRValue total2 = IRValue.reg(tl2Reg, IRType.I64); - this.emit(IRInstr.binop(IROpcode.ADD, total2, total, IRValue.ofInt(1, IRType.I64))); - - IRValue buf = this.emitHeapAlloc(total2); - - // copy a - String aiReg = this.newReg(); - IRValue ai = IRValue.reg(aiReg, IRType.I64); - this.emit(IRInstr.mov(ai, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.label("scat_a")); - this.emit(IRInstr.cmp(ai, alen)); - this.emit(IRInstr.branch(IROpcode.JGE, "scat_a_done")); - String apReg = this.newReg(); - IRValue apv = IRValue.reg(apReg, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, apv, aP, ai)); - String acReg = this.newReg(); - IRValue ac = IRValue.reg(acReg, IRType.I64); - this.emit(IRInstr.load(ac, apv, IRType.I8)); - String dpReg = this.newReg(); - IRValue dpv = IRValue.reg(dpReg, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, dpv, buf, ai)); - this.emit(IRInstr.store(ac, dpv, IRType.I8)); - this.emit(IRInstr.binop(IROpcode.ADD, ai, ai, IRValue.ofInt(1, IRType.I64))); - this.emit(IRInstr.jmp("scat_a")); - this.emit(IRInstr.label("scat_a_done")); - - // copy b - String biReg = this.newReg(); - IRValue bi = IRValue.reg(biReg, IRType.I64); - this.emit(IRInstr.mov(bi, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.label("scat_b")); - this.emit(IRInstr.cmp(bi, blen)); - this.emit(IRInstr.branch(IROpcode.JGE, "scat_b_done")); - String bpReg = this.newReg(); - IRValue bpv = IRValue.reg(bpReg, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, bpv, bP, bi)); - String bcReg = this.newReg(); - IRValue bc = IRValue.reg(bcReg, IRType.I64); - this.emit(IRInstr.load(bc, bpv, IRType.I8)); - String dbReg = this.newReg(); - IRValue dbv = IRValue.reg(dbReg, IRType.PTR); - String dbsReg = this.newReg(); - IRValue dbs = IRValue.reg(dbsReg, IRType.I64); - this.emit(IRInstr.binop(IROpcode.ADD, dbs, alen, bi)); - this.emit(IRInstr.binop(IROpcode.ADD, dbv, buf, dbs)); - this.emit(IRInstr.store(bc, dbv, IRType.I8)); - this.emit(IRInstr.binop(IROpcode.ADD, bi, bi, IRValue.ofInt(1, IRType.I64))); - this.emit(IRInstr.jmp("scat_b")); - this.emit(IRInstr.label("scat_b_done")); - - // null terminate - String nullPReg = this.newReg(); - IRValue nullP = IRValue.reg(nullPReg, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, nullP, buf, total)); - this.emit(IRInstr.store(IRValue.ofInt(0, IRType.I64), nullP, IRType.I8)); - - this.emit(IRInstr.ret(buf)); - } - - private generateSubstr() { - this.beginFn("__arimo_substr", IRType.PTR); - this.addParamToLast("s", IRType.PTR); - this.addParamToLast("from", IRType.I64); - this.addParamToLast("to", IRType.I64); - this.resetFnContext(); - this.emit(IRInstr.label("entry")); - IRValue sP = IRValue.reg("s", IRType.PTR); - IRValue fromP = IRValue.reg("from", IRType.I64); - IRValue toP = IRValue.reg("to", IRType.I64); - - String lenReg = this.newReg(); - IRValue len = IRValue.reg(lenReg, IRType.I64); - this.emit(IRInstr.binop(IROpcode.SUB, len, toP, fromP)); - - String aszReg = this.newReg(); - IRValue asz = IRValue.reg(aszReg, IRType.I64); - this.emit(IRInstr.binop(IROpcode.ADD, asz, len, IRValue.ofInt(1, IRType.I64))); - - IRValue buf = this.emitHeapAlloc(asz); - - String iReg = this.newReg(); - IRValue si = IRValue.reg(iReg, IRType.I64); - this.emit(IRInstr.mov(si, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.label("sub_loop")); - this.emit(IRInstr.cmp(si, len)); - this.emit(IRInstr.branch(IROpcode.JGE, "sub_done")); - - String srcIdxReg = this.newReg(); - IRValue srcIdx = IRValue.reg(srcIdxReg, IRType.I64); - this.emit(IRInstr.binop(IROpcode.ADD, srcIdx, fromP, si)); - String spReg = this.newReg(); - IRValue sp = IRValue.reg(spReg, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, sp, sP, srcIdx)); - String chReg = this.newReg(); - IRValue ch = IRValue.reg(chReg, IRType.I64); - this.emit(IRInstr.load(ch, sp, IRType.I8)); - String dpReg = this.newReg(); - IRValue dp = IRValue.reg(dpReg, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, dp, buf, si)); - this.emit(IRInstr.store(ch, dp, IRType.I8)); - this.emit(IRInstr.binop(IROpcode.ADD, si, si, IRValue.ofInt(1, IRType.I64))); - this.emit(IRInstr.jmp("sub_loop")); - this.emit(IRInstr.label("sub_done")); - - String nPReg = this.newReg(); - IRValue nP = IRValue.reg(nPReg, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, nP, buf, len)); - this.emit(IRInstr.store(IRValue.ofInt(0, IRType.I64), nP, IRType.I8)); - this.emit(IRInstr.ret(buf)); - } - - private generateStartsWith() { - this.beginFn("__arimo_startswith", IRType.I64); - this.addParamToLast("s", IRType.PTR); - this.addParamToLast("prefix", IRType.PTR); - this.resetFnContext(); - this.emit(IRInstr.label("entry")); - IRValue sP = IRValue.reg("s", IRType.PTR); - IRValue pfP = IRValue.reg("prefix", IRType.PTR); - IRValue si = IRValue.reg("swi", IRType.I64); - this.emit(IRInstr.mov(si, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.label("sw_loop")); - - String ppp = this.newReg(); - IRValue pp = IRValue.reg(ppp, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, pp, pfP, si)); - String pcc = this.newReg(); - IRValue pc = IRValue.reg(pcc, IRType.I64); - this.emit(IRInstr.load(pc, pp, IRType.I8)); - this.emit(IRInstr.cmp(pc, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.branch(IROpcode.JE, "sw_yes")); - - String spp = this.newReg(); - IRValue sp = IRValue.reg(spp, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, sp, sP, si)); - String scc = this.newReg(); - IRValue sc = IRValue.reg(scc, IRType.I64); - this.emit(IRInstr.load(sc, sp, IRType.I8)); - this.emit(IRInstr.cmp(sc, pc)); - this.emit(IRInstr.branch(IROpcode.JNE, "sw_no")); - - this.emit(IRInstr.binop(IROpcode.ADD, si, si, IRValue.ofInt(1, IRType.I64))); - this.emit(IRInstr.jmp("sw_loop")); - this.emit(IRInstr.label("sw_yes")); - this.emit(IRInstr.ret(IRValue.ofInt(1, IRType.I64))); - this.emit(IRInstr.label("sw_no")); - this.emit(IRInstr.ret(IRValue.ofInt(0, IRType.I64))); - } - - private generateEndsWith() { - this.beginFn("__arimo_endswith", IRType.I64); - this.addParamToLast("s", IRType.PTR); - this.addParamToLast("suffix", IRType.PTR); - this.resetFnContext(); - this.emit(IRInstr.label("entry")); - IRValue sP = IRValue.reg("s", IRType.PTR); - IRValue sfP = IRValue.reg("suffix", IRType.PTR); - - String slenR = this.newReg(); - IRValue slen = IRValue.reg(slenR, IRType.I64); - List sla = List(); - sla.append(sP); - this.emit(IRInstr.call(slen, "__arimo_strlen", sla)); - - String sflenR = this.newReg(); - IRValue sflen = IRValue.reg(sflenR, IRType.I64); - List slb = List(); - slb.append(sfP); - this.emit(IRInstr.call(sflen, "__arimo_strlen", slb)); - - this.emit(IRInstr.cmp(sflen, slen)); - this.emit(IRInstr.branch(IROpcode.JG, "ew_no")); - - String offR = this.newReg(); - IRValue off = IRValue.reg(offR, IRType.I64); - this.emit(IRInstr.binop(IROpcode.SUB, off, slen, sflen)); - - IRValue ewi = IRValue.reg("ewi", IRType.I64); - this.emit(IRInstr.mov(ewi, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.label("ew_loop")); - - String sfpp = this.newReg(); - IRValue sfp = IRValue.reg(sfpp, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, sfp, sfP, ewi)); - String sfcc = this.newReg(); - IRValue sfc = IRValue.reg(sfcc, IRType.I64); - this.emit(IRInstr.load(sfc, sfp, IRType.I8)); - this.emit(IRInstr.cmp(sfc, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.branch(IROpcode.JE, "ew_yes")); - - String sidxR = this.newReg(); - IRValue sidx = IRValue.reg(sidxR, IRType.I64); - this.emit(IRInstr.binop(IROpcode.ADD, sidx, off, ewi)); - String spp = this.newReg(); - IRValue sp = IRValue.reg(spp, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, sp, sP, sidx)); - String scc = this.newReg(); - IRValue sc = IRValue.reg(scc, IRType.I64); - this.emit(IRInstr.load(sc, sp, IRType.I8)); - this.emit(IRInstr.cmp(sc, sfc)); - this.emit(IRInstr.branch(IROpcode.JNE, "ew_no")); - - this.emit(IRInstr.binop(IROpcode.ADD, ewi, ewi, IRValue.ofInt(1, IRType.I64))); - this.emit(IRInstr.jmp("ew_loop")); - this.emit(IRInstr.label("ew_yes")); - this.emit(IRInstr.ret(IRValue.ofInt(1, IRType.I64))); - this.emit(IRInstr.label("ew_no")); - this.emit(IRInstr.ret(IRValue.ofInt(0, IRType.I64))); - } - - private generateIndexOf() { - this.beginFn("__arimo_indexof_from", IRType.I64); - this.addParamToLast("s", IRType.PTR); - this.addParamToLast("sub", IRType.PTR); - this.addParamToLast("from", IRType.I64); - this.resetFnContext(); - this.emit(IRInstr.label("entry")); - IRValue sP = IRValue.reg("s", IRType.PTR); - IRValue subP = IRValue.reg("sub", IRType.PTR); - IRValue fromV = IRValue.reg("from", IRType.I64); - - String slenR = this.newReg(); - IRValue slen = IRValue.reg(slenR, IRType.I64); - List sla = List(); - sla.append(sP); - this.emit(IRInstr.call(slen, "__arimo_strlen", sla)); - - String sublenR = this.newReg(); - IRValue sublen = IRValue.reg(sublenR, IRType.I64); - List slb = List(); - slb.append(subP); - this.emit(IRInstr.call(sublen, "__arimo_strlen", slb)); - - IRValue oi = IRValue.reg("io_i", IRType.I64); - this.emit(IRInstr.mov(oi, fromV)); - this.emit(IRInstr.label("io_outer")); - - String limR = this.newReg(); - IRValue lim = IRValue.reg(limR, IRType.I64); - this.emit(IRInstr.binop(IROpcode.SUB, lim, slen, sublen)); - this.emit(IRInstr.cmp(oi, lim)); - this.emit(IRInstr.branch(IROpcode.JG, "io_notfound")); - - IRValue ij = IRValue.reg("io_j", IRType.I64); - this.emit(IRInstr.mov(ij, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.label("io_inner")); - - String spjR = this.newReg(); - IRValue spj = IRValue.reg(spjR, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, spj, subP, ij)); - String scjR = this.newReg(); - IRValue scj = IRValue.reg(scjR, IRType.I64); - this.emit(IRInstr.load(scj, spj, IRType.I8)); - this.emit(IRInstr.cmp(scj, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.branch(IROpcode.JE, "io_match")); - - String soffR = this.newReg(); - IRValue soff = IRValue.reg(soffR, IRType.I64); - this.emit(IRInstr.binop(IROpcode.ADD, soff, oi, ij)); - String sppR = this.newReg(); - IRValue spp = IRValue.reg(sppR, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, spp, sP, soff)); - String sccR = this.newReg(); - IRValue scc = IRValue.reg(sccR, IRType.I64); - this.emit(IRInstr.load(scc, spp, IRType.I8)); - this.emit(IRInstr.cmp(scc, scj)); - this.emit(IRInstr.branch(IROpcode.JNE, "io_nomatch")); - - this.emit(IRInstr.binop(IROpcode.ADD, ij, ij, IRValue.ofInt(1, IRType.I64))); - this.emit(IRInstr.jmp("io_inner")); - - this.emit(IRInstr.label("io_match")); - this.emit(IRInstr.ret(oi)); - - this.emit(IRInstr.label("io_nomatch")); - this.emit(IRInstr.binop(IROpcode.ADD, oi, oi, IRValue.ofInt(1, IRType.I64))); - this.emit(IRInstr.jmp("io_outer")); - - this.emit(IRInstr.label("io_notfound")); - this.emit(IRInstr.ret(IRValue.ofInt(-1, IRType.I64))); - } - - private generateCharCodeAt() { - this.beginFn("__arimo_charcodeat", IRType.I64); - this.addParamToLast("s", IRType.PTR); - this.addParamToLast("idx", IRType.I64); - this.resetFnContext(); - this.emit(IRInstr.label("entry")); - IRValue sP = IRValue.reg("s", IRType.PTR); - IRValue idx = IRValue.reg("idx", IRType.I64); - String pReg = this.newReg(); - IRValue p = IRValue.reg(pReg, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, p, sP, idx)); - String cReg = this.newReg(); - IRValue c = IRValue.reg(cReg, IRType.I64); - this.emit(IRInstr.load(c, p, IRType.I8)); - this.emit(IRInstr.ret(c)); - } - - private generateCharAt() { - this.beginFn("__arimo_charat", IRType.PTR); - this.addParamToLast("s", IRType.PTR); - this.addParamToLast("idx", IRType.I64); - this.resetFnContext(); - this.emit(IRInstr.label("entry")); - IRValue sP = IRValue.reg("s", IRType.PTR); - IRValue idx = IRValue.reg("idx", IRType.I64); - String pReg = this.newReg(); - IRValue p = IRValue.reg(pReg, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, p, sP, idx)); - String cReg = this.newReg(); - IRValue c = IRValue.reg(cReg, IRType.I64); - this.emit(IRInstr.load(c, p, IRType.I8)); - IRValue buf = this.emitHeapAlloc(IRValue.ofInt(2, IRType.I64)); - this.emit(IRInstr.store(c, buf, IRType.I8)); - String npReg = this.newReg(); - IRValue np = IRValue.reg(npReg, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, np, buf, IRValue.ofInt(1, IRType.I64))); - this.emit(IRInstr.store(IRValue.ofInt(0, IRType.I64), np, IRType.I8)); - this.emit(IRInstr.ret(buf)); - } - - private generateI64ToStr() { - this.beginFn("__arimo_i64_to_str", IRType.PTR); - this.addParamToLast("n", IRType.I64); - this.resetFnContext(); - this.emit(IRInstr.label("entry")); - IRValue nv = IRValue.reg("n", IRType.I64); - - // Special case: n == 0 - this.emit(IRInstr.cmp(nv, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.branch(IROpcode.JNE, "its_nonzero")); - IRValue zeroBuf = this.emitHeapAlloc(IRValue.ofInt(4, IRType.I64)); - String zp0R = this.newReg(); - IRValue zp0 = IRValue.reg(zp0R, IRType.PTR); - this.emit(IRInstr.mov(zp0, zeroBuf)); - this.emit(IRInstr.store(IRValue.ofInt(48, IRType.I64), zp0, IRType.I8)); // '0'=48 - String zp1R = this.newReg(); - IRValue zp1 = IRValue.reg(zp1R, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, zp1, zeroBuf, IRValue.ofInt(1, IRType.I64))); - this.emit(IRInstr.store(IRValue.ofInt(0, IRType.I64), zp1, IRType.I8)); - this.emit(IRInstr.ret(zeroBuf)); - - this.emit(IRInstr.label("its_nonzero")); - // neg = 0; abs_n = n; if n < 0: neg=1, abs_n = 0-n - IRValue neg = IRValue.reg("its_neg", IRType.I64); - IRValue abs_n = IRValue.reg("its_absn", IRType.I64); - this.emit(IRInstr.mov(neg, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.mov(abs_n, nv)); - this.emit(IRInstr.cmp(nv, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.branch(IROpcode.JGE, "its_pos")); - this.emit(IRInstr.mov(neg, IRValue.ofInt(1, IRType.I64))); - String anR = this.newReg(); - IRValue an = IRValue.reg(anR, IRType.I64); - this.emit(IRInstr.binop(IROpcode.SUB, an, IRValue.ofInt(0, IRType.I64), nv)); - this.emit(IRInstr.mov(abs_n, an)); - this.emit(IRInstr.label("its_pos")); - - // Count digits - IRValue cnt = IRValue.reg("its_cnt", IRType.I64); - IRValue tmp = IRValue.reg("its_tmp", IRType.I64); - this.emit(IRInstr.mov(cnt, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.mov(tmp, abs_n)); - this.emit(IRInstr.label("its_count")); - this.emit(IRInstr.cmp(tmp, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.branch(IROpcode.JLE, "its_counted")); - this.emit(IRInstr.binop(IROpcode.ADD, cnt, cnt, IRValue.ofInt(1, IRType.I64))); - String dvrR = this.newReg(); - IRValue dvr = IRValue.reg(dvrR, IRType.I64); - this.emit(IRInstr.binop(IROpcode.DIV, dvr, tmp, IRValue.ofInt(10, IRType.I64))); - this.emit(IRInstr.mov(tmp, dvr)); - this.emit(IRInstr.jmp("its_count")); - this.emit(IRInstr.label("its_counted")); - - // total = cnt + neg - String totR = this.newReg(); - IRValue tot = IRValue.reg(totR, IRType.I64); - this.emit(IRInstr.binop(IROpcode.ADD, tot, cnt, neg)); - String aszR = this.newReg(); - IRValue asz = IRValue.reg(aszR, IRType.I64); - this.emit(IRInstr.binop(IROpcode.ADD, asz, tot, IRValue.ofInt(1, IRType.I64))); - - IRValue buf = this.emitHeapAlloc(asz); - - // null terminate - String nlPR = this.newReg(); - IRValue nlP = IRValue.reg(nlPR, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, nlP, buf, tot)); - this.emit(IRInstr.store(IRValue.ofInt(0, IRType.I64), nlP, IRType.I8)); - - // write '-' if neg - this.emit(IRInstr.cmp(neg, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.branch(IROpcode.JE, "its_no_neg")); - String np0R = this.newReg(); - IRValue np0 = IRValue.reg(np0R, IRType.PTR); - this.emit(IRInstr.mov(np0, buf)); - this.emit(IRInstr.store(IRValue.ofInt(45, IRType.I64), np0, IRType.I8)); // '-'=45 - this.emit(IRInstr.label("its_no_neg")); - - // write digits right to left: pos = tot-1 - IRValue pos = IRValue.reg("its_pos", IRType.I64); - IRValue abs2 = IRValue.reg("its_ab2", IRType.I64); - this.emit(IRInstr.binop(IROpcode.SUB, pos, tot, IRValue.ofInt(1, IRType.I64))); - this.emit(IRInstr.mov(abs2, abs_n)); - this.emit(IRInstr.label("its_write")); - this.emit(IRInstr.cmp(abs2, IRValue.ofInt(0, IRType.I64))); - this.emit(IRInstr.branch(IROpcode.JLE, "its_written")); - String modR = this.newReg(); - IRValue md = IRValue.reg(modR, IRType.I64); - this.emit(IRInstr.binop(IROpcode.MOD, md, abs2, IRValue.ofInt(10, IRType.I64))); - String digR = this.newReg(); - IRValue dig = IRValue.reg(digR, IRType.I64); - this.emit(IRInstr.binop(IROpcode.ADD, dig, md, IRValue.ofInt(48, IRType.I64))); // '0'=48 - String dPR = this.newReg(); - IRValue dP = IRValue.reg(dPR, IRType.PTR); - this.emit(IRInstr.binop(IROpcode.ADD, dP, buf, pos)); - this.emit(IRInstr.store(dig, dP, IRType.I8)); - String divR = this.newReg(); - IRValue dv = IRValue.reg(divR, IRType.I64); - this.emit(IRInstr.binop(IROpcode.DIV, dv, abs2, IRValue.ofInt(10, IRType.I64))); - this.emit(IRInstr.mov(abs2, dv)); - this.emit(IRInstr.binop(IROpcode.SUB, pos, pos, IRValue.ofInt(1, IRType.I64))); - this.emit(IRInstr.jmp("its_write")); - this.emit(IRInstr.label("its_written")); - this.emit(IRInstr.ret(buf)); - } - - // ===== Entry point ===== - - private generateStartFn() { - this.beginFn("_start", IRType.VOID); - this.resetFnContext(); - this.emit(IRInstr.label("entry")); - List mainArgs = List(); - IRValue mainResult = IRValue.reg("main_result", IRType.I64); - if (this.mainFn != "") { - this.emit(IRInstr.call(mainResult, this.mainFn, mainArgs)); - } else { - this.emit(IRInstr.mov(mainResult, IRValue.ofInt(0, IRType.I64))); - } - List exitArgs = List(); - exitArgs.append(mainResult); - IRValue exitDst = IRValue.reg("ex", IRType.VOID); - if (this.linux) { - this.emit(IRInstr.syscall(exitDst, 60, exitArgs)); - } else { - this.emit(IRInstr.call(exitDst, "__ext__ExitProcess", exitArgs)); - } - this.emit(IRInstr.retVoid()); - } - - public lower(modules: List) { - this.registerClasses(modules); - this.generateTeardownRoutines(); - this.internStr("\n"); - Integer i = 0; - while (i < modules.length()) { - ArimoModule m = modules.get(i) as ArimoModule; - this.lowerModule(m); - i = i + 1; - } - this.generateStrlenHelper(); - this.generateListNew(); - this.generateListLength(); - this.generateListGet(); - this.generateListSet(); - this.generateListAppend(); - this.generateListRemoveAt(); - this.generateStrCmp(); - this.generateStrCat(); - this.generateSubstr(); - this.generateStartsWith(); - this.generateEndsWith(); - this.generateIndexOf(); - this.generateCharCodeAt(); - this.generateCharAt(); - this.generateI64ToStr(); - this.generatePrintlnInt("__arimo_println_int", true); - this.generatePrintlnInt("__arimo_print_int", false); - this.generatePrintHelper("__arimo_println", true); - this.generatePrintHelper("__arimo_print", false); - this.generateStartFn(); - } -} +/* +Arimo Lang Compiler - A modern programming language and compiler +Copyright (C) 2026 Egecan Akıncıoğlu + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package arimo.compiler.backend; + +import arimo.compiler.ast.ArimoModule; +import arimo.compiler.ast.item.Item; +import arimo.compiler.ast.item.ItemKind; +import arimo.compiler.ast.item.ClassDecl; +import arimo.compiler.ast.decl.MethodDecl; +import arimo.compiler.ast.decl.FieldDecl; +import arimo.compiler.ast.decl.ConstructorDecl; +import arimo.compiler.ast.decl.Param; +import arimo.compiler.ast.stmt.Stmt; +import arimo.compiler.ast.stmt.StmtKind; +import arimo.compiler.ast.stmt.ElseIfBranch; +import arimo.compiler.ast.expr.Expr; +import arimo.compiler.ast.expr.ExprKind; +import arimo.compiler.ast.expr.BinaryOp; +import arimo.compiler.ast.expr.StrPart; +import arimo.compiler.ast.types.AstType; +import arimo.compiler.ast.types.TypeKind; +import arimo.compiler.backend.IRFunction; +import arimo.compiler.backend.IRInstr; +import arimo.compiler.backend.IRValue; +import arimo.compiler.backend.IRValueKind; +import arimo.compiler.backend.IROpcode; +import arimo.compiler.backend.IRType; + +public class IRLower { + public irFns : List; + public strNames : List; + public strConts : List; + public bssNames : List; + public bssSizes : List; + private strCnt : Integer; + private labelCnt : Integer; + private regCnt : Integer; + private varNames : List; + private varRegs : List; + private varClassNames : List; + private breakLbl : String; + private contLbl : String; + private breakDepth : Integer; + private contDepth : Integer; + private scopeStack : List>; + private mainFn : String; + private classNames : List; + private classParents : List; + private allFieldNames : List; + private allFieldClasses: List; + private classFldStarts : List; + private classFldCounts : List; + private methodRetNames : List; + private methodRetClasses: List; + private teardownLabels : List; + private curClass : String; + private thisReg : String; + private linux : Boolean; + + public constructor(linux: Boolean) { + this.linux = linux; + this.irFns = List(); + this.strNames = List(); + this.strConts = List(); + this.bssNames = List(); + this.bssSizes = List(); + this.strCnt = 0; + this.labelCnt = 0; + this.regCnt = 0; + this.varNames = List(); + this.varRegs = List(); + this.varClassNames = List(); + this.breakLbl = ""; + this.contLbl = ""; + this.breakDepth = 0; + this.contDepth = 0; + this.scopeStack = List(); + this.mainFn = ""; + this.classNames = List(); + this.classParents = List(); + this.allFieldNames = List(); + this.allFieldClasses = List(); + this.classFldStarts = List(); + this.classFldCounts = List(); + this.methodRetNames = List(); + this.methodRetClasses = List(); + this.teardownLabels = List(); + this.curClass = ""; + this.thisReg = ""; + this.irFns.append(IRFunction("__placeholder", IRType.VOID)); + } + + // ===== Class layout registration ===== + + private registerClasses(modules: List) { + Integer mi = 0; + while (mi < modules.length()) { + ArimoModule m = modules.get(mi) as ArimoModule; + Integer ii = 0; + while (ii < m.items.length()) { + Item it = m.items.get(ii) as Item; + if (it.kind == ItemKind.CLASS) { + this.registerClass(it.classDecl); + } + ii = ii + 1; + } + mi = mi + 1; + } + } + + private registerClass(cd: ClassDecl) { + if (this.classIdxOf(cd.name) >= 0) { return; } + Integer start = this.allFieldNames.length(); + Integer count = 0; + Integer fi = 0; + while (fi < cd.fields.length()) { + FieldDecl fd = cd.fields.get(fi) as FieldDecl; + if (!fd.isStatic) { + this.allFieldNames.append(fd.name); + this.allFieldClasses.append(this.tyToClass(fd.ty)); + count = count + 1; + } + fi = fi + 1; + } + this.classNames.append(cd.name); + this.classFldStarts.append(start); + this.classFldCounts.append(count); + this.teardownLabels.append(""); + // Track parent class for recursive field release (inheritance chain) + if (cd.extends_ != "" && cd.extends_.length() > 0) { + this.classParents.append(this.classIdxOf(cd.extends_)); + } else { + this.classParents.append(-1); + } + + // Track method return types for inferClass(METHOD) + Integer mi = 0; + while (mi < cd.methods.length()) { + MethodDecl md = cd.methods.get(mi) as MethodDecl; + String retCls = this.tyToClass(md.returnTy); + this.methodRetNames.append("${cd.name}__${md.name}"); + this.methodRetClasses.append(retCls); + mi = mi + 1; + } + } + + private classIdxOf(name: String) : Integer { + Integer i = 0; + while (i < this.classNames.length()) { + String n = this.classNames.get(i) as String; + if (n == name) { return i; } + i = i + 1; + } + return -1; + } + + private fieldIdxOf(clsIdx: Integer, fieldName: String) : Integer { + Integer start = this.classFldStarts.get(clsIdx) as Integer; + Integer count = this.classFldCounts.get(clsIdx) as Integer; + Integer i = 0; + while (i < count) { + String fn = this.allFieldNames.get(start + i) as String; + if (fn == fieldName) { return i + 1; } + i = i + 1; + } + return -1; + } + + private classSizeBytes(clsIdx: Integer) : Integer { + Integer count = this.classFldCounts.get(clsIdx) as Integer; + return (1 + count) * 8 + 8; + } + + private refCountOff(clsIdx: Integer) : Integer { + return this.classSizeBytes(clsIdx) - 8; + } + + // ===== Variable class tracking ===== + + private varClassOf(name: String) : String { + Integer i = 0; + while (i < this.varNames.length()) { + String n = this.varNames.get(i) as String; + if (n == name) { return this.varClassNames.get(i) as String; } + i = i + 1; + } + return ""; + } + + private varSetClass(name: String, cls: String) { + Integer i = 0; + while (i < this.varNames.length()) { + String n = this.varNames.get(i) as String; + if (n == name) { this.varClassNames.set(i, cls); return; } + i = i + 1; + } + } + + // ===== Type inference ===== + + private tyToClass(ty: AstType) : String { + if (ty == null) { return ""; } + if (ty.kind == TypeKind.NAMED) { return ty.name; } + if (ty.kind == TypeKind.STR) { return "String"; } + if (ty.kind == TypeKind.LIST) { return "List"; } + if (ty.kind == TypeKind.NULLABLE) { return this.tyToClass(ty.inner); } + if (ty.kind == TypeKind.INTEGER) { return "Integer"; } + if (ty.kind == TypeKind.BOOLEAN) { return "Boolean"; } + if (ty.kind == TypeKind.FLOAT) { return "Float"; } + if (ty.kind == TypeKind.CHAR) { return "Char"; } + if (ty.kind == TypeKind.I64) { return "I64"; } + if (ty.kind == TypeKind.I32) { return "I32"; } + if (ty.kind == TypeKind.I16) { return "I16"; } + if (ty.kind == TypeKind.I8) { return "I8"; } + if (ty.kind == TypeKind.U64) { return "U64"; } + if (ty.kind == TypeKind.U32) { return "U32"; } + if (ty.kind == TypeKind.U16) { return "U16"; } + if (ty.kind == TypeKind.U8) { return "U8"; } + return ""; + } + + private inferClass(expr: Expr) : String { + if (expr.kind == ExprKind.THIS) { return this.curClass; } + if (expr.kind == ExprKind.IDENT) { return this.varClassOf(expr.strVal); } + if (expr.kind == ExprKind.CTOR) { return expr.class_; } + if (expr.kind == ExprKind.CAST) { + if (expr.castTy.kind == TypeKind.NAMED) { return expr.castTy.name; } + if (expr.castTy.kind == TypeKind.STR) { return "String"; } + if (expr.castTy.kind == TypeKind.LIST) { return "List"; } + if (expr.castTy.kind == TypeKind.NULLABLE) { return this.tyToClass(expr.castTy.inner); } + } + if (expr.kind == ExprKind.FIELD) { + String objCls = this.inferClass(expr.object); + Integer clsIdx = this.classIdxOf(objCls); + if (clsIdx >= 0) { + Integer fldIdx = this.fieldIdxOf(clsIdx, expr.field); + if (fldIdx >= 0) { + Integer start = this.classFldStarts.get(clsIdx) as Integer; + return this.allFieldClasses.get(start + fldIdx - 1) as String; + } + } + return ""; + } + if (expr.kind == ExprKind.METHOD) { + String objCls = this.inferClass(expr.object); + if (objCls != "") { + String key = "${objCls}__${expr.method}"; + Integer i = 0; + while (i < this.methodRetNames.length()) { + if (this.methodRetNames.get(i) as String == key) { + return this.methodRetClasses.get(i) as String; + } + i = i + 1; + } + // Built-in method (String/List): assume method returns receiver type (common for chaining) + return objCls; + } + return ""; + } + return ""; + } + + private inferStaticCallReturn(className: String, methodName: String) : String { + String key = "${className}__${methodName}"; + Integer i = 0; + while (i < this.methodRetNames.length()) { + if (this.methodRetNames.get(i) as String == key) { + return this.methodRetClasses.get(i) as String; + } + i = i + 1; + } + // Known built-in returning String + if (className == "Env" && methodName == "platform") { return "String"; } + return ""; + } + + private isIntegerExpr(expr: Expr) : Boolean { + if (expr.kind == ExprKind.INT_LIT) { return true; } + if (expr.kind == ExprKind.BOOL_LIT) { return true; } + if (expr.kind == ExprKind.BINOP) { + Integer bop = expr.op; + if (bop == BinaryOp.ADD || bop == BinaryOp.SUB || + bop == BinaryOp.MUL || bop == BinaryOp.DIV || + bop == BinaryOp.MOD || bop == BinaryOp.EQ || + bop == BinaryOp.NE || bop == BinaryOp.LT || + bop == BinaryOp.LE || bop == BinaryOp.GT || + bop == BinaryOp.GE) { return true; } + } + if (expr.kind == ExprKind.METHOD) { + String m = expr.method; + if (m == "length" || m == "size" || m == "indexOf" || + m == "charCodeAt" || m == "compareTo") { return true; } + } + return false; + } + + private isStringExpr(expr: Expr) : Boolean { + if (expr.kind == ExprKind.STR_LIT) { return true; } + if (expr.kind == ExprKind.STR_INTERP) { return true; } + if (expr.kind == ExprKind.IDENT) { + String cls = this.varClassOf(expr.strVal); + return cls == "String"; + } + if (expr.kind == ExprKind.FIELD) { + String objCls = this.inferClass(expr.object); + Integer clsIdx = this.classIdxOf(objCls); + if (clsIdx >= 0) { + Integer fldIdx = this.fieldIdxOf(clsIdx, expr.field); + if (fldIdx >= 0) { + Integer start = this.classFldStarts.get(clsIdx) as Integer; + String fldCls = this.allFieldClasses.get(start + fldIdx - 1) as String; + return fldCls == "String"; + } + } + return false; + } + if (expr.kind == ExprKind.METHOD) { + // Check method return type via class metadata first + if (this.inferClass(expr) == "String") { return true; } + // Check static method call on known class (e.g., Functions.greet()) + if (expr.object.kind == ExprKind.IDENT) { + String key = "${expr.object.strVal}__${expr.method}"; + Integer ki = 0; + while (ki < this.methodRetNames.length()) { + if (this.methodRetNames.get(ki) as String == key) { + if (this.methodRetClasses.get(ki) as String == "String") { return true; } + } + ki = ki + 1; + } + } + // Fallback: hardcoded built-in string-returning methods + String m = expr.method; + if (m == "concat" || m == "substring" || m == "toString" || + m == "toUpperCase" || m == "toLowerCase" || m == "trim" || + m == "charAt") { return true; } + return false; + } + if (expr.kind == ExprKind.STATIC_CALL) { + return this.inferStaticCallReturn(expr.class_, expr.method) == "String"; + } + if (expr.kind == ExprKind.CTOR) { return expr.class_ == "String"; } + if (expr.kind == ExprKind.CAST) { + if (expr.castTy.kind == TypeKind.STR) { return true; } + if (expr.castTy.kind == TypeKind.NAMED) { return expr.castTy.name == "String"; } + return false; + } + return false; + } + + // ===== Core IR helpers ===== + + private beginFn(name: String, retTy: Integer) { + this.irFns.append(IRFunction(name, retTy)); + } + + private lastFn() : IRFunction { + Integer last = this.irFns.length() - 1; + return this.irFns.get(last) as IRFunction; + } + + private addParamToLast(name: String, ty: Integer) { + IRFunction f = this.lastFn(); + f.addParam(name, ty); + } + + private newReg() : String { + Integer n = this.regCnt; + this.regCnt = this.regCnt + 1; + return "t${n}"; + } + + private newLabel(prefix: String) : String { + Integer n = this.labelCnt; + this.labelCnt = this.labelCnt + 1; + return "${prefix}${n}"; + } + + private internStr(content: String) : String { + Integer i = 0; + while (i < this.strConts.length()) { + String c = this.strConts.get(i) as String; + if (c == content) { return this.strNames.get(i) as String; } + i = i + 1; + } + String name = "__str${this.strCnt}"; + this.strCnt = this.strCnt + 1; + this.strNames.append(name); + this.strConts.append(content); + return name; + } + + private emit(instr: IRInstr) { + IRFunction f = this.lastFn(); + f.instrs.append(instr); + } + + private varLookup(name: String) : String { + Integer i = 0; + while (i < this.varNames.length()) { + String n = this.varNames.get(i) as String; + if (n == name) { return this.varRegs.get(i) as String; } + i = i + 1; + } + return ""; + } + + private varDefine(name: String, reg: String) { + Integer i = 0; + while (i < this.varNames.length()) { + String n = this.varNames.get(i) as String; + if (n == name) { this.varRegs.set(i, reg); return; } + i = i + 1; + } + this.varNames.append(name); + this.varRegs.append(reg); + this.varClassNames.append(""); + this.trackScopedVar(name); + } + + private resetFnContext() { + this.varNames = List(); + this.varRegs = List(); + this.varClassNames = List(); + this.scopeStack = List(); + this.regCnt = 0; + this.curClass = ""; + this.thisReg = ""; + } + + // ===== Scope management ===== + + private pushScope() { + this.scopeStack.append(List()); + } + + private trackScopedVar(name: String) { + Integer n = this.scopeStack.length(); + if (n > 0) { + List top = this.scopeStack.get(n - 1) as List; + top.append(name); + } + } + + private popScope() { + Integer n = this.scopeStack.length(); + if (n == 0) { return; } + List vars = this.scopeStack.get(n - 1) as List; + Integer i = vars.length(); + while (i > 0) { + i = i - 1; + String vn = vars.get(i) as String; + // TODO: replace magic-string "__this" with ownership metadata model + if (vn == "__this") { continue; } + String vc = this.varClassOf(vn); + Integer vci = this.classIdxOf(vc); + if (vci >= 0) { + String vr = this.varLookup(vn); + if (vr != "") { + this.emitRelease(IRValue.reg(vr, IRType.I64), vci); + } + } + } + this.scopeStack.removeAt(n - 1); + } + + private emitUnwindToDepth(targetDepth: Integer) { + Integer n = this.scopeStack.length(); + while (n > targetDepth) { + this.popScope(); + n = this.scopeStack.length(); + } + } + + // ===== Heap + field helpers ===== + + private emitHeapAlloc(sizeVal: IRValue) : IRValue { + if (this.linux) { + // mmap(NULL, size, PROT_READ|PROT_WRITE=3, MAP_PRIVATE|MAP_ANONYMOUS=34, -1, 0) + String allocReg = this.newReg(); + IRValue allocDst = IRValue.reg(allocReg, IRType.PTR); + List mmapArgs = List(); + mmapArgs.append(IRValue.ofInt(0, IRType.I64)); + mmapArgs.append(sizeVal); + mmapArgs.append(IRValue.ofInt(3, IRType.I64)); + mmapArgs.append(IRValue.ofInt(34, IRType.I64)); + mmapArgs.append(IRValue.ofInt(-1, IRType.I64)); + mmapArgs.append(IRValue.ofInt(0, IRType.I64)); + this.emit(IRInstr.syscall(allocDst, 9, mmapArgs)); + return allocDst; + } + String phReg = this.newReg(); + IRValue phDst = IRValue.reg(phReg, IRType.I64); + List phArgs = List(); + this.emit(IRInstr.call(phDst, "__ext__GetProcessHeap", phArgs)); + String allocReg = this.newReg(); + IRValue allocDst = IRValue.reg(allocReg, IRType.PTR); + List haArgs = List(); + haArgs.append(phDst); + haArgs.append(IRValue.ofInt(0, IRType.I64)); + haArgs.append(sizeVal); + this.emit(IRInstr.call(allocDst, "__ext__HeapAlloc", haArgs)); + return allocDst; + } + + private emitHeapFree(objPtr: IRValue, clsIdx: Integer) { + Integer sz = this.classSizeBytes(clsIdx); + if (this.linux) { + List munmapArgs = List(); + munmapArgs.append(objPtr); + munmapArgs.append(IRValue.ofInt(sz, IRType.I64)); + String freeReg = this.newReg(); + IRValue freeDst = IRValue.reg(freeReg, IRType.VOID); + this.emit(IRInstr.syscall(freeDst, 11, munmapArgs)); + } else { + String phReg = this.newReg(); + IRValue phDst = IRValue.reg(phReg, IRType.I64); + List phArgs = List(); + this.emit(IRInstr.call(phDst, "__ext__GetProcessHeap", phArgs)); + List hfArgs = List(); + hfArgs.append(phDst); + hfArgs.append(IRValue.ofInt(0, IRType.I64)); + hfArgs.append(objPtr); + String freeReg = this.newReg(); + IRValue freeDst = IRValue.reg(freeReg, IRType.VOID); + this.emit(IRInstr.call(freeDst, "__ext__HeapFree", hfArgs)); + } + } + + private emitFieldPtr(objVal: IRValue, offset: Integer) : IRValue { + String pReg = this.newReg(); + IRValue pDst = IRValue.reg(pReg, IRType.PTR); + // Compute field pointer via MOV + ADD + this.emit(IRInstr.mov(pDst, objVal)); + this.emit(IRInstr.binop(IROpcode.ADD, pDst, pDst, IRValue.ofInt(offset, IRType.I64))); + // Extend objVal live range past pDst def to prevent register alias. + // The allocator must not assign pDst the same physical register as live objVal. + String guardR = this.newReg(); + this.emit(IRInstr.binop(IROpcode.ADD, IRValue.reg(guardR, IRType.I64), objVal, IRValue.ofInt(0, IRType.I64))); + return pDst; + } + + private emitFieldLoad(objVal: IRValue, offset: Integer) : IRValue { + IRValue ptr = this.emitFieldPtr(objVal, offset); + String vReg = this.newReg(); + IRValue vDst = IRValue.reg(vReg, IRType.I64); + this.emit(IRInstr.load(vDst, ptr, IRType.I64)); + return vDst; + } + + // Safe variants: load/store from pre-computed field pointer (no emitFieldPtr call) + private emitFieldLoadS(ptr: IRValue) : IRValue { + String vReg = this.newReg(); + IRValue vDst = IRValue.reg(vReg, IRType.I64); + this.emit(IRInstr.load(vDst, ptr, IRType.I64)); + return vDst; + } + + private emitFieldStore(objVal: IRValue, offset: Integer, val: IRValue) { + IRValue ptr = this.emitFieldPtr(objVal, offset); + this.emit(IRInstr.store(val, ptr, IRType.I64)); + } + + private emitFieldStoreS(ptr: IRValue, val: IRValue) { + this.emit(IRInstr.store(val, ptr, IRType.I64)); + } + + // ===== ARC retain helper ===== + + private emitRetain(objVal: IRValue, clsIdx: Integer) { + // Null guard: retain(null) is no-op (prevents SIGSEGV when field param is null) + String nullSkipLbl = this.newLabel("ret_null_skip"); + this.emit(IRInstr.cmp(objVal, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.branch(IROpcode.JE, nullSkipLbl)); + + // Inline retain: inc [obj + refCountOff] + // Compute refCount field pointer ONCE to avoid emitFieldPtr double-call reg clobber + IRValue rcPtr = this.emitFieldPtr(objVal, this.refCountOff(clsIdx)); + String curR = this.newReg(); + IRValue curRef = IRValue.reg(curR, IRType.I64); + this.emit(IRInstr.load(curRef, rcPtr, IRType.I64)); + String nr = this.newReg(); + IRValue newRef = IRValue.reg(nr, IRType.I64); + this.emit(IRInstr.binop(IROpcode.ADD, newRef, curRef, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.store(newRef, rcPtr, IRType.I64)); + + this.emit(IRInstr.label(nullSkipLbl)); + } + + // ===== Cached teardown routine generation ===== + + private needsTeardown(clsIdx: Integer) : Boolean { + Integer currIdx = clsIdx; + while (currIdx >= 0) { + Integer start = this.classFldStarts.get(currIdx) as Integer; + Integer count = this.classFldCounts.get(currIdx) as Integer; + Integer fi = 0; + while (fi < count) { + String fldCls = this.allFieldClasses.get(start + fi) as String; + if (this.classIdxOf(fldCls) >= 0) { return true; } + fi = fi + 1; + } + currIdx = this.classParents.get(currIdx) as Integer; + } + return false; + } + + private emitTeardownBody(clsIdx: Integer) { + // obj parameter = first param (RDI on Linux, RCX on Windows) + IRValue objParam = IRValue.reg("obj", IRType.PTR); + + Integer currIdx = clsIdx; + while (currIdx >= 0) { + Integer start = this.classFldStarts.get(currIdx) as Integer; + Integer count = this.classFldCounts.get(currIdx) as Integer; + Integer fi = count; + while (fi > 0) { + fi = fi - 1; + String fldCls = this.allFieldClasses.get(start + fi) as String; + Integer fldClsIdx = this.classIdxOf(fldCls); + if (fldClsIdx >= 0) { + // Load field value + IRValue fldVal = this.emitFieldLoad(objParam, (fi + 1) * 8); + + // Null guard for field + String fldSkipLbl = this.newLabel("td_fld_skip"); + this.emit(IRInstr.cmp(fldVal, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.branch(IROpcode.JE, fldSkipLbl)); + + // Decrement field refcount (compute ptr once to avoid reg clobber) + IRValue fldRCPtr = this.emitFieldPtr(fldVal, this.refCountOff(fldClsIdx)); + IRValue fldRC = this.emitFieldLoadS(fldRCPtr); + String nr = this.newReg(); + IRValue newRC = IRValue.reg(nr, IRType.I64); + this.emit(IRInstr.binop(IROpcode.SUB, newRC, fldRC, IRValue.ofInt(1, IRType.I64))); + this.emitFieldStoreS(fldRCPtr, newRC); + + // Conditional free: only when field refcount reaches 0 + this.emit(IRInstr.cmp(newRC, IRValue.ofInt(0, IRType.I64))); + String freeSkipLbl = this.newLabel("td_free_skip"); + this.emit(IRInstr.branch(IROpcode.JNE, freeSkipLbl)); + + // CALL child teardown (may be same class → recursive, safe via rc guard) + String childTd = this.teardownLabels.get(fldClsIdx) as String; + if (childTd != "") { + List args = List(); + args.append(fldVal); + String dr = this.newReg(); + this.emit(IRInstr.call(IRValue.reg(dr, IRType.I64), childTd, args)); + } + + // Heap free child + this.emitHeapFree(fldVal, fldClsIdx); + this.emit(IRInstr.label(freeSkipLbl)); + this.emit(IRInstr.label(fldSkipLbl)); + } + } + currIdx = this.classParents.get(currIdx) as Integer; + } + + this.emit(IRInstr.retVoid()); + } + + private generateTeardownRoutines() { + Integer i = 0; + while (i < this.classNames.length()) { + if (this.needsTeardown(i)) { + String label = "__arimo_td_${i}"; + this.teardownLabels.set(i, label); + this.beginFn(label, IRType.VOID); + this.addParamToLast("obj", IRType.PTR); + this.emitTeardownBody(i); + } + i = i + 1; + } + } + + // ===== ARC release helper ===== + + private emitRelease(objVal: IRValue, clsIdx: Integer) { + // Null guard: release(null) is no-op (prevents SIGSEGV on x=null → scope exit) + String nullSkipLbl = this.newLabel("rel_null_skip"); + this.emit(IRInstr.cmp(objVal, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.branch(IROpcode.JE, nullSkipLbl)); + + // Inline release: dec [obj + refCountOff] + // Compute refCount field pointer ONCE to avoid emitFieldPtr double-call reg clobber + IRValue rcPtr2 = this.emitFieldPtr(objVal, this.refCountOff(clsIdx)); + String curR2 = this.newReg(); + IRValue curRef2 = IRValue.reg(curR2, IRType.I64); + this.emit(IRInstr.load(curRef2, rcPtr2, IRType.I64)); + String nr = this.newReg(); + IRValue newRef = IRValue.reg(nr, IRType.I64); + this.emit(IRInstr.binop(IROpcode.SUB, newRef, curRef2, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.store(newRef, rcPtr2, IRType.I64)); + + // Conditional free: only when refcount reaches 0 + this.emit(IRInstr.cmp(newRef, IRValue.ofInt(0, IRType.I64))); + String freeSkipLbl = this.newLabel("rel_free_skip"); + this.emit(IRInstr.branch(IROpcode.JNE, freeSkipLbl)); + + // Cached teardown routine: field teardown generated once per class, + // called via runtime CALL (replaces exponential inline expansion). + String tdLabel = this.teardownLabels.get(clsIdx) as String; + if (tdLabel != "") { + List tdArgs = List(); + tdArgs.append(objVal); + String tdReg = this.newReg(); + this.emit(IRInstr.call(IRValue.reg(tdReg, IRType.I64), tdLabel, tdArgs)); + } + + this.emitHeapFree(objVal, clsIdx); + this.emit(IRInstr.label(freeSkipLbl)); + + this.emit(IRInstr.label(nullSkipLbl)); + } + + // ===== Condition lowering ===== + + private lowerCond(expr: Expr) : Integer { + if (expr.kind == ExprKind.BINOP) { + Integer bop = expr.op; + if (bop == BinaryOp.EQ || bop == BinaryOp.NE || + bop == BinaryOp.LT || bop == BinaryOp.LE || + bop == BinaryOp.GT || bop == BinaryOp.GE) { + IRValue lv = this.lowerExpr(expr.left); + IRValue rv = this.lowerExpr(expr.right); + this.emit(IRInstr.cmp(lv, rv)); + if (bop == BinaryOp.EQ) { return IROpcode.JNE; } + if (bop == BinaryOp.NE) { return IROpcode.JE; } + if (bop == BinaryOp.LT) { return IROpcode.JGE; } + if (bop == BinaryOp.LE) { return IROpcode.JG; } + if (bop == BinaryOp.GT) { return IROpcode.JLE; } + return IROpcode.JL; + } + } + IRValue cv = this.lowerExpr(expr); + this.emit(IRInstr.cmp(cv, IRValue.ofInt(0, IRType.I64))); + return IROpcode.JE; + } + + // ===== Statement lowering ===== + + private needsReturnRetain(expr: Expr) : Boolean { + if (expr.kind == ExprKind.IDENT) { return true; } + if (expr.kind == ExprKind.FIELD) { return true; } + // TODO: extend to INDEX/DEREF when indexed/deref field access is lowered + return false; + } + + private lowerStmt(stmt: Stmt) { + if (stmt.kind == StmtKind.RETURN) { + if (stmt.expr.kind != ExprKind.NULL_LIT) { + IRValue rv = this.lowerExpr(stmt.expr); + // ARC: retain ref-type return value before scope release + if (this.needsReturnRetain(stmt.expr)) { + String retCls = this.inferClass(stmt.expr); + Integer retClsIdx = this.classIdxOf(retCls); + if (retClsIdx >= 0) { this.emitRetain(rv, retClsIdx); } + } + // Full scope unwind + this.emitUnwindToDepth(0); + this.emit(IRInstr.ret(rv)); + } else { + this.emitUnwindToDepth(0); + this.emit(IRInstr.retVoid()); + } + return; + } + + if (stmt.kind == StmtKind.EXPR) { + this.lowerExpr(stmt.expr); + return; + } + + if (stmt.kind == StmtKind.VAR_DECL) { + String reg = this.newReg(); + IRValue dst = IRValue.reg(reg, IRType.I64); + String varCls = this.tyToClass(stmt.ty); + if (stmt.initExpr.kind != ExprKind.NULL_LIT) { + IRValue src = this.lowerExpr(stmt.initExpr); + // ARC: retain ref-type initializers (let c = a) + if (stmt.initExpr.kind == ExprKind.IDENT) { + String initCls = this.inferClass(stmt.initExpr); + Integer initClsIdx = this.classIdxOf(initCls); + if (initClsIdx >= 0) { this.emitRetain(src, initClsIdx); } + } + this.emit(IRInstr.mov(dst, src)); + if (varCls == "") { varCls = this.inferClass(stmt.initExpr); } + } else { + this.emit(IRInstr.mov(dst, IRValue.ofInt(0, IRType.I64))); + } + this.varDefine(stmt.name, reg); + if (varCls != "") { this.varSetClass(stmt.name, varCls); } + return; + } + + if (stmt.kind == StmtKind.IF) { + String endL = this.newLabel("if_end"); + String nextL = this.newLabel("if_else"); + Integer jmpOp = this.lowerCond(stmt.cond); + this.emit(IRInstr.branch(jmpOp, nextL)); + this.lowerBody(stmt.thenBody); + this.emit(IRInstr.jmp(endL)); + this.emit(IRInstr.label(nextL)); + Integer ei = 0; + while (ei < stmt.elseIfs.length()) { + ElseIfBranch eib = stmt.elseIfs.get(ei) as ElseIfBranch; + String afterL = this.newLabel("if_else"); + Integer jopEI = this.lowerCond(eib.cond); + this.emit(IRInstr.branch(jopEI, afterL)); + this.lowerBody(eib.body); + this.emit(IRInstr.jmp(endL)); + this.emit(IRInstr.label(afterL)); + ei = ei + 1; + } + if (stmt.elseBody != null && stmt.elseBody.length() > 0) { + this.lowerBody(stmt.elseBody); + } + this.emit(IRInstr.label(endL)); + return; + } + + if (stmt.kind == StmtKind.WHILE) { + String loopL = this.newLabel("while_loop"); + String breakL = this.newLabel("while_brk"); + String savedBreak = this.breakLbl; + String savedCont = this.contLbl; + Integer savedBreakDepth = this.breakDepth; + Integer savedContDepth = this.contDepth; + this.breakLbl = breakL; + this.contLbl = loopL; + this.breakDepth = this.scopeStack.length(); + this.contDepth = this.scopeStack.length(); + this.emit(IRInstr.label(loopL)); + Integer jmpOp = this.lowerCond(stmt.cond); + this.emit(IRInstr.branch(jmpOp, breakL)); + this.lowerBody(stmt.body); + this.emit(IRInstr.jmp(loopL)); + this.emit(IRInstr.label(breakL)); + this.breakLbl = savedBreak; + this.contLbl = savedCont; + this.breakDepth = savedBreakDepth; + this.contDepth = savedContDepth; + return; + } + + if (stmt.kind == StmtKind.FOR) { + if (stmt.forInit != null) { this.lowerStmt(stmt.forInit); } + String loopL = this.newLabel("for_loop"); + String breakL = this.newLabel("for_brk"); + String contL = this.newLabel("for_upd"); + String savedBreak = this.breakLbl; + String savedCont = this.contLbl; + Integer savedBreakDepth = this.breakDepth; + Integer savedContDepth = this.contDepth; + this.breakLbl = breakL; + this.contLbl = contL; + this.breakDepth = this.scopeStack.length(); + this.contDepth = this.scopeStack.length(); + this.emit(IRInstr.label(loopL)); + if (stmt.forCond != null) { + Integer jop = this.lowerCond(stmt.forCond); + this.emit(IRInstr.branch(jop, breakL)); + } + this.lowerBody(stmt.forBody); + this.emit(IRInstr.label(contL)); + if (stmt.forStep != null) { this.lowerExpr(stmt.forStep); } + this.emit(IRInstr.jmp(loopL)); + this.emit(IRInstr.label(breakL)); + this.breakLbl = savedBreak; + this.contLbl = savedCont; + this.breakDepth = savedBreakDepth; + this.contDepth = savedContDepth; + return; + } + + if (stmt.kind == StmtKind.FOR_EACH) { + IRValue iterVal = this.lowerExpr(stmt.iterable); + String iReg = this.newReg(); + IRValue iDst = IRValue.reg(iReg, IRType.I64); + this.emit(IRInstr.mov(iDst, IRValue.ofInt(0, IRType.I64))); + this.varDefine("__fe_i_${iReg}", iReg); + String loopL = this.newLabel("fe_loop"); + String breakL = this.newLabel("fe_brk"); + String savedBreak = this.breakLbl; + String savedCont = this.contLbl; + Integer savedBreakDepth = this.breakDepth; + Integer savedContDepth = this.contDepth; + this.breakLbl = breakL; + this.contLbl = loopL; + this.breakDepth = this.scopeStack.length(); + this.contDepth = this.scopeStack.length(); + this.emit(IRInstr.label(loopL)); + String lenReg = this.newReg(); + IRValue lenDst = IRValue.reg(lenReg, IRType.I64); + List lenArgs = List(); + lenArgs.append(iterVal); + this.emit(IRInstr.call(lenDst, "__arimo_list_length", lenArgs)); + this.emit(IRInstr.cmp(iDst, lenDst)); + this.emit(IRInstr.branch(IROpcode.JGE, breakL)); + String itemReg = this.newReg(); + IRValue itemDst = IRValue.reg(itemReg, IRType.I64); + List getArgs = List(); + getArgs.append(iterVal); + getArgs.append(iDst); + this.emit(IRInstr.call(itemDst, "__arimo_list_get", getArgs)); + this.varDefine(stmt.iterName, itemReg); + String iterCls = this.tyToClass(stmt.iterTy); + if (iterCls != "") { this.varSetClass(stmt.iterName, iterCls); } + this.lowerBody(stmt.body); + this.emit(IRInstr.binop(IROpcode.ADD, iDst, iDst, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.jmp(loopL)); + this.emit(IRInstr.label(breakL)); + this.breakLbl = savedBreak; + this.contLbl = savedCont; + this.breakDepth = savedBreakDepth; + this.contDepth = savedContDepth; + return; + } + + if (stmt.kind == StmtKind.BREAK) { + this.emitUnwindToDepth(this.breakDepth); + this.emit(IRInstr.jmp(this.breakLbl)); + return; + } + if (stmt.kind == StmtKind.CONTINUE) { + this.emitUnwindToDepth(this.contDepth); + this.emit(IRInstr.jmp(this.contLbl)); + return; + } + if (stmt.kind == StmtKind.BLOCK) { this.pushScope(); this.lowerBody(stmt.body); this.popScope(); return; } + if (stmt.kind == StmtKind.THROW) { this.lowerExpr(stmt.expr); return; } + if (stmt.kind == StmtKind.TRY) { this.lowerBody(stmt.tryBody); return; } + } + + private lowerBody(stmts: List) { + Integer i = 0; + while (i < stmts.length()) { + Stmt s = stmts.get(i) as Stmt; + this.lowerStmt(s); + i = i + 1; + } + } + + // ===== Expression lowering ===== + + private lowerExpr(expr: Expr) : IRValue { + if (expr.kind == ExprKind.INT_LIT) { return IRValue.ofInt(expr.intVal, IRType.I64); } + if (expr.kind == ExprKind.CHAR_LIT) { return IRValue.ofInt(expr.intVal, IRType.I64); } + + if (expr.kind == ExprKind.BOOL_LIT) { + if (expr.boolVal) { return IRValue.ofInt(1, IRType.I64); } + return IRValue.ofInt(0, IRType.I64); + } + + if (expr.kind == ExprKind.NULL_LIT) { return IRValue.ofInt(0, IRType.I64); } + + if (expr.kind == ExprKind.STR_LIT) { + String name = this.internStr(expr.strVal); + return IRValue.global(name); + } + + if (expr.kind == ExprKind.IDENT) { + String reg = this.varLookup(expr.strVal); + if (reg != "") { return IRValue.reg(reg, IRType.I64); } + return IRValue.ofInt(0, IRType.I64); + } + + if (expr.kind == ExprKind.THIS) { + if (this.thisReg != "") { return IRValue.reg(this.thisReg, IRType.PTR); } + return IRValue.ofInt(0, IRType.I64); + } + + if (expr.kind == ExprKind.FIELD) { + IRValue objVal = this.lowerExpr(expr.object); + String objCls = this.inferClass(expr.object); + Integer clsIdx = this.classIdxOf(objCls); + if (clsIdx >= 0) { + Integer fldIdx = this.fieldIdxOf(clsIdx, expr.field); + if (fldIdx >= 0) { return this.emitFieldLoad(objVal, fldIdx * 8); } + } + return this.emitFieldLoad(objVal, 0); + } + + if (expr.kind == ExprKind.NULL_SAFE) { + if (expr.args == null || expr.args.length() == 0) { + IRValue objVal = this.lowerExpr(expr.object); + String objCls = this.inferClass(expr.object); + Integer clsIdx = this.classIdxOf(objCls); + if (clsIdx >= 0) { + Integer fldIdx = this.fieldIdxOf(clsIdx, expr.field); + if (fldIdx >= 0) { return this.emitFieldLoad(objVal, fldIdx * 8); } + } + return this.emitFieldLoad(objVal, 0); + } + return this.lowerMethodCall(expr.object, expr.field, expr.args); + } + + if (expr.kind == ExprKind.CTOR) { + return this.lowerCtor(expr.class_, expr.args); + } + + if (expr.kind == ExprKind.STATIC_CALL) { + return this.lowerStaticCall(expr.class_, expr.method, expr.args); + } + + if (expr.kind == ExprKind.METHOD) { + return this.lowerMethodCall(expr.object, expr.method, expr.args); + } + + if (expr.kind == ExprKind.BINOP) { + return this.lowerBinop(expr); + } + + if (expr.kind == ExprKind.UNARY) { + IRValue operand = this.lowerExpr(expr.operand); + String dr = this.newReg(); + IRValue dst = IRValue.reg(dr, IRType.I64); + if (expr.op == 1) { this.emit(IRInstr.unop(IROpcode.NEG, dst, operand)); } + else if (expr.op == 2) { this.emit(IRInstr.unop(IROpcode.NOT, dst, operand)); } + else if (expr.op == 4 || expr.op == 5 || expr.op == 6 || expr.op == 7) { + if (expr.operand.kind == ExprKind.IDENT) { + String varName = expr.operand.strVal; + String oldReg = this.varLookup(varName); + if (oldReg != "") { + IRValue inPlace = IRValue.reg(oldReg, IRType.I64); + if (expr.op == 6 || expr.op == 7) { + // POST: copy old value to dst, then update oldReg in-place + this.emit(IRInstr.mov(dst, inPlace)); + if (expr.op == 6) { this.emit(IRInstr.binop(IROpcode.ADD, inPlace, inPlace, IRValue.ofInt(1, IRType.I64))); } + else { this.emit(IRInstr.binop(IROpcode.SUB, inPlace, inPlace, IRValue.ofInt(1, IRType.I64))); } + } else { + // PRE: update oldReg in-place, return it (now holds new value) + if (expr.op == 4) { this.emit(IRInstr.binop(IROpcode.ADD, inPlace, inPlace, IRValue.ofInt(1, IRType.I64))); } + else { this.emit(IRInstr.binop(IROpcode.SUB, inPlace, inPlace, IRValue.ofInt(1, IRType.I64))); } + return inPlace; + } + } else { + this.emit(IRInstr.mov(dst, operand)); + } + } else if (expr.operand.kind == ExprKind.FIELD) { + String fldName = expr.operand.field; Expr objExpr = expr.operand.object; + String objCls = this.inferClass(objExpr); Integer clsIdx = this.classIdxOf(objCls); + if (clsIdx >= 0) { + Integer fldIdx = this.fieldIdxOf(clsIdx, fldName); + if (fldIdx >= 0) { + Integer fldOff = fldIdx * 8; + IRValue objPtr = this.lowerExpr(objExpr); + IRValue fldPtr = this.emitFieldPtr(objPtr, fldOff); + String oldReg = this.newReg(); IRValue oldVal = IRValue.reg(oldReg, IRType.I64); + this.emit(IRInstr.load(oldVal, fldPtr, IRType.I64)); + if (expr.op == 6 || expr.op == 7) { + this.emit(IRInstr.mov(dst, oldVal)); + String newReg = this.newReg(); IRValue newVal = IRValue.reg(newReg, IRType.I64); + if (expr.op == 6) { this.emit(IRInstr.binop(IROpcode.ADD, newVal, oldVal, IRValue.ofInt(1, IRType.I64))); } + else { this.emit(IRInstr.binop(IROpcode.SUB, newVal, oldVal, IRValue.ofInt(1, IRType.I64))); } + this.emit(IRInstr.store(newVal, fldPtr, IRType.I64)); + } else { + String newReg = this.newReg(); IRValue newVal = IRValue.reg(newReg, IRType.I64); + if (expr.op == 4) { this.emit(IRInstr.binop(IROpcode.ADD, newVal, oldVal, IRValue.ofInt(1, IRType.I64))); } + else { this.emit(IRInstr.binop(IROpcode.SUB, newVal, oldVal, IRValue.ofInt(1, IRType.I64))); } + this.emit(IRInstr.store(newVal, fldPtr, IRType.I64)); + this.emit(IRInstr.mov(dst, newVal)); + } + } else { this.emit(IRInstr.mov(dst, operand)); } + } else { this.emit(IRInstr.mov(dst, operand)); } + } else { + this.emit(IRInstr.mov(dst, operand)); + } + } + else { this.emit(IRInstr.mov(dst, operand)); } + return dst; + } + + if (expr.kind == ExprKind.CAST) { + IRValue src = this.lowerExpr(expr.operand); + String dr = this.newReg(); + IRValue dst = IRValue.reg(dr, IRType.I64); + this.emit(IRInstr.mov(dst, src)); + return dst; + } + + if (expr.kind == ExprKind.TERNARY) { + String falseL = this.newLabel("tern_f"); + String endL = this.newLabel("tern_e"); + String resReg = this.newReg(); + IRValue resDst = IRValue.reg(resReg, IRType.I64); + Integer jop = this.lowerCond(expr.cond); + this.emit(IRInstr.branch(jop, falseL)); + IRValue tv = this.lowerExpr(expr.then_); + this.emit(IRInstr.mov(resDst, tv)); + this.emit(IRInstr.jmp(endL)); + this.emit(IRInstr.label(falseL)); + IRValue fv = this.lowerExpr(expr.else_); + this.emit(IRInstr.mov(resDst, fv)); + this.emit(IRInstr.label(endL)); + return resDst; + } + + if (expr.kind == ExprKind.NULL_COALESCE) { + String notNullL = this.newLabel("nn_ok"); + String endL = this.newLabel("nn_end"); + String resReg = this.newReg(); + IRValue resDst = IRValue.reg(resReg, IRType.I64); + IRValue lv = this.lowerExpr(expr.left); + this.emit(IRInstr.mov(resDst, lv)); + this.emit(IRInstr.cmp(lv, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.branch(IROpcode.JNE, notNullL)); + IRValue rv = this.lowerExpr(expr.right); + this.emit(IRInstr.mov(resDst, rv)); + this.emit(IRInstr.jmp(endL)); + this.emit(IRInstr.label(notNullL)); + this.emit(IRInstr.label(endL)); + return resDst; + } + + if (expr.kind == ExprKind.STR_INTERP) { + String emptyName = this.internStr(""); + IRValue result = IRValue.global(emptyName); + Integer pi = 0; + while (pi < expr.parts.length()) { + StrPart part = expr.parts.get(pi) as StrPart; + IRValue partVal = IRValue.ofInt(0, IRType.I64); + if (part.isLit) { + String sn = this.internStr(part.lit); + partVal = IRValue.global(sn); + } else { + IRValue rawVal = this.lowerExpr(part.expr); + if (this.isIntegerExpr(part.expr)) { + String toStrReg = this.newReg(); + IRValue toStrDst = IRValue.reg(toStrReg, IRType.PTR); + List tsArgs = List(); + tsArgs.append(rawVal); + this.emit(IRInstr.call(toStrDst, "__arimo_i64_to_str", tsArgs)); + partVal = toStrDst; + } else { + partVal = rawVal; + } + } + String catReg = this.newReg(); + IRValue catDst = IRValue.reg(catReg, IRType.PTR); + List catArgs = List(); + catArgs.append(result); + catArgs.append(partVal); + this.emit(IRInstr.call(catDst, "__arimo_strcat", catArgs)); + result = catDst; + pi = pi + 1; + } + return result; + } + + return IRValue.ofInt(0, IRType.I64); + } + + // ===== Binary op lowering ===== + + private lowerBinop(expr: Expr) : IRValue { + Integer bop = expr.op; + + if (bop == BinaryOp.ASSIGN) { + if (expr.left.kind == ExprKind.IDENT) { + String vReg = this.varLookup(expr.left.strVal); + if (vReg != "") { + IRValue rv = this.lowerExpr(expr.right); + // ARC: retain new value if ref-type variable + if (expr.right.kind == ExprKind.IDENT) { + String rhsCls = this.inferClass(expr.right); + Integer rhsClsIdx = this.classIdxOf(rhsCls); + if (rhsClsIdx >= 0) { this.emitRetain(rv, rhsClsIdx); } + } + // ARC: release old value before overwrite + String oldCls = this.varClassOf(expr.left.strVal); + Integer oldClsIdx = this.classIdxOf(oldCls); + if (oldClsIdx >= 0) { + this.emitRelease(IRValue.reg(vReg, IRType.I64), oldClsIdx); + } + IRValue dst = IRValue.reg(vReg, IRType.I64); + this.emit(IRInstr.mov(dst, rv)); + return dst; + } + } + if (expr.left.kind == ExprKind.FIELD) { + String objCls = this.inferClass(expr.left.object); + Integer clsIdx = this.classIdxOf(objCls); + IRValue objVal = this.lowerExpr(expr.left.object); + Integer fldOff = 0; + Integer fldIdx = -1; + if (clsIdx >= 0) { + fldIdx = this.fieldIdxOf(clsIdx, expr.left.field); + if (fldIdx >= 0) { fldOff = fldIdx * 8; } + } + IRValue rv2 = this.lowerExpr(expr.right); + // ARC: retain new value if ref-type variable (mirrors IDENT case) + if (expr.right.kind == ExprKind.IDENT) { + String rhsCls = this.inferClass(expr.right); + Integer rhsClsIdx = this.classIdxOf(rhsCls); + if (rhsClsIdx >= 0) { this.emitRetain(rv2, rhsClsIdx); } + } + // Compute field pointer ONCE (avoid double-call reg-clobber) + IRValue fldPtr = this.emitFieldPtr(objVal, fldOff); + // ARC: release old field value before overwrite + if (clsIdx >= 0 && fldIdx >= 0) { + Integer start = this.classFldStarts.get(clsIdx) as Integer; + String oldFldCls = this.allFieldClasses.get(start + fldIdx - 1) as String; + Integer oldFldClsIdx = this.classIdxOf(oldFldCls); + if (oldFldClsIdx >= 0) { + String oldReg = this.newReg(); + this.emit(IRInstr.load(IRValue.reg(oldReg, IRType.I64), fldPtr, IRType.I64)); + this.emitRelease(IRValue.reg(oldReg, IRType.I64), oldFldClsIdx); + } + } + this.emit(IRInstr.store(rv2, fldPtr, IRType.I64)); + return IRValue.ofInt(0, IRType.I64); + } + IRValue rv3 = this.lowerExpr(expr.right); + return rv3; + } + + if (bop == BinaryOp.AND || bop == BinaryOp.OR) { + IRValue lv = this.lowerExpr(expr.left); + IRValue rv = this.lowerExpr(expr.right); + String dr = this.newReg(); + IRValue dst = IRValue.reg(dr, IRType.I64); + if (bop == BinaryOp.AND) { this.emit(IRInstr.binop(IROpcode.AND, dst, lv, rv)); } + else { this.emit(IRInstr.binop(IROpcode.OR, dst, lv, rv)); } + return dst; + } + + if (bop == BinaryOp.EQ || bop == BinaryOp.NE || + bop == BinaryOp.LT || bop == BinaryOp.LE || + bop == BinaryOp.GT || bop == BinaryOp.GE) { + IRValue lv = this.lowerExpr(expr.left); + IRValue rv = this.lowerExpr(expr.right); + String trueL = this.newLabel("cmp_t"); + String endL = this.newLabel("cmp_e"); + String dr = this.newReg(); + IRValue dst = IRValue.reg(dr, IRType.I64); + this.emit(IRInstr.cmp(lv, rv)); + Integer trueJmp = IROpcode.JE; + if (bop == BinaryOp.EQ) { trueJmp = IROpcode.JE; } + if (bop == BinaryOp.NE) { trueJmp = IROpcode.JNE; } + if (bop == BinaryOp.LT) { trueJmp = IROpcode.JL; } + if (bop == BinaryOp.LE) { trueJmp = IROpcode.JLE; } + if (bop == BinaryOp.GT) { trueJmp = IROpcode.JG; } + if (bop == BinaryOp.GE) { trueJmp = IROpcode.JGE; } + this.emit(IRInstr.branch(trueJmp, trueL)); + this.emit(IRInstr.mov(dst, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.jmp(endL)); + this.emit(IRInstr.label(trueL)); + this.emit(IRInstr.mov(dst, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.label(endL)); + return dst; + } + + Integer irop = IROpcode.ADD; + if (bop == BinaryOp.ADD) { irop = IROpcode.ADD; } + if (bop == BinaryOp.SUB) { irop = IROpcode.SUB; } + if (bop == BinaryOp.MUL) { irop = IROpcode.MUL; } + if (bop == BinaryOp.DIV) { irop = IROpcode.DIV; } + if (bop == BinaryOp.MOD) { irop = IROpcode.MOD; } + if (bop == BinaryOp.BITAND) { irop = IROpcode.AND; } + if (bop == BinaryOp.BITOR) { irop = IROpcode.OR; } + if (bop == BinaryOp.XOR) { irop = IROpcode.XOR; } + if (bop == BinaryOp.SHL) { irop = IROpcode.SHL; } + if (bop == BinaryOp.SHR) { irop = IROpcode.SHR; } + + IRValue lv2 = this.lowerExpr(expr.left); + IRValue rv2 = this.lowerExpr(expr.right); + String dr2 = this.newReg(); + IRValue dst2 = IRValue.reg(dr2, IRType.I64); + this.emit(IRInstr.binop(irop, dst2, lv2, rv2)); + return dst2; + } + + // ===== Static call lowering ===== + + private lowerStaticCall(class_: String, method: String, args: List) : IRValue { + if (class_ == "IO" && method == "println") { + Expr argExpr = args.get(0) as Expr; + Boolean isStr = this.isStringExpr(argExpr); + if (argExpr.kind == ExprKind.METHOD) { + if (argExpr.object.kind == ExprKind.IDENT && argExpr.object.strVal == "Env") { isStr = true; } + } + if (argExpr.kind == ExprKind.STATIC_CALL) { + if (argExpr.class_ == "Env") { isStr = true; } + } + if (isStr) { + IRValue strArg = this.lowerExpr(argExpr); + List callArgs = List(); + callArgs.append(strArg); + String dr = this.newReg(); + IRValue dst = IRValue.reg(dr, IRType.VOID); + this.emit(IRInstr.call(dst, "__arimo_println", callArgs)); + } else { + IRValue rawVal = this.lowerExpr(argExpr); + List intArgs = List(); + intArgs.append(rawVal); + String dr = this.newReg(); + IRValue dst = IRValue.reg(dr, IRType.VOID); + this.emit(IRInstr.call(dst, "__arimo_println_int", intArgs)); + } + return IRValue.none(); + } + if (class_ == "IO" && method == "print") { + Expr argExpr2 = args.get(0) as Expr; + Boolean isStr2 = this.isStringExpr(argExpr2); + if (argExpr2.kind == ExprKind.METHOD) { + if (argExpr2.object.kind == ExprKind.IDENT && argExpr2.object.strVal == "Env") { isStr2 = true; } + } + if (isStr2) { + IRValue strArg2 = this.lowerExpr(argExpr2); + List callArgs2 = List(); + callArgs2.append(strArg2); + String dr2 = this.newReg(); + IRValue dst2 = IRValue.reg(dr2, IRType.VOID); + this.emit(IRInstr.call(dst2, "__arimo_print", callArgs2)); + } else { + IRValue rawVal2 = this.lowerExpr(argExpr2); + List intArgs2 = List(); + intArgs2.append(rawVal2); + String dr2 = this.newReg(); + IRValue dst2 = IRValue.reg(dr2, IRType.VOID); + this.emit(IRInstr.call(dst2, "__arimo_print_int", intArgs2)); + } + return IRValue.none(); + } + + if (class_ == "IO" && method == "error") { + // IO.error(msg) → print to stderr via write syscall + IRValue errArg = this.lowerExpr(args.get(0) as Expr); + this.emit(IRInstr.call(IRValue.reg(this.newReg(), IRType.VOID), "__arimo_print", List())); + // Also print the message: reuse print helper with stderr fd=2 + String plenR = this.newReg(); + IRValue plenDst = IRValue.reg(plenR, IRType.I64); + List lenA = List(); lenA.append(errArg); + this.emit(IRInstr.call(plenDst, "__arimo_strlen", lenA)); + List wrA = List(); + wrA.append(IRValue.ofInt(2, IRType.I64)); + wrA.append(errArg); + wrA.append(IRValue.reg(plenR, IRType.I64)); + this.emit(IRInstr.syscall(IRValue.reg(this.newReg(), IRType.I64), 1, wrA)); + // newline + List nlA = List(); + nlA.append(IRValue.ofInt(2, IRType.I64)); + nlA.append(IRValue.global(this.internStr("\n"))); + nlA.append(IRValue.ofInt(1, IRType.I64)); + this.emit(IRInstr.syscall(IRValue.reg(this.newReg(), IRType.I64), 1, nlA)); + return IRValue.none(); + } + + // Env built-ins + if (class_ == "Env" && method == "platform") { + String platform = "linux"; + if (!this.linux) { platform = "windows"; } + return IRValue.global(this.internStr(platform)); + } + if (class_ == "Env" && method == "exit") { + Integer code = 0; + if (args.length() > 0) { + Expr e = args.get(0) as Expr; + if (e.kind == ExprKind.INT_LIT) { code = e.intVal; } + } + if (this.linux) { + List scArgs = List(); + scArgs.append(IRValue.ofInt(code, IRType.I64)); + this.emit(IRInstr.syscall(IRValue.none(), 60, scArgs)); + } else { + List ea = List(); + ea.append(IRValue.ofInt(code, IRType.I64)); + String er = this.newReg(); + this.emit(IRInstr.call(IRValue.reg(er, IRType.VOID), "__ext__ExitProcess", ea)); + } + return IRValue.none(); + } + if (class_ == "Env" && method == "args") { + String argcReg = this.newReg(); + this.emit(IRInstr.load(IRValue.reg(argcReg, IRType.I64), IRValue.global("__arimo_argc"), IRType.I64)); + String argvReg = this.newReg(); + this.emit(IRInstr.load(IRValue.reg(argvReg, IRType.PTR), IRValue.global("__arimo_argv"), IRType.PTR)); + List ba = List(); ba.append(IRValue.reg(argcReg, IRType.I64)); ba.append(IRValue.reg(argvReg, IRType.PTR)); + String lr = this.newReg(); this.emit(IRInstr.call(IRValue.reg(lr, IRType.PTR), "__arimo_build_args", ba)); + return IRValue.reg(lr, IRType.PTR); + } + if (class_ == "Env" && method == "exePath") { + String avReg = this.newReg(); + this.emit(IRInstr.load(IRValue.reg(avReg, IRType.PTR), IRValue.global("__arimo_argv"), IRType.PTR)); + String a0Reg = this.newReg(); + this.emit(IRInstr.load(IRValue.reg(a0Reg, IRType.PTR), IRValue.reg(avReg, IRType.PTR), IRType.PTR)); + return IRValue.reg(a0Reg, IRType.PTR); + } + // Generic static call fallback + String fnName = "${class_}__${method}"; + List callArgsFn = List(); + Integer ai = 0; + while (ai < args.length()) { + Expr arg = args.get(ai) as Expr; + callArgsFn.append(this.lowerExpr(arg)); + ai = ai + 1; + } + String dr3 = this.newReg(); + IRValue dst3 = IRValue.reg(dr3, IRType.I64); + this.emit(IRInstr.call(dst3, fnName, callArgsFn)); + return dst3; + } + + // ===== Constructor lowering ===== + + private lowerLinuxLibcCall(name: String, args: List) : IRValue { + // fopen(path, mode) → call shared __arimo_fopen helper + if (name == "fopen") { + IRValue pathVal = this.lowerExpr(args.get(0) as Expr); + IRValue modeVal = this.lowerExpr(args.get(1) as Expr); + String retR = this.newReg(); IRValue ret = IRValue.reg(retR, IRType.I64); + List fargs = List(); fargs.append(pathVal); fargs.append(modeVal); + this.emit(IRInstr.call(ret, "__arimo_fopen", fargs)); + return ret; + } + // fread(buf, size, n, fd) → read(fd, buf, size*n) syscall 0 + if (name == "fread") { + IRValue bufVal = this.lowerExpr(args.get(0) as Expr); + IRValue sizeVal = this.lowerExpr(args.get(1) as Expr); + IRValue nVal = this.lowerExpr(args.get(2) as Expr); + IRValue fdVal = this.lowerExpr(args.get(3) as Expr); + String totR = this.newReg(); + IRValue total = IRValue.reg(totR, IRType.I64); + this.emit(IRInstr.binop(IROpcode.MUL, total, sizeVal, nVal)); + String resR = this.newReg(); + IRValue result = IRValue.reg(resR, IRType.I64); + List rdArgs = List(); + rdArgs.append(fdVal); + rdArgs.append(bufVal); + rdArgs.append(total); + this.emit(IRInstr.syscall(result, 0, rdArgs)); + return result; + } + // fwrite(buf, size, n, fd) → write(fd, buf, size*n) syscall 1 + if (name == "fwrite") { + IRValue bufVal = this.lowerExpr(args.get(0) as Expr); + IRValue sizeVal = this.lowerExpr(args.get(1) as Expr); + IRValue nVal = this.lowerExpr(args.get(2) as Expr); + IRValue fdVal = this.lowerExpr(args.get(3) as Expr); + String totR = this.newReg(); + IRValue total = IRValue.reg(totR, IRType.I64); + this.emit(IRInstr.binop(IROpcode.MUL, total, sizeVal, nVal)); + String resR = this.newReg(); + IRValue result = IRValue.reg(resR, IRType.I64); + List wrArgs = List(); + wrArgs.append(fdVal); + wrArgs.append(bufVal); + wrArgs.append(total); + this.emit(IRInstr.syscall(result, 1, wrArgs)); + return result; + } + // fclose(fd) → close(fd) syscall 3 + if (name == "fclose") { + IRValue fdVal = this.lowerExpr(args.get(0) as Expr); + String clR = this.newReg(); + IRValue clDst = IRValue.reg(clR, IRType.I64); + List clArgs = List(); + clArgs.append(fdVal); + this.emit(IRInstr.syscall(clDst, 3, clArgs)); + String zR = this.newReg(); + IRValue zero = IRValue.reg(zR, IRType.I64); + this.emit(IRInstr.mov(zero, IRValue.ofInt(0, IRType.I64))); + return zero; + } + // fseek(fd, offset, whence) → lseek(fd, offset, whence) syscall 8 + if (name == "fseek") { + IRValue fdVal = this.lowerExpr(args.get(0) as Expr); + IRValue offsetVal = this.lowerExpr(args.get(1) as Expr); + IRValue whenceVal = this.lowerExpr(args.get(2) as Expr); + String skR = this.newReg(); + IRValue skDst = IRValue.reg(skR, IRType.I64); + List skArgs = List(); + skArgs.append(fdVal); + skArgs.append(offsetVal); + skArgs.append(whenceVal); + this.emit(IRInstr.syscall(skDst, 8, skArgs)); + String zR = this.newReg(); + IRValue zero = IRValue.reg(zR, IRType.I64); + this.emit(IRInstr.mov(zero, IRValue.ofInt(0, IRType.I64))); + return zero; + } + // ftell(fd) → lseek(fd, 0, SEEK_CUR=1) syscall 8 + if (name == "ftell") { + IRValue fdVal = this.lowerExpr(args.get(0) as Expr); + String posR = this.newReg(); + IRValue pos = IRValue.reg(posR, IRType.I64); + List ftArgs = List(); + ftArgs.append(fdVal); + ftArgs.append(IRValue.ofInt(0, IRType.I64)); + ftArgs.append(IRValue.ofInt(1, IRType.I64)); + this.emit(IRInstr.syscall(pos, 8, ftArgs)); + return pos; + } + // remove(path) → unlink(path) syscall 87 + if (name == "remove") { + IRValue pathVal = this.lowerExpr(args.get(0) as Expr); + String rmR = this.newReg(); + IRValue rmDst = IRValue.reg(rmR, IRType.I64); + List rmArgs = List(); + rmArgs.append(pathVal); + this.emit(IRInstr.syscall(rmDst, 87, rmArgs)); + return rmDst; + } + // calloc(n, size) → mmap(NULL, n*size, PROT_RW=3, MAP_PRIVATE|ANON=34, -1, 0) syscall 9 + if (name == "calloc") { + IRValue nVal = this.lowerExpr(args.get(0) as Expr); + IRValue sizeVal = this.lowerExpr(args.get(1) as Expr); + String totR = this.newReg(); + IRValue total = IRValue.reg(totR, IRType.I64); + this.emit(IRInstr.binop(IROpcode.MUL, total, nVal, sizeVal)); + String ptrR = this.newReg(); + IRValue ptr = IRValue.reg(ptrR, IRType.PTR); + List mmArgs = List(); + mmArgs.append(IRValue.ofInt(0, IRType.I64)); + mmArgs.append(total); + mmArgs.append(IRValue.ofInt(3, IRType.I64)); + mmArgs.append(IRValue.ofInt(34, IRType.I64)); + mmArgs.append(IRValue.ofInt(-1, IRType.I64)); + mmArgs.append(IRValue.ofInt(0, IRType.I64)); + this.emit(IRInstr.syscall(ptr, 9, mmArgs)); + return ptr; + } + // fputc(c, fd) → stack-alloc 1 byte, store c, write(fd, buf, 1) syscall 1 + if (name == "fputc") { + IRValue cVal = this.lowerExpr(args.get(0) as Expr); + IRValue fdVal = this.lowerExpr(args.get(1) as Expr); + String bufR = this.newReg(); + IRValue buf = IRValue.reg(bufR, IRType.PTR); + this.emit(IRInstr.alloc(buf, 1)); + this.emit(IRInstr.store(cVal, buf, IRType.I8)); + String wR = this.newReg(); + IRValue wDst = IRValue.reg(wR, IRType.I64); + List wArgs = List(); + wArgs.append(fdVal); + wArgs.append(buf); + wArgs.append(IRValue.ofInt(1, IRType.I64)); + this.emit(IRInstr.syscall(wDst, 1, wArgs)); + return cVal; + } + // system(cmd) → __arimo_system helper (fork+execve+waitpid via syscalls) + if (name == "system") { + IRValue cmdVal = this.lowerExpr(args.get(0) as Expr); + String retR = this.newReg(); IRValue ret = IRValue.reg(retR, IRType.I64); + List sa = List(); sa.append(cmdVal); + this.emit(IRInstr.call(ret, "__arimo_system", sa)); + return ret; + } + return IRValue.none(); + } + + private lowerCtor(class_: String, args: List) : IRValue { + if (class_ == "List") { + String lr = this.newReg(); + IRValue lDst = IRValue.reg(lr, IRType.PTR); + List lArgs = List(); + this.emit(IRInstr.call(lDst, "__arimo_list_new", lArgs)); + return lDst; + } + // super(...) call inside constructor: resolve parent class, reuse `this` pointer. + // Does NOT allocate — object is already allocated by the derived class. + // Parser generates class_ = "__super__" (Expr.ctorCall("__super__", ...)) + if (class_ == "__super__") { + Integer myIdx = this.classIdxOf(this.curClass); + Integer parentIdx = -1; + if (myIdx >= 0 && myIdx < this.classParents.length()) { + parentIdx = this.classParents.get(myIdx) as Integer; + } + if (parentIdx < 0) { + IO.println("arc: [FAIL] cannot resolve super constructor for ${this.curClass} — no parent class found"); + return IRValue.ofInt(0, IRType.I64); + } + String parentClass = this.classNames.get(parentIdx) as String; + // `this` is a function parameter (not a varDeclare), use reg directly. + List superArgs = List(); + superArgs.append(IRValue.reg("this", IRType.PTR)); + Integer ai = 0; + while (ai < args.length()) { + Expr arg = args.get(ai) as Expr; + superArgs.append(this.lowerExpr(arg)); + ai = ai + 1; + } + String supInitR = this.newReg(); + IRValue supInitDst = IRValue.reg(supInitR, IRType.VOID); + this.emit(IRInstr.call(supInitDst, "${parentClass}__init", superArgs)); + return IRValue.none(); + } + if (this.linux) { + IRValue libcRes = this.lowerLinuxLibcCall(class_, args); + if (!libcRes.isNone()) { return libcRes; } + } + Integer clsIdx = this.classIdxOf(class_); + Integer sz = 16; + if (clsIdx >= 0) { sz = this.classSizeBytes(clsIdx); } + IRValue objPtr = this.emitHeapAlloc(IRValue.ofInt(sz, IRType.I64)); + if (clsIdx >= 0) { + this.emitFieldStore(objPtr, this.refCountOff(clsIdx), IRValue.ofInt(1, IRType.I64)); + } + List initArgs = List(); + initArgs.append(objPtr); + Integer ai = 0; + while (ai < args.length()) { + Expr arg = args.get(ai) as Expr; + initArgs.append(this.lowerExpr(arg)); + ai = ai + 1; + } + String initRes = this.newReg(); + IRValue initDst = IRValue.reg(initRes, IRType.VOID); + this.emit(IRInstr.call(initDst, "${class_}__init", initArgs)); + return objPtr; + } + + // ===== Method call lowering ===== + + private lowerMethodCall(obj: Expr, method: String, args: List) : IRValue { + if (obj.kind == ExprKind.IDENT) { + if (obj.strVal == "IO") { + return this.lowerStaticCall("IO", method, args); + } + if (obj.strVal == "Env") { + return this.lowerStaticCall("Env", method, args); + } + // Static method call on known class: ClassName.method() + if (this.classIdxOf(obj.strVal) >= 0) { + return this.lowerStaticCall(obj.strVal, method, args); + } + } + + IRValue objVal = this.lowerExpr(obj); + String objCls = this.inferClass(obj); + + // List methods + if (objCls == "List") { + return this.lowerListMethod(objVal, method, args); + } + + // toString() handler — must go before String/List dispatch to avoid + // lowerStringMethod/lowerListMethod fallback generating __str_toString + // which is never defined. + if (method == "toString") { + if (objCls == "String" || this.isStringExpr(obj)) { return IRValue.reg(objVal.name, objVal.ty); } + if (objCls == "" || objCls == "Integer") { + String tsReg = this.newReg(); + IRValue tsDst = IRValue.reg(tsReg, IRType.PTR); + List tsArgs = List(); + tsArgs.append(objVal); + this.emit(IRInstr.call(tsDst, "__arimo_i64_to_str", tsArgs)); + return tsDst; + } + // Unknown type: fall through + } + + // String methods + if (objCls == "String") { + return this.lowerStringMethod(objVal, method, args); + } + + // length() on unknown type: prefer list if we can't tell + if (method == "length") { + if (objCls == "") { + String lr = this.newReg(); + IRValue lDst = IRValue.reg(lr, IRType.I64); + List lArgs = List(); + lArgs.append(objVal); + this.emit(IRInstr.call(lDst, "__arimo_strlen", lArgs)); + return lDst; + } + } + // String method heuristic: call on unknown obj with string-like method names + if (method == "compareTo" || method == "concat" || method == "startsWith" || + method == "endsWith" || method == "substring" || method == "indexOf" || + method == "contains" || method == "charCodeAt"|| method == "charAt" || + method == "isEmpty" || method == "equals" || method == "toUpperCase"|| + method == "toLowerCase" || method == "toLower" || method == "toUpper" || + method == "parseInt" || method == "parseFloat") { + return this.lowerStringMethod(objVal, method, args); + } + + // User class method dispatch + if (objCls != "") { + List callArgsCls = List(); + callArgsCls.append(objVal); + Integer ai = 0; + while (ai < args.length()) { + Expr arg = args.get(ai) as Expr; + callArgsCls.append(this.lowerExpr(arg)); + ai = ai + 1; + } + String dr = this.newReg(); + IRValue dst = IRValue.reg(dr, IRType.I64); + this.emit(IRInstr.call(dst, "${objCls}__${method}", callArgsCls)); + return dst; + } + + // Fallback + List fallbackArgs = List(); + fallbackArgs.append(objVal); + Integer ai2 = 0; + while (ai2 < args.length()) { + Expr arg2 = args.get(ai2) as Expr; + fallbackArgs.append(this.lowerExpr(arg2)); + ai2 = ai2 + 1; + } + String dr4 = this.newReg(); + IRValue dst4 = IRValue.reg(dr4, IRType.I64); + this.emit(IRInstr.call(dst4, "__method_${method}", fallbackArgs)); + return dst4; + } + + private lowerListMethod(objVal: IRValue, method: String, args: List) : IRValue { + if (method == "length" || method == "size") { + String dr = this.newReg(); + IRValue dst = IRValue.reg(dr, IRType.I64); + List a = List(); + a.append(objVal); + this.emit(IRInstr.call(dst, "__arimo_list_length", a)); + return dst; + } + if (method == "append" || method == "add") { + IRValue val = this.lowerExpr(args.get(0) as Expr); + List a = List(); + a.append(objVal); + a.append(val); + String dr = this.newReg(); + IRValue dst = IRValue.reg(dr, IRType.VOID); + this.emit(IRInstr.call(dst, "__arimo_list_append", a)); + return IRValue.none(); + } + if (method == "get") { + IRValue idx = this.lowerExpr(args.get(0) as Expr); + List a = List(); + a.append(objVal); + a.append(idx); + String dr = this.newReg(); + IRValue dst = IRValue.reg(dr, IRType.I64); + this.emit(IRInstr.call(dst, "__arimo_list_get", a)); + return dst; + } + if (method == "set") { + IRValue idx = this.lowerExpr(args.get(0) as Expr); + IRValue val = this.lowerExpr(args.get(1) as Expr); + List a = List(); + a.append(objVal); + a.append(idx); + a.append(val); + String dr = this.newReg(); + IRValue dst = IRValue.reg(dr, IRType.VOID); + this.emit(IRInstr.call(dst, "__arimo_list_set", a)); + return IRValue.none(); + } + if (method == "removeAt") { + IRValue idx = this.lowerExpr(args.get(0) as Expr); + List a = List(); + a.append(objVal); + a.append(idx); + String dr = this.newReg(); + IRValue dst = IRValue.reg(dr, IRType.VOID); + this.emit(IRInstr.call(dst, "__arimo_list_remove_at", a)); + return IRValue.none(); + } + if (method == "compareTo") { + IRValue other = this.lowerExpr(args.get(0) as Expr); + // Simple pointer comparison: 0 if same object, -1 otherwise. + String eqL = this.newLabel("lc_eq"); + String endL = this.newLabel("lc_end"); + String cmpR = this.newReg(); + IRValue cmpD = IRValue.reg(cmpR, IRType.I64); + this.emit(IRInstr.cmp(objVal, other)); + this.emit(IRInstr.branch(IROpcode.JE, eqL)); + this.emit(IRInstr.mov(cmpD, IRValue.ofInt(-1, IRType.I64))); + this.emit(IRInstr.jmp(endL)); + this.emit(IRInstr.label(eqL)); + this.emit(IRInstr.mov(cmpD, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.label(endL)); + return cmpD; + } + // Fallback for other list methods + String dr5 = this.newReg(); + IRValue dst5 = IRValue.reg(dr5, IRType.I64); + List a5 = List(); + a5.append(objVal); + Integer xi = 0; + while (xi < args.length()) { + a5.append(this.lowerExpr(args.get(xi) as Expr)); + xi = xi + 1; + } + this.emit(IRInstr.call(dst5, "__list_${method}", a5)); + return dst5; + } + + private lowerStringMethod(objVal: IRValue, method: String, args: List) : IRValue { + if (method == "length") { + String dr = this.newReg(); + IRValue dst = IRValue.reg(dr, IRType.I64); + List a = List(); + a.append(objVal); + this.emit(IRInstr.call(dst, "__arimo_strlen", a)); + return dst; + } + if (method == "compareTo" || method == "equals") { + IRValue other = this.lowerExpr(args.get(0) as Expr); + List a = List(); + a.append(objVal); + a.append(other); + String dr = this.newReg(); + IRValue dst = IRValue.reg(dr, IRType.I64); + this.emit(IRInstr.call(dst, "__arimo_strcmp", a)); + return dst; + } + if (method == "concat") { + IRValue other = this.lowerExpr(args.get(0) as Expr); + List a = List(); + a.append(objVal); + a.append(other); + String dr = this.newReg(); + IRValue dst = IRValue.reg(dr, IRType.PTR); + this.emit(IRInstr.call(dst, "__arimo_strcat", a)); + return dst; + } + if (method == "startsWith") { + IRValue prefix = this.lowerExpr(args.get(0) as Expr); + List a = List(); + a.append(objVal); + a.append(prefix); + String dr = this.newReg(); + IRValue dst = IRValue.reg(dr, IRType.I64); + this.emit(IRInstr.call(dst, "__arimo_startswith", a)); + return dst; + } + if (method == "endsWith") { + IRValue suffix = this.lowerExpr(args.get(0) as Expr); + List a = List(); + a.append(objVal); + a.append(suffix); + String dr = this.newReg(); + IRValue dst = IRValue.reg(dr, IRType.I64); + this.emit(IRInstr.call(dst, "__arimo_endswith", a)); + return dst; + } + if (method == "substring") { + IRValue from = this.lowerExpr(args.get(0) as Expr); + IRValue to = this.lowerExpr(args.get(1) as Expr); + List a = List(); + a.append(objVal); + a.append(from); + a.append(to); + String dr = this.newReg(); + IRValue dst = IRValue.reg(dr, IRType.PTR); + this.emit(IRInstr.call(dst, "__arimo_substr", a)); + return dst; + } + if (method == "contains") { + IRValue sub = this.lowerExpr(args.get(0) as Expr); + List a = List(); a.append(objVal); a.append(sub); a.append(IRValue.ofInt(0, IRType.I64)); + String dr = this.newReg(); this.emit(IRInstr.call(IRValue.reg(dr, IRType.I64), "__arimo_indexof_from", a)); + String foundR = this.newReg(); IRValue found = IRValue.reg(foundR, IRType.I64); + this.emit(IRInstr.mov(found, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.cmp(IRValue.reg(dr, IRType.I64), IRValue.ofInt(-1, IRType.I64))); + String ctLbl = this.newLabel("ct_done"); + this.emit(IRInstr.branch(IROpcode.JE, ctLbl)); + this.emit(IRInstr.mov(found, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.label(ctLbl)); + return found; + } + if (method == "indexOf") { + IRValue sub = this.lowerExpr(args.get(0) as Expr); + IRValue from = IRValue.ofInt(0, IRType.I64); + if (args.length() > 1) { from = this.lowerExpr(args.get(1) as Expr); } + List a = List(); + a.append(objVal); + a.append(sub); + a.append(from); + String dr = this.newReg(); + IRValue dst = IRValue.reg(dr, IRType.I64); + this.emit(IRInstr.call(dst, "__arimo_indexof_from", a)); + return dst; + } + if (method == "charCodeAt") { + IRValue idx = this.lowerExpr(args.get(0) as Expr); + List a = List(); + a.append(objVal); + a.append(idx); + String dr = this.newReg(); + IRValue dst = IRValue.reg(dr, IRType.I64); + this.emit(IRInstr.call(dst, "__arimo_charcodeat", a)); + return dst; + } + if (method == "charAt") { + IRValue idx = this.lowerExpr(args.get(0) as Expr); + List a = List(); + a.append(objVal); + a.append(idx); + String dr = this.newReg(); + IRValue dst = IRValue.reg(dr, IRType.PTR); + this.emit(IRInstr.call(dst, "__arimo_charat", a)); + return dst; + } + if (method == "isEmpty") { + String dr = this.newReg(); + IRValue dst = IRValue.reg(dr, IRType.I64); + List a = List(); + a.append(objVal); + this.emit(IRInstr.call(dst, "__arimo_strlen", a)); + String eqL = this.newLabel("isemp_eq"); + String endL = this.newLabel("isemp_end"); + String res = this.newReg(); + IRValue resDst = IRValue.reg(res, IRType.I64); + this.emit(IRInstr.cmp(dst, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.branch(IROpcode.JE, eqL)); + this.emit(IRInstr.mov(resDst, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.jmp(endL)); + this.emit(IRInstr.label(eqL)); + this.emit(IRInstr.mov(resDst, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.label(endL)); + return resDst; + } + if (method == "toLowerCase" || method == "toLower") { + String tlReg = this.newReg(); + IRValue tlDst = IRValue.reg(tlReg, IRType.PTR); + List tlArgs = List(); tlArgs.append(objVal); + this.emit(IRInstr.call(tlDst, "__arimo_tolower", tlArgs)); + return tlDst; + } + if (method == "toUpperCase" || method == "toUpper") { + // Not yet implemented — stub returns original + return IRValue.reg(objVal.name, objVal.ty); + } + if (method == "parseInt" || method == "parseFloat") { + // Minimal implementation: iterate chars, accumulate value + String lenR = this.newReg(); + IRValue lenDst = IRValue.reg(lenR, IRType.I64); + List la = List(); la.append(objVal); + this.emit(IRInstr.call(lenDst, "__arimo_strlen", la)); + String resultR = this.newReg(); + IRValue result = IRValue.reg(resultR, IRType.I64); + this.emit(IRInstr.mov(result, IRValue.ofInt(0, IRType.I64))); + String iR = this.newReg(); + IRValue iv = IRValue.reg(iR, IRType.I64); + this.emit(IRInstr.mov(iv, IRValue.ofInt(0, IRType.I64))); + String loopL = this.newLabel("pi_loop"); + String doneL = this.newLabel("pi_done"); + this.emit(IRInstr.label(loopL)); + this.emit(IRInstr.cmp(iv, lenDst)); + this.emit(IRInstr.branch(IROpcode.JGE, doneL)); + String cpR = this.newReg(); + IRValue cp = IRValue.reg(cpR, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, cp, objVal, iv)); + String chR = this.newReg(); + IRValue ch = IRValue.reg(chR, IRType.I64); + this.emit(IRInstr.load(ch, cp, IRType.I8)); + // If ch < '0' or ch > '9', break + String skipL = this.newLabel("pi_skip"); + this.emit(IRInstr.cmp(ch, IRValue.ofInt(48, IRType.I64))); + this.emit(IRInstr.branch(IROpcode.JL, skipL)); + this.emit(IRInstr.cmp(ch, IRValue.ofInt(57, IRType.I64))); + this.emit(IRInstr.branch(IROpcode.JG, skipL)); + // result = result * 10 + (ch - '0') + String tmpR = this.newReg(); + IRValue tmp = IRValue.reg(tmpR, IRType.I64); + this.emit(IRInstr.binop(IROpcode.MUL, tmp, result, IRValue.ofInt(10, IRType.I64))); + String tmp2R = this.newReg(); + IRValue tmp2 = IRValue.reg(tmp2R, IRType.I64); + this.emit(IRInstr.binop(IROpcode.SUB, tmp2, ch, IRValue.ofInt(48, IRType.I64))); + this.emit(IRInstr.binop(IROpcode.ADD, result, tmp, tmp2)); + this.emit(IRInstr.label(skipL)); + this.emit(IRInstr.binop(IROpcode.ADD, iv, iv, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.jmp(loopL)); + this.emit(IRInstr.label(doneL)); + return result; + } + // Fallback + List fa = List(); + fa.append(objVal); + Integer xi = 0; + while (xi < args.length()) { + fa.append(this.lowerExpr(args.get(xi) as Expr)); + xi = xi + 1; + } + String fdr = this.newReg(); + IRValue fdst = IRValue.reg(fdr, IRType.I64); + this.emit(IRInstr.call(fdst, "__str_${method}", fa)); + return fdst; + } + + // ===== Method / constructor body lowering ===== + + private lowerMethod(className: String, md: MethodDecl) { + String fnName = "${className}__${md.name}"; + this.beginFn(fnName, IRType.I64); + this.resetFnContext(); + this.curClass = className; + + if (!md.isStatic) { + this.addParamToLast("__this", IRType.PTR); + this.thisReg = "__this"; + } + Integer pi = 0; + while (pi < md.params.length()) { + Param p = md.params.get(pi) as Param; + this.addParamToLast(p.name, IRType.I64); + pi = pi + 1; + } + + this.emit(IRInstr.label("entry")); + + if (!md.isStatic) { + this.varDefine("__this", "__this"); + this.varSetClass("__this", className); + } + Integer pi2 = 0; + while (pi2 < md.params.length()) { + Param p2 = md.params.get(pi2) as Param; + this.varDefine(p2.name, p2.name); + String pCls = this.tyToClass(p2.ty); + if (pCls != "") { this.varSetClass(p2.name, pCls); } + pi2 = pi2 + 1; + } + + this.lowerBody(md.body); + IRFunction curFn = this.lastFn(); + Boolean needsRet = true; + if (curFn.instrs.length() > 0) { + IRInstr lastInstr = curFn.instrs.get(curFn.instrs.length() - 1) as IRInstr; + if (lastInstr.op == IROpcode.RET) { needsRet = false; } + } + if (needsRet) { + this.emit(IRInstr.ret(IRValue.ofInt(0, IRType.I64))); + } + + if (md.isStatic && md.name == "main" && this.mainFn == "") { + this.mainFn = fnName; + } + } + + private lowerConstructor(className: String, ctor: ConstructorDecl) { + String fnName = "${className}__init"; + this.beginFn(fnName, IRType.VOID); + this.resetFnContext(); + this.curClass = className; + + this.addParamToLast("__this", IRType.PTR); + this.thisReg = "__this"; + Integer pi = 0; + while (pi < ctor.params.length()) { + Param p = ctor.params.get(pi) as Param; + this.addParamToLast(p.name, IRType.I64); + pi = pi + 1; + } + + this.emit(IRInstr.label("entry")); + this.varDefine("__this", "__this"); + this.varSetClass("__this", className); + Integer pi2 = 0; + while (pi2 < ctor.params.length()) { + Param p2 = ctor.params.get(pi2) as Param; + this.varDefine(p2.name, p2.name); + String pCls = this.tyToClass(p2.ty); + if (pCls != "") { this.varSetClass(p2.name, pCls); } + pi2 = pi2 + 1; + } + + this.lowerBody(ctor.body); + this.emit(IRInstr.retVoid()); + } + + private lowerClass(cd: ClassDecl) { + if (cd.ctor != null) { + ConstructorDecl ctor = cd.ctor as ConstructorDecl; + if (ctor.body != null) { + this.lowerConstructor(cd.name, ctor); + } + } + Integer i = 0; + while (i < cd.methods.length()) { + MethodDecl md = cd.methods.get(i) as MethodDecl; + if (md.body != null) { + this.lowerMethod(cd.name, md); + } + i = i + 1; + } + } + + private lowerModule(m: ArimoModule) { + Integer i = 0; + while (i < m.items.length()) { + Item it = m.items.get(i) as Item; + if (it.kind == ItemKind.CLASS) { + this.lowerClass(it.classDecl); + } + i = i + 1; + } + } + + // ===== Print-int helpers (avoid two-call register pressure in caller) ===== + + private generatePrintlnInt(name: String, addNewline: Boolean) { + this.beginFn(name, IRType.VOID); + this.addParamToLast("n", IRType.I64); + this.resetFnContext(); + this.emit(IRInstr.label("entry")); + + IRValue nv = IRValue.reg("n", IRType.I64); + + // Convert to string + String tsReg = this.newReg(); + IRValue tsDst = IRValue.reg(tsReg, IRType.PTR); + List tsArgs = List(); + tsArgs.append(nv); + this.emit(IRInstr.call(tsDst, "__arimo_i64_to_str", tsArgs)); + + // Print + String target = "__arimo_println"; + if (!addNewline) { target = "__arimo_print"; } + List callArgs = List(); + callArgs.append(tsDst); + String dr = this.newReg(); + IRValue dst = IRValue.reg(dr, IRType.VOID); + this.emit(IRInstr.call(dst, target, callArgs)); + this.emit(IRInstr.retVoid()); + } + + // ===== Print helpers ===== + + private generatePrintHelper(name: String, addNewline: Boolean) { + this.beginFn(name, IRType.VOID); + this.addParamToLast("str", IRType.PTR); + this.resetFnContext(); + this.emit(IRInstr.label("entry")); + + IRValue strSaved = IRValue.reg("str_saved", IRType.PTR); + this.emit(IRInstr.mov(strSaved, IRValue.reg("str", IRType.PTR))); + + String nlName = this.internStr("\n"); + IRValue strPtr = IRValue.reg("str_saved", IRType.PTR); + + String lenReg = "plen"; + IRValue lenDst = IRValue.reg(lenReg, IRType.I64); + List lenArgs = List(); + lenArgs.append(strPtr); + this.emit(IRInstr.call(lenDst, "__arimo_strlen", lenArgs)); + + if (this.linux) { + // write(1, str, len) + List wrArgs = List(); + wrArgs.append(IRValue.ofInt(1, IRType.I64)); + wrArgs.append(strPtr); + wrArgs.append(IRValue.reg("plen", IRType.I64)); + IRValue wrDst = IRValue.reg("pwr", IRType.VOID); + this.emit(IRInstr.syscall(wrDst, 1, wrArgs)); + if (addNewline) { + List wrArgs2 = List(); + wrArgs2.append(IRValue.ofInt(1, IRType.I64)); + wrArgs2.append(IRValue.global(nlName)); + wrArgs2.append(IRValue.ofInt(1, IRType.I64)); + IRValue wrDst2 = IRValue.reg("pwr2", IRType.VOID); + this.emit(IRInstr.syscall(wrDst2, 1, wrArgs2)); + } + } else { + IRValue hDst = IRValue.reg("ph_h", IRType.I64); + List hsArgs = List(); + hsArgs.append(IRValue.ofInt(-11, IRType.I64)); + this.emit(IRInstr.call(hDst, "__ext__GetStdHandle", hsArgs)); + + IRValue bwPtr = IRValue.reg("bwPtr", IRType.PTR); + this.emit(IRInstr.alloc(bwPtr, 4)); + + List wfArgs = List(); + wfArgs.append(IRValue.reg("ph_h", IRType.I64)); + wfArgs.append(strPtr); + wfArgs.append(IRValue.reg("plen", IRType.I64)); + wfArgs.append(IRValue.reg("bwPtr", IRType.PTR)); + wfArgs.append(IRValue.ofInt(0, IRType.I64)); + IRValue wfDst = IRValue.reg("pwf", IRType.VOID); + this.emit(IRInstr.call(wfDst, "__ext__WriteFile", wfArgs)); + + if (addNewline) { + List wfArgs2 = List(); + wfArgs2.append(IRValue.reg("ph_h", IRType.I64)); + wfArgs2.append(IRValue.global(nlName)); + wfArgs2.append(IRValue.ofInt(1, IRType.I64)); + wfArgs2.append(IRValue.reg("bwPtr", IRType.PTR)); + wfArgs2.append(IRValue.ofInt(0, IRType.I64)); + IRValue wfDst2 = IRValue.reg("pwf2", IRType.VOID); + this.emit(IRInstr.call(wfDst2, "__ext__WriteFile", wfArgs2)); + } + } + this.emit(IRInstr.retVoid()); + } + + // ===== Strlen helper ===== + + private generateStrlenHelper() { + this.beginFn("__arimo_strlen", IRType.I64); + this.addParamToLast("s", IRType.PTR); + this.resetFnContext(); + this.emit(IRInstr.label("entry")); + IRValue sBase = IRValue.reg("sBase", IRType.PTR); + this.emit(IRInstr.mov(sBase, IRValue.reg("s", IRType.PTR))); + IRValue cnt = IRValue.reg("cnt", IRType.I64); + this.emit(IRInstr.mov(cnt, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.label("strlen_loop")); + IRValue idx = IRValue.reg("cidx", IRType.I64); + this.emit(IRInstr.binop(IROpcode.ADD, idx, IRValue.reg("sBase", IRType.PTR), IRValue.reg("cnt", IRType.I64))); + IRValue ch = IRValue.reg("ch", IRType.I64); + this.emit(IRInstr.load(ch, IRValue.reg("cidx", IRType.I64), IRType.I8)); + this.emit(IRInstr.cmp(IRValue.reg("ch", IRType.I64), IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.branch(IROpcode.JE, "strlen_done")); + IRValue cnt2 = IRValue.reg("cnt", IRType.I64); + this.emit(IRInstr.binop(IROpcode.ADD, cnt2, IRValue.reg("cnt", IRType.I64), IRValue.ofInt(1, IRType.I64))); + IRValue sKeep = IRValue.reg("sKeep", IRType.PTR); + this.emit(IRInstr.mov(sKeep, IRValue.reg("sBase", IRType.PTR))); + this.emit(IRInstr.jmp("strlen_loop")); + this.emit(IRInstr.label("strlen_done")); + this.emit(IRInstr.ret(IRValue.reg("cnt", IRType.I64))); + } + + // ===== List runtime ===== + + private generateListNew() { + this.beginFn("__arimo_list_new", IRType.PTR); + this.resetFnContext(); + this.emit(IRInstr.label("entry")); + IRValue listPtr = this.emitHeapAlloc(IRValue.ofInt(24, IRType.I64)); + IRValue dataPtr = this.emitHeapAlloc(IRValue.ofInt(64, IRType.I64)); + this.emitFieldStoreS(this.emitFieldPtr(listPtr, 0), dataPtr); + this.emitFieldStoreS(this.emitFieldPtr(listPtr, 8), IRValue.ofInt(0, IRType.I64)); + this.emitFieldStoreS(this.emitFieldPtr(listPtr, 16), IRValue.ofInt(8, IRType.I64)); + this.emit(IRInstr.ret(listPtr)); + } + + private generateListLength() { + this.beginFn("__arimo_list_length", IRType.I64); + this.addParamToLast("list", IRType.PTR); + this.resetFnContext(); + this.emit(IRInstr.label("entry")); + IRValue listPar = IRValue.reg("list", IRType.PTR); + IRValue lenVal = this.emitFieldLoad(listPar, 8); + this.emit(IRInstr.ret(lenVal)); + } + + private generateListGet() { + this.beginFn("__arimo_list_get", IRType.I64); + this.addParamToLast("list", IRType.PTR); + this.addParamToLast("idx", IRType.I64); + this.resetFnContext(); + this.emit(IRInstr.label("entry")); + IRValue listPar = IRValue.reg("list", IRType.PTR); + IRValue idxPar = IRValue.reg("idx", IRType.I64); + IRValue data = this.emitFieldLoad(listPar, 0); + String offReg = this.newReg(); + IRValue off = IRValue.reg(offReg, IRType.I64); + this.emit(IRInstr.binop(IROpcode.MUL, off, idxPar, IRValue.ofInt(8, IRType.I64))); + String pReg = this.newReg(); + IRValue slot = IRValue.reg(pReg, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, slot, data, off)); + String elemReg = this.newReg(); + IRValue elem = IRValue.reg(elemReg, IRType.I64); + this.emit(IRInstr.load(elem, slot, IRType.I64)); + this.emit(IRInstr.ret(elem)); + } + + private generateListSet() { + this.beginFn("__arimo_list_set", IRType.VOID); + this.addParamToLast("list", IRType.PTR); + this.addParamToLast("idx", IRType.I64); + this.addParamToLast("val", IRType.I64); + this.resetFnContext(); + this.emit(IRInstr.label("entry")); + IRValue listPar = IRValue.reg("list", IRType.PTR); + IRValue idxPar = IRValue.reg("idx", IRType.I64); + IRValue valPar = IRValue.reg("val", IRType.I64); + IRValue data = this.emitFieldLoad(listPar, 0); + String offReg = this.newReg(); + IRValue off = IRValue.reg(offReg, IRType.I64); + this.emit(IRInstr.binop(IROpcode.MUL, off, idxPar, IRValue.ofInt(8, IRType.I64))); + String pReg = this.newReg(); + IRValue slot = IRValue.reg(pReg, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, slot, data, off)); + this.emit(IRInstr.store(valPar, slot, IRType.I64)); + this.emit(IRInstr.retVoid()); + } + + private generateListAppend() { + this.beginFn("__arimo_list_append", IRType.VOID); + this.addParamToLast("list", IRType.PTR); + this.addParamToLast("val", IRType.I64); + this.resetFnContext(); + this.emit(IRInstr.label("entry")); + IRValue listPar = IRValue.reg("list", IRType.PTR); + IRValue valPar = IRValue.reg("val", IRType.I64); + + IRValue lenVal = this.emitFieldLoad(listPar, 8); + IRValue capVal = this.emitFieldLoad(listPar, 16); + + String noResL = this.newLabel("no_resize"); + this.emit(IRInstr.cmp(lenVal, capVal)); + this.emit(IRInstr.branch(IROpcode.JL, noResL)); + + // resize: new_cap = cap*2, alloc new data, copy, update list + String ncReg = this.newReg(); + IRValue newCap = IRValue.reg(ncReg, IRType.I64); + this.emit(IRInstr.binop(IROpcode.ADD, newCap, capVal, capVal)); + + String nszReg = this.newReg(); + IRValue newSz = IRValue.reg(nszReg, IRType.I64); + this.emit(IRInstr.binop(IROpcode.MUL, newSz, newCap, IRValue.ofInt(8, IRType.I64))); + + IRValue newData = this.emitHeapAlloc(newSz); + + IRValue oldData = this.emitFieldLoad(listPar, 0); + + String ciReg = this.newReg(); + IRValue copyI = IRValue.reg(ciReg, IRType.I64); + this.emit(IRInstr.mov(copyI, IRValue.ofInt(0, IRType.I64))); + + String cpLoopL = this.newLabel("cp_loop"); + String cpDoneL = this.newLabel("cp_done"); + this.emit(IRInstr.label(cpLoopL)); + this.emit(IRInstr.cmp(copyI, lenVal)); + this.emit(IRInstr.branch(IROpcode.JGE, cpDoneL)); + + String coReg = this.newReg(); + IRValue copyOff = IRValue.reg(coReg, IRType.I64); + this.emit(IRInstr.binop(IROpcode.MUL, copyOff, copyI, IRValue.ofInt(8, IRType.I64))); + + String spReg = this.newReg(); + IRValue srcPtr = IRValue.reg(spReg, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, srcPtr, oldData, copyOff)); + + String elReg = this.newReg(); + IRValue elem = IRValue.reg(elReg, IRType.I64); + this.emit(IRInstr.load(elem, srcPtr, IRType.I64)); + + String dpReg = this.newReg(); + IRValue dstPtr = IRValue.reg(dpReg, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, dstPtr, newData, copyOff)); + this.emit(IRInstr.store(elem, dstPtr, IRType.I64)); + + this.emit(IRInstr.binop(IROpcode.ADD, copyI, copyI, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.jmp(cpLoopL)); + this.emit(IRInstr.label(cpDoneL)); + + IRValue ndPtr = this.emitFieldPtr(listPar, 0); + this.emit(IRInstr.store(newData, ndPtr, IRType.I64)); + IRValue ncPtr = this.emitFieldPtr(listPar, 16); + this.emit(IRInstr.store(newCap, ncPtr, IRType.I64)); + + this.emit(IRInstr.label(noResL)); + + IRValue data2 = this.emitFieldLoad(listPar, 0); + String loReg = this.newReg(); + IRValue lenOff = IRValue.reg(loReg, IRType.I64); + this.emit(IRInstr.binop(IROpcode.MUL, lenOff, lenVal, IRValue.ofInt(8, IRType.I64))); + String slReg = this.newReg(); + IRValue slot = IRValue.reg(slReg, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, slot, data2, lenOff)); + this.emit(IRInstr.store(valPar, slot, IRType.I64)); + + String nlReg = this.newReg(); + IRValue newLen = IRValue.reg(nlReg, IRType.I64); + this.emit(IRInstr.binop(IROpcode.ADD, newLen, lenVal, IRValue.ofInt(1, IRType.I64))); + IRValue lfPtr = this.emitFieldPtr(listPar, 8); + this.emit(IRInstr.store(newLen, lfPtr, IRType.I64)); + this.emit(IRInstr.retVoid()); + } + + private generateListRemoveAt() { + this.beginFn("__arimo_list_remove_at", IRType.VOID); + this.addParamToLast("list", IRType.PTR); + this.addParamToLast("idx", IRType.I64); + this.resetFnContext(); + this.emit(IRInstr.label("entry")); + IRValue listPar = IRValue.reg("list", IRType.PTR); + IRValue idxPar = IRValue.reg("idx", IRType.I64); + + IRValue lenVal = this.emitFieldLoad(listPar, 8); + IRValue data = this.emitFieldLoad(listPar, 0); + + String iReg = this.newReg(); + IRValue shiftI = IRValue.reg(iReg, IRType.I64); + this.emit(IRInstr.mov(shiftI, idxPar)); + + String shLoopL = this.newLabel("sh_loop"); + String shDoneL = this.newLabel("sh_done"); + + String limReg = this.newReg(); + IRValue lim = IRValue.reg(limReg, IRType.I64); + this.emit(IRInstr.binop(IROpcode.SUB, lim, lenVal, IRValue.ofInt(1, IRType.I64))); + + this.emit(IRInstr.label(shLoopL)); + this.emit(IRInstr.cmp(shiftI, lim)); + this.emit(IRInstr.branch(IROpcode.JGE, shDoneL)); + + String no1Reg = this.newReg(); + IRValue nextOff = IRValue.reg(no1Reg, IRType.I64); + String ni1Reg = this.newReg(); + IRValue nextIdx = IRValue.reg(ni1Reg, IRType.I64); + this.emit(IRInstr.binop(IROpcode.ADD, nextIdx, shiftI, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.binop(IROpcode.MUL, nextOff, nextIdx, IRValue.ofInt(8, IRType.I64))); + + String np1Reg = this.newReg(); + IRValue nextPtr = IRValue.reg(np1Reg, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, nextPtr, data, nextOff)); + + String ev1Reg = this.newReg(); + IRValue elemVal = IRValue.reg(ev1Reg, IRType.I64); + this.emit(IRInstr.load(elemVal, nextPtr, IRType.I64)); + + String co1Reg = this.newReg(); + IRValue currOff = IRValue.reg(co1Reg, IRType.I64); + this.emit(IRInstr.binop(IROpcode.MUL, currOff, shiftI, IRValue.ofInt(8, IRType.I64))); + String cp1Reg = this.newReg(); + IRValue currPtr = IRValue.reg(cp1Reg, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, currPtr, data, currOff)); + this.emit(IRInstr.store(elemVal, currPtr, IRType.I64)); + + this.emit(IRInstr.binop(IROpcode.ADD, shiftI, shiftI, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.jmp(shLoopL)); + this.emit(IRInstr.label(shDoneL)); + + String nl2Reg = this.newReg(); + IRValue newLen = IRValue.reg(nl2Reg, IRType.I64); + this.emit(IRInstr.binop(IROpcode.SUB, newLen, lenVal, IRValue.ofInt(1, IRType.I64))); + IRValue lfPtr = this.emitFieldPtr(listPar, 8); + this.emit(IRInstr.store(newLen, lfPtr, IRType.I64)); + this.emit(IRInstr.retVoid()); + } + + // ===== String builtins ===== + + private generateStrCmp() { + this.beginFn("__arimo_strcmp", IRType.I64); + this.addParamToLast("a", IRType.PTR); + this.addParamToLast("b", IRType.PTR); + this.resetFnContext(); + this.emit(IRInstr.label("entry")); + IRValue aP = IRValue.reg("a", IRType.PTR); + IRValue bP = IRValue.reg("b", IRType.PTR); + IRValue i = IRValue.reg("sci", IRType.I64); + this.emit(IRInstr.mov(i, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.label("scmp_loop")); + + String pca = this.newReg(); + IRValue pa = IRValue.reg(pca, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, pa, aP, i)); + String cca = this.newReg(); + IRValue ca = IRValue.reg(cca, IRType.I64); + this.emit(IRInstr.load(ca, pa, IRType.I8)); + + String pcb = this.newReg(); + IRValue pb = IRValue.reg(pcb, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, pb, bP, i)); + String ccb = this.newReg(); + IRValue cb = IRValue.reg(ccb, IRType.I64); + this.emit(IRInstr.load(cb, pb, IRType.I8)); + + this.emit(IRInstr.cmp(ca, cb)); + this.emit(IRInstr.branch(IROpcode.JNE, "scmp_ne")); + + this.emit(IRInstr.cmp(ca, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.branch(IROpcode.JE, "scmp_eq")); + + this.emit(IRInstr.binop(IROpcode.ADD, i, i, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.jmp("scmp_loop")); + + this.emit(IRInstr.label("scmp_ne")); + String dr1 = this.newReg(); + IRValue diff = IRValue.reg(dr1, IRType.I64); + this.emit(IRInstr.binop(IROpcode.SUB, diff, ca, cb)); + this.emit(IRInstr.ret(diff)); + + this.emit(IRInstr.label("scmp_eq")); + this.emit(IRInstr.ret(IRValue.ofInt(0, IRType.I64))); + } + + private generateStrCat() { + this.beginFn("__arimo_strcat", IRType.PTR); + this.addParamToLast("a", IRType.PTR); + this.addParamToLast("b", IRType.PTR); + this.resetFnContext(); + this.emit(IRInstr.label("entry")); + IRValue aP = IRValue.reg("a", IRType.PTR); + IRValue bP = IRValue.reg("b", IRType.PTR); + + // alen = strlen(a) + String alenReg = this.newReg(); + IRValue alen = IRValue.reg(alenReg, IRType.I64); + List sla = List(); + sla.append(aP); + this.emit(IRInstr.call(alen, "__arimo_strlen", sla)); + + // blen = strlen(b) + String blenReg = this.newReg(); + IRValue blen = IRValue.reg(blenReg, IRType.I64); + List slb = List(); + slb.append(bP); + this.emit(IRInstr.call(blen, "__arimo_strlen", slb)); + + // total = alen + blen + 1 + String tlReg = this.newReg(); + IRValue total = IRValue.reg(tlReg, IRType.I64); + this.emit(IRInstr.binop(IROpcode.ADD, total, alen, blen)); + String tl2Reg = this.newReg(); + IRValue total2 = IRValue.reg(tl2Reg, IRType.I64); + this.emit(IRInstr.binop(IROpcode.ADD, total2, total, IRValue.ofInt(1, IRType.I64))); + + IRValue buf = this.emitHeapAlloc(total2); + + // copy a + String aiReg = this.newReg(); + IRValue ai = IRValue.reg(aiReg, IRType.I64); + this.emit(IRInstr.mov(ai, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.label("scat_a")); + this.emit(IRInstr.cmp(ai, alen)); + this.emit(IRInstr.branch(IROpcode.JGE, "scat_a_done")); + String apReg = this.newReg(); + IRValue apv = IRValue.reg(apReg, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, apv, aP, ai)); + String acReg = this.newReg(); + IRValue ac = IRValue.reg(acReg, IRType.I64); + this.emit(IRInstr.load(ac, apv, IRType.I8)); + String dpReg = this.newReg(); + IRValue dpv = IRValue.reg(dpReg, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, dpv, buf, ai)); + this.emit(IRInstr.store(ac, dpv, IRType.I8)); + this.emit(IRInstr.binop(IROpcode.ADD, ai, ai, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.jmp("scat_a")); + this.emit(IRInstr.label("scat_a_done")); + + // copy b + String biReg = this.newReg(); + IRValue bi = IRValue.reg(biReg, IRType.I64); + this.emit(IRInstr.mov(bi, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.label("scat_b")); + this.emit(IRInstr.cmp(bi, blen)); + this.emit(IRInstr.branch(IROpcode.JGE, "scat_b_done")); + String bpReg = this.newReg(); + IRValue bpv = IRValue.reg(bpReg, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, bpv, bP, bi)); + String bcReg = this.newReg(); + IRValue bc = IRValue.reg(bcReg, IRType.I64); + this.emit(IRInstr.load(bc, bpv, IRType.I8)); + String dbReg = this.newReg(); + IRValue dbv = IRValue.reg(dbReg, IRType.PTR); + String dbsReg = this.newReg(); + IRValue dbs = IRValue.reg(dbsReg, IRType.I64); + this.emit(IRInstr.binop(IROpcode.ADD, dbs, alen, bi)); + this.emit(IRInstr.binop(IROpcode.ADD, dbv, buf, dbs)); + this.emit(IRInstr.store(bc, dbv, IRType.I8)); + this.emit(IRInstr.binop(IROpcode.ADD, bi, bi, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.jmp("scat_b")); + this.emit(IRInstr.label("scat_b_done")); + + // null terminate + String nullPReg = this.newReg(); + IRValue nullP = IRValue.reg(nullPReg, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, nullP, buf, total)); + this.emit(IRInstr.store(IRValue.ofInt(0, IRType.I64), nullP, IRType.I8)); + + this.emit(IRInstr.ret(buf)); + } + + private generateSubstr() { + this.beginFn("__arimo_substr", IRType.PTR); + this.addParamToLast("s", IRType.PTR); + this.addParamToLast("from", IRType.I64); + this.addParamToLast("to", IRType.I64); + this.resetFnContext(); + this.emit(IRInstr.label("entry")); + IRValue sP = IRValue.reg("s", IRType.PTR); + IRValue fromP = IRValue.reg("from", IRType.I64); + IRValue toP = IRValue.reg("to", IRType.I64); + + String lenReg = this.newReg(); + IRValue len = IRValue.reg(lenReg, IRType.I64); + this.emit(IRInstr.binop(IROpcode.SUB, len, toP, fromP)); + + String aszReg = this.newReg(); + IRValue asz = IRValue.reg(aszReg, IRType.I64); + this.emit(IRInstr.binop(IROpcode.ADD, asz, len, IRValue.ofInt(1, IRType.I64))); + + IRValue buf = this.emitHeapAlloc(asz); + + String iReg = this.newReg(); + IRValue si = IRValue.reg(iReg, IRType.I64); + this.emit(IRInstr.mov(si, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.label("sub_loop")); + this.emit(IRInstr.cmp(si, len)); + this.emit(IRInstr.branch(IROpcode.JGE, "sub_done")); + + String srcIdxReg = this.newReg(); + IRValue srcIdx = IRValue.reg(srcIdxReg, IRType.I64); + this.emit(IRInstr.binop(IROpcode.ADD, srcIdx, fromP, si)); + String spReg = this.newReg(); + IRValue sp = IRValue.reg(spReg, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, sp, sP, srcIdx)); + String chReg = this.newReg(); + IRValue ch = IRValue.reg(chReg, IRType.I64); + this.emit(IRInstr.load(ch, sp, IRType.I8)); + String dpReg = this.newReg(); + IRValue dp = IRValue.reg(dpReg, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, dp, buf, si)); + this.emit(IRInstr.store(ch, dp, IRType.I8)); + this.emit(IRInstr.binop(IROpcode.ADD, si, si, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.jmp("sub_loop")); + this.emit(IRInstr.label("sub_done")); + + String nPReg = this.newReg(); + IRValue nP = IRValue.reg(nPReg, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, nP, buf, len)); + this.emit(IRInstr.store(IRValue.ofInt(0, IRType.I64), nP, IRType.I8)); + this.emit(IRInstr.ret(buf)); + } + + private generateStartsWith() { + this.beginFn("__arimo_startswith", IRType.I64); + this.addParamToLast("s", IRType.PTR); + this.addParamToLast("prefix", IRType.PTR); + this.resetFnContext(); + this.emit(IRInstr.label("entry")); + IRValue sP = IRValue.reg("s", IRType.PTR); + IRValue pfP = IRValue.reg("prefix", IRType.PTR); + IRValue si = IRValue.reg("swi", IRType.I64); + this.emit(IRInstr.mov(si, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.label("sw_loop")); + + String ppp = this.newReg(); + IRValue pp = IRValue.reg(ppp, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, pp, pfP, si)); + String pcc = this.newReg(); + IRValue pc = IRValue.reg(pcc, IRType.I64); + this.emit(IRInstr.load(pc, pp, IRType.I8)); + this.emit(IRInstr.cmp(pc, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.branch(IROpcode.JE, "sw_yes")); + + String spp = this.newReg(); + IRValue sp = IRValue.reg(spp, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, sp, sP, si)); + String scc = this.newReg(); + IRValue sc = IRValue.reg(scc, IRType.I64); + this.emit(IRInstr.load(sc, sp, IRType.I8)); + this.emit(IRInstr.cmp(sc, pc)); + this.emit(IRInstr.branch(IROpcode.JNE, "sw_no")); + + this.emit(IRInstr.binop(IROpcode.ADD, si, si, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.jmp("sw_loop")); + this.emit(IRInstr.label("sw_yes")); + this.emit(IRInstr.ret(IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.label("sw_no")); + this.emit(IRInstr.ret(IRValue.ofInt(0, IRType.I64))); + } + + private generateEndsWith() { + this.beginFn("__arimo_endswith", IRType.I64); + this.addParamToLast("s", IRType.PTR); + this.addParamToLast("suffix", IRType.PTR); + this.resetFnContext(); + this.emit(IRInstr.label("entry")); + IRValue sP = IRValue.reg("s", IRType.PTR); + IRValue sfP = IRValue.reg("suffix", IRType.PTR); + + String slenR = this.newReg(); + IRValue slen = IRValue.reg(slenR, IRType.I64); + List sla = List(); + sla.append(sP); + this.emit(IRInstr.call(slen, "__arimo_strlen", sla)); + + String sflenR = this.newReg(); + IRValue sflen = IRValue.reg(sflenR, IRType.I64); + List slb = List(); + slb.append(sfP); + this.emit(IRInstr.call(sflen, "__arimo_strlen", slb)); + + this.emit(IRInstr.cmp(sflen, slen)); + this.emit(IRInstr.branch(IROpcode.JG, "ew_no")); + + String offR = this.newReg(); + IRValue off = IRValue.reg(offR, IRType.I64); + this.emit(IRInstr.binop(IROpcode.SUB, off, slen, sflen)); + + IRValue ewi = IRValue.reg("ewi", IRType.I64); + this.emit(IRInstr.mov(ewi, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.label("ew_loop")); + + String sfpp = this.newReg(); + IRValue sfp = IRValue.reg(sfpp, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, sfp, sfP, ewi)); + String sfcc = this.newReg(); + IRValue sfc = IRValue.reg(sfcc, IRType.I64); + this.emit(IRInstr.load(sfc, sfp, IRType.I8)); + this.emit(IRInstr.cmp(sfc, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.branch(IROpcode.JE, "ew_yes")); + + String sidxR = this.newReg(); + IRValue sidx = IRValue.reg(sidxR, IRType.I64); + this.emit(IRInstr.binop(IROpcode.ADD, sidx, off, ewi)); + String spp = this.newReg(); + IRValue sp = IRValue.reg(spp, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, sp, sP, sidx)); + String scc = this.newReg(); + IRValue sc = IRValue.reg(scc, IRType.I64); + this.emit(IRInstr.load(sc, sp, IRType.I8)); + this.emit(IRInstr.cmp(sc, sfc)); + this.emit(IRInstr.branch(IROpcode.JNE, "ew_no")); + + this.emit(IRInstr.binop(IROpcode.ADD, ewi, ewi, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.jmp("ew_loop")); + this.emit(IRInstr.label("ew_yes")); + this.emit(IRInstr.ret(IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.label("ew_no")); + this.emit(IRInstr.ret(IRValue.ofInt(0, IRType.I64))); + } + + private generateIndexOf() { + this.beginFn("__arimo_indexof_from", IRType.I64); + this.addParamToLast("s", IRType.PTR); + this.addParamToLast("sub", IRType.PTR); + this.addParamToLast("from", IRType.I64); + this.resetFnContext(); + this.emit(IRInstr.label("entry")); + IRValue sP = IRValue.reg("s", IRType.PTR); + IRValue subP = IRValue.reg("sub", IRType.PTR); + IRValue fromV = IRValue.reg("from", IRType.I64); + + String slenR = this.newReg(); + IRValue slen = IRValue.reg(slenR, IRType.I64); + List sla = List(); + sla.append(sP); + this.emit(IRInstr.call(slen, "__arimo_strlen", sla)); + + String sublenR = this.newReg(); + IRValue sublen = IRValue.reg(sublenR, IRType.I64); + List slb = List(); + slb.append(subP); + this.emit(IRInstr.call(sublen, "__arimo_strlen", slb)); + + IRValue oi = IRValue.reg("io_i", IRType.I64); + this.emit(IRInstr.mov(oi, fromV)); + this.emit(IRInstr.label("io_outer")); + + String limR = this.newReg(); + IRValue lim = IRValue.reg(limR, IRType.I64); + this.emit(IRInstr.binop(IROpcode.SUB, lim, slen, sublen)); + this.emit(IRInstr.cmp(oi, lim)); + this.emit(IRInstr.branch(IROpcode.JG, "io_notfound")); + + IRValue ij = IRValue.reg("io_j", IRType.I64); + this.emit(IRInstr.mov(ij, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.label("io_inner")); + + String spjR = this.newReg(); + IRValue spj = IRValue.reg(spjR, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, spj, subP, ij)); + String scjR = this.newReg(); + IRValue scj = IRValue.reg(scjR, IRType.I64); + this.emit(IRInstr.load(scj, spj, IRType.I8)); + this.emit(IRInstr.cmp(scj, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.branch(IROpcode.JE, "io_match")); + + String soffR = this.newReg(); + IRValue soff = IRValue.reg(soffR, IRType.I64); + this.emit(IRInstr.binop(IROpcode.ADD, soff, oi, ij)); + String sppR = this.newReg(); + IRValue spp = IRValue.reg(sppR, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, spp, sP, soff)); + String sccR = this.newReg(); + IRValue scc = IRValue.reg(sccR, IRType.I64); + this.emit(IRInstr.load(scc, spp, IRType.I8)); + this.emit(IRInstr.cmp(scc, scj)); + this.emit(IRInstr.branch(IROpcode.JNE, "io_nomatch")); + + this.emit(IRInstr.binop(IROpcode.ADD, ij, ij, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.jmp("io_inner")); + + this.emit(IRInstr.label("io_match")); + this.emit(IRInstr.ret(oi)); + + this.emit(IRInstr.label("io_nomatch")); + this.emit(IRInstr.binop(IROpcode.ADD, oi, oi, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.jmp("io_outer")); + + this.emit(IRInstr.label("io_notfound")); + this.emit(IRInstr.ret(IRValue.ofInt(-1, IRType.I64))); + } + + private generateCharCodeAt() { + this.beginFn("__arimo_charcodeat", IRType.I64); + this.addParamToLast("s", IRType.PTR); + this.addParamToLast("idx", IRType.I64); + this.resetFnContext(); + this.emit(IRInstr.label("entry")); + IRValue sP = IRValue.reg("s", IRType.PTR); + IRValue idx = IRValue.reg("idx", IRType.I64); + String pReg = this.newReg(); + IRValue p = IRValue.reg(pReg, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, p, sP, idx)); + String cReg = this.newReg(); + IRValue c = IRValue.reg(cReg, IRType.I64); + this.emit(IRInstr.load(c, p, IRType.I8)); + this.emit(IRInstr.ret(c)); + } + + private generateCharAt() { + this.beginFn("__arimo_charat", IRType.PTR); + this.addParamToLast("s", IRType.PTR); + this.addParamToLast("idx", IRType.I64); + this.resetFnContext(); + this.emit(IRInstr.label("entry")); + IRValue sP = IRValue.reg("s", IRType.PTR); + IRValue idx = IRValue.reg("idx", IRType.I64); + String pReg = this.newReg(); + IRValue p = IRValue.reg(pReg, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, p, sP, idx)); + String cReg = this.newReg(); + IRValue c = IRValue.reg(cReg, IRType.I64); + this.emit(IRInstr.load(c, p, IRType.I8)); + IRValue buf = this.emitHeapAlloc(IRValue.ofInt(2, IRType.I64)); + this.emit(IRInstr.store(c, buf, IRType.I8)); + String npReg = this.newReg(); + IRValue np = IRValue.reg(npReg, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, np, buf, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.store(IRValue.ofInt(0, IRType.I64), np, IRType.I8)); + this.emit(IRInstr.ret(buf)); + } + + private generateI64ToStr() { + this.beginFn("__arimo_i64_to_str", IRType.PTR); + this.addParamToLast("n", IRType.I64); + this.resetFnContext(); + this.emit(IRInstr.label("entry")); + IRValue nv = IRValue.reg("n", IRType.I64); + + // Special case: n == 0 + this.emit(IRInstr.cmp(nv, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.branch(IROpcode.JNE, "its_nonzero")); + IRValue zeroBuf = this.emitHeapAlloc(IRValue.ofInt(4, IRType.I64)); + String zp0R = this.newReg(); + IRValue zp0 = IRValue.reg(zp0R, IRType.PTR); + this.emit(IRInstr.mov(zp0, zeroBuf)); + this.emit(IRInstr.store(IRValue.ofInt(48, IRType.I64), zp0, IRType.I8)); // '0'=48 + String zp1R = this.newReg(); + IRValue zp1 = IRValue.reg(zp1R, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, zp1, zeroBuf, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.store(IRValue.ofInt(0, IRType.I64), zp1, IRType.I8)); + this.emit(IRInstr.ret(zeroBuf)); + + this.emit(IRInstr.label("its_nonzero")); + // neg = 0; abs_n = n; if n < 0: neg=1, abs_n = 0-n + IRValue neg = IRValue.reg("its_neg", IRType.I64); + IRValue abs_n = IRValue.reg("its_absn", IRType.I64); + this.emit(IRInstr.mov(neg, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.mov(abs_n, nv)); + this.emit(IRInstr.cmp(nv, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.branch(IROpcode.JGE, "its_pos")); + this.emit(IRInstr.mov(neg, IRValue.ofInt(1, IRType.I64))); + String anR = this.newReg(); + IRValue an = IRValue.reg(anR, IRType.I64); + this.emit(IRInstr.binop(IROpcode.SUB, an, IRValue.ofInt(0, IRType.I64), nv)); + this.emit(IRInstr.mov(abs_n, an)); + this.emit(IRInstr.label("its_pos")); + + // Count digits + IRValue cnt = IRValue.reg("its_cnt", IRType.I64); + IRValue tmp = IRValue.reg("its_tmp", IRType.I64); + this.emit(IRInstr.mov(cnt, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.mov(tmp, abs_n)); + this.emit(IRInstr.label("its_count")); + this.emit(IRInstr.cmp(tmp, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.branch(IROpcode.JLE, "its_counted")); + this.emit(IRInstr.binop(IROpcode.ADD, cnt, cnt, IRValue.ofInt(1, IRType.I64))); + String dvrR = this.newReg(); + IRValue dvr = IRValue.reg(dvrR, IRType.I64); + this.emit(IRInstr.binop(IROpcode.DIV, dvr, tmp, IRValue.ofInt(10, IRType.I64))); + this.emit(IRInstr.mov(tmp, dvr)); + this.emit(IRInstr.jmp("its_count")); + this.emit(IRInstr.label("its_counted")); + + // total = cnt + neg + String totR = this.newReg(); + IRValue tot = IRValue.reg(totR, IRType.I64); + this.emit(IRInstr.binop(IROpcode.ADD, tot, cnt, neg)); + String aszR = this.newReg(); + IRValue asz = IRValue.reg(aszR, IRType.I64); + this.emit(IRInstr.binop(IROpcode.ADD, asz, tot, IRValue.ofInt(1, IRType.I64))); + + IRValue buf = this.emitHeapAlloc(asz); + + // null terminate + String nlPR = this.newReg(); + IRValue nlP = IRValue.reg(nlPR, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, nlP, buf, tot)); + this.emit(IRInstr.store(IRValue.ofInt(0, IRType.I64), nlP, IRType.I8)); + + // write '-' if neg + this.emit(IRInstr.cmp(neg, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.branch(IROpcode.JE, "its_no_neg")); + String np0R = this.newReg(); + IRValue np0 = IRValue.reg(np0R, IRType.PTR); + this.emit(IRInstr.mov(np0, buf)); + this.emit(IRInstr.store(IRValue.ofInt(45, IRType.I64), np0, IRType.I8)); // '-'=45 + this.emit(IRInstr.label("its_no_neg")); + + // write digits right to left: pos = tot-1 + IRValue pos = IRValue.reg("its_pos", IRType.I64); + IRValue abs2 = IRValue.reg("its_ab2", IRType.I64); + this.emit(IRInstr.binop(IROpcode.SUB, pos, tot, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.mov(abs2, abs_n)); + this.emit(IRInstr.label("its_write")); + this.emit(IRInstr.cmp(abs2, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.branch(IROpcode.JLE, "its_written")); + String modR = this.newReg(); + IRValue md = IRValue.reg(modR, IRType.I64); + this.emit(IRInstr.binop(IROpcode.MOD, md, abs2, IRValue.ofInt(10, IRType.I64))); + String digR = this.newReg(); + IRValue dig = IRValue.reg(digR, IRType.I64); + this.emit(IRInstr.binop(IROpcode.ADD, dig, md, IRValue.ofInt(48, IRType.I64))); // '0'=48 + String dPR = this.newReg(); + IRValue dP = IRValue.reg(dPR, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, dP, buf, pos)); + this.emit(IRInstr.store(dig, dP, IRType.I8)); + String divR = this.newReg(); + IRValue dv = IRValue.reg(divR, IRType.I64); + this.emit(IRInstr.binop(IROpcode.DIV, dv, abs2, IRValue.ofInt(10, IRType.I64))); + this.emit(IRInstr.mov(abs2, dv)); + this.emit(IRInstr.binop(IROpcode.SUB, pos, pos, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.jmp("its_write")); + this.emit(IRInstr.label("its_written")); + this.emit(IRInstr.ret(buf)); + } + + // ===== Entry point ===== + + private registerBssGlobal(name: String, size: Integer) : Integer { + Integer i = 0; while (i < this.bssNames.length()) { + if (this.bssNames.get(i) as String == name) { return i; } i = i + 1; } + this.bssNames.append(name); this.bssSizes.append(size); + return this.bssNames.length() - 1; + } + + private generateStartFn() { + if (this.linux) { this.registerBssGlobal("__arimo_argc", 8); this.registerBssGlobal("__arimo_argv", 8); } + this.beginFn("_start", IRType.VOID); this.resetFnContext(); this.emit(IRInstr.label("entry")); + List mainArgs = List(); IRValue mainResult = IRValue.reg("main_result", IRType.I64); + if (this.mainFn != "") { this.emit(IRInstr.call(mainResult, this.mainFn, mainArgs)); } + else { this.emit(IRInstr.mov(mainResult, IRValue.ofInt(0, IRType.I64))); } + List exitArgs = List(); exitArgs.append(mainResult); IRValue exitDst = IRValue.reg("ex", IRType.VOID); + if (this.linux) { this.emit(IRInstr.syscall(exitDst, 60, exitArgs)); } + else { this.emit(IRInstr.call(exitDst, "__ext__ExitProcess", exitArgs)); } + this.emit(IRInstr.retVoid()); + } + + public lower(modules: List) { + this.registerClasses(modules); + this.generateTeardownRoutines(); + this.internStr("\n"); + Integer i = 0; + while (i < modules.length()) { + ArimoModule m = modules.get(i) as ArimoModule; + this.lowerModule(m); + i = i + 1; + } + this.generateStrlenHelper(); + this.generateListNew(); + this.generateListLength(); + this.generateListGet(); + this.generateListSet(); + this.generateListAppend(); + this.generateListRemoveAt(); + this.generateStrCmp(); + this.generateStrCat(); + this.generateSubstr(); + this.generateStartsWith(); + this.generateEndsWith(); + this.generateIndexOf(); + this.generateCharCodeAt(); + this.generateCharAt(); + this.generateI64ToStr(); + this.generatePrintlnInt("__arimo_println_int", true); + this.generatePrintlnInt("__arimo_print_int", false); + this.generatePrintHelper("__arimo_println", true); + this.generatePrintHelper("__arimo_print", false); + this.generateFopen(); + this.generateToLower(); + this.generateSystem(); + this.generateBuildArgs(); + this.generateStartFn(); + } + + private generateBuildArgs() { + this.beginFn("__arimo_build_args", IRType.PTR); + this.addParamToLast("argc", IRType.I64); this.addParamToLast("argvBase", IRType.PTR); + this.resetFnContext(); this.emit(IRInstr.label("entry")); + IRValue argcV = IRValue.reg("argc", IRType.I64); IRValue argvBaseV = IRValue.reg("argvBase", IRType.PTR); + String listReg = this.newReg(); IRValue listV = IRValue.reg(listReg, IRType.PTR); + List noArgs = List(); this.emit(IRInstr.call(listV, "__arimo_list_new", noArgs)); + String iReg = this.newReg(); IRValue iV = IRValue.reg(iReg, IRType.I64); + this.emit(IRInstr.mov(iV, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.label("bargs_loop")); this.emit(IRInstr.cmp(iV, argcV)); + this.emit(IRInstr.branch(IROpcode.JGE, "bargs_done")); + String offReg = this.newReg(); IRValue offV = IRValue.reg(offReg, IRType.I64); + this.emit(IRInstr.binop(IROpcode.SHL, offV, iV, IRValue.ofInt(3, IRType.I64))); + String slotReg = this.newReg(); IRValue slotV = IRValue.reg(slotReg, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, slotV, argvBaseV, offV)); + String ptrReg = this.newReg(); IRValue ptrV = IRValue.reg(ptrReg, IRType.PTR); + this.emit(IRInstr.load(ptrV, slotV, IRType.PTR)); + List appArgs = List(); appArgs.append(listV); appArgs.append(ptrV); + this.emit(IRInstr.call(IRValue.reg(this.newReg(), IRType.VOID), "__arimo_list_append", appArgs)); + this.emit(IRInstr.binop(IROpcode.ADD, iV, iV, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.jmp("bargs_loop")); this.emit(IRInstr.label("bargs_done")); + this.emit(IRInstr.ret(listV)); + } + + private generateFopen() { + this.beginFn("__arimo_fopen", IRType.I64); + this.addParamToLast("path", IRType.PTR); this.addParamToLast("mode", IRType.PTR); + this.resetFnContext(); this.emit(IRInstr.label("entry")); + IRValue pathV = IRValue.reg("path", IRType.PTR); IRValue modeV = IRValue.reg("mode", IRType.PTR); + String mbyteR = this.newReg(); IRValue mbyte = IRValue.reg(mbyteR, IRType.I64); + this.emit(IRInstr.load(mbyte, modeV, IRType.I8)); + String flagsR = this.newReg(); IRValue flags = IRValue.reg(flagsR, IRType.I64); + this.emit(IRInstr.cmp(mbyte, IRValue.ofInt(114, IRType.I64))); + this.emit(IRInstr.branch(IROpcode.JNE, "fo_nr")); this.emit(IRInstr.mov(flags, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.jmp("fo_op")); this.emit(IRInstr.label("fo_nr")); + this.emit(IRInstr.cmp(mbyte, IRValue.ofInt(97, IRType.I64))); + this.emit(IRInstr.branch(IROpcode.JNE, "fo_na")); + this.emit(IRInstr.mov(flags, IRValue.ofInt(1089, IRType.I64))); this.emit(IRInstr.jmp("fo_op")); + this.emit(IRInstr.label("fo_na")); this.emit(IRInstr.mov(flags, IRValue.ofInt(577, IRType.I64))); + this.emit(IRInstr.label("fo_op")); + String fdR = this.newReg(); IRValue fd = IRValue.reg(fdR, IRType.I64); + List openArgs = List(); openArgs.append(pathV); openArgs.append(flags); + openArgs.append(IRValue.ofInt(438, IRType.I64)); this.emit(IRInstr.syscall(fd, 2, openArgs)); + String retR = this.newReg(); IRValue ret = IRValue.reg(retR, IRType.I64); + this.emit(IRInstr.cmp(fd, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.branch(IROpcode.JL, "fo_er")); this.emit(IRInstr.mov(ret, fd)); + this.emit(IRInstr.jmp("fo_ok")); this.emit(IRInstr.label("fo_er")); + this.emit(IRInstr.mov(ret, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.label("fo_ok")); this.emit(IRInstr.ret(ret)); + } + + // generateToLower: ASCII lowercase helper — allocates new string, returns lowered copy. + // Input: PTR to null-terminated source string. + // Output: PTR to new null-terminated lowercase string (mmap-allocated). + private generateToLower() { + this.beginFn("__arimo_tolower", IRType.PTR); + this.addParamToLast("str", IRType.PTR); + this.resetFnContext(); this.emit(IRInstr.label("entry")); + IRValue strV = IRValue.reg("str", IRType.PTR); + + // 1. Get string length via strlen + String lenR = this.newReg(); IRValue lenV = IRValue.reg(lenR, IRType.I64); + List slArgs = List(); slArgs.append(strV); + this.emit(IRInstr.call(lenV, "__arimo_strlen", slArgs)); + + // 2. Allocate len+1 bytes via mmap (syscall 9) + String sizeR = this.newReg(); IRValue sizeV = IRValue.reg(sizeR, IRType.I64); + this.emit(IRInstr.binop(IROpcode.ADD, sizeV, lenV, IRValue.ofInt(1, IRType.I64))); + String bufR = this.newReg(); IRValue bufV = IRValue.reg(bufR, IRType.PTR); + List mmapArgs = List(); + mmapArgs.append(IRValue.ofInt(0, IRType.I64)); // addr = NULL + mmapArgs.append(sizeV); // length + mmapArgs.append(IRValue.ofInt(3, IRType.I64)); // PROT_READ|PROT_WRITE + mmapArgs.append(IRValue.ofInt(34, IRType.I64)); // MAP_PRIVATE|MAP_ANONYMOUS + mmapArgs.append(IRValue.ofInt(-1, IRType.I64)); // fd = -1 + mmapArgs.append(IRValue.ofInt(0, IRType.I64)); // offset = 0 + this.emit(IRInstr.syscall(bufV, 9, mmapArgs)); + + // 3. Loop: copy chars, lowercasing A-Z → a-z + String iR = this.newReg(); IRValue iV = IRValue.reg(iR, IRType.I64); + this.emit(IRInstr.mov(iV, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.label("tl_loop")); + this.emit(IRInstr.cmp(iV, lenV)); + this.emit(IRInstr.branch(IROpcode.JGE, "tl_done")); + + // Load char from source[i] + String srcAddrR = this.newReg(); IRValue srcAddrV = IRValue.reg(srcAddrR, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, srcAddrV, strV, iV)); + String chR = this.newReg(); IRValue chV = IRValue.reg(chR, IRType.I64); + this.emit(IRInstr.load(chV, srcAddrV, IRType.I8)); + + // If 'A' <= ch <= 'Z', add 32 + this.emit(IRInstr.cmp(chV, IRValue.ofInt(65, IRType.I64))); // 'A' + this.emit(IRInstr.branch(IROpcode.JL, "tl_skip")); + this.emit(IRInstr.cmp(chV, IRValue.ofInt(90, IRType.I64))); // 'Z' + this.emit(IRInstr.branch(IROpcode.JG, "tl_skip")); + String loR = this.newReg(); IRValue loV = IRValue.reg(loR, IRType.I64); + this.emit(IRInstr.binop(IROpcode.ADD, loV, chV, IRValue.ofInt(32, IRType.I64))); + this.emit(IRInstr.mov(chV, loV)); + this.emit(IRInstr.label("tl_skip")); + + // Store to dest[i] + String dstAddrR = this.newReg(); IRValue dstAddrV = IRValue.reg(dstAddrR, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, dstAddrV, bufV, iV)); + this.emit(IRInstr.store(chV, dstAddrV, IRType.I8)); + + // i++ + this.emit(IRInstr.binop(IROpcode.ADD, iV, iV, IRValue.ofInt(1, IRType.I64))); + this.emit(IRInstr.jmp("tl_loop")); + this.emit(IRInstr.label("tl_done")); + + // 4. Null-terminate + String nullAddrR = this.newReg(); IRValue nullAddrV = IRValue.reg(nullAddrR, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, nullAddrV, bufV, lenV)); + this.emit(IRInstr.store(IRValue.ofInt(0, IRType.I8), nullAddrV, IRType.I8)); + + // 5. Return new buffer + this.emit(IRInstr.ret(bufV)); + } + + // generateSystem: fork+execve+waitpid via Linux syscalls. + // system(cmd) — runs /bin/sh -c , returns exit status or -1 on error. + private generateSystem() { + this.beginFn("__arimo_system", IRType.I64); + this.addParamToLast("cmd", IRType.PTR); + this.resetFnContext(); this.emit(IRInstr.label("entry")); + IRValue cmdV = IRValue.reg("cmd", IRType.PTR); + + // Allocate argv[4] array on heap (4 pointers = 32 bytes) + String argvR = this.newReg(); IRValue argvV = IRValue.reg(argvR, IRType.PTR); + List mmapA = List(); + mmapA.append(IRValue.ofInt(0, IRType.I64)); // addr=NULL + mmapA.append(IRValue.ofInt(32, IRType.I64)); // 32 bytes + mmapA.append(IRValue.ofInt(3, IRType.I64)); // PROT_RW + mmapA.append(IRValue.ofInt(34, IRType.I64)); // MAP_PRIVATE|ANON + mmapA.append(IRValue.ofInt(-1, IRType.I64)); // fd=-1 + mmapA.append(IRValue.ofInt(0, IRType.I64)); // offset=0 + this.emit(IRInstr.syscall(argvV, 9, mmapA)); + + // argv[0] = "sh", argv[1] = "-c", argv[2] = cmd, argv[3] = NULL + String shStr = this.internStr("sh"); + String dashC = this.internStr("-c"); + String shPath = this.internStr("/bin/sh"); + this.emit(IRInstr.store(IRValue.global(shStr), argvV, IRType.PTR)); + String a1R = this.newReg(); IRValue a1V = IRValue.reg(a1R, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, a1V, argvV, IRValue.ofInt(8, IRType.I64))); + this.emit(IRInstr.store(IRValue.global(dashC), a1V, IRType.PTR)); + String a2R = this.newReg(); IRValue a2V = IRValue.reg(a2R, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, a2V, argvV, IRValue.ofInt(16, IRType.I64))); + this.emit(IRInstr.store(cmdV, a2V, IRType.PTR)); + String a3R = this.newReg(); IRValue a3V = IRValue.reg(a3R, IRType.PTR); + this.emit(IRInstr.binop(IROpcode.ADD, a3V, argvV, IRValue.ofInt(24, IRType.I64))); + this.emit(IRInstr.store(IRValue.ofInt(0, IRType.I64), a3V, IRType.PTR)); + + // fork() syscall 57 + String pidR = this.newReg(); IRValue pidV = IRValue.reg(pidR, IRType.I64); + this.emit(IRInstr.syscall(pidV, 57, List())); + this.emit(IRInstr.cmp(pidV, IRValue.ofInt(0, IRType.I64))); + this.emit(IRInstr.branch(IROpcode.JL, "sy_err")); + this.emit(IRInstr.branch(IROpcode.JE, "sy_child")); + + // Parent: wait4(pid, &status, 0, NULL) syscall 61 + this.emit(IRInstr.label("sy_parent")); + String statR = this.newReg(); IRValue statV = IRValue.reg(statR, IRType.PTR); + this.emit(IRInstr.alloc(statV, 8)); + List wArgs = List(); + wArgs.append(pidV); wArgs.append(statV); + wArgs.append(IRValue.ofInt(0, IRType.I64)); + wArgs.append(IRValue.ofInt(0, IRType.I64)); + this.emit(IRInstr.syscall(IRValue.reg(this.newReg(), IRType.I64), 61, wArgs)); + this.emit(IRInstr.ret(IRValue.ofInt(0, IRType.I64))); + + // Child: execve("/bin/sh", argv, NULL) syscall 59 + this.emit(IRInstr.label("sy_child")); + List exArgs = List(); + exArgs.append(IRValue.global(shPath)); + exArgs.append(argvV); + exArgs.append(IRValue.ofInt(0, IRType.I64)); + this.emit(IRInstr.syscall(IRValue.reg(this.newReg(), IRType.I64), 59, exArgs)); + // execve failed → fall through to error + this.emit(IRInstr.label("sy_err")); + this.emit(IRInstr.ret(IRValue.ofInt(-1, IRType.I64))); + } +} diff --git a/arimo/compiler/backend/IRToX64.arm b/arimo/compiler/backend/IRToX64.arm index 3815a7b..f7cc980 100644 --- a/arimo/compiler/backend/IRToX64.arm +++ b/arimo/compiler/backend/IRToX64.arm @@ -21,6 +21,7 @@ package arimo.compiler.backend; import arimo.compiler.backend.X64Reg; import arimo.compiler.backend.X64Encoder; import arimo.compiler.backend.RegAlloc; +import arimo.compiler.backend.SafeRegAlloc; import arimo.compiler.backend.IRFunction; import arimo.compiler.backend.IRInstr; import arimo.compiler.backend.IRValue; @@ -34,15 +35,23 @@ public class IRToX64 { private curName : String; private frame : Integer; private savedCount : Integer; - private linux : Boolean; + private linux : Boolean; + private useSafeRegAlloc : Boolean; + private safeRa : SafeRegAlloc; public constructor(enc: X64Encoder, ra: RegAlloc, linux: Boolean) { - this.enc = enc; - this.ra = ra; - this.curName = ""; - this.frame = 0; - this.savedCount = 0; - this.linux = linux; + this.enc = enc; + this.ra = ra; + this.curName = ""; + this.frame = 0; + this.savedCount = 0; + this.linux = linux; + this.useSafeRegAlloc = false; + } + + public setSafeRegAlloc(sra: SafeRegAlloc) { + this.safeRa = sra; + this.useSafeRegAlloc = true; } private argRegs() : List { @@ -144,6 +153,76 @@ public class IRToX64 { } } + // ===== SafeRegAlloc helpers (P4) ===== + + // safeLoadVal: load IR value into given scratch register from stack slot. + // Always stack-canonical — no physReg fast path. + private safeLoadVal(val: IRValue, scratchReg: Integer) : Integer { + if (val.kind == IRValueKind.IMM_INT) { + this.enc.movRI(scratchReg, val.immInt); + return scratchReg; + } + if (val.kind == IRValueKind.REG) { + this.safeRa.assertKnown(val.name); + this.safeRa.assertWritten(val.name); + Integer off = this.safeRa.slotOffsetByName(val.name); + this.enc.movRMem(scratchReg, X64Reg.RBP, off); + return scratchReg; + } + if (val.kind == IRValueKind.GLOBAL) { + this.enc.leaRip(scratchReg, val.name); + return scratchReg; + } + this.enc.xorZero(scratchReg); + return scratchReg; + } + + // safeStoreDst: store result from scratch register to its canonical stack slot. + // Marks slot written. After this call, value is on stack and scratchReg is dead. + private safeStoreDst(name: String, srcReg: Integer) { + this.safeRa.assertKnown(name); + Integer off = this.safeRa.slotOffsetByName(name); + this.enc.movMemR(X64Reg.RBP, off, srcReg); + this.safeRa.markValueWritten(name); + } + + // safeSlotOffset: delegate to SafeRegAlloc.slotOffsetByName. + private safeSlotOffset(name: String) : Integer { + return this.safeRa.slotOffsetByName(name); + } + + // safeBeginInstr: clean scratch state before emitting an instruction. + // Checks invariant: no dirty scratch regs from previous instruction. + private safeBeginInstr() { + this.safeRa.assertClean(); + this.safeRa.resetScratch(); + } + + // safeEndInstr: ensure scratch state is clean after instruction emission. + // Resets scratch cursor, then verifies invariant: dirty=false, sci=0. + private safeEndInstr() { + this.safeRa.resetScratch(); + this.safeRa.assertClean(); + } + + // safeLoadArg: load IR value directly into a specific register. + // Does NOT use scratch pool — used for CALL/SYSCALL arg setup where + // the target register is an ABI arg reg that will be clobbered by the call. + private safeLoadArg(val: IRValue, targetReg: Integer) { + if (val.kind == IRValueKind.IMM_INT) { + this.enc.movRI(targetReg, val.immInt); + } else if (val.kind == IRValueKind.REG) { + this.safeRa.assertKnown(val.name); + this.safeRa.assertWritten(val.name); + Integer off = this.safeRa.slotOffsetByName(val.name); + this.enc.movRMem(targetReg, X64Reg.RBP, off); + } else if (val.kind == IRValueKind.GLOBAL) { + this.enc.leaRip(targetReg, val.name); + } else { + this.enc.xorZero(targetReg); + } + } + private emitBinop(op: Integer, instr: IRInstr) { IRValue a = instr.operands.get(0) as IRValue; IRValue b = instr.operands.get(1) as IRValue; @@ -191,7 +270,83 @@ public class IRToX64 { return used; } + // ===== SafeRegAlloc emitFunction (P5) ===== + + private emitFunctionSafe(fn: IRFunction) { + this.safeRa.allocate(fn); + Integer frameSize = this.safeRa.frameSize(); + this.frame = frameSize; + this.curName = fn.name; + this.savedCount = 0; // no callee-saved tracking in safe mode + + this.enc.defineLabel(fn.name); + + // _start raw prologue (Linux): capture argc/argv from initial stack + if (this.linux && fn.name == "_start") { + this.enc.movRMem(X64Reg.RAX, X64Reg.RSP, 0); // rax = [rsp] = argc + this.enc.leaRip(X64Reg.R11, "__arimo_argc"); + this.enc.movMemR(X64Reg.R11, 0, X64Reg.RAX); // [__arimo_argc] = argc + this.enc.leaMem(X64Reg.R10, X64Reg.RSP, 8); // r10 = rsp + 8 + this.enc.leaRip(X64Reg.R11, "__arimo_argv"); + this.enc.movMemR(X64Reg.R11, 0, X64Reg.R10); // [__arimo_argv] = argv + } + + if (!this.linux && fn.name == "_start") { + this.enc.subRI(X64Reg.RSP, 8); + } + + // Standard prologue — no callee-saved register saves + this.enc.pushR(X64Reg.RBP); + this.enc.movRR(X64Reg.RBP, X64Reg.RSP); + if (frameSize > 0) { + this.enc.subRI(X64Reg.RSP, frameSize); + } + + // Save incoming register parameters (0..5 on Linux, 0..3 on Windows) + List paramArgRegs = this.argRegs(); + Integer pi = 0; + while (pi < fn.params.length() && pi < paramArgRegs.length()) { + IRParam param = fn.params.get(pi) as IRParam; + Integer argReg = paramArgRegs.get(pi) as Integer; + this.safeStoreDst(param.name, argReg); + pi = pi + 1; + } + // Save incoming stack parameters (beyond register count). + // Stack args are at [RBP+16], [RBP+24], ... (above return addr and saved RBP). + while (pi < fn.params.length()) { + IRParam param = fn.params.get(pi) as IRParam; + Integer stackOff = 16 + (pi - paramArgRegs.length()) * 8; + this.enc.movRMem(X64Reg.RAX, X64Reg.RBP, stackOff); + this.safeStoreDst(param.name, X64Reg.RAX); + pi = pi + 1; + } + this.safeRa.markAllParamsWritten(fn); + + // Body: instruction emission loop + Integer i = 0; + while (i < fn.instrs.length()) { + this.safeBeginInstr(); + this.emitInstrSafe(fn.instrs.get(i) as IRInstr); + this.safeEndInstr(); + i = i + 1; + } + + // Epilog — simple: no callee-saved restores + this.enc.defineLabel("${fn.name}__epilog"); + if (frameSize > 0) { + this.enc.addRI(X64Reg.RSP, frameSize); + } + this.enc.popR(X64Reg.RBP); + this.enc.ret(); + } + public emitFunction(fn: IRFunction) { + // ===== SafeRegAlloc mode (P5) ===== + if (this.useSafeRegAlloc) { + this.emitFunctionSafe(fn); + return; + } + this.ra.allocate(fn); Integer spillBytes = this.ra.totalSpillBytes() * 8; @@ -213,6 +368,20 @@ public class IRToX64 { this.savedCount = N; this.enc.defineLabel(fn.name); + + // _start raw prologue (Linux): capture argc/argv from initial stack + if (this.linux && fn.name == "_start") { + // [rsp] = argc, [rsp+8] = argv[0], [rsp+16] = argv[1], ... + // Read argc → @__arimo_argc + this.enc.movRMem(X64Reg.RAX, X64Reg.RSP, 0); // rax = [rsp] = argc + this.enc.leaRip(X64Reg.R11, "__arimo_argc"); + this.enc.movMemR(X64Reg.R11, 0, X64Reg.RAX); // [__arimo_argc] = argc + // Read argv ptr → @__arimo_argv + this.enc.leaMem(X64Reg.R10, X64Reg.RSP, 8); // r10 = rsp + 8 + this.enc.leaRip(X64Reg.R11, "__arimo_argv"); + this.enc.movMemR(X64Reg.R11, 0, X64Reg.R10); // [__arimo_argv] = argv + } + if (!this.linux && fn.name == "_start") { this.enc.subRI(X64Reg.RSP, 8); } @@ -247,6 +416,17 @@ public class IRToX64 { Integer i = 0; while (i < fn.instrs.length()) { + // Emit spill saves: store values whose regs were reassigned at this position + Integer si = 0; + while (si < this.ra.spillPoss.length()) { + if (this.ra.spillPoss.get(si) as Integer == i) { + Integer slot = this.ra.spillSaves.get(si) as Integer; + Integer preg = this.ra.spillRegs.get(si) as Integer; + this.enc.movMemR(X64Reg.RBP, this.spillOff(slot), preg); + } + si = si + 1; + } + IRInstr instr = fn.instrs.get(i) as IRInstr; this.emitInstr(instr); i = i + 1; @@ -398,6 +578,11 @@ public class IRToX64 { } if (op == IROpcode.SYSCALL) { + // Save caller-saved regs used for syscall args (r8/r9/r10 may hold live IR values) + this.enc.pushR(X64Reg.R8); + this.enc.pushR(X64Reg.R9); + this.enc.pushR(X64Reg.R10); + Integer sysNum = instr.ty; List scRegs = this.syscallArgRegs(); Integer sargc = instr.operands.length(); @@ -425,6 +610,11 @@ public class IRToX64 { if (!instr.dst.isNone() && instr.dst.kind == IRValueKind.REG) { this.storeDst(instr.dst.name, X64Reg.RAX); } + + // Restore caller-saved regs (reverse order) + this.enc.popR(X64Reg.R10); + this.enc.popR(X64Reg.R9); + this.enc.popR(X64Reg.R8); return; } @@ -480,11 +670,311 @@ public class IRToX64 { } else { callTarget = instr.label; } + // Save caller-saved regs that may hold live IR values (R8/R9 in alloc pool) + this.enc.pushR(X64Reg.R8); + this.enc.pushR(X64Reg.R9); this.enc.callRel(callTarget); + this.enc.popR(X64Reg.R9); + this.enc.popR(X64Reg.R8); if (!instr.dst.isNone() && instr.dst.kind == IRValueKind.REG) { this.storeDst(instr.dst.name, X64Reg.RAX); } return; } } + + // ===== SafeRegAlloc emitInstr (P6-A) ===== + + private emitInstrSafe(instr: IRInstr) { + Integer op = instr.op; + + // --- Control flow (no register usage) --- + + if (op == IROpcode.LABEL) { this.enc.defineLabel(instr.label); return; } + if (op == IROpcode.JMP) { this.enc.jmp(instr.label); return; } + if (op == IROpcode.JE) { this.enc.je(instr.label); return; } + if (op == IROpcode.JNE) { this.enc.jne(instr.label); return; } + if (op == IROpcode.JG) { this.enc.jg(instr.label); return; } + if (op == IROpcode.JL) { this.enc.jl(instr.label); return; } + if (op == IROpcode.JGE) { this.enc.jge(instr.label); return; } + if (op == IROpcode.JLE) { this.enc.jle(instr.label); return; } + if (op == IROpcode.JZ) { this.enc.jz(instr.label); return; } + if (op == IROpcode.JNZ) { this.enc.jnz(instr.label); return; } + + // --- RET --- + + if (op == IROpcode.RET) { + if (instr.operands.length() > 0) { + IRValue rv = instr.operands.get(0) as IRValue; + // Load return value directly into RAX (not in scratch pool) + if (rv.kind == IRValueKind.IMM_INT) { + this.enc.movRI(X64Reg.RAX, rv.immInt); + } else if (rv.kind == IRValueKind.REG) { + this.safeRa.assertKnown(rv.name); + Integer off = this.safeRa.slotOffsetByName(rv.name); + this.enc.movRMem(X64Reg.RAX, X64Reg.RBP, off); + } else if (rv.kind == IRValueKind.GLOBAL) { + this.enc.leaRip(X64Reg.RAX, rv.name); + } else { + this.enc.xorZero(X64Reg.RAX); + } + } + this.enc.jmp("${this.curName}__epilog"); + return; + } + + // --- MOV --- + + if (op == IROpcode.MOV) { + Integer sr = this.safeRa.allocScratch(); + IRValue src = instr.operands.get(0) as IRValue; + sr = this.safeLoadVal(src, sr); + this.safeStoreDst(instr.dst.name, sr); + return; + } + + // --- Integer BINOP: ADD SUB MUL AND OR XOR --- + + if (op == IROpcode.ADD || op == IROpcode.SUB || + op == IROpcode.AND || op == IROpcode.OR || op == IROpcode.XOR || + op == IROpcode.MUL) { + List regs = this.safeRa.allocScratch3(); + IRValue a = instr.operands.get(0) as IRValue; + IRValue b = instr.operands.get(1) as IRValue; + Integer ra = this.safeLoadVal(a, regs.get(0) as Integer); + Integer rb = this.safeLoadVal(b, regs.get(1) as Integer); + Integer dr = regs.get(2) as Integer; + if (dr != ra) { this.enc.movRR(dr, ra); } + if (op == IROpcode.ADD) { this.enc.addRR(dr, rb); } + else if (op == IROpcode.SUB) { this.enc.subRR(dr, rb); } + else if (op == IROpcode.AND) { this.enc.andRR(dr, rb); } + else if (op == IROpcode.OR) { this.enc.orRR(dr, rb); } + else if (op == IROpcode.XOR) { this.enc.xorRR(dr, rb); } + else if (op == IROpcode.MUL) { this.enc.imulRR(dr, rb); } + this.safeStoreDst(instr.dst.name, dr); + return; + } + + // --- DIV / MOD (uses RAX:RDX implicitly, not in scratch pool) --- + + if (op == IROpcode.DIV || op == IROpcode.MOD) { + List regs = this.safeRa.allocScratch2(); + IRValue a = instr.operands.get(0) as IRValue; + IRValue b = instr.operands.get(1) as IRValue; + // Load dividend into a scratch, then move to RAX + Integer ra = this.safeLoadVal(a, regs.get(0) as Integer); + this.enc.movRR(X64Reg.RAX, ra); + this.enc.cqo(); + // Load divisor into scratch, use as idiv operand + Integer rb = this.safeLoadVal(b, regs.get(1) as Integer); + this.enc.idivR(rb); + if (op == IROpcode.DIV) { + this.safeStoreDst(instr.dst.name, X64Reg.RAX); + } else { + this.safeStoreDst(instr.dst.name, X64Reg.RDX); + } + return; + } + + // --- SHL / SHR (shift amount must be in CL; use RAX as temp) --- + + if (op == IROpcode.SHL || op == IROpcode.SHR) { + List regs = this.safeRa.allocScratch2(); + IRValue a = instr.operands.get(0) as IRValue; + IRValue b = instr.operands.get(1) as IRValue; + // Load value into scratch, then move to RAX (safe temp) + Integer ra = this.safeLoadVal(a, regs.get(0) as Integer); + this.enc.movRR(X64Reg.RAX, ra); + // Load shift amount, move to CL (RCX) + Integer rb = this.safeLoadVal(b, regs.get(1) as Integer); + if (rb != X64Reg.RCX) { this.enc.movRR(X64Reg.RCX, rb); } + if (op == IROpcode.SHL) { this.enc.shlRCL(X64Reg.RAX); } + else { this.enc.shrRCL(X64Reg.RAX); } + this.safeStoreDst(instr.dst.name, X64Reg.RAX); + return; + } + + // --- NEG --- + + if (op == IROpcode.NEG) { + Integer sr = this.safeRa.allocScratch(); + IRValue src = instr.operands.get(0) as IRValue; + sr = this.safeLoadVal(src, sr); + this.enc.negR(sr); + this.safeStoreDst(instr.dst.name, sr); + return; + } + + // --- NOT --- + + if (op == IROpcode.NOT) { + Integer sr = this.safeRa.allocScratch(); + IRValue src = instr.operands.get(0) as IRValue; + sr = this.safeLoadVal(src, sr); + this.enc.notR(sr); + this.safeStoreDst(instr.dst.name, sr); + return; + } + + // --- CMP --- + + if (op == IROpcode.CMP) { + List regs = this.safeRa.allocScratch2(); + IRValue a = instr.operands.get(0) as IRValue; + IRValue b = instr.operands.get(1) as IRValue; + Integer ra = this.safeLoadVal(a, regs.get(0) as Integer); + Integer rb = this.safeLoadVal(b, regs.get(1) as Integer); + this.enc.cmpRR(ra, rb); + // CMP has no dst — flags are set, consumed by following branch + return; + } + + // --- ALLOC (stack allocation pointer) --- + + if (op == IROpcode.ALLOC) { + Integer sr = this.safeRa.allocScratch(); + this.enc.leaMem(sr, X64Reg.RSP, 0); + this.safeStoreDst(instr.dst.name, sr); + return; + } + + // --- LOAD (memory read) --- + + if (op == IROpcode.LOAD) { + List regs = this.safeRa.allocScratch2(); + IRValue ptr = instr.operands.get(0) as IRValue; + Integer pr = this.safeLoadVal(ptr, regs.get(0) as Integer); + Integer dr = regs.get(1) as Integer; + if (instr.ty == IRType.I8) { + this.enc.movzxByteMem(dr, pr); + } else { + this.enc.movRMemBase(dr, pr); + } + this.safeStoreDst(instr.dst.name, dr); + return; + } + + // --- STORE (memory write) --- + + if (op == IROpcode.STORE) { + List regs = this.safeRa.allocScratch2(); + IRValue val = instr.operands.get(0) as IRValue; + IRValue ptr = instr.operands.get(1) as IRValue; + Integer vr = this.safeLoadVal(val, regs.get(0) as Integer); + Integer pr = this.safeLoadVal(ptr, regs.get(1) as Integer); + if (instr.ty == IRType.I8) { + this.enc.movByteMemR(pr, vr); + } else { + this.enc.movMemBaseR(pr, vr); + } + // STORE has no dst — value written to memory + return; + } + + // --- Type conversions: ITOP PTOI ZEXT SEXT TRUNC --- + + if (op == IROpcode.ITOP || op == IROpcode.PTOI || + op == IROpcode.ZEXT || op == IROpcode.SEXT || op == IROpcode.TRUNC) { + Integer sr = this.safeRa.allocScratch(); + IRValue src = instr.operands.get(0) as IRValue; + sr = this.safeLoadVal(src, sr); + this.safeStoreDst(instr.dst.name, sr); + return; + } + + // --- SYSCALL --- + + if (op == IROpcode.SYSCALL) { + Integer sysNum = instr.ty; + List scRegs = this.syscallArgRegs(); + Integer sargc = instr.operands.length(); + Integer si = 0; + while (si < sargc && si < scRegs.length()) { + IRValue sarg = instr.operands.get(si) as IRValue; + Integer sreg = scRegs.get(si) as Integer; + this.safeLoadArg(sarg, sreg); + si = si + 1; + } + if (sargc > scRegs.length()) { + this.enc.hasError = true; + IO.println("arc: [FAIL] SafeRegAlloc: SYSCALL too many args (${sargc}) in fn ${this.curName}"); + } + this.enc.movRI(X64Reg.RAX, sysNum); + this.enc.syscall(); + if (!instr.dst.isNone() && instr.dst.kind == IRValueKind.REG) { + this.safeStoreDst(instr.dst.name, X64Reg.RAX); + } + return; + } + + // --- CALL --- + + if (op == IROpcode.CALL) { + List argRegsLocal = this.argRegs(); + Integer argc = instr.operands.length(); + Integer ai = 0; + while (ai < argc && ai < argRegsLocal.length()) { + IRValue arg = instr.operands.get(ai) as IRValue; + Integer areg = argRegsLocal.get(ai) as Integer; + this.safeLoadArg(arg, areg); + ai = ai + 1; + } + // Stack args (>reg count): write to [RSP+off] and align RSP to 16 before CALL. + // Linux ABI: args 7+ at [RSP+0],[RSP+8],... before call (→ [RSP+8],[RSP+16],... after). + Integer stackArgStart = argRegsLocal.length(); + Integer stackArgCount = argc - stackArgStart; + Integer stackBytes = 0; + Integer padBytes = 0; + if (stackArgCount > 0) { + stackBytes = stackArgCount * 8; + if (stackBytes % 16 != 0) { padBytes = 8; } + Integer totalSub = stackBytes + padBytes; + if (totalSub > 0) { this.enc.subRI(X64Reg.RSP, totalSub); } + Integer si = 0; + while (si < stackArgCount) { + IRValue sarg = instr.operands.get(stackArgStart + si) as IRValue; + Integer soff = si * 8; + if (sarg.kind == IRValueKind.IMM_INT) { + this.enc.movRI(X64Reg.RAX, sarg.immInt); + this.enc.movMemR(X64Reg.RSP, soff, X64Reg.RAX); + } else if (sarg.kind == IRValueKind.REG) { + this.safeRa.assertKnown(sarg.name); + Integer slOff = this.safeRa.slotOffsetByName(sarg.name); + this.enc.movRMem(X64Reg.RAX, X64Reg.RBP, slOff); + this.enc.movMemR(X64Reg.RSP, soff, X64Reg.RAX); + } else if (sarg.kind == IRValueKind.GLOBAL) { + this.enc.leaRip(X64Reg.RAX, sarg.name); + this.enc.movMemR(X64Reg.RSP, soff, X64Reg.RAX); + } + si = si + 1; + } + } + String callTarget = ""; + if (instr.label.startsWith("__ext__")) { + String extName = instr.label.substring(7, instr.label.length()); + if (this.linux) { + callTarget = extName; + } else { + callTarget = "${extName}__thunk"; + } + } else { + callTarget = instr.label; + } + // In safe mode, no live IR values in registers — no R8/R9 push/pop needed + this.enc.callRel(callTarget); + // Clean up stack args after call + if (stackBytes + padBytes > 0) { + this.enc.addRI(X64Reg.RSP, stackBytes + padBytes); + } + if (!instr.dst.isNone() && instr.dst.kind == IRValueKind.REG) { + this.safeStoreDst(instr.dst.name, X64Reg.RAX); + } + return; + } + + // --- Unsupported opcode in safe mode --- + + this.enc.hasError = true; + IO.println("arc: [FAIL] SafeRegAlloc: unsupported opcode ${IROpcode.name(op)} (${op}) in fn ${this.curName}"); + } } diff --git a/arimo/compiler/backend/NativeBackend.arm b/arimo/compiler/backend/NativeBackend.arm index 0aaee4e..60a557d 100644 --- a/arimo/compiler/backend/NativeBackend.arm +++ b/arimo/compiler/backend/NativeBackend.arm @@ -23,6 +23,7 @@ import arimo.compiler.backend.IRLower; import arimo.compiler.backend.IRToX64; import arimo.compiler.backend.IRFunction; import arimo.compiler.backend.RegAlloc; +import arimo.compiler.backend.SafeRegAlloc; import arimo.compiler.backend.X64Encoder; import arimo.compiler.backend.PEWriter; import arimo.compiler.backend.ELFWriter; @@ -33,20 +34,26 @@ public class NativeBackend { private ra : RegAlloc; private pe : PEWriter; private elf : ELFWriter; - private linux : Boolean; + private linux : Boolean; + private useSafeRegAlloc : Boolean; public constructor(linux: Boolean) { - this.linux = linux; - this.lowerer = IRLower(linux); - this.enc = X64Encoder(); - this.ra = RegAlloc(); - this.pe = PEWriter(this.enc); - this.elf = ELFWriter(this.enc); + this.linux = linux; + this.lowerer = IRLower(linux); + this.enc = X64Encoder(); + this.ra = RegAlloc(); + this.pe = PEWriter(this.enc); + this.elf = ELFWriter(this.enc); + this.useSafeRegAlloc = false; if (!linux) { this.setupImports(); } } + public setSafeRegAlloc(enabled: Boolean) { + this.useSafeRegAlloc = enabled; + } + private setupImports() { this.pe.addImport("ExitProcess"); this.pe.addImport("GetStdHandle"); @@ -61,6 +68,10 @@ public class NativeBackend { this.lowerer.lower(modules); IRToX64 irx = IRToX64(this.enc, this.ra, this.linux); + if (this.useSafeRegAlloc) { + SafeRegAlloc sra = SafeRegAlloc(); + irx.setSafeRegAlloc(sra); + } Integer entryOff = 0; Integer fi = 0; @@ -71,6 +82,11 @@ public class NativeBackend { fi = fi + 1; } + if (this.enc.hasError) { + IO.println("arc: build failed — errors detected during code generation"); + return; + } + Integer si = 0; if (this.linux) { while (si < this.lowerer.strNames.length()) { @@ -79,7 +95,18 @@ public class NativeBackend { this.elf.addString(nm, ct); si = si + 1; } + Integer bi = 0; + while (bi < this.lowerer.bssNames.length()) { + String bnm = this.lowerer.bssNames.get(bi) as String; + Integer bsz = this.lowerer.bssSizes.get(bi) as Integer; + this.elf.addBss(bnm, bsz); + bi = bi + 1; + } List elfBytes = this.elf.build(entryOff); + if (this.enc.hasError) { + IO.println("arc: build failed — unresolved labels or fixup errors"); + return; + } this.elf.writeTo(outPath, elfBytes); } else { while (si < this.lowerer.strNames.length()) { @@ -89,6 +116,10 @@ public class NativeBackend { si = si + 1; } List peBytes = this.pe.build(entryOff); + if (this.enc.hasError) { + IO.println("arc: build failed — unresolved labels or fixup errors"); + return; + } this.pe.writeTo(outPath, peBytes); } }