~patmaddox

Changes On Branch ffi
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Changes In Branch ffi Excluding Merge-Ins

This is equivalent to a diff from 3e1e2e713a to 4aab2109da

2023-10-19
20:06
merge ffi check-in: 840780d4a4 user: patmaddox tags: trunk
2023-08-04
09:12
ffi: add Lua as a language, since it's included in base Closed-Leaf check-in: 4aab2109da user: patmaddox tags: ffi
2023-08-03
20:30
ffi: notes on C++ including headers check-in: 8e08e01286 user: patmaddox tags: ffi
14:04
merge ffi [f07e90b2c8]

- uclcmd reference implementation
- C implementation check-in: 97a6e46531 user: patmaddox tags: trunk

2023-07-29
17:29
ffi: initial draft of notes check-in: 32aa3d6a2b user: patmaddox tags: ffi
17:16
www: home page links to drafts trunk instead of all checkins check-in: 3e1e2e713a user: patmaddox tags: trunk
17:15
www: update link to drafts on home page check-in: 47312783ee user: patmaddox tags: trunk

Added drafts/ffi.md.

















































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# Comparing FFI in different languages

- goals
  - call FreeBSD C libraries
  - build command line tools
  - write automated tests for operating system libraries
- criteria
  - FFI ergonomics
  - general language ergonomics / toolchain
- noteworthy, but non-criteria
  - performance
  - shared memory access
  - calling other language from C
  - memory control
- preference
  - languages that compile to single binary
- test cases
  - hello world - pass in an arg
  - mutate from C - upcase
  - intentional memory leak
  - concurrency
  - libucl
- notes
  - all pretty much the same
    - most have you write some kind of header definition
    - sometimes you have to write a binding funtion also
  - strings are a pain
    - C strings have no length, are null-terminated
    - newer languages have length, not null-terminated
	- newer languages often use UTF encoded strings, C strings are a sequence of bytes with no specified encoding
    - almost always result in two memory copies - convert language string to C string, call C, convert C string back to language
	- can also create null pointer in calling language, and let C initialize it (sometimes)
  - automatic header import vs explicit bindings
    - Go, Zig, and C++ can use C headers directly, without needing to define bindings
	- D, Nim, and Rust need to explicitly define bindings
    - automatic header imports is very useful, especially as you have to reference more stuff
	- structs seem like a PITA with languages requiring bindings
	- explicit bindings sucks - you get into definition dependencies. Nim has C2Nim, but on ucl.h it used 30GB of RAM and then got OOM killed.
  - C++ calls C functions directly, so the library calling doesn't change at all
    - Supposedly there's a potential issue if the C header includes other headers - they end up including the C++ header with the same name.

Changes to src/ffi-adventure/README.md.

1
2
3
4
5
6
7
8
9
10

11
12
13
14
15
16
17
18






19
20

21
22
23
24
25
26
27
28
29
# FFI Adventure

Explorations in calling C libraries from other languages.

Languages for consideration:

- C (reference)
- D
- Elixir
- Go

- Nim
- Pony
- Rust
- V
- Zig

Languages to investigate:







- Jai
- Nit

- Odin
- Vale (needs FreeBSD port)

## Examples

- hello world (pass a string to C)
- upcase (modify string in calling language)
- intentional memory leak (how easy is it?)
- concurrency (data races?)




|





>






|

>
>
>
>
>
>


>

|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# FFI Adventure

Explorations in calling C libraries from other languages.

## Languages for consideration

- C (reference)
- D
- Elixir
- Go
- Lua - `/usr/libexec/flua`
- Nim
- Pony
- Rust
- V
- Zig

## Languages to investigate

I know less about these, and may have to port some of them to FreeBSD.

- C++
- Common Lisp
- Crystal
- Haxe
- Jai
- Nit
- OCaml
- Odin
- Vale

## Examples

- hello world (pass a string to C)
- upcase (modify string in calling language)
- intentional memory leak (how easy is it?)
- concurrency (data races?)

Added src/ffi-adventure/hello-world/hello-world-cpp/Makefile.



















>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
_build/hello: hello.cpp ../hello-world-c/_build/libhello.a
	@mkdir -p ${.TARGET:H}
	c++ -o ${.TARGET} -Wall -Werror -I../hello-world-c -L../hello-world-c/_build -lhello ${.ALLSRC:[1]}

