mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 16:54:25 +00:00
-g: debug info, in the compiler written in Finch
DWARF as LLVM metadata: a compile unit, a subprogram per function, a location on every call, a variable for each local and parameter with types a debugger can open (structs with their fields, arrays and text as ptr/len/cap, maps, enums with their variants, pointers, fixed arrays). Methods show self. Use it with -O0, as the docs say: the optimizer inlines the program into main, which has no debug info. boot.sh builds ten programs with -g -O0 and checks they still behave. 51 passed.
This commit is contained in:
parent
dd9cbe7611
commit
8c3b5b15b9
3 changed files with 341 additions and 10 deletions
325
boot/gen.fch
325
boot/gen.fch
|
|
@ -16,6 +16,13 @@ struct Var {
|
||||||
|
|
||||||
// A scope's variables, and the defers that run when it ends, newest first.
|
// 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 defer sees only the variables declared before it: `orders` says how many.
|
||||||
|
// What the driver asks for besides the program itself.
|
||||||
|
struct Options {
|
||||||
|
bool shared // finch build --shared
|
||||||
|
bool debug // -g
|
||||||
|
str cwd // the folder the compiler was started in (for the debug info)
|
||||||
|
}
|
||||||
|
|
||||||
// A generic struct or function, kept as written until something asks for a version of it.
|
// A generic struct or function, kept as written until something asks for a version of it.
|
||||||
struct Template {
|
struct Template {
|
||||||
str module
|
str module
|
||||||
|
|
@ -138,6 +145,15 @@ struct Gen {
|
||||||
int nvars // variables declared so far in this function
|
int nvars // variables declared so far in this function
|
||||||
cimport.CImports ci
|
cimport.CImports ci
|
||||||
bool shared // building a library (finch build --shared): no main, its export fns are the way in
|
bool shared // building a library (finch build --shared): no main, its export fns are the way in
|
||||||
|
bool debug // -g: debug info (DWARF, as LLVM metadata)
|
||||||
|
str cwd
|
||||||
|
[]str md // the metadata, numbered by position: !0 = md[0]
|
||||||
|
map[str]int mdcache // metadata made already: types by key, locations by line:column
|
||||||
|
[]int mdfiles // the DIFile of each source file
|
||||||
|
int dbgScope // the DISubprogram of the function being built, or -1 (helpers have none)
|
||||||
|
int dbgLine
|
||||||
|
int dbgCol
|
||||||
|
int dbgArg // the parameter number of the next variable, or 0
|
||||||
[]str cdone // C functions and variables already declared
|
[]str cdone // C functions and variables already declared
|
||||||
str ftarget // the call an enclosing or / try is waiting on (see nodeKey)
|
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 ffailed // set by that call: an i1, true if it failed
|
||||||
|
|
@ -155,6 +171,11 @@ fn fail(ptr[Gen] g, ast.Node n, str msg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit(ptr[Gen] g, str line) {
|
fn emit(ptr[Gen] g, str line) {
|
||||||
|
// with -g every call in a function that has debug info has to say where it is
|
||||||
|
if g.dbgScope >= 0 && line.contains("call ") {
|
||||||
|
g.body.push(" " + line + ", !dbg !" + str(dbgLocation(g)))
|
||||||
|
return
|
||||||
|
}
|
||||||
g.body.push(" " + line)
|
g.body.push(" " + line)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -577,10 +598,12 @@ struct Saved {
|
||||||
[]str allocas
|
[]str allocas
|
||||||
int tmp
|
int tmp
|
||||||
bool term
|
bool term
|
||||||
|
int dbgScope
|
||||||
}
|
}
|
||||||
|
|
||||||
fn beginHelper(ptr[Gen] g) -> Saved {
|
fn beginHelper(ptr[Gen] g) -> Saved {
|
||||||
s := Saved(body: g.body, allocas: g.allocas, tmp: g.tmp, term: g.term)
|
s := Saved(body: g.body, allocas: g.allocas, tmp: g.tmp, term: g.term, dbgScope: g.dbgScope)
|
||||||
|
g.dbgScope = -1 // a helper made on the side has no source lines
|
||||||
[]str empty
|
[]str empty
|
||||||
g.body = empty
|
g.body = empty
|
||||||
g.allocas = empty
|
g.allocas = empty
|
||||||
|
|
@ -595,6 +618,7 @@ fn endHelper(ptr[Gen] g, Saved s, str header) {
|
||||||
g.allocas = s.allocas
|
g.allocas = s.allocas
|
||||||
g.tmp = s.tmp
|
g.tmp = s.tmp
|
||||||
g.term = s.term
|
g.term = s.term
|
||||||
|
g.dbgScope = s.dbgScope
|
||||||
}
|
}
|
||||||
|
|
||||||
// for i in 0..n, inside the current function
|
// for i in 0..n, inside the current function
|
||||||
|
|
@ -1048,6 +1072,7 @@ fn addVar(ptr[Gen] g, ast.Node at, str name, ast.Type t, str init, bool readonly
|
||||||
store(g, llty(g, t), init, slot)
|
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.scopes[g.scopes.len - 1].vars.push(Var(name: name, ty: t, slot: slot, readonly: readonly, order: g.nvars))
|
||||||
g.nvars += 1
|
g.nvars += 1
|
||||||
|
dbgVariable(g, at, name, t, slot)
|
||||||
return slot
|
return slot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1532,6 +1557,244 @@ fn resolveStructBody(ptr[Gen] g, int i) {
|
||||||
g.head.push("%S." + modName(g.structs[i].module) + "." + mangle(g.structs[i].name) + " = type { " + fields.join(", ") + " }")
|
g.head.push("%S." + modName(g.structs[i].module) + "." + mangle(g.structs[i].name) + " = type { " + fields.join(", ") + " }")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- debug info (-g) ----------
|
||||||
|
//
|
||||||
|
// DWARF, written as LLVM metadata: a compile unit, a DISubprogram per function, a DILocation on every call,
|
||||||
|
// and a DILocalVariable for every variable, with types a debugger can open up (structs, arrays, maps, enums).
|
||||||
|
|
||||||
|
fn md(ptr[Gen] g, str text) -> int {
|
||||||
|
g.md.push(text)
|
||||||
|
return g.md.len - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mdRef(int id) -> str {
|
||||||
|
if id < 0 {
|
||||||
|
return "null"
|
||||||
|
}
|
||||||
|
return "!" + str(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SizeAlign {
|
||||||
|
int size
|
||||||
|
int align
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alignUp(int n, int a) -> int {
|
||||||
|
if a <= 1 {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
return (n + a - 1) / a * a
|
||||||
|
}
|
||||||
|
|
||||||
|
// the size and alignment LLVM gives a type (what a debugger needs to find the fields)
|
||||||
|
fn sizeAlign(ptr[Gen] g, ast.Type t) -> SizeAlign {
|
||||||
|
k := t.kind
|
||||||
|
if k == "bool" || k == "char" || k == "i8" || k == "u8" {
|
||||||
|
return SizeAlign(size: 1, align: 1)
|
||||||
|
}
|
||||||
|
if k == "i16" || k == "u16" {
|
||||||
|
return SizeAlign(size: 2, align: 2)
|
||||||
|
}
|
||||||
|
if k == "i32" || k == "u32" || k == "f32" || k == "enum" {
|
||||||
|
return SizeAlign(size: 4, align: 4)
|
||||||
|
}
|
||||||
|
if k == "str" || k == "array" {
|
||||||
|
return SizeAlign(size: 24, align: 8)
|
||||||
|
}
|
||||||
|
if k == "map" {
|
||||||
|
return SizeAlign(size: 56, align: 8)
|
||||||
|
}
|
||||||
|
if k == "fixed" {
|
||||||
|
e := sizeAlign(g, t.elem[0])
|
||||||
|
return SizeAlign(size: e.size * t.count, align: e.align)
|
||||||
|
}
|
||||||
|
if k == "struct" {
|
||||||
|
sd := g.structs[findStruct(g, t.module, t.name)]
|
||||||
|
if sd.isC {
|
||||||
|
return SizeAlign(size: sd.csize, align: sd.calign)
|
||||||
|
}
|
||||||
|
cur := 0
|
||||||
|
most := 1
|
||||||
|
for ft in sd.ftypes {
|
||||||
|
f := sizeAlign(g, ft)
|
||||||
|
cur = alignUp(cur, f.align) + f.size
|
||||||
|
if f.align > most {
|
||||||
|
most = f.align
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SizeAlign(size: alignUp(cur, most), align: most)
|
||||||
|
}
|
||||||
|
return SizeAlign(size: 8, align: 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// where each field of a struct is, in bytes
|
||||||
|
fn fieldOffsets(ptr[Gen] g, StructDef sd) -> []int {
|
||||||
|
if sd.isC {
|
||||||
|
return sd.offs
|
||||||
|
}
|
||||||
|
[]int out
|
||||||
|
cur := 0
|
||||||
|
for ft in sd.ftypes {
|
||||||
|
f := sizeAlign(g, ft)
|
||||||
|
cur = alignUp(cur, f.align)
|
||||||
|
out.push(cur)
|
||||||
|
cur += f.size
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dbgMember(ptr[Gen] g, str name, int scope, int file, int line, int base, int bits, int offset) -> int {
|
||||||
|
return md(g, "!DIDerivedType(tag: DW_TAG_member, name: \"" + name + "\", scope: !" + str(scope) + ", file: !" + str(file) + ", line: " + str(line) + ", baseType: " + mdRef(base) + ", size: " + str(bits) + ", offset: " + str(offset) + ")")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn basicType(ptr[Gen] g, str name, int bits, str enc) -> int {
|
||||||
|
return md(g, "!DIBasicType(name: \"" + name + "\", size: " + str(bits) + ", encoding: " + enc + ")")
|
||||||
|
}
|
||||||
|
|
||||||
|
// the debug type of a Finch type; -1 where there is none (nothing, null)
|
||||||
|
fn diType(ptr[Gen] g, ast.Type t) -> int {
|
||||||
|
k := t.kind
|
||||||
|
if k == "void" || k == "null" {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
key := "T:" + ast.show(t)
|
||||||
|
if g.mdcache.has(key) {
|
||||||
|
return g.mdcache[key]
|
||||||
|
}
|
||||||
|
id := -1
|
||||||
|
file := g.mdfiles[0]
|
||||||
|
if k == "bool" {
|
||||||
|
id = basicType(g, "bool", 8, "DW_ATE_boolean")
|
||||||
|
} else if k == "char" {
|
||||||
|
id = basicType(g, "char", 8, "DW_ATE_signed_char")
|
||||||
|
} else if k == "int" {
|
||||||
|
id = basicType(g, "int", 64, "DW_ATE_signed")
|
||||||
|
} else if k == "float" {
|
||||||
|
id = basicType(g, "float", 64, "DW_ATE_float")
|
||||||
|
} else if k == "f32" {
|
||||||
|
id = basicType(g, "f32", 32, "DW_ATE_float")
|
||||||
|
} else if isInt(t) {
|
||||||
|
enc := "DW_ATE_unsigned"
|
||||||
|
if isSigned(t) {
|
||||||
|
enc = "DW_ATE_signed"
|
||||||
|
}
|
||||||
|
id = basicType(g, k, bitsOf(t), enc)
|
||||||
|
} else if k == "enum" {
|
||||||
|
ed := g.enums[findEnum(g, t.module, t.name)]
|
||||||
|
[]str items
|
||||||
|
for i in 0..ed.variants.len {
|
||||||
|
items.push(mdRef(md(g, "!DIEnumerator(name: \"" + ed.variants[i] + "\", value: " + str(ed.values[i]) + ")")))
|
||||||
|
}
|
||||||
|
id = md(g, "!DICompositeType(tag: DW_TAG_enumeration_type, name: \"" + ed.name + "\", file: !" + str(file) + ", size: 32, elements: !{" + items.join(", ") + "})")
|
||||||
|
} else {
|
||||||
|
// the rest can point at themselves (a list node holds a pointer to the next), so the number comes first
|
||||||
|
id = md(g, "")
|
||||||
|
g.mdcache[key] = id
|
||||||
|
if k == "ptr" {
|
||||||
|
base := -1
|
||||||
|
if t.elem.len > 0 {
|
||||||
|
base = diType(g, t.elem[0])
|
||||||
|
}
|
||||||
|
g.md[id] = "!DIDerivedType(tag: DW_TAG_pointer_type, baseType: " + mdRef(base) + ", size: 64)"
|
||||||
|
} else if k == "fixed" {
|
||||||
|
e := sizeAlign(g, t.elem[0])
|
||||||
|
sub := md(g, "!DISubrange(count: " + str(t.count) + ")")
|
||||||
|
g.md[id] = "!DICompositeType(tag: DW_TAG_array_type, baseType: " + mdRef(diType(g, t.elem[0])) + ", size: " + str(e.size * t.count * 8) + ", elements: !{!" + str(sub) + "})"
|
||||||
|
} else if k == "str" || k == "array" {
|
||||||
|
elem := ast.simple("char")
|
||||||
|
if k == "array" {
|
||||||
|
elem = t.elem[0]
|
||||||
|
}
|
||||||
|
ptrT := md(g, "!DIDerivedType(tag: DW_TAG_pointer_type, baseType: " + mdRef(diType(g, elem)) + ", size: 64)")
|
||||||
|
intT := diType(g, ast.simple("int"))
|
||||||
|
a := dbgMember(g, "ptr", id, file, 0, ptrT, 64, 0)
|
||||||
|
b := dbgMember(g, "len", id, file, 0, intT, 64, 64)
|
||||||
|
c := dbgMember(g, "cap", id, file, 0, intT, 64, 128)
|
||||||
|
g.md[id] = "!DICompositeType(tag: DW_TAG_structure_type, name: \"" + ast.show(t) + "\", file: !" + str(file) + ", size: 192, align: 64, elements: !{!" + str(a) + ", !" + str(b) + ", !" + str(c) + "})"
|
||||||
|
} else if k == "map" {
|
||||||
|
intT := diType(g, ast.simple("int"))
|
||||||
|
raw := md(g, "!DIDerivedType(tag: DW_TAG_pointer_type, baseType: null, size: 64)")
|
||||||
|
names := ["entries", "len", "used", "cap", "index", "icap", "adds"]
|
||||||
|
[]str members
|
||||||
|
for i in 0..7 {
|
||||||
|
base := intT
|
||||||
|
if i == 0 || i == 4 {
|
||||||
|
base = raw
|
||||||
|
}
|
||||||
|
members.push("!" + str(dbgMember(g, names[i], id, file, 0, base, 64, 64 * i)))
|
||||||
|
}
|
||||||
|
g.md[id] = "!DICompositeType(tag: DW_TAG_structure_type, name: \"" + ast.show(t) + "\", file: !" + str(file) + ", size: 448, align: 64, elements: !{" + members.join(", ") + "})"
|
||||||
|
} else if k == "struct" {
|
||||||
|
sd := g.structs[findStruct(g, t.module, t.name)]
|
||||||
|
offs := fieldOffsets(g, sd)
|
||||||
|
sa := sizeAlign(g, t)
|
||||||
|
[]str members
|
||||||
|
for i in 0..sd.ftypes.len {
|
||||||
|
fs := sizeAlign(g, sd.ftypes[i])
|
||||||
|
members.push("!" + str(dbgMember(g, sd.fnames[i], id, file, 0, diType(g, sd.ftypes[i]), fs.size * 8, offs[i] * 8)))
|
||||||
|
}
|
||||||
|
g.md[id] = "!DICompositeType(tag: DW_TAG_structure_type, name: \"" + sd.name + "\", file: !" + str(file) + ", size: " + str(sa.size * 8) + ", align: 8, elements: !{" + members.join(", ") + "})"
|
||||||
|
} else {
|
||||||
|
g.md[id] = "!DIBasicType(name: \"" + k + "\", size: 64, encoding: DW_ATE_unsigned)"
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
g.mdcache[key] = id
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dbgLocation(ptr[Gen] g) -> int {
|
||||||
|
key := "L:" + str(g.dbgLine) + ":" + str(g.dbgCol) + ":" + str(g.dbgScope)
|
||||||
|
if g.mdcache.has(key) {
|
||||||
|
return g.mdcache[key]
|
||||||
|
}
|
||||||
|
id := md(g, "!DILocation(line: " + str(g.dbgLine) + ", column: " + str(g.dbgCol) + ", scope: !" + str(g.dbgScope) + ")")
|
||||||
|
g.mdcache[key] = id
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dbgSubprogram(ptr[Gen] g, ast.Node f) -> int {
|
||||||
|
sig := g.cur
|
||||||
|
[]str types
|
||||||
|
types.push(mdRef(diType(g, sig.ret)))
|
||||||
|
if sig.recv.len > 0 {
|
||||||
|
types.push("null")
|
||||||
|
}
|
||||||
|
for p in sig.params {
|
||||||
|
types.push(mdRef(diType(g, p)))
|
||||||
|
}
|
||||||
|
file := g.mdfiles[f.file]
|
||||||
|
sub := md(g, "!DISubroutineType(types: !{" + types.join(", ") + "})")
|
||||||
|
return md(g, "distinct !DISubprogram(name: \"" + sig.name + "\", linkageName: \"" + sig.ll.sub(1, sig.ll.len) + "\", scope: !" + str(file) + ", file: !" + str(file) + ", line: " + str(f.line) + ", type: !" + str(sub) + ", scopeLine: " + str(f.line) + ", flags: DIFlagPrototyped, spFlags: DISPFlagDefinition | DISPFlagLocalToUnit, unit: !0)")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dbgVariable(ptr[Gen] g, ast.Node at, str name, ast.Type t, str slot) {
|
||||||
|
if !g.debug || g.dbgScope < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ty := diType(g, t)
|
||||||
|
if ty < 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
arg := ""
|
||||||
|
if g.dbgArg > 0 {
|
||||||
|
arg = ", arg: " + str(g.dbgArg)
|
||||||
|
}
|
||||||
|
v := md(g, "!DILocalVariable(name: \"" + name + "\", scope: !" + str(g.dbgScope) + ", file: !" + str(g.mdfiles[at.file]) + ", line: " + str(at.line) + ", type: !" + str(ty) + arg + ")")
|
||||||
|
g.dbgLine = at.line
|
||||||
|
g.dbgCol = at.col
|
||||||
|
emit(g, "call void @llvm.dbg.declare(metadata ptr " + slot + ", metadata !" + str(v) + ", metadata !DIExpression())")
|
||||||
|
}
|
||||||
|
|
||||||
|
// the metadata, at the end of the module
|
||||||
|
fn dbgModule(ptr[Gen] g) -> str {
|
||||||
|
[]str lines = ["!llvm.dbg.cu = !{!0}", "!llvm.module.flags = !{!1, !2}"]
|
||||||
|
for i in 0..g.md.len {
|
||||||
|
lines.push("!" + str(i) + " = " + g.md[i])
|
||||||
|
}
|
||||||
|
return lines.join("\n") + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
fn define(ptr[Gen] g, int si) {
|
fn define(ptr[Gen] g, int si) {
|
||||||
g.cur = g.sigs[si]
|
g.cur = g.sigs[si]
|
||||||
g.module = g.cur.module
|
g.module = g.cur.module
|
||||||
|
|
@ -1548,6 +1811,14 @@ fn define(ptr[Gen] g, int si) {
|
||||||
g.tmp = 0
|
g.tmp = 0
|
||||||
g.term = false
|
g.term = false
|
||||||
g.nvars = 0
|
g.nvars = 0
|
||||||
|
g.dbgScope = -1
|
||||||
|
dbgsp := -1
|
||||||
|
if g.debug {
|
||||||
|
dbgsp = dbgSubprogram(g, f)
|
||||||
|
g.dbgScope = dbgsp
|
||||||
|
g.dbgLine = f.line
|
||||||
|
g.dbgCol = f.col
|
||||||
|
}
|
||||||
g.deferOn = false
|
g.deferOn = false
|
||||||
g.inDefer = 0
|
g.inDefer = 0
|
||||||
[]Scope none
|
[]Scope none
|
||||||
|
|
@ -1561,7 +1832,12 @@ fn define(ptr[Gen] g, int si) {
|
||||||
if g.cur.recvEnum {
|
if g.cur.recvEnum {
|
||||||
selfKind = "enum"
|
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))
|
selfTy := ast.Type(kind: selfKind, name: g.cur.recv, module: g.cur.recvmod)
|
||||||
|
g.scopes[0].vars.push(Var(name: "self", ty: selfTy, slot: "%self", readonly: false))
|
||||||
|
if g.debug {
|
||||||
|
// the struct is at the address %self holds: shown as the struct, whose slot that address is
|
||||||
|
dbgVariable(g, f, "self", selfTy, "%self")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for k in 0..g.cur.params.len {
|
for k in 0..g.cur.params.len {
|
||||||
pt := g.cur.params[k]
|
pt := g.cur.params[k]
|
||||||
|
|
@ -1570,7 +1846,12 @@ fn define(ptr[Gen] g, int si) {
|
||||||
if owning(g, pt) && !g.cur.borrow[k] {
|
if owning(g, pt) && !g.cur.borrow[k] {
|
||||||
v = copyValue(g, v, pt)
|
v = copyValue(g, v, pt)
|
||||||
}
|
}
|
||||||
|
g.dbgArg = k + 1
|
||||||
|
if g.cur.recv.len > 0 {
|
||||||
|
g.dbgArg = k + 2
|
||||||
|
}
|
||||||
addVar(g, f.kids[k], g.cur.pnames[k], pt, v, false)
|
addVar(g, f.kids[k], g.cur.pnames[k], pt, v, false)
|
||||||
|
g.dbgArg = 0
|
||||||
}
|
}
|
||||||
block(g, f.kids[f.kids.len - 1], false)
|
block(g, f.kids[f.kids.len - 1], false)
|
||||||
if !g.term {
|
if !g.term {
|
||||||
|
|
@ -1584,7 +1865,12 @@ fn define(ptr[Gen] g, int si) {
|
||||||
emit(g, "ret void")
|
emit(g, "ret void")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g.out.push("define internal " + retTy(g, g.cur) + " " + g.cur.ll + "(" + params.join(", ") + ") {\nentry:\n" +
|
attach := ""
|
||||||
|
if dbgsp >= 0 {
|
||||||
|
attach = " !dbg !" + str(dbgsp)
|
||||||
|
}
|
||||||
|
g.dbgScope = -1
|
||||||
|
g.out.push("define internal " + retTy(g, g.cur) + " " + g.cur.ll + "(" + params.join(", ") + ")" + attach + " {\nentry:\n" +
|
||||||
g.allocas.join("\n") + "\n" + g.body.join("\n") + "\n}\n")
|
g.allocas.join("\n") + "\n" + g.body.join("\n") + "\n}\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1606,6 +1892,8 @@ fn block(ptr[Gen] g, ast.Node b, bool scope) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stmt(ptr[Gen] g, ast.Node s) {
|
fn stmt(ptr[Gen] g, ast.Node s) {
|
||||||
|
g.dbgLine = s.line
|
||||||
|
g.dbgCol = s.col
|
||||||
k := s.kind
|
k := s.kind
|
||||||
if k == "block" {
|
if k == "block" {
|
||||||
block(g, s, true)
|
block(g, s, true)
|
||||||
|
|
@ -4515,9 +4803,32 @@ fn runtimeDecls() -> str {
|
||||||
return decls.join("\n") + "\n"
|
return decls.join("\n") + "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate([]ast.Program progs, []str files, cimport.CImports ci, bool shared) -> str {
|
fn assemble(ptr[Gen] g) -> str {
|
||||||
gen := Gen(progs: progs, files: files, ci: ci, shared: shared)
|
text := "; generated by the Finch compiler written in Finch\n\n" + g.head.join("\n") + "\n\n" + runtimeDecls() + "\n"
|
||||||
|
if g.debug {
|
||||||
|
text = text + "declare void @llvm.dbg.declare(metadata, metadata, metadata)\n\n"
|
||||||
|
}
|
||||||
|
text = text + g.out.join("\n")
|
||||||
|
if g.debug {
|
||||||
|
text = text + "\n" + dbgModule(g)
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate([]ast.Program progs, []str files, cimport.CImports ci, Options opt) -> str {
|
||||||
|
shared := opt.shared
|
||||||
|
gen := Gen(progs: progs, files: files, ci: ci, shared: opt.shared, debug: opt.debug, cwd: opt.cwd, dbgScope: -1)
|
||||||
g := addr(gen)
|
g := addr(gen)
|
||||||
|
if g.debug {
|
||||||
|
// !0 the compile unit, !1 and !2 the module flags, then the source files
|
||||||
|
g.md.push("")
|
||||||
|
g.md.push("!{i32 7, !\"Dwarf Version\", i32 4}")
|
||||||
|
g.md.push("!{i32 2, !\"Debug Info Version\", i32 3}")
|
||||||
|
for fname in files {
|
||||||
|
g.mdfiles.push(md(g, "!DIFile(filename: \"" + fname + "\", directory: \"" + g.cwd + "\")"))
|
||||||
|
}
|
||||||
|
g.md[0] = "distinct !DICompileUnit(language: DW_LANG_C, file: !" + str(g.mdfiles[0]) + ", producer: \"finch\", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)"
|
||||||
|
}
|
||||||
declare(g)
|
declare(g)
|
||||||
main := findFn(g, "", "main")
|
main := findFn(g, "", "main")
|
||||||
if shared {
|
if shared {
|
||||||
|
|
@ -4542,7 +4853,7 @@ fn generate([]ast.Program progs, []str files, cimport.CImports ci, bool shared)
|
||||||
if shared {
|
if shared {
|
||||||
// finch_module_on_fatal(handler): the host's say on runtime errors (see runtime/finch_rt.c)
|
// finch_module_on_fatal(handler): the host's say on runtime errors (see runtime/finch_rt.c)
|
||||||
g.out.push("define void @finch_module_on_fatal(ptr %handler) {\nentry:\n call void @finch_on_fatal(ptr %handler)\n ret void\n}\n")
|
g.out.push("define void @finch_module_on_fatal(ptr %handler) {\nentry:\n call void @finch_on_fatal(ptr %handler)\n ret void\n}\n")
|
||||||
return "; generated by the Finch compiler written in Finch\n\n" + g.head.join("\n") + "\n\n" + runtimeDecls() + "\n" + g.out.join("\n")
|
return assemble(g)
|
||||||
}
|
}
|
||||||
// the C entry point calls the Finch main
|
// the C entry point calls the Finch main
|
||||||
m := g.sigs[main]
|
m := g.sigs[main]
|
||||||
|
|
@ -4571,5 +4882,5 @@ fn generate([]ast.Program progs, []str files, cimport.CImports ci, bool shared)
|
||||||
ret = " %code = trunc i64 %r to i32\n ret i32 %code\n"
|
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")
|
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")
|
return assemble(g)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,7 @@ fn main([]str args) -> int {
|
||||||
out := stem(file)
|
out := stem(file)
|
||||||
outGiven := false
|
outGiven := false
|
||||||
shared := false
|
shared := false
|
||||||
|
debug := false
|
||||||
opt := "-O2"
|
opt := "-O2"
|
||||||
[]str extraLibs
|
[]str extraLibs
|
||||||
[]str extraDirs
|
[]str extraDirs
|
||||||
|
|
@ -161,8 +162,10 @@ fn main([]str args) -> int {
|
||||||
} else if a == "-I" && ai + 1 < args.len {
|
} else if a == "-I" && ai + 1 < args.len {
|
||||||
extraDirs.push(args[ai + 1])
|
extraDirs.push(args[ai + 1])
|
||||||
ai += 1
|
ai += 1
|
||||||
} else if a == "-g" || a == "--target" {
|
} else if a == "-g" {
|
||||||
print("error: '" + a + "' is not in the compiler written in Finch yet; the C++ one has it (branch before-bootstrap)")
|
debug = true
|
||||||
|
} else if a == "--target" {
|
||||||
|
print("error: '--target' is not in the compiler written in Finch yet; the C++ one has it (branch before-bootstrap)")
|
||||||
return 1
|
return 1
|
||||||
} else if cmd == "run" {
|
} else if cmd == "run" {
|
||||||
progArgs.push(a)
|
progArgs.push(a)
|
||||||
|
|
@ -219,7 +222,7 @@ fn main([]str args) -> int {
|
||||||
print(err)
|
print(err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
ir := gen.generate(loader.progs, loader.files, ci, shared)
|
ir := gen.generate(loader.progs, loader.files, ci, gen.Options(shared: shared, debug: debug, cwd: getenv("PWD")))
|
||||||
if cmd == "ir" {
|
if cmd == "ir" {
|
||||||
print(ir)
|
print(ir)
|
||||||
return 0
|
return 0
|
||||||
|
|
@ -246,6 +249,9 @@ fn main([]str args) -> int {
|
||||||
if shared {
|
if shared {
|
||||||
sharedFlags = " -shared -fPIC"
|
sharedFlags = " -shared -fPIC"
|
||||||
}
|
}
|
||||||
|
if debug {
|
||||||
|
sharedFlags = sharedFlags + " -g" // or clang drops the debug info that is in the IR
|
||||||
|
}
|
||||||
// the compiler of the IR is always a clang (CC may be gcc, which can't read it); FINCH_CC picks another clang
|
// the compiler of the IR is always a clang (CC may be gcc, which can't read it); FINCH_CC picks another clang
|
||||||
cc := getenv("FINCH_CC")
|
cc := getenv("FINCH_CC")
|
||||||
if cc.len == 0 {
|
if cc.len == 0 {
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,20 @@ for f in tests/new/fail/*.fch; do
|
||||||
echo "$got" | head -3
|
echo "$got" | head -3
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
# the same programs with debug info (-g -O0): the metadata has to be valid for every kind of thing a program has
|
||||||
|
for f in tests/new/enums.fch tests/new/generics.fch tests/new/std_json.fch tests/run/basics.fch tests/run/methods_basic.fch \
|
||||||
|
tests/run/maps.fch tests/run/errors.fch tests/run/defer.fch tests/run/c_structs.fch tests/run/memory.fch; do
|
||||||
|
input="${f%.fch}.in"
|
||||||
|
[ -f "$input" ] || input=/dev/null
|
||||||
|
if "$boot" build "$f" -g -O0 -o "$tmp/progg" > "$tmp/err" 2>&1 && [ "$("$tmp/progg" < "$input" 2>&1)" == "$(cat "${f%.fch}.out")" ]; then
|
||||||
|
pass=$((pass + 1))
|
||||||
|
else
|
||||||
|
failed=$((failed + 1))
|
||||||
|
echo "FAIL (-g) $f"
|
||||||
|
head -3 "$tmp/err"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
# a library a C program loads (finch build --shared), with the compiler under test
|
# a library a C program loads (finch build --shared), with the compiler under test
|
||||||
if FINCH_RT="$PWD/runtime/finch_rt.c" FINCH="$boot" tests/shared.sh > "$tmp/shared.out" 2>&1; then
|
if FINCH_RT="$PWD/runtime/finch_rt.c" FINCH="$boot" tests/shared.sh > "$tmp/shared.out" 2>&1; then
|
||||||
pass=$((pass + 1))
|
pass=$((pass + 1))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue