Navigating nimble objects to find internal information

NIMBLE compilation works by creating a series of information-rich objects. These are mostly implemented using R reference classes, so each object can have access to other ones without high copying overhead. This means finding what you need from the internals of these objects can take some exploration. This page outlines the objects created in compilation of a nimbleFunction, what their conceptual role is and what objects they contain.

library(nimble)

First here is an overview of the objects and concepts they represent

  1. nimbleFunction generator: This is returned from nimbleFunction. When called, it runs the setup function and returns a specialized nimbleFunction.
  2. specialized nimbleFunction, also called nimbleFunction object: This is the uncompiled working version of the nimbleFunction.
  3. nfProcessing object (often called nfProc, or nimCompProc [the latter name is used when it could alternatively be of class nlProcessing for nimbleLists]): This manages the compilation process up to where we are ready to generate C++. Two essential fields include:
    1. RCfunProcs: A list of RCfunProcessing objects: Each of these objects manages compilation process for one method up to the point before generating C++.
    2. Hierarchy of symbolTable objects: Each RCfunProcessing object has a local symbolTable, which all have the same parent symbolTable (symTab) that includes class variables.
  4. cppDef object: This manages the generation of C++ for the nfProcessing object along with SEXP functions for generation, finalization (R’s name for destruction) and interface functions.
    1. By a “SEXP” function, we mean a function that takes and returns C++ SEXP objects. SEXP is the C++ class for an R object.
  5. Interface object: This represents the compiled nimbleFunction to the user. We make two kinds:

    1. full interface: This presents the same interface as the uncompiled specialized nimbleFunction but uses the instantiated compiled object internally.
    2. multi-interface: This manages many C++ objects of the same class in a single object. Access to member data and methods is possible but more cumbersome.

The process of running through these steps, including the actual instantiation of a C++ object and population of its member data, is managed by a nimbleProjectClass object. The role of the nimbleProject is to keep track of everything compiled as part of one project. (They need not be compiled at the same time, but they need to interact such as calling each other and hence need access to compilation information.) They could be compiled in different steps, in which case the nimbleProject makes sure headers are found as needed and already compiled objects are found and used by newly compiled objects. The nimbleProject also serves as a hub to find information about any relevant object from any other object.

nimbleFunction generator