run: _build/hello
	./_build/hello

clean:
	rm -rf _build

Added src/ffi-adventure/hello-world/hello-world-cpp/hello.cpp.



















>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
extern "C"
{
#include "libhello.h"
}

int main() {
  hello("C++");
  return 0;
}

Added src/ffi-adventure/libucl/Justfile.

























>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
test:
  #!/bin/sh
  mkdir -p _build
  for j in */Justfile; do
    impl=$(dirname $j)
    just -f $j run > _build/$impl
    cmp expected _build/$impl
  done

@clean:
  rm -rf _build
  for j in */Justfile; do just -f $j clean; done

Added src/ffi-adventure/libucl/data.ucl.











>
>
>
>
>
1
2
3
4
5
greeting = "hello world"
vars {
  foo = "this is foo"
  bar = "this is bar"
}

Added src/ffi-adventure/libucl/expected.







>
>
>
1
2
3
hello world
this is foo
this is bar

Added src/ffi-adventure/libucl/libucl-c/Justfile.













>
>
>
>
>
>
1
2
3
4
5
6
@run:
  make > /dev/null
  ./_build/libucl-c

@clean:
  rm -rf _build

Added src/ffi-adventure/libucl/libucl-c/Makefile.







>
>
>
1
2
3
_build/libucl-c: libucl_c.c
	@mkdir -p ${.TARGET:H}
	cc -I/usr/local/include -L/usr/local/lib -lucl -Wall -Werror -o ${.TARGET} ${.ALLSRC}

Added src/ffi-adventure/libucl/libucl-c/libucl_c.c.









































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <ucl.h>

int main() {
  ucl_object_t* ucl = NULL;
  const char* greeting = NULL;
  const char* foo = NULL;
  const char* bar = NULL;

  struct ucl_parser* parser = ucl_parser_new(UCL_PARSER_DEFAULT);
  ucl_parser_add_file(parser, "../data.ucl");
  if((ucl = ucl_parser_get_object(parser))) {
    ucl_object_tostring_safe(ucl_object_lookup(ucl, "greeting"), &greeting);
    ucl_object_tostring_safe(ucl_object_lookup_path(ucl, "vars.foo"), &foo);
    ucl_object_tostring_safe(ucl_object_lookup_path(ucl, "vars.bar"), &bar);
  }
  puts(greeting);
  puts(foo);
  puts(bar);
}

Added src/ffi-adventure/libucl/libucl-cpp/Justfile.













>
>
>
>
>
>
1
2
3
4
5
6
@run:
  make > /dev/null
  ./_build/libucl-cpp

@clean:
  rm -rf _build

Added src/ffi-adventure/libucl/libucl-cpp/Makefile.







>
>
>
1
2
3
_build/libucl-cpp: libucl_cpp.cpp
	@mkdir -p ${.TARGET:H}
	c++ -I/usr/local/include -L/usr/local/lib -lucl -Wall -Werror -o ${.TARGET} ${.ALLSRC}

Added src/ffi-adventure/libucl/libucl-cpp/libucl_cpp.cpp.

















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
extern "C"
{
#include <ucl.h>
}

int main() {
  ucl_object_t* ucl = NULL;
  const char* greeting = NULL;
  const char* foo = NULL;
  const char* bar = NULL;

  struct ucl_parser* parser = ucl_parser_new(UCL_PARSER_DEFAULT);
  ucl_parser_add_file(parser, "../data.ucl");
  if((ucl = ucl_parser_get_object(parser))) {
    ucl_object_tostring_safe(ucl_object_lookup(ucl, "greeting"), &greeting);
    ucl_object_tostring_safe(ucl_object_lookup_path(ucl, "vars.foo"), &foo);
    ucl_object_tostring_safe(ucl_object_lookup_path(ucl, "vars.bar"), &bar);
  }

  std::cout << greeting << "\n";
  std::cout << foo << "\n";
  std::cout << bar << "\n";
}

Added src/ffi-adventure/libucl/libucl-go/Justfile.















>
>
>
>
>
>
>
1
2
3
4
5
6
7
@run:
  mkdir -p _build
  go build -o _build/libucl-go *.go
  ./_build/libucl-go

@clean:
  rm -rf _build

