TH / FFI interaction on Windows

I’m a bit at my wit’s end here. There’s a weird interaction between Template Haskell and the FFI on Windows. I can’t figure out if I’m doing something wrong, or if there’s some obscure documentation I haven’t been able to find.

Essentially it boils down to:

MODULE1: Uses template haskell. References MODULE2 (e.g. by importing some definition from there)

MODULE2: In addition to the definition needed by MODULE1, it includes a FFI import.

So, on Windows, this does not compile. On Mac, this compiles without issue.

Seemingly, it needs the C file to be compiled and linked in order to run the TH. But this doesn’t happen on Windows.


$ cabal build
Resolving dependencies...
Build profile: -w ghc-9.12.2 -O1
In order, the following will be built (use -v for more details):
 - Small-0.0.0 (lib) (first run)
Configuring library for Small-0.0.0...
Preprocessing library for Small-0.0.0...
Building library for Small-0.0.0...
[1 of 2] Compiling Small.Dep        ( Small\Dep.hs, dist-newstyle\build\x86_64-windows\ghc-9.12.2\Small-0.0.0\build\Small\Dep.o )
[2 of 2] Compiling Small.Foo        ( Small\Foo.hs, dist-newstyle\build\x86_64-windows\ghc-9.12.2\Small-0.0.0\build\Small\Foo.o )
ghc-9.12.2.exe:  | C:\Users\user\dev\windows_link_error\dist-newstyle\build\x86_64-windows\ghc-9.12.2\Small-0.0.0\build\Small\Dep.o: unknown symbol `foreign_fun'
ghc-9.12.2.exe: Could not load Object Code C:\Users\user\dev\windows_link_error\dist-newstyle\build\x86_64-windows\ghc-9.12.2\Small-0.0.0\build\Small\Dep.o.

Here’s a repo that demonstrates the issue: GitHub - asivitz/windows_link_error: Minimal reproduction of a windows link error with GHC

Now, from poking around with this it seems like the contents of foreign_fun.c doesn’t even matter. The fact is that the C file doesn’t get built before the Haskell module. On Windows at least. On Mac it does.

Is this a bug in GHC or Cabal? Asking GPT gives me the following:

You’re hitting a Template Haskell + FFI on Windows gotcha.

What’s happening

    Small.Foo runs a TH splice:
    myString = $(litE (StringL ("foo" ++ depString)))

    To evaluate that splice, GHC must load and execute Small.Dep at compile time (since it needs depString’s value).

    Small.Dep also contains a foreign import of foreign_fun. When GHC loads Small.Dep.o into the TH interpreter on Windows, the loader tries to resolve all externs immediately.

    Your C symbol foreign_fun lives in foreign_fun.o, but that object isn’t linked into the TH process at this point—result:
    unknown symbol 'foreign_fun'.

(Linux/macOS often get away with this because their dynamic linkers are lazier; Windows resolves on load.)

Is this true?? If so, a TH-enabled file probably shouldn’t be allowed to import modules with FFI code at all, otherwise it’s just inviting code that is broken on Windows. Is this documented anywhere?

(I filed a GHC issue, but again I’m not sure if I’m wasting their time with a user issue or a cabal issue!: #26243: Link error during TH generation with foreign import on Windows · Issues · Glasgow Haskell Compiler / GHC · GitLab)

Any advice is appreciated, thank you!

You can test if it is a cabal issue by running cabal in verbose mode, capturing the command-line, then running ghc directly. Or by just attempting to run ghc –make directly even without cabal to begin with (command line shouldn’t be too hard to figure out manually).

Oh good call. I came up with this one liner:

ghc -package base -package template-haskell -iSmall Small/Dep.hs Small/Foo.hs foreign_fun.c

Seems to work fine on both platforms. I guess this confirms that the problem is cabal not building the .c at the right time on windows.

Interestingly, when I look at the verbose output on Mac, the .c file does get built after the Haskell modules (even though the build succeeds). So I guess this isn’t a problem a on Mac, but happens to be a problem on Windows. Maybe the c files should just be built first regardless. I’ll file an issue with Cabal. Thank you!

Opened issue Failure to link c-sources during template haskell on windows · Issue #11130 · haskell/cabal · GitHub