From ff72f68804c42bb7a7d6d96aef5d8ca54aec01f1 Mon Sep 17 00:00:00 2001 From: Nexoniarz Date: Thu, 29 Oct 2026 11:53:24 +0100 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_016GzNP6r8dg6WDxUVZy8wjL --- src/cimport.fch | 5 ++-- src/gen.fch | 21 ++++++++++---- src/linker.fch | 52 ++++++++++++++++++++++++++++++++++- src/main.fch | 4 +-- tests/lsp_test.py | 7 ++++- tests/new/c_unions_macros.fch | 1 + tests/new/c_unions_macros.out | 1 + tests/new/cunion.h | 3 ++ 8 files changed, 81 insertions(+), 13 deletions(-) diff --git a/src/cimport.fch b/src/cimport.fch index c4613af..8d1701f 100644 --- a/src/cimport.fch +++ b/src/cimport.fch @@ -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 { diff --git a/src/gen.fch b/src/gen.fch index dfa2aa3..a016f49 100644 --- a/src/gen.fch +++ b/src/gen.fch @@ -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) diff --git a/src/linker.fch b/src/linker.fch index 19223bc..e55addc 100644 --- a/src/linker.fch +++ b/src/linker.fch @@ -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" } diff --git a/src/main.fch b/src/main.fch index b256fba..f4c4f00 100644 --- a/src/main.fch +++ b/src/main.fch @@ -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 { diff --git a/tests/lsp_test.py b/tests/lsp_test.py index 9228939..ab41ec7 100755 --- a/tests/lsp_test.py +++ b/tests/lsp_test.py @@ -204,7 +204,12 @@ e3 = src3.replace(" print(", " pt.\n print(") c.notify("textDocument/didChange", {"textDocument": {"uri": uri3, "version": 2}, "contentChanges": [{"text": e3}]}) comp = c.request("textDocument/completion", {"textDocument": {"uri": uri3}, "position": {"line": 3, "character": 7}}) labels = [i["label"] for i in comp["items"]] -check("completion: a module under another name", "base" in labels and "join" in labels, labels) +ok3 = "base" in labels and "join" in labels +if not ok3: # show what the compiler's index says about the module, to see where the names differ + import subprocess + idx = subprocess.run([FINCH, "index", path3], capture_output=True, text=True).stdout + labels = [l for l in idx.splitlines() if l.startswith("@I") or (l.startswith("@F") and "base" in l)][:6] +check("completion: a module under another name", ok3, labels) c.request("shutdown", None) c.notify("exit", None) diff --git a/tests/new/c_unions_macros.fch b/tests/new/c_unions_macros.fch index efe07a3..30f93c7 100644 --- a/tests/new/c_unions_macros.fch +++ b/tests/new/c_unions_macros.fch @@ -26,4 +26,5 @@ fn main() { print(abs(-2.5), abs(-3), min(EV_KEY, 7)) // Finch's abs, not C's int-only one bg := RAYWHITE // #define RAYWHITE (Color){ 245, 245, 245, 255 } print(bg.r, bg.a, DARK.g, DARK.a, HALF_UP.x, HALF_UP.y) + print(twice(21)) // static inline in the header } diff --git a/tests/new/c_unions_macros.out b/tests/new/c_unions_macros.out index 95a18c6..512e303 100644 --- a/tests/new/c_unions_macros.out +++ b/tests/new/c_unions_macros.out @@ -6,3 +6,4 @@ true 32 0.5 2.5 3 7 245 255 20 0 0.5 -1 +42 diff --git a/tests/new/cunion.h b/tests/new/cunion.h index 72b5e1e..d478445 100644 --- a/tests/new/cunion.h +++ b/tests/new/cunion.h @@ -36,3 +36,6 @@ static inline int fill_key(Event *e, int key) { return 1; } int poll_event(Event *e); + +static inline int twice(int x) { return 2 * x; } +static inline int sum_all(int n, ...) { return n; }