// Type checking and LLVM IR generation (as text), written in Finch. // // The output is a .ll file that clang turns into a program, linked with the same // C runtime the main compiler uses. This compiler copies values like the main one // (so programs behave the same) but never frees them: it is the bootstrap compiler. import ast struct Var { str name ast.Type ty str slot bool readonly int order // how many variables were declared before this one (for defer) } // A scope's variables, and the defers that run when it ends, newest first. // A defer sees only the variables declared before it: `orders` says how many. struct Scope { []Var vars []ast.Node defers []int orders } struct Loop { str cont str brk int depth // the scope depth at the loop: break and continue unwind down to it } struct FnSig { str module str name str recv // a method of this struct ("" for a function) bool fallible // -> T!: returns { i1 ok, T value, str error } bool mutself // the method changes self str ll ast.Type ret []ast.Type params []str pnames []bool borrow int prog int index } struct StructDef { str module str name []str fnames []ast.Type ftypes int prog int index int state // 0 new, 1 resolving, 2 done bool owning } struct Val { str v ast.Type ty bool fresh // a new value nobody holds: it can be stored without copying bool lit // a number written in the code } struct Place { str addr ast.Type ty } struct Ref { bool isPlace Place pl Val val } struct Gen { []ast.Program progs []str files []str head []str out []str body []str allocas int tmp int lbl int nstr bool term []Scope scopes []Loop loops FnSig cur str module []FnSig sigs []StructDef structs []str done []str mm // names of methods that change self, for the mutation analysis int nvars // variables declared so far in this function str ftarget // the call an enclosing or / try is waiting on (see nodeKey) str ffailed // set by that call: an i1, true if it failed str fmsg // and the error text bool deferOn // running a defer: variables declared after it are out of sight int deferLimit int inDefer } // ---------- basics ---------- fn fail(ptr[Gen] g, ast.Node n, str msg) { print(g.files[n.file] + ":" + str(n.line) + ":" + str(n.col) + ": error: " + msg) exit(1) } fn emit(ptr[Gen] g, str line) { g.body.push(" " + line) } fn tmpName(ptr[Gen] g) -> str { g.tmp += 1 return "%t" + str(g.tmp) } fn newLabel(ptr[Gen] g, str hint) -> str { g.lbl += 1 return hint + str(g.lbl) } fn label(ptr[Gen] g, str l) { g.body.push(l + ":") g.term = false } fn br(ptr[Gen] g, str l) { if !g.term { emit(g, "br label %" + l) g.term = true } } fn condBr(ptr[Gen] g, str c, str yes, str no) { emit(g, "br i1 " + c + ", label %" + yes + ", label %" + no) g.term = true } fn alloca(ptr[Gen] g, str ty) -> str { g.tmp += 1 name := "%a" + str(g.tmp) g.allocas.push(" " + name + " = alloca " + ty + ", align 16") return name } fn load(ptr[Gen] g, str ty, str at) -> str { t := tmpName(g) emit(g, t + " = load " + ty + ", ptr " + at) return t } fn store(ptr[Gen] g, str ty, str v, str at) { emit(g, "store " + ty + " " + v + ", ptr " + at) } fn spill(ptr[Gen] g, Val v) -> str { a := alloca(g, llty(g, v.ty)) store(g, llty(g, v.ty), v.v, a) return a } fn op(ptr[Gen] g, str text) -> str { t := tmpName(g) emit(g, t + " = " + text) return t } fn has([]str list, str x) -> bool { return list.contains(x) } // ---------- types ---------- fn T(str kind) -> ast.Type { return ast.simple(kind) } // `int` is i64 and `float` is f64, as in the C++ compiler: the everyday names // for the two default sizes, and the only way they are ever written back. fn isSigned(ast.Type t) -> bool { k := t.kind return k == "int" || k == "i8" || k == "i16" || k == "i32" } fn isUnsigned(ast.Type t) -> bool { k := t.kind return k == "u8" || k == "u16" || k == "u32" || k == "u64" } fn isInt(ast.Type t) -> bool { return isSigned(t) || isUnsigned(t) } fn isFloat(ast.Type t) -> bool { return t.kind == "float" || t.kind == "f32" } fn isNumber(ast.Type t) -> bool { return isInt(t) || isFloat(t) } fn bitsOf(ast.Type t) -> int { k := t.kind if k == "bool" { return 1 } if k == "char" || k == "i8" || k == "u8" { return 8 } if k == "i16" || k == "u16" { return 16 } if k == "i32" || k == "u32" || k == "f32" { return 32 } return 64 } // Can every value of `from` be stored in `to`? (i8 -> i32 yes, i32 -> u32 no) fn widens(ast.Type from, ast.Type to) -> bool { if !isInt(from) || !isInt(to) { return false } if isSigned(from) && isUnsigned(to) { return false } return bitsOf(to) > bitsOf(from) } // Does the number `text` (written in the code, of type `from`) fit into `to`? fn fits(str text, ast.Type from, ast.Type to) -> bool { if !isInt(to) { return false } n := int(text) b := bitsOf(to) if n < 0 { if isUnsigned(to) { return false } if b == 64 { return true } return n >= 0 - (1 << (b - 1)) } if b == 64 { return true } max := (1 << b) - 1 if isSigned(to) { max = (1 << (b - 1)) - 1 } return n <= max } fn findStruct(ptr[Gen] g, str module, str name) -> int { for i in 0..g.structs.len { if g.structs[i].module == module && g.structs[i].name == name { return i } } return -1 } fn progOf(ptr[Gen] g, str module) -> int { for i in 0..g.progs.len { if g.progs[i].module == module { return i } } return -1 } fn imports(ptr[Gen] g, str module) -> bool { return g.progs[progOf(g, g.module)].imports.contains(module) } fn resolve(ptr[Gen] g, ast.Type t, ast.Node at) -> ast.Type { if t.kind == "array" { return ast.arrayOf(resolve(g, t.elem[0], at)) } if t.kind == "map" { return ast.mapOf(resolve(g, t.elem[0], at), resolve(g, t.elem[1], at)) } if t.kind == "ptr" { if t.elem.len == 0 { return t } return ast.ptrTo(resolve(g, t.elem[0], at)) } if t.kind != "named" { return t } module := g.module if t.module.len > 0 { if !imports(g, t.module) { fail(g, at, "'" + t.module + "' is not imported here (add: import " + t.module + ")") } module = t.module } if findStruct(g, module, t.name) < 0 { fail(g, at, "there is no type named '" + ast.show(t) + "'") } return ast.Type(kind: "struct", name: t.name, module: module) } fn modName(str m) -> str { if m.len == 0 { return "main" } return m } fn llty(ptr[Gen] g, ast.Type t) -> str { k := t.kind if k == "bool" { return "i1" } if isInt(t) || k == "char" { return "i" + str(bitsOf(t)) } if k == "float" { return "double" } if k == "f32" { return "float" } if k == "str" || k == "array" { return "{ ptr, i64, i64 }" } if k == "map" { return "{ ptr, i64, i64, i64, ptr, i64, i64 }" // entries, len, used, cap, index, icap, adds } if k == "ptr" || k == "null" { return "ptr" } if k == "void" { return "void" } return "%S." + modName(t.module) + "." + t.name } // What a function returns as far as LLVM is concerned: a function that can fail // returns { i1 ok, T value, str error } (an i8 stands in for no value). fn retTy(ptr[Gen] g, FnSig sig) -> str { if !sig.fallible { return llty(g, sig.ret) } v := "i8" if sig.ret.kind != "void" { v = llty(g, sig.ret) } return "{ i1, " + v + ", { ptr, i64, i64 } }" } // Identifies a call node, so that the call an `or` / `try` is waiting for can be told from the ones inside it. fn nodeKey(ast.Node n) -> str { return n.kind + ":" + str(n.line) + ":" + str(n.col) + ":" + str(n.file) } fn zeroOf(ast.Type t) -> str { k := t.kind if isInt(t) || k == "char" { return "0" } if k == "bool" { return "false" } if isFloat(t) { return "0.0" } if k == "ptr" || k == "null" { return "null" } return "zeroinitializer" } fn sizeOf(ptr[Gen] g, ast.Type t) -> str { return "ptrtoint (ptr getelementptr (" + llty(g, t) + ", ptr null, i32 1) to i64)" } fn owning(ptr[Gen] g, ast.Type t) -> bool { if t.kind == "str" || t.kind == "array" || t.kind == "map" { return true } if t.kind == "struct" { return g.structs[findStruct(g, t.module, t.name)].owning } return false } fn typeKey(ast.Type t) -> str { if t.kind == "array" { return "arr_" + typeKey(t.elem[0]) } if t.kind == "map" { return "map_" + typeKey(t.elem[0]) + "_" + typeKey(t.elem[1]) } if t.kind == "struct" { return "S_" + modName(t.module) + "_" + t.name } return t.kind } // ---------- string constants ---------- fn hex(int n) -> str { digits := "0123456789ABCDEF" return str(digits[n / 16]) + str(digits[n % 16]) } fn strGlobal(ptr[Gen] g, str s) -> str { g.nstr += 1 name := "@.str." + str(g.nstr) []str enc for c in s { n := int(c) if n < 0 { n += 256 } if n >= 32 && n < 127 && c != '"' && c != '\\' { enc.push(str(c)) } else { enc.push("\\" + hex(n)) } } g.head.push(name + " = private unnamed_addr constant [" + str(s.len + 1) + " x i8] c\"" + enc.join("") + "\\00\"") return name } fn strConst(ptr[Gen] g, str s) -> Val { gl := strGlobal(g, s) return Val(v: "{ ptr " + gl + ", i64 " + str(s.len) + ", i64 0 }", ty: T("str"), fresh: true) } // ---------- runtime errors ---------- fn panicIf(ptr[Gen] g, str cond, str msg, ast.Node at) { bad := newLabel(g, "panic") ok := newLabel(g, "ok") condBr(g, cond, bad, ok) label(g, bad) file := strGlobal(g, g.files[at.file]) m := strGlobal(g, msg) emit(g, "call void @finch_panic(ptr " + file + ", i64 " + str(at.line) + ", ptr " + m + ")") emit(g, "unreachable") g.term = true label(g, ok) } fn boundsCheck(ptr[Gen] g, str i, str len, ast.Node at) { bad := newLabel(g, "range") ok := newLabel(g, "inrange") c := op(g, "icmp uge i64 " + i + ", " + len) condBr(g, c, bad, ok) label(g, bad) file := strGlobal(g, g.files[at.file]) emit(g, "call void @finch_panic_index(ptr " + file + ", i64 " + str(at.line) + ", i64 " + i + ", i64 " + len + ")") emit(g, "unreachable") g.term = true label(g, ok) } // ---------- helper functions (generated once per type) ---------- struct Saved { []str body []str allocas int tmp bool term } fn beginHelper(ptr[Gen] g) -> Saved { s := Saved(body: g.body, allocas: g.allocas, tmp: g.tmp, term: g.term) []str empty g.body = empty g.allocas = empty g.tmp = 0 g.term = false return s } fn endHelper(ptr[Gen] g, Saved s, str header) { g.out.push(header + " {\nentry:\n" + g.allocas.join("\n") + "\n" + g.body.join("\n") + "\n}\n") g.body = s.body g.allocas = s.allocas g.tmp = s.tmp g.term = s.term } // for i in 0..n, inside the current function fn loopStart(ptr[Gen] g, str n) -> []str { iv := alloca(g, "i64") store(g, "i64", "0", iv) cond := newLabel(g, "n.cond") body := newLabel(g, "n.body") end := newLabel(g, "n.end") br(g, cond) label(g, cond) i := load(g, "i64", iv) c := op(g, "icmp slt i64 " + i + ", " + n) condBr(g, c, body, end) label(g, body) return [iv, cond, end, i] } fn loopEnd(ptr[Gen] g, []str l) { i := load(g, "i64", l[0]) store(g, "i64", op(g, "add i64 " + i + ", 1"), l[0]) br(g, l[1]) label(g, l[2]) } fn copyAt(ptr[Gen] g, str dst, str src, ast.Type t) { if t.kind == "str" { emit(g, "call void @finch_str_copy(ptr " + dst + ", ptr " + src + ")") } else if t.kind == "array" || t.kind == "map" || (t.kind == "struct" && owning(g, t)) { emit(g, "call void " + copyFn(g, t) + "(ptr " + dst + ", ptr " + src + ")") } else { store(g, llty(g, t), load(g, llty(g, t), src), dst) } } fn copyFn(ptr[Gen] g, ast.Type t) -> str { name := "@finch.copy." + typeKey(t) if has(g.done, name) { return name } g.done.push(name) s := beginHelper(g) if t.kind == "array" { et := t.elem[0] emit(g, "call void @finch_arr_clone_raw(ptr %dst, ptr %src, i64 " + sizeOf(g, et) + ")") if owning(g, et) { d := load(g, "ptr", "%dst") sp := load(g, "ptr", "%src") len := load(g, "i64", op(g, "getelementptr inbounds { ptr, i64, i64 }, ptr %src, i32 0, i32 1")) l := loopStart(g, len) dp := op(g, "getelementptr inbounds " + llty(g, et) + ", ptr " + d + ", i64 " + l[3]) spp := op(g, "getelementptr inbounds " + llty(g, et) + ", ptr " + sp + ", i64 " + l[3]) copyAt(g, dp, spp, et) loopEnd(g, l) } } else if t.kind == "map" { emit(g, "call void @finch_map_clone_raw(ptr %dst, ptr %src, i64 " + entrySize(g, t) + ")") if owning(g, t.elem[0]) || owning(g, t.elem[1]) { // the slots were copied byte for byte: now give the copy its own keys and values used := load(g, "i64", op(g, "getelementptr inbounds " + llty(g, t) + ", ptr %src, i32 0, i32 2")) l := loopStart(g, used) from := entryAt(g, "%src", t, l[3]) to := entryAt(g, "%dst", t, l[3]) live := newLabel(g, "entry.live") next := newLabel(g, "entry.next") condBr(g, op(g, "icmp ne i64 " + load(g, "i64", op(g, "getelementptr inbounds " + entryTy(g, t) + ", ptr " + from + ", i32 0, i32 0")) + ", 0"), live, next) label(g, live) copyAt(g, entryField(g, to, t, 1), entryField(g, from, t, 1), t.elem[0]) copyAt(g, entryField(g, to, t, 2), entryField(g, from, t, 2), t.elem[1]) br(g, next) label(g, next) loopEnd(g, l) } } else { sd := g.structs[findStruct(g, t.module, t.name)] st := llty(g, t) store(g, st, load(g, st, "%src"), "%dst") for k in 0..sd.ftypes.len { if owning(g, sd.ftypes[k]) { dp := op(g, "getelementptr inbounds " + st + ", ptr %dst, i32 0, i32 " + str(k)) spp := op(g, "getelementptr inbounds " + st + ", ptr %src, i32 0, i32 " + str(k)) copyAt(g, dp, spp, sd.ftypes[k]) } } } emit(g, "ret void") endHelper(g, s, "define internal void " + name + "(ptr %dst, ptr %src)") return name } fn copyValue(ptr[Gen] g, str v, ast.Type t) -> str { if !owning(g, t) { return v } src := spill(g, Val(v: v, ty: t)) dst := alloca(g, llty(g, t)) copyAt(g, dst, src, t) return load(g, llty(g, t), dst) } // something that can be stored: a fresh value moves, anything else is copied fn own(ptr[Gen] g, Val v) -> str { if !owning(g, v.ty) || v.fresh { return v.v } return copyValue(g, v.v, v.ty) } // ---------- printing ---------- fn printf(ptr[Gen] g, str fmt, str args) { f := strGlobal(g, fmt) t := tmpName(g) extra := "" if args.len > 0 { extra = ", " + args } emit(g, t + " = call i32 (ptr, ...) @printf(ptr " + f + extra + ")") } fn cstr(ptr[Gen] g, str v) -> str { p := op(g, "extractvalue { ptr, i64, i64 } " + v + ", 0") isNull := op(g, "icmp eq ptr " + p + ", null") empty := strGlobal(g, "") return op(g, "select i1 " + isNull + ", ptr " + empty + ", ptr " + p) } fn emitPrint(ptr[Gen] g, str v, ast.Type t, bool quoted) { k := t.kind if isSigned(t) { printf(g, "%lld", "i64 " + intCast(g, v, t, T("int"))) } else if isUnsigned(t) { printf(g, "%llu", "i64 " + intCast(g, v, t, T("u64"))) } else if isFloat(t) { printf(g, "%g", "double " + floatCast(g, v, t, T("float"))) } else if k == "char" { z := op(g, "sext i8 " + v + " to i32") if quoted { printf(g, "'%c'", "i32 " + z) } else { printf(g, "%c", "i32 " + z) } } else if k == "bool" { yes := strGlobal(g, "true") no := strGlobal(g, "false") s := op(g, "select i1 " + v + ", ptr " + yes + ", ptr " + no) printf(g, "%s", "ptr " + s) } else if k == "str" { len := op(g, "extractvalue { ptr, i64, i64 } " + v + ", 1") n := op(g, "trunc i64 " + len + " to i32") p := cstr(g, v) if quoted { printf(g, "\"%.*s\"", "i32 " + n + ", ptr " + p) } else { printf(g, "%.*s", "i32 " + n + ", ptr " + p) } } else if k == "ptr" { isNull := op(g, "icmp eq ptr " + v + ", null") nul := newLabel(g, "ptr.null") at := newLabel(g, "ptr.addr") done := newLabel(g, "ptr.done") condBr(g, isNull, nul, at) label(g, nul) printf(g, "null", "") br(g, done) label(g, at) printf(g, "0x%llx", "i64 " + op(g, "ptrtoint ptr " + v + " to i64")) br(g, done) label(g, done) } else if k == "null" { printf(g, "null", "") } else { // arrays and structs: built as text first, the same text str(x) gives buf := alloca(g, "{ ptr, i64, i64 }") store(g, "{ ptr, i64, i64 }", "zeroinitializer", buf) emitFormat(g, buf, v, t, quoted) emit(g, "call void @finch_buf_print(ptr " + buf + ")") } } fn bufText(ptr[Gen] g, str buf, str text) { emit(g, "call void @finch_buf_add(ptr " + buf + ", ptr " + strGlobal(g, text) + ", i64 " + str(text.len) + ")") } // Append a value's text to the str at `buf`. Quoted: how it looks inside an array or struct. fn emitFormat(ptr[Gen] g, str buf, str v, ast.Type t, bool quoted) { k := t.kind q := "0" if quoted { q = "1" } if isSigned(t) { emit(g, "call void @finch_buf_int(ptr " + buf + ", i64 " + intCast(g, v, t, T("int")) + ")") } else if isUnsigned(t) { emit(g, "call void @finch_buf_uint(ptr " + buf + ", i64 " + intCast(g, v, t, T("u64")) + ")") } else if isFloat(t) { emit(g, "call void @finch_buf_float(ptr " + buf + ", double " + floatCast(g, v, t, T("float")) + ")") } else if k == "char" { emit(g, "call void @finch_buf_char(ptr " + buf + ", i8 " + v + ", i32 " + q + ")") } else if k == "bool" { emit(g, "call void @finch_buf_bool(ptr " + buf + ", i32 " + op(g, "zext i1 " + v + " to i32") + ")") } else if k == "str" { emit(g, "call void @finch_buf_str(ptr " + buf + ", ptr " + spill(g, Val(v: v, ty: t)) + ", i32 " + q + ")") } else if k == "ptr" || k == "null" { emit(g, "call void @finch_buf_ptr(ptr " + buf + ", ptr " + v + ")") } else { emit(g, "call void " + formatFn(g, t) + "(ptr " + buf + ", ptr " + spill(g, Val(v: v, ty: t)) + ")") } } // One function per array / struct type: (str *buf, T *value) fn formatFn(ptr[Gen] g, ast.Type t) -> str { name := "@finch.format." + typeKey(t) if has(g.done, name) { return name } g.done.push(name) s := beginHelper(g) if t.kind == "array" { et := t.elem[0] d := load(g, "ptr", "%a") len := load(g, "i64", op(g, "getelementptr inbounds { ptr, i64, i64 }, ptr %a, i32 0, i32 1")) bufText(g, "%buf", "[") l := loopStart(g, len) sep := newLabel(g, "sep") item := newLabel(g, "item") c := op(g, "icmp sgt i64 " + l[3] + ", 0") condBr(g, c, sep, item) label(g, sep) bufText(g, "%buf", ", ") br(g, item) label(g, item) ep := op(g, "getelementptr inbounds " + llty(g, et) + ", ptr " + d + ", i64 " + l[3]) emitFormat(g, "%buf", load(g, llty(g, et), ep), et, true) loopEnd(g, l) bufText(g, "%buf", "]") } else if t.kind == "map" { // {"a": 1, "b": 2} first := alloca(g, "i1") store(g, "i1", "true", first) bufText(g, "%buf", "{") used := load(g, "i64", op(g, "getelementptr inbounds " + llty(g, t) + ", ptr %a, i32 0, i32 2")) l := loopStart(g, used) e := entryAt(g, "%a", t, l[3]) live := newLabel(g, "entry.live") next := newLabel(g, "entry.next") condBr(g, op(g, "icmp ne i64 " + load(g, "i64", entryField(g, e, t, 0)) + ", 0"), live, next) label(g, live) sep := newLabel(g, "sep") item := newLabel(g, "item") condBr(g, load(g, "i1", first), item, sep) label(g, sep) bufText(g, "%buf", ", ") br(g, item) label(g, item) store(g, "i1", "false", first) emitFormat(g, "%buf", load(g, llty(g, t.elem[0]), entryField(g, e, t, 1)), t.elem[0], true) bufText(g, "%buf", ": ") emitFormat(g, "%buf", load(g, llty(g, t.elem[1]), entryField(g, e, t, 2)), t.elem[1], true) br(g, next) label(g, next) loopEnd(g, l) bufText(g, "%buf", "}") } else { sd := g.structs[findStruct(g, t.module, t.name)] st := llty(g, t) bufText(g, "%buf", sd.name + "(") for k in 0..sd.ftypes.len { sep := ", " if k == 0 { sep = "" } bufText(g, "%buf", sep + sd.fnames[k] + ": ") fp := op(g, "getelementptr inbounds " + st + ", ptr %a, i32 0, i32 " + str(k)) emitFormat(g, "%buf", load(g, llty(g, sd.ftypes[k]), fp), sd.ftypes[k], true) } bufText(g, "%buf", ")") } emit(g, "ret void") endHelper(g, s, "define internal void " + name + "(ptr %buf, ptr %a)") return name } // ---------- conversions ---------- // An integer of one size as an integer of another: grow by the sign of the // source (sext for signed, zext for unsigned), shrink by cutting. fn intCast(ptr[Gen] g, str v, ast.Type from, ast.Type to) -> str { fb := bitsOf(from) tb := bitsOf(to) if fb == tb { return v } if tb < fb { return op(g, "trunc " + llty(g, from) + " " + v + " to " + llty(g, to)) } if isSigned(from) { return op(g, "sext " + llty(g, from) + " " + v + " to " + llty(g, to)) } return op(g, "zext " + llty(g, from) + " " + v + " to " + llty(g, to)) } fn floatCast(ptr[Gen] g, str v, ast.Type from, ast.Type to) -> str { if from.kind == to.kind { return v } if bitsOf(to) > bitsOf(from) { return op(g, "fpext " + llty(g, from) + " " + v + " to " + llty(g, to)) } return op(g, "fptrunc " + llty(g, from) + " " + v + " to " + llty(g, to)) } fn intToFloat(ptr[Gen] g, str v, ast.Type from, ast.Type to) -> str { if isSigned(from) { return op(g, "sitofp " + llty(g, from) + " " + v + " to " + llty(g, to)) } return op(g, "uitofp " + llty(g, from) + " " + v + " to " + llty(g, to)) } fn coerce(ptr[Gen] g, Val v, ast.Type want, ast.Node at, str what) -> Val { if ast.same(v.ty, want) { return v } if v.ty.kind == "null" && want.kind == "ptr" { return Val(v: "null", ty: want) } if v.ty.kind == "ptr" && want.kind == "ptr" && (v.ty.elem.len == 0 || want.elem.len == 0) { return Val(v: v.v, ty: want) } if isInt(v.ty) && isInt(want) { if v.lit && fits(v.v, v.ty, want) { return Val(v: v.v, ty: want, lit: true) } if widens(v.ty, want) { return Val(v: intCast(g, v.v, v.ty, want), ty: want) } if v.lit { fail(g, at, "the number " + v.v + " doesn't fit in " + ast.show(want)) } } if isInt(v.ty) && isFloat(want) { if v.lit && want.kind == "float" { return Val(v: v.v + ".0", ty: want, lit: true) } return Val(v: intToFloat(g, v.v, v.ty, want), ty: want) } if v.ty.kind == "f32" && want.kind == "float" { return Val(v: floatCast(g, v.v, v.ty, want), ty: want) } if v.ty.kind == "float" && want.kind == "f32" && v.lit { return Val(v: floatCast(g, v.v, v.ty, want), ty: want) } msg := what + " must be " + ast.show(want) + ", but this is " + ast.show(v.ty) if isNumber(v.ty) && isNumber(want) { msg = msg + " (use " + ast.show(want) + "(...) to convert" if isFloat(v.ty) && isInt(want) { msg = msg + ", it cuts off the fraction" } msg = msg + ")" } if want.kind == "str" && (isNumber(v.ty) || v.ty.kind == "bool" || v.ty.kind == "char") { msg = msg + " (turn it into text with str(...))" } fail(g, at, msg) return v } // ---------- scopes ---------- fn lookup(ptr[Gen] g, str name) -> int { // returns scope * 100000 + index, or -1 for k in 0..g.scopes.len { s := g.scopes.len - 1 - k for i in 0..g.scopes[s].vars.len { if g.scopes[s].vars[i].name == name { if g.deferOn && g.scopes[s].vars[i].order >= g.deferLimit { continue } return s * 100000 + i } } } return -1 } fn varAt(ptr[Gen] g, int id) -> Var { return g.scopes[id / 100000].vars[id % 100000] } fn findFn(ptr[Gen] g, str module, str name) -> int { for i in 0..g.sigs.len { if g.sigs[i].module == module && g.sigs[i].name == name && g.sigs[i].recv.len == 0 { return i } } return -1 } fn findMethod(ptr[Gen] g, str module, str recv, str name) -> int { for i in 0..g.sigs.len { if g.sigs[i].module == module && g.sigs[i].recv == recv && g.sigs[i].name == name { return i } } return -1 } fn isBuiltin(str n) -> bool { names := ["print", "addr", "str", "int", "char", "float", "bool", "read_file", "write_file", "file_exists", "shell", "exit", "input", "new", "free", "ptr", "delete_file", "i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64", "f32", "f64", "error"] return names.contains(n) } fn addVar(ptr[Gen] g, ast.Node at, str name, ast.Type t, str init, bool readonly) -> str { if lookup(g, name) >= 0 && name != "err" { fail(g, at, "a variable named '" + name + "' already exists here") } if findFn(g, g.module, name) >= 0 { fail(g, at, "'" + name + "' is already the name of a function") } if isBuiltin(name) { fail(g, at, "'" + name + "' is a built-in name, pick another one") } slot := alloca(g, llty(g, t)) store(g, llty(g, t), init, slot) g.scopes[g.scopes.len - 1].vars.push(Var(name: name, ty: t, slot: slot, readonly: readonly, order: g.nvars)) g.nvars += 1 return slot } fn pushScope(ptr[Gen] g) { g.scopes.push(Scope()) } fn popScope(ptr[Gen] g) { g.scopes.pop() } // Run the defers of scope `idx`, newest first, as if written where the scope ends. fn runDefers(ptr[Gen] g, int idx) { sc := g.scopes[idx] k := sc.defers.len while k > 0 { k -= 1 savedOn := g.deferOn savedLimit := g.deferLimit g.deferOn = true g.deferLimit = sc.orders[k] g.inDefer += 1 pushScope(g) stmt(g, sc.defers[k]) popScope(g) g.inDefer -= 1 g.deferOn = savedOn g.deferLimit = savedLimit } } // The defers of every scope from the innermost down to `depth`, for return, break and continue. fn unwind(ptr[Gen] g, int depth) { i := g.scopes.len while i > depth { i -= 1 runDefers(g, i) } } // The end of a block: its defers run, unless the block has already left by return, break or continue. fn endScope(ptr[Gen] g) { if !g.term { runDefers(g, g.scopes.len - 1) } g.scopes.pop() } // ---------- mutation analysis: does a function change a parameter? ---------- fn rootVar(ast.Node e) -> str { if e.kind == "var" { return e.name } if e.kind == "member" || e.kind == "index" { return rootVar(e.kids[0]) } return "" } fn mutates(ptr[Gen] g, str name, ast.Node n) -> bool { changing := ["push", "pop", "insert", "remove", "clear", "resize", "sort", "reverse"] if n.kind == "assign" && rootVar(n.kids[0]) == name { return true } // a method that changes its receiver changes whatever it is called on; the // list holds names only, so a plain function by the same name is treated // the same way -- never wrong, sometimes one copy too many if n.kind == "method" && (changing.contains(n.name) || g.mm.contains(n.name)) && rootVar(n.kids[0]) == name { return true } if n.kind == "call" && n.name == "addr" && n.kids.len == 1 && rootVar(n.kids[0]) == name { return true } for k in n.kids { if mutates(g, name, k) { return true } } return false } // ---------- program ---------- fn declare(ptr[Gen] g) { for pi in 0..g.progs.len { g.module = g.progs[pi].module for si in 0..g.progs[pi].structs.len { s := g.progs[pi].structs[si] if findStruct(g, g.module, s.name) >= 0 { fail(g, s, "the struct '" + s.name + "' is defined twice") } g.structs.push(StructDef(module: g.module, name: s.name, prog: pi, index: si)) } } for i in 0..g.structs.len { resolveStruct(g, i) } for pi in 0..g.progs.len { g.module = g.progs[pi].module for fi in 0..g.progs[pi].fns.len { f := g.progs[pi].fns[fi] if f.recv.len > 0 { si := findStruct(g, g.module, f.recv) if si < 0 { fail(g, f, "there is no struct named '" + f.recv + "' here (a method goes in the same file as its struct)") } if findMethod(g, g.module, f.recv, f.name) >= 0 { fail(g, f, "the method " + f.recv + "." + f.name + " is defined twice") } if g.structs[si].fnames.contains(f.name) { fail(g, f, "the struct " + f.recv + " already has a field named '" + f.name + "'") } } else { if findFn(g, g.module, f.name) >= 0 { fail(g, f, "function '" + f.name + "' is defined twice") } if isBuiltin(f.name) { fail(g, f, "'" + f.name + "' is a built-in function, pick another name") } } sig := FnSig(module: g.module, name: f.name, recv: f.recv, fallible: f.fallible, prog: pi, index: fi) sig.ll = "@finch." + modName(g.module) + "." + f.name if f.recv.len > 0 { sig.ll = "@finch." + modName(g.module) + "." + f.recv + "." + f.name } sig.ret = resolve(g, f.ty, f) for k in 0..f.kids.len - 1 { if f.kids[k].name == "self" && f.recv.len > 0 { fail(g, f.kids[k], "'self' is already the " + f.recv + " the method is called on") } sig.params.push(resolve(g, f.kids[k].ty, f.kids[k])) sig.pnames.push(f.kids[k].name) } g.sigs.push(sig) } } // which methods change self? calling one of them on something changes it // too, so repeat until nothing new turns up changed := true while changed { changed = false for i in 0..g.sigs.len { if g.sigs[i].recv.len > 0 && !g.sigs[i].mutself { f := g.progs[g.sigs[i].prog].fns[g.sigs[i].index] if mutates(g, "self", f.kids[f.kids.len - 1]) { g.sigs[i].mutself = true g.mm.push(g.sigs[i].name) changed = true } } } } // a parameter nobody changes is borrowed rather than copied for i in 0..g.sigs.len { f := g.progs[g.sigs[i].prog].fns[g.sigs[i].index] body := f.kids[f.kids.len - 1] for k in 0..g.sigs[i].params.len { pt := g.sigs[i].params[k] g.sigs[i].borrow.push(owning(g, pt) && !mutates(g, g.sigs[i].pnames[k], body)) } } } fn resolveStruct(ptr[Gen] g, int i) { if g.structs[i].state == 2 { return } decl := g.progs[g.structs[i].prog].structs[g.structs[i].index] if g.structs[i].state == 1 { fail(g, decl, "the struct " + decl.name + " contains itself; use ptr[" + decl.name + "] or []" + decl.name + " for that field") } g.structs[i].state = 1 saved := g.module g.module = g.structs[i].module owns := false []str fields for f in decl.kids { t := resolve(g, f.ty, f) if t.kind == "struct" { resolveStruct(g, findStruct(g, t.module, t.name)) } g.structs[i].fnames.push(f.name) g.structs[i].ftypes.push(t) if owning(g, t) { owns = true } fields.push(llty(g, t)) } g.structs[i].owning = owns g.module = saved g.structs[i].state = 2 g.head.push("%S." + modName(g.structs[i].module) + "." + decl.name + " = type { " + fields.join(", ") + " }") } fn define(ptr[Gen] g, int si) { g.cur = g.sigs[si] g.module = g.cur.module f := g.progs[g.cur.prog].fns[g.cur.index] []str empty g.body = empty g.allocas = empty g.tmp = 0 g.term = false g.nvars = 0 g.deferOn = false g.inDefer = 0 []Scope none g.scopes = none pushScope(g) []str params if g.cur.recv.len > 0 { // self is the caller's struct itself, reached through its address params.push("ptr %self") g.scopes[0].vars.push(Var(name: "self", ty: ast.Type(kind: "struct", name: g.cur.recv, module: g.cur.module), slot: "%self", readonly: false)) } for k in 0..g.cur.params.len { pt := g.cur.params[k] params.push(llty(g, pt) + " %p" + str(k)) v := "%p" + str(k) if owning(g, pt) && !g.cur.borrow[k] { v = copyValue(g, v, pt) } addVar(g, f.kids[k], g.cur.pnames[k], pt, v, false) } block(g, f.kids[f.kids.len - 1], false) if !g.term { if g.cur.ret.kind != "void" { fail(g, f, "function '" + f.name + "' can reach its end without returning " + ast.show(g.cur.ret)) } runDefers(g, 0) if g.cur.fallible { emit(g, "ret " + retTy(g, g.cur) + " { i1 true, i8 0, { ptr, i64, i64 } zeroinitializer }") } else { emit(g, "ret void") } } g.out.push("define internal " + retTy(g, g.cur) + " " + g.cur.ll + "(" + params.join(", ") + ") {\nentry:\n" + g.allocas.join("\n") + "\n" + g.body.join("\n") + "\n}\n") } // ---------- statements ---------- fn block(ptr[Gen] g, ast.Node b, bool scope) { if scope { pushScope(g) } for s in b.kids { if g.term { fail(g, s, "this code can never run (it comes after return, break or continue)") } stmt(g, s) } if scope { endScope(g) } } fn stmt(ptr[Gen] g, ast.Node s) { k := s.kind if k == "block" { block(g, s, true) } else if k == "decl" { declStmt(g, s) } else if k == "assign" { assignStmt(g, s) } else if k == "expr" { if s.kids[0].kind == "or" { orElse(g, s.kids[0], true) } else { expr(g, s.kids[0]) } } else if k == "if" { ifStmt(g, s) } else if k == "while" { whileStmt(g, s) } else if k == "for" { forStmt(g, s) } else if k == "each" { eachStmt(g, s) } else if k == "return" { returnStmt(g, s) } else if k == "defer" { if g.inDefer > 0 { fail(g, s, "defer inside defer is not allowed") } sc := g.scopes.len - 1 g.scopes[sc].defers.push(s.kids[0]) g.scopes[sc].orders.push(g.nvars) } else if k == "break" || k == "continue" { if g.inDefer > 0 { fail(g, s, k + " can't be used inside defer") } if g.loops.len == 0 { fail(g, s, k + " can only be used inside a loop") } l := g.loops[g.loops.len - 1] unwind(g, l.depth) if k == "break" { br(g, l.brk) } else { br(g, l.cont) } } else { fail(g, s, "unknown statement " + k) } } fn declStmt(ptr[Gen] g, ast.Node s) { if !s.flag { v := expr(g, s.kids[0]) if v.ty.kind == "void" { fail(g, s.kids[0], "this gives back nothing, so it can't be stored") } if v.ty.kind == "null" { fail(g, s.kids[0], "null needs a type, like: ptr[int] " + s.name + " = null") } addVar(g, s, s.name, v.ty, own(g, v), false) return } t := resolve(g, s.ty, s) if s.kids.len == 0 { if t.kind == "struct" && hasDefaults(g, t) { []ast.Node noArgs []str noNames v := construct(g, findStruct(g, t.module, t.name), noArgs, noNames, s) addVar(g, s, s.name, t, v.v, false) return } addVar(g, s, s.name, t, zeroOf(t), false) return } v := coerce(g, exprWant(g, s.kids[0], t), t, s.kids[0], "'" + s.name + "'") addVar(g, s, s.name, t, own(g, v), false) } fn hasDefaults(ptr[Gen] g, ast.Type t) -> bool { sd := g.structs[findStruct(g, t.module, t.name)] for f in g.progs[sd.prog].structs[sd.index].kids { if f.kids.len > 0 { return true } } return false } fn assignStmt(ptr[Gen] g, ast.Node s) { target := s.kids[0] root := rootVar(target) if root.len > 0 { id := lookup(g, root) if id < 0 && target.kind == "var" { fail(g, s, "there is no variable named '" + root + "' (create it with " + root + " := ...)") } if id >= 0 && varAt(g, id).readonly { fail(g, s, "the loop variable '" + root + "' can't be changed") } } what := "the value" if target.kind == "var" { what = "'" + root + "'" } if s.name == "=" { v := expr(g, s.kids[1]) dst := place(g, target, true) v = coerce(g, v, dst.ty, s.kids[1], what) store(g, llty(g, dst.ty), own(g, v), dst.addr) return } rhs := expr(g, s.kids[1]) dst := place(g, target, true) cur := Val(v: load(g, llty(g, dst.ty), dst.addr), ty: dst.ty) opName := s.name.sub(0, 1) v := coerce(g, arith(g, opName, cur, rhs, s), dst.ty, s, what) store(g, llty(g, dst.ty), own(g, v), dst.addr) } fn condition(ptr[Gen] g, ast.Node e) -> str { return coerce(g, expr(g, e), T("bool"), e, "a condition").v } fn ifStmt(ptr[Gen] g, ast.Node s) { c := condition(g, s.kids[0]) then := newLabel(g, "then") els := newLabel(g, "else") end := newLabel(g, "endif") if s.kids.len > 2 { condBr(g, c, then, els) } else { condBr(g, c, then, end) } label(g, then) block(g, s.kids[1], true) reached := !g.term br(g, end) if s.kids.len > 2 { label(g, els) stmt(g, s.kids[2]) reached = reached || !g.term br(g, end) } else { reached = true } if reached { label(g, end) } } fn whileStmt(ptr[Gen] g, ast.Node s) { cond := newLabel(g, "while.cond") body := newLabel(g, "while.body") end := newLabel(g, "while.end") br(g, cond) label(g, cond) infinite := s.kids[0].kind == "bool" && s.kids[0].text == "true" c := condition(g, s.kids[0]) if infinite { br(g, body) } else { condBr(g, c, body, end) } label(g, body) g.loops.push(Loop(cont: cond, brk: end, depth: g.scopes.len)) breaks := countBreaks(s.kids[1]) block(g, s.kids[1], true) g.loops.pop() br(g, cond) if !infinite || breaks { label(g, end) } } // does a loop body contain a break for this loop? fn countBreaks(ast.Node n) -> bool { if n.kind == "break" { return true } if n.kind == "while" || n.kind == "for" || n.kind == "each" { return false } for k in n.kids { if countBreaks(k) { return true } } return false } fn forStmt(ptr[Gen] g, ast.Node s) { from := coerce(g, expr(g, s.kids[0]), T("int"), s.kids[0], "the start of a range").v to := coerce(g, expr(g, s.kids[1]), T("int"), s.kids[1], "the end of a range").v pushScope(g) i := addVar(g, s, s.name, T("int"), from, true) cond := newLabel(g, "for.cond") body := newLabel(g, "for.body") step := newLabel(g, "for.step") end := newLabel(g, "for.end") br(g, cond) label(g, cond) iv := load(g, "i64", i) c := op(g, "icmp slt i64 " + iv + ", " + to) condBr(g, c, body, end) label(g, body) g.loops.push(Loop(cont: step, brk: end, depth: g.scopes.len)) block(g, s.kids[2], true) g.loops.pop() br(g, step) label(g, step) iv2 := load(g, "i64", i) store(g, "i64", op(g, "add i64 " + iv2 + ", 1"), i) br(g, cond) label(g, end) popScope(g) } // for key in map / for key, value in map: in the order the keys were added fn eachMap(ptr[Gen] g, ast.Node s, str mp, ast.Type t, bool live) { kt := t.elem[0] vt := t.elem[1] changes := live && mutates(g, rootVar(s.kids[0]), s.kids[1]) iv := alloca(g, "i64") store(g, "i64", "0", iv) adds := "" if live { adds = load(g, "i64", op(g, "getelementptr inbounds " + llty(g, t) + ", ptr " + mp + ", i32 0, i32 6")) } cond := newLabel(g, "map.cond") check := newLabel(g, "map.check") body := newLabel(g, "map.body") step := newLabel(g, "map.step") end := newLabel(g, "map.end") br(g, cond) label(g, cond) // adding keys inside the loop could reorder the entries: that stops the program (removing is fine) if live && changes { changed := newLabel(g, "map.changed") same := newLabel(g, "map.same") now := load(g, "i64", op(g, "getelementptr inbounds " + llty(g, t) + ", ptr " + mp + ", i32 0, i32 6")) condBr(g, op(g, "icmp ne i64 " + now + ", " + adds), changed, same) label(g, changed) emit(g, "call void @finch_map_changed(ptr " + strGlobal(g, g.files[s.file]) + ", i64 " + str(s.line) + ")") emit(g, "unreachable") g.term = true label(g, same) } i := load(g, "i64", iv) used := load(g, "i64", op(g, "getelementptr inbounds " + llty(g, t) + ", ptr " + mp + ", i32 0, i32 2")) condBr(g, op(g, "icmp slt i64 " + i + ", " + used), check, end) label(g, check) entry := entryAt(g, mp, t, i) hash := load(g, "i64", entryField(g, entry, t, 0)) condBr(g, op(g, "icmp eq i64 " + hash + ", 0"), step, body) // a removed entry label(g, body) g.loops.push(Loop(cont: step, brk: end, depth: g.scopes.len)) pushScope(g) kv := load(g, llty(g, kt), entryField(g, entry, t, 1)) if changes { kv = copyValue(g, kv, kt) } addVar(g, s, s.name, kt, kv, true) if s.text.len > 0 { vv := load(g, llty(g, vt), entryField(g, entry, t, 2)) if changes { vv = copyValue(g, vv, vt) } addVar(g, s, s.text, vt, vv, true) } block(g, s.kids[1], true) popScope(g) g.loops.pop() br(g, step) label(g, step) i2 := load(g, "i64", iv) store(g, "i64", op(g, "add i64 " + i2 + ", 1"), iv) br(g, cond) label(g, end) } fn eachStmt(ptr[Gen] g, ast.Node s) { pushScope(g) r := ref(g, s.kids[0], false) listAddr := "" lt := T("void") if r.isPlace { listAddr = r.pl.addr lt = r.pl.ty } else { lt = r.val.ty listAddr = spill(g, r.val) } if lt.kind == "map" { eachMap(g, s, listAddr, lt, r.isPlace) popScope(g) return } if lt.kind != "array" && lt.kind != "str" { fail(g, s.kids[0], "for ... in needs an array, a str or a map, but this is " + ast.show(lt) + " (for a range of numbers write 0..n)") } et := T("char") if lt.kind == "array" { et = lt.elem[0] } copyElems := r.isPlace && owning(g, et) && mutates(g, rootVar(s.kids[0]), s.kids[1]) iv := alloca(g, "i64") store(g, "i64", "0", iv) cond := newLabel(g, "each.cond") body := newLabel(g, "each.body") step := newLabel(g, "each.step") end := newLabel(g, "each.end") br(g, cond) label(g, cond) len := load(g, "i64", op(g, "getelementptr inbounds { ptr, i64, i64 }, ptr " + listAddr + ", i32 0, i32 1")) i := load(g, "i64", iv) c := op(g, "icmp slt i64 " + i + ", " + len) condBr(g, c, body, end) label(g, body) data := load(g, "ptr", listAddr) ep := op(g, "getelementptr inbounds " + llty(g, et) + ", ptr " + data + ", i64 " + i) elem := load(g, llty(g, et), ep) if copyElems { elem = copyValue(g, elem, et) } g.loops.push(Loop(cont: step, brk: end, depth: g.scopes.len)) pushScope(g) elemVar := s.name if s.text.len > 0 { // for i, x in list addVar(g, s, s.name, T("int"), i, true) elemVar = s.text } addVar(g, s, elemVar, et, elem, true) block(g, s.kids[1], true) popScope(g) g.loops.pop() br(g, step) label(g, step) i2 := load(g, "i64", iv) store(g, "i64", op(g, "add i64 " + i2 + ", 1"), iv) br(g, cond) label(g, end) popScope(g) } fn returnStmt(ptr[Gen] g, ast.Node s) { if g.inDefer > 0 { fail(g, s, "return can't be used inside defer") } // return error("...") if s.kids.len > 0 && s.kids[0].kind == "call" && s.kids[0].name == "error" { c := s.kids[0] if !g.cur.fallible { want := "!" if g.cur.ret.kind != "void" { want = ast.show(g.cur.ret) + "!" } fail(g, c, "'" + g.cur.name + "' can't fail, so it can't return an error (make it fallible: -> " + want + ")") } checkArgs(g, c, 0, 1, "error") msg := coerce(g, expr(g, c.kids[0]), T("str"), c.kids[0], "the error message") out := own(g, msg) unwind(g, 0) emit(g, "ret " + retTy(g, g.cur) + " " + op(g, "insertvalue " + retTy(g, g.cur) + " zeroinitializer, { ptr, i64, i64 } " + out + ", 2")) g.term = true return } if g.cur.ret.kind == "void" { if s.kids.len > 0 { fail(g, s.kids[0], "'" + g.cur.name + "' doesn't return a value (it has no '-> type')") } unwind(g, 0) if g.cur.fallible { emit(g, "ret " + retTy(g, g.cur) + " { i1 true, i8 0, { ptr, i64, i64 } zeroinitializer }") } else { emit(g, "ret void") } g.term = true return } if s.kids.len == 0 { fail(g, s, "'" + g.cur.name + "' must return " + ast.show(g.cur.ret)) } v := coerce(g, exprWant(g, s.kids[0], g.cur.ret), g.cur.ret, s.kids[0], "the returned value") out := v.v // returning a local variable moves it out; anything else borrowed is copied isLocal := false if s.kids[0].kind == "var" { id := lookup(g, s.kids[0].name) isLocal = id >= 0 && id / 100000 > 0 } if !isLocal { out = own(g, v) } unwind(g, 0) if g.cur.fallible { a := op(g, "insertvalue " + retTy(g, g.cur) + " zeroinitializer, i1 true, 0") b := op(g, "insertvalue " + retTy(g, g.cur) + " " + a + ", " + llty(g, g.cur.ret) + " " + out + ", 1") emit(g, "ret " + retTy(g, g.cur) + " " + b) } else { emit(g, "ret " + llty(g, g.cur.ret) + " " + out) } g.term = true } // ---------- expressions ---------- // ---------- maps ---------- // // map[K]V is { entries, len, used, cap, index, icap, adds } (see runtime/finch_rt.c). The runtime finds and // places entries; the compiler stores, copies and drops the keys and values in them. An entry is // { i64 hash, K key, V value }; hash 0 marks a removed entry. fn entryTy(ptr[Gen] g, ast.Type t) -> str { return "{ i64, " + llty(g, t.elem[0]) + ", " + llty(g, t.elem[1]) + " }" } fn entrySize(ptr[Gen] g, ast.Type t) -> str { return "ptrtoint (ptr getelementptr (" + entryTy(g, t) + ", ptr null, i32 1) to i64)" } // the address of entry number `i` of the map at `mp` fn entryAt(ptr[Gen] g, str mp, ast.Type t, str i) -> str { entries := load(g, "ptr", op(g, "getelementptr inbounds " + llty(g, t) + ", ptr " + mp + ", i32 0, i32 0")) return op(g, "getelementptr inbounds " + entryTy(g, t) + ", ptr " + entries + ", i64 " + i) } // field 0 hash, 1 key, 2 value fn entryField(ptr[Gen] g, str entry, ast.Type t, int f) -> str { return op(g, "getelementptr inbounds " + entryTy(g, t) + ", ptr " + entry + ", i32 0, i32 " + str(f)) } fn mapArgs(ptr[Gen] g, str mp, str keyAddr, ast.Type t) -> str { kstr := "0" if t.elem[0].kind == "str" { kstr = "1" } return "ptr " + mp + ", ptr " + keyAddr + ", i64 " + entrySize(g, t) + ", i64 " + sizeOf(g, t.elem[0]) + ", i32 " + kstr } fn mapFind(ptr[Gen] g, str mp, str keyAddr, ast.Type t) -> str { return op(g, "call i64 @finch_map_find(" + mapArgs(g, mp, keyAddr, t) + ")") } fn defaultVal(ptr[Gen] g, ast.Type t, ast.Node at) -> str { if t.kind == "struct" && hasDefaults(g, t) { []ast.Node noArgs []str noNames return construct(g, findStruct(g, t.module, t.name), noArgs, noNames, at).v } return zeroOf(t) } // The entry for `key`, added if it is missing: the key is copied in, and the value is its // type's default when `fill` (m[k] += 1 starts from 0). fn mapSlot(ptr[Gen] g, str mp, ast.Type t, Val key, bool fill, ast.Node at) -> str { keyAddr := spill(g, key) added := alloca(g, "i32") store(g, "i32", "0", added) i := op(g, "call i64 @finch_map_slot(" + mapArgs(g, mp, keyAddr, t) + ", ptr " + added + ")") entry := entryAt(g, mp, t, i) fresh := newLabel(g, "map.new") have := newLabel(g, "map.have") condBr(g, op(g, "icmp ne i32 " + load(g, "i32", added) + ", 0"), fresh, have) label(g, fresh) copyAt(g, entryField(g, entry, t, 1), keyAddr, t.elem[0]) if fill { store(g, llty(g, t.elem[1]), own(g, Val(v: defaultVal(g, t.elem[1], at), ty: t.elem[1], fresh: true)), entryField(g, entry, t, 2)) } br(g, have) label(g, have) return entry } // m[key]: reading a missing key stops the program; writing (m[k] = v, m[k] += 1, m[k].push(x)) adds it fn mapIndex(ptr[Gen] g, ast.Node e, Ref obj, ast.Type t, bool write) -> Ref { kt := t.elem[0] vt := t.elem[1] key := coerce(g, exprWant(g, e.kids[1], kt), kt, e.kids[1], "the key") if write { if !obj.isPlace { fail(g, e, "this is a temporary map, it can't be changed") } entry := mapSlot(g, obj.pl.addr, t, key, true, e) return Ref(isPlace: true, pl: Place(addr: entryField(g, entry, t, 2), ty: vt)) } mp := "" if obj.isPlace { mp = obj.pl.addr } else { mp = spill(g, obj.val) } keyAddr := spill(g, key) i := mapFind(g, mp, keyAddr, t) missing := newLabel(g, "map.missing") found := newLabel(g, "map.found") condBr(g, op(g, "icmp slt i64 " + i + ", 0"), missing, found) label(g, missing) text := alloca(g, "{ ptr, i64, i64 }") store(g, "{ ptr, i64, i64 }", "zeroinitializer", text) emitFormat(g, text, key.v, kt, true) emit(g, "call void @finch_map_missing(ptr " + strGlobal(g, g.files[e.file]) + ", i64 " + str(e.line) + ", ptr " + text + ")") emit(g, "unreachable") g.term = true label(g, found) va := entryField(g, entryAt(g, mp, t, i), t, 2) if obj.isPlace { return Ref(isPlace: true, pl: Place(addr: va, ty: vt)) } v := load(g, llty(g, vt), va) out := Val(v: v, ty: vt) if owning(g, vt) && obj.val.fresh { out = Val(v: copyValue(g, v, vt), ty: vt, fresh: true) } return Ref(isPlace: false, val: out) } // ["a": 1, "b": 2], or [:] where the type is known fn mapLit(ptr[Gen] g, ast.Node e, ast.Type want) -> Val { t := want first := false k0 := Val() v0 := Val() if want.kind != "map" { if e.kids.len == 0 { fail(g, e, "an empty [:] needs a type, like: map[str]int m = [:]") } // number values written in the code are all floats if any of them has a dot allNum := true anyFloat := false for i in 0..e.kids.len / 2 { y := e.kids[2 * i + 1] if y.kind == "unary" && y.name == "-" { y = y.kids[0] } if y.kind == "float" { anyFloat = true } else if y.kind != "int" { allNum = false } } k0 = expr(g, e.kids[0]) v0 = expr(g, e.kids[1]) if k0.ty.kind == "void" || k0.ty.kind == "null" { fail(g, e.kids[0], "a map key needs a value with a type") } if v0.ty.kind == "void" || v0.ty.kind == "null" { fail(g, e.kids[1], "a map value needs a value with a type") } vt := v0.ty if allNum && anyFloat { vt = T("float") } t = ast.mapOf(k0.ty, vt) first = true } mp := alloca(g, llty(g, t)) store(g, llty(g, t), "zeroinitializer", mp) for i in 0..e.kids.len / 2 { k := k0 v := v0 if !(i == 0 && first) { k = exprWant(g, e.kids[2 * i], t.elem[0]) v = exprWant(g, e.kids[2 * i + 1], t.elem[1]) } k = coerce(g, k, t.elem[0], e.kids[2 * i], "every key of this map") v = coerce(g, v, t.elem[1], e.kids[2 * i + 1], "every value of this map") vv := own(g, v) entry := mapSlot(g, mp, t, k, false, e) store(g, llty(g, t.elem[1]), vv, entryField(g, entry, t, 2)) } return Val(v: load(g, llty(g, t), mp), ty: t, fresh: true) } fn mapMethod(ptr[Gen] g, ast.Node m, str mp, ast.Type t) -> Val { n := m.name kt := t.elem[0] vt := t.elem[1] if n == "has" || n == "get" || n == "remove" { if n == "get" { checkArgs(g, m, 1, 2, n) } else { checkArgs(g, m, 1, 1, n) } key := coerce(g, exprWant(g, m.kids[1], kt), kt, m.kids[1], "the key") i := mapFind(g, mp, spill(g, key), t) was := op(g, "icmp sge i64 " + i + ", 0") if n == "has" { return Val(v: was, ty: T("bool")) } if n == "remove" { hit := newLabel(g, "remove.hit") end := newLabel(g, "remove.end") condBr(g, was, hit, end) label(g, hit) emit(g, "call void @finch_map_remove_at(ptr " + mp + ", i64 " + i + ", i64 " + entrySize(g, t) + ")") br(g, end) label(g, end) return Val(v: was, ty: T("bool")) } // get(key, default): the default is only computed when the key is missing found := newLabel(g, "get.found") missing := newLabel(g, "get.missing") fend := newLabel(g, "get.fend") mend := newLabel(g, "get.mend") end := newLabel(g, "get.end") condBr(g, was, found, missing) label(g, found) v := copyValue(g, load(g, llty(g, vt), entryField(g, entryAt(g, mp, t, i), t, 2)), vt) br(g, fend) label(g, fend) br(g, end) label(g, missing) d := own(g, coerce(g, exprWant(g, m.kids[2], vt), vt, m.kids[2], "the default")) br(g, mend) label(g, mend) br(g, end) label(g, end) return Val(v: op(g, "phi " + llty(g, vt) + " [ " + v + ", %" + fend + " ], [ " + d + ", %" + mend + " ]"), ty: vt, fresh: owning(g, vt)) } if n == "clear" { checkArgs(g, m, 1, 0, n) emit(g, "call void @finch_map_clear(ptr " + mp + ")") return Val(ty: T("void")) } if n == "keys" || n == "values" { checkArgs(g, m, 1, 0, n) field := 1 et := kt if n == "values" { field = 2 et = vt } at := ast.arrayOf(et) out := alloca(g, "{ ptr, i64, i64 }") store(g, "{ ptr, i64, i64 }", "zeroinitializer", out) len := load(g, "i64", op(g, "getelementptr inbounds " + llty(g, t) + ", ptr " + mp + ", i32 0, i32 1")) emit(g, "call void @finch_arr_make(ptr " + out + ", i64 " + len + ", i64 " + sizeOf(g, et) + ")") data := load(g, "ptr", out) j := alloca(g, "i64") store(g, "i64", "0", j) used := load(g, "i64", op(g, "getelementptr inbounds " + llty(g, t) + ", ptr " + mp + ", i32 0, i32 2")) l := loopStart(g, used) entry := entryAt(g, mp, t, l[3]) live := newLabel(g, "entry.live") next := newLabel(g, "entry.next") condBr(g, op(g, "icmp ne i64 " + load(g, "i64", entryField(g, entry, t, 0)) + ", 0"), live, next) label(g, live) jv := load(g, "i64", j) copyAt(g, op(g, "getelementptr inbounds " + llty(g, et) + ", ptr " + data + ", i64 " + jv), entryField(g, entry, t, field), et) store(g, "i64", op(g, "add i64 " + jv + ", 1"), j) br(g, next) label(g, next) loopEnd(g, l) return Val(v: load(g, "{ ptr, i64, i64 }", out), ty: at, fresh: true) } fail(g, m, "a map has no method '" + n + "' (it has: has, get, remove, clear, keys, values)") return Val() } // ---------- errors as values ---------- struct Fallible { Val v str failed // an i1 str msg // a str } // Emit a call that may fail; `failed` and `msg` tell how it went. fn fallibleCall(ptr[Gen] g, ast.Node site) -> Fallible { if site.kind != "call" && site.kind != "method" { fail(g, site, "only a function call can fail; 'or' and 'try' go after one, like int(text) or 0") } savedT := g.ftarget savedF := g.ffailed savedM := g.fmsg g.ftarget = nodeKey(site) g.ffailed = "" g.fmsg = "" v := expr(g, site) out := Fallible(v: v, failed: g.ffailed, msg: g.fmsg) g.ftarget = savedT g.ffailed = savedF g.fmsg = savedM if out.failed.len == 0 { fail(g, site, "this call can't fail, so it needs no 'or' or 'try'") } return out } // try f(x): on failure, the current function fails with the same error fn tryExpr(ptr[Gen] g, ast.Node e) -> Val { if g.inDefer > 0 { fail(g, e, "try can't be used inside defer") } if !g.cur.fallible { want := "!" if g.cur.ret.kind != "void" { want = ast.show(g.cur.ret) + "!" } fail(g, e, "try passes the error on, so '" + g.cur.name + "' must be able to fail too (-> " + want + "); or handle it here: ... or / ... or { ... }") } f := fallibleCall(g, e.kids[0]) bad := newLabel(g, "try.failed") ok := newLabel(g, "try.ok") condBr(g, f.failed, bad, ok) label(g, bad) unwind(g, 0) emit(g, "ret " + retTy(g, g.cur) + " " + op(g, "insertvalue " + retTy(g, g.cur) + " zeroinitializer, { ptr, i64, i64 } " + f.msg + ", 2")) g.term = true label(g, ok) return f.v } // f(x) or fallback / f(x) or { ... err ... } fn orElse(ptr[Gen] g, ast.Node e, bool discard) -> Val { f := fallibleCall(g, e.kids[0]) v := f.v bad := newLabel(g, "or.failed") ok := newLabel(g, "or.ok") condBr(g, f.failed, bad, ok) label(g, bad) if e.flag { pushScope(g) addVar(g, e, "err", T("str"), f.msg, true) block(g, e.kids[1], true) left := g.term popScope(g) if !left && v.ty.kind != "void" && !discard { fail(g, e.kids[1], "this block must leave with return, break, continue or exit(...): after a failure there is no value to go on with (or give one: ... or )") } if discard && v.ty.kind != "void" { join := newLabel(g, "or.end") br(g, join) label(g, ok) br(g, join) label(g, join) return Val(ty: T("void")) } br(g, ok) label(g, ok) return v } if v.ty.kind == "void" { fail(g, e.kids[1], "this call gives back no value, so there is nothing to replace; handle the failure with or { ... }") } pushScope(g) addVar(g, e, "err", T("str"), f.msg, true) fb := coerce(g, exprWant(g, e.kids[1], v.ty), v.ty, e.kids[1], "the value after 'or'") fv := own(g, fb) popScope(g) badEnd := newLabel(g, "or.bend") join := newLabel(g, "or.end") br(g, badEnd) label(g, badEnd) br(g, join) label(g, ok) br(g, join) label(g, join) return Val(v: op(g, "phi " + llty(g, v.ty) + " [ " + fv + ", %" + badEnd + " ], [ " + v.v + ", %" + ok + " ]"), ty: v.ty, fresh: owning(g, v.ty)) } fn exprWant(ptr[Gen] g, ast.Node e, ast.Type want) -> Val { if e.kind == "array" { return arrayLit(g, e, want) } if e.kind == "map" { return mapLit(g, e, want) } return expr(g, e) } fn expr(ptr[Gen] g, ast.Node e) -> Val { k := e.kind if k == "int" { if e.text.starts_with("0x") || e.text.starts_with("0X") { n := 0 for c in e.text.sub(2, e.text.len) { d := int(c) - int('0') if c >= 'a' && c <= 'f' { d = int(c) - int('a') + 10 } if c >= 'A' && c <= 'F' { d = int(c) - int('A') + 10 } n = n * 16 + d } return Val(v: str(n), ty: T("int"), lit: true) } return Val(v: str(int(e.text)), ty: T("int"), lit: true) } if k == "float" { return Val(v: e.text, ty: T("float"), lit: true) } if k == "bool" { return Val(v: e.text, ty: T("bool")) } if k == "char" { n := int(e.text[0]) return Val(v: str(n), ty: T("char")) } if k == "str" { return strConst(g, e.text) } if k == "null" { return Val(v: "null", ty: T("null")) } if k == "var" || k == "member" || k == "index" { r := ref(g, e, false) if !r.isPlace { return r.val } return Val(v: load(g, llty(g, r.pl.ty), r.pl.addr), ty: r.pl.ty) } if k == "try" { return tryExpr(g, e) } if k == "or" { return orElse(g, e, false) } if k == "unary" { return unary(g, e) } if k == "binary" { return binary(g, e) } if k == "call" { return call(g, e) } if k == "method" { return method(g, e) } if k == "array" { return arrayLit(g, e, T("void")) } if k == "map" { return mapLit(g, e, T("void")) } fail(g, e, "unknown expression " + k) return Val() } fn unary(ptr[Gen] g, ast.Node e) -> Val { v := expr(g, e.kids[0]) if e.name == "!" { c := coerce(g, v, T("bool"), e.kids[0], "the value after '!'") return Val(v: op(g, "xor i1 " + c.v + ", true"), ty: T("bool")) } if e.name == "~" { if !isInt(v.ty) { fail(g, e, "'~' flips the bits of a whole number, not of " + ast.show(v.ty)) } return Val(v: op(g, "xor " + llty(g, v.ty) + " " + v.v + ", -1"), ty: v.ty) } if isUnsigned(v.ty) { fail(g, e, ast.show(v.ty) + " can't be negative (it's unsigned)") } if isSigned(v.ty) { if v.lit { if v.v.starts_with("-") { return Val(v: v.v.sub(1, v.v.len), ty: v.ty, lit: true) } return Val(v: "-" + v.v, ty: v.ty, lit: true) } return Val(v: op(g, "sub " + llty(g, v.ty) + " 0, " + v.v), ty: v.ty) } if isFloat(v.ty) { return Val(v: op(g, "fneg " + llty(g, v.ty) + " " + v.v), ty: v.ty) } fail(g, e, "can't put '-' in front of " + ast.show(v.ty)) return v } fn badOperands(ptr[Gen] g, str o, Val l, Val r, ast.Node at) { msg := "can't use '" + o + "' on " + ast.show(l.ty) if !ast.same(l.ty, r.ty) { msg = msg + " and " + ast.show(r.ty) } if o == "+" && (l.ty.kind == "str" || r.ty.kind == "str") { msg = msg + " (turn the other side into text first: str(...))" } fail(g, at, msg) } fn unify(ptr[Gen] g, ptr[Val] l, ptr[Val] r) -> bool { if ast.same(l.ty, r.ty) { return true } if isInt(l.ty) && isInt(r.ty) { // a number written in the code adapts to the other side; otherwise // the smaller type grows into the bigger one if l.lit && !r.lit && fits(l.v, l.ty, r.ty) { l.value = Val(v: l.v, ty: r.ty, lit: true) return true } if r.lit && !l.lit && fits(r.v, r.ty, l.ty) { r.value = Val(v: r.v, ty: l.ty, lit: true) return true } if widens(l.ty, r.ty) { l.value = Val(v: intCast(g, l.v, l.ty, r.ty), ty: r.ty) return true } if widens(r.ty, l.ty) { r.value = Val(v: intCast(g, r.v, r.ty, l.ty), ty: l.ty) return true } return false } if isInt(l.ty) && isFloat(r.ty) { l.value = coerce(g, l.value, r.ty, Node0(), "") return true } if isFloat(l.ty) && isInt(r.ty) { r.value = coerce(g, r.value, l.ty, Node0(), "") return true } if isFloat(l.ty) && isFloat(r.ty) { if l.ty.kind == "f32" { l.value = Val(v: floatCast(g, l.v, l.ty, T("float")), ty: T("float")) } if r.ty.kind == "f32" { r.value = Val(v: floatCast(g, r.v, r.ty, T("float")), ty: T("float")) } return true } return false } fn Node0() -> ast.Node { return ast.Node() } fn arith(ptr[Gen] g, str o, Val l, Val r, ast.Node at) -> Val { if o == "+" && l.ty.kind == "str" && r.ty.kind == "str" { out := alloca(g, "{ ptr, i64, i64 }") emit(g, "call void @finch_str_concat(ptr " + out + ", ptr " + spill(g, l) + ", ptr " + spill(g, r) + ")") return Val(v: load(g, "{ ptr, i64, i64 }", out), ty: T("str"), fresh: true) } if !unify(g, addr(l), addr(r)) { badOperands(g, o, l, r, at) } if isInt(l.ty) { names := ["+", "-", "*", "/", "%", "&", "|", "^", "<<", ">>"] insts := ["add", "sub", "mul", "sdiv", "srem", "and", "or", "xor", "shl", "ashr"] if isUnsigned(l.ty) { insts = ["add", "sub", "mul", "udiv", "urem", "and", "or", "xor", "shl", "lshr"] } k := names.find(o) if k < 0 { badOperands(g, o, l, r, at) } lt := llty(g, l.ty) if o == "/" || o == "%" { if r.lit && r.v == "0" { fail(g, at, "division by zero") } if !r.lit { panicIf(g, op(g, "icmp eq " + lt + " " + r.v + ", 0"), "division by zero", at) } } return Val(v: op(g, insts[k] + " " + lt + " " + l.v + ", " + r.v), ty: l.ty) } if isFloat(l.ty) { names := ["+", "-", "*", "/", "%"] insts := ["fadd", "fsub", "fmul", "fdiv", "frem"] k := names.find(o) if k < 0 { badOperands(g, o, l, r, at) } return Val(v: op(g, insts[k] + " " + llty(g, l.ty) + " " + l.v + ", " + r.v), ty: l.ty) } badOperands(g, o, l, r, at) return l } fn compare(ptr[Gen] g, str o, Val l, Val r, ast.Node at) -> Val { eq := o == "==" || o == "!=" if l.ty.kind == "ptr" || r.ty.kind == "ptr" || l.ty.kind == "null" || r.ty.kind == "null" { if !eq { badOperands(g, o, l, r, at) } pred := "eq" if o == "!=" { pred = "ne" } return Val(v: op(g, "icmp " + pred + " ptr " + l.v + ", " + r.v), ty: T("bool")) } if l.ty.kind == "str" && r.ty.kind == "str" { if eq { c := op(g, "call i32 @finch_str_eq(ptr " + spill(g, l) + ", ptr " + spill(g, r) + ")") pred := "ne" if o == "!=" { pred = "eq" } return Val(v: op(g, "icmp " + pred + " i32 " + c + ", 0"), ty: T("bool")) } c := op(g, "call i64 @finch_str_cmp(ptr " + spill(g, l) + ", ptr " + spill(g, r) + ")") return Val(v: op(g, "icmp " + intPred(o, true) + " i64 " + c + ", 0"), ty: T("bool")) } if !unify(g, addr(l), addr(r)) { badOperands(g, o, l, r, at) } k := l.ty.kind if isFloat(l.ty) { ops := ["==", "!=", "<", "<=", ">", ">="] preds := ["oeq", "une", "olt", "ole", "ogt", "oge"] return Val(v: op(g, "fcmp " + preds[ops.find(o)] + " " + llty(g, l.ty) + " " + l.v + ", " + r.v), ty: T("bool")) } if k == "bool" && !eq { badOperands(g, o, l, r, at) } if !isInt(l.ty) && k != "char" && k != "bool" { badOperands(g, o, l, r, at) } return Val(v: op(g, "icmp " + intPred(o, isSigned(l.ty)) + " " + llty(g, l.ty) + " " + l.v + ", " + r.v), ty: T("bool")) } fn intPred(str o, bool signed) -> str { ops := ["==", "!=", "<", "<=", ">", ">="] preds := ["eq", "ne", "ult", "ule", "ugt", "uge"] if signed { preds = ["eq", "ne", "slt", "sle", "sgt", "sge"] } return preds[ops.find(o)] } fn binary(ptr[Gen] g, ast.Node e) -> Val { o := e.name if o == "&&" || o == "||" { l := condition(g, e.kids[0]) from := newLabel(g, "logic.from") rhs := newLabel(g, "logic.rhs") end := newLabel(g, "logic.end") br(g, from) label(g, from) if o == "&&" { condBr(g, l, rhs, end) } else { condBr(g, l, end, rhs) } label(g, rhs) r := condition(g, e.kids[1]) rhsEnd := newLabel(g, "logic.rend") br(g, rhsEnd) label(g, rhsEnd) br(g, end) label(g, end) short := "true" if o == "&&" { short = "false" } return Val(v: op(g, "phi i1 [ " + short + ", %" + from + " ], [ " + r + ", %" + rhsEnd + " ]"), ty: T("bool")) } l := expr(g, e.kids[0]) r := expr(g, e.kids[1]) if o == "==" || o == "!=" || o == "<" || o == "<=" || o == ">" || o == ">=" { return compare(g, o, l, r, e) } return arith(g, o, l, r, e) } fn arrayLit(ptr[Gen] g, ast.Node a, ast.Type want) -> Val { et := T("void") if want.kind == "array" { et = want.elem[0] } else if a.kids.len == 0 { fail(g, a, "an empty [] needs a type, like: []int nums = []") } []str vals for k in a.kids { v := Val() if et.kind == "void" { v = expr(g, k) et = v.ty } else { v = exprWant(g, k, et) } v = coerce(g, v, et, k, "every element of this array") vals.push(own(g, v)) } at := ast.arrayOf(et) n := str(vals.len) data := "null" if vals.len > 0 { size := op(g, "mul i64 " + n + ", " + sizeOf(g, et)) data = op(g, "call ptr @finch_alloc(i64 " + size + ")") } for i in 0..vals.len { p := op(g, "getelementptr inbounds " + llty(g, et) + ", ptr " + data + ", i64 " + str(i)) store(g, llty(g, et), vals[i], p) } v1 := op(g, "insertvalue { ptr, i64, i64 } undef, ptr " + data + ", 0") v2 := op(g, "insertvalue { ptr, i64, i64 } " + v1 + ", i64 " + n + ", 1") v3 := op(g, "insertvalue { ptr, i64, i64 } " + v2 + ", i64 " + n + ", 2") return Val(v: v3, ty: at, fresh: true) } // ---------- places: variables, fields, elements ---------- fn place(ptr[Gen] g, ast.Node e, bool write) -> Place { if e.kind == "var" && lookup(g, e.name) < 0 { fail(g, e, "there is no variable named '" + e.name + "'") } r := ref(g, e, write) if !r.isPlace { fail(g, e, "this is a temporary value, it can't be changed or pointed to") } return r.pl } fn ref(ptr[Gen] g, ast.Node e, bool write) -> Ref { if e.kind == "var" { id := lookup(g, e.name) if id < 0 { if imports(g, e.name) { fail(g, e, "'" + e.name + "' is a module; use something from it, like " + e.name + ".name(...)") } fail(g, e, "there is no variable named '" + e.name + "'") } v := varAt(g, id) return Ref(isPlace: true, pl: Place(addr: v.slot, ty: v.ty)) } if e.kind == "member" { return member(g, e, write) } if e.kind == "index" { return index(g, e, write) } return Ref(isPlace: false, val: expr(g, e)) } fn member(ptr[Gen] g, ast.Node m, bool write) -> Ref { obj := ref(g, m.kids[0], write) t := obj.val.ty if obj.isPlace { t = obj.pl.ty } if t.kind == "ptr" { pv := obj.val.v if obj.isPlace { pv = load(g, "ptr", obj.pl.addr) } if t.elem.len == 0 { fail(g, m, "this ptr has no type, so Finch doesn't know what's inside") } panicIf(g, op(g, "icmp eq ptr " + pv + ", null"), "used ." + m.name + " on a null pointer", m) target := t.elem[0] structHasValue := false if target.kind == "struct" { structHasValue = g.structs[findStruct(g, target.module, target.name)].fnames.contains("value") } if m.name == "value" && !structHasValue { return Ref(isPlace: true, pl: Place(addr: pv, ty: target)) } if target.kind != "struct" { fail(g, m, "a " + ast.show(t) + " only has '.value' (the thing it points to)") } obj = Ref(isPlace: true, pl: Place(addr: pv, ty: target)) t = target } if t.kind == "array" || t.kind == "str" || t.kind == "map" { if m.name != "len" { fail(g, m, "an array, map or str has .len, not '." + m.name + "'") } if write { fail(g, m, ".len can't be changed directly (use push or pop)") } if obj.isPlace { lp := op(g, "getelementptr inbounds " + llty(g, t) + ", ptr " + obj.pl.addr + ", i32 0, i32 1") return Ref(isPlace: false, val: Val(v: load(g, "i64", lp), ty: T("int"))) } return Ref(isPlace: false, val: Val(v: op(g, "extractvalue " + llty(g, t) + " " + obj.val.v + ", 1"), ty: T("int"))) } if t.kind != "struct" { fail(g, m, ast.show(t) + " has no fields (no '." + m.name + "')") } sd := g.structs[findStruct(g, t.module, t.name)] k := sd.fnames.find(m.name) if k < 0 { fail(g, m, "the struct " + ast.show(t) + " has no field '" + m.name + "' (it has: " + sd.fnames.join(", ") + ")") } ft := sd.ftypes[k] if obj.isPlace { fp := op(g, "getelementptr inbounds " + llty(g, t) + ", ptr " + obj.pl.addr + ", i32 0, i32 " + str(k)) return Ref(isPlace: true, pl: Place(addr: fp, ty: ft)) } fv := op(g, "extractvalue " + llty(g, t) + " " + obj.val.v + ", " + str(k)) out := Val(v: fv, ty: ft) if owning(g, ft) && obj.val.fresh { out = Val(v: copyValue(g, fv, ft), ty: ft, fresh: true) } return Ref(isPlace: false, val: out) } fn index(ptr[Gen] g, ast.Node e, bool write) -> Ref { obj := ref(g, e.kids[0], write) t := obj.val.ty if obj.isPlace { t = obj.pl.ty } if t.kind == "map" { return mapIndex(g, e, obj, t, write) } i := coerce(g, expr(g, e.kids[1]), T("int"), e.kids[1], "an index").v if t.kind == "ptr" { if t.elem.len == 0 { fail(g, e, "this ptr has no type, so Finch doesn't know what's inside") } pv := obj.val.v if obj.isPlace { pv = load(g, "ptr", obj.pl.addr) } et := t.elem[0] return Ref(isPlace: true, pl: Place(addr: op(g, "getelementptr inbounds " + llty(g, et) + ", ptr " + pv + ", i64 " + i), ty: et)) } if t.kind != "array" && t.kind != "str" { fail(g, e, "[ ] works on arrays, str and pointers, not on " + ast.show(t)) } et := T("char") if t.kind == "array" { et = t.elem[0] } if t.kind == "str" && write { if !obj.isPlace { fail(g, e, "this is a temporary str, it can't be changed") } emit(g, "call void @finch_str_own(ptr " + obj.pl.addr + ")") } v := obj.val.v if obj.isPlace { v = load(g, "{ ptr, i64, i64 }", obj.pl.addr) } len := op(g, "extractvalue { ptr, i64, i64 } " + v + ", 1") boundsCheck(g, i, len, e) data := op(g, "extractvalue { ptr, i64, i64 } " + v + ", 0") ep := op(g, "getelementptr inbounds " + llty(g, et) + ", ptr " + data + ", i64 " + i) if obj.isPlace { return Ref(isPlace: true, pl: Place(addr: ep, ty: et)) } ev := load(g, llty(g, et), ep) out := Val(v: ev, ty: et) if owning(g, et) && obj.val.fresh { out = Val(v: copyValue(g, ev, et), ty: et, fresh: true) } return Ref(isPlace: false, val: out) } // ---------- calls ---------- fn checkArgs(ptr[Gen] g, ast.Node c, int first, int want, str name) { have := c.kids.len - first for n in c.names { if n.len > 0 { fail(g, c, "named arguments (" + n + ": ...) are only for creating structs") } } if have != want { fail(g, c, "'" + name + "' takes " + str(want) + " argument(s), but got " + str(have)) } } fn strArg(ptr[Gen] g, ast.Node e, str what) -> Val { return coerce(g, expr(g, e), T("str"), e, what) } fn strOut(ptr[Gen] g) -> str { return alloca(g, "{ ptr, i64, i64 }") } // The type a conversion call names: int(x), u8(x), f32(x), bool(x). i64 and // f64 are the same types as int and float. fn numType(str n) -> str { if n == "i64" { return "int" } if n == "f64" { return "float" } names := ["int", "float", "char", "bool", "i8", "i16", "i32", "u8", "u16", "u32", "u64", "f32"] if names.contains(n) { return n } return "" } // Explicit conversions between the number-like types. Cutting a bigger // integer down wraps, as on hardware; text is parsed, with a runtime error. fn convert(ptr[Gen] g, ast.Node c, ast.Type to) -> Val { checkArgs(g, c, 0, 1, c.name) v := expr(g, c.kids[0]) from := v.ty if ast.same(from, to) { return v } if from.kind == "str" && isNumber(to) && g.ftarget == nodeKey(c) { // int(text) or 0: a bad number is reported to the `or`, not a runtime error err := alloca(g, "{ ptr, i64, i64 }") store(g, "{ ptr, i64, i64 }", "zeroinitializer", err) if isInt(to) { out := alloca(g, "i64") store(g, "i64", "0", out) ok := op(g, "call i32 @finch_str_try_int(ptr " + spill(g, v) + ", ptr " + out + ", ptr " + err + ")") g.ffailed = op(g, "icmp eq i32 " + ok + ", 0") g.fmsg = load(g, "{ ptr, i64, i64 }", err) return Val(v: intCast(g, load(g, "i64", out), T("int"), to), ty: to) } out := alloca(g, "double") store(g, "double", "0.0", out) ok := op(g, "call i32 @finch_str_try_float(ptr " + spill(g, v) + ", ptr " + out + ", ptr " + err + ")") g.ffailed = op(g, "icmp eq i32 " + ok + ", 0") g.fmsg = load(g, "{ ptr, i64, i64 }", err) return Val(v: floatCast(g, load(g, "double", out), T("float"), to), ty: to) } if from.kind == "str" && isNumber(to) { file := strGlobal(g, g.files[c.file]) if isInt(to) { r := op(g, "call i64 @finch_str_to_int(ptr " + spill(g, v) + ", ptr " + file + ", i64 " + str(c.line) + ")") return Val(v: intCast(g, r, T("int"), to), ty: to) } r := op(g, "call double @finch_str_to_float(ptr " + spill(g, v) + ", ptr " + file + ", i64 " + str(c.line) + ")") return Val(v: floatCast(g, r, T("float"), to), ty: to) } if isInt(to) || to.kind == "char" { if isInt(from) || from.kind == "char" { return Val(v: intCast(g, v.v, from, to), ty: to) } if from.kind == "bool" { return Val(v: op(g, "zext i1 " + v.v + " to " + llty(g, to)), ty: to) } if isFloat(from) && isInt(to) { conv := "fptoui" if isSigned(to) { conv = "fptosi" } return Val(v: op(g, conv + " " + llty(g, from) + " " + v.v + " to " + llty(g, to)), ty: to) } } if isFloat(to) { if isInt(from) { return Val(v: intToFloat(g, v.v, from, to), ty: to) } if isFloat(from) { return Val(v: floatCast(g, v.v, from, to), ty: to) } } if to.kind == "bool" && isInt(from) { return Val(v: op(g, "icmp ne " + llty(g, from) + " " + v.v + ", 0"), ty: to) } fail(g, c.kids[0], "can't turn " + ast.show(from) + " into " + c.name) return v } fn call(ptr[Gen] g, ast.Node c) -> Val { n := c.name if n == "error" { fail(g, c, "error(...) makes an error to return from a function that can fail: return error(\"...\")") } if n == "print" { for i in 0..c.kids.len { v := expr(g, c.kids[i]) if v.ty.kind == "void" { fail(g, c.kids[i], "this gives back nothing, so there is nothing to print") } if i > 0 { printf(g, " ", "") } emitPrint(g, v.v, v.ty, false) } printf(g, "\n", "") return Val(ty: T("void")) } if n == "addr" { checkArgs(g, c, 0, 1, n) a := c.kids[0] if a.kind != "var" && a.kind != "member" && a.kind != "index" { fail(g, a, "addr(...) needs a variable, a field or an element, like addr(x)") } pl := place(g, a, true) return Val(v: pl.addr, ty: ast.ptrTo(pl.ty)) } if n == "str" { checkArgs(g, c, 0, 1, n) v := expr(g, c.kids[0]) out := strOut(g) k := v.ty.kind if k == "str" { return Val(v: copyValue(g, v.v, v.ty), ty: v.ty, fresh: true) } else if isSigned(v.ty) { emit(g, "call void @finch_str_from_int(ptr " + out + ", i64 " + intCast(g, v.v, v.ty, T("int")) + ")") } else if isUnsigned(v.ty) { emit(g, "call void @finch_str_from_uint(ptr " + out + ", i64 " + intCast(g, v.v, v.ty, T("u64")) + ")") } else if isFloat(v.ty) { emit(g, "call void @finch_str_from_float(ptr " + out + ", double " + floatCast(g, v.v, v.ty, T("float")) + ")") } else if k == "char" { emit(g, "call void @finch_str_from_char(ptr " + out + ", i8 signext " + v.v + ")") } else if k == "bool" { z := op(g, "zext i1 " + v.v + " to i32") emit(g, "call void @finch_str_from_bool(ptr " + out + ", i32 " + z + ")") } else if k == "bool" || k == "char" || k == "array" || k == "map" || k == "struct" { store(g, "{ ptr, i64, i64 }", "zeroinitializer", out) emitFormat(g, out, v.v, v.ty, false) } else { fail(g, c.kids[0], "can't turn " + ast.show(v.ty) + " into str") } return Val(v: load(g, "{ ptr, i64, i64 }", out), ty: T("str"), fresh: true) } to := numType(n) if to.len > 0 { return convert(g, c, T(to)) } if n == "read_file" || n == "file_exists" { checkArgs(g, c, 0, 1, n) path := spill(g, strArg(g, c.kids[0], "the file name")) if n == "file_exists" { r := op(g, "call i32 @finch_file_exists(ptr " + path + ")") return Val(v: op(g, "icmp ne i32 " + r + ", 0"), ty: T("bool")) } out := strOut(g) if g.ftarget == nodeKey(c) { err := alloca(g, "{ ptr, i64, i64 }") store(g, "{ ptr, i64, i64 }", "zeroinitializer", out) store(g, "{ ptr, i64, i64 }", "zeroinitializer", err) ok := op(g, "call i32 @finch_try_read_file(ptr " + out + ", ptr " + path + ", ptr " + err + ")") g.ffailed = op(g, "icmp eq i32 " + ok + ", 0") g.fmsg = load(g, "{ ptr, i64, i64 }", err) return Val(v: load(g, "{ ptr, i64, i64 }", out), ty: T("str"), fresh: true) } file := strGlobal(g, g.files[c.file]) emit(g, "call void @finch_read_file(ptr " + out + ", ptr " + path + ", ptr " + file + ", i64 " + str(c.line) + ")") return Val(v: load(g, "{ ptr, i64, i64 }", out), ty: T("str"), fresh: true) } if n == "write_file" { checkArgs(g, c, 0, 2, n) path := spill(g, strArg(g, c.kids[0], "the file name")) text := spill(g, strArg(g, c.kids[1], "the text")) if g.ftarget == nodeKey(c) { err := alloca(g, "{ ptr, i64, i64 }") store(g, "{ ptr, i64, i64 }", "zeroinitializer", err) ok := op(g, "call i32 @finch_try_write_file(ptr " + path + ", ptr " + text + ", ptr " + err + ")") g.ffailed = op(g, "icmp eq i32 " + ok + ", 0") g.fmsg = load(g, "{ ptr, i64, i64 }", err) return Val(ty: T("void")) } r := op(g, "call i32 @finch_write_file(ptr " + path + ", ptr " + text + ")") return Val(v: op(g, "icmp ne i32 " + r + ", 0"), ty: T("bool")) } if n == "delete_file" { checkArgs(g, c, 0, 1, n) p := strArg(g, c.kids[0], "the file name") heap := strOut(g) emit(g, "call void @finch_str_copy(ptr " + heap + ", ptr " + spill(g, p) + ")") if g.ftarget == nodeKey(c) { err := alloca(g, "{ ptr, i64, i64 }") store(g, "{ ptr, i64, i64 }", "zeroinitializer", err) ok := op(g, "call i32 @finch_try_delete_file(ptr " + heap + ", ptr " + err + ")") g.ffailed = op(g, "icmp eq i32 " + ok + ", 0") g.fmsg = load(g, "{ ptr, i64, i64 }", err) return Val(ty: T("void")) } r := op(g, "call i32 @finch_delete_file(ptr " + heap + ")") return Val(v: op(g, "icmp ne i32 " + r + ", 0"), ty: T("bool")) } if n == "shell" { checkArgs(g, c, 0, 1, n) cmd := strArg(g, c.kids[0], "the command") heap := strOut(g) emit(g, "call void @finch_str_copy(ptr " + heap + ", ptr " + spill(g, cmd) + ")") return Val(v: op(g, "call i64 @finch_shell(ptr " + heap + ")"), ty: T("int")) } if n == "exit" { checkArgs(g, c, 0, 1, n) code := coerce(g, expr(g, c.kids[0]), T("int"), c.kids[0], "the exit code") t := op(g, "trunc i64 " + code.v + " to i32") f := op(g, "call i32 @fflush(ptr null)") emit(g, "call void @exit(i32 " + t + ")") return Val(ty: T("void")) } if n == "input" { prompt := strConst(g, "") if c.kids.len > 0 { checkArgs(g, c, 0, 1, n) prompt = strArg(g, c.kids[0], "the question") } out := strOut(g) emit(g, "call void @finch_input(ptr " + out + ", ptr " + spill(g, prompt) + ")") return Val(v: load(g, "{ ptr, i64, i64 }", out), ty: T("str"), fresh: true) } if n == "new" { checkArgs(g, c, 0, 1, n) v := expr(g, c.kids[0]) mem := op(g, "call ptr @finch_alloc(i64 " + sizeOf(g, v.ty) + ")") store(g, llty(g, v.ty), own(g, v), mem) return Val(v: mem, ty: ast.ptrTo(v.ty)) } if n == "free" { checkArgs(g, c, 0, 1, n) v := expr(g, c.kids[0]) if v.ty.kind != "ptr" { fail(g, c.kids[0], "free(...) is only for pointers from new(...)") } emit(g, "call void @free(ptr " + v.v + ")") return Val(ty: T("void")) } fi := findFn(g, g.module, n) if fi >= 0 { return callFn(g, fi, c, 0, n, "", "") } si := findStruct(g, g.module, n) if si >= 0 { return construct(g, si, c.kids, c.names, c) } if lookup(g, n) >= 0 { fail(g, c, "'" + n + "' is a variable, not a function") } fail(g, c, "there is no function named '" + n + "'") return Val() } fn callFn(ptr[Gen] g, int fi, ast.Node c, int first, str shown, str selfAddr, str selfRoot) -> Val { sig := g.sigs[fi] if sig.module.len == 0 && sig.name == "main" { fail(g, c, "main can't be called, it is where the program starts") } checkArgs(g, c, first, sig.params.len, shown) []str args if sig.recv.len > 0 { args.push("ptr " + selfAddr) } for k in 0..sig.params.len { a := c.kids[first + k] v := coerce(g, exprWant(g, a, sig.params[k]), sig.params[k], a, "argument '" + sig.pnames[k] + "'") // the argument lives inside the receiver (c.merge(c)): the method gets its own copy if owning(g, sig.params[k]) && !v.fresh && selfRoot.len > 0 && rootVar(a) == selfRoot { v = Val(v: copyValue(g, v.v, v.ty), ty: v.ty, fresh: true) } args.push(llty(g, sig.params[k]) + " " + v.v) } if sig.fallible && g.ftarget != nodeKey(c) { fail(g, c, "'" + shown + "' can fail, so say what happens then: " + shown + "(...) or , " + shown + "(...) or { ... }, or try " + shown + "(...)") } text := "call " + retTy(g, sig) + " " + sig.ll + "(" + args.join(", ") + ")" if sig.fallible { r := op(g, text) ok := op(g, "extractvalue " + retTy(g, sig) + " " + r + ", 0") g.ffailed = op(g, "xor i1 " + ok + ", true") g.fmsg = op(g, "extractvalue " + retTy(g, sig) + " " + r + ", 2") if sig.ret.kind == "void" { return Val(ty: sig.ret) } return Val(v: op(g, "extractvalue " + retTy(g, sig) + " " + r + ", 1"), ty: sig.ret, fresh: owning(g, sig.ret)) } if sig.ret.kind == "void" { emit(g, text) return Val(ty: sig.ret) } return Val(v: op(g, text), ty: sig.ret, fresh: owning(g, sig.ret)) } fn construct(ptr[Gen] g, int si, []ast.Node args, []str names, ast.Node at) -> Val { sd := g.structs[si] decl := g.progs[sd.prog].structs[sd.index] t := ast.Type(kind: "struct", name: sd.name, module: sd.module) st := llty(g, t) named := names.len > 0 && names[0].len > 0 []int given for k in 0..sd.fnames.len { given.push(-1) } for i in 0..args.len { if (names[i].len > 0) != named { fail(g, args[i], "give either all fields by name (x: 1, y: 2) or none (1, 2), not a mix") } if !named { if args.len != sd.fnames.len { fail(g, at, sd.name + " has " + str(sd.fnames.len) + " fields, but got " + str(args.len) + " values (or name them: " + sd.name + "(field: value))") } given[i] = i continue } k := sd.fnames.find(names[i]) if k < 0 { fail(g, args[i], sd.name + " has no field '" + names[i] + "'") } if given[k] >= 0 { fail(g, args[i], "the field '" + names[i] + "' is given twice") } given[k] = i } v := "zeroinitializer" for k in 0..sd.fnames.len { ft := sd.ftypes[k] fv := Val() if given[k] >= 0 { fv = coerce(g, exprWant(g, args[given[k]], ft), ft, args[given[k]], "the field '" + sd.fnames[k] + "'") } else if decl.kids[k].kids.len > 0 { // a default value is evaluated where the struct is defined, without the caller's variables savedScopes := g.scopes savedModule := g.module []Scope none g.scopes = none pushScope(g) g.module = sd.module fv = coerce(g, exprWant(g, decl.kids[k].kids[0], ft), ft, decl.kids[k].kids[0], "the default of '" + sd.fnames[k] + "'") g.module = savedModule g.scopes = savedScopes } else { continue } v = op(g, "insertvalue " + st + " " + v + ", " + llty(g, ft) + " " + own(g, fv) + ", " + str(k)) } return Val(v: v, ty: t, fresh: owning(g, t)) } // ---------- methods ---------- fn method(ptr[Gen] g, ast.Node m) -> Val { objNode := m.kids[0] if objNode.kind == "var" && lookup(g, objNode.name) < 0 { mn := objNode.name if !imports(g, mn) { fail(g, objNode, "there is no variable or module named '" + mn + "' (to use a module, add import " + mn + " at the top)") } fi := findFn(g, mn, m.name) if fi >= 0 { return callFn(g, fi, m, 1, mn + "." + m.name, "", "") } si := findStruct(g, mn, m.name) if si >= 0 { []ast.Node args for k in 1..m.kids.len { args.push(m.kids[k]) } return construct(g, si, args, m.names, m) } fail(g, m, "the module '" + mn + "' has no function or struct named '" + m.name + "'") } changing := ["push", "pop", "insert", "remove", "clear", "resize", "sort", "reverse"] r := ref(g, objNode, changing.contains(m.name)) t := r.val.ty if r.isPlace { t = r.pl.ty } if t.kind == "array" { a := "" if r.isPlace { a = r.pl.addr } else { if changing.contains(m.name) { fail(g, m, m.name + "() would change a temporary array; store it in a variable first") } a = spill(g, r.val) } return arrayMethod(g, m, a, t) } if t.kind == "str" { sv := r.val if r.isPlace { sv = Val(v: load(g, "{ ptr, i64, i64 }", r.pl.addr), ty: t) } return strMethod(g, m, spill(g, sv)) } if t.kind == "map" { mp := "" if r.isPlace { mp = r.pl.addr } else { if changing.contains(m.name) { fail(g, m, m.name + "() would change a temporary map; store it in a variable first") } mp = spill(g, r.val) } return mapMethod(g, m, mp, t) } st := t selfAddr := "" if t.kind == "ptr" && t.elem.len > 0 && t.elem[0].kind == "struct" { pv := r.val.v if r.isPlace { pv = load(g, "ptr", r.pl.addr) } panicIf(g, op(g, "icmp eq ptr " + pv + ", null"), "called ." + m.name + "() on a null pointer", m) st = t.elem[0] selfAddr = pv } else if t.kind == "struct" { if r.isPlace { selfAddr = r.pl.addr } else { selfAddr = spill(g, r.val) } } if st.kind == "struct" { fi := findMethod(g, st.module, st.name, m.name) if fi < 0 { fail(g, m, "the struct " + ast.show(st) + " has no method '" + m.name + "'") } // a method that changes self can't be called on a temporary or a read-only value if g.sigs[fi].mutself && t.kind == "struct" { if !r.isPlace { fail(g, m, m.name + "() would change a temporary " + ast.show(st) + "; store it in a variable first") } root := rootVar(objNode) if root.len > 0 { id := lookup(g, root) if id >= 0 && varAt(g, id).readonly { fail(g, m, "the loop variable '" + root + "' can't be changed") } } } return callFn(g, fi, m, 1, ast.show(st) + "." + m.name, selfAddr, rootVar(objNode)) } fail(g, m, ast.show(t) + " has no methods") return Val() } fn need(ptr[Gen] g, ast.Node m, int n) { checkArgs(g, m, 1, n, m.name) } fn arrayMethod(ptr[Gen] g, ast.Node m, str a, ast.Type at) -> Val { et := at.elem[0] ET := llty(g, et) n := m.name lenAddr := op(g, "getelementptr inbounds { ptr, i64, i64 }, ptr " + a + ", i32 0, i32 1") if n == "push" { need(g, m, 1) v := own(g, coerce(g, exprWant(g, m.kids[1], et), et, m.kids[1], "the element")) l := load(g, "i64", lenAddr) l1 := op(g, "add i64 " + l + ", 1") emit(g, "call void @finch_arr_reserve(ptr " + a + ", i64 " + l1 + ", i64 " + sizeOf(g, et) + ")") data := load(g, "ptr", a) store(g, ET, v, op(g, "getelementptr inbounds " + ET + ", ptr " + data + ", i64 " + l)) store(g, "i64", l1, lenAddr) return Val(ty: T("void")) } if n == "pop" { need(g, m, 0) l := load(g, "i64", lenAddr) panicIf(g, op(g, "icmp eq i64 " + l + ", 0"), "pop() on an empty array", m) last := op(g, "sub i64 " + l + ", 1") data := load(g, "ptr", a) v := load(g, ET, op(g, "getelementptr inbounds " + ET + ", ptr " + data + ", i64 " + last)) store(g, "i64", last, lenAddr) return Val(v: v, ty: et, fresh: owning(g, et)) } if n == "clear" { need(g, m, 0) store(g, "i64", "0", lenAddr) return Val(ty: T("void")) } if n == "find" || n == "contains" { need(g, m, 1) x := coerce(g, exprWant(g, m.kids[1], et), et, m.kids[1], "the value to look for") res := alloca(g, "i64") store(g, "i64", "-1", res) l := load(g, "i64", lenAddr) data := load(g, "ptr", a) iv := alloca(g, "i64") store(g, "i64", "0", iv) cond := newLabel(g, "find.cond") body := newLabel(g, "find.body") hit := newLabel(g, "find.hit") nxt := newLabel(g, "find.next") end := newLabel(g, "find.end") br(g, cond) label(g, cond) i := load(g, "i64", iv) c := op(g, "icmp slt i64 " + i + ", " + l) condBr(g, c, body, end) label(g, body) ev := Val(v: load(g, ET, op(g, "getelementptr inbounds " + ET + ", ptr " + data + ", i64 " + i)), ty: et) eq := compare(g, "==", ev, x, m).v condBr(g, eq, hit, nxt) label(g, hit) store(g, "i64", i, res) br(g, end) label(g, nxt) store(g, "i64", op(g, "add i64 " + i + ", 1"), iv) br(g, cond) label(g, end) r := load(g, "i64", res) if n == "find" { return Val(v: r, ty: T("int")) } return Val(v: op(g, "icmp sge i64 " + r + ", 0"), ty: T("bool")) } if n == "join" { need(g, m, 1) if et.kind != "str" { fail(g, m, "join() works on an array of str") } sep := spill(g, strArg(g, m.kids[1], "the separator")) out := strOut(g) emit(g, "call void @finch_str_join(ptr " + out + ", ptr " + a + ", ptr " + sep + ")") return Val(v: load(g, "{ ptr, i64, i64 }", out), ty: T("str"), fresh: true) } if n == "insert" { need(g, m, 2) i := coerce(g, expr(g, m.kids[1]), T("int"), m.kids[1], "the position").v v := own(g, coerce(g, exprWant(g, m.kids[2], et), et, m.kids[2], "the element")) file := strGlobal(g, g.files[m.file]) emit(g, "call void @finch_arr_insert_gap(ptr " + a + ", i64 " + i + ", i64 " + sizeOf(g, et) + ", ptr " + file + ", i64 " + str(m.line) + ")") data := load(g, "ptr", a) store(g, ET, v, op(g, "getelementptr inbounds " + ET + ", ptr " + data + ", i64 " + i)) return Val(ty: T("void")) } if n == "remove" { need(g, m, 1) i := coerce(g, expr(g, m.kids[1]), T("int"), m.kids[1], "the position").v boundsCheck(g, i, load(g, "i64", lenAddr), m) data := load(g, "ptr", a) v := load(g, ET, op(g, "getelementptr inbounds " + ET + ", ptr " + data + ", i64 " + i)) emit(g, "call void @finch_arr_remove_gap(ptr " + a + ", i64 " + i + ", i64 " + sizeOf(g, et) + ")") return Val(v: v, ty: et, fresh: owning(g, et)) } if n == "resize" { need(g, m, 1) k := coerce(g, expr(g, m.kids[1]), T("int"), m.kids[1], "the new length").v emit(g, "call void @finch_arr_resize(ptr " + a + ", i64 " + k + ", i64 " + sizeOf(g, et) + ")") return Val(ty: T("void")) } if n == "reverse" { need(g, m, 0) l := load(g, "i64", lenAddr) data := load(g, "ptr", a) half := op(g, "sdiv i64 " + l + ", 2") lp := loopStart(g, half) x := op(g, "getelementptr inbounds " + ET + ", ptr " + data + ", i64 " + lp[3]) j := op(g, "sub i64 " + op(g, "sub i64 " + l + ", 1") + ", " + lp[3]) y := op(g, "getelementptr inbounds " + ET + ", ptr " + data + ", i64 " + j) xv := load(g, ET, x) yv := load(g, ET, y) store(g, ET, yv, x) store(g, ET, xv, y) loopEnd(g, lp) return Val(ty: T("void")) } if n == "slice" { need(g, m, 2) st := coerce(g, expr(g, m.kids[1]), T("int"), m.kids[1], "the start").v en := coerce(g, expr(g, m.kids[2]), T("int"), m.kids[2], "the end").v l := load(g, "i64", lenAddr) bad1 := op(g, "icmp slt i64 " + st + ", 0") bad2 := op(g, "icmp sgt i64 " + en + ", " + l) bad3 := op(g, "icmp sgt i64 " + st + ", " + en) panicIf(g, op(g, "or i1 " + op(g, "or i1 " + bad1 + ", " + bad2) + ", " + bad3), "slice(start, end) is out of range", m) cnt := op(g, "sub i64 " + en + ", " + st) out := alloca(g, "{ ptr, i64, i64 }") emit(g, "call void @finch_arr_make(ptr " + out + ", i64 " + cnt + ", i64 " + sizeOf(g, et) + ")") src := load(g, "ptr", a) dst := load(g, "ptr", out) lp := loopStart(g, cnt) dp := op(g, "getelementptr inbounds " + ET + ", ptr " + dst + ", i64 " + lp[3]) sp := op(g, "getelementptr inbounds " + ET + ", ptr " + src + ", i64 " + op(g, "add i64 " + st + ", " + lp[3])) copyAt(g, dp, sp, et) loopEnd(g, lp) return Val(v: load(g, "{ ptr, i64, i64 }", out), ty: at, fresh: true) } if n == "sort" { need(g, m, 0) k := et.kind if !isNumber(et) && k != "char" && k != "str" { fail(g, m, "sort() works on arrays of numbers, chars or str") } cmp := cmpFn(g, et) l := load(g, "i64", lenAddr) data := load(g, "ptr", a) emit(g, "call void @qsort(ptr " + data + ", i64 " + l + ", i64 " + sizeOf(g, et) + ", ptr " + cmp + ")") return Val(ty: T("void")) } fail(g, m, "an array has no method '" + n + "' (it has: push, pop, insert, remove, clear, resize, find, contains, join, slice, reverse, sort)") return Val() } // -1, 0 or 1, for qsort fn cmpFn(ptr[Gen] g, ast.Type t) -> str { name := "@finch.cmp." + typeKey(t) if has(g.done, name) { return name } g.done.push(name) s := beginHelper(g) r := "" if t.kind == "str" { c := op(g, "call i64 @finch_str_cmp(ptr %x, ptr %y)") r = op(g, "trunc i64 " + c + " to i32") } else { ty := llty(g, t) x := load(g, ty, "%x") y := load(g, ty, "%y") lt := "" gt := "" if isFloat(t) { lt = op(g, "fcmp olt " + ty + " " + x + ", " + y) gt = op(g, "fcmp ogt " + ty + " " + x + ", " + y) } else { lt = op(g, "icmp " + intPred("<", isSigned(t)) + " " + ty + " " + x + ", " + y) gt = op(g, "icmp " + intPred(">", isSigned(t)) + " " + ty + " " + x + ", " + y) } r = op(g, "sub i32 " + op(g, "zext i1 " + gt + " to i32") + ", " + op(g, "zext i1 " + lt + " to i32")) } emit(g, "ret i32 " + r) endHelper(g, s, "define internal i32 " + name + "(ptr %x, ptr %y)") return name } fn strMethod(ptr[Gen] g, ast.Node m, str s) -> Val { n := m.name out := strOut(g) file := strGlobal(g, g.files[m.file]) if n == "sub" { need(g, m, 2) a := coerce(g, expr(g, m.kids[1]), T("int"), m.kids[1], "the start").v b := coerce(g, expr(g, m.kids[2]), T("int"), m.kids[2], "the end").v emit(g, "call void @finch_str_sub(ptr " + out + ", ptr " + s + ", i64 " + a + ", i64 " + b + ", ptr " + file + ", i64 " + str(m.line) + ")") return Val(v: load(g, "{ ptr, i64, i64 }", out), ty: T("str"), fresh: true) } if n == "find" || n == "contains" || n == "starts_with" || n == "ends_with" { need(g, m, 1) t := spill(g, strArg(g, m.kids[1], "the text to look for")) if n == "find" { return Val(v: op(g, "call i64 @finch_str_find(ptr " + s + ", ptr " + t + ")"), ty: T("int")) } if n == "contains" { r := op(g, "call i64 @finch_str_find(ptr " + s + ", ptr " + t + ")") return Val(v: op(g, "icmp sge i64 " + r + ", 0"), ty: T("bool")) } fname := "@finch_str_starts" if n == "ends_with" { fname = "@finch_str_ends" } r := op(g, "call i32 " + fname + "(ptr " + s + ", ptr " + t + ")") return Val(v: op(g, "icmp ne i32 " + r + ", 0"), ty: T("bool")) } if n == "trim" || n == "upper" || n == "lower" { need(g, m, 0) emit(g, "call void @finch_str_" + n + "(ptr " + out + ", ptr " + s + ")") return Val(v: load(g, "{ ptr, i64, i64 }", out), ty: T("str"), fresh: true) } if n == "replace" { need(g, m, 2) a := spill(g, strArg(g, m.kids[1], "the text to replace")) b := spill(g, strArg(g, m.kids[2], "the new text")) emit(g, "call void @finch_str_replace(ptr " + out + ", ptr " + s + ", ptr " + a + ", ptr " + b + ")") return Val(v: load(g, "{ ptr, i64, i64 }", out), ty: T("str"), fresh: true) } if n == "split" { need(g, m, 1) sep := spill(g, strArg(g, m.kids[1], "the separator")) ao := strOut(g) emit(g, "call void @finch_str_split(ptr " + ao + ", ptr " + s + ", ptr " + sep + ")") return Val(v: load(g, "{ ptr, i64, i64 }", ao), ty: ast.arrayOf(T("str")), fresh: true) } if n == "repeat" { need(g, m, 1) k := coerce(g, expr(g, m.kids[1]), T("int"), m.kids[1], "the count").v emit(g, "call void @finch_str_repeat(ptr " + out + ", ptr " + s + ", i64 " + k + ")") return Val(v: load(g, "{ ptr, i64, i64 }", out), ty: T("str"), fresh: true) } if n == "bytes" { need(g, m, 0) ao := strOut(g) emit(g, "call void @finch_bytes_from_str(ptr " + ao + ", ptr " + s + ")") return Val(v: load(g, "{ ptr, i64, i64 }", ao), ty: ast.arrayOf(T("u8")), fresh: true) } fail(g, m, "a str has no method '" + n + "' (it has: sub, find, contains, starts_with, ends_with, split, trim, upper, lower, replace, repeat, bytes)") return Val() } // ---------- the whole program ---------- fn runtimeDecls() -> str { decls := [ "declare void @finch_panic(ptr, i64, ptr) noreturn cold", "declare void @finch_panic_index(ptr, i64, i64, i64) noreturn cold", "declare ptr @finch_alloc(i64)", "declare void @finch_str_copy(ptr, ptr)", "declare void @finch_str_own(ptr)", "declare void @finch_str_concat(ptr, ptr, ptr)", "declare i32 @finch_str_eq(ptr, ptr)", "declare i64 @finch_str_cmp(ptr, ptr)", "declare void @finch_buf_add(ptr, ptr, i64)", "declare void @finch_buf_int(ptr, i64)", "declare void @finch_buf_uint(ptr, i64)", "declare void @finch_buf_float(ptr, double)", "declare void @finch_buf_bool(ptr, i32)", "declare void @finch_buf_ptr(ptr, ptr)", "declare void @finch_buf_char(ptr, i8, i32)", "declare void @finch_buf_str(ptr, ptr, i32)", "declare void @finch_buf_print(ptr)", "declare void @finch_str_from_int(ptr, i64)", "declare void @finch_str_from_uint(ptr, i64)", "declare void @finch_str_from_float(ptr, double)", "declare void @finch_str_from_char(ptr, i8 signext)", "declare void @finch_str_from_bool(ptr, i32)", "declare i64 @finch_str_to_int(ptr, ptr, i64)", "declare double @finch_str_to_float(ptr, ptr, i64)", "declare void @finch_str_sub(ptr, ptr, i64, i64, ptr, i64)", "declare i64 @finch_str_find(ptr, ptr)", "declare i32 @finch_str_starts(ptr, ptr)", "declare i32 @finch_str_ends(ptr, ptr)", "declare void @finch_str_trim(ptr, ptr)", "declare void @finch_str_upper(ptr, ptr)", "declare void @finch_str_lower(ptr, ptr)", "declare void @finch_str_replace(ptr, ptr, ptr, ptr)", "declare void @finch_str_split(ptr, ptr, ptr)", "declare void @finch_str_join(ptr, ptr, ptr)", "declare void @finch_str_repeat(ptr, ptr, i64)", "declare void @finch_bytes_from_str(ptr, ptr)", "declare i64 @finch_map_find(ptr, ptr, i64, i64, i32)", "declare i64 @finch_map_slot(ptr, ptr, i64, i64, i32, ptr)", "declare void @finch_map_remove_at(ptr, i64, i64)", "declare void @finch_map_clear(ptr)", "declare void @finch_map_clone_raw(ptr, ptr, i64)", "declare void @finch_map_missing(ptr, i64, ptr) noreturn cold", "declare void @finch_map_changed(ptr, i64) noreturn cold", "declare void @finch_arr_reserve(ptr, i64, i64)", "declare void @finch_arr_clone_raw(ptr, ptr, i64)", "declare void @finch_arr_make(ptr, i64, i64)", "declare void @finch_arr_resize(ptr, i64, i64)", "declare void @finch_arr_insert_gap(ptr, i64, i64, ptr, i64)", "declare void @finch_arr_remove_gap(ptr, i64, i64)", "declare void @qsort(ptr, i64, i64, ptr)", "declare void @finch_args(ptr, i32, ptr)", "declare void @finch_input(ptr, ptr)", "declare i32 @finch_file_exists(ptr)", "declare i32 @finch_str_try_int(ptr, ptr, ptr)", "declare i32 @finch_str_try_float(ptr, ptr, ptr)", "declare i32 @finch_try_read_file(ptr, ptr, ptr)", "declare i32 @finch_try_write_file(ptr, ptr, ptr)", "declare i32 @finch_try_delete_file(ptr, ptr)", "declare void @finch_report_error(ptr)", "declare void @finch_read_file(ptr, ptr, ptr, i64)", "declare i32 @finch_write_file(ptr, ptr)", "declare i64 @finch_shell(ptr)", "declare i32 @finch_delete_file(ptr)", "declare i32 @printf(ptr, ...)", "declare i32 @fflush(ptr)", "declare void @exit(i32) noreturn", "declare void @free(ptr)", ] return decls.join("\n") + "\n" } fn generate([]ast.Program progs, []str files) -> str { gen := Gen(progs: progs, files: files) g := addr(gen) declare(g) main := findFn(g, "", "main") if main < 0 { print(files[0] + ":1:1: error: there is no main function (add 'fn main() { ... }')") exit(1) } for i in 0..g.sigs.len { define(g, i) } // the C entry point calls the Finch main m := g.sigs[main] callText := "call " + retTy(g, m) + " " + m.ll + "(" pre := "" if m.params.len == 1 { pre = " %args = alloca { ptr, i64, i64 }\n call void @finch_args(ptr %args, i32 %argc, ptr %argv)\n %a = load { ptr, i64, i64 }, ptr %args\n" callText = callText + "{ ptr, i64, i64 } %a" } callText = callText + ")" ret := " ret i32 0\n" if m.fallible { // fn main() -> !: a failure is printed, and the exit code is 1 callText = "%r = " + callText pre = pre + " " + callText + "\n" callText = "" rt := retTy(g, m) pre = pre + " %ok = extractvalue " + rt + " %r, 0\n br i1 %ok, label %main.ok, label %main.bad\nmain.bad:\n" pre = pre + " %m = extractvalue " + rt + " %r, 2\n %mp = alloca { ptr, i64, i64 }\n store { ptr, i64, i64 } %m, ptr %mp\n" pre = pre + " call void @finch_report_error(ptr %mp)\n ret i32 1\nmain.ok:\n" if m.ret.kind == "int" { ret = " %v = extractvalue " + rt + " %r, 1\n %code = trunc i64 %v to i32\n ret i32 %code\n" } } else if m.ret.kind == "int" { callText = "%r = " + callText ret = " %code = trunc i64 %r to i32\n ret i32 %code\n" } g.out.push("define i32 @main(i32 %argc, ptr %argv) {\nentry:\n" + pre + " " + callText + "\n" + ret + "}\n") return "; generated by the self-hosted Finch compiler\n\n" + g.head.join("\n") + "\n\n" + runtimeDecls() + "\n" + g.out.join("\n") }