How to Expand Functionality

The wrapping features of Shroud are controlled by data files which are read in upon startup. This allows Shroud to wrap many types of arguments without any additional input from the user. However, there will always be situations that require some additional ability. This section describes the input data files and how a user may add to them.

Shroud can be thought of as a fancy macro processor. It reads the input and performs lots of redundant, tedious replacements. One function argument causes several layers of code to be generated which involve transformations of the input. To help manage the redundency, several layers of abstraction are provided.

The lowest layer is the typemap. It factors out some common fields for individual types. For example, most ‘native’ types such as int and double will produce identical statements except for things such as Fortran kind (C_INT, C_DOUBLE), intrinsic functions (int, real) and C types (int, double).

The next layer is the format dictionary. This is created for each argument for each language. Some values are computed from the argument description and attribute. Other values can be added directly by the user in the YAML file.

Finally, statements use format entries to generate code at the many locations required to create the wrappers.

Tutorial

This tutorial will work through how a statement group is developed for a Fortran wrapper. The function fetchCharPtr returns a pointer to a char array in one of the arguments.

options:
  debug: True
  wrap_c: False

declarations:
- decl: void fetchCharPtr(char **fetch1+intent(out))

Note the use of the debug option. This will write additional comments into the generated code that will list the statement groups used for each argument and the function result. Running Shroud will initially report that the statment group is not found.

Phase: FillMeta function
----------------------------------------
Node: fetchCharPtr
line 15
Unknown statement: f_out_char**_cdesc_pointer_library

The first step is to define the statement group by name. Since this is for a builtin statement group, it is added to shroud/fc-statements.json.

The group name is derived from the language, intent, type and defaults for the +api and +deref attributes.

The usage and notes sections are optional, but will help document the intended usage of the group.

{
    "name": "f_out_char**_cdesc_pointer_library",
    "usage": [
        "char **arg +intent(out)"
    ],
    "notes": [
        "Return a Fortran pointer to C memory."
    ]
}

Shroud will now generate wrapper in Three parts. The interface, the Fortran wrapper and the C wrapper.

interface
    ! ----------------------------------------
    ! Function:  void fetchCharPtr
    ! Statement: f_subroutine
    ! ----------------------------------------
    ! Argument:  char **fetch1 +intent(out)
    ! Statement: f_out_char**_cdesc_pointer_library
    subroutine fetch_char_ptr() &
            bind(C, name="TES_fetchCharPtr_bufferify")
        implicit none
    end subroutine fetch_char_ptr
end interface

Since there are no code required in the Fortran wrapper, only the interface is necessary. The debug option will show how the Fortran wrapper would be created, but it is conditionally compiled out.

#if 0
! Only the interface is needed
! ----------------------------------------
! Function:  void fetchCharPtr
! Statement: f_subroutine
! ----------------------------------------
! Argument:  char **fetch1 +intent(out)
! Statement: f_out_char**_cdesc_pointer_library
subroutine fetch_char_ptr()
    ! splicer begin function.fetch_char_ptr
    call c_fetch_char_ptr_bufferify(fetch1)
    ! splicer end function.fetch_char_ptr
end subroutine fetch_char_ptr
#endif
// ----------------------------------------
// Function:  void fetchCharPtr
// Statement: f_subroutine
// ----------------------------------------
// Argument:  char **fetch1 +intent(out)
// Statement: f_out_char**_cdesc_pointer_library
void TES_fetchCharPtr_bufferify(void)
{
    // splicer begin function.fetchCharPtr_bufferify
    fetchCharPtr();
    // splicer end function.fetchCharPtr_bufferify
}

While it is possible to define every field of the statement group, it is usually better to build up the group by using mixin groups. The mixin groups are also in fc-statements.json. These groups encapsulate parts of the wrapper that can be reused by many other groups.

"name": "f_out_char**_cdesc_pointer_library",
"usage": [
    "char **arg +intent(out)+deref(pointer)"
],
"notes": [
    "Return a Fortran pointer to C memory."
],
"mixin": [
    "f_mixin_dummy_arg",
    "f_mixin_cdesc_char_pointer",
    "f_mixin_cdesc_pass_to_cwrapper",
    "c_mixin_cdesc_fill_char",
    "c_mixin_local-cvar-ptr",
    "c_mixin_arg-call-cvar-address"
],

