Futhark

repository·master·Indexed 19 days ago

https://github.com/pmunch/futhark

A tool for importing C header files directly into Nim. It utilizes a Clang-based parser called Øpir to translate C definitions into Nim-compatible code via the `importc` macro, eliminating the need for manual wrappers. Futhark supports identifier renaming, type redefinition via `retype`, custom pragma injection, and the ability to ship generated bindings using `outputPath`.

Tokens
3.6K
Snippets
12
Records
18
Agent score
18%

What's inside Futhark

  1. How Futhark and Øpir work together

    master

    Futhark consists of two components:

    1. Øpir (or opir): A helper program compiled with libclang. It uses Clang to parse C headers and produces a JSON output containing all definitions with Nim-friendly types.
    2. futhark module: Provides the importc macro. This macro reads the JSON output from Øpir and applies user-defined overrides (like rename or retype) before generating the final Nim definitions.

    Futhark and Øpir cache their results, so subsequent compilations are as fast as using pre-generated Nim files.

  2. Understand Futhark's approach to C parsing

    master

    Unlike tools like c2nim (which attempts to parse C manually) or nimterop (which uses treesitter), Futhark uses Clang.

    By leveraging Clang, Futhark understands both C syntax and C semantics. This allows it to resolve macros and #ifdef statements automatically, providing the final definitions to the Nim translation process. This results in higher fidelity when wrapping complex C headers.

  3. Use Futhark in Project Mode

    master

    Futhark's preferred mode is to see C files directly during compilation to ensure platform-specific alignment. However, for embedded projects or environments without proper OS support, you can use Project Mode.

    In Project Mode, instead of specifying individual files to import, you specify paths to search. Futhark will:

    1. Recreate the original folder structure of the header files found.
    2. Generate Nim files in their place.
    3. Attempt to properly import/export them to mimic C behavior.
    4. If a .c file exists next to a .h file of the same name, it automatically adds {.passL:"-I<path>".} and {.compile: "<file>".} pragmas to handle compilation.

    Ignoring Files

    To reduce pre-processing work, you can use the ignore option within an importc block. This tells Futhark to skip generating input for a specific file or folder while still allowing it to look at headers inside that folder to resolve other parts of the project.

  4. Ship Futhark wrappers with outputPath

    master

    To distribute bindings without requiring users to have Futhark or Clang installed, use the outputPath option in the importc block. This allows you to check the generated .nim files into your version control system.

    Recommended Pattern: Use a when defined(useFuthark) switch to allow users to either use your shipped generated.nim file or regenerate it using Futhark themselves.

    Example Implementation:

    when defined(useFuthark) or defined(useFutharkForExample):
      import futhark, os
    
      importc:
        outputPath currentSourcePath.parentDir / "generated.nim"
        path "<path to library>"
        "libfile.h"
    else:
      include "generated.nim"

    Notes:

    • If outputPath is a file, use -d:futharkRebuild to update it when changes are made to the importc block.
    • If outputPath is a folder, Futhark will store files with an appended hash for caching purposes.
  5. Install Futhark via Nimble

    master

    Futhark requires clang to be installed on your system. Once clang is available in your system path, you can install Futhark using nimble.

    Prerequisites by Platform

    • Linux: Install clang and libclang-dev (e.g., sudo apt install clang libclang-dev on Debian).
    • FreeBSD/OpenBSD: Install an LLVM port via your package manager.
    • Windows: Install LLVM (e.g., LLVM-15.0.7-win64.exe).
    • macOS: Run xcode-select --install in the terminal.

    If clang is not in your system path, you must provide the path to the directory containing libclang.so (or .lib/.dll on Windows) using the --passL flag during installation.

    # Standard installation if clang is in PATH
    nimble install futhark
    
    # Installation specifying the libclang directory
    nimble install --passL:"-L<path to lib directory containing libclang.so file>" futhark
    
    # Example for a specific path
    nimble install --passL:"-L/usr/lib/llvm-6.0/lib" futhark
  6. Import C header files directly into Nim with Futhark

    master

    Futhark allows you to import C header files directly into Nim using the importc macro. This enables you to use C libraries in Nim without manual wrappers. You specify the library paths, any necessary C defines, and the header files to include.

    To compile against a library (e.g., a dynamic library), you can use Nim's compilation flags like --passL:"-l<library name>" within a static block or via command line.

    import futhark, strutils
    
    # 1. Define a rename callback if needed
    proc renameCb(n: string, k: SymbolKind, p: string, overloading: var bool): string =
      n.replace "stbi_", ""
    
    # 2. Use the importc macro to bring in the C headers
    importc:
      path "./stb"
      define STB_IMAGE_IMPLEMENTATION
      renameCallback renameCb
      rename FILE, CFile
      "stb_image.h"
    
    # 3. Tell Nim how to link (example for a dynamic library)
    static:
      {.compile: "test.c".}
    
    # 4. Use the functions just like in C
    var width, height, channels: cint
    var image = load("futhark.png", width.addr, height.addr, channels.addr, STBI_default.cint)
    if not image.isNil:
      image_free(image)
  7. Configure Futhark compatibility and readability features

    master

    Futhark generates code with when defined statements and numbered identifiers to ensure compatibility with pre-wrapped libraries and avoid collisions. If you want more readable output for documentation or manual inspection, you can control these behaviors using define switches.

    DefineEffect
    nodeclguardsDisables object rename/override functionality (removes when declared guards).
    noopaquetypesDisables opaque types used for unknown objects.
    exportallExports all fields, including renamed ones, making them visible in documentation.
  8. Known limitations of Futhark

    master

    Futhark is currently in beta. Users should be aware of the following limitations:

    • No C++ support: It is designed for C.
    • Macro limitations: It does not currently support function-style macros.
    • Beta stability: Occasional bugs or hiccups may occur with unusual C syntax.
  9. Implement destructors for Futhark-wrapped C objects

    master

    Futhark marks all C objects as {.pure, inheritable.}. This allows you to use standard Nim destructor patterns to manage C object lifecycles. To implement a destructor, define a =destroy procedure for a type that inherits from a Futhark-wrapped object.

    Example:

    type TAudioEngine = object of maEngine
    
    proc `=destroy`(engine: var TAudioEngine) = 
      maEngineUninit(engine.addr)
    type TAudioEngine = object of maEngine # maEngine is a type wrapped by Futhark
    
    proc `=destroy`(engine: var TAudioEngine) = 
      maEngineUninit(engine.addr)
  10. Declare forward declarations for library implementations

    master

    If you are building a library that must implement procedures defined in a C header, use the forward keyword within an importc block. This tells Futhark to generate exportc pragmas instead of importc, creating forward declarations that Nim expects to be implemented elsewhere.

    Usage:

    importc:
      forward "proc_to_forward"

    Advanced usage with custom pragmas:

    importc:
      forward "proc_to_forward", customPragma("Hello world"), used

    Debugging: Use the -d:echoForwards flag to print the generated signatures (including argument names) to the terminal during compilation.

  11. Use pragmasCallback to inject pragmas

    master

    To add custom pragmas to any Futhark-generated output, add a pragmasCallback to your importc block.

    Callback Signature: proc(name: string, kind: SymbolKind, pragmas: var seq[NimNode])

    The pragmas sequence is pre-populated with existing pragmas; you can modify it to add, remove, or completely redefine the pragmas attached to a symbol (e.g., adding discardable to procedures).

    importc:
      pragmasCallback myPragmaProc
    
    proc myPragmaProc(name: string, kind: SymbolKind, pragmas: var seq[NimNode]) = 
      # implementation
  12. Redefine C types using retype

    master

    To make C types more idiomatic in Nim (e.g., converting a raw pointer to an UncheckedArray), use the retype directive.

    Syntax: retype <object>.<field>, <Nim type>

    Note that <object> and <field> must be the renamed Nim identifiers, not the original C names.

    Example: To change a C array field some_element* some_field to a Nim UncheckedArray:

    retype some_object.some_field, ptr UncheckedArray[some_element]

    If you need to redefine an entire object, you can define the type in Nim before the importc block. Futhark wraps its definitions in when declared(SomeType) guards, so your custom definition will take precedence. Warning: You must ensure your custom type matches the C memory layout exactly.

    importc:
      retype my_struct.my_field, ptr UncheckedArray[cint]