Added src/ffi-adventure/libucl/libucl-go/libucl_go.go.



























































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

/*
#cgo CFLAGS: -I/usr/local/include
#cgo LDFLAGS: -L/usr/local/lib -lucl
#include <ucl.h>
*/
import "C"
import "fmt"
//import "unsafe"

func main() {
  var greeting *C.char
  var foo *C.char
  var bar *C.char

  var parser = C.ucl_parser_new(C.UCL_PARSER_DEFAULT)
  C.ucl_parser_add_file(parser, C.CString("../data.ucl"))
  var ucl = C.ucl_parser_get_object(parser)
  if(ucl != nil) {
    C.ucl_object_tostring_safe(C.ucl_object_lookup(ucl, C.CString("greeting")), &greeting)
    C.ucl_object_tostring_safe(C.ucl_object_lookup_path(ucl, C.CString("vars.foo")), &foo)
    C.ucl_object_tostring_safe(C.ucl_object_lookup_path(ucl, C.CString("vars.bar")), &bar)
  }

  fmt.Println(C.GoString(greeting))
  fmt.Println(C.GoString(foo))
  fmt.Println(C.GoString(bar))
}

Added src/ffi-adventure/libucl/libucl-zig/Justfile.











>
>
>
>
>
1
2
3
4
5
@run:
  zig build run

@clean:
  rm -rf _build

Added src/ffi-adventure/libucl/libucl-zig/build.zig.











































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const std = @import("std");

pub fn build(b: *std.build.Builder) void {
    // Standard target options allows the person running `zig build` to choose
    // what target to build for. Here we do not override the defaults, which
    // means any target is allowed, and the default is native. Other options
    // for restricting supported target set are available.
    const target = b.standardTargetOptions(.{});

    // Standard release options allow the person running `zig build` to select
    // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
    const mode = b.standardReleaseOptions();

    const exe = b.addExecutable("libucl-zig", "src/main.zig");
    exe.setTarget(target);
    exe.setBuildMode(mode);
    exe.addIncludePath("/usr/local/include");
    exe.addLibraryPath("/usr/local/lib");
    exe.linkSystemLibrary("ucl");
    exe.install();

    const run_cmd = exe.run();
    run_cmd.step.dependOn(b.getInstallStep());
    if (b.args) |args| {
        run_cmd.addArgs(args);
    }

    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);

    const exe_tests = b.addTest("src/main.zig");
    exe_tests.setTarget(target);
    exe_tests.setBuildMode(mode);

    const test_step = b.step("test", "Run unit tests");
    test_step.dependOn(&exe_tests.step);
}

Added src/ffi-adventure/libucl/libucl-zig/src/main.zig.























































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const std = @import("std");

const ucl = @cImport({
  @cInclude("ucl.h");
});

pub fn main() !void {
    var parser: ?*ucl.ucl_parser = ucl.ucl_parser_new(ucl.UCL_PARSER_DEFAULT);
    var ucl_obj: ?*ucl.ucl_object_t = null;
    var greeting: [*c] const u8 = null;
    var foo: [*c] const u8 = null;
    var bar: [*c] const u8 = null;

    _ = ucl.ucl_parser_add_file(parser, "../data.ucl");
    ucl_obj = ucl.ucl_parser_get_object(parser);
    if(ucl_obj != null) {
      _ = ucl.ucl_object_tostring_safe(ucl.ucl_object_lookup(ucl_obj, "greeting"), &greeting);
      _ = ucl.ucl_object_tostring_safe(ucl.ucl_object_lookup_path(ucl_obj, "vars.foo"), &foo);
      _ = ucl.ucl_object_tostring_safe(ucl.ucl_object_lookup_path(ucl_obj, "vars.bar"), &bar);
    }

    const stdout_file = std.io.getStdOut().writer();
    var bw = std.io.bufferedWriter(stdout_file);
    const stdout = bw.writer();
    try stdout.print("{s}\n{s}\n{s}\n", .{greeting, foo, bar});
    try bw.flush();
}

Added src/ffi-adventure/libucl/uclcmd/Justfile.















>
>
>
>
>
>
>
1
2
3
4
5
6
7
run:
  #!/bin/sh
  for v in greeting vars.foo vars.bar; do
    uclcmd get -q -f ../data.ucl $v
  done

clean: