Skip to content

Commit

Permalink
Add example of loading at runtime from C
Browse files Browse the repository at this point in the history
  • Loading branch information
WardBrian committed Oct 16, 2024
1 parent a186e7e commit dfc391b
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 10 deletions.
18 changes: 12 additions & 6 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,23 @@ jobs:
path: ./stan/
key: ${{ runner.os }}-stan-${{ hashFiles('stan/src/stan/version.hpp') }}-v${{ env.CACHE_VERSION }}

- name: Build C example (Unix)
if: matrix.os != 'windows-latest'
- name: Set up TBB for C example
if: matrix.os == 'windows-latest'
run: |
Add-Content $env:GITHUB_PATH "$(pwd)/stan/lib/stan_math/lib/tbb"
- name: Build C example
run: |
cd c-example/
make example -j4
make example_static
rm ../src/bridgestan.o
rm ../test_models/full/full_model.a
make -j4 example example_static example_runtime
rm ../src/bridgestan.o ../test_models/full/*.a
echo "Dynamically linked example"
./example
echo "Statically linked example"
./example_static
echo "Runtime loading example"
./example_runtime ../test_models/full/full_model.so
shell: bash

# we use the cache here to build the Stan models once for multiple interfaces
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*.a
c-example/example
c-example/example_static
c-example/example_runtime
*.exe
make/local

Expand Down
3 changes: 3 additions & 0 deletions c-example/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@ example_static$(EXE): example.c ../test_models/$(MODEL)/$(MODEL)_model.a
$(CC) -c -I ../src example.c -o example.o
$(LINK.cpp) -o example_static$(EXE) example.o ../test_models/$(MODEL)/$(MODEL)_model.a $(LDLIBS) $(LIBSUNDIALS) $(MPI_TARGETS) $(TBB_TARGETS)
$(RM) example.o

example_runtime$(EXE): runtime_loading.c
$(CC) -I ../src runtime_loading.c -o example_runtime$(EXE) -ldl
31 changes: 28 additions & 3 deletions c-example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ This shows how one could write a C program which calls BridgeStan.
Any compiled language with a C foreign function interface and
the ability to link against C libraries should be able to work similarly.

## Usage with dynamic linking
## Binding a specific model at build type

### Dynamic linking

It is possible to link against the same `name_model.so` object used by the other
BridgeStan interfaces. This creates a dynamic link.
Expand All @@ -26,17 +28,20 @@ It has 1 parameters.

You can change the test model by specifying `MODEL` on the command line.
Models which require data can have a path passed in as the first argument.

```shell
make MODEL=multi example
./example ../test_models/multi/multi.data.json
```

This will output

```
This model's name is multi_model.
It has 10 parameters.
```

### Notes
#### Notes

The basic steps for using with a generic BridgeStan model are

Expand All @@ -54,7 +59,7 @@ in the same folder as the executable, or on your `PATH`.
On all platforms, dynamic linking requires that the original `NAME_model.so` object
still exist when the executable is run.

## Usage with static linking
### Static linking

The makefile here also shows how to create a `.a` static library using the BridgeStan
source, and then compiling an executable which is independent of the location of the model.
Expand All @@ -70,3 +75,23 @@ Will output the same as the above. Note that some Stan libraries such as TBB
are still dynamically linked.

`MODEL` can also be used to specify which model to statically link.

## Loading a model at runtime

The `runtime_loading.c` file shows how to use `dlfcn.h` to load a model at runtime.
This is useful if you want to load a model based on user input, or if you want to
load different models in the same executable.

```shell
make example_runtime
./example_runtime ../test_models/full/full_model.so
```

will output

```
This model's name is full_model.
It has 1 parameters.
```

The same executable can be passed different models without recompiling.
66 changes: 66 additions & 0 deletions c-example/runtime_loading.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#include "bridgestan.h"
#include <stdio.h>
#include <dlfcn.h>

#if __STDC_VERSION__ < 202000
#define typeof __typeof__
#endif

int main(int argc, char** argv) {
char* lib;
char* data;

// require at least the library name
if (argc > 2) {
lib = argv[1];
data = argv[2];
} else if (argc > 1) {
lib = argv[1];
data = NULL;
} else {
fprintf(stderr, "Usage: %s <library> [data]\n", argv[0]);
return 1;
}

// load the shared library
void* handle = dlopen(lib, RTLD_LAZY);
if (!handle) {
fprintf(stderr, "Error: %s\n", dlerror());
return 1;
}

int major = *(int*)dlsym(handle, "bs_major_version");
int minor = *(int*)dlsym(handle, "bs_minor_version");
int patch = *(int*)dlsym(handle, "bs_patch_version");
fprintf(stderr, "Using BridgeStan version %d.%d.%d\n", major, minor, patch);

// Get function pointers. Uses C23's typeof to re-use bridgestan.h definitions.
// We could also write out the types and not include bridgestan.h
typeof(&bs_model_construct) bs_model_construct
= dlsym(handle, "bs_model_construct");
typeof(&bs_free_error_msg) bs_free_error_msg
= dlsym(handle, "bs_free_error_msg");
typeof(&bs_model_destruct) bs_model_destruct
= dlsym(handle, "bs_model_destruct");
typeof(&bs_name) bs_name = dlsym(handle, "bs_name");
typeof(&bs_param_num) bs_param_num = dlsym(handle, "bs_param_num");

// from here on, the code is exactly the same as example.c

// this could potentially error, and we may get information back about why.
char* err;
bs_model* model = bs_model_construct(data, 123, &err);
if (!model) {
if (err) {
printf("Error: %s", err);
bs_free_error_msg(err);
}
return 1;
}

printf("This model's name is %s.\n", bs_name(model));
printf("It has %d parameters.\n", bs_param_num(model, 0, 0));

bs_model_destruct(model);
return 0;
}
19 changes: 18 additions & 1 deletion docs/languages/c-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ BridgeStan's pre-requisites and downloaded a copy of the BridgeStan source code.
Example Program
---------------

An example program is provided alongside the BridgeStan source in :file:`c-example/`.
Two example programs are provided alongside the BridgeStan source in :file:`c-example/`.
Details for building the example can be found in :file:`c-example/Makefile`.

The first assumes you wish to link a specific model into the program,
and the second demonstrates how to load a model at runtime

.. raw:: html

<details>
Expand All @@ -30,6 +33,20 @@ Details for building the example can be found in :file:`c-example/Makefile`.
</details>


.. raw:: html

<details>
<summary><a>Show runtime_loading.c</a></summary>


.. literalinclude:: ../../c-example/runtime_loading.c
:language: c

.. raw:: html

</details>


API Reference
-------------

Expand Down

0 comments on commit dfc391b

Please sign in to comment.