Code Generation

Code generation from the Wolfram Language involves converting programs written in the Wolfram Language into other languages and then supporting them so that they can be executed. The Wolfram System compiler provides a system for code generation into the C language.

The CCodeGenerator Package

The CCodeGenerator package is a key component of code generation from the Wolfram Language. It provides a number of functions, which are described below, that make use of the Wolfram System compiler for generating C code.

Functions provided by the CCodeGenerator package.

To use the CCodeGenerator package you have to load it.

<<CCodeGenerator`

This is a compiled function to use to demonstrate code generation.

cFun=Compile[{{x}},x^2+Sin[x^2]];

The following generates a fragment of C code for the compiled function.

CCodeStringGenerate[cFun,"compute"]

The code fragment is designed so that it could be compiled into a library; it includes generic initialization, life cycle management for this function, and the actual body of the function.

An application can call this function by importing a header file and linking against a suitable code module such as a shared library. You can also generate the header by setting the "CodeTarget" option to "WolframRTLHeader" as shown below.

CCodeStringGenerate[cFun,"compute","CodeTarget"->"WolframRTLHeader"]

Generic Initialization

The initial section of a generated C includes generic C initialization. A sample is shown below.

#include "math.h"

#include "WolframRTL.h"

static WolframCompileLibrary_Functions funStructCompile;

static mbool initialize = 1;

WolframRTL.h is a header file that sets up various important definitions.

WolframCompileLibrary_Functions is a type that holds a collection of callback functions; it is an immutable object that holds no state. An instance of this, funStructCompile, is kept in the file.

initialize is an mbool variable, which is used by the specific initialization functions.

Life Cycle Management

Generated C includes a section for managing the life cycle of the code. This involves initialization and uninitialization specific to the particular function. A sample is shown below.

# include "compute.h"
 
 DLLEXPORT int Initialize_compute (WolframLibraryData libData)
{
 if ( initialize)
   {
    funStructCompile = libData -> compileLibraryFunctions;
    initialize = 0;
    }
   return 0;
 }

DLLEXPORT void Uninitialize_compute (WolframLibraryData libData)
{
 if ( ! initialize)
  {
   initialize = 1;
   }
 }

This includes the header file, generated by setting "CodeTarget" option to "WolframRTLHeader"; it also includes a function to initialize the structures, Initialize_compute, and one to uninitialize any state, Uninitialize_compute. The initialization function must be called before the main function is used.

The initialize and the uninitialize functions must be called with a WolframLibraryData argument. This can be created by the loading function.

The Function

The actual body of a function is shown below. Note that the name "compute" was set in the call to GenerateCCodeString.

DLLEXPORT int compute (WolframLibraryData libData, mreal A1, mreal *Res)
{
 mreal R0_0;
 mreal R0_1;
 mreal R0_2;
 mreal R0_3;
 R0_0 = A1;
 R0_1 = R0_0 * R0_0;
 R0_2 = sin (R0_1);
 R0_3 = R0_1 + R0_2;
 *Res = R0_3;
 funStructCompile -> WolframLibraryData_cleanUp (libData, 1);
 return 0;
 }

This shows how the function arguments take the input and also the result as a pointer. The actual return value is used to indicate an error. The function is declared with DLLEXPORT, a macro defined in WolframLibrary.h. This allows the function to be exported from a Windows DLL; on non-Windows platforms the macro has an empty definition.

The function also needs a WolframLibraryData argument. This can be created by the loading function.

Working with Generated Code

Code generated by the Wolfram Language needs various support files, which are included in the Wolfram System layout. This includes header files that contain declarations for building and libraries that contain the actual code needed to support generated functions.

Header file used by generated code, found in $InstallationDirectory/SystemFiles/IncludeFiles/C.

There are various runtime libraries to support the generated code for each platform.

Runtime library support for generated code on Windows platforms, found in $InstallationDirectory/SystemFiles/Libraries/$SystemID.

Runtime library support for generated code on Linux platforms, found in $InstallationDirectory/SystemFiles/Libraries/$SystemID.

Runtime library support for generated code on Macintosh platforms, found in $InstallationDirectory/SystemFiles/Libraries/$SystemID.

The full runtime libraries all have a dependency on other libraries such as the Intel MKL library, which is bundled with the Wolfram Language. This gives very fast execution speed, but it means that to use the library externally from the Wolfram Language, you have to arrange to use the MKL library.

The minimal libraries have fewer dependencies on other libraries. For example, they have no dependency on the Intel MKL library. This gives slower execution speed for computations that need to use MKL, such as matrix multiplications.

You can compile generated files using whatever tools you like. The documentation here will show how to use the Wolfram Language C Compiler Driver package. First, you have to load the packages.

Needs["CCodeGenerator`"];
Needs["CCompilerDriver`"];
Needs["SymbolicC`"];

