Wrapping TensorFlow C API with WrapC
Wrapping TensorFlow C API with WrapC
In this tutorial, we are going to see how we can wrap a C API with WrapC.
WrapC is an Eiffel wrapper generator for C libraries. It is used to create libraries that bridge the gap between Eiffel and C. Originally known as EWG, WrapC is an updated version using the latest Eiffel version.
For more see: https://github.com/eiffel-wrap-c/WrapC
Installation
First, we need to install WrapC and TensorFlow for C
Installing WrapC
To install WrapC:
- clone or download it from https://github.com/eiffel-wrap-c/WrapC
- Compile the binary:
- Assume
$WRAP_C
is the directory where you unpacked the WrapC code. - Open with EiffelStudio the project located at
$WRAP_C/src/ewg/system.ecf
and finalize it. Then copy the binary from theEIFGENs/.../F_code
into$WRAP_C/bin
, you can also put it under your PATH variable.
- Assume
To learn more check WrapC Developer Guide
Check wrap_c binary
$>wrap_c --version
wrap_c version 1.0.0
Installing TensorFlow for C
TensorFlow provides a C API that can be used to generate bindings for other languages. This section is based on TensorFlow installation guide
Linux
For this tutorial, we use the Linux CPU binaries
https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-cpu-linux-x86_64-1.14.0.tar.gz
Extract the code sudo tar -C /usr/local -xzf libtensorflow-cpu-linux-x86_64-1.14.0.tar.gz
Linker
On Linux/macOS, if you extract the TensorFlow C library to a system directory, such as /usr/local, configure the linker with ldconfig:
sudo ldconfig
Example program
With the TensorFlow C library installed, create an example program with the following source code (hello_tf.c):
#include <stdio.h>
#include <tensorflow/c/c_api.h>
int main() {
printf("Hello from TensorFlow C library version %s\n", TF_Version());
return 0;
}
Compile
Compile the example program to create an executable, then run:
gcc hello_tf.c -ltensorflow -o hello_tf
./hello_tf
Windows
For this tutorial, we use the Windows CPU binaries
https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-cpu-windows-x86_64-1.14.0.zip.
For this tutorial, we are using Microsft C/C++ compiler.
Extract the content of the zip file. Then we need to update the file c_api.h
replace the line #include "tensorflow/c/tf_attrtype.h"
with #include "tf_attrtype.h"
Finally download the file https://github.com/tensorflow/tensorflow/blob/r1.14/tensorflow/c/tf_attrtype.h since it's not included in the zip file.
Example program
With the TensorFlow C library installed, create an example program with the following source code (hello_tf.c):
#include <stdio.h>
#include "c_api.h"
int main() {
printf("Hello from TensorFlow C library version %s\n", TF_Version());
return 0;
}
Here we are assuming our source code hello_tc.c
is at the same directory where the TensorFlow headers are located. Be sure to have in the path or in the same directory TensorFlow lib and dll files.
Compile
cl hello_tf.c /link tensorflow.lib
The command outputs: Hello from TensorFlow C library version number.For more details check the installation guide
Create a new project to wrap TensorFlow C API
In the previous step, you have downloaded WrapC code. Got to the directory $WRAP_C/examples
you will see something like this
examples
callback
simple
template
To generate a new Eiffel wrapper, the simplest way is to start from the template wrapping example located at $WRAP_C/examples/template
Directory Structure
template
|___ library
config.xml -- configuration file to customize the way EWG generates the wrapper.
build.eant -- build script.
library.ecf -- library configuration file.
scripts -- optional pre or post-processing scripts
Wrapping Development Process
Step 1: Copy and Rename the template project
Copy the template project (located at $WRAP_C/examples/template to a new location for example
$NEW_PATH/test_wrapc/template) and rename it: for example as:
wrap_tensorflow so your new project now will be
$NEW_PATH/test_wrapc/wrapc_tensorflow`
Now go to $NEW_PATH/test_wrapc/wrapc_tensorflow/library
directory
- Rename and update the
library.ecf
aslibtensorflow.ecf
- update the libtensorflow.ecf file.
- replace all the occurrences of
template_library
bylibtensorflow
. - update externals include location
<external_include location="$HEADER_TEMPLATE_PATH">
with the directory path to the C header/s
- replace all the occurrences of
- update the libtensorflow.ecf file.
Create a new directory C
under $NEW_PATH/test_wrapc/wrapc_tensorflow/library
, now you will see something like this
Note: is not required to create the C directory you can use the path to where you have the TensorFlow libs and headers.
template
|___ library
C -- directory to include headers and libs of the target C library.
config.xml -- configuration file to customize the way EWG generates the wrapper.
build.eant -- build script.
library.ecf -- library configuration file.
scripts -- optional pre or post-processing scripts
Copy the TensorFlow lib
and include
folders into the C
folder, that you have already created.When your Eiffel executable is running, Eiffel TensorFlow library needs tensorflow.dll
. Ne sure the DLL file can be found in your environment PATH or they are in the same folder of your executable.
At this point, our libtensorflow.ecf
will look like as follow
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-21-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-21-0 http://www.eiffel.com/developers/xml/configuration-1-21-0.xsd" name="libtensorflow" uuid="E7B07AD1-10D6-4D4E-A36D-5A7E7C0AA970" library_target="libtensorflow">
<target name="libtensorflow">
<root all_classes="true"/>
<option warning="warning" full_class_checking="false" is_attached_by_default="true" manifest_array_type="mismatch_warning">
<assertions precondition="true"/>
</option>
<setting name="console_application" value="true"/>
<setting name="inlining_size" value="0"/>
<setting name="msil_classes_per_module" value="5"/>
<setting name="dead_code_removal" value="feature"/>
<external_include location="$ECF_CONFIG_PATH\generated_wrapper\c\include">
<condition>
<platform value="windows"/>
</condition>
</external_include>
<external_include location="$ECF_CONFIG_PATH\C\include\tensorflow\c">
<condition>
<platform value="windows"/>
</condition>
</external_include>
<external_include location="/usr/local/include/tensorflow/c">
<condition>
<platform excluded_value="windows"/>
</condition>
</external_include>
<external_include location="$ECF_CONFIG_PATH/generated_wrapper/c/include">
<condition>
<platform excluded_value="windows"/>
</condition>
</external_include>
<external_object location="$ECF_CONFIG_PATH/C/lib/tensorflow.lib">
<condition>
<platform value="windows"/>
</condition>
</external_object>
<external_object location="$ECF_CONFIG_PATH/C/spec/$ISE_C_COMPILER/$ISE_PLATFORM/lib/eif_libtensorflow.lib">
<condition>
<platform value="windows"/>
</condition>
</external_object>
<external_object location="$ECF_CONFIG_PATH/C/spec/$(ISE_PLATFORM)/lib/eif_libtensorflow.a">
<condition>
<platform excluded_value="windows"/>
</condition>
</external_object>
<external_linker_flag value="-ltensorflow ">
<condition>
<platform excluded_value="windows"/>
</condition>
</external_linker_flag>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<cluster name="src" location=".\" recursive="true">
<file_rule>
<exclude>/EIFGENs$</exclude>
<exclude>/c$</exclude>
<exclude>/include$</exclude>
</file_rule>
</cluster>
</target>
</system>
Step 2: Configure config.xml
To customize the way WrapC
generate wrappers, we will update the configuration file config.xml
located at $NEW_PATH/test_wrapc/wrapc_tensorflow/library
. Initially, we will start only including the function to print the tensor flow version. This is the file we will update to wrap Structs, Unions, Functions, Callbacks, and Macros.
<?xml version="1.0"?>
<ewg_config name="libtensorflow">
<rule_list>
<!-- Ignore everything that starts with an underscore. -->
<rule>
<match>
<identifier name="_.*"/>
</match>
<wrapper type="none">
</wrapper>
</rule>
<!-- Include TF function TF_Version-->
<rule>
<match>
<identifier name="TF_Version"/>
</match>
<wrapper type="default">
</wrapper>
</rule>
<!-- And the rest should be ignored -->
<rule>
<match>
</match>
<wrapper type="none">
</wrapper>
</rule>
</rule_list>
</ewg_config>
At this point our configuration file includes only the library function TF_Version
and WrapC will generate a class C_API_FUNCTIONS_API
with the corresponding Eiffel feature. The name of the class is C_API_FUNCTIONS_API
because the function declaration comes from a C header file named c_api.h
. Later we will see we can group all the TensorFlow functions defined in c_api.h
using a uniform class name.
So now we are ready to use the wrap_c
, we can use geant
tool or just run the tool directly. For this tutorial, we will use the wrap_c
tool directly, check the sour code to see the geant
file build.eant
, to learn more about geant
check the following link
We will execute the following command:
Linux
wrap_c --verbose --c_compile_options=-I/usr/local/include/tensorflow/c --output-dir=./generated_wrapper --full-header=/usr/local/include/tensorflow/c/c_api.h --config=config.xml
On Linux we are using the default location for TensorFlow installation, change if you use a different path.
Windows
wrap_c --verbose --output-dir=%cd%/generated_wrapper --full-header=%cd%/C/include/tensorflow/c/c_api.h --config=%cd%/config.xml
On Windows the TensorFlow C headers and lib (.lib and dll) are located at $NEW_PATH/test_wrapc/wrapc_tensorflow/library/C
if you create it as describe previously, in other case change it, if you use a different path.
WrapC execution
wrap_c --verbose --c_compile_options=-I/usr/local/include/tensorflow/c --output-dir=./generated_wrapper --full-header=/usr/local/include/tensorflow/c/c_api.h --config=config.xml
[100%] phase 1: parsing
parsed successfuly
found:
. 424 types
. 178 declarations
[100%] phase 2: resolving aliases for anonymous types
[100%] phase 3: wrapping declarations
[100%] phase 3: selecting types to wrap
[100%] phase 3: wrapping types
found:
. 0 enum wrappers
. 0 struct wrappers
. 0 union wrappers
. 1 function wrappers
. 0 callback wrappers
[100%] phase 4: generating function wrappers
phase 5: generating Makefiles and WrapC header
Linux
Finally, for this library, we need to update the generated MakeFile.SH
file located at /generated_wrapper/c/src
, to fix the CFLAGS
line, since the MakeFile generation code takes the name of the config file and tries to include the headers using pkg-config
. In the future, WrapC code should be improved to handle this issue. As a workaround, we can use the post_processing
facilities to automate this process. Check understanding wrapc to learn more about the command-line options.
For this tutorial, we will first update the file manually, and then we will use the post_processing
command-line option, to automate the process.
Linux: Makefile.SH update
CFLAGS = $optimize $ccflags $large -I$rt_include -I../../../generated_wrapper/c/include -I../include `pkg-config --cflags libtensorflow` -I../../../C/include
With
CFLAGS = $optimize $ccflags $large -I$rt_include -I/usr/local/include/tensorflow/c -I../../../generated_wrapper/c/include -I../include -I../../../C/include
Since we will need to run this command multiple times during the wrapping process we save a copy of this file at the library root ($NEW_PATH/test_wrapc/wrapc_tensorflow/library
) and we will create a script to be called at the post_processing step.
post_script.sh #!/bin/sh
# Post processing script
#copy Makefile
cp Makefile.SH ./generated_wrapper/c/src
Be sure to execute chmod +x post_script.sh
to set executable permissions.
Linux: WrapC command line update
wrap_c --verbose --c_compile_options=-I/usr/local/include/tensorflow/c --script_post_process=post_script.sh --output-dir=./generated_wrapper --full-header=/usr/local/include/tensorflow/c/c_api.h --config=config.xml
So now, we are able to execute the wrapping process without the need to manually fix the Makefile.SH.
Compile C lib
Finally, to compile the C lib, go to /generated_wrapper/c/src
and run finish_freezing --library
It will copy the C lib eif_libtensorflow.a
under the library root $ECF_CONFIG_PATH/C/spec/$(ISE_PLATFORM)/lib/eif_libtensorflow.a
Automate the process
Before we start, all the scripts that we will need will be created at $NEW_PATH/test_wrapc/wrapc_tensorflow/library
.As you can see this process will be repeated multiple times during the development, so we will create a new pre-processing script pre_script
to delete the generated code. Update the script post_script
on Linux and create a new one on Windows, so it can also compile the C lib and finally, we will add a new script generator
to call the wrap_c tool.
First, let's create the pre-processing scripts for Linux and Windows
Linux: pre_script.sh
#!/bin/sh
# Pre processing script
#Delete generated code
echo Removing generated code.
rm -r generated_wrapper
cd C/
rm -r spec
Windows: pre_script.bat
@echo on
title pre_process script
echo Removing generated code.
set current_dir = %~dp0
rd /s /q generated_wrapper
cd %current_dir%C/
rd /s /q spec
cd ..
Linux: Update thepost_script.sh
to compile the C lib
#!/bin/sh
# Post processing script
#copy Makefile
echo Copy MakeFile
cp Makefile.SH ./generated_wrapper/c/src
cd generated_wrapper/c/src/
finish_freezing -library
Windows: Createpost_script.bat
to compile the C lib
@echo on
title post_process script
cd generated_wrapper/c/src/
finish_freezing -library
Finally, we add the generator
script to automate the process
Linux: generator.sh
#!/bin/sh
#Script to automate WrapC development process.
wrap_c --verbose --c_compile_options=-I/usr/local/include/tensorflow/c --script_pre_process=pre_script.sh --script_post_process=post_script.sh --output-dir=./generated_wrapper --full-header=/usr/local/include/tensorflow/c/c_api.h --config=config.xml
Windows: generator.bat
@echo on
title Script to automate WrapC development process.
wrap_c --verbose --script_pre_process=pre_script.bat --script_post_process=post_script.bat --output-dir=%cd%/generated_wrapper --full-header=%cd%/C/include/tensorflow/c/c_api.h --config=%cd%/config.xm
On Linux be sure to execute chmod +x
to set executable permissions to the new scripts. Now we only need to execute the script generator
, every time we change our wrap_c configuratin file config.xml
.
Example using our TensorFlow Library
Create a new project and include the TensorFlow
library that we just wrapped in the previous section, the first example will show the TensorFlow version, as we saw previously with the C example.
First, update the APPLICATION
class as following
class APPLICATION
create
make
feature -- Initialization
make
do
print ("%NTensorFlow Version: " + (create {C_STRING}.make_by_pointer ({C_API_FUNCTIONS_API}.tf_version)).string )
end
end
Now execute it, it will display the TensorFlow library version in the command line
TensorFlow Version: 1.14.0
In this case, as you can see, our wrapper is too low level and we need to create a STRING from a pointer.
class C_API_FUNCTIONS_API
feature -- Access
tf_version: POINTER
external
"C inline use <c_api.h>"
alias
"[
return TF_Version ();
]"
end
end
Manual Wrapping
During the wrapping process we will need to do some manual wrapping to create high-level classes to access the low-level wrapper, or just to fix or wrap things that WrapC can't do.
In our previous example when we want to print the TensorFlow version we need to create a STRING
from a C pointer, so instead of doing this all the time, we can create a new class and create a reusable feature.
We will create a new class TENSORFLOW_API
under the manual_wrapper
cluster as you can see in the following image.
To avoid repeating the following piece of code, this is a very simple case, but the idea is to create reusable code and hide low-level details.
my_string := create {C_STRING}.make_by_pointer ({C_API_FUNCTIONS_API}.tf_version)).string
Here we create a manual class TENSORFLOW_API
to have high-level features using the low-level wrapper class C_API_FUNCTIONS_API
. In this particular example, I decided to use inheritance
.
class
TENSORFLOW_API
inherit
C_API_FUNCTIONS_API
feature -- Access
version: STRING
-- Tensorflow version
do
if tf_version /= default_pointer then
Result := (create {C_STRING}.make_by_pointer (tf_version)).string
else
Result := "Unkown"
end
end
end
Now in our example, we can use our new class as follow
class APPLICATION
inherit
TENSORFLOW_API
create
make
feature -- Initialization
make
do
print ("%NTensorFlow Version: " + version)
end
end
Updating the configuration file to wrap TensorFlow
We will start updating our configuration, let's start with functions
TensforFlow functions
We will add the following rule to groups all TensorFlow functions in a uniform class name TENSORFLOW_FUNCTIONS
<!-- Include all TF functions in a uniform class-->
<rule>
<match>
<identifier name="TF_.*"/>
<type name="function"/>
</match>
<wrapper type="default">
<class_name name="TENSORFLOW_FUNCTIONS"/>
</wrapper>
</rule>
type
: Usingtype
in thematch
clause to constrains the wrapping to functions.-
class_name
: Usingclass_name
in thewrapper
clause to group all functions in a uniform class. This option is only valid forfunctions
andmacros
.
Adding this rule will also wrap dependent types structs
and enums
.
In this case, it is not needed to add anything else to wrap structs
and enums
, but if needed, add a rule to wrap structs
or enums
types filtered by a set of elements you want to wrap identifier
for that given type.
TensforFlow callbacks
Here we will add a rule to wrap C callbacks, the TensorFlow C API defines a few callbacks, and all of them are anonymous.
TF_CAPI_EXPORT extern TF_Tensor* TF_NewTensor(
TF_DataType, const int64_t* dims, int num_dims, void* data, size_t len,
void (*deallocator)(void* data, size_t len, void* arg),
void* deallocator_arg);
Initially, we should try with the following rules
<!-- Include all TF callbacks -->
<rule>
<match>
<identifier name=".*"/>
<type name="callaback"/>
</match>
<wrapper type="default">
</wrapper>
</rule>
If this does not work, we can use a generic way
<!-- Include all TF callbacks -->
<rule>
<match>
<type name="any"/>
</match>
<wrapper type="default">
</wrapper>
</rule>
Our final configuration file will look like this
<?xml version="1.0"?>
<ewg_config name="libtensorflow">
<rule_list>
<!-- Ignore everything that starts with an underscore. -->
<rule>
<match>
<identifier name="_.*"/>
</match>
<wrapper type="none">
</wrapper>
</rule>
<!-- Include all TF functions in a uniform class-->
<rule>
<match>
<identifier name="TF_.*"/>
<type name="function"/>
</match>
<wrapper type="default">
<class_name name="TENSORFLOW_FUNCTIONS"/>
</wrapper>
</rule>
<!-- Include all TF callbacks -->
<rule>
<match>
<type name="any"/>
</match>
<wrapper type="default">
</wrapper>
</rule>
<!-- And the rest should be ignored -->
<rule>
<match>
</match>
<wrapper type="none">
</wrapper>
</rule>
</rule_list>
</ewg_config>