<div dir="ltr"><div>Hello Haskell Friends,</div><div><br></div><div>GHC <a href="https://github.com/ghc/ghc/blob/master/compiler/main/DriverPipeline.hs#L1888">always passes</a> -dead_strip_dylibs to the linker on macOS. This means that Haskell programs that use Objective-C-style dynamic binding (via objc_getClass or similar) won't actually be able to find the Objective-C methods they need at runtime. Here's an example illustrating the problem: Consider this small example program:</div><div><br></div><div><div><font face="monospace, monospace">#include <stdio.h></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">extern void *objc_getClass(char *n);</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">void test_get_class(char *n)</font></div><div><font face="monospace, monospace">{</font></div><div><font face="monospace, monospace">        void *cp = objc_getClass(n);</font></div><div><font face="monospace, monospace">        if(cp == NULL)</font></div><div><font face="monospace, monospace">        {</font></div><div><font face="monospace, monospace">                printf("Didn't find class %s\n", n);</font></div><div><font face="monospace, monospace">        }</font></div><div><font face="monospace, monospace">        else</font></div><div><font face="monospace, monospace">        {</font></div><div><font face="monospace, monospace">                printf("Found class %s\n", n);</font></div><div><font face="monospace, monospace">        }</font></div><div><font face="monospace, monospace">}</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">int main(int argc, char *argv[])</font></div><div><font face="monospace, monospace">{</font></div><div><font face="monospace, monospace">        test_get_class(argv[1]);</font></div><div><font face="monospace, monospace">        return 0;</font></div><div><font face="monospace, monospace">}</font></div></div><div><br></div><div>Building like this:</div><div><br></div><div><font face="monospace, monospace">clang -o hasclass main.c -lobjc -L/usr/lib -framework Foundation -F /System/Library/Frameworks/</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="arial, helvetica, sans-serif">Yields an executable that works like this:</font></div><div><font face="arial, helvetica, sans-serif"><br></font></div><div><font face="monospace, monospace">$ ./hasclass NSObject</font></div><div><font face="monospace, monospace">Found class NSObject<br></font></div><div><font face="monospace, monospace">$ ./hasclass NSString</font></div><div><font face="monospace, monospace">Found class NSString<br></font></div><div><font face="monospace, monospace">$ ./hasclass NSDate</font></div><div><font face="monospace, monospace">Found class NSDate<br></font></div><div><font face="monospace, monospace"><br></font></div><div><font face="arial, helvetica, sans-serif">otool shows that we're linked against Foundation properly:</font></div><div><font face="arial, helvetica, sans-serif"><br></font></div><div><div><font face="monospace, monospace">hasclass:</font></div><div><font face="monospace, monospace">        /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)</font></div><div><font face="monospace, monospace">        /System/Library/Frameworks/<wbr>Foundation.framework/Versions/<wbr>C/Foundation (compatibility version 300.0.0, current version 1452.23.0)</font></div><div><font face="monospace, monospace">        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.50.4)</font></div></div><div><font face="arial, helvetica, sans-serif"><br></font></div><div><font face="arial, helvetica, sans-serif"><br></font></div><div><font face="arial, helvetica, sans-serif">Now consider this equivalent Haskell example:</font></div><div><font face="arial, helvetica, sans-serif"><br></font></div><div><div><font face="monospace, monospace">module Main where</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">import Foreign.C.String</font></div><div><font face="monospace, monospace">import Foreign.Ptr</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">import System.Environment</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">foreign import ccall objc_getClass :: CString -> IO (Ptr a)</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">testGetClass :: String -> IO ()</font></div><div><font face="monospace, monospace">testGetClass n = withCString n $ \cn -> do</font></div><div><font face="monospace, monospace">    cp <- objc_getClass cn</font></div><div><font face="monospace, monospace">    let m | cp == nullPtr = "Didn't find class " ++ n</font></div><div><font face="monospace, monospace">          | otherwise     = "Found class " ++ n</font></div><div><font face="monospace, monospace">    putStrLn m</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">main :: IO ()</font></div><div><font face="monospace, monospace">main = getArgs >>= (testGetClass . head)</font></div><div style="font-family:arial,helvetica,sans-serif"><br></div></div><div style="font-family:arial,helvetica,sans-serif">Building like this:</div><div style="font-family:arial,helvetica,sans-serif"><br></div><div><font face="monospace, monospace">ghc -o hasclass Main.hs -lobjc -L/usr/lib -framework foundation -framework-path /System/Library/Frameworks/</font><br></div><div><font face="monospace, monospace"><br></font></div><div><font face="arial, helvetica, sans-serif">Yields an executable that works like this:</font></div><div><font face="arial, helvetica, sans-serif"><br></font></div><div><div style="font-family:arial,sans-serif;font-size:small;text-decoration-style:initial;text-decoration-color:initial"><font face="monospace, monospace">$ ./hasclass NSObject</font></div><div style="font-family:arial,sans-serif;font-size:small;text-decoration-style:initial;text-decoration-color:initial"><font face="monospace, monospace">Found class NSObject<br></font></div><div style="font-family:arial,sans-serif;font-size:small;text-decoration-style:initial;text-decoration-color:initial"><font face="monospace, monospace">$ ./hasclass NSString</font></div><div style="text-decoration-style:initial;text-decoration-color:initial"><font face="monospace, monospace">Didn't find class NSString</font></div><div style="text-decoration-style:initial;text-decoration-color:initial"><span style="font-family:monospace,monospace;font-size:small">$ ./hasclass NSDate</span><br></div><div style="font-family:arial,sans-serif;font-size:small;text-decoration-style:initial;text-decoration-color:initial"><font face="monospace, monospace">Didn't find class NSDate</font></div><div style="font-family:arial,sans-serif;font-size:small;text-decoration-style:initial;text-decoration-color:initial"><font face="monospace, monospace"><br></font></div><div style="font-size:small;text-decoration-style:initial;text-decoration-color:initial"><font face="arial, helvetica, sans-serif">otool shows that our load commands for Foundation are missing:</font></div><div style="font-size:small;text-decoration-style:initial;text-decoration-color:initial"><font face="arial, helvetica, sans-serif"><br></font></div><div style="font-size:small;text-decoration-style:initial;text-decoration-color:initial"><div><font face="monospace, monospace">hasclass:</font></div><div><font face="monospace, monospace">        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.50.4)</font></div><div><font face="monospace, monospace">        /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)</font></div><div><font face="monospace, monospace">        /nix/store/<wbr>7jdxjpy1p5ynl9qrr3ymx01973a1ab<wbr>f6-gmp-6.1.2/lib/libgmp.10.<wbr>dylib (compatibility version 14.0.0, current version 14.2.0)</font></div><div><font face="monospace, monospace">        /usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)</font></div></div></div><div><br></div><div><br></div><div>Interestingly, the testGetClass function will work just fine in GHCi, since it always loads all of the shared objects and frameworks it's asked to. As far as I can tell the only way to get a hasclass executable with the correct behavior is to do the final linking manually with Clang.</div><div><br></div><div>My understanding is that this behavior was introduced to work around symbol count limitations introduced in macOS Sierra. It would be nice to wrap the frameworks passed to the linker in some flags that spares them from -dead_strip_dylibs. I haven't found such a feature in my limited digging around, but perhaps someone who knows more about systems programming on macOS will have an idea. Statically linking against the system frameworks would be a workable stopgap solution, but I have yet to find an easy way to do that.</div><div><br></div><div>I'm curious what others' thoughts are on this issue; it's very difficult to call Objective-C methods from Haskell (without generating Objective-C) without a fix for this.</div><div><br></div><div>Regards,</div><div><br></div><div>Travis Whitaker</div></div>