mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 08:54:25 +00:00
import json import path import time import proc -- found in std/ after the program's own files, so a program's own json.fch always wins. path: join, base, dir, ext, stem, parts, clean, is_abs. time: now, millis, sleep, date, local, format, days_in_month (clock_gettime, strftime through C import). proc: run, output (an error with the command's output when it fails), env, pid, cwd. json: parse (errors with line and column), stringify, pretty, new_*, get/at/set/push/has/names and str_or/int_or/float_or/bool_or; \u escapes with surrogate pairs. The C importer now leaves a plain (non-const) char * parameter a ptr[char], a buffer C writes into; only const char * is text going in. str(ptr) and str([]u8) are in the self-hosted compiler. boot.sh: 40 passed, 0 failed.
4491 lines
153 KiB
Text
4491 lines
153 KiB
Text
// 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
|
|
import cimport
|
|
|
|
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.
|
|
// A generic struct or function, kept as written until something asks for a version of it.
|
|
struct Template {
|
|
str module
|
|
str name
|
|
str recv // a method: the generic struct it belongs to
|
|
[]str tparams
|
|
int prog
|
|
int index
|
|
bool isStruct
|
|
bool mutself
|
|
}
|
|
|
|
struct Binds {
|
|
[]ast.Type types
|
|
[]bool bound
|
|
}
|
|
|
|
struct EnumDef {
|
|
str module
|
|
str name
|
|
[]str variants
|
|
[]int values
|
|
}
|
|
|
|
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)
|
|
str recvmod // and the module of that struct: "C" for a struct from a header
|
|
bool recvEnum // the receiver is an enum
|
|
[]str tnames // an instance of a generic function: its type parameters and what they stand for
|
|
[]ast.Type targs
|
|
str shown // max[int], for messages
|
|
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
|
|
bool isC // a struct from a C header: module "C", laid out as C laid it out
|
|
[]int lidx // the LLVM element number of each field (C structs have padding between)
|
|
[]int offs // C: the byte offset of each field
|
|
str base // an instance of a generic struct: Stack for Stack[int]
|
|
[]str tnames
|
|
[]ast.Type targs
|
|
str unsupported // C: why it can't be passed by value
|
|
int csize
|
|
int calign
|
|
[]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
|
|
[]Template templates
|
|
[]str tnames // the type parameters in force, and what they stand for
|
|
[]ast.Type targs
|
|
str instNote // " (while building max[Pixel])", added to errors found inside a generic
|
|
[]EnumDef enums
|
|
[]str done
|
|
[]str mm // names of methods that change self, for the mutation analysis
|
|
int nvars // variables declared so far in this function
|
|
cimport.CImports ci
|
|
[]str cdone // C functions and variables already declared
|
|
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 + g.instNote)
|
|
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 findEnum(ptr[Gen] g, str module, str name) -> int {
|
|
for i in 0..g.enums.len {
|
|
if g.enums[i].module == module && g.enums[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 == "fixed" {
|
|
out := ast.Type(kind: "fixed", count: t.count)
|
|
out.elem.push(resolve(g, t.elem[0], at))
|
|
return out
|
|
}
|
|
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
|
|
}
|
|
// T inside a generic
|
|
if t.module.len == 0 && t.elem.len == 0 && g.tnames.contains(t.name) {
|
|
return g.targs[g.tnames.find(t.name)]
|
|
}
|
|
// Stack[int]
|
|
if t.elem.len > 0 {
|
|
[]ast.Type args
|
|
for a in t.elem {
|
|
args.push(resolve(g, a, at))
|
|
}
|
|
gm := 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 + ")")
|
|
}
|
|
gm = t.module
|
|
}
|
|
return instStruct(g, gm, t.name, args, at)
|
|
}
|
|
if t.module == "C" {
|
|
if findStruct(g, "C", t.name) < 0 {
|
|
fail(g, at, "there is no C struct named '" + t.name + "'")
|
|
}
|
|
return ast.Type(kind: "struct", name: t.name, module: "C")
|
|
}
|
|
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 findEnum(g, module, t.name) >= 0 {
|
|
return ast.Type(kind: "enum", name: t.name, module: module)
|
|
}
|
|
if findStruct(g, module, t.name) < 0 {
|
|
// a bare name may be a struct from a C header
|
|
if t.module.len == 0 && findStruct(g, "C", t.name) >= 0 {
|
|
return ast.Type(kind: "struct", name: t.name, module: "C")
|
|
}
|
|
fail(g, at, "there is no type named '" + ast.show(t) + "'")
|
|
}
|
|
return ast.Type(kind: "struct", name: t.name, module: module)
|
|
}
|
|
|
|
// Stack[int] and Pair[str, int] as part of an LLVM name
|
|
fn mangle(str name) -> str {
|
|
return name.replace("[", ".").replace("]", "").replace(",", ".").replace(" ", "")
|
|
}
|
|
|
|
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 == "fixed" {
|
|
return "[" + str(t.count) + " x " + llty(g, t.elem[0]) + "]"
|
|
}
|
|
if k == "enum" {
|
|
return "i32"
|
|
}
|
|
if k == "ptr" || k == "null" {
|
|
return "ptr"
|
|
}
|
|
if k == "void" {
|
|
return "void"
|
|
}
|
|
return "%S." + modName(t.module) + "." + mangle(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" || k == "enum" {
|
|
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 == "enum" {
|
|
return "E_" + modName(t.module) + "_" + t.name
|
|
}
|
|
if t.kind == "struct" {
|
|
return "S_" + modName(t.module) + "_" + mangle(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(sd.lidx[k]))
|
|
spp := op(g, "getelementptr inbounds " + st + ", ptr %src, i32 0, i32 " + str(sd.lidx[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 == "enum" {
|
|
// the variant's name; a number that is no variant shows as Color(7)
|
|
ed := g.enums[findEnum(g, t.module, t.name)]
|
|
v := load(g, "i32", "%a")
|
|
def := newLabel(g, "enum.other")
|
|
done := newLabel(g, "enum.done")
|
|
[]str cases
|
|
[]str labels
|
|
for k in 0..ed.variants.len {
|
|
labels.push(newLabel(g, "enum.v"))
|
|
cases.push("i32 " + str(ed.values[k]) + ", label %" + labels[k])
|
|
}
|
|
emit(g, "switch i32 " + v + ", label %" + def + " [ " + cases.join(" ") + " ]")
|
|
g.term = true
|
|
for k in 0..ed.variants.len {
|
|
label(g, labels[k])
|
|
bufText(g, "%buf", ed.variants[k])
|
|
br(g, done)
|
|
}
|
|
label(g, def)
|
|
bufText(g, "%buf", ed.name + "(")
|
|
emit(g, "call void @finch_buf_int(ptr %buf, i64 " + op(g, "sext i32 " + v + " to i64") + ")")
|
|
bufText(g, "%buf", ")")
|
|
br(g, done)
|
|
label(g, done)
|
|
} 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(sd.lidx[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].recvmod == 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 || findTemplate(g, g.module, s.name, "", true) >= 0 {
|
|
fail(g, s, "the struct '" + s.name + "' is defined twice")
|
|
}
|
|
if s.tparams.len > 0 {
|
|
// a generic struct: it only becomes a real struct for the types it is used with
|
|
g.templates.push(Template(module: g.module, name: s.name, tparams: s.tparams, prog: pi, index: si, isStruct: true))
|
|
continue
|
|
}
|
|
g.structs.push(StructDef(module: g.module, name: s.name, prog: pi, index: si))
|
|
}
|
|
}
|
|
for pi in 0..g.progs.len {
|
|
g.module = g.progs[pi].module
|
|
for ei in 0..g.progs[pi].enums.len {
|
|
e := g.progs[pi].enums[ei]
|
|
if findEnum(g, g.module, e.name) >= 0 || findStruct(g, g.module, e.name) >= 0 {
|
|
fail(g, e, "'" + e.name + "' is defined twice")
|
|
}
|
|
ed := EnumDef(module: g.module, name: e.name)
|
|
next := 0
|
|
for v in e.kids {
|
|
if v.text.len > 0 {
|
|
next = int(v.text)
|
|
}
|
|
if ed.variants.contains(v.name) {
|
|
fail(g, v, "the enum " + e.name + " has the variant " + v.name + " twice")
|
|
}
|
|
if ed.values.contains(next) {
|
|
fail(g, v, "two variants of " + e.name + " have the number " + str(next) + " (give each its own)")
|
|
}
|
|
ed.variants.push(v.name)
|
|
ed.values.push(next)
|
|
next += 1
|
|
}
|
|
g.enums.push(ed)
|
|
}
|
|
}
|
|
for ni in 0..g.ci.structOrder.len {
|
|
g.structs.push(StructDef(module: "C", name: g.ci.structOrder[ni], isC: true, prog: -1, index: ni))
|
|
}
|
|
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.tparams.len > 0 {
|
|
// generic: kept as written, and made for the types it is called with
|
|
if findTemplate(g, g.module, f.name, f.recv, false) >= 0 {
|
|
fail(g, f, "function '" + f.name + "' is defined twice")
|
|
}
|
|
if f.recv.len > 0 && findTemplate(g, g.module, f.recv, "", true) < 0 {
|
|
fail(g, f, "there is no generic struct named '" + f.recv + "' here (a method goes in the same file as its struct)")
|
|
}
|
|
g.templates.push(Template(module: g.module, name: f.name, recv: f.recv, tparams: f.tparams, prog: pi, index: fi))
|
|
continue
|
|
}
|
|
recvmod := g.module
|
|
recvEnum := false
|
|
if f.recv.len > 0 && findEnum(g, g.module, f.recv) >= 0 {
|
|
recvEnum = true
|
|
if findMethod(g, recvmod, f.recv, f.name) >= 0 {
|
|
fail(g, f, "the method " + f.recv + "." + f.name + " is defined twice")
|
|
}
|
|
} else if f.recv.len > 0 {
|
|
si := findStruct(g, g.module, f.recv)
|
|
if si < 0 && findStruct(g, "C", f.recv) >= 0 {
|
|
si = findStruct(g, "C", f.recv) // a method on a struct from C
|
|
recvmod = "C"
|
|
}
|
|
if si < 0 {
|
|
fail(g, f, "there is no struct named '" + f.recv + "' here (a method goes in the same file as its struct, or on a struct from C)")
|
|
}
|
|
if findMethod(g, recvmod, 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, recvmod: recvmod, recvEnum: recvEnum, 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
|
|
}
|
|
}
|
|
}
|
|
for ti in 0..g.templates.len {
|
|
tpl := g.templates[ti]
|
|
if tpl.recv.len > 0 && !tpl.mutself {
|
|
tf := g.progs[tpl.prog].fns[tpl.index]
|
|
if mutates(g, "self", tf.kids[tf.kids.len - 1]) {
|
|
g.templates[ti].mutself = true
|
|
g.mm.push(tpl.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 findTemplate(ptr[Gen] g, str module, str name, str recv, bool isStruct) -> int {
|
|
for i in 0..g.templates.len {
|
|
t := g.templates[i]
|
|
if t.module == module && t.name == name && t.recv == recv && t.isStruct == isStruct {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
fn shownTypes([]ast.Type args) -> str {
|
|
[]str parts
|
|
for a in args {
|
|
parts.push(ast.show(a))
|
|
}
|
|
return parts.join(", ")
|
|
}
|
|
|
|
// Stack[int]: the struct with T = int, made the first time it is asked for.
|
|
fn instStruct(ptr[Gen] g, str module, str base, []ast.Type args, ast.Node at) -> ast.Type {
|
|
ti := findTemplate(g, module, base, "", true)
|
|
if ti < 0 {
|
|
fail(g, at, "there is no generic struct named '" + base + "' (a struct written struct " + base + "[T] { ... } takes type arguments)")
|
|
}
|
|
tpl := g.templates[ti]
|
|
if args.len != tpl.tparams.len {
|
|
fail(g, at, base + " takes " + str(tpl.tparams.len) + " type argument(s), like " + base + "[" + tpl.tparams.join(", ") + "], but got " + str(args.len))
|
|
}
|
|
name := base + "[" + shownTypes(args) + "]"
|
|
if findStruct(g, module, name) < 0 {
|
|
g.structs.push(StructDef(module: module, name: name, prog: tpl.prog, index: tpl.index, base: base, tnames: tpl.tparams, targs: args))
|
|
resolveStruct(g, g.structs.len - 1)
|
|
}
|
|
return ast.Type(kind: "struct", name: name, module: module)
|
|
}
|
|
|
|
// max[int]: the function with T = int; for a method, `recvType` is the struct instance it belongs to.
|
|
fn instFn(ptr[Gen] g, int ti, []ast.Type args, ast.Type recvType, ast.Node at) -> int {
|
|
tpl := g.templates[ti]
|
|
shown := tpl.name + "[" + shownTypes(args) + "]"
|
|
if tpl.recv.len > 0 {
|
|
existing := findMethod(g, tpl.module, recvType.name, tpl.name)
|
|
if existing >= 0 {
|
|
return existing
|
|
}
|
|
} else {
|
|
existing := findFn(g, tpl.module, shown)
|
|
if existing >= 0 {
|
|
return existing
|
|
}
|
|
}
|
|
f := g.progs[tpl.prog].fns[tpl.index]
|
|
sig := FnSig(module: tpl.module, name: shown, recvmod: tpl.module, fallible: f.fallible, prog: tpl.prog, index: tpl.index, tnames: tpl.tparams, targs: args, mutself: tpl.mutself, shown: shown)
|
|
sig.ll = "@finch." + modName(tpl.module) + "." + mangle(shown)
|
|
if tpl.recv.len > 0 {
|
|
sig.name = tpl.name
|
|
sig.recv = recvType.name
|
|
sig.shown = recvType.name + "." + tpl.name
|
|
sig.ll = "@finch." + modName(tpl.module) + "." + mangle(recvType.name) + "." + tpl.name
|
|
}
|
|
savedN := g.tnames
|
|
savedA := g.targs
|
|
savedM := g.module
|
|
savedNote := g.instNote
|
|
g.tnames = tpl.tparams
|
|
g.targs = args
|
|
g.module = tpl.module
|
|
g.instNote = " (while building " + sig.shown + ")"
|
|
sig.ret = resolve(g, f.ty, f)
|
|
body := f.kids[f.kids.len - 1]
|
|
for k in 0..f.kids.len - 1 {
|
|
pt := resolve(g, f.kids[k].ty, f.kids[k])
|
|
sig.params.push(pt)
|
|
sig.pnames.push(f.kids[k].name)
|
|
sig.borrow.push(owning(g, pt) && !mutates(g, f.kids[k].name, body))
|
|
}
|
|
g.tnames = savedN
|
|
g.targs = savedA
|
|
g.module = savedM
|
|
g.instNote = savedNote
|
|
g.sigs.push(sig)
|
|
return g.sigs.len - 1
|
|
}
|
|
|
|
// What each type parameter stands for, from the types the arguments turned out to have.
|
|
fn bindType(ptr[Gen] g, ast.Type pat, ast.Type actual, []str tparams, ptr[Binds] b) {
|
|
if pat.kind == "named" && pat.module.len == 0 && pat.elem.len == 0 && tparams.contains(pat.name) {
|
|
i := tparams.find(pat.name)
|
|
if !b.bound[i] {
|
|
b.types[i] = actual
|
|
b.bound[i] = true
|
|
}
|
|
return
|
|
}
|
|
if (pat.kind == "array" || pat.kind == "ptr" || pat.kind == "fixed") && pat.elem.len > 0 && actual.kind == pat.kind && actual.elem.len > 0 {
|
|
bindType(g, pat.elem[0], actual.elem[0], tparams, b)
|
|
return
|
|
}
|
|
if pat.kind == "map" && actual.kind == "map" {
|
|
bindType(g, pat.elem[0], actual.elem[0], tparams, b)
|
|
bindType(g, pat.elem[1], actual.elem[1], tparams, b)
|
|
return
|
|
}
|
|
if pat.kind == "named" && pat.elem.len > 0 && actual.kind == "struct" {
|
|
sd := g.structs[findStruct(g, actual.module, actual.name)]
|
|
if sd.base == pat.name && sd.targs.len == pat.elem.len {
|
|
for i in 0..pat.elem.len {
|
|
bindType(g, pat.elem[i], sd.targs[i], tparams, b)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// max(3, 4), or max[float](3, 4): make the version the arguments ask for, and call it.
|
|
fn genericCall(ptr[Gen] g, ast.Node c, int ti, int first, str shown) -> Val {
|
|
tpl := g.templates[ti]
|
|
f := g.progs[tpl.prog].fns[tpl.index]
|
|
nparams := f.kids.len - 1
|
|
checkArgs(g, c, first, nparams, shown)
|
|
[]ast.Type args
|
|
if c.ty.elem.len > 0 {
|
|
if c.ty.elem.len != tpl.tparams.len {
|
|
fail(g, c, shown + " takes " + str(tpl.tparams.len) + " type argument(s), but got " + str(c.ty.elem.len))
|
|
}
|
|
for a in c.ty.elem {
|
|
args.push(resolve(g, a, c))
|
|
}
|
|
return callFn(g, instFn(g, ti, args, T("void"), c), c, first, shown, "", "")
|
|
}
|
|
b := Binds()
|
|
for i in 0..tpl.tparams.len {
|
|
b.types.push(T("void"))
|
|
b.bound.push(false)
|
|
}
|
|
[]Val vals
|
|
for k in 0..nparams {
|
|
v := expr(g, c.kids[first + k])
|
|
if v.ty.kind == "void" || v.ty.kind == "null" {
|
|
fail(g, c.kids[first + k], "this has no type to work out " + tpl.tparams.join(", ") + " from")
|
|
}
|
|
vals.push(v)
|
|
bindType(g, f.kids[k].ty, v.ty, tpl.tparams, addr(b))
|
|
}
|
|
for i in 0..tpl.tparams.len {
|
|
if !b.bound[i] {
|
|
fail(g, c, "can't tell what " + tpl.tparams[i] + " is here; write it out: " + tpl.name + "[int](...)")
|
|
}
|
|
}
|
|
si := instFn(g, ti, b.types, T("void"), c)
|
|
sig := g.sigs[si]
|
|
[]str out
|
|
for k in 0..nparams {
|
|
v := coerce(g, vals[k], sig.params[k], c.kids[first + k], "argument '" + sig.pnames[k] + "'")
|
|
out.push(llty(g, sig.params[k]) + " " + v.v)
|
|
}
|
|
return emitCall(g, sig, c, out, shown)
|
|
}
|
|
|
|
// A struct from a C header: the fields clang says it has, at the offsets clang says,
|
|
// with byte padding wherever C left a gap (or a field Finch can't see).
|
|
fn resolveCStruct(ptr[Gen] g, int i) {
|
|
cs := g.ci.structs[g.structs[i].name]
|
|
g.structs[i].state = 1
|
|
cur := 0
|
|
[]str elems
|
|
for f in cs.fields {
|
|
if f.hidden {
|
|
continue
|
|
}
|
|
t := resolve(g, f.ty, ast.Node())
|
|
if t.kind == "struct" {
|
|
resolveStruct(g, findStruct(g, t.module, t.name))
|
|
}
|
|
if f.offset > cur {
|
|
elems.push("[" + str(f.offset - cur) + " x i8]")
|
|
}
|
|
g.structs[i].fnames.push(f.name)
|
|
g.structs[i].ftypes.push(t)
|
|
g.structs[i].lidx.push(elems.len)
|
|
g.structs[i].offs.push(f.offset)
|
|
elems.push(llty(g, t))
|
|
cur = f.offset + f.size
|
|
}
|
|
if cs.size > cur {
|
|
elems.push("[" + str(cs.size - cur) + " x i8]")
|
|
}
|
|
g.structs[i].unsupported = cs.unsupported
|
|
if cs.hasHidden && cs.unsupported.len == 0 {
|
|
g.structs[i].unsupported = "has fields Finch can't see (unions or bit fields)"
|
|
}
|
|
g.structs[i].csize = cs.size
|
|
g.structs[i].calign = cs.align
|
|
g.structs[i].state = 2
|
|
g.head.push("%S.C." + cs.name + " = type { " + elems.join(", ") + " }")
|
|
}
|
|
|
|
fn resolveStruct(ptr[Gen] g, int i) {
|
|
if g.structs[i].state == 2 {
|
|
return
|
|
}
|
|
if g.structs[i].isC {
|
|
resolveCStruct(g, i)
|
|
return
|
|
}
|
|
if g.structs[i].base.len == 0 {
|
|
resolveStructBody(g, i)
|
|
return
|
|
}
|
|
// an instance of a generic struct: its fields are written in terms of T
|
|
savedN := g.tnames
|
|
savedA := g.targs
|
|
savedNote := g.instNote
|
|
g.tnames = g.structs[i].tnames
|
|
g.targs = g.structs[i].targs
|
|
g.instNote = " (while building " + g.structs[i].name + ")"
|
|
resolveStructBody(g, i)
|
|
g.tnames = savedN
|
|
g.targs = savedA
|
|
g.instNote = savedNote
|
|
}
|
|
|
|
fn resolveStructBody(ptr[Gen] g, int i) {
|
|
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].lidx.push(g.structs[i].fnames.len)
|
|
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) + "." + mangle(g.structs[i].name) + " = type { " + fields.join(", ") + " }")
|
|
}
|
|
|
|
fn define(ptr[Gen] g, int si) {
|
|
g.cur = g.sigs[si]
|
|
g.module = g.cur.module
|
|
g.tnames = g.cur.tnames
|
|
g.targs = g.cur.targs
|
|
g.instNote = ""
|
|
if g.cur.targs.len > 0 {
|
|
g.instNote = " (while building " + g.cur.shown + ")"
|
|
}
|
|
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")
|
|
selfKind := "struct"
|
|
if g.cur.recvEnum {
|
|
selfKind = "enum"
|
|
}
|
|
g.scopes[0].vars.push(Var(name: "self", ty: ast.Type(kind: selfKind, name: g.cur.recv, module: g.cur.recvmod), 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 == "match" {
|
|
matchGen(g, s, false)
|
|
} 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)]
|
|
if sd.isC {
|
|
return false
|
|
}
|
|
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()
|
|
}
|
|
|
|
// ---------- match ----------
|
|
|
|
// A pattern as a value of the subject's type. For an enum, a bare variant name (Red) works too.
|
|
fn patternValue(ptr[Gen] g, ast.Node pat, ast.Type st) -> Val {
|
|
if st.kind == "enum" && pat.kind == "var" && lookup(g, pat.name) < 0 {
|
|
ed := g.enums[findEnum(g, st.module, st.name)]
|
|
k := ed.variants.find(pat.name)
|
|
if k < 0 {
|
|
fail(g, pat, "'" + pat.name + "' is not a variant of " + ed.name + " (it has: " + ed.variants.join(", ") + ")")
|
|
}
|
|
return Val(v: str(ed.values[k]), ty: st)
|
|
}
|
|
return coerce(g, exprWant(g, pat, st), st, pat, "this pattern")
|
|
}
|
|
|
|
// match value { A, B -> ... C -> ... else -> ... }, as a statement or (asValue) as an expression.
|
|
// The patterns are tried in order; for an enum the arms have to cover every variant, or end with else.
|
|
fn matchGen(ptr[Gen] g, ast.Node e, bool asValue) -> Val {
|
|
subj := expr(g, e.kids[0])
|
|
st := subj.ty
|
|
if st.kind == "void" || st.kind == "null" {
|
|
fail(g, e.kids[0], "there is nothing to match here")
|
|
}
|
|
end := newLabel(g, "match.end")
|
|
hasElse := false
|
|
reached := false // does any arm go on to the end?
|
|
[]str covered
|
|
[]str inVals
|
|
[]str inLabels
|
|
resultTy := T("void")
|
|
arms := e.kids.len - 1
|
|
for ai in 0..arms {
|
|
arm := e.kids[1 + ai]
|
|
if hasElse {
|
|
fail(g, arm, "this arm can never be used: 'else' already takes everything that is left")
|
|
}
|
|
body := newLabel(g, "match.arm")
|
|
next := newLabel(g, "match.next")
|
|
if arm.flag {
|
|
hasElse = true
|
|
br(g, body)
|
|
} else {
|
|
pats := arm.kids[0].kids
|
|
for pi in 0..pats.len {
|
|
pv := patternValue(g, pats[pi], st)
|
|
if covered.contains(pv.v) && st.kind == "enum" {
|
|
fail(g, pats[pi], "this variant is already handled by an earlier arm")
|
|
}
|
|
covered.push(pv.v)
|
|
hit := compare(g, "==", subj, pv, pats[pi])
|
|
if pi == pats.len - 1 {
|
|
condBr(g, hit.v, body, next)
|
|
} else {
|
|
more := newLabel(g, "match.or")
|
|
condBr(g, hit.v, body, more)
|
|
label(g, more)
|
|
}
|
|
}
|
|
}
|
|
label(g, body)
|
|
node := arm.kids[1]
|
|
if asValue {
|
|
v := expr(g, node)
|
|
if ai == 0 || resultTy.kind == "void" {
|
|
if v.ty.kind == "void" || v.ty.kind == "null" {
|
|
fail(g, node, "an arm of a match that gives a value has to give one")
|
|
}
|
|
resultTy = v.ty
|
|
}
|
|
v = coerce(g, v, resultTy, node, "this arm (the arms of a match have to give the same type)")
|
|
out := own(g, v)
|
|
from := newLabel(g, "match.from")
|
|
br(g, from)
|
|
label(g, from)
|
|
inVals.push(out)
|
|
inLabels.push(from)
|
|
br(g, end)
|
|
reached = true
|
|
} else {
|
|
if node.kind == "block" {
|
|
block(g, node, true)
|
|
} else {
|
|
stmt(g, node)
|
|
}
|
|
if !g.term {
|
|
reached = true
|
|
}
|
|
br(g, end)
|
|
}
|
|
if !arm.flag {
|
|
label(g, next)
|
|
}
|
|
}
|
|
if !hasElse {
|
|
if st.kind == "enum" {
|
|
ed := g.enums[findEnum(g, st.module, st.name)]
|
|
[]str missing
|
|
for k in 0..ed.variants.len {
|
|
if !covered.contains(str(ed.values[k])) {
|
|
missing.push(ed.variants[k])
|
|
}
|
|
}
|
|
if missing.len > 0 {
|
|
fail(g, e, "this match doesn't handle " + missing.join(", ") + " (add them, or an 'else' arm)")
|
|
}
|
|
} else {
|
|
fail(g, e, "a match on " + ast.show(st) + " needs an 'else' arm for everything that is not listed")
|
|
}
|
|
// every variant is handled; only a number that was never a variant gets here
|
|
panicIf(g, "true", "no arm of this match fits the value", e)
|
|
emit(g, "unreachable")
|
|
g.term = true
|
|
}
|
|
label(g, end)
|
|
if !reached {
|
|
emit(g, "unreachable")
|
|
g.term = true
|
|
return Val(ty: T("void"))
|
|
}
|
|
if asValue {
|
|
return Val(v: op(g, "phi " + llty(g, resultTy) + " " + phiList(inVals, inLabels)), ty: resultTy, fresh: owning(g, resultTy))
|
|
}
|
|
return Val(ty: T("void"))
|
|
}
|
|
|
|
fn phiList([]str vals, []str labels) -> str {
|
|
[]str parts
|
|
for i in 0..vals.len {
|
|
parts.push("[ " + vals[i] + ", %" + labels[i] + " ]")
|
|
}
|
|
return parts.join(", ")
|
|
}
|
|
|
|
// ---------- 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 <value> / ... 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 <value>)")
|
|
}
|
|
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 == "match" {
|
|
return matchGen(g, e, true)
|
|
}
|
|
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 == "enum" {
|
|
if !eq {
|
|
fail(g, at, "enums can only be compared with == and != (to go through them in order, give the variants numbers and use int(...))")
|
|
}
|
|
pred := "eq"
|
|
if o == "!=" {
|
|
pred = "ne"
|
|
}
|
|
return Val(v: op(g, "icmp " + pred + " i32 " + 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 && g.ci.consts.has(e.name) {
|
|
c := g.ci.consts[e.name]
|
|
if c.isFloat {
|
|
return Ref(isPlace: false, val: Val(v: llFloat(c.text), ty: T("float"), lit: true))
|
|
}
|
|
return Ref(isPlace: false, val: Val(v: str(c.i), ty: T("int"), lit: true))
|
|
}
|
|
if id < 0 && g.ci.globals.has(e.name) {
|
|
gt := resolve(g, g.ci.globals[e.name], e)
|
|
if !g.cdone.contains("g:" + e.name) {
|
|
g.cdone.push("g:" + e.name)
|
|
g.head.push("@" + e.name + " = external global " + llty(g, gt))
|
|
}
|
|
return Ref(isPlace: true, pl: Place(addr: "@" + e.name, ty: gt))
|
|
}
|
|
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))
|
|
}
|
|
|
|
// A float as C wrote it, in the form LLVM's text format wants: 1e10 -> 1.0e10, .5 -> 0.5
|
|
fn llFloat(str text) -> str {
|
|
s := text
|
|
if s.starts_with(".") {
|
|
s = "0" + s
|
|
}
|
|
if !s.contains(".") {
|
|
e := s.find("e")
|
|
if e < 0 {
|
|
e = s.find("E")
|
|
}
|
|
if e < 0 {
|
|
return s + ".0"
|
|
}
|
|
return s.sub(0, e) + ".0" + s.sub(e, s.len)
|
|
}
|
|
return s
|
|
}
|
|
|
|
// Which enum does `m.kids[0]` name, if it names one? Color, or shapes.Color. -1 if neither.
|
|
fn enumNamed(ptr[Gen] g, ast.Node n) -> int {
|
|
if n.kind == "var" && lookup(g, n.name) < 0 {
|
|
return findEnum(g, g.module, n.name)
|
|
}
|
|
if n.kind == "member" && n.kids[0].kind == "var" && lookup(g, n.kids[0].name) < 0 && imports(g, n.kids[0].name) {
|
|
return findEnum(g, n.kids[0].name, n.name)
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// Color.Red
|
|
fn enumValue(ptr[Gen] g, ast.Node m, int ei) -> Val {
|
|
ed := g.enums[ei]
|
|
k := ed.variants.find(m.name)
|
|
if k < 0 {
|
|
fail(g, m, "the enum " + ed.name + " has no variant '" + m.name + "' (it has: " + ed.variants.join(", ") + ")")
|
|
}
|
|
return Val(v: str(ed.values[k]), ty: ast.Type(kind: "enum", name: ed.name, module: ed.module))
|
|
}
|
|
|
|
fn member(ptr[Gen] g, ast.Node m, bool write) -> Ref {
|
|
ei := enumNamed(g, m.kids[0])
|
|
if ei >= 0 {
|
|
if write {
|
|
fail(g, m, "a variant of an enum can't be changed")
|
|
}
|
|
return Ref(isPlace: false, val: enumValue(g, m, ei))
|
|
}
|
|
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 == "fixed" {
|
|
if write {
|
|
fail(g, m, "." + m.name + " can't be changed directly")
|
|
}
|
|
if m.name == "len" {
|
|
return Ref(isPlace: false, val: Val(v: str(t.count), ty: T("int"), lit: true))
|
|
}
|
|
if m.name != "ptr" {
|
|
fail(g, m, "an array has .len and .ptr, not '." + m.name + "'")
|
|
}
|
|
if !obj.isPlace {
|
|
fail(g, m, ".ptr of a temporary value would point to nothing")
|
|
}
|
|
return Ref(isPlace: false, val: Val(v: obj.pl.addr, ty: ast.ptrTo(t.elem[0])))
|
|
}
|
|
if (t.kind == "array" || t.kind == "str") && m.name == "ptr" {
|
|
// the address of the first element, for C; a literal lives as long as the program, other temporaries do not
|
|
if write {
|
|
fail(g, m, ".ptr can't be changed directly")
|
|
}
|
|
// a constant (a text literal) lives as long as the program; a computed value is freed right after
|
|
if !obj.isPlace && obj.val.fresh && obj.val.v.starts_with("%") {
|
|
fail(g, m, ".ptr of a temporary value would point to freed memory")
|
|
}
|
|
v := obj.val.v
|
|
if obj.isPlace {
|
|
v = load(g, llty(g, t), obj.pl.addr)
|
|
}
|
|
if t.kind == "str" {
|
|
return Ref(isPlace: false, val: Val(v: cstr(g, v), ty: ast.ptrTo(T("char"))))
|
|
}
|
|
return Ref(isPlace: false, val: Val(v: op(g, "extractvalue { ptr, i64, i64 } " + v + ", 0"), ty: ast.ptrTo(t.elem[0])))
|
|
}
|
|
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(sd.lidx[k]))
|
|
return Ref(isPlace: true, pl: Place(addr: fp, ty: ft))
|
|
}
|
|
fv := op(g, "extractvalue " + llty(g, t) + " " + obj.val.v + ", " + str(sd.lidx[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 == "fixed" {
|
|
base := ""
|
|
if obj.isPlace {
|
|
base = obj.pl.addr
|
|
} else {
|
|
base = spill(g, obj.val)
|
|
}
|
|
boundsCheck(g, i, str(t.count), e)
|
|
ep := op(g, "getelementptr inbounds " + llty(g, t) + ", ptr " + base + ", i64 0, i64 " + i)
|
|
return Ref(isPlace: true, pl: Place(addr: ep, ty: t.elem[0]))
|
|
}
|
|
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", "ptr"]
|
|
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) && from.kind == "enum" {
|
|
return Val(v: intCast(g, v.v, T("i32"), 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)
|
|
}
|
|
if to.kind == "ptr" && (from.kind == "ptr" || from.kind == "null") {
|
|
return Val(v: v.v, ty: to)
|
|
}
|
|
if to.kind == "ptr" && isInt(from) {
|
|
return Val(v: op(g, "inttoptr i64 " + intCast(g, v.v, from, T("u64")) + " to ptr"), ty: to) // an address or offset for C
|
|
}
|
|
if isInt(to) && from.kind == "ptr" {
|
|
return Val(v: op(g, "ptrtoint ptr " + v.v + " to " + llty(g, to)), ty: to)
|
|
}
|
|
if to.kind == "ptr" && from.kind == "str" {
|
|
fail(g, c.kids[0], "use .ptr to get a str's address (it is only valid while the str lives)")
|
|
}
|
|
fail(g, c.kids[0], "can't turn " + ast.show(from) + " into " + c.name)
|
|
return v
|
|
}
|
|
|
|
// ---------- the C calling convention for structs passed by value (System V x86-64) ----------
|
|
//
|
|
// A struct of up to 16 bytes travels in registers, one INTEGER or SSE class per eightbyte;
|
|
// bigger ones are copied to the stack (byval) and returned through a hidden pointer (sret).
|
|
// Other targets (Windows, ARM64) have their own rules and are not done here yet.
|
|
|
|
struct Abi {
|
|
str kind // "direct", "expand" (in registers: `parts`) or "memory"
|
|
[]str parts
|
|
str coerced
|
|
int align
|
|
}
|
|
|
|
struct Leaf {
|
|
int offset
|
|
int size
|
|
bool isFloat
|
|
bool isDouble
|
|
}
|
|
|
|
struct Plan {
|
|
Abi ret
|
|
[]Abi params
|
|
str retLL
|
|
bool sret
|
|
}
|
|
|
|
fn sizeBytes(ptr[Gen] g, ast.Type t) -> int {
|
|
if t.kind == "struct" {
|
|
return g.structs[findStruct(g, t.module, t.name)].csize
|
|
}
|
|
if t.kind == "fixed" {
|
|
return t.count * sizeBytes(g, t.elem[0])
|
|
}
|
|
if t.kind == "ptr" || t.kind == "str" {
|
|
return 8
|
|
}
|
|
if t.kind == "float" {
|
|
return 8
|
|
}
|
|
if t.kind == "f32" {
|
|
return 4
|
|
}
|
|
if t.kind == "bool" {
|
|
return 1
|
|
}
|
|
return bitsOf(t) / 8
|
|
}
|
|
|
|
// A value flattened into its scalar pieces, with byte offsets.
|
|
fn leavesOf(ptr[Gen] g, ast.Type t, int base) -> []Leaf {
|
|
[]Leaf out
|
|
if t.kind == "struct" {
|
|
sd := g.structs[findStruct(g, t.module, t.name)]
|
|
for k in 0..sd.ftypes.len {
|
|
for l in leavesOf(g, sd.ftypes[k], base + sd.offs[k]) {
|
|
out.push(l)
|
|
}
|
|
}
|
|
} else if t.kind == "fixed" {
|
|
es := sizeBytes(g, t.elem[0])
|
|
for i in 0..t.count {
|
|
for l in leavesOf(g, t.elem[0], base + i * es) {
|
|
out.push(l)
|
|
}
|
|
}
|
|
} else {
|
|
out.push(Leaf(offset: base, size: sizeBytes(g, t), isFloat: isFloat(t), isDouble: t.kind == "float"))
|
|
}
|
|
return out
|
|
}
|
|
|
|
fn classify(ptr[Gen] g, ast.Type t) -> Abi {
|
|
a := Abi(kind: "direct")
|
|
if t.kind != "struct" {
|
|
return a
|
|
}
|
|
sd := g.structs[findStruct(g, t.module, t.name)]
|
|
size := sd.csize
|
|
a.align = 8
|
|
if sd.calign > 8 {
|
|
a.align = sd.calign
|
|
}
|
|
if size > 16 || size == 0 {
|
|
a.kind = "memory"
|
|
return a
|
|
}
|
|
ls := leavesOf(g, t, 0)
|
|
n := (size + 7) / 8
|
|
[]int cls = [0, 0] // 0 nothing, 1 INTEGER, 2 SSE
|
|
[]bool dbl = [false, false]
|
|
for l in ls {
|
|
m := l.size
|
|
if m > 8 {
|
|
m = 8
|
|
}
|
|
if l.offset % m != 0 { // a misaligned field (a packed struct): memory
|
|
a.kind = "memory"
|
|
return a
|
|
}
|
|
k := l.offset / 8
|
|
if cls[k] == 1 || !l.isFloat {
|
|
cls[k] = 1
|
|
} else {
|
|
cls[k] = 2
|
|
}
|
|
if l.isDouble {
|
|
dbl[k] = true
|
|
}
|
|
}
|
|
a.kind = "expand"
|
|
for k in 0..n {
|
|
bytes := size - 8 * k
|
|
if bytes > 8 {
|
|
bytes = 8
|
|
}
|
|
if cls[k] == 2 {
|
|
if dbl[k] {
|
|
a.parts.push("double")
|
|
} else if bytes > 4 {
|
|
a.parts.push("<2 x float>")
|
|
} else {
|
|
a.parts.push("float")
|
|
}
|
|
} else {
|
|
a.parts.push("i" + str(bytes * 8))
|
|
}
|
|
}
|
|
if a.parts.len == 1 {
|
|
a.coerced = a.parts[0]
|
|
} else {
|
|
a.coerced = "{ " + a.parts.join(", ") + " }"
|
|
}
|
|
return a
|
|
}
|
|
|
|
// Decide how every parameter travels, counting the 6 integer and 8 SSE argument registers.
|
|
fn makePlan(ptr[Gen] g, ast.Type ret, []ast.Type params) -> Plan {
|
|
pl := Plan()
|
|
intRegs := 6
|
|
sseRegs := 8
|
|
pl.ret = classify(g, ret)
|
|
pl.retLL = "void"
|
|
if ret.kind != "void" {
|
|
pl.retLL = cLL(g, ret)
|
|
}
|
|
if pl.ret.kind == "memory" {
|
|
pl.sret = true
|
|
pl.retLL = "void"
|
|
intRegs -= 1
|
|
} else if pl.ret.kind == "expand" {
|
|
pl.retLL = pl.ret.coerced
|
|
}
|
|
for t in params {
|
|
a := classify(g, t)
|
|
if a.kind == "expand" {
|
|
ni := 0
|
|
ns := 0
|
|
for p in a.parts {
|
|
if p.starts_with("i") {
|
|
ni += 1
|
|
} else {
|
|
ns += 1
|
|
}
|
|
}
|
|
if ni <= intRegs && ns <= sseRegs {
|
|
intRegs -= ni
|
|
sseRegs -= ns
|
|
} else {
|
|
a.kind = "memory" // out of registers: the whole struct goes on the stack
|
|
}
|
|
} else if a.kind == "direct" {
|
|
if isFloat(t) {
|
|
sseRegs -= 1
|
|
} else {
|
|
intRegs -= 1
|
|
}
|
|
}
|
|
pl.params.push(a)
|
|
}
|
|
return pl
|
|
}
|
|
|
|
fn checkByValue(ptr[Gen] g, ast.Node at, str fname, ast.Type t, str what) {
|
|
if t.kind != "struct" {
|
|
return
|
|
}
|
|
sd := g.structs[findStruct(g, t.module, t.name)]
|
|
if !sd.isC {
|
|
fail(g, at, "the C function '" + fname + "' " + what + " the Finch struct " + ast.show(t) + ", which C can't take")
|
|
}
|
|
if sd.unsupported.len > 0 {
|
|
fail(g, at, "the C function '" + fname + "' " + what + " the struct " + ast.show(t) + " by value, which Finch can't do yet: it " + sd.unsupported)
|
|
}
|
|
}
|
|
|
|
// A C-callable door into a Finch function, for addr(fn): callbacks like qsort's or libclang's.
|
|
fn cThunk(ptr[Gen] g, int fi, ast.Node at) -> str {
|
|
sig := g.sigs[fi]
|
|
name := "@finch.c." + modName(sig.module) + "." + sig.name
|
|
if g.cdone.contains("t:" + name) {
|
|
return name
|
|
}
|
|
g.cdone.push("t:" + name)
|
|
if sig.recv.len > 0 || sig.fallible {
|
|
fail(g, at, "addr(" + sig.name + ") gives C a function pointer, so it can't be a method or a function that can fail")
|
|
}
|
|
for t in sig.params {
|
|
if !(isNumber(t) || t.kind == "bool" || t.kind == "char" || t.kind == "ptr" || (t.kind == "struct" && g.structs[findStruct(g, t.module, t.name)].isC)) {
|
|
fail(g, at, "addr(" + sig.name + ") gives C a function pointer, so its parameters must be C types (numbers, ptr, C structs), not " + ast.show(t))
|
|
}
|
|
}
|
|
if !(isNumber(sig.ret) || sig.ret.kind == "bool" || sig.ret.kind == "char" || sig.ret.kind == "ptr" || sig.ret.kind == "void" || (sig.ret.kind == "struct" && g.structs[findStruct(g, sig.ret.module, sig.ret.name)].isC)) {
|
|
fail(g, at, "addr(" + sig.name + "): a function given to C can't return " + ast.show(sig.ret))
|
|
}
|
|
pl := makePlan(g, sig.ret, sig.params)
|
|
s := beginHelper(g)
|
|
[]str hdr
|
|
ai := 0
|
|
sretName := ""
|
|
if pl.sret {
|
|
hdr.push("ptr sret(" + llty(g, sig.ret) + ") align 16 %c0")
|
|
sretName = "%c0"
|
|
ai = 1
|
|
}
|
|
[]str args
|
|
for i in 0..sig.params.len {
|
|
t := sig.params[i]
|
|
a := pl.params[i]
|
|
if a.kind == "memory" {
|
|
hdr.push("ptr byval(" + llty(g, t) + ") align " + str(a.align) + " %c" + str(ai))
|
|
args.push(llty(g, t) + " " + load(g, llty(g, t), "%c" + str(ai)))
|
|
ai += 1
|
|
} else if a.kind == "expand" {
|
|
slot := alloca(g, "[16 x i8]")
|
|
for k in 0..a.parts.len {
|
|
hdr.push(a.parts[k] + " %c" + str(ai))
|
|
store(g, a.parts[k], "%c" + str(ai), op(g, "getelementptr inbounds i8, ptr " + slot + ", i64 " + str(8 * k)))
|
|
ai += 1
|
|
}
|
|
args.push(llty(g, t) + " " + load(g, llty(g, t), slot))
|
|
} else {
|
|
hdr.push(cLL(g, t) + cExt(t) + " %c" + str(ai))
|
|
args.push(llty(g, t) + " %c" + str(ai))
|
|
ai += 1
|
|
}
|
|
}
|
|
text := "call " + llty(g, sig.ret) + " " + sig.ll + "(" + args.join(", ") + ")"
|
|
retExt := ""
|
|
if sig.ret.kind == "void" {
|
|
emit(g, text)
|
|
emit(g, "ret void")
|
|
} else if pl.sret {
|
|
store(g, llty(g, sig.ret), op(g, text), sretName)
|
|
emit(g, "ret void")
|
|
} else if pl.ret.kind == "expand" {
|
|
slot := alloca(g, "[16 x i8]")
|
|
store(g, llty(g, sig.ret), op(g, text), slot)
|
|
emit(g, "ret " + pl.ret.coerced + " " + load(g, pl.ret.coerced, slot))
|
|
} else {
|
|
retExt = cExt(sig.ret)
|
|
emit(g, "ret " + cLL(g, sig.ret) + " " + op(g, text))
|
|
}
|
|
endHelper(g, s, "define internal" + retExt + " " + pl.retLL + " " + name + "(" + hdr.join(", ") + ")")
|
|
return name
|
|
}
|
|
|
|
// Color(n): a number as an enum. A literal is checked while compiling, anything else when it runs.
|
|
fn enumFrom(ptr[Gen] g, ast.Node c, int ei) -> Val {
|
|
ed := g.enums[ei]
|
|
checkArgs(g, c, 0, 1, ed.name)
|
|
v := expr(g, c.kids[0])
|
|
if !isInt(v.ty) {
|
|
fail(g, c.kids[0], ed.name + "(...) turns a whole number into the enum, but this is " + ast.show(v.ty))
|
|
}
|
|
t := ast.Type(kind: "enum", name: ed.name, module: ed.module)
|
|
if v.lit {
|
|
if !ed.values.contains(int(v.v)) {
|
|
fail(g, c.kids[0], v.v + " is not the number of any variant of " + ed.name)
|
|
}
|
|
return Val(v: v.v, ty: t)
|
|
}
|
|
wide := intCast(g, v.v, v.ty, T("int"))
|
|
known := ""
|
|
for k in 0..ed.values.len {
|
|
hit := op(g, "icmp eq i64 " + wide + ", " + str(ed.values[k]))
|
|
if k == 0 {
|
|
known = hit
|
|
} else {
|
|
known = op(g, "or i1 " + known + ", " + hit)
|
|
}
|
|
}
|
|
panicIf(g, op(g, "xor i1 " + known + ", true"), "this number is not a variant of " + ed.name, c)
|
|
return Val(v: op(g, "trunc i64 " + wide + " to i32"), ty: t)
|
|
}
|
|
|
|
// ---------- calling C ----------
|
|
|
|
// How C extends a number narrower than 32 bits: plain `char` is signed on x86.
|
|
fn cExt(ast.Type t) -> str {
|
|
if t.kind == "bool" || isUnsigned(t) {
|
|
if bitsOf(t) < 32 {
|
|
return " zeroext"
|
|
}
|
|
}
|
|
if (isSigned(t) || t.kind == "char") && bitsOf(t) < 32 {
|
|
return " signext"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
fn cLL(ptr[Gen] g, ast.Type t) -> str {
|
|
if t.kind == "str" {
|
|
return "ptr"
|
|
}
|
|
return llty(g, t)
|
|
}
|
|
|
|
fn callC(ptr[Gen] g, ast.Node c, cimport.CFunc f) -> Val {
|
|
if f.unsupported.len > 0 {
|
|
fail(g, c, "the C function '" + f.name + "' can't be used from Finch yet: it " + f.unsupported)
|
|
}
|
|
have := c.kids.len
|
|
if f.variadic {
|
|
if have < f.params.len {
|
|
fail(g, c, "'" + f.name + "' takes at least " + str(f.params.len) + " argument(s), but got " + str(have))
|
|
}
|
|
} else if have != f.params.len {
|
|
fail(g, c, "'" + f.name + "' takes " + str(f.params.len) + " argument(s), but got " + str(have))
|
|
}
|
|
ret := resolve(g, f.ret, c)
|
|
checkByValue(g, c, f.name, ret, "returns")
|
|
[]ast.Type ptypes
|
|
for p in f.params {
|
|
pt := resolve(g, p, c)
|
|
checkByValue(g, c, f.name, pt, "takes")
|
|
ptypes.push(pt)
|
|
}
|
|
pl := makePlan(g, ret, ptypes)
|
|
[]str ptys
|
|
[]str args
|
|
sretSlot := ""
|
|
if pl.sret {
|
|
sretSlot = alloca(g, llty(g, ret))
|
|
ptys.push("ptr sret(" + llty(g, ret) + ") align 16")
|
|
args.push("ptr sret(" + llty(g, ret) + ") align 16 " + sretSlot)
|
|
}
|
|
for i in 0..c.kids.len {
|
|
if i < f.params.len {
|
|
pt := ptypes[i]
|
|
a := pl.params[i]
|
|
v := coerce(g, exprWant(g, c.kids[i], pt), pt, c.kids[i], "argument " + str(i + 1) + " of " + f.name)
|
|
if a.kind == "memory" {
|
|
slot := alloca(g, llty(g, pt))
|
|
store(g, llty(g, pt), v.v, slot)
|
|
spec := "ptr byval(" + llty(g, pt) + ") align " + str(a.align)
|
|
ptys.push(spec)
|
|
args.push(spec + " " + slot)
|
|
} else if a.kind == "expand" {
|
|
slot := alloca(g, "[16 x i8]")
|
|
store(g, llty(g, pt), v.v, slot)
|
|
for k in 0..a.parts.len {
|
|
x := load(g, a.parts[k], op(g, "getelementptr inbounds i8, ptr " + slot + ", i64 " + str(8 * k)))
|
|
ptys.push(a.parts[k])
|
|
args.push(a.parts[k] + " " + x)
|
|
}
|
|
} else {
|
|
ptys.push(cLL(g, pt) + cExt(pt))
|
|
if pt.kind == "str" {
|
|
args.push("ptr " + cstr(g, v.v))
|
|
} else {
|
|
args.push(llty(g, pt) + cExt(pt) + " " + v.v)
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
// the '...' part: C's default promotions
|
|
v := expr(g, c.kids[i])
|
|
t := v.ty
|
|
if t.kind == "void" {
|
|
fail(g, c.kids[i], "this gives back nothing")
|
|
}
|
|
if t.kind == "struct" || t.kind == "array" {
|
|
fail(g, c.kids[i], "structs and arrays can't be passed to the '...' part of a C function")
|
|
}
|
|
x := v.v
|
|
if t.kind == "f32" {
|
|
x = floatCast(g, x, t, T("float"))
|
|
t = T("float")
|
|
} else if t.kind == "str" {
|
|
x = cstr(g, x)
|
|
t = T("ptr")
|
|
} else if t.kind == "bool" {
|
|
x = op(g, "zext i1 " + v.v + " to i32")
|
|
t = T("i32")
|
|
} else if (isInt(t) || t.kind == "char") && bitsOf(t) < 32 {
|
|
x = intCast(g, x, t, T("i32"))
|
|
t = T("i32")
|
|
}
|
|
args.push(llty(g, t) + " " + x)
|
|
}
|
|
// declared once (the runtime already declares a few of the libc ones)
|
|
retLL := pl.retLL
|
|
retExt := ""
|
|
if pl.ret.kind == "direct" {
|
|
retExt = cExt(ret)
|
|
}
|
|
tail := ""
|
|
if f.variadic {
|
|
tail = ", ..."
|
|
if ptys.len == 0 {
|
|
tail = "..."
|
|
}
|
|
}
|
|
if !g.cdone.contains("f:" + f.name) {
|
|
g.cdone.push("f:" + f.name)
|
|
if !runtimeDeclares(f.name) {
|
|
g.head.push("declare" + retExt + " " + retLL + " @" + f.name + "(" + ptys.join(", ") + tail + ")")
|
|
}
|
|
}
|
|
sig := ""
|
|
if f.variadic {
|
|
sig = " (" + ptys.join(", ") + tail + ")"
|
|
}
|
|
text := "call" + retExt + " " + retLL + sig + " @" + f.name + "(" + args.join(", ") + ")"
|
|
if ret.kind == "void" {
|
|
emit(g, text)
|
|
return Val(ty: ret)
|
|
}
|
|
if pl.sret {
|
|
emit(g, text)
|
|
return Val(v: load(g, llty(g, ret), sretSlot), ty: ret)
|
|
}
|
|
r := op(g, text)
|
|
if pl.ret.kind == "expand" {
|
|
slot := alloca(g, "[16 x i8]")
|
|
store(g, pl.ret.coerced, r, slot)
|
|
return Val(v: load(g, llty(g, ret), slot), ty: ret)
|
|
}
|
|
if ret.kind == "str" {
|
|
out := strOut(g)
|
|
store(g, "{ ptr, i64, i64 }", "zeroinitializer", out)
|
|
emit(g, "call void @finch_str_from_c(ptr " + out + ", ptr " + r + ")")
|
|
return Val(v: load(g, "{ ptr, i64, i64 }", out), ty: ret) // borrowed: copied if it is kept
|
|
}
|
|
return Val(v: r, ty: ret)
|
|
}
|
|
|
|
// libc functions the runtime declares itself, with the signatures C has
|
|
fn runtimeDeclares(str name) -> bool {
|
|
return ["printf", "fflush", "exit", "free", "qsort"].contains(name)
|
|
}
|
|
|
|
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" && lookup(g, a.name) < 0 {
|
|
fi := findFn(g, g.module, a.name)
|
|
if fi >= 0 {
|
|
return Val(v: cThunk(g, fi, a), ty: T("ptr")) // a C function pointer to a Finch function
|
|
}
|
|
}
|
|
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 == "ptr" {
|
|
// a C string, copied
|
|
store(g, "{ ptr, i64, i64 }", "zeroinitializer", out)
|
|
emit(g, "call void @finch_str_from_c(ptr " + out + ", ptr " + v.v + ")")
|
|
} else if k == "array" && (v.ty.elem[0].kind == "u8" || v.ty.elem[0].kind == "char") {
|
|
// bytes become text
|
|
store(g, "{ ptr, i64, i64 }", "zeroinitializer", out)
|
|
emit(g, "call void @finch_str_from_bytes(ptr " + out + ", ptr " + spill(g, v) + ")")
|
|
} else if k == "bool" || k == "char" || k == "array" || k == "map" || k == "struct" || k == "enum" {
|
|
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 {
|
|
if c.ty.elem.len > 0 {
|
|
fail(g, c, "'" + n + "' is not generic, so it takes no type arguments")
|
|
}
|
|
return callFn(g, fi, c, 0, n, "", "")
|
|
}
|
|
gti := findTemplate(g, g.module, n, "", false)
|
|
if gti >= 0 {
|
|
return genericCall(g, c, gti, 0, n)
|
|
}
|
|
if findTemplate(g, g.module, n, "", true) >= 0 {
|
|
// Stack[int](...): a generic struct made with its type
|
|
if c.ty.elem.len == 0 {
|
|
fail(g, c, "'" + n + "' is generic, so say which type it holds: " + n + "[int](...)")
|
|
}
|
|
[]ast.Type targs
|
|
for a in c.ty.elem {
|
|
targs.push(resolve(g, a, c))
|
|
}
|
|
gt := instStruct(g, g.module, n, targs, c)
|
|
return construct(g, findStruct(g, gt.module, gt.name), c.kids, c.names, c)
|
|
}
|
|
si := findStruct(g, g.module, n)
|
|
if si >= 0 {
|
|
return construct(g, si, c.kids, c.names, c)
|
|
}
|
|
ei := findEnum(g, g.module, n)
|
|
if ei >= 0 {
|
|
return enumFrom(g, c, ei)
|
|
}
|
|
if g.ci.fns.has(n) {
|
|
return callC(g, c, g.ci.fns[n])
|
|
}
|
|
si = findStruct(g, "C", 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)
|
|
}
|
|
return emitCall(g, sig, c, args, shown)
|
|
}
|
|
|
|
// The call itself, once the arguments are values ("i64 5"); the receiver's address, if any, is already first.
|
|
fn emitCall(ptr[Gen] g, FnSig sig, ast.Node c, []str args, str shown) -> Val {
|
|
if sig.fallible && g.ftarget != nodeKey(c) {
|
|
fail(g, c, "'" + shown + "' can fail, so say what happens then: " + shown + "(...) or <value>, " + 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 := ast.Node()
|
|
if !sd.isC {
|
|
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 !sd.isC && 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
|
|
savedTN := g.tnames
|
|
savedTA := g.targs
|
|
g.tnames = sd.tnames
|
|
g.targs = sd.targs
|
|
[]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.tnames = savedTN
|
|
g.targs = savedTA
|
|
g.scopes = savedScopes
|
|
} else {
|
|
continue
|
|
}
|
|
v = op(g, "insertvalue " + st + " " + v + ", " + llty(g, ft) + " " + own(g, fv) + ", " + str(sd.lidx[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, "", "")
|
|
}
|
|
mti := findTemplate(g, mn, m.name, "", false)
|
|
if mti >= 0 {
|
|
return genericCall(g, m, mti, 1, mn + "." + m.name)
|
|
}
|
|
mei := findEnum(g, mn, m.name)
|
|
if mei >= 0 {
|
|
return enumFrom(g, m, mei)
|
|
}
|
|
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" || t.kind == "enum" {
|
|
if r.isPlace {
|
|
selfAddr = r.pl.addr
|
|
} else {
|
|
selfAddr = spill(g, r.val)
|
|
}
|
|
}
|
|
if st.kind == "struct" || st.kind == "enum" {
|
|
fi := findMethod(g, st.module, st.name, m.name)
|
|
if fi < 0 && st.kind == "struct" {
|
|
sdi := findStruct(g, st.module, st.name)
|
|
if g.structs[sdi].base.len > 0 {
|
|
mti := findTemplate(g, st.module, m.name, g.structs[sdi].base, false)
|
|
if mti >= 0 {
|
|
fi = instFn(g, mti, g.structs[sdi].targs, st, m)
|
|
}
|
|
}
|
|
}
|
|
if fi < 0 {
|
|
fail(g, m, "the " + st.kind + " " + 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" || t.kind == "enum") {
|
|
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 @finch_str_from_c(ptr, ptr)",
|
|
"declare void @finch_str_from_bytes(ptr, ptr)",
|
|
"declare void @exit(i32) noreturn",
|
|
"declare void @free(ptr)",
|
|
]
|
|
return decls.join("\n") + "\n"
|
|
}
|
|
|
|
fn generate([]ast.Program progs, []str files, cimport.CImports ci) -> str {
|
|
gen := Gen(progs: progs, files: files, ci: ci)
|
|
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)
|
|
}
|
|
// instantiating a generic adds functions while this goes on, so the end is not fixed
|
|
di := 0
|
|
while di < g.sigs.len {
|
|
define(g, di)
|
|
di += 1
|
|
}
|
|
// 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")
|
|
}
|