A nimbleFunction generator is created by a call to nimbleFunction that has setup code. (If there is no setup code, the nimbleFunction is compiled more simply as an RCfunction. If you don’t want it to be an RCfunction but don’t need any meaningful setup processing, use setup = TRUE or setup = function(){}.

nfGen <- nimbleFunction(
      setup = function(){
        message('hello world from setup code')
        x <- rnorm(3)
      },
      run = function(y = double(1)){
        print('hello world from run')
        z <- x + y[1:3]
        return(z)
        returnType(double(1))
      }
      )

Let us look at the object returned. It is a function that contains the setup code and some other code for copying setup outputs into its nfObject. The best way see the information is:

ls(environment(nfGen))
##  [1] "check"                    "className"               
##  [3] "contains"                 "declaredSetupOutputNames"
##  [5] "generatorFunction"        "globalSetup"             
##  [7] "methodList"               "methods"                 
##  [9] "name"                     "nfRefClass"              
## [11] "nfRefClassDef"            "run"                     
## [13] "setup"                    "virtual"                 
## [15] "where"

A utility for accessing objects in a function’s closure is:

nimble:::getFunctionEnvVar(nfGen, 'className')
## [1] "nfRefClass_R_GlobalEnv84"
## className is the unique name for this nfGen
## It is generated uniquely or can be provided to nimbleFunction

## Can check is something is a nimbleFunction generator:
nimble:::is.nfGenerator(nfGen)
## [1] TRUE

And of course we can look at the code-generated function itself:

nfGen
## function () 
## {
##     {
##         message("hello world from setup code")
##         x <- rnorm(3)
##     }
##     nfRefClassObject <- nfRefClass()
##     nfRefClassObject$.generatorFunction <- generatorFunction
##     if (!nimbleOptions()$compileOnly) 
##         for (.var_unique_name_1415927 in .namesToCopyFromGlobalSetup) {
##             nfRefClassObject[[.var_unique_name_1415927]] <- nf_preProcessMemberDataObject(get(.var_unique_name_1415927, 
##                 envir = .globalSetupEnv))
##         }
##     for (.var_unique_name_1415927 in .namesToCopyFromSetup) {
##         nfRefClassObject[[.var_unique_name_1415927]] <- nf_preProcessMemberDataObject(get(.var_unique_name_1415927))
##     }
##     return(nfRefClassObject)
## }
## <environment: 0x5477050>

This shows that first any setup code is executed. Then an object of nfRefClass (a class definition in nfGen’s closure) is created. Then member data are populated.

Specialized nimbleFunction, also called a nimbleFunction object

A specialized nimbleFunction is created by running the generator, which takes arguments to the setup function.

nf1 <- nfGen()
## hello world from setup code

This is an object of the reference class className, whose generator is called nfRefClass in the closure of the nimbleFunction generator.

class(nf1)
## [1] "nfRefClass_R_GlobalEnv84"
## attr(,"package")
## [1] ".GlobalEnv"
ls(nf1, all = TRUE) ## includes "." names, normally hidden
## [1] ".CobjectInterface"       ".generatorFunction"     
## [3] "initFields"              "initialize"             
## [5] ".newSetupLinesProcessed" ".refClassDef"           
## [7] ".self"                   "x"
nf1$run
## Class method definition for method run()
## function (y) 
## {
##     nimPrint("hello world from run")
##     z <- x + y[1:3]
##     return(z)
## }
## <environment: 0x66da4b8>
inherits(nf1, 'nimbleFunctionBase')
## [1] TRUE
## Can check if something is a specialized nimbleFunction
nimble:::is.nf(nf1)
## [1] TRUE

Notice that x is a “setupOutput”, a variable created or passed to setup that is used in run (or any method). It has become a field in nfRefClass. The utility nf_getSetupOutputNames finds setupOutput names.

The (dynamically generated) class of nf1 inherits from nimbleFunctionBase. It allows access to its generator, which is useful for looking up setupOutputs etc:

nf1$.generatorFunction ## not shown
## Or
nimble:::nf_getGeneratorFunction(nf1) ## not shown
nimble:::getFunctionEnvVar(nimble:::nf_getGeneratorFunction(nf1), 'className')
## [1] "nfRefClass_R_GlobalEnv84"

And of course we can use it

nf1$x
## [1] -1.157805 -1.702539 -2.543283
z <- 1:3
nf1$run(z)
## hello world from run
## [1] -0.1578050  0.2974611  0.4567169

Compile the nimbleFunction so we can look at contents from various steps along the way

cnf1 <- compileNimble(nf1) ## add dirName = '.' if you want to look at generated C++ locally.  Otherwise it is in tempdir()
## compiling... this may take a minute. Use 'showCompilerOutput = TRUE' to see C++ compiler details.
## compilation finished.
cnf1$run(z)
## hello world from run
## [1] -0.1578050  0.2974611  0.4567169

nfProcessing object

The nfProcessing object is initialized from a list of nimbleFunction objects from the same generator. Some of its member data include:

  1. instances: A list of the nfRefClass objects of nimbleFunctions objects used for initialization. These are used to inspect the type of setupOutputs. setupOutputs are objects in the evaluation environment of each nimbleFunction object that will become member data of the C++ class.
  2. setupSymTab: A symbolTable object with setupOutput symbols.
  3. neededTypes: A list of symbolTable entries of other types needed to successfully compiled this nimbleFunction. E.g. this might contain other nimbleFunctions, or modelValues of nimbleLists.
  4. neededObjectNames: A list of names of setupOutput objects that will need to be instantiated separately. This would include nimbleFunctions, modelValues or nimbleList objects. E.g. nfA creates nfB in nfA’s setup function. Eventually, when we have an instantiated C++ object for nfA, its member data will include a pointer to an nfB object. To make the compiled nfA object complete, we need to instantiate a compiled nfB object and point nfA’s pointer to it.
  5. newSetupOutputNames: These are additional setupOutputs created by keyword processing (aka partial evaluation).
  6. newSetupCode: This is code to be evaluated in the environment of the setup code for each nimbleFunction object. This code is generated from keyword processing to create the objects named in newSetupOutputNames.
  7. nfGenerator: The nfGenerator used for the nimbleFunction objects.
  8. RCfunProcs: A list of RCfunProcessing objects, one for each method.
  9. compileInfos: This is a list of the RCfunctionCompileClass objects, one from each RCfunProcessing object. They are collected into a list for use by the nfProcessing object only for convenience (they could be accessed through the RCfunProcs).

Accessing the nfProcessing object

There are several ways to get hold of the nfProcessing object (after something has been compiled).

Via the nimbleProject

nf1Project <- getNimbleProject(cnf1)
nf1Project
## nimbleProject object
## getNimbleProject(nf1) ## also works
nf1NFproc <- nf1Project$getNimbleFunctionNFproc(nf1)
nf1NFproc
## nfProcessing object nfRefClass_R_GlobalEnv84

Via the internals of the compiled nimbleFunction interface

nf1NFproc <- cnf1$compiledNodeFun$nimCompProc
nf1NFproc ## same object as obtained above
## nfProcessing object nfRefClass_R_GlobalEnv84

Via the uncompiled nimbleFunction

nf1$.CobjectInterface ## this is the same as cnf1
## Derived CnimbleFunctionBase object (compiled nimbleFunction) for nimbleFunction with class nfRefClass_R_GlobalEnv84
identical(nf1$.CobjectInterface, cnf1)
## [1] TRUE
## hence it can be used as above
nf1$.CobjectInterface$compiledNodeFun$nimCompProc
## nfProcessing object nfRefClass_R_GlobalEnv84

Here is a brief tour of some of the contents of the “nfProc” as it is often called in the code.

ls(nf1NFproc)
##  [1] "addBaseClassTypes"               "addMemberFunctionsToSymbolTable"
##  [3] "blockFromCppNames"               "collectRCfunNeededTypes"        
##  [5] "compileInfos"                    "cppDef"                         
##  [7] "doRCfunProcess"                  "doSetupTypeInference"           
##  [9] "doSetupTypeInference_processNF"  "evalNewSetupLines"              
## [11] "evalNewSetupLinesOneInstance"    "getSymbolTable"                 
## [13] "initialize"                      "initialize#virtualNFprocessing" 
## [15] "inModel"                         "instances"                      
## [17] "makeNewSetupLinesOneExpr"        "makeTypeObject"                 
## [19] "matchKeywords_all"               "name"                           
## [21] "neededObjectNames"               "neededTypes"                    
## [23] "newSetupCode"                    "newSetupCodeOneExpr"            
## [25] "newSetupOutputNames"             "nfGenerator"                    
## [27] "nimbleProject"                   "origMethods"                    
## [29] "process"                         "processKeywords_all"            
## [31] "RCfunProcs"                      "setupLocalSymbolTables"         
## [33] "setupSymTab"                     "setupTypesForUsingFunction"     
## [35] "show"
nf1NFproc$name ## unique name, can be provided or generated
## [1] "nfRefClass_R_GlobalEnv84"
nf1NFproc$setupSymTab$getSymbolNames()
## [1] "x"   "run"
nf1NFproc$setupSymTab$getSymbolObject('x')
## x: double sizes = (NA), nDim = 1
nf1NFproc$setupSymTab$getSymbolObject('run')
## symbolMemberFunction run
nf1NFproc$neededTypes ## nothing in this case
## list()
nf1NFproc$origMethods[['run']] ## This nfMethodRC object has information on the RCfunction for run
## Reference class object of class "nfMethodRC"
## Field "argInfo":
## $y
## double(1)
## 
## Field "arguments":
## $y
## 
## 
## Field "returnType":
## double(1)
## Field "uniqueName":
## character(0)
## Field "template":
## function (y) 
## {
## }
## <environment: 0x554d300>
## Field "code":
## {
##     nimPrint("hello world from run")
##     z <- x + y[1:3]
##     return(z)
## }
## Field "neededRCfuns":
## list()
##nf1NFproc$nfGenerator ## has access to the nfGenerator
class(nf1NFproc$RCfunProcs[['run']]) ## An RCfunProcessing object for run. See below
## [1] "RCfunProcessing"
## attr(,"package")
## [1] "nimble"
class(nf1NFproc$compileInfos[['run']]) ## This RCfunctionCompileClass object is copied from a compileInfo of the RCfunProcessing objects for convenience:
## [1] "RCfunctionCompileClass"
## attr(,"package")
## [1] "nimble"
identical(nf1NFproc$RCfunProcs[['run']]$compileInfo, nf1NFproc$compileInfos[['run']])
## [1] TRUE
nf1NFproc$newSetupCode ## code for partial evaluation or other steps created by keyword processing (empty here)
## list()

RCfunProcessing objects

Each RCfunProcessing object (in RCfunProcs of nfProcessing) is initialized from a single method of an nfProcessing object. RCfunProcessing objects are also used for simple RCfunctions (no setup code, hence a standalone function rather than a method of a nimbleFunction class).

These objects manage the heart of the compilation process: annotating and transforming the abstract syntax tree of each method and building up a symbolTable. Some of these steps are described here.

Here is a brief tour of the some of the contents of an RCfunProcessing object

runRCfunProc <- nf1NFproc$RCfunProcs[['run']]
ls(runRCfunProc)
##  [1] "compileInfo"       "const"             "initialize"       
##  [4] "matchKeywords"     "name"              "nameSubList"      
##  [7] "neededRCfuns"      "process"           "processKeywords"  
## [10] "RCfun"             "setupSymbolTables"
runRCfunProc$neededRCfuns ## list of any RCfunProcs needed for compilation of this one to succeed
## list()
class(runRCfunProc$RCfun) ## see below
## [1] "nfMethodRC"
## attr(,"package")
## [1] "nimble"
class(runRCfunProc$compileInfo) ## see below
## [1] "RCfunctionCompileClass"
## attr(,"package")
## [1] "nimble"

nfMethodRC

The nfMethodRC class has the extracted/expanded contents of an RCfunction (stand-alone, or class method)

runNFmethodRC <- runRCfunProc$RCfun
ls(runNFmethodRC)
##  [1] "argInfo"                "arguments"             
##  [3] "code"                   "field"                 
##  [5] "generateArgs"           "generateFunctionObject"
##  [7] "generateTemplate"       "initialize"            
##  [9] "neededRCfuns"           "removeAndSetReturnType"
## [11] "returnType"             "show"                  
## [13] "template"               "uniqueName"
runNFmethodRC$arguments
## $y
runNFmethodRC$argInfo
## $y
## double(1)
runNFmethodRC$code
## {
##     nimPrint("hello world from run")
##     z <- x + y[1:3]
##     return(z)
## }
runNFmethodRC$returnType
## double(1)

RCfunctionCompileClass objects

The RCfunctionCompileClass contains the guts of the results of compilation: the annotated AST and related information.

runCompileInfo <- runRCfunProc$compileInfo
ls(runCompileInfo)
## [1] "initFields"      "initialize"      "newLocalSymTab"  "newRcode"       
## [5] "nimExpr"         "origLocalSymTab" "origRcode"       "returnSymbol"   
## [9] "typeEnv"
runCompileInfo$nimExpr ## This is the final code.  It is not actually ready for C++ code generation because we need a C++ version of the symbol table first.  This is managed by a cppDef (in this case a cppDef_nimbleFunction and the RCfunctionDef(s) it contains) object.
## {
##   {
##     nimPrint
##       hello world from run
##   }
##   {
##     {
##       {
##         if
##           !=
##             [
##               dim
##                 x
##               1
##             3
##           {
##             {
##               nimPrint
##                 Run-time size error: expected dim(x)[1L] == 3
##             }
##           }
##       }
##       {
##         setSize
##           z
##           3
##           0
##           0
##       }
##       {
##         {
##           {
##             AssignEigenMap
##               Eig_z
##               getPtr
##                 z
##               MatrixXd
##               3
##               1
##           }
##           {
##             AssignEigenMap
##               Eig_x
##               getPtr
##                 x
##               MatrixXd
##               [
##                 dim
##                   x
##                 1
##               1
##           }
##           {
##             AssignEigenMap
##               Eig_ARG1_y_Interm_1
##               +
##                 getPtr
##                   ARG1_y_
##                 chainedCall
##                   template
##                     static_cast
##                     int
##                   +
##                     getOffset
##                       ARG1_y_
##                     chainedCall
##                       template
##                         static_cast
##                         int
##                       0
##               MatrixXd
##               [
##                 dim
##                   ARG1_y_
##                 1
##               1
##               0
##               [
##                 strides
##                   ARG1_y_
##                 1
##           }
##           {
##             <-
##               Eig_z
##               +
##                 Eig_x
##                 eigenBlock
##                   Eig_ARG1_y_Interm_1
##                   0
##                   0
##                   3
##                   1
##           }
##         }
##       }
##     }
##   }
##   {
##     return
##       z
##   }
## }
## more readable: (note that extraneous {}s are inserted during various processing steps and will be omitted from final generated code)
nimble:::writeCode(nimble:::nimDeparse(runCompileInfo$nimExpr))
## {
##     {
##       nimPrint("hello world from run")
##     }
##     {
##           {
##                   {
##                             if(dim(x)[1] != 3) {
##                                 {
##                                   nimPrint("Run-time size error: expected dim(x)[1L] == 3")
##                                 }
##                             }
##                   }
##                   {
##                     setSize(z,3,0,0)
##                   }
##                   {
##                             {
##                                         {
##                                           AssignEigenMap(Eig_z,getPtr(z),MatrixXd,3,1)
##                                         }
##                                         {
##                                           AssignEigenMap(Eig_x,getPtr(x),MatrixXd,dim(x)[1],1)
##                                         }
##                                         {
##                                           AssignEigenMap(Eig_ARG1_y_Interm_1,getPtr(ARG1_y_) + template(static_cast,int)(getOffset(ARG1_y_) + template(static_cast,int)(0)),MatrixXd,dim(ARG1_y_)[1],1,0,strides(ARG1_y_)[1])
##                                         }
##                                         {
##                                           Eig_z <- Eig_x + eigenBlock(Eig_ARG1_y_Interm_1,0,0,3,1)
##                                         }
##                             }
##                   }
##           }
##     }
##     {
##       return(z)
##     }
## }
## example of annotations
ls(runCompileInfo$typeEnv, all = TRUE)
## [1] ".AllowUnknowns"      "ARG1_y_"             ".ensureNimbleBlocks"
## [4] "neededRCfuns"        "passedArgumentNames" "return"             
## [7] "x"                   "z"
runCompileInfo$origRcode
## {
##     nimPrint("hello world from run")
##     z <- x + y[1:3]
##     return(z)
## }
runCompileInfo$newRcode
## {
##     nimPrint("hello world from run")
##     z <- x + ARG1_y_[1:3]
##     return(z)
## }
runCompileInfo$origLocalSymTab$getSymbolNames()
## [1] "ARG1_y_"
runCompileInfo$newLocalSymTab$getSymbolNames()
## [1] "ARG1_y_"             "z"                   "Eig_z"              
## [4] "Eig_x"               "Eig_ARG1_y_Interm_1"
runCompileInfo$newLocalSymTab$getParentST() ## parent symbol table
## symbol table:
## x: double sizes = (NA), nDim = 1
## symbolMemberFunction run

cppDef object

NIMBLE has a hierarchy of classes to represent C++ code such as namespaces, class definitions, function definitions, and derived classes for NIMBLE-specific versions of these, such as NamedObject classes. (NamedObject classes all inherit from a common base class that provides a single way to access member data pointers from string keys from R.) These class names mostly begin with “cppDef” (RCfunctionDef is an exception – perhaps should be renamed) so we refer to a “cppDef” object as a generic term.

Here is a brief tour of the cppDef_nimbleFunction objects for this example:

nf1CppDef <- nf1NFproc$cppDef
nimble:::writeCode(nf1CppDef$generate()) ## Here is the C++ declaration
## class nfRefClass_R_GlobalEnv84 : public NamedObjects {
## public:
##   NimArr<1, double> x;
## NimArr<1, double>  run ( NimArr<1, double> & ARG1_y_ );
##  nfRefClass_R_GlobalEnv84 (  );
## };
nimble:::writeCode(nf1CppDef$generate(declaration = FALSE)) ## And the definition
## NimArr<1, double>  nfRefClass_R_GlobalEnv84::run ( NimArr<1, double> & ARG1_y_ )  {
## NimArr<1, double> z;
## Map<MatrixXd> Eig_z(0,0,0);
## Map<MatrixXd> Eig_x(0,0,0);
## EigenMapStrd Eig_ARG1_y_Interm_1(0,0,0, EigStrDyn(0, 0));
## _nimble_global_output <<"hello world from run"<<"\n"; nimble_print_to_R(_nimble_global_output);
## if(x.dim()[0] != 3) {
##  _nimble_global_output <<"Run-time size error: expected dim(x)[1L] == 3"<<"\n"; nimble_print_to_R(_nimble_global_output);
## }
## z.setSize(3, 0, 0);
## new (&Eig_z) Map< MatrixXd >(z.getPtr(),3,1);
## new (&Eig_x) Map< MatrixXd >(x.getPtr(),x.dim()[0],1);
## new (&Eig_ARG1_y_Interm_1) EigenMapStrd(ARG1_y_.getPtr() + static_cast<int>(ARG1_y_.getOffset() + static_cast<int>(0)),ARG1_y_.dim()[0],1,EigStrDyn(0, ARG1_y_.strides()[0]));
## Eig_z = Eig_x + (Eig_ARG1_y_Interm_1).block(0, 0, 3, 1);
## return(z);
## }
##  nfRefClass_R_GlobalEnv84::nfRefClass_R_GlobalEnv84 (  )  {
## namedObjects["x"]=&x;
## }
nf1CppDef$nfProc ## can get back to the nfProcessing object
## nfProcessing object nfRefClass_R_GlobalEnv84
nf1CppDef$objectDefs$getSymbolNames() ## a symbol table of C++ symbols
## [1] "x"
nf1CppDef$objectDefs$getSymbolObject('x')$generate()
## [1] "NimArr<1, double> x"
class(nf1CppDef$Rgenerator) ## a function to instantiate objects and return a "full" R interface object
## [1] "function"
class(nf1CppDef$CmultiInterface) ## a class to instantiate objects and manage all via a single "multiInterface" object
## [1] "CmultiNimbleFunctionClass"
## attr(,"package")
## [1] "nimble"
names(nf1CppDef$functionDefs) ## all member functions, 
## [1] "run"         "constructor"
names(nf1CppDef$RCfunDefs) ## member RCfunctions
## [1] "run"

Here is a brief tour of the cppDef_RCfunction object for the run method for this example:

runCppDef <- nf1CppDef$RCfunDefs[['run']]
class(runCppDef)
## [1] "RCfunctionDef"
## attr(,"package")
## [1] "nimble"
ls(runCppDef)
##  [1] "abstract"                  "args"                     
##  [3] "buildFunction"             "buildRwrapperFunCode"     
##  [5] "buildSEXPinterfaceFun"     "code"                     
##  [7] "const"                     "CPPincludes"              
##  [9] "CPPusings"                 "externC"                  
## [11] "filename"                  "generate"                 
## [13] "getCPPincludes"            "getCPPusings"             
## [15] "getHincludes"              "Hincludes"                
## [17] "initFields"                "initialize"               
## [19] "initialize#cppDefinition"  "initialize#cppFunctionDef"
## [21] "name"                      "neededTypeDefs"           
## [23] "nimbleProject"             "RCfunProc"                
## [25] "returnType"                "SEXPinterfaceCname"       
## [27] "SEXPinterfaceFun"          "virtual"
nimble:::writeCode(runCppDef$generate()) ## definition just for this method
## NimArr<1, double>  run ( NimArr<1, double> & ARG1_y_ )  {
## NimArr<1, double> z;
## Map<MatrixXd> Eig_z(0,0,0);
## Map<MatrixXd> Eig_x(0,0,0);
## EigenMapStrd Eig_ARG1_y_Interm_1(0,0,0, EigStrDyn(0, 0));
## _nimble_global_output <<"hello world from run"<<"\n"; nimble_print_to_R(_nimble_global_output);
## if(x.dim()[0] != 3) {
##  _nimble_global_output <<"Run-time size error: expected dim(x)[1L] == 3"<<"\n"; nimble_print_to_R(_nimble_global_output);
## }
## z.setSize(3, 0, 0);
## new (&Eig_z) Map< MatrixXd >(z.getPtr(),3,1);
## new (&Eig_x) Map< MatrixXd >(x.getPtr(),x.dim()[0],1);
## new (&Eig_ARG1_y_Interm_1) EigenMapStrd(ARG1_y_.getPtr() + static_cast<int>(ARG1_y_.getOffset() + static_cast<int>(0)),ARG1_y_.dim()[0],1,EigStrDyn(0, ARG1_y_.strides()[0]));
## Eig_z = Eig_x + (Eig_ARG1_y_Interm_1).block(0, 0, 3, 1);
## return(z);
## }
nimble:::writeCode(runCppDef$generate(declaration = TRUE)) ## declaration just for this method
## NimArr<1, double>  run ( NimArr<1, double> & ARG1_y_ );
nimble:::writeCode(runCppDef$SEXPinterfaceFun$generate()) ## contains an cppFunctionDef for the SEXP interface
## SEXP  CALL_nfRefClass_R_GlobalEnv84_run ( SEXP S_ARG1_y_, SEXP SextPtrToObject )  {
## NimArr<1, double> ARG1_y_;
## SEXP S_returnValue_1234;
## NimArr<1, double> LHSvar_1234;
## SEXP S_returnValue_LIST_1234;
## SEXP_2_NimArr<1>(S_ARG1_y_, ARG1_y_);
## GetRNGstate();
## LHSvar_1234 = static_cast<nfRefClass_R_GlobalEnv84*>(R_ExternalPtrAddr(SextPtrToObject))->run(ARG1_y_);
## PutRNGstate();
## PROTECT(S_returnValue_LIST_1234 = allocVector(VECSXP, 2));
## PROTECT(S_returnValue_1234 = NimArr_2_SEXP<1>(LHSvar_1234));
## PROTECT(S_ARG1_y_ = NimArr_2_SEXP<1>(ARG1_y_));
## SET_VECTOR_ELT(S_returnValue_LIST_1234, 0, S_ARG1_y_);
## SET_VECTOR_ELT(S_returnValue_LIST_1234, 1, S_returnValue_1234);
## UNPROTECT(3);
## return(S_returnValue_LIST_1234);
## }
runCppDef$args$getSymbolNames() ## local C++ symbol table for arguments
## [1] "ARG1_y_"
class(runCppDef$code) ## actual body of the function
## [1] "cppCodeBlock"
## attr(,"package")
## [1] "nimble"
ls(runCppDef$code)
## [1] "code"         "generate"     "objectDefs"   "skipBrackets"
runCppDef$code$objectDefs$getSymbolNames() ## local variables
## [1] "z"                   "Eig_z"               "Eig_x"              
## [4] "Eig_ARG1_y_Interm_1"
runCppDef$code$code ## The processed AST, same as we saw from the RCfunProcessing object's compileInfo's nimExpr above
## {
##   {
##     nimPrint
##       hello world from run
##   }
##   {
##     {
##       {
##         if
##           !=
##             [
##               dim
##                 x
##               1
##             3
##           {
##             {
##               nimPrint
##                 Run-time size error: expected dim(x)[1L] == 3
##             }
##           }
##       }
##       {
##         setSize
##           z
##           3
##           0
##           0
##       }
##       {
##         {
##           {
##             AssignEigenMap
##               Eig_z
##               getPtr
##                 z
##               MatrixXd
##               3
##               1
##           }
##           {
##             AssignEigenMap
##               Eig_x
##               getPtr
##                 x
##               MatrixXd
##               [
##                 dim
##                   x
##                 1
##               1
##           }
##           {
##             AssignEigenMap
##               Eig_ARG1_y_Interm_1
##               +
##                 getPtr
##                   ARG1_y_
##                 chainedCall
##                   template
##                     static_cast
##                     int
##                   +
##                     getOffset
##                       ARG1_y_
##                     chainedCall
##                       template
##                         static_cast
##                         int
##                       0
##               MatrixXd
##               [
##                 dim
##                   ARG1_y_
##                 1
##               1
##               0
##               [
##                 strides
##                   ARG1_y_
##                 1
##           }
##           {
##             <-
##               Eig_z
##               +
##                 Eig_x
##                 eigenBlock
##                   Eig_ARG1_y_Interm_1
##                   0
##                   0
##                   3
##                   1
##           }
##         }
##       }
##     }
##   }
##   {
##     return
##       z
##   }
## }
## runCppDef$buildRwrapperFunCode ## function to build R code to call C++ methods, but not trivial to demo here

cppInterface object

The cppInterface object, in this case cnf1, lets a user call the compiled code.

cnf1
## Derived CnimbleFunctionBase object (compiled nimbleFunction) for nimbleFunction with class nfRefClass_R_GlobalEnv84
cnf1$run
## Class method definition for method run()
## function (y) 
## {
##     if (is.null(".basePtr")) 
##         stop("Object for calling this function is NULL (may have been cleared)")
##     ans <- .Call("CALL_nfRefClass_R_GlobalEnv84_run", y, .basePtr)
##     ans <- ans[[2]]
##     ans
## }
## <environment: 0x6427e48>
cnf1$x
## [1] -1.157805 -1.702539 -2.543283

The process of building this is fairly involved. In particular it uses active bindings for member data such as x.

full interfaces

The cnf1 is an example of a full interface.

multi-interfaces

The alternative is a mutli-interface. A single multi-interface object keeps track of arbitrarily many C++ instances of a compiled nimbleFunction. This saves time and memory relative to creating many full interfaces. These do not have active bindings for member data and hence cannot be used as easily as full interfaces. By default NIMBLE builds multi-interfaces for any nimbleFunction contained in another nimbleFunction and full interfaces otherwise. Hence an MCMC gets a full interface but its samplers get multi-interfaces.