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!