static inline C functions (mingw's printf and time), macOS libraries that call their host, lld's missing-library message

- a static inline function in a header is called through a pointer a small C file keeps
  (void *finch_inl_f = &f), built with the program's include folders and cached
- --shared on macOS links with -undefined dynamic_lookup, as a .so does on Linux
- "unable to find library -lx" (lld, MinGW) gives the library-not-found advice
- the language server test shows the index records when alias completion fails

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016GzNP6r8dg6WDxUVZy8wjL
This commit is contained in:
Nexoniarz 2026-10-29 11:53:24 +01:00
commit ff72f68804
8 changed files with 81 additions and 13 deletions

View file

@ -18,6 +18,7 @@ struct CFunc {
ast.Type ret
[]ast.Type params
bool variadic
bool inline // static inline in the header: called through a pointer a small C file keeps (finch_inl_name)
str unsupported // non-empty: why Finch can't call it (yet)
}
@ -535,9 +536,7 @@ fn visit(ptr[Importer] imp, CXCursor c) {
}
f := CFunc(name: name, header: imp.header)
if clang_Cursor_getStorageClass(c) == CX_SC_Static {
f.unsupported = "is a static inline C function (it has no code to link against)"
imp.out.fns[name] = f
return
f.inline = true // mingw's printf and time, header-only helpers
}
ft := clang_getCanonicalType(clang_getCursorType(c))
if ft.kind == CXType_FunctionNoProto {

View file

@ -5086,17 +5086,26 @@ fn callC(ptr[Gen] g, ast.Node c, cimport.CFunc f) -> Val {
tail = "..."
}
}
if !g.cdone.contains("f:" + f.name) {
callee := "@" + f.name
sig := ""
if f.variadic {
sig = " (" + ptys.join(", ") + tail + ")"
}
if f.inline {
// a static inline function has no symbol: the linker builds a C file with void *finch_inl_name = &name;
if !g.cdone.contains("i:" + f.name) {
g.cdone.push("i:" + f.name)
g.head.push("@finch_inl_" + f.name + " = external global ptr")
}
callee = load(g, "ptr", "@finch_inl_" + f.name)
sig = " (" + ptys.join(", ") + tail + ")"
} else 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(", ") + ")"
text := "call" + retExt + " " + retLL + sig + " " + callee + "(" + args.join(", ") + ")"
if ret.kind == "void" {
emit(g, text)
releaseAll(g, temps, mark)

View file

@ -98,6 +98,40 @@ fn depsKey([]str files, str flags) -> str {
return fingerprint2(all.join("\n"))
}
// "@finch_inl_name = external global ptr" in the IR -> a C file (in the cache) that defines them; "" if none
fn inlineFile(str ir, cimport.CImports ci) -> str {
[]str headers
[]str lines
for line in ir.split("\n") {
if line.starts_with("@finch_inl_") && line.ends_with(" = external global ptr") {
name := line.sub(11, line.find(" = "))
h := ci.fns[name].header
if !headers.contains(h) {
headers.push(h)
}
lines.push("void *finch_inl_" + name + " = (void *)&" + name + ";")
}
}
if lines.len == 0 {
return ""
}
[]str src
for h in headers {
src.push("#include \"" + h + "\"")
}
for l in lines {
src.push(l)
}
text := src.join("\n") + "\n"
dir := host.cacheDir() + "/c"
host.makeDirs(dir)
path := dir + "/inline_" + fingerprint2(text) + ".c"
if !file_exists(path) {
write_file(path, text)
}
return path
}
// A C file of the program (link "x.c"), built once and kept until it or a header it includes changes.
fn cObject(str src, str cc, str flags, platform.Target tg, str log) -> str! {
dir := host.cacheDir() + "/c"
@ -225,6 +259,8 @@ fn linkFailed(str output, cimport.CImports ci, platform.Target tg) -> int {
missing.add(between(l, "unresolved external symbol ", " \n\r"))
} else if l.contains("cannot find -l") {
libs.add(between(l, "cannot find -l", ": \n\r"))
} else if l.contains("unable to find library -l") {
libs.add(between(l, "unable to find library -l", ": \n\r")) // lld (MinGW)
} else if l.contains("library not found for -l") {
libs.add(between(l, "library not found for -l", ": \n\r"))
} else if l.contains("ld: library '") {
@ -368,6 +404,20 @@ fn build(Build b, platform.Target tg, cimport.CImports ci) -> int {
}
}
cmd := linkWith + " " + quote(obj) + " " + quote(rtobj) + " -o " + quote(b.out)
// static inline C functions the program calls: a C file that keeps a pointer to each
inl := inlineFile(b.ir, ci)
if inl.len > 0 {
pic := " -fPIC"
if tg.windows {
pic = ""
}
o := cObject(inl, cc, " -O2" + pic + b.cflags, tg, log) or {
print(err)
host.remove(log)
return 1
}
cmd = cmd + " " + quote(o)
}
if !tg.msvc {
cmd = cmd + " -lm"
}
@ -384,7 +434,7 @@ fn build(Build b, platform.Target tg, cimport.CImports ci) -> int {
}
if b.shared {
if tg.darwin {
cmd = cmd + " -dynamiclib"
cmd = cmd + " -dynamiclib -undefined dynamic_lookup" // like a .so: the host's functions are found when it is loaded
} else {
cmd = cmd + " -shared"
}

View file

@ -495,8 +495,8 @@ fn main([]str args) -> int {
extra = extra + " -g" // or clang drops the debug info that is in the IR
}
[]str cflagList
for d in extraDirs {
cflagList.push(" -I" + linker.quote(d))
for d in dirs {
cflagList.push(" -I" + linker.quote(d)) // also the program's own folders (its headers, for inline C functions)
}
cflagList.push(cflags)
if linker.build(linker.Build(out: out, ir: ir, rt: rt, opt: opt, extra: extra, shared: shared, links: links, name: absolute(file), cflags: cflagList.join(""), ldflags: ldflags), tg, ci) != 0 {