The command line option --write-statements will create a file with the final form of each statement group. The final statement group becomes:

  name: f_out_char**_cdesc_pointer_library
  intent: out
  comments:
  - Assign Fortran pointer from cdesc using helper pointer_string.
  - Pass cdesc as argument to C wrapper.
  - Fill cdesc from char * in the C wrapper.
  - Declare a local pointer variable in the C wrapper.
  - Pass address of cxx variable to library.
  notes:
  - Return a Fortran pointer to C memory.
  usage:
  - char **arg +intent(out)+deref(pointer)
  mixin_names:
  - '  f_mixin_dummy_arg'
  - '  f_mixin_cdesc_char_pointer'
  - '  f_mixin_cdesc_pass_to_cwrapper'
  - '  c_mixin_cdesc_fill_char'
  - '    c_mixin_header_cstring'
  - '  c_mixin_local-cvar-ptr'
  - '  c_mixin_arg-call-cvar-address'
  f_dummy_arg:
  - '{f_var}'
  f_dummy_decl:
  - 'character(len=:){f_intent_attr}{f_deref_attr} :: {f_var}'
  f_local_decl:
  - 'type({F_array_type}) :: {f_var_cdesc}'
  f_arg_call:
  - '{f_var_cdesc}'
  f_post_call:
  - "call {f_helper_pointer_string}(\t{f_var_cdesc},\t {f_var})"
  f_temps:
  - cdesc
  f_need_wrapper: true
  i_dummy_arg:
  - '{i_var_cdesc}'
  i_dummy_decl:
  - 'type({F_array_type}), intent(OUT) :: {i_var_cdesc}'
  i_import:
  - '{F_array_type}'
  c_prototype:
  - '{C_array_type} *{c_var_cdesc}'
  c_pre_call:
  - '{c_const}{c_type} *{c_var};'
  c_arg_call:
  - '&{cxx_var}'
  c_post_call:
  - "{c_var_cdesc}->base_addr =\t const_cast<char *>(\t{cxx_var});"
  - '{c_var_cdesc}->type = {typemap.sh_type};'
  - '{c_var_cdesc}->elem_len = {cxx_var} == {nullptr} ? 0 : {stdlib}strlen({cxx_var});'
  - '{c_var_cdesc}->size = 1;'
  - '{c_var_cdesc}->rank = 0;'
  c_temps:
  - cdesc
  helper:
  - pointer_string
  - array_context
  - type_defines
  impl_header:
  - <cstring>

The final Fortran wrapper become:

subroutine fetch_char_ptr_library(outstr)
    character(len=:), intent(OUT), pointer :: outstr
    ! splicer begin function.fetch_char_ptr_library
    type(CHA_SHROUD_array) :: SHT_outstr_cdesc
    call c_fetch_char_ptr_library_bufferify(SHT_outstr_cdesc)
    call CHA_SHROUD_pointer_string(SHT_outstr_cdesc, outstr)
    ! splicer end function.fetch_char_ptr_library
end subroutine fetch_char_ptr_library

With an interface:

interface
    subroutine c_fetch_char_ptr_library(outstr) &
            bind(C, name="CHA_fetchCharPtrLibrary")
        use iso_c_binding, only : C_PTR
        implicit none
        type(C_PTR), intent(OUT) :: outstr
    end subroutine c_fetch_char_ptr_library
end interface

And the C wrapper:

void CHA_fetchCharPtrLibrary_bufferify(
    CHA_SHROUD_array *SHT_outstr_cdesc)
{
    // splicer begin function.fetchCharPtrLibrary_bufferify
    char *outstr;
    fetchCharPtrLibrary(&outstr);
    SHT_outstr_cdesc->base_addr = const_cast<char *>(outstr);
    SHT_outstr_cdesc->type = SH_TYPE_CHAR;
    SHT_outstr_cdesc->elem_len = outstr == nullptr ? 0 : std::strlen(outstr);
    SHT_outstr_cdesc->size = 1;
    SHT_outstr_cdesc->rank = 0;
    // splicer end function.fetchCharPtrLibrary_bufferify
}

A Fortran helper function is also added to properly define the LEN of the CHARACTER argument.

    ! helper pointer_string
    ! Assign context to an assumed-length character pointer
    subroutine CHA_SHROUD_pointer_string(context, var)
        use iso_c_binding, only : c_associated, c_f_pointer, C_PTR
        implicit none
        type(CHA_SHROUD_array), intent(IN) :: context
        character(len=:), pointer, intent(OUT) :: var
        character(len=context%elem_len), pointer :: fptr
        if (c_associated(context%base_addr)) then
            call c_f_pointer(context%base_addr, fptr)
            var => fptr
        else
            nullify(var)
        endif
    end subroutine CHA_SHROUD_pointer_string