This creates a temporary directory to hold files and the resulting executable.

outDir=CreateDirectory[]

This is some sample C code to test the generated code; it is written using SymbolicC. It calls a function compute passing in an argument and receives the result, which is then printed in output.

code=
CProgram[
CInclude["stdio.h"],
CInclude["compute.h"],
CInclude["WolframRTL.h"],
CFunction["int","main",{{"int", "argc"}, {"char", CDereference[CArray["arg",{}]]}},
CBlock[{
CDeclare["double",CAssign["num1",20.4]],
CDeclare["double","num2"],
CDeclare["WolframLibraryData", CAssign["libData", CCall["WolframLibraryData_new",{"WolframLibraryVersion"}]]],
CCall["Initialize_compute",{"libData"}],
CCall["compute",{"libData","num1",CAddress["num2"]}],
CCall["printf",{CString["%5.2f\\n"],"num2"}],
CCall["WolframLibraryData_free",{"libData"}],
CReturn[0]}]]
];
cStr=ToCCodeString[code]

Notice how the calling function creates a WolframLibraryData object; this is used for the call to the initialize function and also in the call to the compute function.

The following writes the C code to an output file.

mainSource=Export[FileNameJoin[{outDir,"main.c"}],cStr,"Text"]

Here, a compiled function is created, which is then used to generate a C file and a header file. All the output goes into the output directory created earlier.

c=Compile[{{x}},x^2];
CCodeGenerate[c,"compute",FileNameJoin[{outDir,"compute.c"}],"CodeTarget"->"WolframRTL"];
CCodeGenerate[c,"compute",FileNameJoin[{outDir,"compute.h"}],"CodeTarget"->"WolframRTLHeader"];

This compiles all the source files into an executable. Notice how it links in the static minimal library; this is an easy way to set up this demonstration. On a different platform you would need to use the appropriate library.

exe=CreateExecutable[{mainSource,FileNameJoin[{outDir,"compute.c"}]}, "testgenerate", "IncludeDirectories"->outDir, "Libraries"->"WolframRTL_Static_Minimal",
"TargetDirectory"->outDir]

You can run the generated code in a shell; it will run the computation that started in the compiled function and print out the result.

You can also run the generated code from the Wolfram Language, using (RefLink[Import, paclet:ref/Import]) to read the result.

Import["!" <> exe, "Text"]

For your own code-generation purposes, you might wish to link your executable to one of the other runtime libraries.

Control Expressions

You can generate C code for certain control expressions such as (RefLink[StateSpaceModel, paclet:ref/StateSpaceModel]) expressions.

ss=StateSpaceModel[TransferFunctionModel[z/((z-0.9)*(z+0.8)),z,SamplingPeriod->1]]

The following makes an output directory to store the generated code.

outDir=CreateDirectory[]

Here, C code for the state space model is generated and stored in the file control.c.

file=FileNameJoin[{outDir,"control.c"}];
Export[ file,ss,"C"];

A header file was created as well as a C file.

FileNames["*",outDir]

You can compile this C code in an executable or a library with the (RefLinkPlain[CCompilerDriver, paclet:CCompilerDriver/tutorial/Overview]) package. First, it has to be loaded.

Needs["CCompilerDriver`"]

This creates a library for the control expression.

CreateLibrary[{file},
"control",
 "IncludeDirectories"->outDir, 
"TargetDirectory"->outDir]

The library can then be used in computations external to the Wolfram Language.

Scope of Generated Code

This section describes the scope of Wolfram Language computations that can be used with the code generator.

The code generator is based on the Wolfram System compiler. You must be using (RefLink[Compile, paclet:ref/Compile]) in order to make any progress with the code generator.

However, not all inputs processed by the compiler can be turned into generated code. This includes any external calls, which call back to the Wolfram Language. These cannot be used for code generation.